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 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}