WIP: add a simulator #3

Draft
programmerjake wants to merge 36 commits from adding-simulator into master
29 changed files with 15382 additions and 117 deletions

View file

@ -38,7 +38,7 @@ jobs:
z3 \
zlib1g-dev
- run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.80.1
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.82.0
source "$HOME/.cargo/env"
echo "$PATH" >> "$GITHUB_PATH"
- uses: https://code.forgejo.org/actions/cache/restore@v3
@ -57,4 +57,5 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/master' }}
- run: cargo test
- run: cargo build --tests --features=unstable-doc
- run: cargo test --doc --features=unstable-doc
- run: cargo doc --features=unstable-doc

32
Cargo.lock generated
View file

@ -315,10 +315,12 @@ dependencies = [
"num-bigint",
"num-traits",
"os_pipe",
"petgraph",
"serde",
"serde_json",
"tempfile",
"trybuild",
"vec_map",
"which",
]
@ -357,6 +359,12 @@ dependencies = [
"thiserror",
]
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "funty"
version = "2.0.0"
@ -423,9 +431,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "2.2.6"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown",
@ -472,11 +480,10 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "num-bigint"
version = "0.4.4"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
@ -515,6 +522,15 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "petgraph"
version = "0.6.5"
source = "git+https://github.com/programmerjake/petgraph.git?rev=258ea8071209a924b73fe96f9f87a3b7b45cbc9f#258ea8071209a924b73fe96f9f87a3b7b45cbc9f"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]]
name = "prettyplease"
version = "0.2.20"
@ -720,6 +736,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"

View file

@ -11,7 +11,7 @@ edition = "2021"
repository = "https://git.libre-chip.org/libre-chip/fayalite"
keywords = ["hdl", "hardware", "semiconductors", "firrtl", "fpga"]
categories = ["simulation", "development-tools", "compilers"]
rust-version = "1.80.1"
rust-version = "1.82.0"
[workspace.dependencies]
fayalite-proc-macros = { version = "=0.2.1", path = "crates/fayalite-proc-macros" }
@ -24,11 +24,13 @@ clap = { version = "4.5.9", features = ["derive", "env", "string"] }
ctor = "0.2.8"
eyre = "0.6.12"
hashbrown = "0.14.3"
indexmap = { version = "2.2.6", features = ["serde"] }
indexmap = { version = "2.5.0", features = ["serde"] }
jobslot = "0.2.19"
num-bigint = "0.4.4"
num-bigint = "0.4.6"
num-traits = "0.2.16"
os_pipe = "1.2.1"
# TODO: switch back to crates.io once petgraph accepts PR #684 and releases a new version
petgraph = { git = "https://github.com/programmerjake/petgraph.git", rev = "258ea8071209a924b73fe96f9f87a3b7b45cbc9f" }
prettyplease = "0.2.20"
proc-macro2 = "1.0.83"
quote = "1.0.36"
@ -39,4 +41,5 @@ syn = { version = "2.0.66", features = ["full", "fold", "visit", "extra-traits"]
tempfile = "3.10.1"
thiserror = "1.0.61"
trybuild = "1.0"
vec_map = "0.8.2"
which = "6.0.1"

View file

@ -2044,6 +2044,7 @@ pub(crate) mod known_items {
impl_known_item!(::fayalite::int::Size);
impl_known_item!(::fayalite::int::UInt);
impl_known_item!(::fayalite::int::UIntType);
impl_known_item!(::fayalite::reset::ResetType);
impl_known_item!(::fayalite::ty::CanonicalType);
impl_known_item!(::fayalite::ty::StaticType);
impl_known_item!(::fayalite::ty::Type);
@ -2239,6 +2240,7 @@ impl_bounds! {
EnumType,
IntType,
KnownSize,
ResetType,
Size,
StaticType,
Type,
@ -2252,6 +2254,7 @@ impl_bounds! {
BundleType,
EnumType,
IntType,
ResetType,
StaticType,
Type,
}
@ -2264,6 +2267,7 @@ impl From<ParsedTypeBound> for ParsedBound {
ParsedTypeBound::BundleType(v) => ParsedBound::BundleType(v),
ParsedTypeBound::EnumType(v) => ParsedBound::EnumType(v),
ParsedTypeBound::IntType(v) => ParsedBound::IntType(v),
ParsedTypeBound::ResetType(v) => ParsedBound::ResetType(v),
ParsedTypeBound::StaticType(v) => ParsedBound::StaticType(v),
ParsedTypeBound::Type(v) => ParsedBound::Type(v),
}
@ -2277,6 +2281,7 @@ impl From<ParsedTypeBounds> for ParsedBounds {
BundleType,
EnumType,
IntType,
ResetType,
StaticType,
Type,
} = value;
@ -2286,6 +2291,7 @@ impl From<ParsedTypeBounds> for ParsedBounds {
EnumType,
IntType,
KnownSize: None,
ResetType,
Size: None,
StaticType,
Type,
@ -2314,6 +2320,11 @@ impl ParsedTypeBound {
ParsedTypeBound::BoolOrIntType(known_items::BoolOrIntType(span)),
ParsedTypeBound::Type(known_items::Type(span)),
]),
Self::ResetType(v) => ParsedTypeBounds::from_iter([
ParsedTypeBound::from(v),
ParsedTypeBound::StaticType(known_items::StaticType(span)),
ParsedTypeBound::Type(known_items::Type(span)),
]),
Self::StaticType(v) => ParsedTypeBounds::from_iter([
ParsedTypeBound::from(v),
ParsedTypeBound::Type(known_items::Type(span)),
@ -2349,6 +2360,7 @@ impl From<ParsedSizeTypeBounds> for ParsedBounds {
EnumType: None,
IntType: None,
KnownSize,
ResetType: None,
Size,
StaticType: None,
Type: None,
@ -2425,6 +2437,7 @@ impl ParsedBound {
Self::EnumType(v) => ParsedBoundCategory::Type(ParsedTypeBound::EnumType(v)),
Self::IntType(v) => ParsedBoundCategory::Type(ParsedTypeBound::IntType(v)),
Self::KnownSize(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::KnownSize(v)),
Self::ResetType(v) => ParsedBoundCategory::Type(ParsedTypeBound::ResetType(v)),
Self::Size(v) => ParsedBoundCategory::SizeType(ParsedSizeTypeBound::Size(v)),
Self::StaticType(v) => ParsedBoundCategory::Type(ParsedTypeBound::StaticType(v)),
Self::Type(v) => ParsedBoundCategory::Type(ParsedTypeBound::Type(v)),
@ -3310,7 +3323,8 @@ impl ParsedGenerics {
ParsedTypeBound::BoolOrIntType(_)
| ParsedTypeBound::BundleType(_)
| ParsedTypeBound::EnumType(_)
| ParsedTypeBound::IntType(_) => {
| ParsedTypeBound::IntType(_)
| ParsedTypeBound::ResetType(_) => {
errors.error(bound, "bound on mask type not implemented");
}
ParsedTypeBound::StaticType(bound) => {

View file

@ -25,9 +25,11 @@ jobslot.workspace = true
num-bigint.workspace = true
num-traits.workspace = true
os_pipe.workspace = true
petgraph.workspace = true
serde_json.workspace = true
serde.workspace = true
tempfile.workspace = true
vec_map.workspace = true
which.workspace = true
[dev-dependencies]

View file

@ -4,7 +4,7 @@ use crate::{
expr::{Expr, ToExpr},
hdl,
int::Bool,
reset::Reset,
reset::{Reset, ResetType},
source_location::SourceLocation,
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
};
@ -88,9 +88,9 @@ impl ToClock for Expr<Clock> {
}
#[hdl]
pub struct ClockDomain {
pub struct ClockDomain<R: ResetType = Reset> {
pub clk: Clock,
pub rst: Reset,
pub rst: R,
}
impl ToClock for bool {

View file

@ -17,6 +17,7 @@ use crate::{
Instance, ModuleIO,
},
reg::Reg,
reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset},
ty::{CanonicalType, StaticType, Type, TypeWithDeref},
wire::Wire,
};
@ -209,7 +210,9 @@ expr_enum! {
ModuleIO(ModuleIO<CanonicalType>),
Instance(Instance<Bundle>),
Wire(Wire<CanonicalType>),
Reg(Reg<CanonicalType>),
Reg(Reg<CanonicalType, Reset>),
RegSync(Reg<CanonicalType, SyncReset>),
RegAsync(Reg<CanonicalType, AsyncReset>),
MemPort(MemPort<DynPortType>),
}
}
@ -593,25 +596,42 @@ impl<T: Type> GetTarget for Wire<T> {
}
}
impl<T: Type> ToExpr for Reg<T> {
impl<T: Type, R: ResetType> ToExpr for Reg<T, R> {
type Type = T;
fn to_expr(&self) -> Expr<Self::Type> {
struct Dispatch;
impl ResetTypeDispatch for Dispatch {
type Input<T: ResetType> = Reg<CanonicalType, T>;
type Output<T: ResetType> = ExprEnum;
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset> {
ExprEnum::Reg(input)
}
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset> {
ExprEnum::RegSync(input)
}
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset> {
ExprEnum::RegAsync(input)
}
}
Expr {
__enum: ExprEnum::Reg(self.canonical()).intern_sized(),
__enum: R::dispatch(self.canonical(), Dispatch).intern_sized(),
__ty: self.ty(),
__flow: self.flow(),
}
}
}
impl<T: Type> ToLiteralBits for Reg<T> {
impl<T: Type, R: ResetType> ToLiteralBits for Reg<T, R> {
fn to_literal_bits(&self) -> Result<Interned<BitSlice>, NotALiteralExpr> {
Err(NotALiteralExpr)
}
}
impl<T: Type> GetTarget for Reg<T> {
impl<T: Type, R: ResetType> GetTarget for Reg<T, R> {
fn target(&self) -> Option<Interned<Target>> {
Some(Intern::intern_sized(self.canonical().into()))
}

View file

@ -19,7 +19,10 @@ use crate::{
UIntType, UIntValue,
},
intern::{Intern, Interned},
reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset},
reset::{
AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset, ToAsyncReset, ToReset,
ToSyncReset,
},
ty::{CanonicalType, StaticType, Type},
util::ConstUsize,
};
@ -1773,11 +1776,11 @@ impl_cast_bit_op!(CastSIntToAsyncReset, SInt<1>, #[dyn] SInt, AsyncReset, #[trai
impl_cast_bit_op!(CastSyncResetToBool, SyncReset, Bool);
impl_cast_bit_op!(CastSyncResetToUInt, SyncReset, UInt<1>, #[dyn] UInt);
impl_cast_bit_op!(CastSyncResetToSInt, SyncReset, SInt<1>, #[dyn] SInt);
impl_cast_bit_op!(CastSyncResetToReset, SyncReset, Reset, #[trait] ToReset::to_reset);
impl_cast_bit_op!(CastSyncResetToReset, SyncReset, Reset);
impl_cast_bit_op!(CastAsyncResetToBool, AsyncReset, Bool);
impl_cast_bit_op!(CastAsyncResetToUInt, AsyncReset, UInt<1>, #[dyn] UInt);
impl_cast_bit_op!(CastAsyncResetToSInt, AsyncReset, SInt<1>, #[dyn] SInt);
impl_cast_bit_op!(CastAsyncResetToReset, AsyncReset, Reset, #[trait] ToReset::to_reset);
impl_cast_bit_op!(CastAsyncResetToReset, AsyncReset, Reset);
impl_cast_bit_op!(CastResetToBool, Reset, Bool);
impl_cast_bit_op!(CastResetToUInt, Reset, UInt<1>, #[dyn] UInt);
impl_cast_bit_op!(CastResetToSInt, Reset, SInt<1>, #[dyn] SInt);
@ -1788,6 +1791,107 @@ impl_cast_bit_op!(CastClockToBool, Clock, Bool);
impl_cast_bit_op!(CastClockToUInt, Clock, UInt<1>, #[dyn] UInt);
impl_cast_bit_op!(CastClockToSInt, Clock, SInt<1>, #[dyn] SInt);
impl<T: ResetType> ToReset for Expr<T> {
fn to_reset(&self) -> Expr<Reset> {
struct Dispatch;
impl ResetTypeDispatch for Dispatch {
type Input<T: ResetType> = Expr<T>;
type Output<T: ResetType> = Expr<Reset>;
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset> {
input
}
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset> {
input.cast_to_static()
}
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset> {
input.cast_to_static()
}
}
T::dispatch(*self, Dispatch)
}
}
impl ExprCastTo<AsyncReset> for AsyncReset {
fn cast_to(src: Expr<Self>, _to_type: AsyncReset) -> Expr<AsyncReset> {
src
}
}
impl ExprCastTo<SyncReset> for AsyncReset {
fn cast_to(src: Expr<Self>, to_type: SyncReset) -> Expr<SyncReset> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<Clock> for AsyncReset {
fn cast_to(src: Expr<Self>, to_type: Clock) -> Expr<Clock> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<AsyncReset> for SyncReset {
fn cast_to(src: Expr<Self>, to_type: AsyncReset) -> Expr<AsyncReset> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<SyncReset> for SyncReset {
fn cast_to(src: Expr<Self>, _to_type: SyncReset) -> Expr<SyncReset> {
src
}
}
impl ExprCastTo<Clock> for SyncReset {
fn cast_to(src: Expr<Self>, to_type: Clock) -> Expr<Clock> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<AsyncReset> for Reset {
fn cast_to(src: Expr<Self>, to_type: AsyncReset) -> Expr<AsyncReset> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<SyncReset> for Reset {
fn cast_to(src: Expr<Self>, to_type: SyncReset) -> Expr<SyncReset> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<Reset> for Reset {
fn cast_to(src: Expr<Self>, _to_type: Reset) -> Expr<Reset> {
src
}
}
impl ExprCastTo<Clock> for Reset {
fn cast_to(src: Expr<Self>, to_type: Clock) -> Expr<Clock> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<AsyncReset> for Clock {
fn cast_to(src: Expr<Self>, to_type: AsyncReset) -> Expr<AsyncReset> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<SyncReset> for Clock {
fn cast_to(src: Expr<Self>, to_type: SyncReset) -> Expr<SyncReset> {
src.cast_to(Bool).cast_to(to_type)
}
}
impl ExprCastTo<Clock> for Clock {
fn cast_to(src: Expr<Self>, _to_type: Clock) -> Expr<Clock> {
src
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct FieldAccess<FieldType: Type = CanonicalType> {
base: Expr<Bundle>,

View file

@ -8,13 +8,14 @@ use crate::{
memory::{DynPortType, MemPort},
module::{Instance, ModuleIO, TargetName},
reg::Reg,
reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset},
source_location::SourceLocation,
ty::{CanonicalType, Type},
wire::Wire,
};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathBundleField {
pub name: Interned<str>,
}
@ -25,7 +26,7 @@ impl fmt::Display for TargetPathBundleField {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathArrayElement {
pub index: usize,
}
@ -36,7 +37,7 @@ impl fmt::Display for TargetPathArrayElement {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct TargetPathDynArrayElement {}
impl fmt::Display for TargetPathDynArrayElement {
@ -45,7 +46,7 @@ impl fmt::Display for TargetPathDynArrayElement {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum TargetPathElement {
BundleField(TargetPathBundleField),
ArrayElement(TargetPathArrayElement),
@ -127,6 +128,7 @@ macro_rules! impl_target_base {
$(#[$enum_meta:meta])*
$enum_vis:vis enum $TargetBase:ident {
$(
$(#[from = $from:ident])?
#[is = $is_fn:ident]
#[to = $to_fn:ident]
$(#[$variant_meta:meta])*
@ -150,19 +152,19 @@ macro_rules! impl_target_base {
}
}
$(
$($(
impl From<$VariantTy> for $TargetBase {
fn from(value: $VariantTy) -> Self {
fn $from(value: $VariantTy) -> Self {
Self::$Variant(value)
}
}
impl From<$VariantTy> for Target {
fn from(value: $VariantTy) -> Self {
fn $from(value: $VariantTy) -> Self {
$TargetBase::$Variant(value).into()
}
}
)*
)*)?
impl $TargetBase {
$(
@ -197,26 +199,65 @@ macro_rules! impl_target_base {
}
impl_target_base! {
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum TargetBase {
#[from = from]
#[is = is_module_io]
#[to = module_io]
ModuleIO(ModuleIO<CanonicalType>),
#[from = from]
#[is = is_mem_port]
#[to = mem_port]
MemPort(MemPort<DynPortType>),
#[is = is_reg]
#[to = reg]
Reg(Reg<CanonicalType>),
Reg(Reg<CanonicalType, Reset>),
#[is = is_reg_sync]
#[to = reg_sync]
RegSync(Reg<CanonicalType, SyncReset>),
#[is = is_reg_async]
#[to = reg_async]
RegAsync(Reg<CanonicalType, AsyncReset>),
#[from = from]
#[is = is_wire]
#[to = wire]
Wire(Wire<CanonicalType>),
#[from = from]
#[is = is_instance]
#[to = instance]
Instance(Instance<Bundle>),
}
}
impl<R: ResetType> From<Reg<CanonicalType, R>> for TargetBase {
fn from(value: Reg<CanonicalType, R>) -> Self {
struct Dispatch;
impl ResetTypeDispatch for Dispatch {
type Input<T: ResetType> = Reg<CanonicalType, T>;
type Output<T: ResetType> = TargetBase;
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset> {
TargetBase::Reg(input)
}
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset> {
TargetBase::RegSync(input)
}
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset> {
TargetBase::RegAsync(input)
}
}
R::dispatch(value, Dispatch)
}
}
impl<R: ResetType> From<Reg<CanonicalType, R>> for Target {
fn from(value: Reg<CanonicalType, R>) -> Self {
TargetBase::from(value).into()
}
}
impl fmt::Display for TargetBase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.target_name())
@ -229,6 +270,8 @@ impl TargetBase {
TargetBase::ModuleIO(v) => TargetName(v.scoped_name(), None),
TargetBase::MemPort(v) => TargetName(v.mem_name(), Some(v.port_name())),
TargetBase::Reg(v) => TargetName(v.scoped_name(), None),
TargetBase::RegSync(v) => TargetName(v.scoped_name(), None),
TargetBase::RegAsync(v) => TargetName(v.scoped_name(), None),
TargetBase::Wire(v) => TargetName(v.scoped_name(), None),
TargetBase::Instance(v) => TargetName(v.scoped_name(), None),
}
@ -238,6 +281,8 @@ impl TargetBase {
TargetBase::ModuleIO(v) => v.ty(),
TargetBase::MemPort(v) => v.ty().canonical(),
TargetBase::Reg(v) => v.ty(),
TargetBase::RegSync(v) => v.ty(),
TargetBase::RegAsync(v) => v.ty(),
TargetBase::Wire(v) => v.ty(),
TargetBase::Instance(v) => v.ty().canonical(),
}
@ -313,7 +358,7 @@ impl TargetChild {
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum Target {
Base(Interned<TargetBase>),
Child(TargetChild),

View file

@ -31,7 +31,7 @@ use crate::{
StmtConnect, StmtDeclaration, StmtFormal, StmtIf, StmtInstance, StmtMatch, StmtReg,
StmtWire,
},
reset::{AsyncReset, Reset, SyncReset},
reset::{AsyncReset, Reset, ResetType, SyncReset},
source_location::SourceLocation,
ty::{CanonicalType, Type},
util::{
@ -1739,6 +1739,14 @@ impl<'a> Exporter<'a> {
assert!(!const_ty, "not a constant");
self.module.ns.get(expr.scoped_name().1).to_string()
}
ExprEnum::RegSync(expr) => {
assert!(!const_ty, "not a constant");
self.module.ns.get(expr.scoped_name().1).to_string()
}
ExprEnum::RegAsync(expr) => {
assert!(!const_ty, "not a constant");
self.module.ns.get(expr.scoped_name().1).to_string()
}
ExprEnum::MemPort(expr) => {
assert!(!const_ty, "not a constant");
let mem_name = self.module.ns.get(expr.mem_name().1);
@ -1848,6 +1856,8 @@ impl<'a> Exporter<'a> {
self.module.ns.get(v.mem_name().1)
}
TargetBase::Reg(v) => self.module.ns.get(v.name_id()),
TargetBase::RegSync(v) => self.module.ns.get(v.name_id()),
TargetBase::RegAsync(v) => self.module.ns.get(v.name_id()),
TargetBase::Wire(v) => self.module.ns.get(v.name_id()),
TargetBase::Instance(v) => self.module.ns.get(v.name_id()),
};
@ -1956,6 +1966,37 @@ impl<'a> Exporter<'a> {
drop(memory_indent);
Ok(body)
}
fn stmt_reg<R: ResetType>(
&mut self,
stmt_reg: StmtReg<R>,
module_name: Ident,
definitions: &RcDefinitions,
body: &mut String,
) {
let StmtReg { annotations, reg } = stmt_reg;
let indent = self.indent;
self.targeted_annotations(module_name, vec![], &annotations);
let name = self.module.ns.get(reg.name_id());
let ty = self.type_state.ty(reg.ty());
let clk = self.expr(Expr::canonical(reg.clock_domain().clk), definitions, false);
if let Some(init) = reg.init() {
let rst = self.expr(Expr::canonical(reg.clock_domain().rst), definitions, false);
let init = self.expr(init, definitions, false);
writeln!(
body,
"{indent}regreset {name}: {ty}, {clk}, {rst}, {init}{}",
FileInfo::new(reg.source_location()),
)
.unwrap();
} else {
writeln!(
body,
"{indent}reg {name}: {ty}, {clk}{}",
FileInfo::new(reg.source_location()),
)
.unwrap();
}
}
fn block(
&mut self,
module: Interned<Module<Bundle>>,
@ -2126,30 +2167,14 @@ impl<'a> Exporter<'a> {
)
.unwrap();
}
Stmt::Declaration(StmtDeclaration::Reg(StmtReg { annotations, reg })) => {
self.targeted_annotations(module_name, vec![], &annotations);
let name = self.module.ns.get(reg.name_id());
let ty = self.type_state.ty(reg.ty());
let clk =
self.expr(Expr::canonical(reg.clock_domain().clk), &definitions, false);
if let Some(init) = reg.init() {
let rst =
self.expr(Expr::canonical(reg.clock_domain().rst), &definitions, false);
let init = self.expr(init, &definitions, false);
writeln!(
body,
"{indent}regreset {name}: {ty}, {clk}, {rst}, {init}{}",
FileInfo::new(reg.source_location()),
)
.unwrap();
} else {
writeln!(
body,
"{indent}reg {name}: {ty}, {clk}{}",
FileInfo::new(reg.source_location()),
)
.unwrap();
}
Stmt::Declaration(StmtDeclaration::Reg(stmt_reg)) => {
self.stmt_reg(stmt_reg, module_name, &definitions, &mut body);
}
Stmt::Declaration(StmtDeclaration::RegSync(stmt_reg)) => {
self.stmt_reg(stmt_reg, module_name, &definitions, &mut body);
}
Stmt::Declaration(StmtDeclaration::RegAsync(stmt_reg)) => {
self.stmt_reg(stmt_reg, module_name, &definitions, &mut body);
}
Stmt::Declaration(StmtDeclaration::Instance(StmtInstance {
annotations,

View file

@ -227,12 +227,29 @@ macro_rules! impl_int {
impl<Width: Size> BoolOrIntType for $name<Width> {
type Width = Width;
type Signed = ConstBool<$SIGNED>;
type Value = $value<Width>;
fn width(self) -> usize {
$name::width(self)
}
fn new(width: Width::SizeType) -> Self {
$name { width }
}
fn value_from_bigint_wrapping(self, v: BigInt) -> Self::Value {
$value::<Width>::from_bigint_wrapping(self, v)
}
fn bits_to_value(bits: Cow<'_, BitSlice>) -> Self::Value {
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
struct MemoizeBitsToValue;
impl Memoize for MemoizeBitsToValue {
type Input = BitSlice;
type InputOwned = BitVec;
type Output = Arc<BitVec>;
fn inner(self, input: &Self::Input) -> Self::Output {
Arc::new(input.to_bitvec())
}
}
$value::new(MemoizeBitsToValue.get_cow(bits))
}
fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr<Self> {
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
struct MemoizeBitsToExpr;
@ -334,6 +351,24 @@ macro_rules! impl_int {
}
}
impl<Width: Size> PartialOrd for $value<Width> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<Width: Size> Ord for $value<Width> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.to_bigint().cmp(&other.to_bigint())
}
}
impl<Width: Size> From<$value<Width>> for BigInt {
fn from(v: $value<Width>) -> BigInt {
v.to_bigint()
}
}
impl<Width: Size> $value<Width> {
pub fn width(&self) -> usize {
if let Some(retval) = Width::KNOWN_VALUE {
@ -485,6 +520,19 @@ macro_rules! impl_prim_int {
$(#[$meta:meta])*
$prim_int:ident, $ty:ty
) => {
impl From<$prim_int> for <$ty as BoolOrIntType>::Value {
fn from(v: $prim_int) -> Self {
<$ty>::le_bytes_to_value_wrapping(
&v.to_le_bytes(),
<$ty as BoolOrIntType>::Width::VALUE,
)
}
}
impl From<NonZero<$prim_int>> for <$ty as BoolOrIntType>::Value {
fn from(v: NonZero<$prim_int>) -> Self {
v.get().into()
}
}
$(#[$meta])*
impl ToExpr for $prim_int {
type Type = $ty;
@ -501,10 +549,7 @@ macro_rules! impl_prim_int {
type Type = $ty;
fn to_expr(&self) -> Expr<Self::Type> {
<$ty>::le_bytes_to_expr_wrapping(
&self.get().to_le_bytes(),
<$ty as BoolOrIntType>::Width::VALUE,
)
self.get().to_expr()
}
}
};
@ -534,6 +579,15 @@ impl_prim_int!(
pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
type Width: Size;
type Signed: GenericConstBool;
type Value: Clone
+ Ord
+ std::hash::Hash
+ fmt::Debug
+ Send
+ Sync
+ 'static
+ ToExpr<Type = Self>
+ Into<BigInt>;
fn width(self) -> usize;
fn new(width: <Self::Width as Size>::SizeType) -> Self;
fn new_static() -> Self
@ -548,6 +602,10 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
fn as_same_width_uint(self) -> UIntType<Self::Width> {
UIntType::new(Self::Width::from_usize(self.width()))
}
fn value_from_int_wrapping(self, v: impl Into<BigInt>) -> Self::Value {
self.value_from_bigint_wrapping(v.into())
}
fn value_from_bigint_wrapping(self, v: BigInt) -> Self::Value;
fn bits_from_bigint_wrapping(self, v: BigInt) -> BitVec {
let width = self.width();
let mut bytes = v.to_signed_bytes_le();
@ -570,8 +628,9 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
BitSlice::<u8, Lsb0>::from_slice_mut(&mut bytes)[..bits.len()].clone_from_bitslice(bits);
BigInt::from_signed_bytes_le(&bytes)
}
fn bits_to_value(bits: Cow<'_, BitSlice>) -> Self::Value;
fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr<Self>;
fn le_bytes_to_expr_wrapping(bytes: &[u8], bit_width: usize) -> Expr<Self> {
fn le_bytes_to_bits_wrapping(bytes: &[u8], bit_width: usize) -> BitVec {
let bitslice = BitSlice::<u8, Lsb0>::from_slice(bytes);
let bitslice = &bitslice[..bit_width.min(bitslice.len())];
let mut bits = BitVec::new();
@ -580,7 +639,17 @@ pub trait BoolOrIntType: Type + sealed::BoolOrIntTypeSealed {
bit_width,
Self::Signed::VALUE && bits.last().as_deref().copied().unwrap_or(false),
);
Self::bits_to_expr(Cow::Owned(bits))
bits
}
fn le_bytes_to_expr_wrapping(bytes: &[u8], bit_width: usize) -> Expr<Self> {
Self::bits_to_expr(Cow::Owned(Self::le_bytes_to_bits_wrapping(
bytes, bit_width,
)))
}
fn le_bytes_to_value_wrapping(bytes: &[u8], bit_width: usize) -> Self::Value {
Self::bits_to_value(Cow::Owned(Self::le_bytes_to_bits_wrapping(
bytes, bit_width,
)))
}
}
@ -632,6 +701,7 @@ impl sealed::BoolOrIntTypeSealed for Bool {}
impl BoolOrIntType for Bool {
type Width = ConstUsize<1>;
type Signed = ConstBool<false>;
type Value = bool;
fn width(self) -> usize {
1
@ -642,10 +712,19 @@ impl BoolOrIntType for Bool {
Bool
}
fn value_from_bigint_wrapping(self, v: BigInt) -> Self::Value {
v.bit(0)
}
fn bits_to_expr(bits: Cow<'_, BitSlice>) -> Expr<Self> {
assert_eq!(bits.len(), 1);
bits[0].to_expr()
}
fn bits_to_value(bits: Cow<'_, BitSlice>) -> Self::Value {
assert_eq!(bits.len(), 1);
bits[0]
}
}
impl Bool {

View file

@ -46,6 +46,7 @@ pub mod module;
pub mod prelude;
pub mod reg;
pub mod reset;
pub mod sim;
pub mod source_location;
pub mod testing;
pub mod ty;

View file

@ -20,6 +20,7 @@ use crate::{
intern::{Intern, Interned},
memory::{Mem, MemBuilder, MemBuilderTarget, PortName},
reg::Reg,
reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset},
source_location::SourceLocation,
ty::{CanonicalType, Type},
util::ScopedRef,
@ -180,7 +181,7 @@ impl Block {
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct StmtConnect {
pub lhs: Expr<CanonicalType>,
pub rhs: Expr<CanonicalType>,
@ -235,7 +236,7 @@ impl fmt::Debug for StmtConnect {
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct StmtFormal {
pub kind: FormalKind,
pub clk: Expr<Clock>,
@ -284,6 +285,8 @@ pub struct StmtIf<S: ModuleBuildingStatus = ModuleBuilt> {
pub blocks: [S::Block; 2],
}
impl Copy for StmtIf {}
impl<S: ModuleBuildingStatus> StmtIf<S> {
pub fn then_block(&self) -> S::Block {
self.blocks[0]
@ -315,6 +318,8 @@ pub struct StmtMatch<S: ModuleBuildingStatus = ModuleBuilt> {
pub blocks: Interned<[S::Block]>,
}
impl Copy for StmtMatch {}
impl StmtMatch {
#[track_caller]
fn assert_validity(&self) {
@ -346,7 +351,7 @@ macro_rules! wrapper_enum {
$(#[$enum_meta:meta])*
$vis:vis enum $enum_name:ident<$T_enum:ident: $T_bound:ident = $T_enum_default:ident> {
$(
#[is = $is_fn:ident, as_ref = $as_ref_fn:ident]
#[is = $is_fn:ident, as_ref = $as_ref_fn:ident $(, from = $from:ident)?]
$(#[$variant_meta:meta])*
$Variant:ident($VariantTy:ty),
)*
@ -358,7 +363,7 @@ macro_rules! wrapper_enum {
$(#[$enum_meta])*
$vis enum $enum_name<$T_enum: $T_bound = $T_enum_default> {
$(
#[is = $is_fn, as_ref = $as_ref_fn]
#[is = $is_fn, as_ref = $as_ref_fn $(, from = $from)?]
$(#[$variant_meta])*
$Variant($VariantTy),
)*
@ -385,7 +390,7 @@ macro_rules! wrapper_enum {
$(#[$enum_meta:meta])*
$vis:vis enum $enum_name:ident<$T_enum:ident: $T_bound:ident = $T_enum_default:ident> {
$(
#[is = $is_fn:ident, as_ref = $as_ref_fn:ident]
#[is = $is_fn:ident, as_ref = $as_ref_fn:ident $(, from = $from:ident)?]
$(#[$variant_meta:meta])*
$Variant:ident($VariantTy:ty),
)*
@ -397,22 +402,22 @@ macro_rules! wrapper_enum {
$(#[$enum_meta])*
$vis enum $enum_name<$T_enum: $T_bound = $T_enum_default> {
$(
#[is = $is_fn, as_ref = $as_ref_fn]
#[is = $is_fn, as_ref = $as_ref_fn $(, from = $from)?]
$(#[$variant_meta])*
$Variant($VariantTy),
)*
}
}
$(
$($(
wrapper_enum! {
impl $T_to From<$VariantTy> for $to_type {
fn from(value: $VariantTy) -> Self {
fn $from(value: $VariantTy) -> Self {
$enum_name::$Variant(value).into()
}
}
}
)*
)?)*
};
(
#[impl()]
@ -420,7 +425,7 @@ macro_rules! wrapper_enum {
$(#[$enum_meta:meta])*
$vis:vis enum $enum_name:ident<$T_enum:ident: $T_bound:ident = $T_enum_default:ident> {
$(
#[is = $is_fn:ident, as_ref = $as_ref_fn:ident]
#[is = $is_fn:ident, as_ref = $as_ref_fn:ident $(, from = $from:ident)?]
$(#[$variant_meta:meta])*
$Variant:ident($VariantTy:ty),
)*
@ -459,13 +464,15 @@ pub struct StmtWire<S: ModuleBuildingStatus = ModuleBuilt> {
pub wire: Wire<CanonicalType>,
}
impl Copy for StmtWire {}
#[derive(Hash, Clone, PartialEq, Eq, Debug)]
pub struct StmtReg<S: ModuleBuildingStatus = ModuleBuilt> {
pub struct StmtReg<R: ResetType, S: ModuleBuildingStatus = ModuleBuilt> {
pub annotations: S::StmtAnnotations,
pub reg: Reg<CanonicalType>,
pub reg: Reg<CanonicalType, R>,
}
impl Copy for StmtReg {}
impl<R: ResetType> Copy for StmtReg<R> {}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct StmtInstance<S: ModuleBuildingStatus = ModuleBuilt> {
@ -473,6 +480,8 @@ pub struct StmtInstance<S: ModuleBuildingStatus = ModuleBuilt> {
pub instance: Instance<Bundle>,
}
impl Copy for StmtInstance {}
wrapper_enum! {
#[impl(
(<S: ModuleBuildingStatus>) self: StmtDeclaration<S> = self,
@ -481,20 +490,57 @@ wrapper_enum! {
#[to((<S: ModuleBuildingStatus>) StmtDeclaration<S>, (<S: ModuleBuildingStatus>) Stmt<S>)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum StmtDeclaration<S: ModuleBuildingStatus = ModuleBuilt> {
#[is = is_wire, as_ref = wire]
#[is = is_wire, as_ref = wire, from = from]
Wire(StmtWire<S>),
#[is = is_reg, as_ref = reg]
Reg(StmtReg<S>),
#[is = is_instance, as_ref = instance]
Reg(StmtReg<Reset, S>),
#[is = is_reg_sync, as_ref = reg_sync]
RegSync(StmtReg<SyncReset, S>),
#[is = is_reg_async, as_ref = reg_async]
RegAsync(StmtReg<AsyncReset, S>),
#[is = is_instance, as_ref = instance, from = from]
Instance(StmtInstance<S>),
}
}
impl Copy for StmtDeclaration {}
impl<S: ModuleBuildingStatus, R: ResetType> From<StmtReg<R, S>> for Stmt<S> {
fn from(value: StmtReg<R, S>) -> Self {
StmtDeclaration::from(value).into()
}
}
impl<S: ModuleBuildingStatus, R: ResetType> From<StmtReg<R, S>> for StmtDeclaration<S> {
fn from(value: StmtReg<R, S>) -> Self {
struct Dispatch<S>(PhantomData<S>);
impl<S: ModuleBuildingStatus> ResetTypeDispatch for Dispatch<S> {
type Input<T: ResetType> = StmtReg<T, S>;
type Output<T: ResetType> = StmtDeclaration<S>;
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset> {
StmtDeclaration::Reg(input)
}
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset> {
StmtDeclaration::RegSync(input)
}
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset> {
StmtDeclaration::RegAsync(input)
}
}
R::dispatch(value, Dispatch(PhantomData))
}
}
impl<S: ModuleBuildingStatus> StmtDeclaration<S> {
pub fn annotations(&self) -> S::StmtAnnotations {
match self {
StmtDeclaration::Wire(v) => v.annotations,
StmtDeclaration::Reg(v) => v.annotations,
StmtDeclaration::RegSync(v) => v.annotations,
StmtDeclaration::RegAsync(v) => v.annotations,
StmtDeclaration::Instance(v) => v.annotations,
}
}
@ -502,6 +548,8 @@ impl<S: ModuleBuildingStatus> StmtDeclaration<S> {
match self {
StmtDeclaration::Wire(v) => v.wire.source_location(),
StmtDeclaration::Reg(v) => v.reg.source_location(),
StmtDeclaration::RegSync(v) => v.reg.source_location(),
StmtDeclaration::RegAsync(v) => v.reg.source_location(),
StmtDeclaration::Instance(v) => v.instance.source_location(),
}
}
@ -509,20 +557,26 @@ impl<S: ModuleBuildingStatus> StmtDeclaration<S> {
match self {
StmtDeclaration::Wire(v) => v.wire.scoped_name(),
StmtDeclaration::Reg(v) => v.reg.scoped_name(),
StmtDeclaration::RegSync(v) => v.reg.scoped_name(),
StmtDeclaration::RegAsync(v) => v.reg.scoped_name(),
StmtDeclaration::Instance(v) => v.instance.scoped_name(),
}
}
pub fn sub_stmt_blocks(&self) -> &[S::Block] {
match self {
StmtDeclaration::Wire(_) | StmtDeclaration::Reg(_) | StmtDeclaration::Instance(_) => {
&[]
}
StmtDeclaration::Wire(_)
| StmtDeclaration::Reg(_)
| StmtDeclaration::RegSync(_)
| StmtDeclaration::RegAsync(_)
| StmtDeclaration::Instance(_) => &[],
}
}
pub fn canonical_ty(&self) -> CanonicalType {
match self {
StmtDeclaration::Wire(v) => v.wire.ty(),
StmtDeclaration::Reg(v) => v.reg.ty(),
StmtDeclaration::RegSync(v) => v.reg.ty(),
StmtDeclaration::RegAsync(v) => v.reg.ty(),
StmtDeclaration::Instance(v) => CanonicalType::Bundle(v.instance.ty()),
}
}
@ -533,19 +587,21 @@ wrapper_enum! {
#[to((<S: ModuleBuildingStatus>) Stmt<S>)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Stmt<S: ModuleBuildingStatus = ModuleBuilt> {
#[is = is_connect, as_ref = connect]
#[is = is_connect, as_ref = connect, from = from]
Connect(StmtConnect),
#[is = is_formal, as_ref = formal]
#[is = is_formal, as_ref = formal, from = from]
Formal(StmtFormal),
#[is = is_if, as_ref = if_]
#[is = is_if, as_ref = if_, from = from]
If(StmtIf<S>),
#[is = is_match, as_ref = match_]
#[is = is_match, as_ref = match_, from = from]
Match(StmtMatch<S>),
#[is = is_declaration, as_ref = declaration]
#[is = is_declaration, as_ref = declaration, from = from]
Declaration(StmtDeclaration<S>),
}
}
impl Copy for Stmt {}
impl<S: ModuleBuildingStatus> Stmt<S> {
pub fn sub_stmt_blocks(&self) -> &[S::Block] {
match self {
@ -714,6 +770,18 @@ impl<T: BundleType> Instance<T> {
source_location,
}
}
pub fn from_canonical(v: Instance<Bundle>) -> Self {
let Instance {
scoped_name,
instantiated,
source_location,
} = v;
Self {
scoped_name,
instantiated: Module::from_canonical(*instantiated).intern_sized(),
source_location,
}
}
pub fn containing_module_name(self) -> Interned<str> {
self.containing_module_name_id().0
}
@ -958,6 +1026,14 @@ impl From<NormalModuleBody<ModuleBuilding>> for NormalModuleBody {
annotations: (),
reg,
}) => StmtReg { annotations, reg }.into(),
StmtDeclaration::RegSync(StmtReg {
annotations: (),
reg,
}) => StmtReg { annotations, reg }.into(),
StmtDeclaration::RegAsync(StmtReg {
annotations: (),
reg,
}) => StmtReg { annotations, reg }.into(),
StmtDeclaration::Instance(StmtInstance {
annotations: (),
instance,
@ -1661,6 +1737,14 @@ impl AssertValidityState {
annotations: _,
reg,
})) => self.insert_new_base(TargetBase::intern_sized(reg.into()), block),
Stmt::Declaration(StmtDeclaration::RegSync(StmtReg {
annotations: _,
reg,
})) => self.insert_new_base(TargetBase::intern_sized(reg.into()), block),
Stmt::Declaration(StmtDeclaration::RegAsync(StmtReg {
annotations: _,
reg,
})) => self.insert_new_base(TargetBase::intern_sized(reg.into()), block),
Stmt::Declaration(StmtDeclaration::Instance(StmtInstance {
annotations: _,
instance,
@ -1842,10 +1926,10 @@ impl<CD> RegBuilder<CD, (), ()> {
}
impl<I, T: Type> RegBuilder<(), I, T> {
pub fn clock_domain(
pub fn clock_domain<R: ResetType>(
self,
clock_domain: impl ToExpr<Type = ClockDomain>,
) -> RegBuilder<Expr<ClockDomain>, I, T> {
clock_domain: impl ToExpr<Type = ClockDomain<R>>,
) -> RegBuilder<Expr<ClockDomain<R>>, I, T> {
let Self {
name,
source_location,
@ -1863,7 +1947,7 @@ impl<I, T: Type> RegBuilder<(), I, T> {
}
}
impl<T: Type> RegBuilder<Expr<ClockDomain>, Option<Expr<T>>, T> {
impl<T: Type, R: ResetType> RegBuilder<Expr<ClockDomain<R>>, Option<Expr<T>>, T> {
#[track_caller]
pub fn build(self) -> Expr<T> {
let Self {
@ -2188,6 +2272,16 @@ pub fn annotate<T: Type>(target: Expr<T>, annotations: impl IntoAnnotations) {
reg,
}
.into(),
TargetBase::RegSync(reg) => StmtReg {
annotations: (),
reg,
}
.into(),
TargetBase::RegAsync(reg) => StmtReg {
annotations: (),
reg,
}
.into(),
TargetBase::Wire(wire) => StmtWire {
annotations: (),
wire,
@ -2629,3 +2723,50 @@ impl<T: Type> ModuleIO<T> {
self.ty
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub enum InstantiatedModule {
Base(Interned<Module<Bundle>>),
Child {
parent: Interned<InstantiatedModule>,
instance: Interned<Instance<Bundle>>,
},
}
impl InstantiatedModule {
pub fn leaf_module(self) -> Interned<Module<Bundle>> {
match self {
InstantiatedModule::Base(base) => base,
InstantiatedModule::Child { instance, .. } => instance.instantiated(),
}
}
fn write_path(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InstantiatedModule::Base(base) => fmt::Debug::fmt(&base.name_id(), f),
InstantiatedModule::Child { parent, instance } => {
parent.write_path(f)?;
write!(f, ".{}", instance.name_id())
}
}
}
}
impl fmt::Debug for InstantiatedModule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "InstantiatedModule(")?;
self.write_path(f)?;
write!(f, ": {})", self.leaf_module().name_id())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct TargetInInstantiatedModule {
pub instantiated_module: InstantiatedModule,
pub target: Target,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct ExprInInstantiatedModule<T: Type> {
pub instantiated_module: InstantiatedModule,
pub expr: Expr<T>,
}

View file

@ -1,5 +1,6 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
pub mod deduce_resets;
pub mod simplify_enums;
pub mod simplify_memories;
pub mod visit;

File diff suppressed because it is too large Load diff

View file

@ -764,7 +764,9 @@ impl Folder for State {
| ExprEnum::ModuleIO(_)
| ExprEnum::Instance(_)
| ExprEnum::Wire(_)
| ExprEnum::Reg(_) => op.default_fold(self),
| ExprEnum::Reg(_)
| ExprEnum::RegSync(_)
| ExprEnum::RegAsync(_) => op.default_fold(self),
}
}

View file

@ -29,7 +29,7 @@ use crate::{
StmtInstance, StmtMatch, StmtReg, StmtWire,
},
reg::Reg,
reset::{AsyncReset, Reset, SyncReset},
reset::{AsyncReset, Reset, ResetType, SyncReset},
source_location::SourceLocation,
ty::{CanonicalType, Type},
wire::Wire,

View file

@ -5,21 +5,22 @@ use crate::{
expr::{Expr, Flow},
intern::Interned,
module::{NameId, ScopedNameId},
reset::{Reset, ResetType},
source_location::SourceLocation,
ty::{CanonicalType, Type},
};
use std::fmt;
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Reg<T: Type> {
pub struct Reg<T: Type, R: ResetType = Reset> {
name: ScopedNameId,
source_location: SourceLocation,
ty: T,
clock_domain: Expr<ClockDomain>,
clock_domain: Expr<ClockDomain<R>>,
init: Option<Expr<T>>,
}
impl<T: Type + fmt::Debug> fmt::Debug for Reg<T> {
impl<T: Type + fmt::Debug, R: ResetType> fmt::Debug for Reg<T, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
name,
@ -37,8 +38,8 @@ impl<T: Type + fmt::Debug> fmt::Debug for Reg<T> {
}
}
impl<T: Type> Reg<T> {
pub fn canonical(&self) -> Reg<CanonicalType> {
impl<T: Type, R: ResetType> Reg<T, R> {
pub fn canonical(&self) -> Reg<CanonicalType, R> {
let Self {
name,
source_location,
@ -59,7 +60,7 @@ impl<T: Type> Reg<T> {
scoped_name: ScopedNameId,
source_location: SourceLocation,
ty: T,
clock_domain: Expr<ClockDomain>,
clock_domain: Expr<ClockDomain<R>>,
init: Option<Expr<T>>,
) -> Self {
assert!(
@ -98,7 +99,7 @@ impl<T: Type> Reg<T> {
pub fn scoped_name(&self) -> ScopedNameId {
self.name
}
pub fn clock_domain(&self) -> Expr<ClockDomain> {
pub fn clock_domain(&self) -> Expr<ClockDomain<R>> {
self.clock_domain
}
pub fn init(&self) -> Option<Expr<T>> {

View file

@ -1,8 +1,9 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
expr::{Expr, ToExpr},
int::Bool,
clock::Clock,
expr::{ops, Expr, ToExpr},
int::{Bool, SInt, UInt},
source_location::SourceLocation,
ty::{impl_match_variant_as_self, CanonicalType, StaticType, Type, TypeProperties},
};
@ -11,10 +12,33 @@ mod sealed {
pub trait ResetTypeSealed {}
}
pub trait ResetType: StaticType<MaskType = Bool> + sealed::ResetTypeSealed {}
pub trait ResetType:
StaticType<MaskType = Bool>
+ sealed::ResetTypeSealed
+ ops::ExprCastTo<Bool>
+ ops::ExprCastTo<Reset>
+ ops::ExprCastTo<SyncReset>
+ ops::ExprCastTo<AsyncReset>
+ ops::ExprCastTo<Clock>
+ ops::ExprCastTo<UInt<1>>
+ ops::ExprCastTo<SInt<1>>
+ ops::ExprCastTo<UInt>
+ ops::ExprCastTo<SInt>
{
fn dispatch<D: ResetTypeDispatch>(input: D::Input<Self>, dispatch: D) -> D::Output<Self>;
}
pub trait ResetTypeDispatch: Sized {
type Input<T: ResetType>;
type Output<T: ResetType>;
fn reset(self, input: Self::Input<Reset>) -> Self::Output<Reset>;
fn sync_reset(self, input: Self::Input<SyncReset>) -> Self::Output<SyncReset>;
fn async_reset(self, input: Self::Input<AsyncReset>) -> Self::Output<AsyncReset>;
}
macro_rules! reset_type {
($name:ident, $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal) => {
($name:ident, $(#[$impl_trait:ident])? $Trait:ident::$trait_fn:ident, $is_castable_from_bits:literal, $dispatch_fn:ident) => {
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct $name;
@ -67,7 +91,14 @@ macro_rules! reset_type {
impl sealed::ResetTypeSealed for $name {}
impl ResetType for $name {}
impl ResetType for $name {
fn dispatch<D: ResetTypeDispatch>(
input: D::Input<Self>,
dispatch: D,
) -> D::Output<Self> {
dispatch.$dispatch_fn(input)
}
}
pub trait $Trait {
fn $trait_fn(&self) -> Expr<$name>;
@ -91,20 +122,21 @@ macro_rules! reset_type {
}
}
impl $Trait for Expr<$name> {
$($impl_trait $Trait for Expr<$name> {
fn $trait_fn(&self) -> Expr<$name> {
*self
}
}
})?
};
}
reset_type!(AsyncReset, ToAsyncReset::to_async_reset, true);
reset_type!(SyncReset, ToSyncReset::to_sync_reset, true);
reset_type!(AsyncReset, #[impl] ToAsyncReset::to_async_reset, true, async_reset);
reset_type!(SyncReset, #[impl] ToSyncReset::to_sync_reset, true, sync_reset);
reset_type!(
Reset,
ToReset::to_reset,
false // Reset is not castable from bits because we don't know if it's async or sync
false, // Reset is not castable from bits because we don't know if it's async or sync
reset
);
impl ToSyncReset for bool {

4689
crates/fayalite/src/sim.rs Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,397 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use std::{
fmt,
ops::{Add, AddAssign, Sub, SubAssign},
time::Duration,
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SimInstant {
time_since_start: SimDuration,
}
impl SimInstant {
pub const fn checked_add(self, duration: SimDuration) -> Option<Self> {
let Some(time_since_start) = self.time_since_start.checked_add(duration) else {
return None;
};
Some(SimInstant { time_since_start })
}
pub const fn checked_duration_since(self, earlier: Self) -> Option<SimDuration> {
self.time_since_start.checked_sub(earlier.time_since_start)
}
pub const fn checked_sub(self, duration: SimDuration) -> Option<Self> {
let Some(time_since_start) = self.time_since_start.checked_sub(duration) else {
return None;
};
Some(SimInstant { time_since_start })
}
#[track_caller]
pub const fn duration_since(self, earlier: Self) -> SimDuration {
let Some(retval) = self.checked_duration_since(earlier) else {
panic!(
"tried to compute the duration since a later time -- durations can't be negative"
);
};
retval
}
pub const fn saturating_duration_since(self, earlier: Self) -> SimDuration {
let Some(retval) = self.checked_duration_since(earlier) else {
return SimDuration::ZERO;
};
retval
}
}
impl Add<SimDuration> for SimInstant {
type Output = SimInstant;
#[track_caller]
fn add(mut self, rhs: SimDuration) -> Self::Output {
self += rhs;
self
}
}
impl AddAssign<SimDuration> for SimInstant {
#[track_caller]
fn add_assign(&mut self, rhs: SimDuration) {
self.time_since_start += rhs;
}
}
impl Add<SimInstant> for SimDuration {
type Output = SimInstant;
#[track_caller]
fn add(self, rhs: SimInstant) -> Self::Output {
rhs.add(self)
}
}
impl Sub for SimInstant {
type Output = SimDuration;
#[track_caller]
fn sub(self, rhs: SimInstant) -> Self::Output {
self.duration_since(rhs)
}
}
impl Sub<SimDuration> for SimInstant {
type Output = SimInstant;
#[track_caller]
fn sub(self, rhs: SimDuration) -> Self::Output {
let Some(retval) = self.checked_sub(rhs) else {
panic!("SimInstant underflow");
};
retval
}
}
impl SubAssign<SimDuration> for SimInstant {
#[track_caller]
fn sub_assign(&mut self, rhs: SimDuration) {
*self = *self - rhs;
}
}
impl SimInstant {
pub const START: SimInstant = SimInstant {
time_since_start: SimDuration::ZERO,
};
}
impl fmt::Debug for SimInstant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.time_since_start.fmt(f)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SimDuration {
attos: u128,
}
impl AddAssign for SimDuration {
#[track_caller]
fn add_assign(&mut self, rhs: SimDuration) {
*self = *self + rhs;
}
}
impl Add for SimDuration {
type Output = SimDuration;
#[track_caller]
fn add(self, rhs: SimDuration) -> Self::Output {
SimDuration {
attos: self
.attos
.checked_add(rhs.attos)
.expect("overflow adding durations"),
}
}
}
impl Sub for SimDuration {
type Output = Self;
#[track_caller]
fn sub(self, rhs: Self) -> Self::Output {
SimDuration {
attos: self
.attos
.checked_add(rhs.attos)
.expect("underflow subtracting durations -- durations can't be negative"),
}
}
}
impl SubAssign for SimDuration {
#[track_caller]
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct SimDurationParts {
pub attos: u16,
pub femtos: u16,
pub picos: u16,
pub nanos: u16,
pub micros: u16,
pub millis: u16,
pub secs: u128,
}
macro_rules! impl_duration_units {
(
$(
#[unit_const = $UNIT:ident, from_units = $from_units:ident, as_units = $as_units:ident, units = $units:ident, suffix = $suffix:literal]
const $log10_units_per_sec:ident: u32 = $log10_units_per_sec_value:expr;
)*
) => {
impl SimDuration {
$(
const $log10_units_per_sec: u32 = $log10_units_per_sec_value;
pub const fn $from_units($units: u128) -> Self {
Self::from_units_helper::<{ Self::$log10_units_per_sec }>($units)
}
pub const fn $as_units(self) -> u128 {
self.attos / const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }
}
)*
pub const fn to_parts(mut self) -> SimDurationParts {
$(
let $units = self.attos / const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
self.attos %= const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
)*
SimDurationParts {
$($units: $units as _,)*
}
}
pub const fn from_parts_checked(parts: SimDurationParts) -> Option<Self> {
let attos = 0u128;
$(
let Some(product) = const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) }.checked_mul(parts.$units as u128) else {
return None;
};
let Some(attos) = attos.checked_add(product) else {
return None;
};
)*
Some(Self {
attos,
})
}
}
impl fmt::Debug for SimDuration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ilog10_attos = match self.attos.checked_ilog10() {
Some(v) => v,
None => Self::LOG10_ATTOS_PER_SEC,
};
let (suffix, int, fraction, fraction_digits) =
match Self::LOG10_ATTOS_PER_SEC.saturating_sub(ilog10_attos) {
$(
..=Self::$log10_units_per_sec => {
let divisor = const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) };
(
$suffix,
self.attos / divisor,
self.attos % divisor,
(Self::LOG10_ATTOS_PER_SEC - Self::$log10_units_per_sec) as usize,
)
},
)*
_ => unreachable!(),
};
write!(f, "{int}")?;
if fraction != 0 {
write!(f, ".{fraction:0fraction_digits$}")?;
}
write!(f, " {suffix}")
}
}
#[cfg(test)]
#[test]
fn test_duration_debug() {
$(
assert_eq!(
format!("{:?}", SimDuration::$from_units(123)),
concat!("123 ", $suffix)
);
assert_eq!(
format!("{:?}", SimDuration::$from_units(1)),
concat!("1 ", $suffix),
);
let mut v = SimDuration::$from_units(1);
if v.attos < 1 << 53 {
v.attos += 1;
assert_eq!(
format!("{v:?}"),
format!("{} {}", v.attos as f64 / 10.0f64.powf((SimDuration::LOG10_ATTOS_PER_SEC - SimDuration::$log10_units_per_sec) as f64), $suffix),
"1 {} + 1 as == {} as", $suffix, v.attos,
);
}
)*
}
};
}
impl_duration_units! {
#[unit_const = SECOND, from_units = from_secs, as_units = as_secs, units = secs, suffix = "s"]
const LOG10_SECS_PER_SEC: u32 = 0;
#[unit_const = MILLISECOND, from_units = from_millis, as_units = as_millis, units = millis, suffix = "ms"]
const LOG10_MILLIS_PER_SEC: u32 = 3;
#[unit_const = MICROSECOND, from_units = from_micros, as_units = as_micros, units = micros, suffix = "μs"]
const LOG10_MICROS_PER_SEC: u32 = 6;
#[unit_const = NANOSECOND, from_units = from_nanos, as_units = as_nanos, units = nanos, suffix = "ns"]
const LOG10_NANOS_PER_SEC: u32 = 9;
#[unit_const = PICOSECOND, from_units = from_picos, as_units = as_picos, units = picos, suffix = "ps"]
const LOG10_PICOS_PER_SEC: u32 = 12;
#[unit_const = FEMTOSECOND, from_units = from_femtos, as_units = as_femtos, units = femtos, suffix = "fs"]
const LOG10_FEMTOS_PER_SEC: u32 = 15;
#[unit_const = ATTOSECOND, from_units = from_attos, as_units = as_attos, units = attos, suffix = "as"]
const LOG10_ATTOS_PER_SEC: u32 = 18;
}
impl SimDuration {
const fn from_units_helper<const UNITS_PER_SEC: u32>(units: u128) -> Self {
let Some(attos) =
units.checked_mul(const { 10u128.pow(Self::LOG10_ATTOS_PER_SEC - UNITS_PER_SEC) })
else {
panic!("duration too big");
};
Self { attos }
}
pub const ZERO: SimDuration = SimDuration::from_secs(0);
pub const fn from_parts(parts: SimDurationParts) -> Self {
match Self::from_parts_checked(parts) {
Some(v) => v,
None => panic!("duration too big"),
}
}
pub const fn abs_diff(self, other: Self) -> Self {
Self {
attos: self.attos.abs_diff(other.attos),
}
}
pub const fn checked_add(self, rhs: Self) -> Option<Self> {
let Some(attos) = self.attos.checked_add(rhs.attos) else {
return None;
};
Some(Self { attos })
}
pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
let Some(attos) = self.attos.checked_sub(rhs.attos) else {
return None;
};
Some(Self { attos })
}
pub const fn is_zero(self) -> bool {
self.attos == 0
}
pub const fn saturating_add(self, rhs: Self) -> Self {
Self {
attos: self.attos.saturating_add(rhs.attos),
}
}
pub const fn saturating_sub(self, rhs: Self) -> Self {
Self {
attos: self.attos.saturating_sub(rhs.attos),
}
}
pub const fn checked_ilog10(self) -> Option<i32> {
let Some(ilog10_attos) = self.attos.checked_ilog10() else {
return None;
};
Some(ilog10_attos as i32 - Self::LOG10_ATTOS_PER_SEC as i32)
}
#[track_caller]
pub const fn ilog10(self) -> i32 {
let Some(retval) = self.checked_ilog10() else {
panic!("tried to take the ilog10 of 0");
};
retval
}
pub const fn checked_pow10(log10: i32, underflow_is_zero: bool) -> Option<Self> {
let Some(log10) = Self::LOG10_ATTOS_PER_SEC.checked_add_signed(log10) else {
return if log10 < 0 && underflow_is_zero {
Some(Self::ZERO)
} else {
None
};
};
let Some(attos) = 10u128.checked_pow(log10) else {
return None;
};
Some(Self { attos })
}
#[track_caller]
pub const fn pow10(log10: i32) -> Self {
let Some(retval) = Self::checked_pow10(log10, true) else {
panic!("pow10 overflowed");
};
retval
}
pub const fn is_power_of_ten(self) -> bool {
const TEN: u128 = 10;
const NUMBER_OF_POWERS_OF_TEN: usize = {
let mut n = 0;
while let Some(_) = TEN.checked_pow(n as u32) {
n += 1;
}
n
};
const POWERS_OF_TEN: [u128; NUMBER_OF_POWERS_OF_TEN] = {
let mut retval = [0; NUMBER_OF_POWERS_OF_TEN];
let mut i = 0;
while i < NUMBER_OF_POWERS_OF_TEN {
retval[i] = TEN.pow(i as u32);
i += 1;
}
retval
};
let mut i = 0;
while i < NUMBER_OF_POWERS_OF_TEN {
if self.attos == POWERS_OF_TEN[i] {
return true;
}
i += 1;
}
false
}
}
impl From<Duration> for SimDuration {
fn from(duration: Duration) -> Self {
Self::from_nanos(duration.as_nanos())
}
}

View file

@ -0,0 +1,628 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{
enum_::{Enum, EnumType},
expr::Flow,
int::UInt,
sim::{
time::{SimDuration, SimInstant},
TraceArray, TraceAsyncReset, TraceBool, TraceBundle, TraceClock, TraceDecl,
TraceEnumDiscriminant, TraceEnumWithFields, TraceFieldlessEnum, TraceInstance,
TraceMemPort, TraceModule, TraceModuleIO, TraceReg, TraceSInt, TraceScalar, TraceScalarId,
TraceScope, TraceSyncReset, TraceUInt, TraceWire, TraceWriter, TraceWriterDecls,
},
};
use bitvec::slice::BitSlice;
use std::{
fmt::{self, Display},
io::{self, Write},
mem,
};
pub struct VcdWriterDecls<W: io::Write + 'static> {
writer: W,
timescale: SimDuration,
}
impl<W: io::Write + 'static> VcdWriterDecls<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
timescale: SimDuration::from_picos(1),
}
}
pub fn timescale(&self) -> SimDuration {
self.timescale
}
#[track_caller]
pub fn with_timescale(mut self, timescale: SimDuration) -> Self {
// check timescale validity
vcd_timescale(timescale);
self.timescale = timescale;
self
}
}
#[track_caller]
const fn vcd_timescale(timescale: SimDuration) -> &'static str {
if !timescale.is_power_of_ten() {
panic!("VCD timescale must be a power of 10");
}
macro_rules! timescales {
($($const_name:ident = ($dur:expr, $text:literal),)*) => {
$(const $const_name: SimDuration = $dur;)*
match timescale {
$($const_name => $text,)*
_ => panic!("VCD timescale is too big"),
}
};
}
timescales! {
TIMESCALE_1_AS = (SimDuration::from_attos(1), "1 as"),
TIMESCALE_10_AS = (SimDuration::from_attos(10), "10 as"),
TIMESCALE_100_AS = (SimDuration::from_attos(100), "100 as"),
TIMESCALE_1_FS = (SimDuration::from_femtos(1), "1 fs"),
TIMESCALE_10_FS = (SimDuration::from_femtos(10), "10 fs"),
TIMESCALE_100_FS = (SimDuration::from_femtos(100), "100 fs"),
TIMESCALE_1_PS = (SimDuration::from_picos(1), "1 ps"),
TIMESCALE_10_PS = (SimDuration::from_picos(10), "10 ps"),
TIMESCALE_100_PS = (SimDuration::from_picos(100), "100 ps"),
TIMESCALE_1_NS = (SimDuration::from_nanos(1), "1 ns"),
TIMESCALE_10_NS = (SimDuration::from_nanos(10), "10 ns"),
TIMESCALE_100_NS = (SimDuration::from_nanos(100), "100 ns"),
TIMESCALE_1_US = (SimDuration::from_micros(1), "1 us"),
TIMESCALE_10_US = (SimDuration::from_micros(10), "10 us"),
TIMESCALE_100_US = (SimDuration::from_micros(100), "100 us"),
TIMESCALE_1_MS = (SimDuration::from_millis(1), "1 ms"),
TIMESCALE_10_MS = (SimDuration::from_millis(10), "10 ms"),
TIMESCALE_100_MS = (SimDuration::from_millis(100), "100 ms"),
TIMESCALE_1_S = (SimDuration::from_secs(1), "1 s"),
TIMESCALE_10_S = (SimDuration::from_secs(10), "10 s"),
TIMESCALE_100_S = (SimDuration::from_secs(100), "100 s"),
TIMESCALE_1000_S = (SimDuration::from_secs(1000), "1000 s"),
}
}
impl<W: io::Write> fmt::Debug for VcdWriterDecls<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
writer: _,
timescale,
} = self;
f.debug_struct("VcdWriterDecls")
.field("timescale", timescale)
.finish_non_exhaustive()
}
}
fn write_vcd_scope<W: io::Write, R>(
writer: &mut W,
scope_type: &str,
scope_name: &str,
f: impl FnOnce(&mut W) -> io::Result<R>,
) -> io::Result<R> {
writeln!(writer, "$scope {scope_type} {scope_name} $end")?;
let retval = f(writer)?;
writeln!(writer, "$upscope $end")?;
Ok(retval)
}
macro_rules! trait_arg {
(
trait $Arg:ident {
$(
fn $fn:ident(self) -> $ty:ty;
)*
}
) => {
trait $Arg: Sized {
$(fn $fn(self) -> $ty {
unreachable!()
})*
}
$(
impl $Arg for $ty {
fn $fn(self) -> $ty {
self
}
}
)*
};
}
trait_arg! {
trait Arg {
fn module(self) -> ArgModule;
fn module_body(self) -> ArgModuleBody;
fn in_type(self) -> ArgInType;
}
}
struct ArgModule {}
struct ArgModuleBody {}
#[derive(Clone, Copy)]
struct ArgInType {
source_var_type: &'static str,
sink_var_type: &'static str,
duplex_var_type: &'static str,
}
trait WriteTrace: Copy {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()>;
}
impl WriteTrace for TraceDecl {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
match self {
Self::Scope(v) => v.write_trace(writer, arg),
Self::Scalar(v) => v.write_trace(writer, arg),
}
}
}
impl WriteTrace for TraceScalar {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
match self {
Self::UInt(v) => v.write_trace(writer, arg),
Self::SInt(v) => v.write_trace(writer, arg),
Self::Bool(v) => v.write_trace(writer, arg),
Self::FieldlessEnum(v) => v.write_trace(writer, arg),
Self::EnumDiscriminant(v) => v.write_trace(writer, arg),
Self::Clock(v) => v.write_trace(writer, arg),
Self::SyncReset(v) => v.write_trace(writer, arg),
Self::AsyncReset(v) => v.write_trace(writer, arg),
}
}
}
fn write_scalar_id<W: io::Write>(writer: &mut W, id: TraceScalarId) -> io::Result<()> {
let min_char = b'!';
let max_char = b'~';
let base = (max_char - min_char + 1) as usize;
let mut id = id.as_usize();
loop {
let digit = (id % base) as u8 + min_char;
id /= base;
writer.write_all(&[digit])?;
if id == 0 {
break;
}
}
Ok(())
}
fn write_escaped<W: io::Write>(writer: &mut W, value: impl Display) -> io::Result<()> {
// escaping rules from function GTKWave uses to decode VCD strings:
// https://github.com/gtkwave/gtkwave/blob/491f24d7e8619cfc1fcc65704ee5c967d1083c18/lib/libfst/fstapi.c#L7090
struct Wrapper<W>(W);
impl<W: io::Write> io::Write for Wrapper<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if buf.is_empty() {
return self.0.write(buf);
}
let mut retval = 0;
for &byte in buf {
match byte {
b'\\' | b'\'' | b'"' | b'?' => self.0.write_all(&[b'\\', byte])?,
b'\n' => self.0.write_all(br"\n")?,
b'\r' => self.0.write_all(br"\r")?,
b'\t' => self.0.write_all(br"\t")?,
0x7 => self.0.write_all(br"\a")?,
0x8 => self.0.write_all(br"\b")?,
0xC => self.0.write_all(br"\f")?,
0xB => self.0.write_all(br"\v")?,
_ => {
if byte.is_ascii_graphic() {
self.0.write_all(&[byte])?;
} else {
write!(self.0, r"\x{byte:02x}")?;
}
}
}
retval += 1;
}
Ok(retval)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
write!(Wrapper(writer), "{value}")
}
fn write_vcd_var<W: io::Write>(
writer: &mut W,
var_type: &str,
size: usize,
id: TraceScalarId,
name: &str,
) -> io::Result<()> {
write!(writer, "$var {var_type} {size} ")?;
write_scalar_id(writer, id)?;
writeln!(writer, " {name} $end")
}
impl WriteTrace for TraceUInt {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let ArgInType {
source_var_type,
sink_var_type,
duplex_var_type,
} = arg.in_type();
let Self { id, name, ty, flow } = self;
let var_type = match flow {
Flow::Source => source_var_type,
Flow::Sink => sink_var_type,
Flow::Duplex => duplex_var_type,
};
if ty.width() == 0 {
write_vcd_var(writer, "string", ty.width(), id, &name)
} else {
write_vcd_var(writer, var_type, ty.width(), id, &name)
}
}
}
impl WriteTrace for TraceSInt {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self { id, name, ty, flow } = self;
TraceUInt {
id,
name,
ty: UInt::new_dyn(ty.width()),
flow,
}
.write_trace(writer, arg)
}
}
impl WriteTrace for TraceBool {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self { id, name, flow } = self;
TraceUInt {
id,
name,
flow,
ty: UInt::new_dyn(1),
}
.write_trace(writer, arg)
}
}
impl WriteTrace for TraceFieldlessEnum {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self { id, name, ty, flow } = self;
TraceEnumDiscriminant { id, name, ty, flow }.write_trace(writer, arg)
}
}
impl WriteTrace for TraceEnumDiscriminant {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let ArgInType {
source_var_type: _,
sink_var_type: _,
duplex_var_type: _,
} = arg.in_type();
let Self {
id,
name,
ty: _,
flow: _,
} = self;
write_vcd_var(writer, "string", 1, id, &name)
}
}
impl WriteTrace for TraceClock {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self { id, name, flow } = self;
TraceBool { id, name, flow }.write_trace(writer, arg)
}
}
impl WriteTrace for TraceSyncReset {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self { id, name, flow } = self;
TraceBool { id, name, flow }.write_trace(writer, arg)
}
}
impl WriteTrace for TraceAsyncReset {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let Self { id, name, flow } = self;
TraceBool { id, name, flow }.write_trace(writer, arg)
}
}
impl WriteTrace for TraceScope {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
match self {
Self::Module(v) => v.write_trace(writer, arg),
Self::Instance(v) => v.write_trace(writer, arg),
Self::MemPort(v) => v.write_trace(writer, arg),
Self::Wire(v) => v.write_trace(writer, arg),
Self::Reg(v) => v.write_trace(writer, arg),
Self::ModuleIO(v) => v.write_trace(writer, arg),
Self::Bundle(v) => v.write_trace(writer, arg),
Self::Array(v) => v.write_trace(writer, arg),
Self::EnumWithFields(v) => v.write_trace(writer, arg),
}
}
}
impl WriteTrace for TraceModule {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let ArgModule {} = arg.module();
let Self { name, children } = self;
write_vcd_scope(writer, "module", &name, |writer| {
for child in children {
child.write_trace(writer, ArgModuleBody {})?;
}
Ok(())
})
}
}
impl WriteTrace for TraceInstance {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let ArgModuleBody {} = arg.module_body();
let Self {
name: _,
instance_io,
module,
ty: _,
} = self;
instance_io.write_trace(
writer,
ArgInType {
source_var_type: "wire",
sink_var_type: "wire",
duplex_var_type: "wire",
},
)?;
module.write_trace(writer, ArgModule {})
}
}
impl WriteTrace for TraceMemPort {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
todo!()
}
}
impl WriteTrace for TraceWire {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let ArgModuleBody {} = arg.module_body();
let Self {
name: _,
child,
ty: _,
} = self;
child.write_trace(
writer,
ArgInType {
source_var_type: "wire",
sink_var_type: "wire",
duplex_var_type: "wire",
},
)
}
}
impl WriteTrace for TraceReg {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let ArgModuleBody {} = arg.module_body();
let Self {
name: _,
child,
ty: _,
} = self;
child.write_trace(
writer,
ArgInType {
source_var_type: "reg",
sink_var_type: "reg",
duplex_var_type: "reg",
},
)
}
}
impl WriteTrace for TraceModuleIO {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let ArgModuleBody {} = arg.module_body();
let Self {
name: _,
child,
ty: _,
flow: _,
} = self;
child.write_trace(
writer,
ArgInType {
source_var_type: "wire",
sink_var_type: "wire",
duplex_var_type: "wire",
},
)
}
}
impl WriteTrace for TraceBundle {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let arg = arg.in_type();
let Self {
name,
fields,
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", &name, |writer| {
for field in fields {
field.write_trace(writer, arg)?;
}
Ok(())
})
}
}
impl WriteTrace for TraceArray {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
let arg = arg.in_type();
let Self {
name,
elements,
ty: _,
flow: _,
} = self;
write_vcd_scope(writer, "struct", &name, |writer| {
for element in elements {
element.write_trace(writer, arg)?;
}
Ok(())
})
}
}
impl WriteTrace for TraceEnumWithFields {
fn write_trace<W: io::Write, A: Arg>(self, writer: &mut W, arg: A) -> io::Result<()> {
todo!()
}
}
impl<W: io::Write> TraceWriterDecls for VcdWriterDecls<W> {
type Error = io::Error;
type TraceWriter = VcdWriter<W>;
fn write_decls(self, module: TraceModule) -> Result<Self::TraceWriter, Self::Error> {
let Self {
mut writer,
timescale,
} = self;
writeln!(writer, "$timescale {} $end", vcd_timescale(timescale))?;
module.write_trace(&mut writer, ArgModule {})?;
writeln!(writer, "$enddefinitions $end")?;
writeln!(writer, "$dumpvars")?;
Ok(VcdWriter {
writer,
finished_init: false,
timescale,
})
}
}
pub struct VcdWriter<W: io::Write + 'static> {
writer: W,
finished_init: bool,
timescale: SimDuration,
}
impl<W: io::Write + 'static> VcdWriter<W> {
pub fn timescale(&self) -> SimDuration {
self.timescale
}
fn write_string_value_change(
&mut self,
value: impl Display,
id: TraceScalarId,
) -> io::Result<()> {
self.writer.write_all(b"s")?;
write_escaped(&mut self.writer, value)?;
self.writer.write_all(b" ")?;
write_scalar_id(&mut self.writer, id)?;
self.writer.write_all(b"\n")
}
}
impl<W: io::Write> TraceWriter for VcdWriter<W> {
type Error = io::Error;
fn set_signal_uint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> {
match value.len() {
0 => self.writer.write_all(b"s0 ")?,
1 => self.writer.write_all(if value[0] { b"1" } else { b"0" })?,
_ => {
self.writer.write_all(b"b")?;
let mut any_ones = false;
for bit in value.iter().rev() {
if *bit {
any_ones = true;
self.writer.write_all(b"1")?;
} else if any_ones {
self.writer.write_all(b"0")?;
}
}
if !any_ones {
self.writer.write_all(b"0")?;
}
self.writer.write_all(b" ")?;
}
}
write_scalar_id(&mut self.writer, id)?;
self.writer.write_all(b"\n")
}
fn set_signal_sint(&mut self, id: TraceScalarId, value: &BitSlice) -> Result<(), Self::Error> {
self.set_signal_uint(id, value)
}
fn finish_init(&mut self) -> Result<(), Self::Error> {
if mem::replace(&mut self.finished_init, true) {
return Ok(());
}
writeln!(self.writer, "$end")
}
fn change_time_to(&mut self, instant: SimInstant) -> Result<(), Self::Error> {
assert!(self.finished_init);
let mut instant_attos = (instant - SimInstant::START).as_attos();
instant_attos += self.timescale.as_attos() / 2;
let timestamp = instant_attos / self.timescale.as_attos();
writeln!(self.writer, "#{timestamp}")
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.writer.flush()
}
fn close(mut self) -> Result<(), Self::Error> {
self.writer.flush()
}
fn set_signal_enum_discriminant(
&mut self,
id: TraceScalarId,
variant_index: usize,
ty: Enum,
) -> Result<(), Self::Error> {
self.write_string_value_change(
format_args!(
"{} ({variant_index})",
ty.variants()
.get(variant_index)
.map(|v| &*v.name)
.unwrap_or("<invalid>"),
),
id,
)
}
}
impl<W: io::Write> fmt::Debug for VcdWriter<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
writer: _,
finished_init,
timescale,
} = self;
f.debug_struct("VcdWriter")
.field("finished_init", finished_init)
.field("timescale", timescale)
.finish_non_exhaustive()
}
}

View file

@ -24,7 +24,8 @@ pub use scoped_ref::ScopedRef;
#[doc(inline)]
pub use misc::{
interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice,
get_many_mut, interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay,
DebugAsRawString, MakeMutSlice, RcWriter,
};
pub mod job_server;

View file

@ -3,6 +3,7 @@
use crate::intern::{Intern, Interned};
use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView};
use std::{
cell::Cell,
fmt::{self, Debug, Write},
rc::Rc,
sync::{Arc, OnceLock},
@ -94,9 +95,15 @@ pub fn interned_bit(v: bool) -> Interned<BitSlice> {
RETVAL.get_or_init(|| [bits![0; 1].intern(), bits![1; 1].intern()])[v as usize]
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone)]
pub struct BitSliceWriteWithBase<'a>(pub &'a BitSlice);
impl<'a> Debug for BitSliceWriteWithBase<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:#x}")
}
}
impl BitSliceWriteWithBase<'_> {
fn fmt_with_base<const BITS_PER_DIGIT: usize, const UPPER_CASE: bool>(
self,
@ -155,3 +162,66 @@ impl fmt::UpperHex for BitSliceWriteWithBase<'_> {
self.fmt_with_base::<4, true>(f)
}
}
#[inline]
#[track_caller]
pub fn get_many_mut<T, const N: usize>(slice: &mut [T], indexes: [usize; N]) -> [&mut T; N] {
for i in 0..N {
for j in 0..i {
assert!(indexes[i] != indexes[j], "duplicate index");
}
assert!(indexes[i] < slice.len(), "index out of bounds");
}
// Safety: checked that no indexes are duplicates and no indexes are out of bounds
unsafe {
let base = slice.as_mut_ptr(); // convert to a raw pointer before loop to avoid aliasing with &mut [T]
std::array::from_fn(|i| &mut *base.add(indexes[i]))
}
}
#[derive(Clone, Default)]
pub struct RcWriter(Rc<Cell<Vec<u8>>>);
impl Debug for RcWriter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.borrow_impl(|buf| {
f.debug_tuple("RcWriter")
.field(&DebugAsDisplay(format_args!("b\"{}\"", buf.escape_ascii())))
.finish()
})
}
}
impl RcWriter {
fn borrow_impl<R>(&self, f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
let buf = Cell::take(&self.0);
struct PutBackOnDrop<'a> {
buf: Vec<u8>,
this: &'a RcWriter,
}
impl Drop for PutBackOnDrop<'_> {
fn drop(&mut self) {
self.this.0.set(std::mem::take(&mut self.buf));
}
}
let mut buf = PutBackOnDrop { buf, this: self };
f(&mut buf.buf)
}
pub fn borrow<R>(&mut self, f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
self.borrow_impl(f)
}
pub fn take(&mut self) -> Vec<u8> {
Cell::take(&self.0)
}
}
impl std::io::Write for RcWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.borrow(|v| v.extend_from_slice(buf));
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}

View file

@ -37,6 +37,18 @@ impl<T: Type> Wire<T> {
ty: ty.canonical(),
}
}
pub fn from_canonical(v: Wire<CanonicalType>) -> Self {
let Wire {
name,
source_location,
ty,
} = v;
Self {
name,
source_location,
ty: T::from_canonical(ty),
}
}
pub fn ty(&self) -> T {
self.ty
}

View file

@ -2,7 +2,8 @@
// See Notices.txt for copyright information
use fayalite::{
assert_export_firrtl, firrtl::ExportOptions, intern::Intern,
module::transform::simplify_enums::SimplifyEnumsKind, prelude::*, ty::StaticType,
module::transform::simplify_enums::SimplifyEnumsKind, prelude::*, reset::ResetType,
ty::StaticType,
};
use serde_json::json;
@ -4026,3 +4027,263 @@ circuit check_enum_connect_any:
",
};
}
#[hdl_module(outline_generated)]
pub fn check_deduce_resets<T: ResetType>(ty: T) {
#[hdl]
let cd: ClockDomain<T> = m.input(ClockDomain[ty]);
#[hdl]
let my_reg = reg_builder().reset(0u8).clock_domain(cd);
#[hdl]
let u8_in: UInt<8> = m.input();
connect(my_reg, u8_in);
#[hdl]
let u8_out: UInt<8> = m.output();
connect(u8_out, my_reg);
#[hdl]
let enum_in: OneOfThree<Reset, AsyncReset, SyncReset> = m.input();
#[hdl]
let enum_out: OneOfThree<Reset, AsyncReset, SyncReset> = m.output();
#[hdl]
let reset_out: Reset = m.output();
connect(reset_out, cd.rst.to_reset());
#[hdl]
match enum_in {
OneOfThree::<_, _, _>::A(v) => {
connect(
enum_out,
OneOfThree[Reset][AsyncReset][SyncReset].A(cd.rst.to_reset()),
);
connect(reset_out, v);
}
OneOfThree::<_, _, _>::B(v) => {
connect(enum_out, OneOfThree[Reset][AsyncReset][SyncReset].B(v))
}
OneOfThree::<_, _, _>::C(v) => {
connect(enum_out, OneOfThree[Reset][AsyncReset][SyncReset].C(v))
}
}
}
#[test]
fn test_deduce_resets() {
let _n = SourceLocation::normalize_files_for_tests();
let m = check_deduce_resets(Reset);
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
options: ExportOptions {
simplify_enums: None,
..ExportOptions::default()
},
"/test/check_deduce_resets.fir": r"FIRRTL version 3.2.0
circuit check_deduce_resets:
type Ty0 = {clk: Clock, rst: Reset}
type Ty1 = {|A: Reset, B: AsyncReset, C: UInt<1>|}
module check_deduce_resets: @[module-XXXXXXXXXX.rs 1:1]
input cd: Ty0 @[module-XXXXXXXXXX.rs 2:1]
input u8_in: UInt<8> @[module-XXXXXXXXXX.rs 4:1]
output u8_out: UInt<8> @[module-XXXXXXXXXX.rs 6:1]
input enum_in: Ty1 @[module-XXXXXXXXXX.rs 8:1]
output enum_out: Ty1 @[module-XXXXXXXXXX.rs 9:1]
output reset_out: Reset @[module-XXXXXXXXXX.rs 10:1]
regreset my_reg: UInt<8>, cd.clk, cd.rst, UInt<8>(0h0) @[module-XXXXXXXXXX.rs 3:1]
connect my_reg, u8_in @[module-XXXXXXXXXX.rs 5:1]
connect u8_out, my_reg @[module-XXXXXXXXXX.rs 7:1]
connect reset_out, cd.rst @[module-XXXXXXXXXX.rs 11:1]
match enum_in: @[module-XXXXXXXXXX.rs 12:1]
A(_match_arm_value):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(A, cd.rst) @[module-XXXXXXXXXX.rs 13:1]
connect reset_out, _match_arm_value @[module-XXXXXXXXXX.rs 14:1]
B(_match_arm_value_1):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(B, _match_arm_value_1) @[module-XXXXXXXXXX.rs 15:1]
C(_match_arm_value_2):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(C, _match_arm_value_2) @[module-XXXXXXXXXX.rs 16:1]
",
};
fayalite::module::transform::deduce_resets::deduce_resets(m.canonical().intern_sized(), false)
.unwrap_err();
let m = fayalite::module::transform::deduce_resets::deduce_resets(
m.canonical().intern_sized(),
true,
)
.unwrap();
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
options: ExportOptions {
simplify_enums: None,
..ExportOptions::default()
},
"/test/check_deduce_resets.fir": r"FIRRTL version 3.2.0
circuit check_deduce_resets:
type Ty0 = {clk: Clock, rst: UInt<1>}
type Ty1 = {|A: UInt<1>, B: AsyncReset, C: UInt<1>|}
module check_deduce_resets: @[module-XXXXXXXXXX.rs 1:1]
input cd: Ty0 @[module-XXXXXXXXXX.rs 2:1]
input u8_in: UInt<8> @[module-XXXXXXXXXX.rs 4:1]
output u8_out: UInt<8> @[module-XXXXXXXXXX.rs 6:1]
input enum_in: Ty1 @[module-XXXXXXXXXX.rs 8:1]
output enum_out: Ty1 @[module-XXXXXXXXXX.rs 9:1]
output reset_out: UInt<1> @[module-XXXXXXXXXX.rs 10:1]
regreset my_reg: UInt<8>, cd.clk, cd.rst, UInt<8>(0h0) @[module-XXXXXXXXXX.rs 3:1]
connect my_reg, u8_in @[module-XXXXXXXXXX.rs 5:1]
connect u8_out, my_reg @[module-XXXXXXXXXX.rs 7:1]
connect reset_out, cd.rst @[module-XXXXXXXXXX.rs 11:1]
match enum_in: @[module-XXXXXXXXXX.rs 12:1]
A(_match_arm_value):
connect enum_out, {|A: UInt<1>, B: AsyncReset, C: UInt<1>|}(A, cd.rst) @[module-XXXXXXXXXX.rs 13:1]
connect reset_out, _match_arm_value @[module-XXXXXXXXXX.rs 14:1]
B(_match_arm_value_1):
connect enum_out, {|A: UInt<1>, B: AsyncReset, C: UInt<1>|}(B, _match_arm_value_1) @[module-XXXXXXXXXX.rs 15:1]
C(_match_arm_value_2):
connect enum_out, {|A: UInt<1>, B: AsyncReset, C: UInt<1>|}(C, _match_arm_value_2) @[module-XXXXXXXXXX.rs 16:1]
",
};
let m = check_deduce_resets(SyncReset);
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
options: ExportOptions {
simplify_enums: None,
..ExportOptions::default()
},
"/test/check_deduce_resets.fir": r"FIRRTL version 3.2.0
circuit check_deduce_resets:
type Ty0 = {clk: Clock, rst: UInt<1>}
type Ty1 = {|A: Reset, B: AsyncReset, C: UInt<1>|}
module check_deduce_resets: @[module-XXXXXXXXXX.rs 1:1]
input cd: Ty0 @[module-XXXXXXXXXX.rs 2:1]
input u8_in: UInt<8> @[module-XXXXXXXXXX.rs 4:1]
output u8_out: UInt<8> @[module-XXXXXXXXXX.rs 6:1]
input enum_in: Ty1 @[module-XXXXXXXXXX.rs 8:1]
output enum_out: Ty1 @[module-XXXXXXXXXX.rs 9:1]
output reset_out: Reset @[module-XXXXXXXXXX.rs 10:1]
regreset my_reg: UInt<8>, cd.clk, cd.rst, UInt<8>(0h0) @[module-XXXXXXXXXX.rs 3:1]
connect my_reg, u8_in @[module-XXXXXXXXXX.rs 5:1]
connect u8_out, my_reg @[module-XXXXXXXXXX.rs 7:1]
connect reset_out, cd.rst @[module-XXXXXXXXXX.rs 11:1]
match enum_in: @[module-XXXXXXXXXX.rs 12:1]
A(_match_arm_value):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(A, cd.rst) @[module-XXXXXXXXXX.rs 13:1]
connect reset_out, _match_arm_value @[module-XXXXXXXXXX.rs 14:1]
B(_match_arm_value_1):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(B, _match_arm_value_1) @[module-XXXXXXXXXX.rs 15:1]
C(_match_arm_value_2):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(C, _match_arm_value_2) @[module-XXXXXXXXXX.rs 16:1]
",
};
let m = fayalite::module::transform::deduce_resets::deduce_resets(
m.canonical().intern_sized(),
false,
)
.unwrap();
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
options: ExportOptions {
simplify_enums: None,
..ExportOptions::default()
},
"/test/check_deduce_resets.fir": r"FIRRTL version 3.2.0
circuit check_deduce_resets:
type Ty0 = {clk: Clock, rst: UInt<1>}
type Ty1 = {|A: UInt<1>, B: AsyncReset, C: UInt<1>|}
module check_deduce_resets: @[module-XXXXXXXXXX.rs 1:1]
input cd: Ty0 @[module-XXXXXXXXXX.rs 2:1]
input u8_in: UInt<8> @[module-XXXXXXXXXX.rs 4:1]
output u8_out: UInt<8> @[module-XXXXXXXXXX.rs 6:1]
input enum_in: Ty1 @[module-XXXXXXXXXX.rs 8:1]
output enum_out: Ty1 @[module-XXXXXXXXXX.rs 9:1]
output reset_out: UInt<1> @[module-XXXXXXXXXX.rs 10:1]
regreset my_reg: UInt<8>, cd.clk, cd.rst, UInt<8>(0h0) @[module-XXXXXXXXXX.rs 3:1]
connect my_reg, u8_in @[module-XXXXXXXXXX.rs 5:1]
connect u8_out, my_reg @[module-XXXXXXXXXX.rs 7:1]
connect reset_out, cd.rst @[module-XXXXXXXXXX.rs 11:1]
match enum_in: @[module-XXXXXXXXXX.rs 12:1]
A(_match_arm_value):
connect enum_out, {|A: UInt<1>, B: AsyncReset, C: UInt<1>|}(A, cd.rst) @[module-XXXXXXXXXX.rs 13:1]
connect reset_out, _match_arm_value @[module-XXXXXXXXXX.rs 14:1]
B(_match_arm_value_1):
connect enum_out, {|A: UInt<1>, B: AsyncReset, C: UInt<1>|}(B, _match_arm_value_1) @[module-XXXXXXXXXX.rs 15:1]
C(_match_arm_value_2):
connect enum_out, {|A: UInt<1>, B: AsyncReset, C: UInt<1>|}(C, _match_arm_value_2) @[module-XXXXXXXXXX.rs 16:1]
",
};
let m = check_deduce_resets(AsyncReset);
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
options: ExportOptions {
simplify_enums: None,
..ExportOptions::default()
},
"/test/check_deduce_resets.fir": r"FIRRTL version 3.2.0
circuit check_deduce_resets:
type Ty0 = {clk: Clock, rst: AsyncReset}
type Ty1 = {|A: Reset, B: AsyncReset, C: UInt<1>|}
module check_deduce_resets: @[module-XXXXXXXXXX.rs 1:1]
input cd: Ty0 @[module-XXXXXXXXXX.rs 2:1]
input u8_in: UInt<8> @[module-XXXXXXXXXX.rs 4:1]
output u8_out: UInt<8> @[module-XXXXXXXXXX.rs 6:1]
input enum_in: Ty1 @[module-XXXXXXXXXX.rs 8:1]
output enum_out: Ty1 @[module-XXXXXXXXXX.rs 9:1]
output reset_out: Reset @[module-XXXXXXXXXX.rs 10:1]
regreset my_reg: UInt<8>, cd.clk, cd.rst, UInt<8>(0h0) @[module-XXXXXXXXXX.rs 3:1]
connect my_reg, u8_in @[module-XXXXXXXXXX.rs 5:1]
connect u8_out, my_reg @[module-XXXXXXXXXX.rs 7:1]
connect reset_out, cd.rst @[module-XXXXXXXXXX.rs 11:1]
match enum_in: @[module-XXXXXXXXXX.rs 12:1]
A(_match_arm_value):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(A, cd.rst) @[module-XXXXXXXXXX.rs 13:1]
connect reset_out, _match_arm_value @[module-XXXXXXXXXX.rs 14:1]
B(_match_arm_value_1):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(B, _match_arm_value_1) @[module-XXXXXXXXXX.rs 15:1]
C(_match_arm_value_2):
connect enum_out, {|A: Reset, B: AsyncReset, C: UInt<1>|}(C, _match_arm_value_2) @[module-XXXXXXXXXX.rs 16:1]
",
};
let m = fayalite::module::transform::deduce_resets::deduce_resets(
m.canonical().intern_sized(),
false,
)
.unwrap();
dbg!(m);
#[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161
assert_export_firrtl! {
m =>
options: ExportOptions {
simplify_enums: None,
..ExportOptions::default()
},
"/test/check_deduce_resets.fir": r"FIRRTL version 3.2.0
circuit check_deduce_resets:
type Ty0 = {clk: Clock, rst: AsyncReset}
type Ty1 = {|A: AsyncReset, B: AsyncReset, C: UInt<1>|}
module check_deduce_resets: @[module-XXXXXXXXXX.rs 1:1]
input cd: Ty0 @[module-XXXXXXXXXX.rs 2:1]
input u8_in: UInt<8> @[module-XXXXXXXXXX.rs 4:1]
output u8_out: UInt<8> @[module-XXXXXXXXXX.rs 6:1]
input enum_in: Ty1 @[module-XXXXXXXXXX.rs 8:1]
output enum_out: Ty1 @[module-XXXXXXXXXX.rs 9:1]
output reset_out: AsyncReset @[module-XXXXXXXXXX.rs 10:1]
regreset my_reg: UInt<8>, cd.clk, cd.rst, UInt<8>(0h0) @[module-XXXXXXXXXX.rs 3:1]
connect my_reg, u8_in @[module-XXXXXXXXXX.rs 5:1]
connect u8_out, my_reg @[module-XXXXXXXXXX.rs 7:1]
connect reset_out, cd.rst @[module-XXXXXXXXXX.rs 11:1]
match enum_in: @[module-XXXXXXXXXX.rs 12:1]
A(_match_arm_value):
connect enum_out, {|A: AsyncReset, B: AsyncReset, C: UInt<1>|}(A, cd.rst) @[module-XXXXXXXXXX.rs 13:1]
connect reset_out, _match_arm_value @[module-XXXXXXXXXX.rs 14:1]
B(_match_arm_value_1):
connect enum_out, {|A: AsyncReset, B: AsyncReset, C: UInt<1>|}(B, _match_arm_value_1) @[module-XXXXXXXXXX.rs 15:1]
C(_match_arm_value_2):
connect enum_out, {|A: AsyncReset, B: AsyncReset, C: UInt<1>|}(C, _match_arm_value_2) @[module-XXXXXXXXXX.rs 16:1]
",
};
}

3795
crates/fayalite/tests/sim.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1047,9 +1047,9 @@
"clock_domain()": "Visible",
"init()": "Visible"
},
"generics": "<T: Type>",
"fold_where": "T: Fold<State>",
"visit_where": "T: Visit<State>"
"generics": "<T: Type, R: ResetType>",
"fold_where": "T: Fold<State>, R: Fold<State>",
"visit_where": "T: Visit<State>, R: Visit<State>"
},
"Wire": {
"data": {
@ -1078,6 +1078,8 @@
"$kind": "Enum",
"Wire": "Visible",
"Reg": "Visible",
"RegSync": "Visible",
"RegAsync": "Visible",
"Instance": "Visible"
}
},
@ -1136,7 +1138,10 @@
"$kind": "Struct",
"annotations": "Visible",
"reg": "Visible"
}
},
"generics": "<R: ResetType>",
"fold_where": "R: Fold<State>",
"visit_where": "R: Visit<State>"
},
"StmtWire": {
"data": {
@ -1219,6 +1224,8 @@
"ModuleIO": "Visible",
"MemPort": "Visible",
"Reg": "Visible",
"RegSync": "Visible",
"RegAsync": "Visible",
"Wire": "Visible",
"Instance": "Visible"
}