use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeSet, fmt};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DomainKind {
ClockPosedge,
ClockNegedge,
ResetAsyncHigh,
ResetAsyncLow,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TriggerIdWithKind {
pub kind: DomainKind,
pub id: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PortTypeKind {
Clock,
ResetAsyncHigh,
ResetAsyncLow,
ResetSyncHigh,
ResetSyncLow,
Logic,
Bit,
Other,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct VariableMetadata {
pub width: usize,
pub is_4state: bool,
pub kind: DomainKind,
pub type_kind: PortTypeKind,
pub array_dims: Vec<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct TriggerSet<A> {
pub clock: A,
pub resets: Vec<A>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum RuntimeEventKind {
Display,
Write,
AssertContinue,
AssertFatal,
Finish,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum DisplaySizing {
#[default]
Veryl,
Ieee,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RuntimeEventSite {
pub kind: RuntimeEventKind,
pub template: Option<String>,
#[serde(default)]
pub sizing: DisplaySizing,
pub scope: Option<String>,
pub arg_widths: Vec<usize>,
pub arg_signed: Vec<bool>,
pub arg_is_string: Vec<bool>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExternFunction {
pub name: String,
pub signature: ExternSignature,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExternSignature {
pub pure: bool,
pub result: Option<ExternType>,
pub arguments: Vec<ExternType>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExternType {
Bit,
Logic,
Integer { width: usize, signed: bool },
}
impl fmt::Display for ExternType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (name, signed) = match *self {
ExternType::Bit => return f.write_str("bit"),
ExternType::Logic => return f.write_str("logic"),
ExternType::Integer { width: 8, signed } => ("byte", signed),
ExternType::Integer { width: 16, signed } => ("shortint", signed),
ExternType::Integer { width: 32, signed } => ("int", signed),
ExternType::Integer { width: 64, signed } => ("longint", signed),
ExternType::Integer { width, signed } => {
return write!(f, "{}{width}", if signed { "i" } else { "u" });
}
};
f.write_str(name)?;
if !signed {
f.write_str(" unsigned")?;
}
Ok(())
}
}
impl fmt::Display for ExternFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let signature = &self.signature;
if signature.pure {
f.write_str("pure ")?;
}
f.write_str("function ")?;
match signature.result {
Some(result) => write!(f, "{result}")?,
None => f.write_str("void")?,
}
write!(f, " {}(", self.name)?;
for (index, argument) in signature.arguments.iter().enumerate() {
if index > 0 {
f.write_str(", ")?;
}
write!(f, "input {argument}")?;
}
f.write_str(")")
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RuntimeCombObserver<A> {
pub site_id: u32,
pub activation_group: u32,
pub sensitivity: Vec<VarAtomBase<A>>,
pub written_inputs: Vec<A>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct InitialStateWriteRun {
pub bit_offset: usize,
pub bit_width: usize,
pub value_bytes: Vec<u8>,
pub mask_bytes: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum InitialStateData {
Packed {
value: BigUint,
mask: BigUint,
written_mask: BigUint,
},
Writes(Vec<InitialStateWriteRun>),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct InitialStateValue<A> {
pub address: A,
pub data: InitialStateData,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeErrorInfo<A> {
pub message: String,
pub signals: Vec<A>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ProcessStatus {
Delay,
Done,
Finish,
}
impl ProcessStatus {
pub const fn code(self) -> u8 {
match self {
Self::Delay => 1,
Self::Done => 2,
Self::Finish => 3,
}
}
pub const fn from_code(code: u8) -> Option<Self> {
match code {
1 => Some(Self::Delay),
2 => Some(Self::Done),
3 => Some(Self::Finish),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ProcessSlots<A> {
pub resume: A,
pub status: A,
pub delay: A,
}
impl<A> ProcessSlots<A> {
pub fn map<B>(self, mut map: impl FnMut(A) -> B) -> ProcessSlots<B> {
ProcessSlots {
resume: map(self.resume),
status: map(self.status),
delay: map(self.delay),
}
}
pub fn iter(&self) -> impl Iterator<Item = &A> {
[&self.resume, &self.status, &self.delay].into_iter()
}
}
pub const PROCESS_STATUS_WIDTH: usize = 8;
pub const PROCESS_DELAY_WIDTH: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum DeadStorePolicy {
#[default]
Off,
PreserveListedSignals,
PreserveTopPorts,
PreserveAllPorts,
}
#[derive(Clone, Debug)]
pub struct RuntimeSchema<A> {
pub runtime_errors: HashMap<i64, RuntimeErrorInfo<A>>,
pub runtime_event_sites: Vec<RuntimeEventSite>,
pub extern_functions: Vec<ExternFunction>,
pub comb_observers: Vec<RuntimeCombObserver<A>>,
pub testbench_read_roots: HashSet<A>,
pub rtl_writes: HashSet<VarAtomBase<A>>,
pub comb_writes: HashSet<A>,
pub dead_store_policy: DeadStorePolicy,
pub live_signals: HashSet<A>,
pub processes: Vec<ProcessSlots<A>>,
}
impl<A> Default for RuntimeSchema<A> {
fn default() -> Self {
Self {
runtime_errors: HashMap::default(),
runtime_event_sites: Vec::new(),
extern_functions: Vec::new(),
comb_observers: Vec::new(),
testbench_read_roots: HashSet::default(),
rtl_writes: HashSet::default(),
comb_writes: HashSet::default(),
dead_store_policy: DeadStorePolicy::Off,
live_signals: HashSet::default(),
processes: Vec::new(),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound(
serialize = "A: Serialize + Eq + std::hash::Hash + Ord",
deserialize = "A: Deserialize<'de> + Eq + std::hash::Hash + Ord"
))]
pub struct EventTopology<A> {
pub aliases: HashMap<A, A>,
pub ordered_events: Vec<A>,
pub cascaded_events: BTreeSet<A>,
pub reset_clocks: HashMap<A, A>,
}
impl<A> Default for EventTopology<A> {
fn default() -> Self {
Self {
aliases: HashMap::default(),
ordered_events: Vec::new(),
cascaded_events: BTreeSet::new(),
reset_clocks: HashMap::default(),
}
}
}
impl<A: Copy + Eq + std::hash::Hash> EventTopology<A> {
pub fn canonical(&self, address: A) -> A {
self.aliases.get(&address).copied().unwrap_or(address)
}
pub fn len(&self) -> usize {
self.ordered_events.len()
}
pub fn is_empty(&self) -> bool {
self.ordered_events.is_empty()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(bound(
serialize = "A: Serialize + Eq + std::hash::Hash + Ord",
deserialize = "A: Deserialize<'de> + Eq + std::hash::Hash + Ord"
))]
pub struct ElaboratedDesign<A> {
pub state_objects: HashMap<A, VariableMetadata>,
pub events: EventTopology<A>,
pub initial_state: Vec<InitialStateValue<A>>,
}
impl<A> Default for ElaboratedDesign<A> {
fn default() -> Self {
Self {
state_objects: HashMap::default(),
events: EventTopology::default(),
initial_state: Vec::new(),
}
}
}
impl<A: Copy + Eq + std::hash::Hash> ElaboratedDesign<A> {
pub fn event_kinds(&self) -> HashMap<A, DomainKind> {
let mut inferred: HashMap<A, Option<DomainKind>> = HashMap::default();
for (address, metadata) in &self.state_objects {
if metadata.width != 1
|| !matches!(
metadata.kind,
DomainKind::ClockPosedge
| DomainKind::ClockNegedge
| DomainKind::ResetAsyncHigh
| DomainKind::ResetAsyncLow
)
{
continue;
}
let canonical = self.events.canonical(*address);
let kind = inferred.entry(canonical).or_insert(Some(metadata.kind));
if kind.is_some_and(|kind| kind != metadata.kind) {
*kind = None;
}
}
self.events
.ordered_events
.iter()
.filter_map(|address| {
let metadata = self.state_objects.get(address)?;
let kind = if metadata.kind == DomainKind::Other && metadata.width == 1 {
inferred
.get(address)
.copied()
.flatten()
.unwrap_or(metadata.kind)
} else {
metadata.kind
};
Some((*address, kind))
})
.collect()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BinaryOp {
Add,
Sub,
Mul,
DivU,
DivS,
RemU,
RemS,
And,
Or,
Xor,
Shl, Shr, Sar, Eq,
Ne,
EqCase,
NeCase,
LtU,
LtS, LeU,
LeS, GtU,
GtS, GeU,
GeS, LogicAnd,
LogicOr,
EqWildcard,
NeWildcard,
}
impl BinaryOp {
pub fn is_commutative(&self) -> bool {
matches!(
self,
BinaryOp::Add
| BinaryOp::Mul
| BinaryOp::And
| BinaryOp::Or
| BinaryOp::Xor
| BinaryOp::Eq
| BinaryOp::Ne
| BinaryOp::EqCase
| BinaryOp::NeCase
| BinaryOp::LogicAnd
| BinaryOp::LogicOr
)
}
}
impl fmt::Display for BinaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let op_str = match self {
BinaryOp::Add => "Add",
BinaryOp::Sub => "Sub",
BinaryOp::Mul => "Mul",
BinaryOp::DivU => "DivU",
BinaryOp::DivS => "DivS",
BinaryOp::RemU => "RemU",
BinaryOp::RemS => "RemS",
BinaryOp::And => "And",
BinaryOp::Or => "Or",
BinaryOp::Xor => "Xor",
BinaryOp::Shl => "Shl",
BinaryOp::Shr => "Shr",
BinaryOp::Sar => "Sar",
BinaryOp::Eq => "Eq",
BinaryOp::Ne => "Ne",
BinaryOp::EqCase => "EqCase",
BinaryOp::NeCase => "NeCase",
BinaryOp::LtU => "LtU",
BinaryOp::LtS => "LtS",
BinaryOp::LeU => "LeU",
BinaryOp::LeS => "LeS",
BinaryOp::GtU => "GtU",
BinaryOp::GtS => "GtS",
BinaryOp::GeU => "GeU",
BinaryOp::GeS => "GeS",
BinaryOp::LogicAnd => "LogicAnd",
BinaryOp::LogicOr => "LogicOr",
BinaryOp::EqWildcard => "EqWildcard",
BinaryOp::NeWildcard => "NeWildcard",
};
write!(f, "{}", op_str)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum UnaryOp {
Ident,
ToTwoState,
Minus,
BitNot,
LogicNot,
And,
Or,
Xor,
PopCount,
CountLeadingZeros,
CountTrailingZeros,
}
impl UnaryOp {
pub fn result_width(self, operand_width: usize) -> usize {
match self {
UnaryOp::LogicNot | UnaryOp::And | UnaryOp::Or | UnaryOp::Xor => 1,
UnaryOp::Ident | UnaryOp::ToTwoState | UnaryOp::Minus | UnaryOp::BitNot => {
operand_width
}
UnaryOp::PopCount | UnaryOp::CountLeadingZeros | UnaryOp::CountTrailingZeros => {
usize::BITS as usize - operand_width.leading_zeros() as usize
}
}
}
}
impl fmt::Display for UnaryOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let op_str = match self {
UnaryOp::Ident => "Ident",
UnaryOp::ToTwoState => "ToTwoState",
UnaryOp::Minus => "Minus",
UnaryOp::BitNot => "BitNot",
UnaryOp::LogicNot => "LogicNot",
UnaryOp::And => "And",
UnaryOp::Or => "Or",
UnaryOp::Xor => "Xor",
UnaryOp::PopCount => "PopCount",
UnaryOp::CountLeadingZeros => "CountLeadingZeros",
UnaryOp::CountTrailingZeros => "CountTrailingZeros",
};
write!(f, "{}", op_str)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct BitAccess {
pub lsb: usize,
pub msb: usize,
}
impl BitAccess {
pub fn new(lsb: usize, msb: usize) -> Self {
debug_assert!(lsb <= msb, "lsb must be less than or equal to msb");
Self { lsb, msb }
}
pub fn overlaps(&self, other: &Self) -> bool {
!(self.msb < other.lsb || other.msb < self.lsb)
}
pub fn calculate_atoms(&self, bounds: &BTreeSet<usize>) -> Vec<Self> {
use std::ops::Bound::*;
let mut atoms = Vec::new();
let mut current_lsb = self.lsb;
for &bound in bounds.range((Excluded(self.lsb), Included(self.msb))) {
atoms.push(Self::new(current_lsb, bound - 1));
current_lsb = bound;
}
if current_lsb <= self.msb {
atoms.push(Self::new(current_lsb, self.msb));
}
atoms
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct VarAtomBase<A> {
pub id: A,
pub access: BitAccess,
}
impl<A> VarAtomBase<A> {
pub fn new(id: A, lsb: usize, msb: usize) -> Self {
Self {
id,
access: BitAccess { lsb, msb },
}
}
}
impl fmt::Display for BitAccess {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.lsb == self.msb {
write!(f, "[{}]", self.lsb)
} else {
write!(f, "[{}:{}]", self.msb, self.lsb)
}
}
}
impl<A> fmt::Display for VarAtomBase<A>
where
A: fmt::Display + std::hash::Hash + Eq,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.id, self.access)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ModuleId(pub usize);
impl fmt::Display for ModuleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mod{}", self.0)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct InstanceId(pub usize);
impl fmt::Display for InstanceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "inst{}", self.0)
}
}
#[derive(
Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
)]
pub struct StateObjectId(pub u32);
impl StateObjectId {
pub const fn from_raw(value: u32) -> Self {
Self(value)
}
}
impl fmt::Display for StateObjectId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "state{}", self.0)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct AbsoluteAddrBase<V> {
pub instance_id: InstanceId,
pub var_id: V,
}
impl<V: fmt::Display> fmt::Display for AbsoluteAddrBase<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AbsoluteAddr({}, {})", self.instance_id, self.var_id)
}
}
pub const STABLE_REGION: u32 = 0;
pub const WORKING_REGION: u32 = 1;
pub const SPARSE_WORKING_REGION: u32 = 2;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct RegionedVarAddrBase<V> {
pub region: u32,
pub var_id: V,
}
impl<V: fmt::Display> fmt::Display for RegionedVarAddrBase<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"RegionedVarAddr(region={}, {})",
self.region, self.var_id
)
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct RegionedAbsoluteAddrBase<V> {
pub region: u32,
pub instance_id: InstanceId,
pub var_id: V,
}
pub type StateAddr = AbsoluteAddrBase<StateObjectId>;
pub type RegionedStateAddr = RegionedAbsoluteAddrBase<StateObjectId>;
impl<V: Copy> RegionedAbsoluteAddrBase<V> {
pub fn from_absolute_addr(region: u32, addr: AbsoluteAddrBase<V>) -> Self {
Self {
region,
instance_id: addr.instance_id,
var_id: addr.var_id,
}
}
pub fn absolute_addr(&self) -> AbsoluteAddrBase<V> {
AbsoluteAddrBase {
instance_id: self.instance_id,
var_id: self.var_id,
}
}
}
impl<V: fmt::Display> fmt::Display for RegionedAbsoluteAddrBase<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"RegionedAbsoluteAddr(region={}, {}, {})",
self.region, self.instance_id, self.var_id
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bit_access_splits_only_at_internal_boundaries() {
let access = BitAccess::new(4, 11);
let bounds = [0, 4, 7, 12, 20].into_iter().collect();
assert_eq!(
access.calculate_atoms(&bounds),
vec![BitAccess::new(4, 6), BitAccess::new(7, 11)]
);
}
#[test]
fn design_ids_and_addresses_have_stable_display() {
let address = AbsoluteAddrBase {
instance_id: InstanceId(42),
var_id: 7,
};
assert_eq!(ModuleId(3).to_string(), "mod3");
assert_eq!(InstanceId(42).to_string(), "inst42");
assert_eq!(StateObjectId(7).to_string(), "state7");
assert_eq!(address.to_string(), "AbsoluteAddr(inst42, 7)");
}
#[test]
fn regioned_address_round_trips_semantic_identity() {
let address = AbsoluteAddrBase {
instance_id: InstanceId(2),
var_id: 9,
};
let regioned = RegionedAbsoluteAddrBase::from_absolute_addr(WORKING_REGION, address);
assert_eq!(regioned.absolute_addr(), address);
assert_eq!(regioned.region, WORKING_REGION);
}
#[test]
fn semantic_operator_contracts_are_source_independent() {
assert!(BinaryOp::Add.is_commutative());
assert!(!BinaryOp::Sub.is_commutative());
assert_eq!(UnaryOp::LogicNot.result_width(128), 1);
assert_eq!(UnaryOp::PopCount.result_width(128), 8);
}
#[test]
fn initial_state_and_runtime_error_schemas_accept_design_owned_ids() {
let initial = InitialStateValue {
address: AbsoluteAddrBase {
instance_id: InstanceId(1),
var_id: 7u32,
},
data: InitialStateData::Writes(vec![InitialStateWriteRun {
bit_offset: 3,
bit_width: 5,
value_bytes: vec![0x15],
mask_bytes: vec![0],
}]),
};
let error = RuntimeErrorInfo {
message: "failed".to_string(),
signals: vec![initial.address],
};
let mut runtime = RuntimeSchema::default();
runtime.runtime_errors.insert(1, error.clone());
runtime.runtime_event_sites.push(RuntimeEventSite {
kind: RuntimeEventKind::AssertFatal,
template: Some("failed".to_string()),
sizing: DisplaySizing::Veryl,
scope: None,
arg_widths: Vec::new(),
arg_signed: Vec::new(),
arg_is_string: Vec::new(),
});
runtime.comb_observers.push(RuntimeCombObserver {
site_id: 0,
activation_group: 0,
sensitivity: vec![VarAtomBase {
id: initial.address,
access: BitAccess { lsb: 3, msb: 7 },
}],
written_inputs: vec![initial.address],
});
runtime.testbench_read_roots.insert(initial.address);
assert_eq!(error.signals, vec![initial.address]);
assert!(matches!(initial.data, InitialStateData::Writes(_)));
assert_eq!(runtime.runtime_errors[&1], error);
assert_eq!(runtime.runtime_event_sites.len(), 1);
assert_eq!(runtime.comb_observers[0].sensitivity[0].id, initial.address);
assert!(runtime.testbench_read_roots.contains(&initial.address));
}
#[test]
fn variable_metadata_preserves_elaborated_shape_and_domain() {
let metadata = VariableMetadata {
width: 32,
is_4state: true,
kind: DomainKind::Other,
type_kind: PortTypeKind::Logic,
array_dims: vec![4],
};
assert_eq!(metadata.width, 32);
assert_eq!(metadata.array_dims, vec![4]);
}
#[test]
fn elaborated_design_uses_flat_addresses_and_canonical_event_topology() {
let mut design = ElaboratedDesign::<u32>::default();
design.state_objects.insert(
10,
VariableMetadata {
width: 1,
is_4state: false,
kind: DomainKind::ClockPosedge,
type_kind: PortTypeKind::Clock,
array_dims: Vec::new(),
},
);
design.events.aliases.insert(11, 10);
design.events.ordered_events.push(10);
assert_eq!(design.events.canonical(11), 10);
assert_eq!(design.events.canonical(12), 12);
assert_eq!(design.events.len(), 1);
assert!(!design.events.is_empty());
assert_eq!(design.state_objects[&10].width, 1);
}
#[test]
fn alias_event_kinds_are_separate_from_declaration_metadata() {
let mut design = ElaboratedDesign::<u32>::default();
for (address, kind) in [
(10, DomainKind::Other),
(11, DomainKind::ClockPosedge),
(20, DomainKind::Other),
(21, DomainKind::ClockNegedge),
(30, DomainKind::Other),
(31, DomainKind::ClockPosedge),
(32, DomainKind::ClockNegedge),
] {
design.state_objects.insert(
address,
VariableMetadata {
width: 1,
is_4state: false,
kind,
type_kind: PortTypeKind::Logic,
array_dims: Vec::new(),
},
);
}
design.events.ordered_events = vec![10, 20, 30];
for (alias, canonical) in [(11, 10), (21, 20), (31, 30), (32, 30)] {
design.events.aliases.insert(alias, canonical);
}
let kinds = design.event_kinds();
assert_eq!(kinds[&10], DomainKind::ClockPosedge);
assert_eq!(kinds[&20], DomainKind::ClockNegedge);
assert_eq!(
kinds[&30],
DomainKind::Other,
"conflicting aliases do not select one edge"
);
assert_eq!(design.state_objects[&10].kind, DomainKind::Other);
assert_eq!(design.state_objects[&20].type_kind, PortTypeKind::Logic);
}
}