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