1use num_bigint::BigUint;
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11mod format;
12mod vm;
13pub use format::{DisplayFormatArg, format_display_arg};
14pub use vm::{CompiledExpr, TestbenchValue};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct StateLocation<A> {
18 pub address: A,
19 pub byte_offset: usize,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
23pub enum TestbenchOperator {
24 Add,
25 Sub,
26 Mul,
27 Div,
28 Rem,
29 Pow,
30 BitAnd,
31 BitOr,
32 BitXor,
33 BitXnor,
34 BitNand,
35 BitNor,
36 LogicShiftL,
37 LogicShiftR,
38 ArithShiftL,
39 ArithShiftR,
40 Eq,
41 EqWildcard,
42 Ne,
43 NeWildcard,
44 Less,
45 LessEq,
46 Greater,
47 GreaterEq,
48 LogicAnd,
49 LogicOr,
50 LogicNot,
51 BitNot,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct SourceLocation {
56 pub file: String,
57 pub line: u32,
58 pub column: u32,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
62pub enum AssertMessage<Argument> {
63 Formatted {
64 template: String,
65 args: Vec<Argument>,
66 },
67 DynamicArgs(Vec<Argument>),
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
71pub enum ClockCount<Expression> {
72 Static(u64),
73 Dynamic(Expression),
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
77pub enum LoopBound<Expression> {
78 Static(usize),
79 Dynamic {
80 expr: Expression,
81 width: usize,
82 signed: bool,
83 },
84}
85
86#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
87pub enum ComponentParameterValue {
88 Bits { words: Vec<u64>, width: u32 },
89 String(String),
90}
91
92#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
93pub struct ComponentConnection {
94 pub port: String,
95 pub group: Option<String>,
96 pub member: Option<String>,
97 pub input: bool,
98 pub has_output: bool,
99 pub is_clock: bool,
100 pub is_reset: bool,
101 pub width: u32,
102}
103
104#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
105pub struct TestbenchComponent {
106 pub instance: String,
107 pub component: String,
108 pub params: Vec<(String, ComponentParameterValue)>,
109 pub connections: Vec<ComponentConnection>,
110 pub is_var_form: bool,
111 pub source: Option<SourceLocation>,
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
115pub struct ComponentLibrary {
116 pub export: String,
117 pub type_name: String,
118 pub path: PathBuf,
119}
120
121#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
122pub struct ComponentConnectionBinding<Event, Signal, Expression> {
123 pub port: String,
124 pub input: Option<Expression>,
125 pub input_target: Option<TestbenchTarget<Signal, Expression>>,
129 pub output: Option<TestbenchTarget<Signal, Expression>>,
130 pub output_rtl_driven: bool,
131 pub event: Option<Event>,
132}
133
134#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
135pub struct ComponentBinding<Event, Signal, Expression> {
136 pub instance: String,
137 pub connections: Vec<ComponentConnectionBinding<Event, Signal, Expression>>,
138}
139
140#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
146pub enum TestbenchStatement<Event, Signal, Expression, Argument, Target = Signal> {
147 ClockNext {
148 clock_event: Event,
149 count: ClockCount<Expression>,
150 },
151 ResetAssert {
152 reset_signal: Signal,
153 reset_event: Option<Event>,
154 clock_event: Event,
155 duration: ClockCount<Expression>,
156 assert_value: u8,
157 deassert_value: u8,
158 },
159 Assert {
160 expr: Expression,
161 site_id: u32,
162 continue_on_fail: bool,
163 message: Option<AssertMessage<Argument>>,
164 location: Option<SourceLocation>,
165 },
166 Display {
167 message: Option<AssertMessage<Argument>>,
168 newline: bool,
169 },
170 If {
171 expr: Expression,
172 then_block: Vec<Self>,
173 else_block: Vec<Self>,
174 },
175 For {
176 loop_var: Option<(Signal, usize, bool)>,
177 start: LoopBound<Expression>,
178 end: LoopBound<Expression>,
179 inclusive: bool,
180 step: usize,
181 step_op: Option<TestbenchOperator>,
182 reverse: bool,
183 body: Vec<Self>,
184 },
185 Assign {
186 dst: Target,
187 expr: Expression,
188 },
189 RandomSeed {
190 handle: String,
191 value: Expression,
192 },
193 RandomGet {
194 handle: String,
195 width: u32,
196 signed: bool,
197 ret: Option<Target>,
198 },
199 RandomGetRange {
200 handle: String,
201 min: Expression,
202 max: Expression,
203 width: u32,
204 signed: bool,
205 ret: Option<Target>,
206 },
207 RandomGetSeed {
208 handle: String,
209 ret: Option<Target>,
210 },
211 ComponentMethod {
212 instance: String,
213 method: String,
214 args: Vec<Argument>,
215 ret: Option<Target>,
216 ret_width: Option<u32>,
217 ret_signed: bool,
218 ret_strict: bool,
219 },
220 Break,
221 Finish,
222}
223
224#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
225pub struct SemanticSignal<A> {
226 pub address: A,
227 pub width: usize,
228}
229
230#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
231pub struct TestbenchSelection<Expression> {
232 pub offset: Expression,
233 pub width: usize,
234}
235
236#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
237pub struct TestbenchTarget<Signal, Expression> {
238 pub signal: Signal,
239 pub selection: Option<TestbenchSelection<Expression>>,
240 pub width: usize,
241}
242
243#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
244pub struct SemanticArgument<A> {
245 pub expr: ExprBytecode<StateLocation<A>>,
246 pub width: usize,
247 pub signed: bool,
248 pub is_string: bool,
249}
250
251pub type SemanticStatement<A> = TestbenchStatement<
252 A,
253 SemanticSignal<A>,
254 ExprBytecode<StateLocation<A>>,
255 SemanticArgument<A>,
256 TestbenchTarget<SemanticSignal<A>, ExprBytecode<StateLocation<A>>>,
257>;
258pub type SemanticComponentBinding<A> =
259 ComponentBinding<A, SemanticSignal<A>, ExprBytecode<StateLocation<A>>>;
260
261#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
262pub struct TestbenchProgram<A> {
263 statements: Vec<SemanticStatement<A>>,
264 random_seed: Option<u64>,
265 components: Vec<TestbenchComponent>,
266 component_libraries: Vec<ComponentLibrary>,
267 component_file_base: Option<PathBuf>,
268 component_bindings: Vec<SemanticComponentBinding<A>>,
269}
270
271impl<A> Default for TestbenchProgram<A> {
272 fn default() -> Self {
273 Self {
274 statements: Vec::new(),
275 random_seed: None,
276 components: Vec::new(),
277 component_libraries: Vec::new(),
278 component_file_base: None,
279 component_bindings: Vec::new(),
280 }
281 }
282}
283
284impl<A> TestbenchProgram<A> {
285 pub fn new(statements: Vec<SemanticStatement<A>>) -> Self {
286 Self {
287 statements,
288 random_seed: None,
289 components: Vec::new(),
290 component_libraries: Vec::new(),
291 component_file_base: None,
292 component_bindings: Vec::new(),
293 }
294 }
295
296 pub fn with_random_seed(mut self, random_seed: u64) -> Self {
297 self.random_seed = Some(random_seed);
298 self
299 }
300
301 pub fn with_random_seed_option(mut self, random_seed: Option<u64>) -> Self {
302 self.random_seed = random_seed;
303 self
304 }
305
306 pub fn statements(&self) -> &[SemanticStatement<A>] {
307 &self.statements
308 }
309
310 pub fn into_statements(self) -> Vec<SemanticStatement<A>> {
311 self.statements
312 }
313
314 pub fn random_seed(&self) -> u64 {
315 self.random_seed.unwrap_or_default()
316 }
317
318 pub fn configured_random_seed(&self) -> Option<u64> {
319 self.random_seed
320 }
321
322 pub fn with_components(mut self, components: Vec<TestbenchComponent>) -> Self {
323 self.components = components;
324 self
325 }
326
327 pub fn with_component_runtime(
328 mut self,
329 libraries: Vec<ComponentLibrary>,
330 file_base: Option<PathBuf>,
331 bindings: Vec<SemanticComponentBinding<A>>,
332 ) -> Self {
333 self.component_libraries = libraries;
334 self.component_file_base = file_base;
335 self.component_bindings = bindings;
336 self
337 }
338
339 pub fn components(&self) -> &[TestbenchComponent] {
340 &self.components
341 }
342
343 pub fn component_libraries(&self) -> &[ComponentLibrary] {
344 &self.component_libraries
345 }
346
347 pub fn component_file_base(&self) -> Option<&std::path::Path> {
348 self.component_file_base.as_deref()
349 }
350
351 pub fn component_bindings(&self) -> &[SemanticComponentBinding<A>] {
352 &self.component_bindings
353 }
354
355 pub fn try_map_paths<E>(
359 &mut self,
360 map: &mut impl FnMut(&std::path::Path) -> Result<PathBuf, E>,
361 ) -> Result<(), E> {
362 fn location<E>(
363 source: &mut Option<SourceLocation>,
364 map: &mut impl FnMut(&std::path::Path) -> Result<PathBuf, E>,
365 ) -> Result<(), E> {
366 if let Some(source) = source
367 && !source.file.is_empty()
368 {
369 source.file = map(std::path::Path::new(&source.file))?
370 .to_string_lossy()
371 .into_owned();
372 }
373 Ok(())
374 }
375 fn statements<A, E>(
376 block: &mut [SemanticStatement<A>],
377 map: &mut impl FnMut(&std::path::Path) -> Result<PathBuf, E>,
378 ) -> Result<(), E> {
379 for statement in block {
380 match statement {
381 TestbenchStatement::Assert {
382 location: source, ..
383 } => location(source, map)?,
384 TestbenchStatement::If {
385 then_block,
386 else_block,
387 ..
388 } => {
389 statements(then_block, map)?;
390 statements(else_block, map)?;
391 }
392 TestbenchStatement::For { body, .. } => statements(body, map)?,
393 _ => {}
394 }
395 }
396 Ok(())
397 }
398 for library in &mut self.component_libraries {
399 library.path = map(&library.path)?;
400 }
401 if let Some(base) = &mut self.component_file_base {
402 *base = map(base)?;
403 }
404 for component in &mut self.components {
405 location(&mut component.source, map)?;
406 }
407 statements(&mut self.statements, map)
408 }
409
410 pub fn is_empty(&self) -> bool {
411 self.statements.is_empty()
412 }
413}
414
415#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
416pub enum ExprOpcode<L = usize> {
417 ConstU64(u64),
418 ConstWide(BigUint),
419 LoadU64 {
420 location: L,
421 byte_size: usize,
422 mask: u64,
423 },
424 LoadWide {
425 location: L,
426 byte_size: usize,
427 width: usize,
428 },
429 BinOp(TestbenchOperator),
430 TypedBinOp {
431 op: TestbenchOperator,
432 lhs_width: usize,
433 rhs_width: usize,
434 result_width: usize,
435 lhs_signed: bool,
436 rhs_signed: bool,
437 },
438 TypedUnary {
439 op: TestbenchOperator,
440 operand_width: usize,
441 result_width: usize,
442 },
443 Resize {
444 source_width: usize,
445 target_width: usize,
446 signed: bool,
447 },
448 ConcatPart {
449 part_width: usize,
450 result_width: usize,
451 },
452 Ternary {
453 then_len: usize,
454 else_len: usize,
455 },
456 LoadIndexed {
457 location: L,
458 stride_bits: usize,
459 base_bit_offset: usize,
460 element_width: usize,
461 },
462 LoadBitSelect {
463 location: L,
464 base_byte_size: usize,
465 select_width: usize,
466 },
467 StoreU64 {
468 location: L,
469 byte_size: usize,
470 },
471}
472
473#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
474pub struct ExprBytecode<L = usize> {
475 ops: Vec<ExprOpcode<L>>,
476}
477
478impl<L> ExprBytecode<L> {
479 pub fn new(ops: Vec<ExprOpcode<L>>) -> Self {
480 Self { ops }
481 }
482
483 pub fn ops(&self) -> &[ExprOpcode<L>] {
484 &self.ops
485 }
486
487 pub fn is_empty(&self) -> bool {
488 self.ops.is_empty()
489 }
490}
491
492#[derive(Clone, Debug, PartialEq, Eq)]
493pub struct BindError<A> {
494 pub address: A,
495}
496
497pub struct ExecutableArgument {
498 pub expr: CompiledExpr,
499 pub width: usize,
500 pub signed: bool,
501 pub is_string: bool,
502}
503
504pub type ExecutableAssertMessage = AssertMessage<ExecutableArgument>;
505pub type ExecutableClockCount = ClockCount<CompiledExpr>;
506pub type ExecutableLoopBound = LoopBound<CompiledExpr>;
507pub type ExecutableStatement<Event, Signal> = TestbenchStatement<
508 Event,
509 Signal,
510 CompiledExpr,
511 ExecutableArgument,
512 TestbenchTarget<Signal, CompiledExpr>,
513>;
514pub type ExecutableComponentBinding<Event, Signal> = ComponentBinding<Event, Signal, CompiledExpr>;
515
516pub struct ExecutableTestbench<Event, Signal> {
517 statements: Vec<ExecutableStatement<Event, Signal>>,
518 random_seed: Option<u64>,
519 components: Vec<TestbenchComponent>,
520 component_libraries: Vec<ComponentLibrary>,
521 component_file_base: Option<PathBuf>,
522 component_bindings: Vec<ExecutableComponentBinding<Event, Signal>>,
523}
524
525impl<Event, Signal> ExecutableTestbench<Event, Signal> {
526 pub fn new(statements: Vec<ExecutableStatement<Event, Signal>>, random_seed: u64) -> Self {
527 Self::new_with_random_seed(statements, Some(random_seed))
528 }
529
530 pub fn new_with_random_seed(
531 statements: Vec<ExecutableStatement<Event, Signal>>,
532 random_seed: Option<u64>,
533 ) -> Self {
534 Self {
535 statements,
536 random_seed,
537 components: Vec::new(),
538 component_libraries: Vec::new(),
539 component_file_base: None,
540 component_bindings: Vec::new(),
541 }
542 }
543
544 pub fn with_component_runtime(
545 mut self,
546 components: Vec<TestbenchComponent>,
547 libraries: Vec<ComponentLibrary>,
548 file_base: Option<PathBuf>,
549 bindings: Vec<ExecutableComponentBinding<Event, Signal>>,
550 ) -> Self {
551 self.components = components;
552 self.component_libraries = libraries;
553 self.component_file_base = file_base;
554 self.component_bindings = bindings;
555 self
556 }
557
558 pub fn statements(&self) -> &[ExecutableStatement<Event, Signal>] {
559 &self.statements
560 }
561
562 pub fn into_statements(self) -> Vec<ExecutableStatement<Event, Signal>> {
563 self.statements
564 }
565
566 pub fn random_seed(&self) -> u64 {
567 self.random_seed.unwrap_or_default()
568 }
569
570 pub fn configured_random_seed(&self) -> Option<u64> {
571 self.random_seed
572 }
573
574 pub fn components(&self) -> &[TestbenchComponent] {
575 &self.components
576 }
577
578 pub fn component_libraries(&self) -> &[ComponentLibrary] {
579 &self.component_libraries
580 }
581
582 pub fn component_file_base(&self) -> Option<&std::path::Path> {
583 self.component_file_base.as_deref()
584 }
585
586 pub fn component_bindings(&self) -> &[ExecutableComponentBinding<Event, Signal>] {
587 &self.component_bindings
588 }
589}
590
591impl<A> ExprBytecode<StateLocation<A>> {
592 pub fn bind_with(
593 self,
594 mut resolve: impl FnMut(&A) -> Option<usize>,
595 ) -> Result<ExprBytecode, BindError<A>> {
596 let mut ops = Vec::with_capacity(self.ops.len());
597 for op in self.ops {
598 let bound = match op {
599 ExprOpcode::ConstU64(value) => ExprOpcode::ConstU64(value),
600 ExprOpcode::ConstWide(value) => ExprOpcode::ConstWide(value),
601 ExprOpcode::LoadU64 {
602 location,
603 byte_size,
604 mask,
605 } => ExprOpcode::LoadU64 {
606 location: bind_location(location, &mut resolve)?,
607 byte_size,
608 mask,
609 },
610 ExprOpcode::LoadWide {
611 location,
612 byte_size,
613 width,
614 } => ExprOpcode::LoadWide {
615 location: bind_location(location, &mut resolve)?,
616 byte_size,
617 width,
618 },
619 ExprOpcode::BinOp(op) => ExprOpcode::BinOp(op),
620 ExprOpcode::TypedBinOp {
621 op,
622 lhs_width,
623 rhs_width,
624 result_width,
625 lhs_signed,
626 rhs_signed,
627 } => ExprOpcode::TypedBinOp {
628 op,
629 lhs_width,
630 rhs_width,
631 result_width,
632 lhs_signed,
633 rhs_signed,
634 },
635 ExprOpcode::TypedUnary {
636 op,
637 operand_width,
638 result_width,
639 } => ExprOpcode::TypedUnary {
640 op,
641 operand_width,
642 result_width,
643 },
644 ExprOpcode::Resize {
645 source_width,
646 target_width,
647 signed,
648 } => ExprOpcode::Resize {
649 source_width,
650 target_width,
651 signed,
652 },
653 ExprOpcode::ConcatPart {
654 part_width,
655 result_width,
656 } => ExprOpcode::ConcatPart {
657 part_width,
658 result_width,
659 },
660 ExprOpcode::Ternary { then_len, else_len } => {
661 ExprOpcode::Ternary { then_len, else_len }
662 }
663 ExprOpcode::LoadIndexed {
664 location,
665 stride_bits,
666 base_bit_offset,
667 element_width,
668 } => ExprOpcode::LoadIndexed {
669 location: bind_location(location, &mut resolve)?,
670 stride_bits,
671 base_bit_offset,
672 element_width,
673 },
674 ExprOpcode::LoadBitSelect {
675 location,
676 base_byte_size,
677 select_width,
678 } => ExprOpcode::LoadBitSelect {
679 location: bind_location(location, &mut resolve)?,
680 base_byte_size,
681 select_width,
682 },
683 ExprOpcode::StoreU64 {
684 location,
685 byte_size,
686 } => ExprOpcode::StoreU64 {
687 location: bind_location(location, &mut resolve)?,
688 byte_size,
689 },
690 };
691 ops.push(bound);
692 }
693 Ok(ExprBytecode::new(ops))
694 }
695}
696
697fn bind_location<A>(
698 location: StateLocation<A>,
699 resolve: &mut impl FnMut(&A) -> Option<usize>,
700) -> Result<usize, BindError<A>> {
701 let Some(base) = resolve(&location.address) else {
702 return Err(BindError {
703 address: location.address,
704 });
705 };
706 Ok(base + location.byte_offset)
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712
713 #[test]
714 fn bytecode_uses_source_independent_operators() {
715 let code: ExprBytecode = ExprBytecode::new(vec![
716 ExprOpcode::ConstU64(1),
717 ExprOpcode::ConstU64(2),
718 ExprOpcode::TypedBinOp {
719 op: TestbenchOperator::Add,
720 lhs_width: 8,
721 rhs_width: 8,
722 result_width: 8,
723 lhs_signed: false,
724 rhs_signed: false,
725 },
726 ]);
727 assert_eq!(code.ops().len(), 3);
728 assert!(!code.is_empty());
729 }
730
731 #[test]
732 fn semantic_state_locations_bind_after_layout() {
733 let code = ExprBytecode::new(vec![ExprOpcode::LoadU64 {
734 location: StateLocation {
735 address: 7u32,
736 byte_offset: 3,
737 },
738 byte_size: 2,
739 mask: 0xffff,
740 }]);
741
742 let bound = code
743 .bind_with(|address| (*address == 7).then_some(100))
744 .unwrap();
745 assert_eq!(
746 bound.ops(),
747 &[ExprOpcode::LoadU64 {
748 location: 103,
749 byte_size: 2,
750 mask: 0xffff,
751 }]
752 );
753 }
754
755 #[test]
756 fn binding_reports_an_unmapped_semantic_address() {
757 let code = ExprBytecode::new(vec![ExprOpcode::StoreU64 {
758 location: StateLocation {
759 address: 9u32,
760 byte_offset: 0,
761 },
762 byte_size: 1,
763 }]);
764
765 assert_eq!(code.bind_with(|_| None), Err(BindError { address: 9 }));
766 }
767
768 #[test]
769 fn control_program_is_independent_of_runtime_handles() {
770 type SemanticStatement = TestbenchStatement<u32, u32, ExprBytecode<StateLocation<u32>>, ()>;
771
772 let statement = SemanticStatement::If {
773 expr: ExprBytecode::new(vec![ExprOpcode::LoadU64 {
774 location: StateLocation {
775 address: 1,
776 byte_offset: 0,
777 },
778 byte_size: 1,
779 mask: 1,
780 }]),
781 then_block: vec![SemanticStatement::ClockNext {
782 clock_event: 2,
783 count: ClockCount::Static(1),
784 }],
785 else_block: vec![SemanticStatement::Finish],
786 };
787
788 assert!(matches!(statement, SemanticStatement::If { .. }));
789 }
790}