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}
63
64#[derive(Clone, Debug, Serialize, Deserialize)]
65pub struct RuntimeEventSite {
66 pub kind: RuntimeEventKind,
67 pub template: Option<String>,
68 pub scope: Option<String>,
70 pub arg_widths: Vec<usize>,
71 pub arg_signed: Vec<bool>,
72 pub arg_is_string: Vec<bool>,
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize)]
81pub struct RuntimeCombObserver<A> {
82 pub site_id: u32,
83 pub activation_group: u32,
84 pub sensitivity: Vec<VarAtomBase<A>>,
85 pub written_inputs: Vec<A>,
86}
87
88#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
89pub struct InitialStateWriteRun {
90 pub bit_offset: usize,
91 pub bit_width: usize,
92 pub value_bytes: Vec<u8>,
93 pub mask_bytes: Vec<u8>,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
97pub enum InitialStateData {
98 Packed {
99 value: BigUint,
100 mask: BigUint,
101 written_mask: BigUint,
102 },
103 Writes(Vec<InitialStateWriteRun>),
104}
105
106#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
107pub struct InitialStateValue<A> {
108 pub address: A,
109 pub data: InitialStateData,
110}
111
112#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
113pub struct RuntimeErrorInfo<A> {
114 pub message: String,
115 pub signals: Vec<A>,
116}
117
118#[derive(Clone, Debug)]
120pub struct RuntimeSchema<A> {
121 pub runtime_errors: HashMap<i64, RuntimeErrorInfo<A>>,
122 pub runtime_event_sites: Vec<RuntimeEventSite>,
123 pub comb_observers: Vec<RuntimeCombObserver<A>>,
124 pub testbench_read_roots: HashSet<A>,
127 pub rtl_writes: HashSet<VarAtomBase<A>>,
130}
131
132impl<A> Default for RuntimeSchema<A> {
133 fn default() -> Self {
134 Self {
135 runtime_errors: HashMap::default(),
136 runtime_event_sites: Vec::new(),
137 comb_observers: Vec::new(),
138 testbench_read_roots: HashSet::default(),
139 rtl_writes: HashSet::default(),
140 }
141 }
142}
143
144#[derive(Clone, Debug, Serialize, Deserialize)]
149#[serde(bound(
150 serialize = "A: Serialize + Eq + std::hash::Hash + Ord",
151 deserialize = "A: Deserialize<'de> + Eq + std::hash::Hash + Ord"
152))]
153pub struct EventTopology<A> {
154 pub aliases: HashMap<A, A>,
156 pub ordered_events: Vec<A>,
158 pub cascaded_events: BTreeSet<A>,
160 pub reset_clocks: HashMap<A, A>,
162}
163
164impl<A> Default for EventTopology<A> {
165 fn default() -> Self {
166 Self {
167 aliases: HashMap::default(),
168 ordered_events: Vec::new(),
169 cascaded_events: BTreeSet::new(),
170 reset_clocks: HashMap::default(),
171 }
172 }
173}
174
175impl<A: Copy + Eq + std::hash::Hash> EventTopology<A> {
176 pub fn canonical(&self, address: A) -> A {
177 self.aliases.get(&address).copied().unwrap_or(address)
178 }
179
180 pub fn len(&self) -> usize {
181 self.ordered_events.len()
182 }
183
184 pub fn is_empty(&self) -> bool {
185 self.ordered_events.is_empty()
186 }
187}
188
189#[derive(Clone, Debug, Serialize, Deserialize)]
195#[serde(bound(
196 serialize = "A: Serialize + Eq + std::hash::Hash + Ord",
197 deserialize = "A: Deserialize<'de> + Eq + std::hash::Hash + Ord"
198))]
199pub struct ElaboratedDesign<A> {
200 pub state_objects: HashMap<A, VariableMetadata>,
201 pub events: EventTopology<A>,
202 pub initial_state: Vec<InitialStateValue<A>>,
203}
204
205impl<A> Default for ElaboratedDesign<A> {
206 fn default() -> Self {
207 Self {
208 state_objects: HashMap::default(),
209 events: EventTopology::default(),
210 initial_state: Vec::new(),
211 }
212 }
213}
214
215#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
216pub enum BinaryOp {
217 Add,
218 Sub,
219 Mul,
220 DivU,
221 DivS,
222 RemU,
223 RemS,
224 And,
225 Or,
226 Xor,
227 Shl, Shr, Sar, Eq,
231 Ne,
232 EqCase,
233 NeCase,
234 LtU,
235 LtS, LeU,
237 LeS, GtU,
239 GtS, GeU,
241 GeS, LogicAnd,
243 LogicOr,
244 EqWildcard,
245 NeWildcard,
246}
247
248impl BinaryOp {
249 pub fn is_commutative(&self) -> bool {
251 matches!(
252 self,
253 BinaryOp::Add
254 | BinaryOp::Mul
255 | BinaryOp::And
256 | BinaryOp::Or
257 | BinaryOp::Xor
258 | BinaryOp::Eq
259 | BinaryOp::Ne
260 | BinaryOp::EqCase
261 | BinaryOp::NeCase
262 | BinaryOp::LogicAnd
263 | BinaryOp::LogicOr
264 )
265 }
266}
267
268impl fmt::Display for BinaryOp {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 let op_str = match self {
271 BinaryOp::Add => "Add",
272 BinaryOp::Sub => "Sub",
273 BinaryOp::Mul => "Mul",
274 BinaryOp::DivU => "DivU",
275 BinaryOp::DivS => "DivS",
276 BinaryOp::RemU => "RemU",
277 BinaryOp::RemS => "RemS",
278 BinaryOp::And => "And",
279 BinaryOp::Or => "Or",
280 BinaryOp::Xor => "Xor",
281 BinaryOp::Shl => "Shl",
282 BinaryOp::Shr => "Shr",
283 BinaryOp::Sar => "Sar",
284 BinaryOp::Eq => "Eq",
285 BinaryOp::Ne => "Ne",
286 BinaryOp::EqCase => "EqCase",
287 BinaryOp::NeCase => "NeCase",
288 BinaryOp::LtU => "LtU",
289 BinaryOp::LtS => "LtS",
290 BinaryOp::LeU => "LeU",
291 BinaryOp::LeS => "LeS",
292 BinaryOp::GtU => "GtU",
293 BinaryOp::GtS => "GtS",
294 BinaryOp::GeU => "GeU",
295 BinaryOp::GeS => "GeS",
296 BinaryOp::LogicAnd => "LogicAnd",
297 BinaryOp::LogicOr => "LogicOr",
298 BinaryOp::EqWildcard => "EqWildcard",
299 BinaryOp::NeWildcard => "NeWildcard",
300 };
301 write!(f, "{}", op_str)
302 }
303}
304
305#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
306pub enum UnaryOp {
307 Ident,
308 ToTwoState,
311 Minus,
312 BitNot,
313 LogicNot,
314 And,
315 Or,
316 Xor,
317 PopCount,
318 CountLeadingZeros,
319 CountTrailingZeros,
320}
321
322impl UnaryOp {
323 pub fn result_width(self, operand_width: usize) -> usize {
330 match self {
331 UnaryOp::LogicNot | UnaryOp::And | UnaryOp::Or | UnaryOp::Xor => 1,
332 UnaryOp::Ident | UnaryOp::ToTwoState | UnaryOp::Minus | UnaryOp::BitNot => {
333 operand_width
334 }
335 UnaryOp::PopCount | UnaryOp::CountLeadingZeros | UnaryOp::CountTrailingZeros => {
336 usize::BITS as usize - operand_width.leading_zeros() as usize
337 }
338 }
339 }
340}
341
342impl fmt::Display for UnaryOp {
343 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344 let op_str = match self {
345 UnaryOp::Ident => "Ident",
346 UnaryOp::ToTwoState => "ToTwoState",
347 UnaryOp::Minus => "Minus",
348 UnaryOp::BitNot => "BitNot",
349 UnaryOp::LogicNot => "LogicNot",
350 UnaryOp::And => "And",
351 UnaryOp::Or => "Or",
352 UnaryOp::Xor => "Xor",
353 UnaryOp::PopCount => "PopCount",
354 UnaryOp::CountLeadingZeros => "CountLeadingZeros",
355 UnaryOp::CountTrailingZeros => "CountTrailingZeros",
356 };
357 write!(f, "{}", op_str)
358 }
359}
360
361#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
362pub struct BitAccess {
363 pub lsb: usize,
364 pub msb: usize,
365}
366impl BitAccess {
367 pub fn new(lsb: usize, msb: usize) -> Self {
368 debug_assert!(lsb <= msb, "lsb must be less than or equal to msb");
369 Self { lsb, msb }
370 }
371 pub fn overlaps(&self, other: &Self) -> bool {
372 !(self.msb < other.lsb || other.msb < self.lsb)
373 }
374
375 pub fn calculate_atoms(&self, bounds: &BTreeSet<usize>) -> Vec<Self> {
377 use std::ops::Bound::*;
378 let mut atoms = Vec::new();
379 let mut current_lsb = self.lsb;
380
381 for &bound in bounds.range((Excluded(self.lsb), Included(self.msb))) {
384 atoms.push(Self::new(current_lsb, bound - 1));
385 current_lsb = bound;
386 }
387
388 if current_lsb <= self.msb {
390 atoms.push(Self::new(current_lsb, self.msb));
391 }
392
393 atoms
394 }
395}
396#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
397pub struct VarAtomBase<A> {
398 pub id: A,
399 pub access: BitAccess,
400}
401impl<A> VarAtomBase<A> {
402 pub fn new(id: A, lsb: usize, msb: usize) -> Self {
403 Self {
404 id,
405 access: BitAccess { lsb, msb },
406 }
407 }
408}
409impl fmt::Display for BitAccess {
410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 if self.lsb == self.msb {
412 write!(f, "[{}]", self.lsb)
413 } else {
414 write!(f, "[{}:{}]", self.msb, self.lsb)
415 }
416 }
417}
418
419impl<A> fmt::Display for VarAtomBase<A>
420where
421 A: fmt::Display + std::hash::Hash + Eq,
422{
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 write!(f, "{}{}", self.id, self.access)
425 }
426}
427
428#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
429pub struct ModuleId(pub usize);
430
431impl fmt::Display for ModuleId {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 write!(f, "mod{}", self.0)
434 }
435}
436
437#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
438pub struct InstanceId(pub usize);
439
440impl fmt::Display for InstanceId {
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 write!(f, "inst{}", self.0)
443 }
444}
445
446#[derive(
451 Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
452)]
453pub struct StateObjectId(pub u32);
454
455impl StateObjectId {
456 pub const fn from_raw(value: u32) -> Self {
457 Self(value)
458 }
459}
460
461impl fmt::Display for StateObjectId {
462 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
463 write!(f, "state{}", self.0)
464 }
465}
466
467#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
468pub struct AbsoluteAddrBase<V> {
469 pub instance_id: InstanceId,
470 pub var_id: V,
471}
472
473impl<V: fmt::Display> fmt::Display for AbsoluteAddrBase<V> {
474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475 write!(f, "AbsoluteAddr({}, {})", self.instance_id, self.var_id)
476 }
477}
478
479pub const STABLE_REGION: u32 = 0;
480pub const WORKING_REGION: u32 = 1;
481pub const SPARSE_WORKING_REGION: u32 = 2;
482
483#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
484pub struct RegionedVarAddrBase<V> {
485 pub region: u32,
486 pub var_id: V,
487}
488
489impl<V: fmt::Display> fmt::Display for RegionedVarAddrBase<V> {
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 write!(
492 f,
493 "RegionedVarAddr(region={}, {})",
494 self.region, self.var_id
495 )
496 }
497}
498
499#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
500pub struct RegionedAbsoluteAddrBase<V> {
501 pub region: u32,
502 pub instance_id: InstanceId,
503 pub var_id: V,
504}
505
506pub type StateAddr = AbsoluteAddrBase<StateObjectId>;
507pub type RegionedStateAddr = RegionedAbsoluteAddrBase<StateObjectId>;
508
509impl<V: Copy> RegionedAbsoluteAddrBase<V> {
510 pub fn from_absolute_addr(region: u32, addr: AbsoluteAddrBase<V>) -> Self {
511 Self {
512 region,
513 instance_id: addr.instance_id,
514 var_id: addr.var_id,
515 }
516 }
517
518 pub fn absolute_addr(&self) -> AbsoluteAddrBase<V> {
519 AbsoluteAddrBase {
520 instance_id: self.instance_id,
521 var_id: self.var_id,
522 }
523 }
524}
525
526impl<V: fmt::Display> fmt::Display for RegionedAbsoluteAddrBase<V> {
527 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528 write!(
529 f,
530 "RegionedAbsoluteAddr(region={}, {}, {})",
531 self.region, self.instance_id, self.var_id
532 )
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 #[test]
541 fn bit_access_splits_only_at_internal_boundaries() {
542 let access = BitAccess::new(4, 11);
543 let bounds = [0, 4, 7, 12, 20].into_iter().collect();
544
545 assert_eq!(
546 access.calculate_atoms(&bounds),
547 vec![BitAccess::new(4, 6), BitAccess::new(7, 11)]
548 );
549 }
550
551 #[test]
552 fn design_ids_and_addresses_have_stable_display() {
553 let address = AbsoluteAddrBase {
554 instance_id: InstanceId(42),
555 var_id: 7,
556 };
557
558 assert_eq!(ModuleId(3).to_string(), "mod3");
559 assert_eq!(InstanceId(42).to_string(), "inst42");
560 assert_eq!(StateObjectId(7).to_string(), "state7");
561 assert_eq!(address.to_string(), "AbsoluteAddr(inst42, 7)");
562 }
563
564 #[test]
565 fn regioned_address_round_trips_semantic_identity() {
566 let address = AbsoluteAddrBase {
567 instance_id: InstanceId(2),
568 var_id: 9,
569 };
570 let regioned = RegionedAbsoluteAddrBase::from_absolute_addr(WORKING_REGION, address);
571
572 assert_eq!(regioned.absolute_addr(), address);
573 assert_eq!(regioned.region, WORKING_REGION);
574 }
575
576 #[test]
577 fn semantic_operator_contracts_are_source_independent() {
578 assert!(BinaryOp::Add.is_commutative());
579 assert!(!BinaryOp::Sub.is_commutative());
580 assert_eq!(UnaryOp::LogicNot.result_width(128), 1);
581 assert_eq!(UnaryOp::PopCount.result_width(128), 8);
582 }
583
584 #[test]
585 fn initial_state_and_runtime_error_schemas_accept_design_owned_ids() {
586 let initial = InitialStateValue {
587 address: AbsoluteAddrBase {
588 instance_id: InstanceId(1),
589 var_id: 7u32,
590 },
591 data: InitialStateData::Writes(vec![InitialStateWriteRun {
592 bit_offset: 3,
593 bit_width: 5,
594 value_bytes: vec![0x15],
595 mask_bytes: vec![0],
596 }]),
597 };
598 let error = RuntimeErrorInfo {
599 message: "failed".to_string(),
600 signals: vec![initial.address],
601 };
602 let mut runtime = RuntimeSchema::default();
603 runtime.runtime_errors.insert(1, error.clone());
604 runtime.runtime_event_sites.push(RuntimeEventSite {
605 kind: RuntimeEventKind::AssertFatal,
606 template: Some("failed".to_string()),
607 scope: None,
608 arg_widths: Vec::new(),
609 arg_signed: Vec::new(),
610 arg_is_string: Vec::new(),
611 });
612 runtime.comb_observers.push(RuntimeCombObserver {
613 site_id: 0,
614 activation_group: 0,
615 sensitivity: vec![VarAtomBase {
616 id: initial.address,
617 access: BitAccess { lsb: 3, msb: 7 },
618 }],
619 written_inputs: vec![initial.address],
620 });
621 runtime.testbench_read_roots.insert(initial.address);
622
623 assert_eq!(error.signals, vec![initial.address]);
624 assert!(matches!(initial.data, InitialStateData::Writes(_)));
625 assert_eq!(runtime.runtime_errors[&1], error);
626 assert_eq!(runtime.runtime_event_sites.len(), 1);
627 assert_eq!(runtime.comb_observers[0].sensitivity[0].id, initial.address);
628 assert!(runtime.testbench_read_roots.contains(&initial.address));
629 }
630
631 #[test]
632 fn variable_metadata_preserves_elaborated_shape_and_domain() {
633 let metadata = VariableMetadata {
634 width: 32,
635 is_4state: true,
636 kind: DomainKind::Other,
637 type_kind: PortTypeKind::Logic,
638 array_dims: vec![4],
639 };
640
641 assert_eq!(metadata.width, 32);
642 assert_eq!(metadata.array_dims, vec![4]);
643 }
644
645 #[test]
646 fn elaborated_design_uses_flat_addresses_and_canonical_event_topology() {
647 let mut design = ElaboratedDesign::<u32>::default();
648 design.state_objects.insert(
649 10,
650 VariableMetadata {
651 width: 1,
652 is_4state: false,
653 kind: DomainKind::ClockPosedge,
654 type_kind: PortTypeKind::Clock,
655 array_dims: Vec::new(),
656 },
657 );
658 design.events.aliases.insert(11, 10);
659 design.events.ordered_events.push(10);
660
661 assert_eq!(design.events.canonical(11), 10);
662 assert_eq!(design.events.canonical(12), 12);
663 assert_eq!(design.events.len(), 1);
664 assert!(!design.events.is_empty());
665 assert_eq!(design.state_objects[&10].width, 1);
666 }
667}