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 period: u64,
152 },
153 ResetAssert {
154 reset_signal: Signal,
155 reset_event: Option<Event>,
156 clock_event: Event,
157 duration: ClockCount<Expression>,
158 period: u64,
159 assert_value: u8,
160 deassert_value: u8,
161 },
162 Assert {
163 expr: Expression,
164 site_id: u32,
165 continue_on_fail: bool,
166 message: Option<AssertMessage<Argument>>,
167 location: Option<SourceLocation>,
168 },
169 Display {
170 message: Option<AssertMessage<Argument>>,
171 newline: bool,
172 },
173 If {
174 expr: Expression,
175 then_block: Vec<Self>,
176 else_block: Vec<Self>,
177 },
178 For {
179 loop_var: Option<(Signal, usize, bool)>,
180 start: LoopBound<Expression>,
181 end: LoopBound<Expression>,
182 inclusive: bool,
183 step: usize,
184 step_op: Option<TestbenchOperator>,
185 reverse: bool,
186 body: Vec<Self>,
187 },
188 Assign {
189 dst: Target,
190 expr: Expression,
191 },
192 RandomSeed {
193 handle: String,
194 value: Expression,
195 },
196 RandomGet {
197 handle: String,
198 width: u32,
199 signed: bool,
200 ret: Option<Target>,
201 },
202 RandomGetRange {
203 handle: String,
204 min: Expression,
205 max: Expression,
206 width: u32,
207 signed: bool,
208 ret: Option<Target>,
209 },
210 RandomGetSeed {
211 handle: String,
212 ret: Option<Target>,
213 },
214 ComponentMethod {
215 instance: String,
216 method: String,
217 args: Vec<Argument>,
218 ret: Option<Target>,
219 ret_width: Option<u32>,
220 ret_signed: bool,
221 ret_strict: bool,
222 },
223 Break,
224 Finish,
225 WriteMemory {
227 signal: Signal,
228 writes: Vec<celox_design::InitialStateWriteRun>,
229 },
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
233pub struct SemanticSignal<A> {
234 pub address: A,
235 pub width: usize,
236}
237
238#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
239pub struct TestbenchSelection<Expression> {
240 pub offset: Expression,
241 pub width: usize,
242}
243
244#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
245pub struct TestbenchTarget<Signal, Expression> {
246 pub signal: Signal,
247 pub selection: Option<TestbenchSelection<Expression>>,
248 pub width: usize,
249}
250
251#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
252pub struct SemanticArgument<A> {
253 pub expr: ExprBytecode<StateLocation<A>>,
254 pub width: usize,
255 pub signed: bool,
256 pub is_string: bool,
257}
258
259pub type SemanticStatement<A> = TestbenchStatement<
260 A,
261 SemanticSignal<A>,
262 ExprBytecode<StateLocation<A>>,
263 SemanticArgument<A>,
264 TestbenchTarget<SemanticSignal<A>, ExprBytecode<StateLocation<A>>>,
265>;
266pub type SemanticComponentBinding<A> =
267 ComponentBinding<A, SemanticSignal<A>, ExprBytecode<StateLocation<A>>>;
268
269#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
270pub struct TestbenchProgram<A> {
271 statements: Vec<SemanticStatement<A>>,
272 process_lengths: Vec<usize>,
273 private_variables: Vec<A>,
274 random_seed: Option<u64>,
275 components: Vec<TestbenchComponent>,
276 component_libraries: Vec<ComponentLibrary>,
277 component_file_base: Option<PathBuf>,
278 component_bindings: Vec<SemanticComponentBinding<A>>,
279}
280
281impl<A> Default for TestbenchProgram<A> {
282 fn default() -> Self {
283 Self {
284 statements: Vec::new(),
285 process_lengths: Vec::new(),
286 private_variables: Vec::new(),
287 random_seed: None,
288 components: Vec::new(),
289 component_libraries: Vec::new(),
290 component_file_base: None,
291 component_bindings: Vec::new(),
292 }
293 }
294}
295
296impl<A> TestbenchProgram<A> {
297 pub fn new(statements: Vec<SemanticStatement<A>>) -> Self {
298 Self {
299 process_lengths: vec![statements.len()],
300 private_variables: Vec::new(),
301 statements,
302 random_seed: None,
303 components: Vec::new(),
304 component_libraries: Vec::new(),
305 component_file_base: None,
306 component_bindings: Vec::new(),
307 }
308 }
309
310 pub fn from_processes(processes: Vec<Vec<SemanticStatement<A>>>) -> Self {
312 let lengths = processes.iter().map(Vec::len).collect();
313 let mut program = Self::new(processes.into_iter().flatten().collect());
314 program.process_lengths = lengths;
315 program
316 }
317
318 pub fn with_private_variables(mut self, variables: Vec<A>) -> Self {
320 self.private_variables = variables;
321 self
322 }
323
324 pub fn private_variables(&self) -> &[A] {
325 &self.private_variables
326 }
327
328 pub fn process_lengths(&self) -> &[usize] {
329 &self.process_lengths
330 }
331
332 pub fn with_random_seed(mut self, random_seed: u64) -> Self {
333 self.random_seed = Some(random_seed);
334 self
335 }
336
337 pub fn with_random_seed_option(mut self, random_seed: Option<u64>) -> Self {
338 self.random_seed = random_seed;
339 self
340 }
341
342 pub fn statements(&self) -> &[SemanticStatement<A>] {
343 &self.statements
344 }
345
346 pub fn into_statements(self) -> Vec<SemanticStatement<A>> {
347 self.statements
348 }
349
350 pub fn random_seed(&self) -> u64 {
351 self.random_seed.unwrap_or_default()
352 }
353
354 pub fn configured_random_seed(&self) -> Option<u64> {
355 self.random_seed
356 }
357
358 pub fn with_components(mut self, components: Vec<TestbenchComponent>) -> Self {
359 self.components = components;
360 self
361 }
362
363 pub fn with_component_runtime(
364 mut self,
365 libraries: Vec<ComponentLibrary>,
366 file_base: Option<PathBuf>,
367 bindings: Vec<SemanticComponentBinding<A>>,
368 ) -> Self {
369 self.component_libraries = libraries;
370 self.component_file_base = file_base;
371 self.component_bindings = bindings;
372 self
373 }
374
375 pub fn components(&self) -> &[TestbenchComponent] {
376 &self.components
377 }
378
379 pub fn component_libraries(&self) -> &[ComponentLibrary] {
380 &self.component_libraries
381 }
382
383 pub fn component_file_base(&self) -> Option<&std::path::Path> {
384 self.component_file_base.as_deref()
385 }
386
387 pub fn component_bindings(&self) -> &[SemanticComponentBinding<A>] {
388 &self.component_bindings
389 }
390
391 pub fn try_map_paths<E>(
395 &mut self,
396 map: &mut impl FnMut(&std::path::Path) -> Result<PathBuf, E>,
397 ) -> Result<(), E> {
398 fn location<E>(
399 source: &mut Option<SourceLocation>,
400 map: &mut impl FnMut(&std::path::Path) -> Result<PathBuf, E>,
401 ) -> Result<(), E> {
402 if let Some(source) = source
403 && !source.file.is_empty()
404 {
405 source.file = map(std::path::Path::new(&source.file))?
406 .to_string_lossy()
407 .into_owned();
408 }
409 Ok(())
410 }
411 fn statements<A, E>(
412 block: &mut [SemanticStatement<A>],
413 map: &mut impl FnMut(&std::path::Path) -> Result<PathBuf, E>,
414 ) -> Result<(), E> {
415 for statement in block {
416 match statement {
417 TestbenchStatement::Assert {
418 location: source, ..
419 } => location(source, map)?,
420 TestbenchStatement::If {
421 then_block,
422 else_block,
423 ..
424 } => {
425 statements(then_block, map)?;
426 statements(else_block, map)?;
427 }
428 TestbenchStatement::For { body, .. } => statements(body, map)?,
429 _ => {}
430 }
431 }
432 Ok(())
433 }
434 for library in &mut self.component_libraries {
435 library.path = map(&library.path)?;
436 }
437 if let Some(base) = &mut self.component_file_base {
438 *base = map(base)?;
439 }
440 for component in &mut self.components {
441 location(&mut component.source, map)?;
442 }
443 statements(&mut self.statements, map)
444 }
445
446 pub fn is_empty(&self) -> bool {
447 self.statements.is_empty()
448 }
449}
450
451#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
452pub enum ExprOpcode<L = usize> {
453 ConstU64(u64),
454 ConstWide(BigUint),
455 LoadU64 {
456 location: L,
457 byte_size: usize,
458 mask: u64,
459 },
460 LoadWide {
461 location: L,
462 byte_size: usize,
463 width: usize,
464 },
465 BinOp(TestbenchOperator),
466 TypedBinOp {
467 op: TestbenchOperator,
468 lhs_width: usize,
469 rhs_width: usize,
470 result_width: usize,
471 lhs_signed: bool,
472 rhs_signed: bool,
473 },
474 TypedUnary {
475 op: TestbenchOperator,
476 operand_width: usize,
477 result_width: usize,
478 },
479 Resize {
480 source_width: usize,
481 target_width: usize,
482 signed: bool,
483 },
484 ConcatPart {
485 part_width: usize,
486 result_width: usize,
487 },
488 Ternary {
489 then_len: usize,
490 else_len: usize,
491 },
492 LoadIndexed {
493 location: L,
494 stride_bits: usize,
495 base_bit_offset: usize,
496 element_width: usize,
497 },
498 LoadBitSelect {
499 location: L,
500 base_byte_size: usize,
501 select_width: usize,
502 },
503 StoreU64 {
504 location: L,
505 byte_size: usize,
506 },
507}
508
509#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
510pub struct ExprBytecode<L = usize> {
511 ops: Vec<ExprOpcode<L>>,
512}
513
514impl<L> ExprBytecode<L> {
515 pub fn new(ops: Vec<ExprOpcode<L>>) -> Self {
516 Self { ops }
517 }
518
519 pub fn ops(&self) -> &[ExprOpcode<L>] {
520 &self.ops
521 }
522
523 pub fn is_empty(&self) -> bool {
524 self.ops.is_empty()
525 }
526}
527
528#[derive(Clone, Debug, PartialEq, Eq)]
529pub struct BindError<A> {
530 pub address: A,
531}
532
533pub struct ExecutableArgument {
534 pub expr: CompiledExpr,
535 pub width: usize,
536 pub signed: bool,
537 pub is_string: bool,
538}
539
540pub type ExecutableAssertMessage = AssertMessage<ExecutableArgument>;
541pub type ExecutableClockCount = ClockCount<CompiledExpr>;
542pub type ExecutableLoopBound = LoopBound<CompiledExpr>;
543pub type ExecutableStatement<Event, Signal> = TestbenchStatement<
544 Event,
545 Signal,
546 CompiledExpr,
547 ExecutableArgument,
548 TestbenchTarget<Signal, CompiledExpr>,
549>;
550pub type ExecutableComponentBinding<Event, Signal> = ComponentBinding<Event, Signal, CompiledExpr>;
551
552pub struct ExecutableTestbench<Event, Signal> {
553 statements: Vec<ExecutableStatement<Event, Signal>>,
554 process_lengths: Vec<usize>,
555 private_signals: Vec<Signal>,
556 random_seed: Option<u64>,
557 components: Vec<TestbenchComponent>,
558 component_libraries: Vec<ComponentLibrary>,
559 component_file_base: Option<PathBuf>,
560 component_bindings: Vec<ExecutableComponentBinding<Event, Signal>>,
561}
562
563impl<Event, Signal> ExecutableTestbench<Event, Signal> {
564 pub fn new(statements: Vec<ExecutableStatement<Event, Signal>>, random_seed: u64) -> Self {
565 Self::new_with_random_seed(statements, Some(random_seed))
566 }
567
568 pub fn new_with_random_seed(
569 statements: Vec<ExecutableStatement<Event, Signal>>,
570 random_seed: Option<u64>,
571 ) -> Self {
572 Self {
573 process_lengths: vec![statements.len()],
574 private_signals: Vec::new(),
575 statements,
576 random_seed,
577 components: Vec::new(),
578 component_libraries: Vec::new(),
579 component_file_base: None,
580 component_bindings: Vec::new(),
581 }
582 }
583
584 pub fn with_component_runtime(
585 mut self,
586 components: Vec<TestbenchComponent>,
587 libraries: Vec<ComponentLibrary>,
588 file_base: Option<PathBuf>,
589 bindings: Vec<ExecutableComponentBinding<Event, Signal>>,
590 ) -> Self {
591 self.components = components;
592 self.component_libraries = libraries;
593 self.component_file_base = file_base;
594 self.component_bindings = bindings;
595 self
596 }
597
598 pub fn from_processes(
600 processes: Vec<Vec<ExecutableStatement<Event, Signal>>>,
601 random_seed: Option<u64>,
602 ) -> Self {
603 let lengths = processes.iter().map(Vec::len).collect();
604 let mut program =
605 Self::new_with_random_seed(processes.into_iter().flatten().collect(), random_seed);
606 program.process_lengths = lengths;
607 program
608 }
609
610 pub fn with_private_signals(mut self, signals: Vec<Signal>) -> Self {
611 self.private_signals = signals;
612 self
613 }
614
615 pub fn private_signals(&self) -> &[Signal] {
616 &self.private_signals
617 }
618
619 pub fn processes(&self) -> impl Iterator<Item = &[ExecutableStatement<Event, Signal>]> {
620 self.process_lengths.iter().scan(0, |offset, &length| {
621 let start = *offset;
622 *offset += length;
623 Some(&self.statements[start..*offset])
624 })
625 }
626
627 pub fn statements(&self) -> &[ExecutableStatement<Event, Signal>] {
628 &self.statements
629 }
630
631 pub fn into_statements(self) -> Vec<ExecutableStatement<Event, Signal>> {
632 self.statements
633 }
634
635 pub fn random_seed(&self) -> u64 {
636 self.random_seed.unwrap_or_default()
637 }
638
639 pub fn configured_random_seed(&self) -> Option<u64> {
640 self.random_seed
641 }
642
643 pub fn components(&self) -> &[TestbenchComponent] {
644 &self.components
645 }
646
647 pub fn component_libraries(&self) -> &[ComponentLibrary] {
648 &self.component_libraries
649 }
650
651 pub fn component_file_base(&self) -> Option<&std::path::Path> {
652 self.component_file_base.as_deref()
653 }
654
655 pub fn component_bindings(&self) -> &[ExecutableComponentBinding<Event, Signal>] {
656 &self.component_bindings
657 }
658}
659
660impl<A> ExprBytecode<StateLocation<A>> {
661 pub fn bind_with(
662 self,
663 mut resolve: impl FnMut(&A) -> Option<usize>,
664 ) -> Result<ExprBytecode, BindError<A>> {
665 let mut ops = Vec::with_capacity(self.ops.len());
666 for op in self.ops {
667 let bound = match op {
668 ExprOpcode::ConstU64(value) => ExprOpcode::ConstU64(value),
669 ExprOpcode::ConstWide(value) => ExprOpcode::ConstWide(value),
670 ExprOpcode::LoadU64 {
671 location,
672 byte_size,
673 mask,
674 } => ExprOpcode::LoadU64 {
675 location: bind_location(location, &mut resolve)?,
676 byte_size,
677 mask,
678 },
679 ExprOpcode::LoadWide {
680 location,
681 byte_size,
682 width,
683 } => ExprOpcode::LoadWide {
684 location: bind_location(location, &mut resolve)?,
685 byte_size,
686 width,
687 },
688 ExprOpcode::BinOp(op) => ExprOpcode::BinOp(op),
689 ExprOpcode::TypedBinOp {
690 op,
691 lhs_width,
692 rhs_width,
693 result_width,
694 lhs_signed,
695 rhs_signed,
696 } => ExprOpcode::TypedBinOp {
697 op,
698 lhs_width,
699 rhs_width,
700 result_width,
701 lhs_signed,
702 rhs_signed,
703 },
704 ExprOpcode::TypedUnary {
705 op,
706 operand_width,
707 result_width,
708 } => ExprOpcode::TypedUnary {
709 op,
710 operand_width,
711 result_width,
712 },
713 ExprOpcode::Resize {
714 source_width,
715 target_width,
716 signed,
717 } => ExprOpcode::Resize {
718 source_width,
719 target_width,
720 signed,
721 },
722 ExprOpcode::ConcatPart {
723 part_width,
724 result_width,
725 } => ExprOpcode::ConcatPart {
726 part_width,
727 result_width,
728 },
729 ExprOpcode::Ternary { then_len, else_len } => {
730 ExprOpcode::Ternary { then_len, else_len }
731 }
732 ExprOpcode::LoadIndexed {
733 location,
734 stride_bits,
735 base_bit_offset,
736 element_width,
737 } => ExprOpcode::LoadIndexed {
738 location: bind_location(location, &mut resolve)?,
739 stride_bits,
740 base_bit_offset,
741 element_width,
742 },
743 ExprOpcode::LoadBitSelect {
744 location,
745 base_byte_size,
746 select_width,
747 } => ExprOpcode::LoadBitSelect {
748 location: bind_location(location, &mut resolve)?,
749 base_byte_size,
750 select_width,
751 },
752 ExprOpcode::StoreU64 {
753 location,
754 byte_size,
755 } => ExprOpcode::StoreU64 {
756 location: bind_location(location, &mut resolve)?,
757 byte_size,
758 },
759 };
760 ops.push(bound);
761 }
762 Ok(ExprBytecode::new(ops))
763 }
764}
765
766fn bind_location<A>(
767 location: StateLocation<A>,
768 resolve: &mut impl FnMut(&A) -> Option<usize>,
769) -> Result<usize, BindError<A>> {
770 let Some(base) = resolve(&location.address) else {
771 return Err(BindError {
772 address: location.address,
773 });
774 };
775 Ok(base + location.byte_offset)
776}
777
778#[cfg(test)]
779mod tests {
780 use super::*;
781
782 #[test]
783 fn bytecode_uses_source_independent_operators() {
784 let code: ExprBytecode = ExprBytecode::new(vec![
785 ExprOpcode::ConstU64(1),
786 ExprOpcode::ConstU64(2),
787 ExprOpcode::TypedBinOp {
788 op: TestbenchOperator::Add,
789 lhs_width: 8,
790 rhs_width: 8,
791 result_width: 8,
792 lhs_signed: false,
793 rhs_signed: false,
794 },
795 ]);
796 assert_eq!(code.ops().len(), 3);
797 assert!(!code.is_empty());
798 }
799
800 #[test]
801 fn semantic_state_locations_bind_after_layout() {
802 let code = ExprBytecode::new(vec![ExprOpcode::LoadU64 {
803 location: StateLocation {
804 address: 7u32,
805 byte_offset: 3,
806 },
807 byte_size: 2,
808 mask: 0xffff,
809 }]);
810
811 let bound = code
812 .bind_with(|address| (*address == 7).then_some(100))
813 .unwrap();
814 assert_eq!(
815 bound.ops(),
816 &[ExprOpcode::LoadU64 {
817 location: 103,
818 byte_size: 2,
819 mask: 0xffff,
820 }]
821 );
822 }
823
824 #[test]
825 fn binding_reports_an_unmapped_semantic_address() {
826 let code = ExprBytecode::new(vec![ExprOpcode::StoreU64 {
827 location: StateLocation {
828 address: 9u32,
829 byte_offset: 0,
830 },
831 byte_size: 1,
832 }]);
833
834 assert_eq!(code.bind_with(|_| None), Err(BindError { address: 9 }));
835 }
836
837 #[test]
838 fn control_program_is_independent_of_runtime_handles() {
839 type SemanticStatement = TestbenchStatement<u32, u32, ExprBytecode<StateLocation<u32>>, ()>;
840
841 let statement = SemanticStatement::If {
842 expr: ExprBytecode::new(vec![ExprOpcode::LoadU64 {
843 location: StateLocation {
844 address: 1,
845 byte_offset: 0,
846 },
847 byte_size: 1,
848 mask: 1,
849 }]),
850 then_block: vec![SemanticStatement::ClockNext {
851 clock_event: 2,
852 count: ClockCount::Static(1),
853 period: 2,
854 }],
855 else_block: vec![SemanticStatement::Finish],
856 };
857
858 assert!(matches!(statement, SemanticStatement::If { .. }));
859 }
860}