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 { clock_event, count } => {
188            Some(GenericTestbenchStatement::ClockNext {
189                clock_event: backend.resolve_event_opt(&clock_event)?,
190                count: bind_clock_count(backend, count)?,
191            })
192        }
193        GenericTestbenchStatement::ResetAssert {
194            reset_signal,
195            reset_event,
196            clock_event,
197            duration,
198            assert_value,
199            deassert_value,
200        } => Some(GenericTestbenchStatement::ResetAssert {
201            reset_signal: backend.resolve_signal(&reset_signal.address),
202            reset_event: reset_event.and_then(|event| backend.resolve_event_opt(&event)),
203            clock_event: backend.resolve_event_opt(&clock_event)?,
204            duration: bind_clock_count(backend, duration)?,
205            assert_value,
206            deassert_value,
207        }),
208        GenericTestbenchStatement::Assert {
209            expr,
210            site_id,
211            continue_on_fail,
212            message,
213            location,
214        } => Some(GenericTestbenchStatement::Assert {
215            expr: bind_expr(backend, expr)?,
216            site_id,
217            continue_on_fail,
218            message: match message {
219                Some(message) => Some(bind_assert_message(backend, message)?),
220                None => None,
221            },
222            location,
223        }),
224        GenericTestbenchStatement::Display { message, newline } => {
225            Some(GenericTestbenchStatement::Display {
226                message: match message {
227                    Some(message) => Some(bind_assert_message(backend, message)?),
228                    None => None,
229                },
230                newline,
231            })
232        }
233        GenericTestbenchStatement::If {
234            expr,
235            then_block,
236            else_block,
237        } => Some(GenericTestbenchStatement::If {
238            expr: bind_expr(backend, expr)?,
239            then_block: then_block
240                .into_iter()
241                .map(|statement| bind_statement(backend, statement))
242                .collect::<Option<Vec<_>>>()?,
243            else_block: else_block
244                .into_iter()
245                .map(|statement| bind_statement(backend, statement))
246                .collect::<Option<Vec<_>>>()?,
247        }),
248        GenericTestbenchStatement::For {
249            loop_var,
250            start,
251            end,
252            inclusive,
253            step,
254            step_op,
255            reverse,
256            body,
257        } => Some(GenericTestbenchStatement::For {
258            loop_var: loop_var.map(|(signal, width, signed)| {
259                (backend.resolve_signal(&signal.address), width, signed)
260            }),
261            start: bind_loop_bound(backend, start)?,
262            end: bind_loop_bound(backend, end)?,
263            inclusive,
264            step,
265            step_op,
266            reverse,
267            body: body
268                .into_iter()
269                .map(|statement| bind_statement(backend, statement))
270                .collect::<Option<Vec<_>>>()?,
271        }),
272        GenericTestbenchStatement::Assign { dst, expr } => {
273            Some(GenericTestbenchStatement::Assign {
274                dst: bind_target(backend, dst)?,
275                expr: bind_expr(backend, expr)?,
276            })
277        }
278        GenericTestbenchStatement::RandomSeed { handle, value } => {
279            Some(GenericTestbenchStatement::RandomSeed {
280                handle,
281                value: bind_expr(backend, value)?,
282            })
283        }
284        GenericTestbenchStatement::RandomGet {
285            handle,
286            width,
287            signed,
288            ret,
289        } => Some(GenericTestbenchStatement::RandomGet {
290            handle,
291            width,
292            signed,
293            ret: bind_optional_target(backend, ret)?,
294        }),
295        GenericTestbenchStatement::RandomGetRange {
296            handle,
297            min,
298            max,
299            width,
300            signed,
301            ret,
302        } => Some(GenericTestbenchStatement::RandomGetRange {
303            handle,
304            min: bind_expr(backend, min)?,
305            max: bind_expr(backend, max)?,
306            width,
307            signed,
308            ret: bind_optional_target(backend, ret)?,
309        }),
310        GenericTestbenchStatement::RandomGetSeed { handle, ret } => {
311            Some(GenericTestbenchStatement::RandomGetSeed {
312                handle,
313                ret: bind_optional_target(backend, ret)?,
314            })
315        }
316        GenericTestbenchStatement::ComponentMethod {
317            instance,
318            method,
319            args,
320            ret,
321            ret_width,
322            ret_signed,
323            ret_strict,
324        } => Some(GenericTestbenchStatement::ComponentMethod {
325            instance,
326            method,
327            args: args
328                .into_iter()
329                .map(|arg| bind_assert_arg(backend, arg))
330                .collect::<Option<Vec<_>>>()?,
331            ret: bind_optional_target(backend, ret)?,
332            ret_width,
333            ret_signed,
334            ret_strict,
335        }),
336        GenericTestbenchStatement::Break => Some(GenericTestbenchStatement::Break),
337        GenericTestbenchStatement::Finish => Some(GenericTestbenchStatement::Finish),
338    }
339}
340
341pub fn bind_testbench_program<B: SimBackend>(
342    backend: &B,
343    program: TestbenchProgram<AbsoluteAddr>,
344    rtl_writes: &fxhash::FxHashSet<VarAtomBase<AbsoluteAddr>>,
345) -> Option<ExecutableTestbench<B::Event, SignalRef>> {
346    let random_seed = program.configured_random_seed();
347    let components = program.components().to_vec();
348    let component_libraries = program.component_libraries().to_vec();
349    let component_file_base = program.component_file_base().map(ToOwned::to_owned);
350    let component_bindings = program
351        .component_bindings()
352        .to_vec()
353        .into_iter()
354        .map(|component| bind_component(backend, component, rtl_writes))
355        .collect::<Option<Vec<_>>>()?;
356    let statements = program
357        .into_statements()
358        .into_iter()
359        .map(|statement| bind_statement(backend, statement))
360        .collect::<Option<Vec<_>>>()?;
361    Some(
362        ExecutableTestbench::new_with_random_seed(statements, random_seed).with_component_runtime(
363            components,
364            component_libraries,
365            component_file_base,
366            component_bindings,
367        ),
368    )
369}