Skip to main content

celox_runtime/
testbench.rs

1use celox_design::{BitAccess, StateAddr as AbsoluteAddr, VarAtomBase};
2use celox_testbench::{
3    AssertMessage as GenericAssertMessage, ClockCount as GenericClockCount, CompiledExpr,
4    ExecutableArgument, ExecutableAssertMessage, ExecutableClockCount, ExecutableLoopBound,
5    ExecutableStatement, ExecutableTestbench, ExprBytecode, LoopBound as GenericLoopBound,
6    SemanticArgument, SemanticComponentBinding, SemanticStatement, StateLocation, TestbenchProgram,
7    TestbenchSelection, TestbenchStatement as GenericTestbenchStatement, TestbenchTarget,
8};
9
10use crate::{SignalRef, backend::SimBackend};
11
12fn bind_expr<B: SimBackend>(
13    backend: &B,
14    expr: ExprBytecode<StateLocation<AbsoluteAddr>>,
15) -> Option<CompiledExpr> {
16    let layout = backend.layout();
17    let bytecode = expr
18        .bind_with(|address| layout.offsets.get(address).copied())
19        .ok()?;
20    Some(CompiledExpr::new(bytecode))
21}
22
23fn bind_component<B: SimBackend>(
24    backend: &B,
25    component: SemanticComponentBinding<AbsoluteAddr>,
26    rtl_writes: &fxhash::FxHashSet<VarAtomBase<AbsoluteAddr>>,
27) -> Option<celox_testbench::ExecutableComponentBinding<B::Event, SignalRef>> {
28    Some(celox_testbench::ComponentBinding {
29        instance: component.instance,
30        connections: component
31            .connections
32            .into_iter()
33            .map(|connection| {
34                let output = match connection.output {
35                    Some(output) => Some(bind_target(backend, output)?),
36                    None => None,
37                };
38                let output_rtl_driven = output.as_ref().is_some_and(|output| {
39                    let target_access = match &output.selection {
40                        Some(selection) => selection
41                            .offset
42                            .constant_u64()
43                            .and_then(|offset| usize::try_from(offset).ok())
44                            .and_then(|lsb| {
45                                output
46                                    .width
47                                    .checked_sub(1)
48                                    .and_then(|tail| lsb.checked_add(tail))
49                                    .map(|msb| BitAccess::new(lsb, msb))
50                            }),
51                        None => output
52                            .signal
53                            .width
54                            .checked_sub(1)
55                            .map(|msb| BitAccess::new(0, msb)),
56                    };
57                    rtl_writes.iter().any(|write| {
58                        backend.resolve_signal(&write.id) == output.signal
59                            && target_access.is_none_or(|target| target.overlaps(&write.access))
60                    })
61                });
62                Some(celox_testbench::ComponentConnectionBinding {
63                    port: connection.port,
64                    input: match connection.input {
65                        Some(input) => Some(bind_expr(backend, input)?),
66                        None => None,
67                    },
68                    input_target: match connection.input_target {
69                        Some(input) => Some(bind_target(backend, input)?),
70                        None => None,
71                    },
72                    output,
73                    output_rtl_driven,
74                    event: connection
75                        .event
76                        .and_then(|event| backend.resolve_event_opt(&event)),
77                })
78            })
79            .collect::<Option<Vec<_>>>()?,
80    })
81}
82
83fn bind_assert_arg<B: SimBackend>(
84    backend: &B,
85    arg: SemanticArgument<AbsoluteAddr>,
86) -> Option<ExecutableArgument> {
87    Some(ExecutableArgument {
88        expr: bind_expr(backend, arg.expr)?,
89        width: arg.width,
90        signed: arg.signed,
91        is_string: arg.is_string,
92    })
93}
94
95fn bind_assert_message<B: SimBackend>(
96    backend: &B,
97    message: GenericAssertMessage<SemanticArgument<AbsoluteAddr>>,
98) -> Option<ExecutableAssertMessage> {
99    match message {
100        GenericAssertMessage::Formatted { template, args } => {
101            let args = args
102                .into_iter()
103                .map(|arg| bind_assert_arg(backend, arg))
104                .collect::<Option<Vec<_>>>()?;
105            Some(GenericAssertMessage::Formatted { template, args })
106        }
107        GenericAssertMessage::DynamicArgs(args) => {
108            let args = args
109                .into_iter()
110                .map(|arg| bind_assert_arg(backend, arg))
111                .collect::<Option<Vec<_>>>()?;
112            Some(GenericAssertMessage::DynamicArgs(args))
113        }
114    }
115}
116
117fn bind_clock_count<B: SimBackend>(
118    backend: &B,
119    count: GenericClockCount<ExprBytecode<StateLocation<AbsoluteAddr>>>,
120) -> Option<ExecutableClockCount> {
121    match count {
122        GenericClockCount::Static(count) => Some(GenericClockCount::Static(count)),
123        GenericClockCount::Dynamic(expr) => {
124            Some(GenericClockCount::Dynamic(bind_expr(backend, expr)?))
125        }
126    }
127}
128
129fn bind_loop_bound<B: SimBackend>(
130    backend: &B,
131    bound: GenericLoopBound<ExprBytecode<StateLocation<AbsoluteAddr>>>,
132) -> Option<ExecutableLoopBound> {
133    match bound {
134        GenericLoopBound::Static(bound) => Some(GenericLoopBound::Static(bound)),
135        GenericLoopBound::Dynamic {
136            expr,
137            width,
138            signed,
139        } => Some(GenericLoopBound::Dynamic {
140            expr: bind_expr(backend, expr)?,
141            width,
142            signed,
143        }),
144    }
145}
146
147fn bind_target<B: SimBackend>(
148    backend: &B,
149    target: TestbenchTarget<
150        celox_testbench::SemanticSignal<AbsoluteAddr>,
151        ExprBytecode<StateLocation<AbsoluteAddr>>,
152    >,
153) -> Option<TestbenchTarget<SignalRef, CompiledExpr>> {
154    Some(TestbenchTarget {
155        signal: backend.resolve_signal(&target.signal.address),
156        selection: match target.selection {
157            Some(selection) => Some(TestbenchSelection {
158                offset: bind_expr(backend, selection.offset)?,
159                width: selection.width,
160            }),
161            None => None,
162        },
163        width: target.width,
164    })
165}
166
167fn bind_optional_target<B: SimBackend>(
168    backend: &B,
169    target: Option<
170        TestbenchTarget<
171            celox_testbench::SemanticSignal<AbsoluteAddr>,
172            ExprBytecode<StateLocation<AbsoluteAddr>>,
173        >,
174    >,
175) -> Option<Option<TestbenchTarget<SignalRef, CompiledExpr>>> {
176    match target {
177        Some(target) => Some(Some(bind_target(backend, target)?)),
178        None => Some(None),
179    }
180}
181
182fn bind_statement<B: SimBackend>(
183    backend: &B,
184    statement: SemanticStatement<AbsoluteAddr>,
185) -> Option<ExecutableStatement<B::Event, SignalRef>> {
186    match statement {
187        GenericTestbenchStatement::ClockNext {
188            clock_event,
189            count,
190            period,
191        } => Some(GenericTestbenchStatement::ClockNext {
192            clock_event: backend.resolve_event_opt(&clock_event)?,
193            count: bind_clock_count(backend, count)?,
194            period,
195        }),
196        GenericTestbenchStatement::ResetAssert {
197            reset_signal,
198            reset_event,
199            clock_event,
200            duration,
201            period,
202            assert_value,
203            deassert_value,
204        } => Some(GenericTestbenchStatement::ResetAssert {
205            reset_signal: backend.resolve_signal(&reset_signal.address),
206            reset_event: reset_event.and_then(|event| backend.resolve_event_opt(&event)),
207            clock_event: backend.resolve_event_opt(&clock_event)?,
208            duration: bind_clock_count(backend, duration)?,
209            period,
210            assert_value,
211            deassert_value,
212        }),
213        GenericTestbenchStatement::Assert {
214            expr,
215            site_id,
216            continue_on_fail,
217            message,
218            location,
219        } => Some(GenericTestbenchStatement::Assert {
220            expr: bind_expr(backend, expr)?,
221            site_id,
222            continue_on_fail,
223            message: match message {
224                Some(message) => Some(bind_assert_message(backend, message)?),
225                None => None,
226            },
227            location,
228        }),
229        GenericTestbenchStatement::Display { message, newline } => {
230            Some(GenericTestbenchStatement::Display {
231                message: match message {
232                    Some(message) => Some(bind_assert_message(backend, message)?),
233                    None => None,
234                },
235                newline,
236            })
237        }
238        GenericTestbenchStatement::If {
239            expr,
240            then_block,
241            else_block,
242        } => Some(GenericTestbenchStatement::If {
243            expr: bind_expr(backend, expr)?,
244            then_block: then_block
245                .into_iter()
246                .map(|statement| bind_statement(backend, statement))
247                .collect::<Option<Vec<_>>>()?,
248            else_block: else_block
249                .into_iter()
250                .map(|statement| bind_statement(backend, statement))
251                .collect::<Option<Vec<_>>>()?,
252        }),
253        GenericTestbenchStatement::For {
254            loop_var,
255            start,
256            end,
257            inclusive,
258            step,
259            step_op,
260            reverse,
261            body,
262        } => Some(GenericTestbenchStatement::For {
263            loop_var: loop_var.map(|(signal, width, signed)| {
264                (backend.resolve_signal(&signal.address), width, signed)
265            }),
266            start: bind_loop_bound(backend, start)?,
267            end: bind_loop_bound(backend, end)?,
268            inclusive,
269            step,
270            step_op,
271            reverse,
272            body: body
273                .into_iter()
274                .map(|statement| bind_statement(backend, statement))
275                .collect::<Option<Vec<_>>>()?,
276        }),
277        GenericTestbenchStatement::Assign { dst, expr } => {
278            Some(GenericTestbenchStatement::Assign {
279                dst: bind_target(backend, dst)?,
280                expr: bind_expr(backend, expr)?,
281            })
282        }
283        GenericTestbenchStatement::RandomSeed { handle, value } => {
284            Some(GenericTestbenchStatement::RandomSeed {
285                handle,
286                value: bind_expr(backend, value)?,
287            })
288        }
289        GenericTestbenchStatement::RandomGet {
290            handle,
291            width,
292            signed,
293            ret,
294        } => Some(GenericTestbenchStatement::RandomGet {
295            handle,
296            width,
297            signed,
298            ret: bind_optional_target(backend, ret)?,
299        }),
300        GenericTestbenchStatement::RandomGetRange {
301            handle,
302            min,
303            max,
304            width,
305            signed,
306            ret,
307        } => Some(GenericTestbenchStatement::RandomGetRange {
308            handle,
309            min: bind_expr(backend, min)?,
310            max: bind_expr(backend, max)?,
311            width,
312            signed,
313            ret: bind_optional_target(backend, ret)?,
314        }),
315        GenericTestbenchStatement::RandomGetSeed { handle, ret } => {
316            Some(GenericTestbenchStatement::RandomGetSeed {
317                handle,
318                ret: bind_optional_target(backend, ret)?,
319            })
320        }
321        GenericTestbenchStatement::ComponentMethod {
322            instance,
323            method,
324            args,
325            ret,
326            ret_width,
327            ret_signed,
328            ret_strict,
329        } => Some(GenericTestbenchStatement::ComponentMethod {
330            instance,
331            method,
332            args: args
333                .into_iter()
334                .map(|arg| bind_assert_arg(backend, arg))
335                .collect::<Option<Vec<_>>>()?,
336            ret: bind_optional_target(backend, ret)?,
337            ret_width,
338            ret_signed,
339            ret_strict,
340        }),
341        GenericTestbenchStatement::Break => Some(GenericTestbenchStatement::Break),
342        GenericTestbenchStatement::Finish => Some(GenericTestbenchStatement::Finish),
343        GenericTestbenchStatement::WriteMemory { signal, writes } => {
344            Some(GenericTestbenchStatement::WriteMemory {
345                signal: backend.resolve_signal(&signal.address),
346                writes,
347            })
348        }
349    }
350}
351
352pub fn bind_testbench_program<B: SimBackend>(
353    backend: &B,
354    program: TestbenchProgram<AbsoluteAddr>,
355    rtl_writes: &fxhash::FxHashSet<VarAtomBase<AbsoluteAddr>>,
356) -> Option<ExecutableTestbench<B::Event, SignalRef>> {
357    let lengths = program.process_lengths().to_vec();
358    if lengths
359        .iter()
360        .try_fold(0usize, |sum, n| sum.checked_add(*n))?
361        != program.statements().len()
362    {
363        return None;
364    }
365    let mut private_signals = Vec::new();
366    for address in program.private_variables() {
367        let signal = backend.resolve_signal(address);
368        if let Some(array) = signal.array_layout {
369            // Bind each array plane separately: padding and mask-plane strides
370            // need not match a scalar signal's adjacent value/mask layout.
371            for index in 0..array.element_count {
372                let element = SignalRef {
373                    offset: signal.offset + index * array.element_stride,
374                    width: array.element_width,
375                    is_4state: false,
376                    array_layout: None,
377                };
378                private_signals.push(element);
379                if signal.is_4state && backend.layout().four_state {
380                    private_signals.push(SignalRef {
381                        offset: element.offset + array.plane_size,
382                        ..element
383                    });
384                }
385            }
386        } else {
387            private_signals.push(signal);
388        }
389    }
390    let random_seed = program.configured_random_seed();
391    let components = program.components().to_vec();
392    let component_libraries = program.component_libraries().to_vec();
393    let component_file_base = program.component_file_base().map(ToOwned::to_owned);
394    let component_bindings = program
395        .component_bindings()
396        .to_vec()
397        .into_iter()
398        .map(|component| bind_component(backend, component, rtl_writes))
399        .collect::<Option<Vec<_>>>()?;
400    let statements = program
401        .into_statements()
402        .into_iter()
403        .map(|statement| bind_statement(backend, statement))
404        .collect::<Option<Vec<_>>>()?;
405    let mut statements = statements.into_iter();
406    let processes = lengths
407        .into_iter()
408        .map(|n| statements.by_ref().take(n).collect())
409        .collect();
410    Some(
411        ExecutableTestbench::from_processes(processes, random_seed)
412            .with_private_signals(private_signals)
413            .with_component_runtime(
414                components,
415                component_libraries,
416                component_file_base,
417                component_bindings,
418            ),
419    )
420}