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 is_empty(&self) -> bool {
356 self.statements.is_empty()
357 }
358}
359
360#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
361pub enum ExprOpcode<L = usize> {
362 ConstU64(u64),
363 ConstWide(BigUint),
364 LoadU64 {
365 location: L,
366 byte_size: usize,
367 mask: u64,
368 },
369 LoadWide {
370 location: L,
371 byte_size: usize,
372 width: usize,
373 },
374 BinOp(TestbenchOperator),
375 TypedBinOp {
376 op: TestbenchOperator,
377 lhs_width: usize,
378 rhs_width: usize,
379 result_width: usize,
380 lhs_signed: bool,
381 rhs_signed: bool,
382 },
383 TypedUnary {
384 op: TestbenchOperator,
385 operand_width: usize,
386 result_width: usize,
387 },
388 Resize {
389 source_width: usize,
390 target_width: usize,
391 signed: bool,
392 },
393 ConcatPart {
394 part_width: usize,
395 result_width: usize,
396 },
397 Ternary {
398 then_len: usize,
399 else_len: usize,
400 },
401 LoadIndexed {
402 location: L,
403 stride_bits: usize,
404 base_bit_offset: usize,
405 element_width: usize,
406 },
407 LoadBitSelect {
408 location: L,
409 base_byte_size: usize,
410 select_width: usize,
411 },
412 StoreU64 {
413 location: L,
414 byte_size: usize,
415 },
416}
417
418#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
419pub struct ExprBytecode<L = usize> {
420 ops: Vec<ExprOpcode<L>>,
421}
422
423impl<L> ExprBytecode<L> {
424 pub fn new(ops: Vec<ExprOpcode<L>>) -> Self {
425 Self { ops }
426 }
427
428 pub fn ops(&self) -> &[ExprOpcode<L>] {
429 &self.ops
430 }
431
432 pub fn is_empty(&self) -> bool {
433 self.ops.is_empty()
434 }
435}
436
437#[derive(Clone, Debug, PartialEq, Eq)]
438pub struct BindError<A> {
439 pub address: A,
440}
441
442pub struct ExecutableArgument {
443 pub expr: CompiledExpr,
444 pub width: usize,
445 pub signed: bool,
446 pub is_string: bool,
447}
448
449pub type ExecutableAssertMessage = AssertMessage<ExecutableArgument>;
450pub type ExecutableClockCount = ClockCount<CompiledExpr>;
451pub type ExecutableLoopBound = LoopBound<CompiledExpr>;
452pub type ExecutableStatement<Event, Signal> = TestbenchStatement<
453 Event,
454 Signal,
455 CompiledExpr,
456 ExecutableArgument,
457 TestbenchTarget<Signal, CompiledExpr>,
458>;
459pub type ExecutableComponentBinding<Event, Signal> = ComponentBinding<Event, Signal, CompiledExpr>;
460
461pub struct ExecutableTestbench<Event, Signal> {
462 statements: Vec<ExecutableStatement<Event, Signal>>,
463 random_seed: Option<u64>,
464 components: Vec<TestbenchComponent>,
465 component_libraries: Vec<ComponentLibrary>,
466 component_file_base: Option<PathBuf>,
467 component_bindings: Vec<ExecutableComponentBinding<Event, Signal>>,
468}
469
470impl<Event, Signal> ExecutableTestbench<Event, Signal> {
471 pub fn new(statements: Vec<ExecutableStatement<Event, Signal>>, random_seed: u64) -> Self {
472 Self::new_with_random_seed(statements, Some(random_seed))
473 }
474
475 pub fn new_with_random_seed(
476 statements: Vec<ExecutableStatement<Event, Signal>>,
477 random_seed: Option<u64>,
478 ) -> Self {
479 Self {
480 statements,
481 random_seed,
482 components: Vec::new(),
483 component_libraries: Vec::new(),
484 component_file_base: None,
485 component_bindings: Vec::new(),
486 }
487 }
488
489 pub fn with_component_runtime(
490 mut self,
491 components: Vec<TestbenchComponent>,
492 libraries: Vec<ComponentLibrary>,
493 file_base: Option<PathBuf>,
494 bindings: Vec<ExecutableComponentBinding<Event, Signal>>,
495 ) -> Self {
496 self.components = components;
497 self.component_libraries = libraries;
498 self.component_file_base = file_base;
499 self.component_bindings = bindings;
500 self
501 }
502
503 pub fn statements(&self) -> &[ExecutableStatement<Event, Signal>] {
504 &self.statements
505 }
506
507 pub fn into_statements(self) -> Vec<ExecutableStatement<Event, Signal>> {
508 self.statements
509 }
510
511 pub fn random_seed(&self) -> u64 {
512 self.random_seed.unwrap_or_default()
513 }
514
515 pub fn configured_random_seed(&self) -> Option<u64> {
516 self.random_seed
517 }
518
519 pub fn components(&self) -> &[TestbenchComponent] {
520 &self.components
521 }
522
523 pub fn component_libraries(&self) -> &[ComponentLibrary] {
524 &self.component_libraries
525 }
526
527 pub fn component_file_base(&self) -> Option<&std::path::Path> {
528 self.component_file_base.as_deref()
529 }
530
531 pub fn component_bindings(&self) -> &[ExecutableComponentBinding<Event, Signal>] {
532 &self.component_bindings
533 }
534}
535
536impl<A> ExprBytecode<StateLocation<A>> {
537 pub fn bind_with(
538 self,
539 mut resolve: impl FnMut(&A) -> Option<usize>,
540 ) -> Result<ExprBytecode, BindError<A>> {
541 let mut ops = Vec::with_capacity(self.ops.len());
542 for op in self.ops {
543 let bound = match op {
544 ExprOpcode::ConstU64(value) => ExprOpcode::ConstU64(value),
545 ExprOpcode::ConstWide(value) => ExprOpcode::ConstWide(value),
546 ExprOpcode::LoadU64 {
547 location,
548 byte_size,
549 mask,
550 } => ExprOpcode::LoadU64 {
551 location: bind_location(location, &mut resolve)?,
552 byte_size,
553 mask,
554 },
555 ExprOpcode::LoadWide {
556 location,
557 byte_size,
558 width,
559 } => ExprOpcode::LoadWide {
560 location: bind_location(location, &mut resolve)?,
561 byte_size,
562 width,
563 },
564 ExprOpcode::BinOp(op) => ExprOpcode::BinOp(op),
565 ExprOpcode::TypedBinOp {
566 op,
567 lhs_width,
568 rhs_width,
569 result_width,
570 lhs_signed,
571 rhs_signed,
572 } => ExprOpcode::TypedBinOp {
573 op,
574 lhs_width,
575 rhs_width,
576 result_width,
577 lhs_signed,
578 rhs_signed,
579 },
580 ExprOpcode::TypedUnary {
581 op,
582 operand_width,
583 result_width,
584 } => ExprOpcode::TypedUnary {
585 op,
586 operand_width,
587 result_width,
588 },
589 ExprOpcode::Resize {
590 source_width,
591 target_width,
592 signed,
593 } => ExprOpcode::Resize {
594 source_width,
595 target_width,
596 signed,
597 },
598 ExprOpcode::ConcatPart {
599 part_width,
600 result_width,
601 } => ExprOpcode::ConcatPart {
602 part_width,
603 result_width,
604 },
605 ExprOpcode::Ternary { then_len, else_len } => {
606 ExprOpcode::Ternary { then_len, else_len }
607 }
608 ExprOpcode::LoadIndexed {
609 location,
610 stride_bits,
611 base_bit_offset,
612 element_width,
613 } => ExprOpcode::LoadIndexed {
614 location: bind_location(location, &mut resolve)?,
615 stride_bits,
616 base_bit_offset,
617 element_width,
618 },
619 ExprOpcode::LoadBitSelect {
620 location,
621 base_byte_size,
622 select_width,
623 } => ExprOpcode::LoadBitSelect {
624 location: bind_location(location, &mut resolve)?,
625 base_byte_size,
626 select_width,
627 },
628 ExprOpcode::StoreU64 {
629 location,
630 byte_size,
631 } => ExprOpcode::StoreU64 {
632 location: bind_location(location, &mut resolve)?,
633 byte_size,
634 },
635 };
636 ops.push(bound);
637 }
638 Ok(ExprBytecode::new(ops))
639 }
640}
641
642fn bind_location<A>(
643 location: StateLocation<A>,
644 resolve: &mut impl FnMut(&A) -> Option<usize>,
645) -> Result<usize, BindError<A>> {
646 let Some(base) = resolve(&location.address) else {
647 return Err(BindError {
648 address: location.address,
649 });
650 };
651 Ok(base + location.byte_offset)
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 #[test]
659 fn bytecode_uses_source_independent_operators() {
660 let code: ExprBytecode = ExprBytecode::new(vec![
661 ExprOpcode::ConstU64(1),
662 ExprOpcode::ConstU64(2),
663 ExprOpcode::TypedBinOp {
664 op: TestbenchOperator::Add,
665 lhs_width: 8,
666 rhs_width: 8,
667 result_width: 8,
668 lhs_signed: false,
669 rhs_signed: false,
670 },
671 ]);
672 assert_eq!(code.ops().len(), 3);
673 assert!(!code.is_empty());
674 }
675
676 #[test]
677 fn semantic_state_locations_bind_after_layout() {
678 let code = ExprBytecode::new(vec![ExprOpcode::LoadU64 {
679 location: StateLocation {
680 address: 7u32,
681 byte_offset: 3,
682 },
683 byte_size: 2,
684 mask: 0xffff,
685 }]);
686
687 let bound = code
688 .bind_with(|address| (*address == 7).then_some(100))
689 .unwrap();
690 assert_eq!(
691 bound.ops(),
692 &[ExprOpcode::LoadU64 {
693 location: 103,
694 byte_size: 2,
695 mask: 0xffff,
696 }]
697 );
698 }
699
700 #[test]
701 fn binding_reports_an_unmapped_semantic_address() {
702 let code = ExprBytecode::new(vec![ExprOpcode::StoreU64 {
703 location: StateLocation {
704 address: 9u32,
705 byte_offset: 0,
706 },
707 byte_size: 1,
708 }]);
709
710 assert_eq!(code.bind_with(|_| None), Err(BindError { address: 9 }));
711 }
712
713 #[test]
714 fn control_program_is_independent_of_runtime_handles() {
715 type SemanticStatement = TestbenchStatement<u32, u32, ExprBytecode<StateLocation<u32>>, ()>;
716
717 let statement = SemanticStatement::If {
718 expr: ExprBytecode::new(vec![ExprOpcode::LoadU64 {
719 location: StateLocation {
720 address: 1,
721 byte_offset: 0,
722 },
723 byte_size: 1,
724 mask: 1,
725 }]),
726 then_block: vec![SemanticStatement::ClockNext {
727 clock_event: 2,
728 count: ClockCount::Static(1),
729 }],
730 else_block: vec![SemanticStatement::Finish],
731 };
732
733 assert!(matches!(statement, SemanticStatement::If { .. }));
734 }
735}