1use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
4use num_bigint::BigUint;
5use serde::{Deserialize, Serialize};
6use std::{collections::BTreeSet, fmt};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum DomainKind {
10 ClockPosedge,
11 ClockNegedge,
12 ResetAsyncHigh,
13 ResetAsyncLow,
14 Other,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct TriggerIdWithKind {
19 pub kind: DomainKind,
20 pub id: usize,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24pub enum PortTypeKind {
25 Clock,
26 ResetAsyncHigh,
27 ResetAsyncLow,
28 ResetSyncHigh,
29 ResetSyncLow,
30 Logic,
31 Bit,
32 Other,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
40pub struct VariableMetadata {
41 pub width: usize,
42 pub is_4state: bool,
43 pub kind: DomainKind,
44 pub type_kind: PortTypeKind,
45 pub array_dims: Vec<usize>,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
51pub struct TriggerSet<A> {
52 pub clock: A,
53 pub resets: Vec<A>,
54}
55
56#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
57pub enum RuntimeEventKind {
58 Display,
59 Write,
60 AssertContinue,
61 AssertFatal,
62 Finish,
64}
65
66#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
68pub enum DisplaySizing {
69 #[default]
73 Veryl,
74 Ieee,
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize)]
79pub struct RuntimeEventSite {
80 pub kind: RuntimeEventKind,
81 pub template: Option<String>,
82 #[serde(default)]
83 pub sizing: DisplaySizing,
84 pub scope: Option<String>,
86 pub arg_widths: Vec<usize>,
87 pub arg_signed: Vec<bool>,
88 pub arg_is_string: Vec<bool>,
89}
90
91#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
95pub struct ExternFunction {
96 pub name: String,
98 pub signature: ExternSignature,
101}
102
103#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
105pub struct ExternSignature {
106 pub pure: bool,
108 pub result: Option<ExternType>,
110 pub arguments: Vec<ExternType>,
111}
112
113#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
115pub enum ExternType {
116 Bit,
118 Logic,
120 Integer { width: usize, signed: bool },
123}
124
125impl fmt::Display for ExternType {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 let (name, signed) = match *self {
128 ExternType::Bit => return f.write_str("bit"),
129 ExternType::Logic => return f.write_str("logic"),
130 ExternType::Integer { width: 8, signed } => ("byte", signed),
131 ExternType::Integer { width: 16, signed } => ("shortint", signed),
132 ExternType::Integer { width: 32, signed } => ("int", signed),
133 ExternType::Integer { width: 64, signed } => ("longint", signed),
134 ExternType::Integer { width, signed } => {
135 return write!(f, "{}{width}", if signed { "i" } else { "u" });
136 }
137 };
138 f.write_str(name)?;
139 if !signed {
140 f.write_str(" unsigned")?;
141 }
142 Ok(())
143 }
144}
145
146impl fmt::Display for ExternFunction {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 let signature = &self.signature;
149 if signature.pure {
150 f.write_str("pure ")?;
151 }
152 f.write_str("function ")?;
153 match signature.result {
154 Some(result) => write!(f, "{result}")?,
155 None => f.write_str("void")?,
156 }
157 write!(f, " {}(", self.name)?;
158 for (index, argument) in signature.arguments.iter().enumerate() {
159 if index > 0 {
160 f.write_str(", ")?;
161 }
162 write!(f, "input {argument}")?;
163 }
164 f.write_str(")")
165 }
166}
167
168#[derive(Clone, Debug, Serialize, Deserialize)]
174pub struct RuntimeCombObserver<A> {
175 pub site_id: u32,
176 pub activation_group: u32,
177 pub sensitivity: Vec<VarAtomBase<A>>,
178 pub written_inputs: Vec<A>,
179}
180
181#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
182pub struct InitialStateWriteRun {
183 pub bit_offset: usize,
184 pub bit_width: usize,
185 pub value_bytes: Vec<u8>,
186 pub mask_bytes: Vec<u8>,
187}
188
189#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
190pub enum InitialStateData {
191 Packed {
192 value: BigUint,
193 mask: BigUint,
194 written_mask: BigUint,
195 },
196 Writes(Vec<InitialStateWriteRun>),
197}
198
199#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
200pub struct InitialStateValue<A> {
201 pub address: A,
202 pub data: InitialStateData,
203}
204
205#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
206pub struct RuntimeErrorInfo<A> {
207 pub message: String,
208 pub signals: Vec<A>,
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
214pub enum ProcessStatus {
215 Delay,
217 Done,
219 Finish,
221}
222
223impl ProcessStatus {
224 pub const fn code(self) -> u8 {
227 match self {
228 Self::Delay => 1,
229 Self::Done => 2,
230 Self::Finish => 3,
231 }
232 }
233
234 pub const fn from_code(code: u8) -> Option<Self> {
235 match code {
236 1 => Some(Self::Delay),
237 2 => Some(Self::Done),
238 3 => Some(Self::Finish),
239 _ => None,
240 }
241 }
242}
243
244#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
252pub struct ProcessSlots<A> {
253 pub resume: A,
254 pub status: A,
255 pub delay: A,
256}
257
258impl<A> ProcessSlots<A> {
259 pub fn map<B>(self, mut map: impl FnMut(A) -> B) -> ProcessSlots<B> {
260 ProcessSlots {
261 resume: map(self.resume),
262 status: map(self.status),
263 delay: map(self.delay),
264 }
265 }
266
267 pub fn iter(&self) -> impl Iterator<Item = &A> {
268 [&self.resume, &self.status, &self.delay].into_iter()
269 }
270}
271
272pub const PROCESS_STATUS_WIDTH: usize = 8;
274pub const PROCESS_DELAY_WIDTH: usize = 64;
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
283pub enum DeadStorePolicy {
284 #[default]
286 Off,
287 PreserveListedSignals,
290 PreserveTopPorts,
292 PreserveAllPorts,
294}
295
296#[derive(Clone, Debug)]
298pub struct RuntimeSchema<A> {
299 pub runtime_errors: HashMap<i64, RuntimeErrorInfo<A>>,
300 pub runtime_event_sites: Vec<RuntimeEventSite>,
301 pub extern_functions: Vec<ExternFunction>,
303 pub comb_observers: Vec<RuntimeCombObserver<A>>,
304 pub testbench_read_roots: HashSet<A>,
307 pub rtl_writes: HashSet<VarAtomBase<A>>,
310 pub comb_writes: HashSet<A>,
314 pub dead_store_policy: DeadStorePolicy,
318 pub live_signals: HashSet<A>,
320 pub processes: Vec<ProcessSlots<A>>,
322}
323
324impl<A> Default for RuntimeSchema<A> {
325 fn default() -> Self {
326 Self {
327 runtime_errors: HashMap::default(),
328 runtime_event_sites: Vec::new(),
329 extern_functions: Vec::new(),
330 comb_observers: Vec::new(),
331 testbench_read_roots: HashSet::default(),
332 rtl_writes: HashSet::default(),
333 comb_writes: HashSet::default(),
334 dead_store_policy: DeadStorePolicy::Off,
335 live_signals: HashSet::default(),
336 processes: Vec::new(),
337 }
338 }
339}
340
341#[derive(Clone, Debug, Serialize, Deserialize)]
346#[serde(bound(
347 serialize = "A: Serialize + Eq + std::hash::Hash + Ord",
348 deserialize = "A: Deserialize<'de> + Eq + std::hash::Hash + Ord"
349))]
350pub struct EventTopology<A> {
351 pub aliases: HashMap<A, A>,
353 pub ordered_events: Vec<A>,
355 pub cascaded_events: BTreeSet<A>,
357 pub reset_clocks: HashMap<A, A>,
359}
360
361impl<A> Default for EventTopology<A> {
362 fn default() -> Self {
363 Self {
364 aliases: HashMap::default(),
365 ordered_events: Vec::new(),
366 cascaded_events: BTreeSet::new(),
367 reset_clocks: HashMap::default(),
368 }
369 }
370}
371
372impl<A: Copy + Eq + std::hash::Hash> EventTopology<A> {
373 pub fn canonical(&self, address: A) -> A {
374 self.aliases.get(&address).copied().unwrap_or(address)
375 }
376
377 pub fn len(&self) -> usize {
378 self.ordered_events.len()
379 }
380
381 pub fn is_empty(&self) -> bool {
382 self.ordered_events.is_empty()
383 }
384}
385
386#[derive(Clone, Debug, Serialize, Deserialize)]
392#[serde(bound(
393 serialize = "A: Serialize + Eq + std::hash::Hash + Ord",
394 deserialize = "A: Deserialize<'de> + Eq + std::hash::Hash + Ord"
395))]
396pub struct ElaboratedDesign<A> {
397 pub state_objects: HashMap<A, VariableMetadata>,
398 pub events: EventTopology<A>,
399 pub initial_state: Vec<InitialStateValue<A>>,
400}
401
402impl<A> Default for ElaboratedDesign<A> {
403 fn default() -> Self {
404 Self {
405 state_objects: HashMap::default(),
406 events: EventTopology::default(),
407 initial_state: Vec::new(),
408 }
409 }
410}
411
412impl<A: Copy + Eq + std::hash::Hash> ElaboratedDesign<A> {
413 pub fn event_kinds(&self) -> HashMap<A, DomainKind> {
418 let mut inferred: HashMap<A, Option<DomainKind>> = HashMap::default();
419 for (address, metadata) in &self.state_objects {
420 if metadata.width != 1
421 || !matches!(
422 metadata.kind,
423 DomainKind::ClockPosedge
424 | DomainKind::ClockNegedge
425 | DomainKind::ResetAsyncHigh
426 | DomainKind::ResetAsyncLow
427 )
428 {
429 continue;
430 }
431 let canonical = self.events.canonical(*address);
432 let kind = inferred.entry(canonical).or_insert(Some(metadata.kind));
433 if kind.is_some_and(|kind| kind != metadata.kind) {
434 *kind = None;
435 }
436 }
437 self.events
438 .ordered_events
439 .iter()
440 .filter_map(|address| {
441 let metadata = self.state_objects.get(address)?;
442 let kind = if metadata.kind == DomainKind::Other && metadata.width == 1 {
443 inferred
444 .get(address)
445 .copied()
446 .flatten()
447 .unwrap_or(metadata.kind)
448 } else {
449 metadata.kind
450 };
451 Some((*address, kind))
452 })
453 .collect()
454 }
455}
456
457#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
458pub enum BinaryOp {
459 Add,
460 Sub,
461 Mul,
462 DivU,
463 DivS,
464 RemU,
465 RemS,
466 And,
467 Or,
468 Xor,
469 Shl, Shr, Sar, Eq,
473 Ne,
474 EqCase,
475 NeCase,
476 LtU,
477 LtS, LeU,
479 LeS, GtU,
481 GtS, GeU,
483 GeS, LogicAnd,
485 LogicOr,
486 EqWildcard,
487 NeWildcard,
488}
489
490impl BinaryOp {
491 pub fn is_commutative(&self) -> bool {
493 matches!(
494 self,
495 BinaryOp::Add
496 | BinaryOp::Mul
497 | BinaryOp::And
498 | BinaryOp::Or
499 | BinaryOp::Xor
500 | BinaryOp::Eq
501 | BinaryOp::Ne
502 | BinaryOp::EqCase
503 | BinaryOp::NeCase
504 | BinaryOp::LogicAnd
505 | BinaryOp::LogicOr
506 )
507 }
508}
509
510impl fmt::Display for BinaryOp {
511 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512 let op_str = match self {
513 BinaryOp::Add => "Add",
514 BinaryOp::Sub => "Sub",
515 BinaryOp::Mul => "Mul",
516 BinaryOp::DivU => "DivU",
517 BinaryOp::DivS => "DivS",
518 BinaryOp::RemU => "RemU",
519 BinaryOp::RemS => "RemS",
520 BinaryOp::And => "And",
521 BinaryOp::Or => "Or",
522 BinaryOp::Xor => "Xor",
523 BinaryOp::Shl => "Shl",
524 BinaryOp::Shr => "Shr",
525 BinaryOp::Sar => "Sar",
526 BinaryOp::Eq => "Eq",
527 BinaryOp::Ne => "Ne",
528 BinaryOp::EqCase => "EqCase",
529 BinaryOp::NeCase => "NeCase",
530 BinaryOp::LtU => "LtU",
531 BinaryOp::LtS => "LtS",
532 BinaryOp::LeU => "LeU",
533 BinaryOp::LeS => "LeS",
534 BinaryOp::GtU => "GtU",
535 BinaryOp::GtS => "GtS",
536 BinaryOp::GeU => "GeU",
537 BinaryOp::GeS => "GeS",
538 BinaryOp::LogicAnd => "LogicAnd",
539 BinaryOp::LogicOr => "LogicOr",
540 BinaryOp::EqWildcard => "EqWildcard",
541 BinaryOp::NeWildcard => "NeWildcard",
542 };
543 write!(f, "{}", op_str)
544 }
545}
546
547#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
548pub enum UnaryOp {
549 Ident,
550 ToTwoState,
553 Minus,
554 BitNot,
555 LogicNot,
556 And,
557 Or,
558 Xor,
559 PopCount,
560 CountLeadingZeros,
561 CountTrailingZeros,
562}
563
564impl UnaryOp {
565 pub fn result_width(self, operand_width: usize) -> usize {
572 match self {
573 UnaryOp::LogicNot | UnaryOp::And | UnaryOp::Or | UnaryOp::Xor => 1,
574 UnaryOp::Ident | UnaryOp::ToTwoState | UnaryOp::Minus | UnaryOp::BitNot => {
575 operand_width
576 }
577 UnaryOp::PopCount | UnaryOp::CountLeadingZeros | UnaryOp::CountTrailingZeros => {
578 usize::BITS as usize - operand_width.leading_zeros() as usize
579 }
580 }
581 }
582}
583
584impl fmt::Display for UnaryOp {
585 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586 let op_str = match self {
587 UnaryOp::Ident => "Ident",
588 UnaryOp::ToTwoState => "ToTwoState",
589 UnaryOp::Minus => "Minus",
590 UnaryOp::BitNot => "BitNot",
591 UnaryOp::LogicNot => "LogicNot",
592 UnaryOp::And => "And",
593 UnaryOp::Or => "Or",
594 UnaryOp::Xor => "Xor",
595 UnaryOp::PopCount => "PopCount",
596 UnaryOp::CountLeadingZeros => "CountLeadingZeros",
597 UnaryOp::CountTrailingZeros => "CountTrailingZeros",
598 };
599 write!(f, "{}", op_str)
600 }
601}
602
603#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
604pub struct BitAccess {
605 pub lsb: usize,
606 pub msb: usize,
607}
608impl BitAccess {
609 pub fn new(lsb: usize, msb: usize) -> Self {
610 debug_assert!(lsb <= msb, "lsb must be less than or equal to msb");
611 Self { lsb, msb }
612 }
613 pub fn overlaps(&self, other: &Self) -> bool {
614 !(self.msb < other.lsb || other.msb < self.lsb)
615 }
616
617 pub fn calculate_atoms(&self, bounds: &BTreeSet<usize>) -> Vec<Self> {
619 use std::ops::Bound::*;
620 let mut atoms = Vec::new();
621 let mut current_lsb = self.lsb;
622
623 for &bound in bounds.range((Excluded(self.lsb), Included(self.msb))) {
626 atoms.push(Self::new(current_lsb, bound - 1));
627 current_lsb = bound;
628 }
629
630 if current_lsb <= self.msb {
632 atoms.push(Self::new(current_lsb, self.msb));
633 }
634
635 atoms
636 }
637}
638#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
639pub struct VarAtomBase<A> {
640 pub id: A,
641 pub access: BitAccess,
642}
643impl<A> VarAtomBase<A> {
644 pub fn new(id: A, lsb: usize, msb: usize) -> Self {
645 Self {
646 id,
647 access: BitAccess { lsb, msb },
648 }
649 }
650}
651impl fmt::Display for BitAccess {
652 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653 if self.lsb == self.msb {
654 write!(f, "[{}]", self.lsb)
655 } else {
656 write!(f, "[{}:{}]", self.msb, self.lsb)
657 }
658 }
659}
660
661impl<A> fmt::Display for VarAtomBase<A>
662where
663 A: fmt::Display + std::hash::Hash + Eq,
664{
665 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
666 write!(f, "{}{}", self.id, self.access)
667 }
668}
669
670#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
671pub struct ModuleId(pub usize);
672
673impl fmt::Display for ModuleId {
674 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
675 write!(f, "mod{}", self.0)
676 }
677}
678
679#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
680pub struct InstanceId(pub usize);
681
682impl fmt::Display for InstanceId {
683 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684 write!(f, "inst{}", self.0)
685 }
686}
687
688#[derive(
693 Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
694)]
695pub struct StateObjectId(pub u32);
696
697impl StateObjectId {
698 pub const fn from_raw(value: u32) -> Self {
699 Self(value)
700 }
701}
702
703impl fmt::Display for StateObjectId {
704 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
705 write!(f, "state{}", self.0)
706 }
707}
708
709#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
710pub struct AbsoluteAddrBase<V> {
711 pub instance_id: InstanceId,
712 pub var_id: V,
713}
714
715impl<V: fmt::Display> fmt::Display for AbsoluteAddrBase<V> {
716 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
717 write!(f, "AbsoluteAddr({}, {})", self.instance_id, self.var_id)
718 }
719}
720
721pub const STABLE_REGION: u32 = 0;
722pub const WORKING_REGION: u32 = 1;
723pub const SPARSE_WORKING_REGION: u32 = 2;
724
725#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
726pub struct RegionedVarAddrBase<V> {
727 pub region: u32,
728 pub var_id: V,
729}
730
731impl<V: fmt::Display> fmt::Display for RegionedVarAddrBase<V> {
732 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733 write!(
734 f,
735 "RegionedVarAddr(region={}, {})",
736 self.region, self.var_id
737 )
738 }
739}
740
741#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
742pub struct RegionedAbsoluteAddrBase<V> {
743 pub region: u32,
744 pub instance_id: InstanceId,
745 pub var_id: V,
746}
747
748pub type StateAddr = AbsoluteAddrBase<StateObjectId>;
749pub type RegionedStateAddr = RegionedAbsoluteAddrBase<StateObjectId>;
750
751impl<V: Copy> RegionedAbsoluteAddrBase<V> {
752 pub fn from_absolute_addr(region: u32, addr: AbsoluteAddrBase<V>) -> Self {
753 Self {
754 region,
755 instance_id: addr.instance_id,
756 var_id: addr.var_id,
757 }
758 }
759
760 pub fn absolute_addr(&self) -> AbsoluteAddrBase<V> {
761 AbsoluteAddrBase {
762 instance_id: self.instance_id,
763 var_id: self.var_id,
764 }
765 }
766}
767
768impl<V: fmt::Display> fmt::Display for RegionedAbsoluteAddrBase<V> {
769 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
770 write!(
771 f,
772 "RegionedAbsoluteAddr(region={}, {}, {})",
773 self.region, self.instance_id, self.var_id
774 )
775 }
776}
777
778#[cfg(test)]
779mod tests {
780 use super::*;
781
782 #[test]
783 fn bit_access_splits_only_at_internal_boundaries() {
784 let access = BitAccess::new(4, 11);
785 let bounds = [0, 4, 7, 12, 20].into_iter().collect();
786
787 assert_eq!(
788 access.calculate_atoms(&bounds),
789 vec![BitAccess::new(4, 6), BitAccess::new(7, 11)]
790 );
791 }
792
793 #[test]
794 fn design_ids_and_addresses_have_stable_display() {
795 let address = AbsoluteAddrBase {
796 instance_id: InstanceId(42),
797 var_id: 7,
798 };
799
800 assert_eq!(ModuleId(3).to_string(), "mod3");
801 assert_eq!(InstanceId(42).to_string(), "inst42");
802 assert_eq!(StateObjectId(7).to_string(), "state7");
803 assert_eq!(address.to_string(), "AbsoluteAddr(inst42, 7)");
804 }
805
806 #[test]
807 fn regioned_address_round_trips_semantic_identity() {
808 let address = AbsoluteAddrBase {
809 instance_id: InstanceId(2),
810 var_id: 9,
811 };
812 let regioned = RegionedAbsoluteAddrBase::from_absolute_addr(WORKING_REGION, address);
813
814 assert_eq!(regioned.absolute_addr(), address);
815 assert_eq!(regioned.region, WORKING_REGION);
816 }
817
818 #[test]
819 fn semantic_operator_contracts_are_source_independent() {
820 assert!(BinaryOp::Add.is_commutative());
821 assert!(!BinaryOp::Sub.is_commutative());
822 assert_eq!(UnaryOp::LogicNot.result_width(128), 1);
823 assert_eq!(UnaryOp::PopCount.result_width(128), 8);
824 }
825
826 #[test]
827 fn initial_state_and_runtime_error_schemas_accept_design_owned_ids() {
828 let initial = InitialStateValue {
829 address: AbsoluteAddrBase {
830 instance_id: InstanceId(1),
831 var_id: 7u32,
832 },
833 data: InitialStateData::Writes(vec![InitialStateWriteRun {
834 bit_offset: 3,
835 bit_width: 5,
836 value_bytes: vec![0x15],
837 mask_bytes: vec![0],
838 }]),
839 };
840 let error = RuntimeErrorInfo {
841 message: "failed".to_string(),
842 signals: vec![initial.address],
843 };
844 let mut runtime = RuntimeSchema::default();
845 runtime.runtime_errors.insert(1, error.clone());
846 runtime.runtime_event_sites.push(RuntimeEventSite {
847 kind: RuntimeEventKind::AssertFatal,
848 template: Some("failed".to_string()),
849 sizing: DisplaySizing::Veryl,
850 scope: None,
851 arg_widths: Vec::new(),
852 arg_signed: Vec::new(),
853 arg_is_string: Vec::new(),
854 });
855 runtime.comb_observers.push(RuntimeCombObserver {
856 site_id: 0,
857 activation_group: 0,
858 sensitivity: vec![VarAtomBase {
859 id: initial.address,
860 access: BitAccess { lsb: 3, msb: 7 },
861 }],
862 written_inputs: vec![initial.address],
863 });
864 runtime.testbench_read_roots.insert(initial.address);
865
866 assert_eq!(error.signals, vec![initial.address]);
867 assert!(matches!(initial.data, InitialStateData::Writes(_)));
868 assert_eq!(runtime.runtime_errors[&1], error);
869 assert_eq!(runtime.runtime_event_sites.len(), 1);
870 assert_eq!(runtime.comb_observers[0].sensitivity[0].id, initial.address);
871 assert!(runtime.testbench_read_roots.contains(&initial.address));
872 }
873
874 #[test]
875 fn variable_metadata_preserves_elaborated_shape_and_domain() {
876 let metadata = VariableMetadata {
877 width: 32,
878 is_4state: true,
879 kind: DomainKind::Other,
880 type_kind: PortTypeKind::Logic,
881 array_dims: vec![4],
882 };
883
884 assert_eq!(metadata.width, 32);
885 assert_eq!(metadata.array_dims, vec![4]);
886 }
887
888 #[test]
889 fn elaborated_design_uses_flat_addresses_and_canonical_event_topology() {
890 let mut design = ElaboratedDesign::<u32>::default();
891 design.state_objects.insert(
892 10,
893 VariableMetadata {
894 width: 1,
895 is_4state: false,
896 kind: DomainKind::ClockPosedge,
897 type_kind: PortTypeKind::Clock,
898 array_dims: Vec::new(),
899 },
900 );
901 design.events.aliases.insert(11, 10);
902 design.events.ordered_events.push(10);
903
904 assert_eq!(design.events.canonical(11), 10);
905 assert_eq!(design.events.canonical(12), 12);
906 assert_eq!(design.events.len(), 1);
907 assert!(!design.events.is_empty());
908 assert_eq!(design.state_objects[&10].width, 1);
909 }
910 #[test]
911 fn alias_event_kinds_are_separate_from_declaration_metadata() {
912 let mut design = ElaboratedDesign::<u32>::default();
913 for (address, kind) in [
914 (10, DomainKind::Other),
915 (11, DomainKind::ClockPosedge),
916 (20, DomainKind::Other),
917 (21, DomainKind::ClockNegedge),
918 (30, DomainKind::Other),
919 (31, DomainKind::ClockPosedge),
920 (32, DomainKind::ClockNegedge),
921 ] {
922 design.state_objects.insert(
923 address,
924 VariableMetadata {
925 width: 1,
926 is_4state: false,
927 kind,
928 type_kind: PortTypeKind::Logic,
929 array_dims: Vec::new(),
930 },
931 );
932 }
933 design.events.ordered_events = vec![10, 20, 30];
934 for (alias, canonical) in [(11, 10), (21, 20), (31, 30), (32, 30)] {
935 design.events.aliases.insert(alias, canonical);
936 }
937 let kinds = design.event_kinds();
938 assert_eq!(kinds[&10], DomainKind::ClockPosedge);
939 assert_eq!(kinds[&20], DomainKind::ClockNegedge);
940 assert_eq!(
941 kinds[&30],
942 DomainKind::Other,
943 "conflicting aliases do not select one edge"
944 );
945 assert_eq!(design.state_objects[&10].kind, DomainKind::Other);
946 assert_eq!(design.state_objects[&20].type_kind, PortTypeKind::Logic);
947 }
948}