forked from libre-chip/fayalite
2635 lines
81 KiB
Rust
2635 lines
81 KiB
Rust
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
// See Notices.txt for copyright information
|
|
#![allow(clippy::type_complexity)]
|
|
use crate::{
|
|
annotations::{Annotation, IntoAnnotations, TargetedAnnotation},
|
|
array::{ArrayType, ValueArrayOrSlice},
|
|
bundle::{BundleType, BundleValue, DynBundle, DynBundleType, FieldType, FieldsHint},
|
|
clock::ClockDomain,
|
|
enum_::{DynEnum, EnumMatchVariantsIter, EnumType, EnumValue},
|
|
expr::{
|
|
ops::VariantAccess, Expr, Flow, Target, TargetBase, TargetPathArrayElement,
|
|
TargetPathBundleField, TargetPathElement, ToExpr,
|
|
},
|
|
int::{DynUInt, DynUIntType, FixedOrDynIntType, IntValue, UInt},
|
|
intern::{Intern, Interned},
|
|
memory::{Mem, MemBuilder, MemBuilderTarget, PortName},
|
|
reg::Reg,
|
|
source_location::SourceLocation,
|
|
ty::{
|
|
CanonicalType, Connect, DynCanonicalType, DynCanonicalValue, DynType, FixedType, Type,
|
|
TypeEnum, Value,
|
|
},
|
|
util::ConstBool,
|
|
wire::Wire,
|
|
};
|
|
use hashbrown::{hash_map::Entry, HashMap, HashSet};
|
|
use num_bigint::BigInt;
|
|
use std::{
|
|
cell::RefCell,
|
|
collections::VecDeque,
|
|
convert::Infallible,
|
|
fmt,
|
|
hash::{Hash, Hasher},
|
|
iter::FusedIterator,
|
|
marker::PhantomData,
|
|
ops::Deref,
|
|
rc::Rc,
|
|
sync::Mutex,
|
|
};
|
|
|
|
pub mod transform;
|
|
|
|
mod module_building_status_sealed {
|
|
pub trait Sealed {}
|
|
}
|
|
|
|
pub trait ModuleBuildingStatus:
|
|
'static
|
|
+ Send
|
|
+ Sync
|
|
+ Copy
|
|
+ Ord
|
|
+ Hash
|
|
+ fmt::Debug
|
|
+ Default
|
|
+ module_building_status_sealed::Sealed
|
|
{
|
|
type Block: 'static + Send + Sync + Copy + Eq + Hash + fmt::Debug;
|
|
type ModuleBody;
|
|
type StmtAnnotations: 'static + Send + Sync + Copy + Eq + Hash + fmt::Debug;
|
|
type ModuleIOAnnotations;
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)]
|
|
pub struct ModuleBuilt;
|
|
|
|
impl module_building_status_sealed::Sealed for ModuleBuilt {}
|
|
|
|
impl ModuleBuildingStatus for ModuleBuilt {
|
|
type Block = Block;
|
|
type ModuleBody = Block;
|
|
type StmtAnnotations = Interned<[TargetedAnnotation]>;
|
|
type ModuleIOAnnotations = Interned<[TargetedAnnotation]>;
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)]
|
|
pub struct ModuleBuilding;
|
|
|
|
impl module_building_status_sealed::Sealed for ModuleBuilding {}
|
|
|
|
impl ModuleBuildingStatus for ModuleBuilding {
|
|
type Block = BlockId;
|
|
type ModuleBody = BuilderModuleBody;
|
|
type StmtAnnotations = ();
|
|
type ModuleIOAnnotations = Vec<TargetedAnnotation>;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BuilderModuleBody {
|
|
blocks: Vec<BuilderBlock>,
|
|
annotations_map: HashMap<StmtDeclaration<ModuleBuilding>, Vec<TargetedAnnotation>>,
|
|
memory_map: HashMap<ScopedNameId, Rc<RefCell<MemBuilderTarget>>>,
|
|
}
|
|
|
|
/// implicit name argument that's automatically provided by `#[hdl]` let
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
pub struct ImplicitName<'a>(pub &'a str);
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
pub struct BlockId(usize);
|
|
|
|
impl BlockId {
|
|
pub const ENTRY_BLOCK: BlockId = BlockId(0);
|
|
pub fn as_usize(self) -> usize {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for BlockId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "block#{}", self.0)
|
|
}
|
|
}
|
|
|
|
pub trait BlockRef: 'static + Send + Sync + Copy + Eq + Hash + fmt::Debug {}
|
|
|
|
impl BlockRef for BlockId {}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BuilderBlock {
|
|
memories: Vec<Rc<RefCell<MemBuilderTarget>>>,
|
|
stmts: Vec<Stmt<ModuleBuilding>>,
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
|
|
pub struct Block {
|
|
pub memories: Interned<[Mem<[DynCanonicalValue]>]>,
|
|
pub stmts: Interned<[Stmt]>,
|
|
}
|
|
|
|
impl BlockRef for Block {}
|
|
|
|
impl fmt::Debug for Block {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_list()
|
|
.entries(self.memories)
|
|
.entries(self.stmts)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Block {
|
|
pub fn child_blocks(
|
|
&self,
|
|
) -> impl DoubleEndedIterator<Item = Block> + FusedIterator + Clone + '_ {
|
|
self.stmts
|
|
.iter()
|
|
.flat_map(|stmt| stmt.sub_stmt_blocks())
|
|
.copied()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
pub struct StmtConnect {
|
|
pub lhs: Expr<DynCanonicalValue>,
|
|
pub rhs: Expr<DynCanonicalValue>,
|
|
pub source_location: SourceLocation,
|
|
}
|
|
|
|
impl fmt::Debug for StmtConnect {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let Self {
|
|
lhs,
|
|
rhs,
|
|
source_location: _,
|
|
} = self;
|
|
f.debug_struct("StmtConnect")
|
|
.field("lhs", lhs)
|
|
.field("rhs", rhs)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
pub struct StmtIf<S: ModuleBuildingStatus = ModuleBuilt> {
|
|
pub cond: Expr<UInt<1>>,
|
|
pub source_location: SourceLocation,
|
|
pub blocks: [S::Block; 2],
|
|
}
|
|
|
|
impl<S: ModuleBuildingStatus> StmtIf<S> {
|
|
pub fn then_block(&self) -> S::Block {
|
|
self.blocks[0]
|
|
}
|
|
pub fn else_block(&self) -> S::Block {
|
|
self.blocks[1]
|
|
}
|
|
}
|
|
|
|
impl<S: ModuleBuildingStatus> fmt::Debug for StmtIf<S> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let Self {
|
|
cond,
|
|
source_location: _,
|
|
blocks: _,
|
|
} = self;
|
|
f.debug_struct("StmtIf")
|
|
.field("cond", cond)
|
|
.field("then_block", &self.then_block())
|
|
.field("else_block", &self.else_block())
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
pub struct StmtMatch<S: ModuleBuildingStatus = ModuleBuilt> {
|
|
pub expr: Expr<DynEnum>,
|
|
pub source_location: SourceLocation,
|
|
pub blocks: Interned<[S::Block]>,
|
|
}
|
|
|
|
impl<S: ModuleBuildingStatus> fmt::Debug for StmtMatch<S> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let Self {
|
|
expr,
|
|
source_location: _,
|
|
blocks,
|
|
} = self;
|
|
f.debug_struct("StmtMatch")
|
|
.field("expr", expr)
|
|
.field("blocks", blocks)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
macro_rules! wrapper_enum {
|
|
(
|
|
#[impl(
|
|
($($T_impl:tt)*) $impl_self:ident: $impl_type:ty = $impl_self_expr:expr
|
|
$(, ($($T_impls:tt)*) $impls_self:ident: $impls_type:ty = $impls_self_expr:expr)*
|
|
)]
|
|
#[to($($T_to:tt $to_types:ty),*)]
|
|
$(#[$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]
|
|
$(#[$variant_meta:meta])*
|
|
$Variant:ident($VariantTy:ty),
|
|
)*
|
|
}
|
|
) => {
|
|
wrapper_enum! {
|
|
#[impl($(($($T_impls)*) $impls_self: $impls_type = $impls_self_expr),*)]
|
|
#[to($($T_to $to_types),*)]
|
|
$(#[$enum_meta])*
|
|
$vis enum $enum_name<$T_enum: $T_bound = $T_enum_default> {
|
|
$(
|
|
#[is = $is_fn, as_ref = $as_ref_fn]
|
|
$(#[$variant_meta])*
|
|
$Variant($VariantTy),
|
|
)*
|
|
}
|
|
}
|
|
|
|
impl $($T_impl)* $impl_type {
|
|
$(
|
|
$vis fn $is_fn(&self) -> bool {
|
|
self.$as_ref_fn().is_some()
|
|
}
|
|
$vis fn $as_ref_fn(&$impl_self) -> Option<&$VariantTy> {
|
|
match $impl_self_expr {
|
|
$enum_name::$Variant(value) => Some(value),
|
|
_ => None,
|
|
}
|
|
}
|
|
)*
|
|
}
|
|
};
|
|
(
|
|
#[impl()]
|
|
#[to($T_to:tt $to_type:ty $(, $T_tos:tt $to_types:ty)*)]
|
|
$(#[$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]
|
|
$(#[$variant_meta:meta])*
|
|
$Variant:ident($VariantTy:ty),
|
|
)*
|
|
}
|
|
) => {
|
|
wrapper_enum! {
|
|
#[impl()]
|
|
#[to($($T_tos $to_types),*)]
|
|
$(#[$enum_meta])*
|
|
$vis enum $enum_name<$T_enum: $T_bound = $T_enum_default> {
|
|
$(
|
|
#[is = $is_fn, as_ref = $as_ref_fn]
|
|
$(#[$variant_meta])*
|
|
$Variant($VariantTy),
|
|
)*
|
|
}
|
|
}
|
|
|
|
$(
|
|
wrapper_enum! {
|
|
impl $T_to From<$VariantTy> for $to_type {
|
|
fn from(value: $VariantTy) -> Self {
|
|
$enum_name::$Variant(value).into()
|
|
}
|
|
}
|
|
}
|
|
)*
|
|
};
|
|
(
|
|
#[impl()]
|
|
#[to()]
|
|
$(#[$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]
|
|
$(#[$variant_meta:meta])*
|
|
$Variant:ident($VariantTy:ty),
|
|
)*
|
|
}
|
|
) => {
|
|
$(#[$enum_meta])*
|
|
$vis enum $enum_name<$T_enum: $T_bound = $T_enum_default> {
|
|
$(
|
|
$(#[$variant_meta])*
|
|
$Variant($VariantTy),
|
|
)*
|
|
}
|
|
|
|
impl<$T_enum: fmt::Debug + $T_bound> fmt::Debug for $enum_name<$T_enum> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
$(Self::$Variant(v) => v.fmt(f),)*
|
|
}
|
|
}
|
|
}
|
|
};
|
|
(
|
|
impl ($($T_to:tt)*) From<$VariantTy:ty> for $to_type:ty {
|
|
$from:item
|
|
}
|
|
) => {
|
|
impl $($T_to)* From<$VariantTy> for $to_type {
|
|
$from
|
|
}
|
|
};
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub struct StmtWire<S: ModuleBuildingStatus = ModuleBuilt> {
|
|
pub annotations: S::StmtAnnotations,
|
|
pub wire: Wire<Interned<dyn DynCanonicalType>>,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub struct StmtReg<S: ModuleBuildingStatus = ModuleBuilt> {
|
|
pub annotations: S::StmtAnnotations,
|
|
pub reg: Reg<Interned<dyn DynCanonicalType>>,
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub struct StmtInstance<S: ModuleBuildingStatus = ModuleBuilt> {
|
|
pub annotations: S::StmtAnnotations,
|
|
pub instance: Instance<DynBundle>,
|
|
}
|
|
|
|
wrapper_enum! {
|
|
#[impl(
|
|
(<S: ModuleBuildingStatus>) self: StmtDeclaration<S> = self,
|
|
(<S: ModuleBuildingStatus>) self: Stmt<S> = self.declaration()?
|
|
)]
|
|
#[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]
|
|
Wire(StmtWire<S>),
|
|
#[is = is_reg, as_ref = reg]
|
|
Reg(StmtReg<S>),
|
|
#[is = is_instance, as_ref = instance]
|
|
Instance(StmtInstance<S>),
|
|
}
|
|
}
|
|
|
|
impl<S: ModuleBuildingStatus> StmtDeclaration<S> {
|
|
pub fn annotations(&self) -> S::StmtAnnotations {
|
|
match self {
|
|
StmtDeclaration::Wire(v) => v.annotations,
|
|
StmtDeclaration::Reg(v) => v.annotations,
|
|
StmtDeclaration::Instance(v) => v.annotations,
|
|
}
|
|
}
|
|
pub fn source_location(&self) -> SourceLocation {
|
|
match self {
|
|
StmtDeclaration::Wire(v) => v.wire.source_location(),
|
|
StmtDeclaration::Reg(v) => v.reg.source_location(),
|
|
StmtDeclaration::Instance(v) => v.instance.source_location(),
|
|
}
|
|
}
|
|
pub fn scoped_name(&self) -> ScopedNameId {
|
|
match self {
|
|
StmtDeclaration::Wire(v) => v.wire.scoped_name(),
|
|
StmtDeclaration::Reg(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(_) => {
|
|
&[]
|
|
}
|
|
}
|
|
}
|
|
pub fn canonical_ty(&self) -> Interned<dyn DynCanonicalType> {
|
|
match self {
|
|
StmtDeclaration::Wire(v) => v.wire.ty(),
|
|
StmtDeclaration::Reg(v) => v.reg.ty(),
|
|
StmtDeclaration::Instance(v) => v.instance.ty().canonical_dyn(),
|
|
}
|
|
}
|
|
}
|
|
|
|
wrapper_enum! {
|
|
#[impl((<S: ModuleBuildingStatus>) self: Stmt<S> = self)]
|
|
#[to((<S: ModuleBuildingStatus>) Stmt<S>)]
|
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
pub enum Stmt<S: ModuleBuildingStatus = ModuleBuilt> {
|
|
#[is = is_connect, as_ref = connect]
|
|
Connect(StmtConnect),
|
|
#[is = is_if, as_ref = if_]
|
|
If(StmtIf<S>),
|
|
#[is = is_match, as_ref = match_]
|
|
Match(StmtMatch<S>),
|
|
#[is = is_declaration, as_ref = declaration]
|
|
Declaration(StmtDeclaration<S>),
|
|
}
|
|
}
|
|
|
|
impl<S: ModuleBuildingStatus> Stmt<S> {
|
|
pub fn sub_stmt_blocks(&self) -> &[S::Block] {
|
|
match self {
|
|
Stmt::Connect(_) => &[],
|
|
Stmt::If(v) => &v.blocks,
|
|
Stmt::Match(v) => &v.blocks,
|
|
Stmt::Declaration(v) => v.sub_stmt_blocks(),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct BlockStackEntry {
|
|
// entries are marked removed instead of
|
|
removed: bool,
|
|
block_id: BlockId,
|
|
}
|
|
|
|
struct BlockStack(RefCell<Vec<BlockStackEntry>>);
|
|
|
|
impl BlockStack {
|
|
fn new(top_block: BlockId) -> Self {
|
|
Self(RefCell::new(vec![BlockStackEntry {
|
|
removed: false,
|
|
block_id: top_block,
|
|
}]))
|
|
}
|
|
fn push_scope(self: Rc<Self>, block_id: BlockId) -> Scope {
|
|
let mut block_stack = self.0.borrow_mut();
|
|
Self::clean_stack_and_get_top(&mut block_stack);
|
|
let block_stack_index = block_stack.len();
|
|
block_stack.push(BlockStackEntry {
|
|
removed: false,
|
|
block_id,
|
|
});
|
|
drop(block_stack);
|
|
Scope {
|
|
block_id,
|
|
block_stack_index,
|
|
block_stack: self,
|
|
}
|
|
}
|
|
fn clean_stack_and_get_top(block_stack: &mut Vec<BlockStackEntry>) -> &BlockStackEntry {
|
|
for i in (0..block_stack.len()).rev() {
|
|
if block_stack[i].removed {
|
|
block_stack.pop();
|
|
} else {
|
|
return &block_stack[i];
|
|
}
|
|
}
|
|
unreachable!("top block should never be popped from block stack");
|
|
}
|
|
fn top(&self) -> BlockId {
|
|
let mut block_stack = self.0.borrow_mut();
|
|
Self::clean_stack_and_get_top(&mut block_stack).block_id
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
|
pub struct NameId(pub Interned<str>, pub usize);
|
|
|
|
impl fmt::Debug for NameId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
fmt::Display::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for NameId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
if self.0.is_empty() {
|
|
write!(f, "{}", self.1)
|
|
} else if self.1 == 0 {
|
|
f.write_str(&self.0)
|
|
} else {
|
|
write!(f, "{}_{}", self.0, self.1)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
|
pub struct ScopedNameId(pub NameId, pub NameId);
|
|
|
|
impl fmt::Debug for ScopedNameId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
self.0.fmt(f)?;
|
|
f.write_str("::")?;
|
|
self.1.fmt(f)
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
|
pub struct TargetName(pub ScopedNameId, pub Option<PortName>);
|
|
|
|
impl fmt::Debug for TargetName {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
self.0.fmt(f)?;
|
|
if let Some(port_name) = self.1 {
|
|
f.write_str("::")?;
|
|
port_name.fmt(f)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct NameIdGen(HashMap<Interned<str>, usize>);
|
|
|
|
impl NameIdGen {
|
|
pub fn gen(&mut self, name: Interned<str>) -> NameId {
|
|
let next_id = self.0.entry(name).or_default();
|
|
let id = *next_id;
|
|
*next_id += 1;
|
|
NameId(name, id)
|
|
}
|
|
pub fn mark_as_used(&mut self, name_id: NameId) {
|
|
let next_id = self.0.entry(name_id.0).or_default();
|
|
*next_id = (*next_id).max(name_id.1 + 1);
|
|
}
|
|
pub fn for_module(module: Module<DynBundle>) -> Self {
|
|
let mut retval = Self::default();
|
|
let Module {
|
|
name: _,
|
|
source_location: _,
|
|
body,
|
|
io_ty: _,
|
|
module_io,
|
|
module_annotations: _,
|
|
} = module;
|
|
for module_io in &module_io {
|
|
retval.mark_as_used(module_io.module_io.name_id());
|
|
}
|
|
match body {
|
|
ModuleBody::Extern(ExternModuleBody {
|
|
verilog_name: _,
|
|
parameters: _,
|
|
}) => {}
|
|
ModuleBody::Normal(NormalModuleBody { body }) => {
|
|
let mut blocks = vec![body];
|
|
while let Some(block) = blocks.pop() {
|
|
let Block { memories, stmts } = block;
|
|
for memory in memories {
|
|
retval.mark_as_used(memory.scoped_name().1);
|
|
}
|
|
for stmt in &stmts {
|
|
blocks.extend_from_slice(stmt.sub_stmt_blocks());
|
|
match stmt {
|
|
Stmt::Connect(StmtConnect {
|
|
lhs: _,
|
|
rhs: _,
|
|
source_location: _,
|
|
})
|
|
| Stmt::If(StmtIf {
|
|
cond: _,
|
|
source_location: _,
|
|
blocks: _,
|
|
})
|
|
| Stmt::Match(StmtMatch {
|
|
expr: _,
|
|
source_location: _,
|
|
blocks: _,
|
|
}) => {}
|
|
Stmt::Declaration(StmtDeclaration::Instance(StmtInstance {
|
|
annotations: _,
|
|
instance,
|
|
})) => {
|
|
retval.mark_as_used(instance.name_id());
|
|
}
|
|
Stmt::Declaration(StmtDeclaration::Reg(StmtReg {
|
|
annotations: _,
|
|
reg,
|
|
})) => {
|
|
retval.mark_as_used(reg.name_id());
|
|
}
|
|
Stmt::Declaration(StmtDeclaration::Wire(StmtWire {
|
|
annotations: _,
|
|
wire,
|
|
})) => {
|
|
retval.mark_as_used(wire.name_id());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
retval
|
|
}
|
|
pub fn gen_module_name(name: Interned<str>) -> NameId {
|
|
static MODULE_NAME_GEN: Mutex<Option<NameIdGen>> = Mutex::new(None);
|
|
MODULE_NAME_GEN
|
|
.lock()
|
|
.unwrap()
|
|
.get_or_insert_with(NameIdGen::default)
|
|
.gen(name)
|
|
}
|
|
}
|
|
|
|
pub struct Instance<T: BundleValue>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
scoped_name: ScopedNameId,
|
|
instantiated: Interned<Module<T>>,
|
|
source_location: SourceLocation,
|
|
}
|
|
|
|
impl<T: BundleValue> Clone for Instance<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn clone(&self) -> Self {
|
|
*self
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> Copy for Instance<T> where T::Type: BundleType<Value = T> {}
|
|
|
|
impl<T: BundleValue> Eq for Instance<T> where T::Type: BundleType<Value = T> {}
|
|
|
|
impl<T: BundleValue> fmt::Debug for Instance<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("Instance")
|
|
.field("name", &self.scoped_name)
|
|
.field("instantiated", &self.instantiated.debug_short())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> PartialEq for Instance<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn eq(&self, other: &Self) -> bool {
|
|
let Self {
|
|
scoped_name,
|
|
instantiated,
|
|
source_location,
|
|
} = *self;
|
|
scoped_name == other.scoped_name
|
|
&& instantiated == other.instantiated
|
|
&& source_location == other.source_location
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> Hash for Instance<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
let Self {
|
|
scoped_name,
|
|
instantiated,
|
|
source_location,
|
|
} = self;
|
|
scoped_name.hash(state);
|
|
instantiated.hash(state);
|
|
source_location.hash(state);
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> Instance<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
pub fn canonical(self) -> Instance<DynBundle> {
|
|
let Self {
|
|
scoped_name,
|
|
instantiated,
|
|
source_location,
|
|
} = self;
|
|
Instance {
|
|
scoped_name,
|
|
instantiated: instantiated.canonical().intern_sized(),
|
|
source_location,
|
|
}
|
|
}
|
|
pub fn containing_module_name(self) -> Interned<str> {
|
|
self.containing_module_name_id().0
|
|
}
|
|
pub fn containing_module_name_id(self) -> NameId {
|
|
self.scoped_name.0
|
|
}
|
|
pub fn name(self) -> Interned<str> {
|
|
self.name_id().0
|
|
}
|
|
pub fn name_id(self) -> NameId {
|
|
self.scoped_name.1
|
|
}
|
|
pub fn scoped_name(self) -> ScopedNameId {
|
|
self.scoped_name
|
|
}
|
|
pub fn instantiated(self) -> Interned<Module<T>> {
|
|
self.instantiated
|
|
}
|
|
pub fn source_location(self) -> SourceLocation {
|
|
self.source_location
|
|
}
|
|
pub fn new_unchecked(
|
|
scoped_name: ScopedNameId,
|
|
instantiated: Interned<Module<T>>,
|
|
source_location: SourceLocation,
|
|
) -> Self {
|
|
Self {
|
|
scoped_name,
|
|
instantiated,
|
|
source_location,
|
|
}
|
|
}
|
|
pub fn flow(&self) -> Flow {
|
|
Flow::Source
|
|
}
|
|
pub fn must_connect_to(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
mod module_kind_trait_sealed {
|
|
pub trait Sealed {}
|
|
}
|
|
|
|
pub trait ModuleKindTrait:
|
|
Into<ModuleKind>
|
|
+ 'static
|
|
+ Send
|
|
+ Sync
|
|
+ Copy
|
|
+ Ord
|
|
+ Hash
|
|
+ fmt::Debug
|
|
+ Default
|
|
+ module_kind_trait_sealed::Sealed
|
|
{
|
|
const MODULE_KIND: ModuleKind;
|
|
type ModuleBuilderBody: fmt::Debug;
|
|
type ModuleBody: Into<ModuleBody>
|
|
+ From<Self::ModuleBuilderBody>
|
|
+ 'static
|
|
+ Send
|
|
+ Sync
|
|
+ Copy
|
|
+ Eq
|
|
+ Hash
|
|
+ fmt::Debug;
|
|
fn builder_normal_body(
|
|
body: &mut Self::ModuleBuilderBody,
|
|
) -> Option<&mut NormalModuleBody<ModuleBuilding>>;
|
|
fn new_body(module_name: NameId) -> Self::ModuleBuilderBody;
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
|
pub struct ExternModule;
|
|
|
|
impl From<ExternModule> for ModuleKind {
|
|
fn from(value: ExternModule) -> Self {
|
|
Self::Extern(value)
|
|
}
|
|
}
|
|
|
|
impl module_kind_trait_sealed::Sealed for ExternModule {}
|
|
|
|
impl ModuleKindTrait for ExternModule {
|
|
const MODULE_KIND: ModuleKind = ModuleKind::Extern(Self);
|
|
type ModuleBuilderBody = ExternModuleBody<Vec<ExternModuleParameter>>;
|
|
type ModuleBody = ExternModuleBody;
|
|
fn builder_normal_body(
|
|
_body: &mut Self::ModuleBuilderBody,
|
|
) -> Option<&mut NormalModuleBody<ModuleBuilding>> {
|
|
None
|
|
}
|
|
fn new_body(name: NameId) -> Self::ModuleBuilderBody {
|
|
ExternModuleBody {
|
|
verilog_name: name.0,
|
|
parameters: vec![],
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
|
pub struct NormalModule;
|
|
|
|
impl From<NormalModule> for ModuleKind {
|
|
fn from(value: NormalModule) -> Self {
|
|
Self::Normal(value)
|
|
}
|
|
}
|
|
|
|
impl module_kind_trait_sealed::Sealed for NormalModule {}
|
|
|
|
impl ModuleKindTrait for NormalModule {
|
|
const MODULE_KIND: ModuleKind = ModuleKind::Normal(Self);
|
|
type ModuleBuilderBody = NormalModuleBody<ModuleBuilding>;
|
|
type ModuleBody = NormalModuleBody;
|
|
fn builder_normal_body(
|
|
body: &mut Self::ModuleBuilderBody,
|
|
) -> Option<&mut NormalModuleBody<ModuleBuilding>> {
|
|
Some(body)
|
|
}
|
|
fn new_body(_module_name: NameId) -> Self::ModuleBuilderBody {
|
|
NormalModuleBody {
|
|
body: BuilderModuleBody {
|
|
blocks: vec![BuilderBlock {
|
|
memories: vec![],
|
|
stmts: vec![],
|
|
}],
|
|
annotations_map: HashMap::new(),
|
|
memory_map: HashMap::new(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub struct AnnotatedModuleIO<S: ModuleBuildingStatus = ModuleBuilt> {
|
|
pub annotations: S::ModuleIOAnnotations,
|
|
pub module_io: ModuleIO<Interned<dyn DynCanonicalType>>,
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub enum ModuleKind {
|
|
Extern(ExternModule),
|
|
Normal(NormalModule),
|
|
}
|
|
|
|
pub struct ModuleBuilder<T: BundleValue, Kind: ModuleKindTrait>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
name: NameId,
|
|
body: Kind::ModuleBuilderBody,
|
|
block_stack: Rc<BlockStack>,
|
|
io: Vec<AnnotatedModuleIO<ModuleBuilding>>,
|
|
io_indexes: HashMap<ModuleIO<Interned<dyn DynCanonicalType>>, usize>,
|
|
io_fields_hint: FieldsHint,
|
|
name_id_gen: NameIdGen,
|
|
module_annotations: Vec<Annotation>,
|
|
_phantom: PhantomData<fn(Kind) -> T::Type>,
|
|
}
|
|
|
|
#[must_use]
|
|
pub struct Scope {
|
|
block_id: BlockId,
|
|
block_stack_index: usize,
|
|
block_stack: Rc<BlockStack>,
|
|
}
|
|
|
|
impl Drop for Scope {
|
|
fn drop(&mut self) {
|
|
let mut block_stack = self.block_stack.0.borrow_mut();
|
|
let entry = &mut block_stack[self.block_stack_index];
|
|
assert_eq!(
|
|
entry.block_id, self.block_id,
|
|
"block stack not managed properly",
|
|
);
|
|
assert!(!entry.removed, "block stack not managed properly",);
|
|
entry.removed = true; // only mark removed and don't pop it here so scopes can be dropped out of order
|
|
}
|
|
}
|
|
|
|
pub struct IfScope {
|
|
then: Scope,
|
|
else_block: BlockId,
|
|
}
|
|
|
|
impl IfScope {
|
|
pub fn else_(self) -> Scope {
|
|
let block_stack = self.then.block_stack.clone();
|
|
drop(self.then);
|
|
block_stack.push_scope(self.else_block)
|
|
}
|
|
}
|
|
|
|
pub(crate) struct EnumMatchVariantAndInactiveScopeImpl<T: EnumType>
|
|
where
|
|
T::Value: EnumValue<Type = T>,
|
|
{
|
|
block_stack: Rc<BlockStack>,
|
|
match_arm_block_id: BlockId,
|
|
variant_access: Interned<VariantAccess<T, Interned<dyn DynCanonicalType>>>,
|
|
}
|
|
|
|
impl<T: EnumType> EnumMatchVariantAndInactiveScopeImpl<T>
|
|
where
|
|
T::Value: EnumValue<Type = T>,
|
|
{
|
|
pub(crate) fn activate(
|
|
self,
|
|
) -> (
|
|
Interned<VariantAccess<T, Interned<dyn DynCanonicalType>>>,
|
|
Scope,
|
|
) {
|
|
let scope = self.block_stack.push_scope(self.match_arm_block_id);
|
|
(self.variant_access, scope)
|
|
}
|
|
pub(crate) fn variant_access(
|
|
&self,
|
|
) -> Interned<VariantAccess<T, Interned<dyn DynCanonicalType>>> {
|
|
self.variant_access
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct EnumMatchVariantsIterImpl<T> {
|
|
block_stack: Rc<BlockStack>,
|
|
blocks: Interned<[BlockId]>,
|
|
base: Expr<DynEnum>,
|
|
_phantom: PhantomData<T>,
|
|
}
|
|
|
|
impl<T: EnumType> EnumMatchVariantsIterImpl<T>
|
|
where
|
|
T::Value: EnumValue<Type = T>,
|
|
{
|
|
pub(crate) fn for_variant_index(
|
|
&self,
|
|
variant_index: usize,
|
|
) -> EnumMatchVariantAndInactiveScopeImpl<T> {
|
|
EnumMatchVariantAndInactiveScopeImpl {
|
|
block_stack: self.block_stack.clone(),
|
|
match_arm_block_id: self.blocks[variant_index],
|
|
variant_access: Intern::intern_sized(VariantAccess::new_unchecked(
|
|
self.base,
|
|
variant_index,
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
|
pub enum ExternModuleParameterValue {
|
|
Integer(BigInt),
|
|
String(Interned<str>),
|
|
RawVerilog(Interned<str>),
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
|
pub struct ExternModuleParameter {
|
|
pub name: Interned<str>,
|
|
pub value: ExternModuleParameterValue,
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
|
pub struct NormalModuleBody<S: ModuleBuildingStatus = ModuleBuilt> {
|
|
pub body: S::ModuleBody,
|
|
}
|
|
|
|
impl From<NormalModuleBody<ModuleBuilding>> for NormalModuleBody {
|
|
fn from(value: NormalModuleBody<ModuleBuilding>) -> Self {
|
|
let NormalModuleBody {
|
|
body:
|
|
BuilderModuleBody {
|
|
mut blocks,
|
|
mut annotations_map,
|
|
memory_map: _,
|
|
},
|
|
} = value;
|
|
fn finalize_block(
|
|
blocks: &mut [BuilderBlock],
|
|
annotations_map: &mut HashMap<StmtDeclaration<ModuleBuilding>, Vec<TargetedAnnotation>>,
|
|
block_id: BlockId,
|
|
) -> Block {
|
|
let BuilderBlock { memories, stmts } = &mut blocks[block_id.as_usize()];
|
|
let memories = Interned::from_iter(
|
|
memories
|
|
.drain(..)
|
|
.filter_map(|memory| memory.borrow().make_memory()),
|
|
);
|
|
let stmts = std::mem::take(stmts);
|
|
let stmts = Interned::from_iter(stmts.into_iter().map(|stmt| {
|
|
match stmt {
|
|
Stmt::Connect(stmt) => stmt.into(),
|
|
Stmt::If(StmtIf {
|
|
cond,
|
|
source_location,
|
|
blocks: if_blocks,
|
|
}) => StmtIf {
|
|
cond,
|
|
source_location,
|
|
blocks: if_blocks
|
|
.map(|block_id| finalize_block(blocks, annotations_map, block_id)),
|
|
}
|
|
.into(),
|
|
Stmt::Match(StmtMatch {
|
|
expr,
|
|
source_location,
|
|
blocks: match_blocks,
|
|
}) => StmtMatch {
|
|
expr,
|
|
source_location,
|
|
blocks: Interned::from_iter(
|
|
match_blocks
|
|
.into_iter()
|
|
.map(|block_id| finalize_block(blocks, annotations_map, block_id)),
|
|
),
|
|
}
|
|
.into(),
|
|
Stmt::Declaration(decl) => {
|
|
let annotations = annotations_map
|
|
.remove(&decl)
|
|
.map(|v| Intern::intern_owned(v))
|
|
.unwrap_or_default();
|
|
match decl {
|
|
StmtDeclaration::Wire(StmtWire {
|
|
annotations: (),
|
|
wire,
|
|
}) => StmtWire { annotations, wire }.into(),
|
|
StmtDeclaration::Reg(StmtReg {
|
|
annotations: (),
|
|
reg,
|
|
}) => StmtReg { annotations, reg }.into(),
|
|
StmtDeclaration::Instance(StmtInstance {
|
|
annotations: (),
|
|
instance,
|
|
}) => StmtInstance {
|
|
annotations,
|
|
instance,
|
|
}
|
|
.into(),
|
|
}
|
|
}
|
|
}
|
|
}));
|
|
Block { memories, stmts }
|
|
}
|
|
Self {
|
|
body: finalize_block(&mut blocks, &mut annotations_map, BlockId::ENTRY_BLOCK),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<NormalModuleBody> for ModuleBody {
|
|
fn from(value: NormalModuleBody) -> Self {
|
|
Self::Normal(value)
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
|
pub struct ExternModuleBody<
|
|
P: Deref<Target = [ExternModuleParameter]> = Interned<[ExternModuleParameter]>,
|
|
> {
|
|
pub verilog_name: Interned<str>,
|
|
pub parameters: P,
|
|
}
|
|
|
|
impl From<ExternModuleBody<Vec<ExternModuleParameter>>> for ExternModuleBody {
|
|
fn from(value: ExternModuleBody<Vec<ExternModuleParameter>>) -> Self {
|
|
let ExternModuleBody {
|
|
verilog_name,
|
|
parameters,
|
|
} = value;
|
|
let parameters = Intern::intern_owned(parameters);
|
|
Self {
|
|
verilog_name,
|
|
parameters,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ExternModuleBody> for ModuleBody {
|
|
fn from(value: ExternModuleBody) -> Self {
|
|
Self::Extern(value)
|
|
}
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
|
pub enum ModuleBody {
|
|
Normal(NormalModuleBody),
|
|
Extern(ExternModuleBody),
|
|
}
|
|
|
|
impl ModuleBody {
|
|
pub fn kind(&self) -> ModuleKind {
|
|
match self {
|
|
Self::Normal(_) => ModuleKind::Normal(NormalModule),
|
|
Self::Extern(_) => ModuleKind::Extern(ExternModule),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The runtime representation of a Fayalite module. The preferred way to create a [`Module`] is by
|
|
/// calling a function annotated with the [`#[hdl_module]`][`crate::hdl_module`] attribute.
|
|
#[derive(PartialEq, Eq, Hash)]
|
|
pub struct Module<T: BundleValue>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
name: NameId,
|
|
source_location: SourceLocation,
|
|
body: ModuleBody,
|
|
io_ty: Interned<T::Type>,
|
|
module_io: Interned<[AnnotatedModuleIO]>,
|
|
module_annotations: Interned<[Annotation]>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct DebugFmtModulesState {
|
|
seen: HashSet<NameId>,
|
|
unwritten: VecDeque<(NameId, Box<dyn fmt::Debug>)>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct DebugFmtState {
|
|
modules_state: RefCell<Option<DebugFmtModulesState>>,
|
|
}
|
|
|
|
impl DebugFmtState {
|
|
fn with<F: FnOnce(&Self) -> R, R>(f: F) -> R {
|
|
thread_local! {
|
|
static STATE: RefCell<Option<DebugFmtState>> = RefCell::default();
|
|
}
|
|
let mut clear_on_drop = None;
|
|
struct ClearOnDrop;
|
|
impl Drop for ClearOnDrop {
|
|
fn drop(&mut self) {
|
|
STATE.with_borrow_mut(|v| *v = None);
|
|
}
|
|
}
|
|
STATE.with(|state| {
|
|
let mut read = state.borrow();
|
|
if read.is_none() {
|
|
drop(read);
|
|
clear_on_drop = Some(ClearOnDrop);
|
|
state.replace(Some(DebugFmtState::default()));
|
|
read = state.borrow();
|
|
}
|
|
f(read.as_ref().unwrap())
|
|
})
|
|
}
|
|
fn push_module_if_in_module<T: BundleValue>(&self, module: Module<T>) -> bool
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
DebugFmtState::with(|state| {
|
|
let mut modules_state_ref = state.modules_state.borrow_mut();
|
|
if let Some(modules_state) = &mut *modules_state_ref {
|
|
if modules_state.seen.insert(module.name) {
|
|
modules_state
|
|
.unwritten
|
|
.push_back((module.name, Box::new(DebugModuleBody(module))));
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
})
|
|
}
|
|
}
|
|
|
|
struct DebugModuleBody<T: BundleValue>(Module<T>)
|
|
where
|
|
T::Type: BundleType<Value = T>;
|
|
|
|
impl<T: BundleValue> fmt::Debug for DebugModuleBody<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let Module {
|
|
name: _,
|
|
body,
|
|
io_ty,
|
|
module_io,
|
|
source_location: _,
|
|
module_annotations,
|
|
} = &self.0;
|
|
let mut debug_struct = f.debug_struct("Module");
|
|
let kind = body.kind();
|
|
debug_struct
|
|
.field("io_ty", io_ty)
|
|
.field("module_io", module_io)
|
|
.field("module_annotations", module_annotations)
|
|
.field("kind", &kind);
|
|
match body {
|
|
ModuleBody::Normal(NormalModuleBody { body }) => {
|
|
debug_struct.field("body", body);
|
|
}
|
|
ModuleBody::Extern(ExternModuleBody {
|
|
verilog_name,
|
|
parameters,
|
|
}) => {
|
|
debug_struct
|
|
.field("verilog_name", verilog_name)
|
|
.field("parameters", parameters);
|
|
}
|
|
}
|
|
debug_struct.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
pub struct ModuleDebugShort<T: BundleValue>(Module<T>)
|
|
where
|
|
T::Type: BundleType<Value = T>;
|
|
|
|
impl<T: BundleValue> fmt::Debug for ModuleDebugShort<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
DebugFmtState::with(|state| state.push_module_if_in_module(self.0));
|
|
f.debug_struct("Module")
|
|
.field("name", &self.0.name)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> Module<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
pub fn debug_short(self) -> ModuleDebugShort<T> {
|
|
ModuleDebugShort(self)
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> fmt::Debug for Module<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
DebugFmtState::with(|state| {
|
|
struct Outermost<'a>(&'a DebugFmtState);
|
|
impl Drop for Outermost<'_> {
|
|
fn drop(&mut self) {
|
|
self.0.modules_state.replace(None);
|
|
}
|
|
}
|
|
let mut outermost: Option<Outermost<'_>> = None;
|
|
state.modules_state.borrow_mut().get_or_insert_with(|| {
|
|
outermost = Some(Outermost(state));
|
|
DebugFmtModulesState::default()
|
|
});
|
|
state.push_module_if_in_module(*self);
|
|
if outermost.is_some() {
|
|
let mut debug = f.debug_map();
|
|
loop {
|
|
let mut modules_state_ref = state.modules_state.borrow_mut();
|
|
let modules_state = modules_state_ref.as_mut().unwrap();
|
|
let Some((name, m)) = modules_state.unwritten.pop_front() else {
|
|
break;
|
|
};
|
|
drop(modules_state_ref);
|
|
debug.entry(&name, &m);
|
|
}
|
|
debug.finish()
|
|
} else {
|
|
self.debug_short().fmt(f)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> Clone for Module<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn clone(&self) -> Self {
|
|
*self
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> Copy for Module<T> where T::Type: BundleType<Value = T> {}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
|
pub struct SubBlockId {
|
|
pub block: BlockId,
|
|
pub stmt_index: usize,
|
|
pub sub_stmt_index: usize,
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
|
|
enum TargetWritten {
|
|
#[default]
|
|
Unwritten,
|
|
MaybeWritten,
|
|
Written,
|
|
}
|
|
|
|
impl TargetWritten {
|
|
fn conditional_merge_written(self, other: Self) -> Self {
|
|
match (self, other) {
|
|
(Self::Written, Self::Written) => Self::Written,
|
|
(Self::MaybeWritten | Self::Written, _) | (_, Self::MaybeWritten | Self::Written) => {
|
|
Self::MaybeWritten
|
|
}
|
|
(Self::Unwritten, Self::Unwritten) => Self::Unwritten,
|
|
}
|
|
}
|
|
fn most_written(self, other: Self) -> Self {
|
|
if self as u8 > other as u8 {
|
|
self
|
|
} else {
|
|
other
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum TargetStateInner {
|
|
Single {
|
|
declared_in_block: usize,
|
|
written_in_blocks: RefCell<Vec<TargetWritten>>,
|
|
},
|
|
Decomposed {
|
|
subtargets: HashMap<Interned<TargetPathElement>, TargetState>,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct TargetState {
|
|
target: Interned<Target>,
|
|
inner: TargetStateInner,
|
|
}
|
|
|
|
impl TargetState {
|
|
#[track_caller]
|
|
fn assert_written(&self) {
|
|
match &self.inner {
|
|
TargetStateInner::Single {
|
|
declared_in_block,
|
|
written_in_blocks,
|
|
} => {
|
|
if self.target.flow() != Flow::Source {
|
|
match written_in_blocks
|
|
.borrow()
|
|
.get(*declared_in_block)
|
|
.copied()
|
|
.unwrap_or_default()
|
|
{
|
|
TargetWritten::Unwritten => panic!(
|
|
"at {}: {} is not connected to",
|
|
self.target.base().source_location(),
|
|
self.target,
|
|
),
|
|
TargetWritten::MaybeWritten => panic!(
|
|
"at {}: {} is not always connected to",
|
|
self.target.base().source_location(),
|
|
self.target,
|
|
),
|
|
TargetWritten::Written => {}
|
|
}
|
|
}
|
|
}
|
|
TargetStateInner::Decomposed { subtargets } => {
|
|
for subtarget in subtargets.values() {
|
|
subtarget.assert_written();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fn merge_conditional_sub_blocks_into_block(
|
|
&self,
|
|
target_block: usize,
|
|
sub_blocks: impl Clone + IntoIterator<Item = usize>,
|
|
) {
|
|
match &self.inner {
|
|
TargetStateInner::Single {
|
|
declared_in_block: _,
|
|
written_in_blocks,
|
|
} => {
|
|
let Some(written) = sub_blocks
|
|
.into_iter()
|
|
.map(|block| {
|
|
written_in_blocks
|
|
.borrow()
|
|
.get(block)
|
|
.copied()
|
|
.unwrap_or_default()
|
|
})
|
|
.reduce(TargetWritten::conditional_merge_written)
|
|
else {
|
|
unreachable!("merge_conditional_sub_blocks_into_block must be called with at least one sub-block");
|
|
};
|
|
let mut written_in_blocks = written_in_blocks.borrow_mut();
|
|
if target_block >= written_in_blocks.len() {
|
|
written_in_blocks.resize(target_block + 1, Default::default());
|
|
}
|
|
let target_written = &mut written_in_blocks[target_block];
|
|
*target_written = target_written.most_written(written);
|
|
}
|
|
TargetStateInner::Decomposed { subtargets } => {
|
|
for i in subtargets.values() {
|
|
i.merge_conditional_sub_blocks_into_block(target_block, sub_blocks.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fn new(target: Interned<Target>, declared_in_block: usize) -> Self {
|
|
Self {
|
|
target,
|
|
inner: match target.canonical_ty().type_enum() {
|
|
TypeEnum::BundleType(ty) => TargetStateInner::Decomposed {
|
|
subtargets: ty
|
|
.fields()
|
|
.iter()
|
|
.map(|&field| {
|
|
let path_element = TargetPathElement::intern_sized(
|
|
TargetPathBundleField { name: field.name }.into(),
|
|
);
|
|
(
|
|
path_element,
|
|
Self::new(
|
|
Target::intern_sized(target.join(path_element)),
|
|
declared_in_block,
|
|
),
|
|
)
|
|
})
|
|
.collect(),
|
|
},
|
|
TypeEnum::ArrayType(ty) => TargetStateInner::Decomposed {
|
|
subtargets: (0..ty.len())
|
|
.map(|index| {
|
|
let path_element =
|
|
Intern::intern_sized(TargetPathArrayElement { index }.into());
|
|
(
|
|
path_element,
|
|
Self::new(
|
|
Target::intern_sized(target.join(path_element)),
|
|
declared_in_block,
|
|
),
|
|
)
|
|
})
|
|
.collect(),
|
|
},
|
|
TypeEnum::EnumType(_)
|
|
| TypeEnum::UInt(_)
|
|
| TypeEnum::SInt(_)
|
|
| TypeEnum::Clock(_)
|
|
| TypeEnum::AsyncReset(_)
|
|
| TypeEnum::SyncReset(_)
|
|
| TypeEnum::Reset(_) => TargetStateInner::Single {
|
|
declared_in_block,
|
|
written_in_blocks: RefCell::default(),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
struct AssertValidityState {
|
|
module: Module<DynBundle>,
|
|
blocks: Vec<Block>,
|
|
target_states: HashMap<Interned<TargetBase>, TargetState>,
|
|
}
|
|
|
|
impl AssertValidityState {
|
|
fn make_block_index(&mut self, block: Block) -> usize {
|
|
let retval = self.blocks.len();
|
|
self.blocks.push(block);
|
|
retval
|
|
}
|
|
fn get_target_states<'a>(
|
|
&'a self,
|
|
target: &Target,
|
|
process_target_state: &dyn Fn(&'a TargetState, bool),
|
|
) -> Result<(), ()> {
|
|
match target {
|
|
Target::Base(target_base) => {
|
|
let target_state = self.get_base_state(*target_base)?;
|
|
process_target_state(target_state, false);
|
|
Ok(())
|
|
}
|
|
Target::Child(target_child) => self.get_target_states(
|
|
&target_child.parent(),
|
|
&|target_state, exact_target_unknown| {
|
|
let TargetStateInner::Decomposed { subtargets } = &target_state.inner else {
|
|
unreachable!(
|
|
"TargetState::new makes TargetState tree match the Target type"
|
|
);
|
|
};
|
|
match *target_child.path_element() {
|
|
TargetPathElement::BundleField(_) => process_target_state(
|
|
subtargets
|
|
.get(&target_child.path_element())
|
|
.expect("bundle fields filled in by TargetState::new"),
|
|
exact_target_unknown,
|
|
),
|
|
TargetPathElement::ArrayElement(_) => process_target_state(
|
|
subtargets
|
|
.get(&target_child.path_element())
|
|
.expect("array elements filled in by TargetState::new"),
|
|
exact_target_unknown,
|
|
),
|
|
TargetPathElement::DynArrayElement(_) => {
|
|
for target_state in subtargets.values() {
|
|
process_target_state(target_state, true);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
),
|
|
}
|
|
}
|
|
fn get_base_state(&self, target_base: Interned<TargetBase>) -> Result<&TargetState, ()> {
|
|
self.target_states.get(&target_base).ok_or(())
|
|
}
|
|
#[track_caller]
|
|
fn insert_new_base(&mut self, target_base: Interned<TargetBase>, declared_in_block: usize) {
|
|
match self.target_states.entry(target_base) {
|
|
Entry::Occupied(_) => panic!(
|
|
"at {}: duplicate declaration: {target_base}",
|
|
target_base.source_location(),
|
|
),
|
|
Entry::Vacant(entry) => {
|
|
entry.insert(TargetState::new(
|
|
Target::intern_sized(target_base.into()),
|
|
declared_in_block,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
fn set_connect_target_written(
|
|
target_state: &TargetState,
|
|
is_lhs: bool,
|
|
block: usize,
|
|
exact_target_unknown: bool,
|
|
) {
|
|
match &target_state.inner {
|
|
TargetStateInner::Single {
|
|
declared_in_block: _,
|
|
written_in_blocks,
|
|
} => {
|
|
if is_lhs {
|
|
let mut written_in_blocks = written_in_blocks.borrow_mut();
|
|
if block >= written_in_blocks.len() {
|
|
written_in_blocks.resize(block + 1, Default::default());
|
|
}
|
|
let written = &mut written_in_blocks[block];
|
|
if exact_target_unknown {
|
|
*written = written.most_written(TargetWritten::MaybeWritten);
|
|
} else {
|
|
*written = TargetWritten::Written;
|
|
}
|
|
}
|
|
}
|
|
TargetStateInner::Decomposed { subtargets } => {
|
|
for (&path_element, sub_target_state) in subtargets {
|
|
match &*path_element {
|
|
&TargetPathElement::BundleField(_) => {
|
|
let field = sub_target_state
|
|
.target
|
|
.child()
|
|
.expect("known to be a child")
|
|
.bundle_field()
|
|
.expect("known to be a bundle field");
|
|
let sub_is_lhs = if field.flipped { !is_lhs } else { is_lhs };
|
|
Self::set_connect_target_written(
|
|
sub_target_state,
|
|
sub_is_lhs,
|
|
block,
|
|
exact_target_unknown,
|
|
);
|
|
}
|
|
TargetPathElement::ArrayElement { .. } => {
|
|
Self::set_connect_target_written(
|
|
sub_target_state,
|
|
is_lhs,
|
|
block,
|
|
exact_target_unknown,
|
|
);
|
|
}
|
|
TargetPathElement::DynArrayElement { .. } => {
|
|
Self::set_connect_target_written(sub_target_state, is_lhs, block, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#[track_caller]
|
|
fn set_connect_side_written(
|
|
&mut self,
|
|
expr: Expr<DynCanonicalValue>,
|
|
source_location: SourceLocation,
|
|
is_lhs: bool,
|
|
block: usize,
|
|
) {
|
|
let Some(target) = expr.target() else {
|
|
debug_assert!(!is_lhs, "the ModuleBuilder asserts lhs.target().is_some()");
|
|
return;
|
|
};
|
|
let result = self.get_target_states(&target, &|target_state, exact_target_unknown| {
|
|
Self::set_connect_target_written(target_state, is_lhs, block, exact_target_unknown);
|
|
});
|
|
if result.is_err() {
|
|
if is_lhs {
|
|
panic!("at {source_location}: tried to connect to not-yet-defined item: {target}");
|
|
} else {
|
|
panic!(
|
|
"at {source_location}: tried to connect from not-yet-defined item: {target}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
fn process_conditional_sub_blocks(
|
|
&mut self,
|
|
parent_block: usize,
|
|
sub_blocks: impl Clone + IntoIterator<Item = usize>,
|
|
) {
|
|
for i in sub_blocks.clone() {
|
|
self.assert_subtree_validity(i);
|
|
}
|
|
for i in self.target_states.values() {
|
|
i.merge_conditional_sub_blocks_into_block(parent_block, sub_blocks.clone());
|
|
}
|
|
}
|
|
#[track_caller]
|
|
fn assert_subtree_validity(&mut self, block: usize) {
|
|
let module = self.module;
|
|
if block == 0 {
|
|
for module_io in &*module.module_io {
|
|
self.insert_new_base(
|
|
TargetBase::intern_sized(module_io.module_io.clone().into()),
|
|
block,
|
|
);
|
|
}
|
|
}
|
|
let Block { memories, stmts } = self.blocks[block];
|
|
for m in memories {
|
|
for port in m.ports() {
|
|
self.insert_new_base(TargetBase::intern_sized((*port).into()), block);
|
|
}
|
|
}
|
|
for stmt in stmts {
|
|
match stmt {
|
|
Stmt::Connect(StmtConnect {
|
|
lhs,
|
|
rhs,
|
|
source_location,
|
|
}) => {
|
|
self.set_connect_side_written(lhs, source_location, true, block);
|
|
self.set_connect_side_written(rhs, source_location, false, block);
|
|
}
|
|
Stmt::If(if_stmt) => {
|
|
let sub_blocks = if_stmt.blocks.map(|block| self.make_block_index(block));
|
|
self.process_conditional_sub_blocks(block, sub_blocks)
|
|
}
|
|
Stmt::Match(match_stmt) => {
|
|
let sub_blocks = Vec::from_iter(
|
|
match_stmt
|
|
.blocks
|
|
.into_iter()
|
|
.map(|block| self.make_block_index(block)),
|
|
);
|
|
self.process_conditional_sub_blocks(block, sub_blocks.iter().copied())
|
|
}
|
|
Stmt::Declaration(StmtDeclaration::Wire(StmtWire {
|
|
annotations: _,
|
|
wire,
|
|
})) => self.insert_new_base(TargetBase::intern_sized(wire.into()), block),
|
|
Stmt::Declaration(StmtDeclaration::Reg(StmtReg {
|
|
annotations: _,
|
|
reg,
|
|
})) => self.insert_new_base(TargetBase::intern_sized(reg.into()), block),
|
|
Stmt::Declaration(StmtDeclaration::Instance(StmtInstance {
|
|
annotations: _,
|
|
instance,
|
|
})) => self.insert_new_base(TargetBase::intern_sized(instance.into()), block),
|
|
}
|
|
}
|
|
}
|
|
#[track_caller]
|
|
fn assert_validity(&mut self) {
|
|
match self.module.body {
|
|
ModuleBody::Extern(ExternModuleBody {
|
|
verilog_name: _,
|
|
parameters: _,
|
|
}) => {}
|
|
ModuleBody::Normal(NormalModuleBody { body }) => {
|
|
let body = self.make_block_index(body);
|
|
assert_eq!(body, 0);
|
|
self.assert_subtree_validity(body);
|
|
for (base, state) in &self.target_states {
|
|
if base.must_connect_to() {
|
|
state.assert_written();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> Module<T>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
/// you generally should use the [`#[hdl_module]`][`crate::hdl_module`] proc-macro and [`ModuleBuilder`] instead
|
|
pub fn new_unchecked(
|
|
name_id: NameId,
|
|
source_location: SourceLocation,
|
|
body: ModuleBody,
|
|
module_io: impl IntoIterator<Item = AnnotatedModuleIO>,
|
|
module_annotations: impl IntoAnnotations,
|
|
) -> Module<T> {
|
|
let module_io: Interned<[_]> = module_io.into_iter().collect();
|
|
let module_annotations = module_annotations.into_annotations().into_iter().collect();
|
|
let retval = Module {
|
|
name: name_id,
|
|
source_location,
|
|
body,
|
|
io_ty: T::Type::from_canonical_type(DynBundleType::new(Intern::intern_owned(
|
|
Vec::from_iter(module_io.iter().map(|io| io.module_io.field_type)),
|
|
)))
|
|
.intern_sized(),
|
|
module_io,
|
|
module_annotations,
|
|
};
|
|
retval.assert_validity();
|
|
retval
|
|
}
|
|
pub fn canonical(self) -> Module<DynBundle> {
|
|
Module {
|
|
name: self.name,
|
|
body: self.body,
|
|
io_ty: self.io_ty.canonical().intern_sized(),
|
|
module_io: self.module_io,
|
|
source_location: self.source_location,
|
|
module_annotations: self.module_annotations,
|
|
}
|
|
}
|
|
pub fn from_canonical(canonical_module: Module<DynBundle>) -> Self {
|
|
Module {
|
|
name: canonical_module.name,
|
|
body: canonical_module.body,
|
|
io_ty: T::Type::from_canonical_type(*canonical_module.io_ty).intern_sized(),
|
|
module_io: canonical_module.module_io,
|
|
source_location: canonical_module.source_location,
|
|
module_annotations: canonical_module.module_annotations,
|
|
}
|
|
}
|
|
pub fn top_block(self) -> Option<Block> {
|
|
match self.body {
|
|
ModuleBody::Normal(NormalModuleBody { body }) => Some(body),
|
|
ModuleBody::Extern(_) => None,
|
|
}
|
|
}
|
|
pub fn kind(self) -> ModuleKind {
|
|
self.body.kind()
|
|
}
|
|
pub fn body(self) -> ModuleBody {
|
|
self.body
|
|
}
|
|
pub fn io_ty(self) -> Interned<T::Type> {
|
|
self.io_ty
|
|
}
|
|
pub fn module_io(self) -> Interned<[AnnotatedModuleIO]> {
|
|
self.module_io
|
|
}
|
|
pub fn name(self) -> Interned<str> {
|
|
self.name_id().0
|
|
}
|
|
pub fn name_id(self) -> NameId {
|
|
self.name
|
|
}
|
|
pub fn source_location(self) -> SourceLocation {
|
|
self.source_location
|
|
}
|
|
pub fn module_annotations(self) -> Interned<[Annotation]> {
|
|
self.module_annotations
|
|
}
|
|
#[track_caller]
|
|
pub fn assert_validity(self) {
|
|
AssertValidityState {
|
|
module: self.canonical(),
|
|
blocks: vec![],
|
|
target_states: HashMap::with_capacity(64),
|
|
}
|
|
.assert_validity();
|
|
}
|
|
}
|
|
|
|
#[must_use = "does nothing unless `build` is called"]
|
|
pub struct RegBuilder<'a, CD, I, V: Value> {
|
|
scoped_name: ScopedNameId,
|
|
source_location: SourceLocation,
|
|
clock_domain: CD,
|
|
init: I,
|
|
ty: V::Type,
|
|
stmts: &'a mut Vec<Stmt<ModuleBuilding>>,
|
|
}
|
|
|
|
impl<'a, CD> RegBuilder<'a, CD, (), ()> {
|
|
pub fn no_reset<V: Value>(self, ty: V::Type) -> RegBuilder<'a, CD, Option<Expr<V>>, V> {
|
|
let Self {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain,
|
|
init: _,
|
|
ty: _,
|
|
stmts,
|
|
} = self;
|
|
RegBuilder {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain,
|
|
init: None,
|
|
ty,
|
|
stmts,
|
|
}
|
|
}
|
|
pub fn reset_default<V: Value + Default>(self) -> RegBuilder<'a, CD, Option<Expr<V>>, V>
|
|
where
|
|
V::Type: FixedType<Value = V>,
|
|
{
|
|
self.reset(V::default().to_expr())
|
|
}
|
|
pub fn opt_reset<T: Type>(
|
|
self,
|
|
ty: T,
|
|
init: Option<impl ToExpr<Type = T>>,
|
|
) -> RegBuilder<'a, CD, Option<Expr<T::Value>>, T::Value> {
|
|
let Self {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain,
|
|
init: _,
|
|
ty: _,
|
|
stmts,
|
|
} = self;
|
|
RegBuilder {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain,
|
|
init: init.map(|e| e.to_expr()),
|
|
ty,
|
|
stmts,
|
|
}
|
|
}
|
|
pub fn reset<V: Value>(self, init: Expr<V>) -> RegBuilder<'a, CD, Option<Expr<V>>, V>
|
|
where
|
|
V::Type: Type<Value = V>,
|
|
{
|
|
let Self {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain,
|
|
init: _,
|
|
ty: _,
|
|
stmts,
|
|
} = self;
|
|
let ty = init.ty();
|
|
RegBuilder {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain,
|
|
init: Some(init),
|
|
ty,
|
|
stmts,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, I, V: Value> RegBuilder<'a, (), I, V> {
|
|
pub fn clock_domain(
|
|
self,
|
|
clock_domain: impl ToExpr<Type = <ClockDomain as ToExpr>::Type>,
|
|
) -> RegBuilder<'a, Expr<ClockDomain>, I, V> {
|
|
let Self {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain: _,
|
|
init,
|
|
ty,
|
|
stmts,
|
|
} = self;
|
|
RegBuilder {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain: clock_domain.to_expr(),
|
|
init,
|
|
ty,
|
|
stmts,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<V: Value> RegBuilder<'_, Expr<ClockDomain>, Option<Expr<V>>, V>
|
|
where
|
|
V::Type: Type<Value = V>,
|
|
{
|
|
#[track_caller]
|
|
pub fn build(self) -> Expr<V> {
|
|
let Self {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain,
|
|
init,
|
|
ty,
|
|
stmts,
|
|
} = self;
|
|
let reg =
|
|
Reg::<V::Type>::new_unchecked(scoped_name, source_location, ty, clock_domain, init);
|
|
let retval = reg.to_expr();
|
|
stmts.push(
|
|
StmtReg {
|
|
annotations: (),
|
|
reg: reg.to_dyn_canonical_reg(),
|
|
}
|
|
.into(),
|
|
);
|
|
retval
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue, Kind: ModuleKindTrait> ModuleBuilder<T, Kind>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
#[track_caller]
|
|
pub fn io_with_loc<IO: Value>(
|
|
&mut self,
|
|
name: &str,
|
|
source_location: SourceLocation,
|
|
is_input: bool,
|
|
ty: IO::Type,
|
|
) -> Expr<IO>
|
|
where
|
|
IO::Type: Type<Value = IO>,
|
|
{
|
|
let module_io = ModuleIO::<IO::Type>::new_unchecked(
|
|
self.name,
|
|
name.intern(),
|
|
source_location,
|
|
is_input,
|
|
ty,
|
|
);
|
|
if let Err(e) = self.io_fields_hint.check_field(
|
|
self.io.len(),
|
|
module_io
|
|
.field_type
|
|
.as_ref_ty()
|
|
.map_ty(|v: &IO::Type| -> &dyn DynType { v }),
|
|
) {
|
|
panic!("error: {source_location}: {e}");
|
|
}
|
|
let retval = module_io.to_expr();
|
|
let module_io = module_io.to_canonical_dyn_module_io();
|
|
self.io_indexes.insert(module_io.clone(), self.io.len());
|
|
self.io.push(AnnotatedModuleIO {
|
|
annotations: vec![],
|
|
module_io,
|
|
});
|
|
retval
|
|
}
|
|
#[track_caller]
|
|
pub fn io<IO: Value>(
|
|
&mut self,
|
|
implicit_name: ImplicitName<'_>,
|
|
is_input: bool,
|
|
ty: IO::Type,
|
|
) -> Expr<IO>
|
|
where
|
|
IO::Type: Type<Value = IO>,
|
|
{
|
|
self.io_with_loc(implicit_name.0, SourceLocation::caller(), is_input, ty)
|
|
}
|
|
#[track_caller]
|
|
pub fn input_with_loc<IO: Value>(
|
|
&mut self,
|
|
name: &str,
|
|
source_location: SourceLocation,
|
|
ty: IO::Type,
|
|
) -> Expr<IO>
|
|
where
|
|
IO::Type: Type<Value = IO>,
|
|
{
|
|
self.io_with_loc(name, source_location, true, ty)
|
|
}
|
|
#[track_caller]
|
|
pub fn output_with_loc<IO: Value>(
|
|
&mut self,
|
|
name: &str,
|
|
source_location: SourceLocation,
|
|
ty: IO::Type,
|
|
) -> Expr<IO>
|
|
where
|
|
IO::Type: Type<Value = IO>,
|
|
{
|
|
self.io_with_loc(name, source_location, false, ty)
|
|
}
|
|
#[track_caller]
|
|
pub fn input<IO: Value>(&mut self, implicit_name: ImplicitName<'_>, ty: IO::Type) -> Expr<IO>
|
|
where
|
|
IO::Type: Type<Value = IO>,
|
|
{
|
|
self.input_with_loc(implicit_name.0, SourceLocation::caller(), ty)
|
|
}
|
|
#[track_caller]
|
|
pub fn output<IO: Value>(&mut self, implicit_name: ImplicitName<'_>, ty: IO::Type) -> Expr<IO>
|
|
where
|
|
IO::Type: Type<Value = IO>,
|
|
{
|
|
self.output_with_loc(implicit_name.0, SourceLocation::caller(), ty)
|
|
}
|
|
#[track_caller]
|
|
pub fn run<F: FnOnce(&mut Self)>(name: &str, f: F) -> Interned<Module<T>> {
|
|
Self::run_with_loc(name, SourceLocation::caller(), f)
|
|
}
|
|
#[track_caller]
|
|
pub fn try_run<F: FnOnce(&mut Self) -> Result<(), E>, E>(
|
|
name: &str,
|
|
f: F,
|
|
) -> Result<Interned<Module<T>>, E> {
|
|
Self::try_run_with_loc(name, SourceLocation::caller(), f)
|
|
}
|
|
#[track_caller]
|
|
pub fn run_with_loc<F: FnOnce(&mut Self)>(
|
|
name: &str,
|
|
source_location: SourceLocation,
|
|
f: F,
|
|
) -> Interned<Module<T>> {
|
|
Self::try_run_with_loc(name, source_location, |this| -> Result<(), Infallible> {
|
|
f(this);
|
|
Ok(())
|
|
})
|
|
.unwrap()
|
|
}
|
|
#[track_caller]
|
|
pub fn try_run_with_loc<F: FnOnce(&mut Self) -> Result<(), E>, E>(
|
|
name: &str,
|
|
source_location: SourceLocation,
|
|
f: F,
|
|
) -> Result<Interned<Module<T>>, E> {
|
|
let name = NameIdGen::gen_module_name(name.intern());
|
|
let body = Kind::new_body(name);
|
|
let mut builder = Self {
|
|
name,
|
|
body,
|
|
block_stack: Rc::new(BlockStack::new(BlockId(0))),
|
|
io: vec![],
|
|
io_indexes: HashMap::new(),
|
|
io_fields_hint: T::Type::fields_hint(),
|
|
name_id_gen: NameIdGen::default(),
|
|
module_annotations: vec![],
|
|
_phantom: PhantomData,
|
|
};
|
|
f(&mut builder)?;
|
|
Ok(Module::new_unchecked(
|
|
builder.name,
|
|
source_location,
|
|
Kind::ModuleBody::from(builder.body).into(),
|
|
builder.io.into_iter().map(
|
|
|AnnotatedModuleIO {
|
|
annotations,
|
|
module_io,
|
|
}| AnnotatedModuleIO {
|
|
annotations: Intern::intern_owned(annotations),
|
|
module_io,
|
|
},
|
|
),
|
|
builder.module_annotations,
|
|
)
|
|
.intern_sized())
|
|
}
|
|
#[track_caller]
|
|
pub fn annotate_module(&mut self, annotations: impl IntoAnnotations) {
|
|
self.module_annotations
|
|
.extend(annotations.into_annotations());
|
|
}
|
|
#[track_caller]
|
|
pub fn annotate<V: Value>(&mut self, target: Expr<V>, annotations: impl IntoAnnotations) {
|
|
macro_rules! unwrap {
|
|
($v:expr) => {
|
|
match $v {
|
|
Some(v) => v,
|
|
None => panic!("not a valid annotation target"),
|
|
}
|
|
};
|
|
}
|
|
let target = unwrap!(target.target());
|
|
TargetedAnnotation::assert_valid_target(target);
|
|
let apply_annotations = |targeted_annotations: &mut Vec<TargetedAnnotation>| {
|
|
targeted_annotations.extend(
|
|
annotations
|
|
.into_annotations()
|
|
.into_iter()
|
|
.map(|annotation| TargetedAnnotation::new(target, annotation)),
|
|
)
|
|
};
|
|
let decl = match TargetBase::clone(&target.base()) {
|
|
TargetBase::ModuleIO(v) => {
|
|
apply_annotations(&mut self.io[*unwrap!(self.io_indexes.get(&v))].annotations);
|
|
return;
|
|
}
|
|
TargetBase::MemPort(v) => {
|
|
apply_annotations(
|
|
&mut unwrap!(unwrap!(Kind::builder_normal_body(&mut self.body))
|
|
.body
|
|
.memory_map
|
|
.get_mut(&v.mem_name()))
|
|
.borrow_mut()
|
|
.port_annotations,
|
|
);
|
|
return;
|
|
}
|
|
TargetBase::Reg(reg) => StmtReg {
|
|
annotations: (),
|
|
reg,
|
|
}
|
|
.into(),
|
|
TargetBase::Wire(wire) => StmtWire {
|
|
annotations: (),
|
|
wire,
|
|
}
|
|
.into(),
|
|
TargetBase::Instance(instance) => StmtInstance {
|
|
annotations: (),
|
|
instance,
|
|
}
|
|
.into(),
|
|
};
|
|
apply_annotations(
|
|
unwrap!(Kind::builder_normal_body(&mut self.body))
|
|
.body
|
|
.annotations_map
|
|
.entry(decl)
|
|
.or_default(),
|
|
);
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> ModuleBuilder<T, ExternModule>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
#[track_caller]
|
|
pub fn verilog_name(&mut self, name: impl AsRef<str>) {
|
|
let name = name.as_ref();
|
|
assert!(
|
|
name.chars()
|
|
.all(|ch| ch.is_ascii_alphanumeric() || ch == '_'),
|
|
"invalid verilog name -- FIRRTL doesn't yet support verilog names with special characters.\n\
|
|
see https://github.com/chipsalliance/firrtl-spec/issues/134"
|
|
);
|
|
self.body.verilog_name = name.intern();
|
|
}
|
|
pub fn parameter(&mut self, name: impl AsRef<str>, value: ExternModuleParameterValue) {
|
|
self.body.parameters.push(ExternModuleParameter {
|
|
name: name.as_ref().intern(),
|
|
value,
|
|
});
|
|
}
|
|
pub fn parameter_int(&mut self, name: impl AsRef<str>, value: impl Into<BigInt>) {
|
|
self.body.parameters.push(ExternModuleParameter {
|
|
name: name.as_ref().intern(),
|
|
value: ExternModuleParameterValue::Integer(value.into()),
|
|
});
|
|
}
|
|
pub fn parameter_str(&mut self, name: impl AsRef<str>, value: impl AsRef<str>) {
|
|
self.body.parameters.push(ExternModuleParameter {
|
|
name: name.as_ref().intern(),
|
|
value: ExternModuleParameterValue::String(value.as_ref().intern()),
|
|
});
|
|
}
|
|
pub fn parameter_raw_verilog(&mut self, name: impl AsRef<str>, raw_verilog: impl AsRef<str>) {
|
|
self.body.parameters.push(ExternModuleParameter {
|
|
name: name.as_ref().intern(),
|
|
value: ExternModuleParameterValue::RawVerilog(raw_verilog.as_ref().intern()),
|
|
});
|
|
}
|
|
}
|
|
|
|
impl<T: BundleValue> ModuleBuilder<T, NormalModule>
|
|
where
|
|
T::Type: BundleType<Value = T>,
|
|
{
|
|
fn block(&mut self, block_id: BlockId) -> &mut BuilderBlock {
|
|
&mut self.body.body.blocks[block_id.as_usize()]
|
|
}
|
|
fn new_block(&mut self) -> BlockId {
|
|
let index = self.body.body.blocks.len();
|
|
self.body.body.blocks.push(BuilderBlock {
|
|
memories: vec![],
|
|
stmts: vec![],
|
|
});
|
|
BlockId(index)
|
|
}
|
|
pub fn wire_with_loc<V: Value>(
|
|
&mut self,
|
|
name: &str,
|
|
source_location: SourceLocation,
|
|
ty: V::Type,
|
|
) -> Expr<V>
|
|
where
|
|
V::Type: Type<Value = V>,
|
|
{
|
|
let wire = Wire::<V::Type>::new_unchecked(
|
|
ScopedNameId(self.name, self.name_id_gen.gen(name.intern())),
|
|
source_location,
|
|
ty,
|
|
);
|
|
let retval = wire.to_expr();
|
|
self.block(self.block_stack.top()).stmts.push(
|
|
StmtWire {
|
|
annotations: (),
|
|
wire: wire.to_dyn_canonical_wire(),
|
|
}
|
|
.into(),
|
|
);
|
|
retval
|
|
}
|
|
#[track_caller]
|
|
pub fn wire<V: Value>(&mut self, implicit_name: ImplicitName<'_>, ty: V::Type) -> Expr<V>
|
|
where
|
|
V::Type: Type<Value = V>,
|
|
{
|
|
self.wire_with_loc(implicit_name.0, SourceLocation::caller(), ty)
|
|
}
|
|
#[track_caller]
|
|
pub fn reg_builder_with_loc<'a>(
|
|
&'a mut self,
|
|
name: &str,
|
|
source_location: SourceLocation,
|
|
) -> RegBuilder<'a, (), (), ()> {
|
|
let scoped_name = ScopedNameId(self.name, self.name_id_gen.gen(name.intern()));
|
|
let stmts = &mut self.block(self.block_stack.top()).stmts;
|
|
RegBuilder {
|
|
scoped_name,
|
|
source_location,
|
|
clock_domain: (),
|
|
init: (),
|
|
ty: (),
|
|
stmts,
|
|
}
|
|
}
|
|
#[track_caller]
|
|
pub fn reg_builder<'a>(
|
|
&'a mut self,
|
|
implicit_name: ImplicitName<'_>,
|
|
) -> RegBuilder<'a, (), (), ()> {
|
|
self.reg_builder_with_loc(implicit_name.0, SourceLocation::caller())
|
|
}
|
|
// intentionally takes Expr instead of impl ToExpr to help prevent
|
|
// `#[hdl] if a == b {}` instead of using `cmp_eq`
|
|
#[track_caller]
|
|
pub fn if_<Ty>(&mut self, cond: Expr<IntValue<Ty>>) -> IfScope
|
|
where
|
|
Ty: FixedOrDynIntType<
|
|
1,
|
|
Signed = ConstBool<false>,
|
|
CanonicalType = DynUIntType,
|
|
CanonicalValue = DynUInt,
|
|
>,
|
|
{
|
|
self.if_with_loc(cond, SourceLocation::caller())
|
|
}
|
|
pub fn if_with_loc<Ty>(
|
|
&mut self,
|
|
cond: Expr<IntValue<Ty>>,
|
|
source_location: SourceLocation,
|
|
) -> IfScope
|
|
where
|
|
Ty: FixedOrDynIntType<
|
|
1,
|
|
Signed = ConstBool<false>,
|
|
CanonicalType = DynUIntType,
|
|
CanonicalValue = DynUInt,
|
|
>,
|
|
{
|
|
let cond = cond.as_bool();
|
|
let outer_block = self.block_stack.top();
|
|
let then_block = self.new_block();
|
|
let else_block = self.new_block();
|
|
self.block(outer_block).stmts.push(
|
|
StmtIf {
|
|
cond,
|
|
source_location,
|
|
blocks: [then_block, else_block],
|
|
}
|
|
.into(),
|
|
);
|
|
IfScope {
|
|
then: self.block_stack.clone().push_scope(then_block),
|
|
else_block,
|
|
}
|
|
}
|
|
pub fn enum_match_variants_helper<Enum: EnumValue>(
|
|
&mut self,
|
|
base: Expr<Enum>,
|
|
source_location: SourceLocation,
|
|
) -> EnumMatchVariantsIter<Enum::Type>
|
|
where
|
|
Enum::Type: EnumType<Value = Enum>,
|
|
{
|
|
let base = base.canonical();
|
|
let enum_ty = base.ty();
|
|
let outer_block = self.block_stack.top();
|
|
let blocks = Intern::intern_owned(Vec::from_iter(
|
|
enum_ty.variants().iter().map(|_| self.new_block()),
|
|
));
|
|
self.block(outer_block).stmts.push(
|
|
StmtMatch {
|
|
expr: base,
|
|
source_location,
|
|
blocks,
|
|
}
|
|
.into(),
|
|
);
|
|
let block_stack = self.block_stack.clone();
|
|
EnumMatchVariantsIter {
|
|
inner: EnumMatchVariantsIterImpl {
|
|
block_stack,
|
|
blocks,
|
|
base,
|
|
_phantom: PhantomData,
|
|
},
|
|
variant_index: 0..blocks.len(),
|
|
}
|
|
}
|
|
#[track_caller]
|
|
pub fn match_<Ty: Type>(&mut self, expr: impl ToExpr<Type = Ty>) -> Ty::MatchVariantsIter {
|
|
self.match_with_loc(expr, SourceLocation::caller())
|
|
}
|
|
pub fn match_with_loc<Ty: Type>(
|
|
&mut self,
|
|
expr: impl ToExpr<Type = Ty>,
|
|
source_location: SourceLocation,
|
|
) -> Ty::MatchVariantsIter {
|
|
Ty::match_variants(expr.to_expr(), self, source_location)
|
|
}
|
|
#[track_caller]
|
|
pub fn connect_any_with_loc<Lhs: ToExpr, Rhs: ToExpr>(
|
|
&mut self,
|
|
lhs: Lhs,
|
|
rhs: Rhs,
|
|
source_location: SourceLocation,
|
|
) where
|
|
Lhs::Type: Connect<Rhs::Type>
|
|
+ Type<
|
|
CanonicalType = <Rhs::Type as Type>::CanonicalType,
|
|
CanonicalValue = <Rhs::Type as Type>::CanonicalValue,
|
|
>,
|
|
{
|
|
let lhs = lhs.to_expr();
|
|
let rhs = rhs.to_expr();
|
|
assert!(
|
|
matches!(lhs.flow(), Flow::Sink | Flow::Duplex),
|
|
"can't connect to source, connect lhs must have sink or duplex flow"
|
|
);
|
|
assert!(lhs.target().is_some(), "can't connect to non-target");
|
|
match rhs.flow() {
|
|
Flow::Source | Flow::Duplex => {}
|
|
Flow::Sink => assert!(
|
|
rhs.ty().is_passive(),
|
|
"can't connect from sink with non-passive type"
|
|
),
|
|
}
|
|
assert!(
|
|
lhs.canonical_type().can_connect(&rhs.canonical_type()),
|
|
"can't connect types that are not equivalent:\nlhs type:\n{:?}\nrhs type:\n{:?}",
|
|
lhs.ty(),
|
|
rhs.ty()
|
|
);
|
|
self.block(self.block_stack.top()).stmts.push(
|
|
StmtConnect {
|
|
lhs: lhs.to_canonical_dyn(),
|
|
rhs: rhs.to_canonical_dyn(),
|
|
source_location,
|
|
}
|
|
.into(),
|
|
);
|
|
}
|
|
#[track_caller]
|
|
pub fn connect_any<Lhs: ToExpr, Rhs: ToExpr>(&mut self, lhs: Lhs, rhs: Rhs)
|
|
where
|
|
Lhs::Type: Connect<Rhs::Type>
|
|
+ Type<
|
|
CanonicalType = <Rhs::Type as Type>::CanonicalType,
|
|
CanonicalValue = <Rhs::Type as Type>::CanonicalValue,
|
|
>,
|
|
{
|
|
self.connect_any_with_loc(lhs, rhs, SourceLocation::caller())
|
|
}
|
|
#[track_caller]
|
|
pub fn connect<V: Value>(&mut self, lhs: Expr<V>, rhs: Expr<V>)
|
|
where
|
|
V::Type: Type<Value = V>,
|
|
{
|
|
self.connect_with_loc(lhs, rhs, SourceLocation::caller())
|
|
}
|
|
#[track_caller]
|
|
pub fn connect_with_loc<V: Value>(
|
|
&mut self,
|
|
lhs: Expr<V>,
|
|
rhs: Expr<V>,
|
|
source_location: SourceLocation,
|
|
) where
|
|
V::Type: Type<Value = V>,
|
|
{
|
|
self.connect_any_with_loc(lhs, rhs, source_location)
|
|
}
|
|
pub fn instance_with_loc<T2: BundleValue>(
|
|
&mut self,
|
|
name: &str,
|
|
instantiated: Interned<Module<T2>>,
|
|
source_location: SourceLocation,
|
|
) -> Expr<T2>
|
|
where
|
|
T2::Type: BundleType<Value = T2>,
|
|
{
|
|
let instance = Instance::<T2> {
|
|
scoped_name: ScopedNameId(self.name, self.name_id_gen.gen(name.intern())),
|
|
instantiated,
|
|
source_location,
|
|
};
|
|
self.block(self.block_stack.top()).stmts.push(
|
|
StmtInstance {
|
|
annotations: (),
|
|
instance: instance.canonical(),
|
|
}
|
|
.into(),
|
|
);
|
|
instance.to_expr()
|
|
}
|
|
#[track_caller]
|
|
pub fn instance<T2: BundleValue>(
|
|
&mut self,
|
|
implicit_name: ImplicitName<'_>,
|
|
instantiated: Interned<Module<T2>>,
|
|
) -> Expr<T2>
|
|
where
|
|
T2::Type: BundleType<Value = T2>,
|
|
{
|
|
self.instance_with_loc(implicit_name.0, instantiated, SourceLocation::caller())
|
|
}
|
|
#[track_caller]
|
|
fn memory_impl<VA: ValueArrayOrSlice + ?Sized>(
|
|
&mut self,
|
|
name: &str,
|
|
mem_element_type: VA::ElementType,
|
|
source_location: SourceLocation,
|
|
) -> MemBuilder<VA> {
|
|
let scoped_name = ScopedNameId(self.name, self.name_id_gen.gen(name.intern()));
|
|
let (retval, target_mem) = MemBuilder::new(scoped_name, source_location, mem_element_type);
|
|
self.block(self.block_stack.top())
|
|
.memories
|
|
.push(target_mem.clone());
|
|
self.body.body.memory_map.insert(scoped_name, target_mem);
|
|
retval
|
|
}
|
|
#[track_caller]
|
|
pub fn memory_array_with_loc<VA: ValueArrayOrSlice + ?Sized>(
|
|
&mut self,
|
|
name: &str,
|
|
mem_array_type: ArrayType<VA>,
|
|
source_location: SourceLocation,
|
|
) -> MemBuilder<VA> {
|
|
let mut retval = self.memory_impl(name, mem_array_type.element().clone(), source_location);
|
|
retval.depth(mem_array_type.len());
|
|
retval
|
|
}
|
|
#[track_caller]
|
|
pub fn memory_array<VA: ValueArrayOrSlice + ?Sized>(
|
|
&mut self,
|
|
implicit_name: ImplicitName<'_>,
|
|
mem_array_type: ArrayType<VA>,
|
|
) -> MemBuilder<VA> {
|
|
self.memory_array_with_loc(implicit_name.0, mem_array_type, SourceLocation::caller())
|
|
}
|
|
#[track_caller]
|
|
pub fn memory_with_init_and_loc<VA: ValueArrayOrSlice + ?Sized>(
|
|
&mut self,
|
|
name: &str,
|
|
initial_value: impl ToExpr<Type = ArrayType<VA>>,
|
|
source_location: SourceLocation,
|
|
) -> MemBuilder<VA> {
|
|
let mut retval = self.memory_array_with_loc(name, initial_value.ty(), source_location);
|
|
retval.initial_value(initial_value);
|
|
retval
|
|
}
|
|
#[track_caller]
|
|
pub fn memory_with_init<VA: ValueArrayOrSlice + ?Sized>(
|
|
&mut self,
|
|
implicit_name: ImplicitName<'_>,
|
|
initial_value: impl ToExpr<Type = ArrayType<VA>>,
|
|
) -> MemBuilder<VA> {
|
|
self.memory_with_init_and_loc(
|
|
implicit_name.0,
|
|
initial_value.to_expr(),
|
|
SourceLocation::caller(),
|
|
)
|
|
}
|
|
#[track_caller]
|
|
pub fn memory_with_loc<Element: Value>(
|
|
&mut self,
|
|
name: &str,
|
|
element_type: Element::Type,
|
|
source_location: SourceLocation,
|
|
) -> MemBuilder<[Element]>
|
|
where
|
|
Element::Type: Type<Value = Element>,
|
|
{
|
|
self.memory_impl(name, element_type, source_location)
|
|
}
|
|
#[track_caller]
|
|
pub fn memory<Element: Value>(
|
|
&mut self,
|
|
implicit_name: ImplicitName<'_>,
|
|
element_type: Element::Type,
|
|
) -> MemBuilder<[Element]>
|
|
where
|
|
Element::Type: Type<Value = Element>,
|
|
{
|
|
self.memory_with_loc(implicit_name.0, element_type, SourceLocation::caller())
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
|
pub struct ModuleIO<T: Type> {
|
|
containing_module_name: NameId,
|
|
field_type: FieldType<T>,
|
|
source_location: SourceLocation,
|
|
}
|
|
|
|
impl<T: Type> fmt::Debug for ModuleIO<T> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("ModuleIO")
|
|
.field("name", &self.scoped_name())
|
|
.field("is_input", &self.is_input())
|
|
.field("ty", &self.field_type().ty)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
impl<T: Type> ModuleIO<T> {
|
|
pub fn canonical(&self) -> ModuleIO<T::CanonicalType> {
|
|
let Self {
|
|
containing_module_name,
|
|
ref field_type,
|
|
source_location,
|
|
} = *self;
|
|
ModuleIO {
|
|
containing_module_name,
|
|
field_type: field_type.canonical(),
|
|
source_location,
|
|
}
|
|
}
|
|
pub fn to_canonical_dyn_module_io(&self) -> ModuleIO<Interned<dyn DynCanonicalType>> {
|
|
let Self {
|
|
containing_module_name,
|
|
ref field_type,
|
|
source_location,
|
|
} = *self;
|
|
ModuleIO {
|
|
containing_module_name,
|
|
field_type: field_type.canonical_dyn(),
|
|
source_location,
|
|
}
|
|
}
|
|
pub fn field_type(&self) -> &FieldType<T> {
|
|
&self.field_type
|
|
}
|
|
pub fn is_input(&self) -> bool {
|
|
self.field_type.flipped
|
|
}
|
|
pub fn is_output(&self) -> bool {
|
|
!self.field_type.flipped
|
|
}
|
|
pub fn containing_module_name(&self) -> Interned<str> {
|
|
self.containing_module_name.0
|
|
}
|
|
pub fn containing_module_name_id(&self) -> NameId {
|
|
self.containing_module_name
|
|
}
|
|
pub fn name(&self) -> Interned<str> {
|
|
self.field_type.name
|
|
}
|
|
pub fn name_id(&self) -> NameId {
|
|
NameId(self.field_type.name, 0)
|
|
}
|
|
pub fn scoped_name(&self) -> ScopedNameId {
|
|
ScopedNameId(self.containing_module_name, self.name_id())
|
|
}
|
|
pub fn source_location(&self) -> SourceLocation {
|
|
self.source_location
|
|
}
|
|
pub fn new_unchecked(
|
|
containing_module_name: NameId,
|
|
name: Interned<str>,
|
|
source_location: SourceLocation,
|
|
is_input: bool,
|
|
ty: T,
|
|
) -> Self {
|
|
Self {
|
|
containing_module_name,
|
|
field_type: FieldType {
|
|
name,
|
|
flipped: is_input,
|
|
ty,
|
|
},
|
|
source_location,
|
|
}
|
|
}
|
|
pub fn must_connect_to(&self) -> bool {
|
|
true
|
|
}
|
|
pub fn flow(&self) -> Flow {
|
|
if self.is_input() {
|
|
Flow::Source
|
|
} else if self.is_output() {
|
|
Flow::Sink
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|