1use std::collections::{BTreeMap, BTreeSet};
2
3use crate::symbolic::artifact::{
4 RelocationModule, SimModule, SymbolicGlueAddr as GlueAddr, SymbolicRtl,
5};
6use crate::{
7 FrontendLookup, FrontendTrace, FrontendTraceOptions, FusedSirOptimizationHints, HashMap,
8 HashSet, InstancePath, ParserError, ScheduledRtl, ScheduledRtlOutput, SourceAddr,
9 SourceLocation, SourceVarId, VariableInfo, flattening,
10};
11use celox_design::{
12 BitAccess, DomainKind, ElaboratedDesign, EventTopology, InitialStateValue, InstanceId,
13 ModuleId, RegionedAbsoluteAddrBase, RegionedStateAddr, RuntimeCombObserver, RuntimeErrorInfo,
14 RuntimeEventKind, RuntimeEventSite, RuntimeSchema, STABLE_REGION, StateAddr, StateObjectId,
15 TriggerSet, VarAtomBase, VariableMetadata,
16};
17use celox_sir::{BasicBlock, ExecutionUnit, SIRInstruction, SIRTerminator, SirProgram};
18use celox_slt::{
19 CombObserver, FfAccessSummary, LogicPath, LogicPathId, LogicPathTarget, NodeId, SLTNodeArena,
20 scheduler::{self, SchedulerError},
21};
22
23type AbsoluteAddr = SourceAddr;
24type RegionedAbsoluteAddr = RegionedAbsoluteAddrBase<SourceVarId>;
25type RegionedVarAddr = celox_design::RegionedVarAddrBase<SourceVarId>;
26
27#[derive(Clone, Debug)]
31pub struct FfRuntimeRelocation {
32 pub error_codes: HashMap<i64, i64>,
33 pub event_site_base: u32,
34}
35
36#[derive(Clone)]
39pub struct FusedFfAction {
40 pub id: usize,
41 pub instance_id: InstanceId,
42 pub module_id: ModuleId,
43 pub trigger: TriggerSet<SourceVarId>,
44 pub summary: FfAccessSummary<RegionedAbsoluteAddrBase<SourceVarId>>,
45 pub runtime: FfRuntimeRelocation,
46}
47
48pub trait FusedFfLoweringFactory {
52 fn create(
53 &self,
54 actions: Vec<FusedFfAction>,
55 ) -> Result<
56 Box<
57 dyn scheduler::ClockFfLowering<
58 RegionedAbsoluteAddrBase<SourceVarId>,
59 Error = ParserError,
60 > + '_,
61 >,
62 ParserError,
63 >;
64}
65
66fn elaborated_scope_name(
67 root_name: &str,
68 path: &InstancePath,
69 expanded: &HashMap<InstancePath, InstanceId>,
70 indexed_instances: &HashSet<InstanceId>,
71) -> String {
72 let mut prefix = Vec::with_capacity(path.0.len());
73 let segments = path
74 .0
75 .iter()
76 .map(|(name, index)| {
77 prefix.push((name.clone(), *index));
78 let indexed = expanded
79 .get(&InstancePath(prefix.clone()))
80 .is_some_and(|id| indexed_instances.contains(id));
81 if indexed {
82 format!("{name}[{index}]")
83 } else {
84 name.clone()
85 }
86 })
87 .collect::<Vec<_>>();
88 if segments.is_empty() {
89 root_name.to_string()
90 } else {
91 format!("{root_name}.{}", segments.join("."))
92 }
93}
94
95fn flatten_with_trace(
96 module: &SimModule,
97 path: &InstancePath,
98 instance_ids: &HashMap<InstancePath, InstanceId>,
99 global_boundaries: &HashMap<AbsoluteAddr, BTreeSet<usize>>,
100 unpacked_element_widths: &HashMap<AbsoluteAddr, usize>,
101 arena: &mut SLTNodeArena<AbsoluteAddr>,
102 trace_opts: &FrontendTraceOptions,
103 mut trace: Option<&mut FrontendTrace>,
104) -> Result<RelocationModule, celox_slt::SLTNodeFactsError> {
105 let flattened = flattening::flatten_module(
106 module,
107 path,
108 instance_ids,
109 global_boundaries,
110 unpacked_element_widths,
111 arena,
112 )?;
113
114 if let Some(trace) = trace.as_deref_mut()
115 && trace_opts.pre_atomized_comb_blocks
116 {
117 match &mut trace.pre_atomized_comb_blocks {
118 Some((blocks, trace_arena)) => {
119 blocks.extend(flattened.pre_atomized_comb_blocks);
120 *trace_arena = arena.clone();
121 }
122 slot @ None => *slot = Some((flattened.pre_atomized_comb_blocks, arena.clone())),
123 }
124 }
125
126 if let Some(trace) = trace
127 && trace_opts.atomized_comb_blocks
128 {
129 match &mut trace.atomized_comb_blocks {
130 Some((blocks, trace_arena)) => {
131 blocks.extend(flattened.relocation.comb_blocks.iter().cloned());
132 *trace_arena = arena.clone();
133 }
134 slot @ None => {
135 *slot = Some((flattened.relocation.comb_blocks.clone(), arena.clone()));
136 }
137 }
138 }
139
140 Ok(flattened.relocation)
141}
142
143fn remap_for_fold_runtime_event_sites<A: std::hash::Hash + Eq + Clone>(
144 arena: &mut SLTNodeArena<A>,
145 start: usize,
146 runtime_event_site_map: &HashMap<u32, u32>,
147) -> Result<(), ParserError> {
148 arena
149 .remap_for_fold_effect_sites(start..arena.len(), |site_id, fatal_error_code| {
150 Ok(runtime_event_site_map.get(&site_id).map(|&global_site| {
151 (
152 global_site,
153 fatal_error_code.map(|_| i64::from(global_site)),
154 )
155 }))
156 })
157 .map_err(|error| {
158 ParserError::illegal_context(
159 "ForFold runtime-event relocation",
160 error.to_string(),
161 None,
162 )
163 })
164}
165
166fn create_absolute_addr(
167 instance_path: &[(String, usize)],
168 var_path: &[String],
169 instance_modules: &HashMap<InstanceId, ModuleId>,
170 modules: &HashMap<ModuleId, SimModule>,
171 expanded: &HashMap<InstancePath, InstanceId>,
172) -> AbsoluteAddr {
173 let instance_path = InstancePath(instance_path.to_vec());
174 let instance_id = expanded[&instance_path];
175 let module_id = instance_modules[&instance_id];
176 let module = &modules[&module_id];
177 let var_id = *module
178 .variables
179 .iter()
180 .find(|(_, variable)| variable.path == var_path)
181 .unwrap()
182 .0;
183 AbsoluteAddr {
184 instance_id,
185 var_id,
186 }
187}
188fn parse_ignored_loops(
189 ignored_loops: &[(
190 (Vec<(String, usize)>, Vec<String>),
191 (Vec<(String, usize)>, Vec<String>),
192 )],
193 instance_modules: &HashMap<InstanceId, ModuleId>,
194 modules: &HashMap<ModuleId, SimModule>,
195 expanded: &HashMap<InstancePath, InstanceId>,
196) -> HashSet<(AbsoluteAddr, AbsoluteAddr)> {
197 let mut res = HashSet::default();
198
199 for ((from_instance_path, from_var_path), (to_instance_path, to_var_path)) in ignored_loops {
200 let from = create_absolute_addr(
201 from_instance_path,
202 from_var_path,
203 instance_modules,
204 modules,
205 expanded,
206 );
207 let to = create_absolute_addr(
208 to_instance_path,
209 to_var_path,
210 instance_modules,
211 modules,
212 expanded,
213 );
214 res.insert((from, to));
215 }
216 res
217}
218fn parse_true_loops(
219 true_loops: &[(
220 (Vec<(String, usize)>, Vec<String>),
221 (Vec<(String, usize)>, Vec<String>),
222 usize,
223 )],
224 instance_modules: &HashMap<InstanceId, ModuleId>,
225 modules: &HashMap<ModuleId, SimModule>,
226 expanded: &HashMap<InstancePath, InstanceId>,
227) -> HashMap<(AbsoluteAddr, AbsoluteAddr), usize> {
228 let mut res = HashMap::default();
229
230 for ((from_instance_path, from_var_path), (to_instance_path, to_var_path), max_iter) in
231 true_loops
232 {
233 let from = create_absolute_addr(
234 from_instance_path,
235 from_var_path,
236 instance_modules,
237 modules,
238 expanded,
239 );
240 let to = create_absolute_addr(
241 to_instance_path,
242 to_var_path,
243 instance_modules,
244 modules,
245 expanded,
246 );
247 res.insert((from, to), *max_iter);
248 }
249 res
250}
251
252fn scheduler_source_locations(
253 error: &SchedulerError<AbsoluteAddr>,
254 modules: &HashMap<ModuleId, SimModule>,
255 instance_modules: &HashMap<InstanceId, ModuleId>,
256) -> Vec<SourceLocation> {
257 let blocks = match error {
258 SchedulerError::CombinationalLoop { blocks } => blocks,
259 SchedulerError::MultipleDriver { blocks } => blocks,
260 SchedulerError::InvalidDependencyGraph => return Vec::new(),
261 };
262 let mut seen = HashSet::default();
263 blocks
264 .iter()
265 .filter_map(|block| {
266 let addr = block.target.var()?.id;
267 if !seen.insert(addr) {
268 return None;
269 }
270 let module_id = instance_modules.get(&addr.instance_id)?;
271 let module = modules.get(module_id)?;
272 let var = module.variables.get(&addr.var_id)?;
273 var.source.clone()
274 })
275 .collect()
276}
277
278pub fn schedule_symbolic_rtl(
279 symbolic: SymbolicRtl,
280 fused_ff_factory: Option<&dyn FusedFfLoweringFactory>,
281 ignored_loops: &[(
282 (Vec<(String, usize)>, Vec<String>),
283 (Vec<(String, usize)>, Vec<String>),
284 )],
285 true_loops: &[(
286 (Vec<(String, usize)>, Vec<String>),
287 (Vec<(String, usize)>, Vec<String>),
288 usize,
289 )],
290 four_state: bool,
291 trace_opts: &FrontendTraceOptions,
292 mut trace: Option<&mut FrontendTrace>,
293) -> Result<ScheduledRtlOutput, ParserError> {
294 let SymbolicRtl {
295 modules,
296 module_names,
297 root_id,
298 } = symbolic;
299 let flatten_timing = trace_opts.phase_timing;
300 macro_rules! timed_sub {
301 ($label:expr, $body:expr) => {{
302 if flatten_timing {
303 let start = std::time::Instant::now();
304 let result = $body;
305 tracing::debug!("[flatten] {}: {:?}", $label, start.elapsed());
306 result
307 } else {
308 $body
309 }
310 }};
311 }
312
313 if let Some(t) = trace.as_deref_mut()
314 && trace_opts.sim_modules
315 {
316 t.sim_modules = Some(modules.clone());
317 }
318
319 let (expanded, instance_modules, indexed_instances) =
320 timed_sub!("expand_hierarchy", expand_hierarchy(&root_id, &modules));
321 let global_boundaries = timed_sub!(
322 "propagate_boundaries",
323 propagate_boundaries(&expanded, &instance_modules, &modules)
324 );
325 let unpacked_element_widths = instance_modules
326 .iter()
327 .flat_map(|(&instance_id, &module_id)| {
328 modules[&module_id]
329 .variables
330 .iter()
331 .filter_map(move |(&var_id, variable)| {
332 let element_count = variable
333 .metadata
334 .array_dims
335 .iter()
336 .try_fold(1usize, |total, &dim| total.checked_mul(dim))?;
337 let element_width = variable.metadata.width.checked_div(element_count)?;
338 (element_count > 1 && element_width > 0).then_some((
339 AbsoluteAddr {
340 instance_id,
341 var_id,
342 },
343 element_width,
344 ))
345 })
346 })
347 .collect::<HashMap<_, _>>();
348
349 let clock_domains = timed_sub!(
350 "unify_clock_domains",
351 unify_clock_domains(&expanded, &instance_modules, &modules)
352 );
353 let (
354 mut global_arena,
355 mut eval_apply_ffs,
356 mut eval_only_ffs,
357 mut apply_ffs,
358 _ff_access_summaries,
359 ff_runtime_relocations,
360 mut comb_blocks,
361 mut comb_observers,
362 mut runtime_errors,
363 runtime_event_sites,
364 next_runtime_error_code,
365 ) = timed_sub!(
366 "relocate_units",
367 relocate_units(
368 &expanded,
369 &instance_modules,
370 &modules,
371 &module_names[&root_id],
372 &indexed_instances,
373 &global_boundaries,
374 &unpacked_element_widths,
375 &clock_domains,
376 trace_opts,
377 &mut trace,
378 )
379 )?;
380 let ignored_loops = parse_ignored_loops(ignored_loops, &instance_modules, &modules, &expanded);
381 let true_loops = parse_true_loops(true_loops, &instance_modules, &modules, &expanded);
382
383 let mut reset_clock_map: HashMap<AbsoluteAddr, AbsoluteAddr> = HashMap::default();
385 for id in expanded.values() {
386 let module_id = &instance_modules[id];
387 let sim_module = &modules[module_id];
388 for (reset_var_id, clock_var_id) in &sim_module.reset_clock_map {
389 let reset_addr = AbsoluteAddr {
390 instance_id: *id,
391 var_id: *reset_var_id,
392 };
393 let clock_addr = AbsoluteAddr {
394 instance_id: *id,
395 var_id: *clock_var_id,
396 };
397 let canonical_clock = clock_domains
399 .get(&clock_addr)
400 .copied()
401 .unwrap_or(clock_addr);
402 let canonical_reset = clock_domains
403 .get(&reset_addr)
404 .copied()
405 .unwrap_or(reset_addr);
406 reset_clock_map.insert(canonical_reset, canonical_clock);
407 }
408 }
409
410 let (topological_clocks, cascaded_clocks) = timed_sub!(
411 "analyze_clock_dependencies",
412 analyze_clock_dependencies(
413 &mut eval_apply_ffs,
414 &mut eval_only_ffs,
415 &mut apply_ffs,
416 &comb_blocks,
417 &global_arena,
418 &clock_domains,
419 &expanded,
420 &instance_modules,
421 &modules,
422 )
423 );
424
425 if let Some(t) = trace.as_deref_mut()
426 && trace_opts.flattened_comb_blocks
427 {
428 t.flattened_comb_blocks = Some((comb_blocks.clone(), global_arena.clone()));
429 }
430
431 celox_slt::const_inline::inline_constant_variables(&mut comb_blocks, &mut global_arena)?;
436 apply_always_comb_previous_source_ordering(&mut comb_blocks);
437
438 let var_widths: HashMap<AbsoluteAddr, usize> = instance_modules
439 .iter()
440 .flat_map(|(&inst_id, &mod_id)| {
441 modules[&mod_id].variables.iter().map(move |(var_id, var)| {
442 (
443 AbsoluteAddr {
444 instance_id: inst_id,
445 var_id: *var_id,
446 },
447 var.metadata.width,
448 )
449 })
450 })
451 .collect();
452 let var_signedness: HashMap<AbsoluteAddr, bool> = instance_modules
453 .iter()
454 .flat_map(|(&inst_id, &mod_id)| {
455 modules[&mod_id].variables.iter().map(move |(var_id, var)| {
456 (
457 AbsoluteAddr {
458 instance_id: inst_id,
459 var_id: *var_id,
460 },
461 var.signed,
462 )
463 })
464 })
465 .collect();
466
467 build_comb_observer_capture_paths(
468 &mut comb_blocks,
469 &mut comb_observers,
470 &runtime_event_sites,
471 &mut global_arena,
472 )?;
473 for (site_id, site) in runtime_event_sites.iter().enumerate() {
474 if !matches!(site.kind, RuntimeEventKind::AssertFatal) {
475 continue;
476 }
477 runtime_errors
478 .entry(site_id as i64)
479 .or_insert_with(|| RuntimeErrorInfo {
480 message: site
481 .template
482 .clone()
483 .unwrap_or_else(|| "assertion failed".to_string()),
484 signals: Vec::new(),
485 });
486 }
487
488 celox_slt::verify_symbolic_roots(
489 &global_arena,
490 &comb_blocks,
491 &comb_observers,
492 &var_widths,
493 &var_signedness,
494 )
495 .map_err(|error| ParserError::SltVerify {
496 phase: "after flattening symbolic logic",
497 error,
498 })?;
499
500 let fused_inputs = if fused_ff_factory.is_some() {
501 let actions = build_fused_ff_actions(
502 &modules,
503 &instance_modules,
504 &clock_domains,
505 &ff_runtime_relocations,
506 );
507 let mut clock_arena = SLTNodeArena::<RegionedAbsoluteAddr>::new();
508 let mut clock_node_cache = HashMap::default();
509 let clock_comb_blocks = comb_blocks
510 .iter()
511 .map(|path| {
512 path.map_addr(
513 &global_arena,
514 &mut clock_arena,
515 &mut clock_node_cache,
516 &|addr| RegionedAbsoluteAddr::from_absolute_addr(STABLE_REGION, *addr),
517 )
518 })
519 .collect::<Result<Vec<_>, _>>()?;
520 let clock_var_widths = var_widths
521 .iter()
522 .map(|(&addr, &width)| {
523 (
524 RegionedAbsoluteAddr::from_absolute_addr(STABLE_REGION, addr),
525 width,
526 )
527 })
528 .collect::<HashMap<_, _>>();
529 let clock_unpacked_element_widths = unpacked_element_widths
530 .iter()
531 .map(|(&addr, &element_width)| {
532 (
533 RegionedAbsoluteAddr::from_absolute_addr(STABLE_REGION, addr),
534 element_width,
535 )
536 })
537 .collect::<HashMap<_, _>>();
538 let clock_ignored_loops = ignored_loops
539 .iter()
540 .map(|&(from, to)| {
541 (
542 RegionedAbsoluteAddr::from_absolute_addr(STABLE_REGION, from),
543 RegionedAbsoluteAddr::from_absolute_addr(STABLE_REGION, to),
544 )
545 })
546 .collect::<HashSet<_>>();
547 let clock_true_loops = true_loops
548 .iter()
549 .map(|(&(from, to), &limit)| {
550 (
551 (
552 RegionedAbsoluteAddr::from_absolute_addr(STABLE_REGION, from),
553 RegionedAbsoluteAddr::from_absolute_addr(STABLE_REGION, to),
554 ),
555 limit,
556 )
557 })
558 .collect::<HashMap<_, _>>();
559 Some((
560 actions,
561 clock_arena,
562 clock_comb_blocks,
563 clock_var_widths,
564 clock_unpacked_element_widths,
565 clock_ignored_loops,
566 clock_true_loops,
567 ))
568 } else {
569 None
570 };
571
572 let sched_start = flatten_timing.then(std::time::Instant::now);
573 let schedule = match scheduler::sort_with_unpacked_element_widths(
574 comb_blocks,
575 &global_arena,
576 &ignored_loops,
577 &true_loops,
578 four_state,
579 &var_widths,
580 &unpacked_element_widths,
581 next_runtime_error_code,
582 ) {
583 Ok(schedule) => schedule,
584 Err(error) => {
585 let (err_vars, err_path_idx) = module_variables(&modules);
586 let frontend_lookup = FrontendLookup {
587 instance_ids: expanded.clone(),
588 instance_module: instance_modules.clone(),
589 indexed_instances: indexed_instances.clone(),
590 module_variables: err_vars,
591 module_var_path_index: err_path_idx,
592 module_names: module_names.clone(),
593 source_to_state: HashMap::default(),
594 state_to_source: HashMap::default(),
595 event_aliases: HashMap::default(),
596 };
597 let source_locations = scheduler_source_locations(&error, &modules, &instance_modules);
598 let mut target_arena = SLTNodeArena::new();
599 let error = error.map_addr(&global_arena, &mut target_arena, &|addr| {
600 frontend_lookup.get_path(addr)
601 })?;
602 return Err(if source_locations.is_empty() {
603 ParserError::Scheduler(error)
604 } else {
605 ParserError::SchedulerWithLocation {
606 error,
607 source_locations,
608 }
609 });
610 }
611 };
612 if let Some(s) = sched_start {
613 tracing::debug!("[flatten] scheduler::sort: {:?}", s.elapsed());
614 }
615 runtime_errors.extend(schedule.runtime_errors);
616 let schduled: Vec<ExecutionUnit<RegionedAbsoluteAddr>> = schedule
617 .execution_units
618 .into_iter()
619 .map(|eu| ExecutionUnit {
620 entry_block_id: eu.entry_block_id,
621 blocks: eu
622 .blocks
623 .into_iter()
624 .map(|(id, bb)| {
625 (
626 id,
627 BasicBlock {
628 id: bb.id,
629 params: bb.params,
630 instructions: bb
631 .instructions
632 .into_iter()
633 .map(|inst| {
634 inst.into_map_addr(|addr| RegionedAbsoluteAddr {
635 region: STABLE_REGION,
636 instance_id: addr.instance_id,
637 var_id: addr.var_id,
638 })
639 })
640 .collect(),
641 terminator: bb.terminator,
642 },
643 )
644 })
645 .collect(),
646 register_map: eu.register_map,
647 })
648 .collect();
649 let eval_comb = schduled.clone();
650 let mut eval_comb_apply_ffs = HashMap::default();
651 let mut fused_direct_ff_writes = HashMap::default();
652 if let (
653 Some(factory),
654 Some((
655 actions,
656 clock_arena,
657 clock_comb_blocks,
658 clock_var_widths,
659 clock_unpacked_element_widths,
660 clock_ignored_loops,
661 clock_true_loops,
662 )),
663 ) = (fused_ff_factory, fused_inputs)
664 {
665 let mut fused_schedule_cache = HashMap::<
666 Vec<usize>,
667 (
668 Vec<ExecutionUnit<RegionedAbsoluteAddr>>,
669 Vec<VarAtomBase<RegionedAbsoluteAddr>>,
670 ),
671 >::default();
672 for (trigger, actions) in actions {
673 let action_ids = actions.iter().map(|action| action.id).collect::<Vec<_>>();
674 if let Some((units, direct_ff_writes)) = fused_schedule_cache.get(&action_ids) {
675 eval_comb_apply_ffs.insert(trigger, units.clone());
676 fused_direct_ff_writes.insert(trigger, direct_ff_writes.clone());
677 continue;
678 }
679 let mut ff_lowering = factory.create(actions)?;
680 let fused_start = flatten_timing.then(std::time::Instant::now);
681 let fused = match scheduler::sort_clock(
682 clock_comb_blocks.clone(),
683 &clock_arena,
684 &clock_ignored_loops,
685 &clock_true_loops,
686 four_state,
687 &clock_var_widths,
688 &clock_unpacked_element_widths,
689 next_runtime_error_code,
690 ff_lowering.as_mut(),
691 ) {
692 Ok(schedule) => schedule,
693 Err(scheduler::ClockSortError::Lowering(error)) => return Err(error),
694 Err(scheduler::ClockSortError::Scheduler(error)) => {
695 let mut error_arena = SLTNodeArena::new();
696 let error =
697 error.map_addr(&clock_arena, &mut error_arena, &|addr| addr.to_string())?;
698 return Err(ParserError::Scheduler(error));
699 }
700 };
701 if let Some(start) = fused_start {
702 tracing::debug!("[flatten] scheduler::sort_clock: {:?}", start.elapsed());
703 }
704 let direct_ff_writes = fused.direct_ff_writes;
705 let units = fused.execution_units;
706 fused_schedule_cache.insert(action_ids, (units.clone(), direct_ff_writes.clone()));
707 eval_comb_apply_ffs.insert(trigger, units);
708 fused_direct_ff_writes.insert(trigger, direct_ff_writes);
709 }
710 }
711
712 if let Some(t) = trace
713 && trace_opts.scheduled_units
714 {
715 t.scheduled_units = Some(schduled.clone());
716 }
717
718 let active_ff_domains = eval_apply_ffs
722 .values()
723 .filter(|units| !units.is_empty())
724 .count();
725 let needs_split_path = active_ff_domains > 1 || !cascaded_clocks.is_empty();
726 let (eval_only_ffs, apply_ffs) = if needs_split_path {
727 (eval_only_ffs, apply_ffs)
728 } else {
729 (HashMap::default(), HashMap::default())
730 };
731
732 let (mod_vars, mod_path_idx) = module_variables(&modules);
733 let initial_memory_values: Vec<InitialStateValue<AbsoluteAddr>> = instance_modules
734 .iter()
735 .flat_map(|(&instance_id, module_id)| {
736 modules[module_id]
737 .initial_memory_values
738 .iter()
739 .map(move |init| InitialStateValue {
740 address: AbsoluteAddr {
741 instance_id,
742 var_id: init.address,
743 },
744 data: init.data.clone(),
745 })
746 })
747 .collect();
748 let state_objects: HashMap<AbsoluteAddr, VariableMetadata> = instance_modules
749 .iter()
750 .flat_map(|(&instance_id, module_id)| {
751 modules[module_id]
752 .variables
753 .iter()
754 .map(move |(&var_id, variable)| {
755 (
756 AbsoluteAddr {
757 instance_id,
758 var_id,
759 },
760 variable.metadata.clone(),
761 )
762 })
763 })
764 .collect();
765 let runtime_comb_observers: Vec<RuntimeCombObserver<AbsoluteAddr>> = comb_observers
766 .iter()
767 .map(|observer| RuntimeCombObserver {
768 site_id: observer.site_id,
769 activation_group: observer.activation_group,
770 sensitivity: observer.sensitivity.clone(),
771 written_inputs: observer.written_inputs.clone(),
772 })
773 .collect();
774 let source_sir = SirProgram {
775 eval_apply_ffs,
776 eval_comb_apply_ffs,
777 eval_only_ffs,
778 apply_ffs,
779 eval_comb,
780 };
781 let mut source_addresses = state_objects.keys().copied().collect::<Vec<_>>();
782 source_addresses.sort_unstable();
783 let mut source_to_state = HashMap::default();
784 for (index, source) in source_addresses.into_iter().enumerate() {
785 let object = StateObjectId(u32::try_from(index).map_err(|_| {
786 ParserError::illegal_context(
787 "design state projection",
788 "flattened state object count exceeds u32",
789 None,
790 )
791 })?);
792 let state = StateAddr {
793 instance_id: source.instance_id,
794 var_id: object,
795 };
796 source_to_state.insert(source, state);
797 }
798 let project = |source: AbsoluteAddr| source_to_state[&source];
799 let project_regioned = |source: RegionedAbsoluteAddr| RegionedStateAddr {
800 region: source.region,
801 instance_id: source.instance_id,
802 var_id: source_to_state[&source.absolute_addr()].var_id,
803 };
804
805 let sir = source_sir.into_map_addr(project, project_regioned);
806 let state_objects: HashMap<StateAddr, VariableMetadata> = state_objects
807 .into_iter()
808 .map(|(address, metadata)| (project(address), metadata))
809 .collect();
810 let initial_state = initial_memory_values
811 .into_iter()
812 .map(|initial| InitialStateValue {
813 address: project(initial.address),
814 data: initial.data,
815 })
816 .collect();
817 let events = EventTopology {
818 aliases: clock_domains
819 .into_iter()
820 .map(|(alias, canonical)| (project(alias), project(canonical)))
821 .collect(),
822 ordered_events: topological_clocks.into_iter().map(project).collect(),
823 cascaded_events: cascaded_clocks.into_iter().map(project).collect(),
824 reset_clocks: reset_clock_map
825 .into_iter()
826 .map(|(reset, clock)| (project(reset), project(clock)))
827 .collect(),
828 };
829 let event_aliases = events.aliases.clone();
830 let runtime_errors = runtime_errors
831 .into_iter()
832 .map(|(code, info)| {
833 (
834 code,
835 RuntimeErrorInfo {
836 message: info.message,
837 signals: info.signals.into_iter().map(project).collect(),
838 },
839 )
840 })
841 .collect();
842 let comb_observers = runtime_comb_observers
843 .into_iter()
844 .map(|observer| RuntimeCombObserver {
845 site_id: observer.site_id,
846 activation_group: observer.activation_group,
847 sensitivity: observer
848 .sensitivity
849 .into_iter()
850 .map(|atom| VarAtomBase {
851 id: project(atom.id),
852 access: atom.access,
853 })
854 .collect(),
855 written_inputs: observer.written_inputs.into_iter().map(project).collect(),
856 })
857 .collect();
858 let direct_ff_writes = fused_direct_ff_writes
859 .into_iter()
860 .map(|(source, writes)| {
861 (
862 project(source),
863 writes
864 .into_iter()
865 .map(|write| VarAtomBase {
866 id: project_regioned(write.id),
867 access: write.access,
868 })
869 .collect(),
870 )
871 })
872 .collect();
873
874 let mut rtl_writes = HashSet::default();
875 for unit in sir
876 .eval_comb
877 .iter()
878 .chain(sir.eval_apply_ffs.values().flatten())
879 .chain(sir.eval_comb_apply_ffs.values().flatten())
880 .chain(sir.eval_only_ffs.values().flatten())
881 .chain(sir.apply_ffs.values().flatten())
882 {
883 for block in unit.blocks.values() {
884 for instruction in &block.instructions {
885 let (address, offset, width) = match instruction {
886 SIRInstruction::Store(address, offset, width, ..)
887 | SIRInstruction::Commit(_, address, offset, width, _) => {
888 (address.absolute_addr(), offset, *width)
889 }
890 _ => continue,
891 };
892 let access = offset
893 .constant_bit_offset()
894 .and_then(|lsb| {
895 width
896 .checked_sub(1)
897 .and_then(|tail| lsb.checked_add(tail))
898 .map(|msb| BitAccess::new(lsb, msb))
899 })
900 .or_else(|| {
901 state_objects
902 .get(&address)
903 .and_then(|object| object.width.checked_sub(1))
904 .map(|msb| BitAccess::new(0, msb))
905 });
906 if let Some(access) = access {
907 rtl_writes.insert(VarAtomBase {
908 id: address,
909 access,
910 });
911 }
912 }
913 }
914 }
915
916 let state_to_source = source_to_state
917 .iter()
918 .map(|(source, state)| (*state, *source))
919 .collect();
920
921 let scheduled = ScheduledRtl {
922 sir,
923 design: ElaboratedDesign {
924 state_objects,
925 events,
926 initial_state,
927 },
928 frontend_lookup: FrontendLookup {
929 instance_ids: expanded,
930 instance_module: instance_modules,
931 indexed_instances,
932 module_variables: mod_vars,
933 module_var_path_index: mod_path_idx,
934 module_names,
935 source_to_state,
936 state_to_source,
937 event_aliases,
938 },
939 runtime_schema: RuntimeSchema {
940 runtime_errors,
941 runtime_event_sites,
942 comb_observers,
943 testbench_read_roots: Default::default(),
944 rtl_writes,
945 },
946 };
947
948 Ok(ScheduledRtlOutput {
949 scheduled,
950 fused_optimization_hints: FusedSirOptimizationHints { direct_ff_writes },
951 })
952}
953
954fn module_variables(
955 modules: &HashMap<ModuleId, SimModule>,
956) -> (
957 HashMap<ModuleId, HashMap<SourceVarId, VariableInfo>>,
958 HashMap<ModuleId, HashMap<Vec<String>, Option<SourceVarId>>>,
959) {
960 let mut res = HashMap::default();
961 let mut path_index = HashMap::default();
962 for (id, module) in modules {
963 let mut variables = HashMap::default();
964 let mut paths: HashMap<Vec<String>, Option<SourceVarId>> = HashMap::default();
965 for (&source_id, variable) in &module.variables {
966 if variable.module_affiliated {
970 match paths.entry(variable.path.clone()) {
971 std::collections::hash_map::Entry::Vacant(e) => {
972 e.insert(Some(source_id));
973 }
974 std::collections::hash_map::Entry::Occupied(mut e) => {
975 e.insert(None);
977 }
978 }
979 }
980 variables.insert(
981 source_id,
982 VariableInfo {
983 id: source_id,
984 path: variable.path.clone(),
985 var_kind: variable.kind,
986 signed: variable.signed,
987 packed_dims: variable.packed_dims.clone(),
988 metadata: variable.metadata.clone(),
989 },
990 );
991 }
992 res.insert(*id, variables);
993 path_index.insert(*id, paths);
994 }
995 (res, path_index)
996}
997
998fn expand_hierarchy(
999 top: &ModuleId,
1000 modules: &HashMap<ModuleId, SimModule>,
1001) -> (
1002 HashMap<InstancePath, InstanceId>,
1003 HashMap<InstanceId, ModuleId>,
1004 HashSet<InstanceId>,
1005) {
1006 let mut expanded = HashMap::default();
1007 let mut instance_modules = HashMap::default();
1008 let mut indexed_instances = HashSet::default();
1009 let mut instance_id = 0;
1010 let path = vec![];
1011 let id = InstanceId(instance_id);
1012 instance_modules.insert(id, *top);
1013 expanded.insert(InstancePath(path.clone()), id);
1014 instance_id += 1;
1015 expand(
1016 top,
1017 path,
1018 modules,
1019 &mut expanded,
1020 &mut instance_modules,
1021 &mut indexed_instances,
1022 &mut instance_id,
1023 );
1024 (expanded, instance_modules, indexed_instances)
1025}
1026
1027fn extend_boundaries(
1028 boundaries: &mut HashMap<AbsoluteAddr, BTreeSet<usize>>,
1029 source: AbsoluteAddr,
1030 target: AbsoluteAddr,
1031) -> bool {
1032 if source == target || boundaries.get(&source).is_none_or(BTreeSet::is_empty) {
1033 return false;
1034 }
1035 boundaries.entry(target).or_default();
1036 let [Some(source), Some(target)] = boundaries.get_disjoint_mut([&source, &target]) else {
1037 unreachable!("distinct boundary keys were inserted before lookup");
1038 };
1039 let old_len = target.len();
1040 target.extend(source.iter().copied());
1041 target.len() != old_len
1042}
1043
1044fn propagate_boundaries(
1045 expanded: &HashMap<InstancePath, InstanceId>,
1046 instance_modules: &HashMap<InstanceId, ModuleId>,
1047 modules: &HashMap<ModuleId, SimModule>,
1048) -> HashMap<AbsoluteAddr, BTreeSet<usize>> {
1049 let mut current_boundaries = HashMap::default();
1050
1051 for id in expanded.values() {
1053 let module_id = &instance_modules[id];
1054 let sim_module = &modules[module_id];
1055 for (var_id, boundaries) in &sim_module.comb_boundaries {
1056 let addr = AbsoluteAddr {
1057 instance_id: *id,
1058 var_id: *var_id,
1059 };
1060 current_boundaries.insert(addr, boundaries.clone());
1061 }
1062 }
1063
1064 let mut changed = true;
1066 while changed {
1067 changed = false;
1068 for (path, id) in expanded {
1069 let module_id = &instance_modules[id];
1070 let sim_module = &modules[module_id];
1071
1072 for (inst_name, glue_blocks) in &sim_module.glue_blocks {
1073 for (idx, glue_block) in glue_blocks.iter().enumerate() {
1074 let mut child_path = path.0.clone();
1075 child_path.push((inst_name.clone(), idx));
1076 let child_id = expanded[&InstancePath(child_path)];
1077
1078 for (parent_vars, child_addr) in &glue_block.input_ports {
1080 if let Some(target) = child_addr.target.var()
1081 && let GlueAddr::Child(child_var_id) = target.id
1082 {
1083 let child_abs = AbsoluteAddr {
1084 instance_id: child_id,
1085 var_id: child_var_id,
1086 };
1087
1088 for parent_var in parent_vars {
1090 let parent_abs = AbsoluteAddr {
1091 instance_id: *id,
1092 var_id: *parent_var,
1093 };
1094 changed |= extend_boundaries(
1095 &mut current_boundaries,
1096 parent_abs,
1097 child_abs,
1098 );
1099 }
1100 }
1101 }
1102
1103 for (parent_vars, logic_path) in &glue_block.output_ports {
1105 for source in &logic_path.sources {
1107 if let GlueAddr::Child(child_var_id) = source.id {
1108 let child_abs = AbsoluteAddr {
1109 instance_id: child_id,
1110 var_id: child_var_id,
1111 };
1112
1113 for parent_var in parent_vars {
1115 let parent_abs = AbsoluteAddr {
1116 instance_id: *id,
1117 var_id: *parent_var,
1118 };
1119 changed |= extend_boundaries(
1120 &mut current_boundaries,
1121 child_abs,
1122 parent_abs,
1123 );
1124 }
1125
1126 for parent_var in parent_vars {
1130 let parent_abs = AbsoluteAddr {
1131 instance_id: *id,
1132 var_id: *parent_var,
1133 };
1134 changed |= extend_boundaries(
1135 &mut current_boundaries,
1136 parent_abs,
1137 child_abs,
1138 );
1139 }
1140 }
1141 }
1142 }
1143 }
1144 }
1145 }
1146 }
1147 current_boundaries
1148}
1149
1150fn expand(
1151 target: &ModuleId,
1152 path: Vec<(String, usize)>,
1153 modules: &HashMap<ModuleId, SimModule>,
1154 expanded: &mut HashMap<InstancePath, InstanceId>,
1155 instance_modules: &mut HashMap<InstanceId, ModuleId>,
1156 indexed_instances: &mut HashSet<InstanceId>,
1157 instance_id: &mut usize,
1158) {
1159 let module = &modules[target];
1160 for (inst_name, gbs) in &module.glue_blocks {
1161 let indexed = module.indexed_instance_names.contains(inst_name) || gbs.len() > 1;
1165 for (idx, gb) in gbs.iter().enumerate() {
1166 let mut path = path.clone();
1167 path.push((inst_name.clone(), idx));
1168 let id = InstanceId(*instance_id);
1169 expanded.insert(InstancePath(path.clone()), id);
1170 instance_modules.insert(id, gb.module_id);
1171 if indexed {
1172 indexed_instances.insert(id);
1173 }
1174 *instance_id += 1;
1175 expand(
1176 &gb.module_id,
1177 path,
1178 modules,
1179 expanded,
1180 instance_modules,
1181 indexed_instances,
1182 instance_id,
1183 );
1184 }
1185 }
1186}
1187
1188fn relocate_executation_unit_with_errors<A, B>(
1189 eu: &ExecutionUnit<A>,
1190 f: &impl Fn(&A) -> B,
1191 runtime_error_codes: &HashMap<i64, i64>,
1192 runtime_event_sites: &HashMap<u32, u32>,
1193) -> ExecutionUnit<B> {
1194 ExecutionUnit {
1195 entry_block_id: eu.entry_block_id,
1196 blocks: eu
1197 .blocks
1198 .iter()
1199 .map(|(id, block)| {
1200 (
1201 *id,
1202 BasicBlock {
1203 id: block.id,
1204 instructions: block
1205 .instructions
1206 .iter()
1207 .map(|inst| match inst {
1208 SIRInstruction::RuntimeEvent { site_id, args } => {
1209 SIRInstruction::RuntimeEvent {
1210 site_id: runtime_event_sites
1211 .get(site_id)
1212 .copied()
1213 .unwrap_or(*site_id),
1214 args: args.clone(),
1215 }
1216 }
1217 SIRInstruction::CombCaptureEvent {
1218 site_id,
1219 args,
1220 fatal_error_code,
1221 consume_enabled,
1222 } => SIRInstruction::CombCaptureEvent {
1223 site_id: runtime_event_sites
1224 .get(site_id)
1225 .copied()
1226 .unwrap_or(*site_id),
1227 args: args.clone(),
1228 fatal_error_code: *fatal_error_code,
1229 consume_enabled: *consume_enabled,
1230 },
1231 _ => inst.map_addr(f),
1232 })
1233 .collect(),
1234 params: block.params.clone(),
1235 terminator: match block.terminator {
1236 SIRTerminator::Error(code) => SIRTerminator::Error(
1237 runtime_error_codes.get(&code).copied().unwrap_or(code),
1238 ),
1239 ref terminator => terminator.clone(),
1240 },
1241 },
1242 )
1243 })
1244 .collect(),
1245 register_map: eu.register_map.clone(),
1246 }
1247}
1248
1249fn unify_clock_domains(
1250 expanded: &HashMap<InstancePath, InstanceId>,
1251 instance_modules: &HashMap<InstanceId, ModuleId>,
1252 modules: &HashMap<ModuleId, SimModule>,
1253) -> HashMap<AbsoluteAddr, AbsoluteAddr> {
1254 let mut drive_graph: HashMap<AbsoluteAddr, Vec<AbsoluteAddr>> = HashMap::default();
1255
1256 for (path, id) in expanded {
1257 let module_id = &instance_modules[id];
1258 let sim_module = &modules[module_id];
1259
1260 for logic_path in &sim_module.comb_blocks {
1262 if logic_path.sources.len() == 1 {
1264 let expr_node = sim_module.arena.get(logic_path.expr);
1265 let is_alias = matches!(
1266 expr_node,
1267 celox_slt::SLTNode::Input { .. } | celox_slt::SLTNode::Slice { .. }
1268 );
1269 if is_alias {
1270 let Some(target) = logic_path.target.var() else {
1271 continue;
1272 };
1273 let target_abs = AbsoluteAddr {
1274 instance_id: *id,
1275 var_id: target.id,
1276 };
1277 let source_abs = AbsoluteAddr {
1278 instance_id: *id,
1279 var_id: logic_path.sources.iter().next().unwrap().id,
1280 };
1281 drive_graph.entry(source_abs).or_default().push(target_abs);
1282 }
1283 }
1284 }
1285 for (inst_name, glue_blocks) in &sim_module.glue_blocks {
1286 for (idx, glue_block) in glue_blocks.iter().enumerate() {
1287 let mut child_path = path.0.clone();
1288 child_path.push((inst_name.clone(), idx));
1289 let child_id = expanded[&InstancePath(child_path)];
1290
1291 for (parent_vars, logic_path) in &glue_block.input_ports {
1293 if let Some(target) = logic_path.target.var()
1294 && let GlueAddr::Child(child_var_id) = target.id
1295 {
1296 let child_abs = AbsoluteAddr {
1297 instance_id: child_id,
1298 var_id: child_var_id,
1299 };
1300 for parent_var in parent_vars {
1301 let parent_abs = AbsoluteAddr {
1302 instance_id: *id,
1303 var_id: *parent_var,
1304 };
1305 drive_graph.entry(parent_abs).or_default().push(child_abs);
1306 }
1307 }
1308 }
1309 for (parent_vars, logic_path) in &glue_block.output_ports {
1311 for parent_var in parent_vars {
1312 let parent_abs = AbsoluteAddr {
1313 instance_id: *id,
1314 var_id: *parent_var,
1315 };
1316 for source in &logic_path.sources {
1317 if let GlueAddr::Child(child_var_id) = source.id {
1318 let child_abs = AbsoluteAddr {
1319 instance_id: child_id,
1320 var_id: child_var_id,
1321 };
1322 drive_graph.entry(child_abs).or_default().push(parent_abs);
1323 }
1324 }
1325 }
1326 }
1327 }
1328 }
1329 }
1330
1331 let mut clock_domains: HashMap<AbsoluteAddr, AbsoluteAddr> = HashMap::default();
1333
1334 let mut reverse_drive_graph: HashMap<AbsoluteAddr, Vec<AbsoluteAddr>> = HashMap::default();
1336 for (src, sinks) in &drive_graph {
1337 for sink in sinks {
1338 reverse_drive_graph.entry(*sink).or_default().push(*src);
1339 }
1340 }
1341
1342 let mut all_addrs = HashSet::default();
1344 for src in drive_graph.keys() {
1345 all_addrs.insert(*src);
1346 }
1347 for sinks in drive_graph.values() {
1348 for sink in sinks {
1349 all_addrs.insert(*sink);
1350 }
1351 }
1352
1353 for addr in all_addrs {
1355 let mut current = addr;
1356 let mut visited = HashSet::default();
1357 while let Some(sources) = reverse_drive_graph.get(¤t) {
1359 if sources.is_empty() {
1360 break;
1361 }
1362 let next = sources[0];
1365 if visited.contains(&next) {
1366 break; }
1368 visited.insert(next);
1369 current = next;
1370 }
1371 clock_domains.insert(addr, current);
1372 }
1373 clock_domains
1374}
1375
1376fn build_fused_ff_actions(
1377 modules: &HashMap<ModuleId, SimModule>,
1378 instance_modules: &HashMap<InstanceId, ModuleId>,
1379 clock_domains: &HashMap<AbsoluteAddr, AbsoluteAddr>,
1380 runtime_relocations: &HashMap<InstanceId, FfRuntimeRelocation>,
1381) -> HashMap<AbsoluteAddr, Vec<FusedFfAction>> {
1382 let mut instances = instance_modules.iter().collect::<Vec<_>>();
1383 instances.sort_unstable_by_key(|(instance, _)| instance.0);
1384 let mut result = HashMap::<AbsoluteAddr, Vec<FusedFfAction>>::default();
1385 let mut next_action_id = 0usize;
1386
1387 for (&instance_id, &module_id) in instances {
1388 let module = &modules[&module_id];
1389 let mut summaries = module.ff_access_summaries.iter().collect::<Vec<_>>();
1390 summaries.sort_unstable_by_key(|(trigger, _)| (*trigger).clone());
1391 for (trigger, summary) in summaries {
1392 let relocate = |address: RegionedVarAddr| RegionedAbsoluteAddr {
1393 region: address.region,
1394 instance_id,
1395 var_id: address.var_id,
1396 };
1397 let summary = FfAccessSummary {
1398 reads: summary
1399 .reads
1400 .iter()
1401 .map(|read| VarAtomBase {
1402 id: relocate(read.id),
1403 access: read.access,
1404 })
1405 .collect(),
1406 writes: summary
1407 .writes
1408 .iter()
1409 .map(|write| VarAtomBase {
1410 id: RegionedAbsoluteAddr {
1411 region: STABLE_REGION,
1412 instance_id,
1413 var_id: write.id.var_id,
1414 },
1415 access: write.access,
1416 })
1417 .collect(),
1418 dynamic_writes: summary
1419 .dynamic_writes
1420 .iter()
1421 .map(|address| RegionedAbsoluteAddr {
1422 region: STABLE_REGION,
1423 instance_id,
1424 var_id: address.var_id,
1425 })
1426 .collect(),
1427 };
1428 let action = FusedFfAction {
1429 id: next_action_id,
1430 instance_id,
1431 module_id,
1432 trigger: trigger.clone(),
1433 summary,
1434 runtime: runtime_relocations[&instance_id].clone(),
1435 };
1436 next_action_id += 1;
1437 let clock = AbsoluteAddr {
1438 instance_id,
1439 var_id: trigger.clock,
1440 };
1441 let clock = clock_domains.get(&clock).copied().unwrap_or(clock);
1442 result.entry(clock).or_default().push(action.clone());
1443 for &reset_id in &trigger.resets {
1444 let reset = AbsoluteAddr {
1445 instance_id,
1446 var_id: reset_id,
1447 };
1448 let reset = clock_domains.get(&reset).copied().unwrap_or(reset);
1449 result.entry(reset).or_default().push(action.clone());
1450 }
1451 }
1452 }
1453 result
1454}
1455
1456fn relocate_units(
1457 expanded: &HashMap<InstancePath, InstanceId>,
1458 instance_modules: &HashMap<InstanceId, ModuleId>,
1459 modules: &HashMap<ModuleId, SimModule>,
1460 root_name: &str,
1461 indexed_instances: &HashSet<InstanceId>,
1462 global_boundaries: &HashMap<AbsoluteAddr, std::collections::BTreeSet<usize>>,
1463 unpacked_element_widths: &HashMap<AbsoluteAddr, usize>,
1464 clock_domains: &HashMap<AbsoluteAddr, AbsoluteAddr>,
1465 trace_opts: &crate::FrontendTraceOptions,
1466 trace: &mut Option<&mut crate::FrontendTrace>,
1467) -> Result<
1468 (
1469 SLTNodeArena<AbsoluteAddr>,
1470 HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>>,
1471 HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>>,
1472 HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>>,
1473 HashMap<AbsoluteAddr, Vec<FfAccessSummary<RegionedAbsoluteAddr>>>,
1474 HashMap<InstanceId, FfRuntimeRelocation>,
1475 Vec<celox_slt::LogicPath<AbsoluteAddr>>,
1476 Vec<CombObserver<AbsoluteAddr>>,
1477 HashMap<i64, RuntimeErrorInfo<AbsoluteAddr>>,
1478 Vec<RuntimeEventSite>,
1479 i64,
1480 ),
1481 ParserError,
1482> {
1483 let mut global_arena = SLTNodeArena::<AbsoluteAddr>::new();
1484 let mut eval_apply_ffs: HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>> =
1485 HashMap::default();
1486 let mut eval_only_ffs: HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>> =
1487 HashMap::default();
1488 let mut apply_ffs: HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>> =
1489 HashMap::default();
1490 let mut ff_access_summaries: HashMap<AbsoluteAddr, Vec<FfAccessSummary<RegionedAbsoluteAddr>>> =
1491 HashMap::default();
1492 let mut ff_runtime_relocations = HashMap::default();
1493 let mut comb_blocks = Vec::new();
1494 let mut comb_observers = Vec::new();
1495 let mut runtime_errors = HashMap::default();
1496 let mut runtime_event_sites = Vec::new();
1497 let mut next_runtime_error_code = 2000;
1498
1499 for (path, id) in expanded {
1500 let module_id = &instance_modules[id];
1501 let sim_module = &modules[module_id];
1502 let runtime_event_site_base = u32::try_from(runtime_event_sites.len()).map_err(|_| {
1503 ParserError::illegal_context(
1504 "FF runtime-event relocation",
1505 "runtime event site count exceeds u32",
1506 None,
1507 )
1508 })?;
1509 let relocate_ff_summary = |summary: &FfAccessSummary<RegionedVarAddr>| {
1510 let relocate_addr = |addr: RegionedVarAddr| RegionedAbsoluteAddr {
1511 region: addr.region,
1512 instance_id: *id,
1513 var_id: addr.var_id,
1514 };
1515 FfAccessSummary {
1516 reads: summary
1517 .reads
1518 .iter()
1519 .map(|read| VarAtomBase {
1520 id: relocate_addr(read.id),
1521 access: read.access,
1522 })
1523 .collect(),
1524 writes: summary
1525 .writes
1526 .iter()
1527 .map(|write| VarAtomBase {
1528 id: relocate_addr(write.id),
1529 access: write.access,
1530 })
1531 .collect(),
1532 dynamic_writes: summary
1533 .dynamic_writes
1534 .iter()
1535 .copied()
1536 .map(relocate_addr)
1537 .collect(),
1538 }
1539 };
1540 for (trigger_set, summary) in &sim_module.ff_access_summaries {
1541 let clock_addr = AbsoluteAddr {
1542 instance_id: *id,
1543 var_id: trigger_set.clock,
1544 };
1545 let canonical_clock = clock_domains
1546 .get(&clock_addr)
1547 .copied()
1548 .unwrap_or(clock_addr);
1549 ff_access_summaries
1550 .entry(canonical_clock)
1551 .or_default()
1552 .push(relocate_ff_summary(summary));
1553 for &reset in &trigger_set.resets {
1554 let reset_addr = AbsoluteAddr {
1555 instance_id: *id,
1556 var_id: reset,
1557 };
1558 let canonical_reset = clock_domains
1559 .get(&reset_addr)
1560 .copied()
1561 .unwrap_or(reset_addr);
1562 ff_access_summaries
1563 .entry(canonical_reset)
1564 .or_default()
1565 .push(relocate_ff_summary(summary));
1566 }
1567 }
1568 let mut runtime_error_codes = HashMap::default();
1569 for (&local_code, info) in &sim_module.runtime_errors {
1570 let global_code = next_runtime_error_code;
1571 next_runtime_error_code += 1;
1572 runtime_error_codes.insert(local_code, global_code);
1573 runtime_errors.insert(
1574 global_code,
1575 RuntimeErrorInfo {
1576 message: info.message.clone(),
1577 signals: info
1578 .signals
1579 .iter()
1580 .filter(|var_id| sim_module.variables.contains_key(var_id))
1581 .map(|&var_id| AbsoluteAddr {
1582 instance_id: *id,
1583 var_id,
1584 })
1585 .collect(),
1586 },
1587 );
1588 }
1589 ff_runtime_relocations.insert(
1590 *id,
1591 FfRuntimeRelocation {
1592 error_codes: runtime_error_codes.clone(),
1593 event_site_base: runtime_event_site_base,
1594 },
1595 );
1596 let mut runtime_event_site_map = HashMap::default();
1597 let scope = elaborated_scope_name(root_name, path, expanded, indexed_instances);
1598 for (local_site, site) in sim_module.runtime_event_sites.iter().enumerate() {
1599 let global_site = runtime_event_sites.len() as u32;
1600 runtime_event_site_map.insert(local_site as u32, global_site);
1601 let mut site = site.clone();
1602 site.scope = Some(scope.clone());
1603 runtime_event_sites.push(site);
1604 }
1605
1606 let arena_start = global_arena.len();
1607 let mut relocated_module = flatten_with_trace(
1608 sim_module,
1609 path,
1610 expanded,
1611 global_boundaries,
1612 unpacked_element_widths,
1613 &mut global_arena,
1614 trace_opts,
1615 trace.as_deref_mut(),
1616 )?;
1617 remap_for_fold_runtime_event_sites(
1618 &mut global_arena,
1619 arena_start,
1620 &runtime_event_site_map,
1621 )?;
1622 for observer in &mut relocated_module.comb_observers {
1623 observer.site_id = runtime_event_site_map[&observer.site_id];
1624 observer.activation_group = runtime_event_site_map[&observer.activation_group];
1625 }
1626 comb_blocks.extend(relocated_module.comb_blocks);
1627 comb_observers.extend(relocated_module.comb_observers);
1628
1629 for (trigger_set, eu) in &sim_module.eval_apply_ff_blocks {
1631 let clock_addr = AbsoluteAddr {
1632 instance_id: *id,
1633 var_id: trigger_set.clock,
1634 };
1635 let canonical_addr = clock_domains
1636 .get(&clock_addr)
1637 .copied()
1638 .unwrap_or(clock_addr);
1639
1640 eval_apply_ffs.entry(canonical_addr).or_default().push(
1641 relocate_executation_unit_with_errors(
1642 eu,
1643 &|addr| RegionedAbsoluteAddr {
1644 region: addr.region,
1645 instance_id: *id,
1646 var_id: addr.var_id,
1647 },
1648 &runtime_error_codes,
1649 &runtime_event_site_map,
1650 ),
1651 );
1652
1653 for &reset in &trigger_set.resets {
1654 let reset_addr = AbsoluteAddr {
1655 instance_id: *id,
1656 var_id: reset,
1657 };
1658 let canonical_addr = clock_domains
1659 .get(&reset_addr)
1660 .copied()
1661 .unwrap_or(reset_addr);
1662 eval_apply_ffs.entry(canonical_addr).or_default().push(
1663 relocate_executation_unit_with_errors(
1664 eu,
1665 &|addr| RegionedAbsoluteAddr {
1666 region: addr.region,
1667 instance_id: *id,
1668 var_id: addr.var_id,
1669 },
1670 &runtime_error_codes,
1671 &runtime_event_site_map,
1672 ),
1673 );
1674 }
1675 }
1676
1677 for (trigger_set, eu) in &sim_module.eval_only_ff_blocks {
1678 let clock_addr = AbsoluteAddr {
1679 instance_id: *id,
1680 var_id: trigger_set.clock,
1681 };
1682 let canonical_addr = clock_domains
1683 .get(&clock_addr)
1684 .copied()
1685 .unwrap_or(clock_addr);
1686 eval_only_ffs.entry(canonical_addr).or_default().push(
1687 relocate_executation_unit_with_errors(
1688 eu,
1689 &|addr| RegionedAbsoluteAddr {
1690 region: addr.region,
1691 instance_id: *id,
1692 var_id: addr.var_id,
1693 },
1694 &runtime_error_codes,
1695 &runtime_event_site_map,
1696 ),
1697 );
1698
1699 for &reset in &trigger_set.resets {
1700 let reset_addr = AbsoluteAddr {
1701 instance_id: *id,
1702 var_id: reset,
1703 };
1704 let canonical_addr = clock_domains
1705 .get(&reset_addr)
1706 .copied()
1707 .unwrap_or(reset_addr);
1708 eval_only_ffs.entry(canonical_addr).or_default().push(
1709 relocate_executation_unit_with_errors(
1710 eu,
1711 &|addr| RegionedAbsoluteAddr {
1712 region: addr.region,
1713 instance_id: *id,
1714 var_id: addr.var_id,
1715 },
1716 &runtime_error_codes,
1717 &runtime_event_site_map,
1718 ),
1719 );
1720 }
1721 }
1722
1723 for (trigger_set, eu) in &sim_module.apply_ff_blocks {
1724 let clock_addr = AbsoluteAddr {
1725 instance_id: *id,
1726 var_id: trigger_set.clock,
1727 };
1728 let canonical_addr = clock_domains
1729 .get(&clock_addr)
1730 .copied()
1731 .unwrap_or(clock_addr);
1732 apply_ffs.entry(canonical_addr).or_default().push(
1733 relocate_executation_unit_with_errors(
1734 eu,
1735 &|addr| RegionedAbsoluteAddr {
1736 region: addr.region,
1737 instance_id: *id,
1738 var_id: addr.var_id,
1739 },
1740 &runtime_error_codes,
1741 &runtime_event_site_map,
1742 ),
1743 );
1744
1745 for &reset in &trigger_set.resets {
1746 let reset_addr = AbsoluteAddr {
1747 instance_id: *id,
1748 var_id: reset,
1749 };
1750 let canonical_addr = clock_domains
1751 .get(&reset_addr)
1752 .copied()
1753 .unwrap_or(reset_addr);
1754 apply_ffs.entry(canonical_addr).or_default().push(
1755 relocate_executation_unit_with_errors(
1756 eu,
1757 &|addr| RegionedAbsoluteAddr {
1758 region: addr.region,
1759 instance_id: *id,
1760 var_id: addr.var_id,
1761 },
1762 &runtime_error_codes,
1763 &runtime_event_site_map,
1764 ),
1765 );
1766 }
1767 }
1768 }
1769 Ok((
1770 global_arena,
1771 eval_apply_ffs,
1772 eval_only_ffs,
1773 apply_ffs,
1774 ff_access_summaries,
1775 ff_runtime_relocations,
1776 comb_blocks,
1777 comb_observers,
1778 runtime_errors,
1779 runtime_event_sites,
1780 next_runtime_error_code,
1781 ))
1782}
1783
1784fn build_comb_observer_capture_paths(
1785 comb_blocks: &mut Vec<LogicPath<AbsoluteAddr>>,
1786 observers: &mut [CombObserver<AbsoluteAddr>],
1787 sites: &[RuntimeEventSite],
1788 arena: &mut SLTNodeArena<AbsoluteAddr>,
1789) -> Result<(), ParserError> {
1790 if observers.is_empty() {
1791 return Ok(());
1792 }
1793
1794 annotate_comb_capture_enable_sites(comb_blocks, observers);
1795
1796 let mut group_members: HashMap<u32, Vec<usize>> = HashMap::default();
1797 for (idx, observer) in observers.iter().enumerate() {
1798 group_members
1799 .entry(observer.activation_group)
1800 .or_default()
1801 .push(idx);
1802 }
1803 let mut emitted_group_triggers = HashSet::default();
1804 let mut previous_primary_capture_path: Option<LogicPathId> = None;
1805 let mut previous_trigger_capture_path: Option<LogicPathId> = None;
1806 for observer_idx in 0..observers.len() {
1807 let observer = &observers[observer_idx];
1808 let has_statement_position_dependency =
1809 observer_has_statement_position_dependency(comb_blocks, observer);
1810 let order_before = observer_order_before(comb_blocks, observer);
1811 let order_after = observer_order_after(comb_blocks, observer);
1812 let trigger_paths = if has_statement_position_dependency {
1813 observer_trigger_paths(comb_blocks, observer)
1814 } else {
1815 Vec::new()
1816 };
1817 if observer.captured_in_loop {
1818 let Some(loop_runner) = observer.loop_runner else {
1819 continue;
1820 };
1821 let sources: HashSet<_> = observer
1822 .sensitivity
1823 .iter()
1824 .copied()
1825 .filter(|atom| !observer_written_input_overlaps(observer, atom))
1826 .filter(|atom| !observer_statement_position_overlaps(comb_blocks, observer, atom))
1827 .collect();
1828 let path_id = LogicPathId(comb_blocks.len());
1829 if let Some(prev) = previous_primary_capture_path {
1830 comb_blocks[prev.0].order_before.insert(path_id);
1831 }
1832 for idx in &order_after {
1833 comb_blocks[idx.0].order_before.insert(path_id);
1834 }
1835 comb_blocks.push(LogicPath {
1836 target: LogicPathTarget::CombCaptureEvent {
1837 site_id: observer.site_id,
1838 guard: None,
1839 emit_on_true: true,
1840 args: Vec::new(),
1841 loop_runner: Some(loop_runner),
1842 fatal_error_code: None,
1843 consume_enabled: true,
1844 },
1845 sources,
1846 previous_sources: HashSet::default(),
1847 address_sources: HashSet::default(),
1848 local_inputs: observer.local_inputs.clone(),
1849 order_before: order_before.clone(),
1850 comb_capture_enable_sites: Vec::new(),
1851 comb_capture_enable_always: false,
1852 pre_lower_nodes: Vec::new(),
1853 expr: loop_runner,
1854 });
1855 previous_primary_capture_path = Some(path_id);
1856 for trigger_idx in trigger_paths {
1857 let Some(trigger_target) = comb_blocks[trigger_idx.0].target.var().copied() else {
1858 continue;
1859 };
1860 let trigger_order_before =
1861 direct_consumers_of_path_target(comb_blocks, trigger_idx);
1862 let path_id = LogicPathId(comb_blocks.len());
1863 if let Some(prev) = previous_trigger_capture_path {
1864 comb_blocks[prev.0].order_before.insert(path_id);
1865 }
1866 comb_blocks[trigger_idx.0].order_before.insert(path_id);
1867 comb_blocks.push(LogicPath {
1868 target: LogicPathTarget::CombCaptureEvent {
1869 site_id: observer.site_id,
1870 guard: None,
1871 emit_on_true: true,
1872 args: Vec::new(),
1873 loop_runner: Some(loop_runner),
1874 fatal_error_code: None,
1875 consume_enabled: true,
1876 },
1877 sources: std::iter::once(trigger_target).collect(),
1878 previous_sources: HashSet::default(),
1879 address_sources: HashSet::default(),
1880 local_inputs: observer.local_inputs.clone(),
1881 order_before: trigger_order_before,
1882 comb_capture_enable_sites: Vec::new(),
1883 comb_capture_enable_always: false,
1884 pre_lower_nodes: Vec::new(),
1885 expr: loop_runner,
1886 });
1887 previous_trigger_capture_path = Some(path_id);
1888 }
1889 continue;
1890 }
1891 let local_input_ids: HashSet<_> = observer
1892 .local_inputs
1893 .iter()
1894 .map(|(addr, _)| *addr)
1895 .collect();
1896 let mut sources: HashSet<_> = observer
1897 .observed_inputs
1898 .iter()
1899 .copied()
1900 .filter(|atom| !observer_written_input_overlaps(observer, atom))
1901 .filter(|atom| !local_input_ids.contains(&atom.id))
1902 .filter(|atom| !observer_statement_position_overlaps(comb_blocks, observer, atom))
1903 .collect();
1904 for (_, node) in &observer.local_inputs {
1905 let mut local_sources = HashSet::default();
1906 crate::flattening::collect_inputs(*node, arena, &mut local_sources);
1907 sources.extend(
1908 local_sources
1909 .into_iter()
1910 .filter(|atom| !observer_written_input_overlaps(observer, atom))
1911 .filter(|atom| !local_input_ids.contains(&atom.id))
1912 .filter(|atom| {
1913 !observer_statement_position_overlaps(comb_blocks, observer, atom)
1914 }),
1915 );
1916 }
1917 let expr = match observer.guard.or_else(|| observer.args.first().copied()) {
1918 Some(expr) => expr,
1919 None => arena.alloc(celox_slt::SLTNode::Constant(
1920 num_bigint::BigUint::from(1u8),
1921 num_bigint::BigUint::from(0u8),
1922 1,
1923 false,
1924 ))?,
1925 };
1926 let emit_on_true = matches!(
1927 sites[observer.site_id as usize].kind,
1928 RuntimeEventKind::Display | RuntimeEventKind::Write
1929 );
1930 let fatal_error_code = matches!(
1931 sites[observer.site_id as usize].kind,
1932 RuntimeEventKind::AssertFatal
1933 )
1934 .then_some(observer.site_id as i64);
1935 let pre_lower_nodes = observer_pre_lower_nodes(observer, arena);
1936 for idx in &order_after {
1937 comb_blocks[idx.0]
1938 .pre_lower_nodes
1939 .extend(pre_lower_nodes.iter().copied());
1940 }
1941 let path_id = LogicPathId(comb_blocks.len());
1942 if let Some(prev) = previous_primary_capture_path {
1943 comb_blocks[prev.0].order_before.insert(path_id);
1944 }
1945 for idx in &order_after {
1946 comb_blocks[idx.0].order_before.insert(path_id);
1947 }
1948 comb_blocks.push(LogicPath {
1949 target: LogicPathTarget::CombCaptureEvent {
1950 site_id: observer.site_id,
1951 guard: observer.guard,
1952 emit_on_true,
1953 args: observer.args.clone(),
1954 loop_runner: None,
1955 fatal_error_code,
1956 consume_enabled: !trigger_paths.is_empty(),
1957 },
1958 sources,
1959 previous_sources: HashSet::default(),
1960 address_sources: HashSet::default(),
1961 local_inputs: observer.local_inputs.clone(),
1962 order_before,
1963 comb_capture_enable_sites: Vec::new(),
1964 comb_capture_enable_always: false,
1965 pre_lower_nodes: Vec::new(),
1966 expr,
1967 });
1968 previous_primary_capture_path = Some(path_id);
1969 for trigger_idx in trigger_paths {
1970 if !emitted_group_triggers.insert((observer.activation_group, trigger_idx)) {
1971 continue;
1972 }
1973 let Some(trigger_target) = comb_blocks[trigger_idx.0].target.var().copied() else {
1974 continue;
1975 };
1976 let trigger_order_before = direct_consumers_of_path_target(comb_blocks, trigger_idx);
1977 for &member_idx in &group_members[&observer.activation_group] {
1978 let member = &observers[member_idx];
1979 let member_emit_on_true = matches!(
1980 sites[member.site_id as usize].kind,
1981 RuntimeEventKind::Display | RuntimeEventKind::Write
1982 );
1983 let member_fatal_error_code = matches!(
1984 sites[member.site_id as usize].kind,
1985 RuntimeEventKind::AssertFatal
1986 )
1987 .then_some(member.site_id as i64);
1988 let member_expr = match member
1989 .loop_runner
1990 .or(member.guard)
1991 .or_else(|| member.args.first().copied())
1992 {
1993 Some(expr) => expr,
1994 None => arena.alloc(celox_slt::SLTNode::Constant(
1995 num_bigint::BigUint::from(1u8),
1996 num_bigint::BigUint::from(0u8),
1997 1,
1998 false,
1999 ))?,
2000 };
2001 let path_id = LogicPathId(comb_blocks.len());
2002 if let Some(prev) = previous_trigger_capture_path {
2003 comb_blocks[prev.0].order_before.insert(path_id);
2004 }
2005 comb_blocks[trigger_idx.0].order_before.insert(path_id);
2006 comb_blocks.push(LogicPath {
2007 target: LogicPathTarget::CombCaptureEvent {
2008 site_id: member.site_id,
2009 guard: member.guard,
2010 emit_on_true: member_emit_on_true,
2011 args: member.args.clone(),
2012 loop_runner: member.loop_runner,
2013 fatal_error_code: member_fatal_error_code,
2014 consume_enabled: true,
2015 },
2016 sources: std::iter::once(trigger_target).collect(),
2017 previous_sources: HashSet::default(),
2018 address_sources: HashSet::default(),
2019 local_inputs: member.local_inputs.clone(),
2020 order_before: trigger_order_before.clone(),
2021 comb_capture_enable_sites: Vec::new(),
2022 comb_capture_enable_always: false,
2023 pre_lower_nodes: Vec::new(),
2024 expr: member_expr,
2025 });
2026 previous_trigger_capture_path = Some(path_id);
2027 }
2028 }
2029 }
2030 Ok(())
2031}
2032
2033fn apply_always_comb_previous_source_ordering(comb_blocks: &mut [LogicPath<AbsoluteAddr>]) {
2034 let targets: Vec<_> = comb_blocks
2035 .iter()
2036 .map(|path| path.target.var().copied())
2037 .collect();
2038
2039 for (idx, path) in comb_blocks.iter_mut().enumerate() {
2040 if path.previous_sources.is_empty() {
2041 continue;
2042 }
2043
2044 let previous_sources = path.previous_sources.clone();
2045 let address_sources = path.address_sources.clone();
2046 path.sources.retain(|source| {
2047 let is_previous = previous_sources.iter().any(|previous| {
2048 previous.id == source.id && previous.access.overlaps(&source.access)
2049 });
2050 let is_address = address_sources
2051 .iter()
2052 .any(|address| address.id == source.id && address.access.overlaps(&source.access));
2053 !is_previous || is_address
2054 });
2055
2056 let mut order_before = Vec::new();
2057 for (target_idx, target) in targets.iter().enumerate() {
2058 if target_idx == idx {
2059 continue;
2060 }
2061 let Some(target) = target else {
2062 continue;
2063 };
2064 if previous_sources.iter().any(|previous| {
2065 previous.id == target.id && previous.access.overlaps(&target.access)
2066 }) {
2067 order_before.push(LogicPathId(target_idx));
2068 }
2069 }
2070 path.order_before.extend(order_before);
2071 }
2072}
2073
2074fn observer_written_input_overlaps(
2075 observer: &CombObserver<AbsoluteAddr>,
2076 atom: &VarAtomBase<AbsoluteAddr>,
2077) -> bool {
2078 observer
2079 .written_input_atoms
2080 .iter()
2081 .any(|written| written.id == atom.id && written.access.overlaps(&atom.access))
2082}
2083
2084fn observer_statement_position_overlaps(
2085 paths: &[LogicPath<AbsoluteAddr>],
2086 observer: &CombObserver<AbsoluteAddr>,
2087 atom: &VarAtomBase<AbsoluteAddr>,
2088) -> bool {
2089 atom_overlaps_any(atom, observer_affected_by_preceding_writes(paths, observer))
2090}
2091
2092fn observer_has_statement_position_dependency(
2093 paths: &[LogicPath<AbsoluteAddr>],
2094 observer: &CombObserver<AbsoluteAddr>,
2095) -> bool {
2096 if observer.preceding_writes.is_empty() {
2097 return false;
2098 }
2099 let affected = observer_affected_by_preceding_writes(paths, observer);
2100 observer
2101 .position_inputs
2102 .iter()
2103 .chain(observer.observed_inputs.iter())
2104 .any(|input| atom_overlaps_any(input, &affected))
2105}
2106
2107fn observer_trigger_paths(
2108 paths: &[LogicPath<AbsoluteAddr>],
2109 observer: &CombObserver<AbsoluteAddr>,
2110) -> Vec<LogicPathId> {
2111 let mut seen_targets = HashSet::default();
2112 let affected = observer_affected_by_preceding_writes(paths, observer);
2113 paths
2114 .iter()
2115 .enumerate()
2116 .filter_map(|(idx, path)| {
2117 let target = path.target.var()?;
2118 if observer_written_input_overlaps(observer, target) {
2119 return None;
2120 }
2121 let matches_observer_operand = observer
2122 .position_inputs
2123 .iter()
2124 .chain(observer.observed_inputs.iter())
2125 .any(|atom| target.id == atom.id && target.access.overlaps(&atom.access));
2126 if !matches_observer_operand
2127 || !atom_overlaps_any(target, &affected)
2128 || !seen_targets.insert((target.id, target.access.lsb, target.access.msb))
2129 {
2130 return None;
2131 }
2132 Some(LogicPathId(idx))
2133 })
2134 .collect()
2135}
2136
2137fn observer_pre_lower_nodes(
2138 observer: &CombObserver<AbsoluteAddr>,
2139 arena: &SLTNodeArena<AbsoluteAddr>,
2140) -> Vec<NodeId> {
2141 let local_input_ids: HashSet<_> = observer.local_inputs.iter().map(|(id, _)| *id).collect();
2142 let mut nodes = Vec::with_capacity(observer.args.len() + usize::from(observer.guard.is_some()));
2143 if let Some(guard) = observer.guard {
2144 nodes.push(guard);
2145 }
2146 nodes.extend(observer.args.iter().copied());
2147 nodes.extend(observer.local_inputs.iter().filter_map(|(_, node)| {
2148 matches!(arena.get(*node), celox_slt::SLTNode::Capture { .. }).then_some(*node)
2149 }));
2150 nodes
2151 .into_iter()
2152 .filter(|node| {
2153 let mut inputs = HashSet::default();
2154 crate::flattening::collect_inputs(*node, arena, &mut inputs);
2155 !inputs.is_empty()
2160 && (capture_contains_nested_snapshot(*node, arena)
2161 || inputs
2162 .iter()
2163 .all(|input| !local_input_ids.contains(&input.id)))
2164 })
2165 .collect()
2166}
2167
2168fn capture_contains_nested_snapshot(node: NodeId, arena: &SLTNodeArena<AbsoluteAddr>) -> bool {
2169 let celox_slt::SLTNode::Capture { expr, .. } = arena.get(node) else {
2170 return false;
2171 };
2172 let mut work = vec![*expr];
2173 let mut visited = HashSet::default();
2174 while let Some(node) = work.pop() {
2175 if !visited.insert(node) {
2176 continue;
2177 }
2178 match arena.get(node) {
2179 celox_slt::SLTNode::Capture { .. } => return true,
2180 celox_slt::SLTNode::Input { index, .. } => {
2181 work.extend(index.iter().map(|entry| entry.node));
2182 }
2183 celox_slt::SLTNode::Constant(..) => {}
2184 celox_slt::SLTNode::Binary(lhs, _, rhs) => {
2185 work.push(*lhs);
2186 work.push(*rhs);
2187 }
2188 celox_slt::SLTNode::Unary(_, inner) => work.push(*inner),
2189 celox_slt::SLTNode::Mux {
2190 cond,
2191 then_expr,
2192 else_expr,
2193 } => {
2194 work.push(*cond);
2195 work.push(*then_expr);
2196 work.push(*else_expr);
2197 }
2198 celox_slt::SLTNode::Concat(parts) => {
2199 work.extend(parts.iter().map(|(part, _)| *part));
2200 }
2201 celox_slt::SLTNode::Slice { expr, .. } => work.push(*expr),
2202 celox_slt::SLTNode::ForFold {
2203 start,
2204 end,
2205 result,
2206 initials,
2207 updates,
2208 effects,
2209 continue_cond,
2210 ..
2211 } => {
2212 if let celox_slt::SLTLoopBound::Expr(node) = start {
2213 work.push(*node);
2214 }
2215 if let celox_slt::SLTLoopBound::Expr(node) = end {
2216 work.push(*node);
2217 }
2218 if let celox_slt::SLTForFoldResult::Transient { initial, update } = result {
2219 work.push(*initial);
2220 work.push(*update);
2221 }
2222 work.extend(initials.iter().map(|state| state.expr));
2223 work.extend(updates.iter().map(|state| state.expr));
2224 for effect in effects {
2225 match effect {
2226 celox_slt::SLTForEffect::Event { guard, args, .. } => {
2227 work.extend(*guard);
2228 work.extend(args.iter().copied());
2229 }
2230 celox_slt::SLTForEffect::Runner(runner) => work.push(*runner),
2231 }
2232 }
2233 work.push(*continue_cond);
2234 }
2235 celox_slt::SLTNode::ForFoldGroup {
2236 entry_guard,
2237 states,
2238 ..
2239 } => {
2240 work.push(*entry_guard);
2241 for state in states {
2242 work.push(state.initial);
2243 work.push(state.update);
2244 }
2245 }
2246 }
2247 }
2248 false
2249}
2250
2251fn annotate_comb_capture_enable_sites(
2252 comb_blocks: &mut [LogicPath<AbsoluteAddr>],
2253 observers: &[CombObserver<AbsoluteAddr>],
2254) {
2255 let mut group_sites: HashMap<u32, Vec<u32>> = HashMap::default();
2256 for observer in observers {
2257 group_sites
2258 .entry(observer.activation_group)
2259 .or_default()
2260 .push(observer.site_id);
2261 }
2262 for observer in observers {
2263 for atom in &observer.sensitivity {
2264 for path in comb_blocks.iter_mut() {
2265 let Some(target) = path.target.var() else {
2266 continue;
2267 };
2268 if target.id == atom.id && target.access.overlaps(&atom.access) {
2269 for site_id in &group_sites[&observer.activation_group] {
2270 if !path.comb_capture_enable_sites.contains(site_id) {
2271 path.comb_capture_enable_sites.push(*site_id);
2272 }
2273 }
2274 }
2275 }
2276 }
2277 }
2278}
2279
2280fn observer_order_after(
2281 paths: &[LogicPath<AbsoluteAddr>],
2282 observer: &CombObserver<AbsoluteAddr>,
2283) -> HashSet<LogicPathId> {
2284 let mut result = HashSet::default();
2285 if !observer_has_statement_position_dependency(paths, observer) {
2286 return result;
2287 }
2288 for written in &observer.preceding_writes {
2289 for (idx, path) in paths.iter().enumerate() {
2290 let Some(target) = path.target.var() else {
2291 continue;
2292 };
2293 if target.id == written.id && target.access.overlaps(&written.access) {
2294 result.insert(LogicPathId(idx));
2295 }
2296 }
2297 }
2298 result
2299}
2300
2301fn observer_order_before(
2302 paths: &[LogicPath<AbsoluteAddr>],
2303 observer: &CombObserver<AbsoluteAddr>,
2304) -> HashSet<LogicPathId> {
2305 let preceding_writes = observer.preceding_writes.iter().collect::<Vec<_>>();
2306 let affected_by_preceding_writes = observer_has_statement_position_dependency(paths, observer)
2307 .then(|| observer_affected_by_preceding_writes(paths, observer));
2308 let mut result = HashSet::default();
2309 for (idx, path) in paths.iter().enumerate() {
2310 let Some(target) = path.target.var() else {
2311 continue;
2312 };
2313 let already_written = preceding_writes
2314 .iter()
2315 .any(|written| target.id == written.id && target.access.overlaps(&written.access));
2316 let is_later_observed_write = observer_written_input_overlaps(observer, target);
2317 let is_later_affected_write = affected_by_preceding_writes
2318 .as_ref()
2319 .is_some_and(|affected| atom_overlaps_any(target, affected));
2320 if !already_written && (is_later_observed_write || is_later_affected_write) {
2321 result.insert(LogicPathId(idx));
2322 }
2323 }
2324 result
2325}
2326
2327fn direct_consumers_of_path_target(
2331 paths: &[LogicPath<AbsoluteAddr>],
2332 trigger: LogicPathId,
2333) -> HashSet<LogicPathId> {
2334 let Some(target) = paths.get(trigger.0).and_then(|path| path.target.var()) else {
2335 return HashSet::default();
2336 };
2337 paths
2338 .iter()
2339 .enumerate()
2340 .filter_map(|(index, path)| {
2341 (index != trigger.0
2342 && path
2343 .sources
2344 .iter()
2345 .any(|source| source.id == target.id && source.access.overlaps(&target.access)))
2346 .then_some(LogicPathId(index))
2347 })
2348 .collect()
2349}
2350
2351fn observer_affected_by_preceding_writes(
2352 paths: &[LogicPath<AbsoluteAddr>],
2353 observer: &CombObserver<AbsoluteAddr>,
2354) -> HashSet<VarAtomBase<AbsoluteAddr>> {
2355 let mut affected: HashSet<VarAtomBase<AbsoluteAddr>> =
2356 observer.preceding_writes.iter().copied().collect();
2357 let mut changed = true;
2358 while changed {
2359 changed = false;
2360 for path in paths {
2361 let Some(target) = path.target.var() else {
2362 continue;
2363 };
2364 if !path
2365 .sources
2366 .iter()
2367 .any(|source| atom_overlaps_any(source, &affected))
2368 {
2369 continue;
2370 }
2371 if affected.insert(*target) {
2372 changed = true;
2373 }
2374 }
2375 }
2376 affected
2377}
2378
2379fn atom_overlaps_any<A: Eq + std::hash::Hash + Copy>(
2380 atom: &VarAtomBase<A>,
2381 atoms: impl IntoIterator<Item = impl std::borrow::Borrow<VarAtomBase<A>>>,
2382) -> bool {
2383 atoms.into_iter().any(|other| {
2384 let other = other.borrow();
2385 atom.id == other.id && atom.access.overlaps(&other.access)
2386 })
2387}
2388
2389fn analyze_clock_dependencies(
2390 eval_apply_ffs: &mut HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>>,
2391 eval_only_ffs: &mut HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>>,
2392 apply_ffs: &mut HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>>,
2393 comb_blocks: &[LogicPath<AbsoluteAddr>],
2394 arena: &SLTNodeArena<AbsoluteAddr>,
2395 clock_domains: &HashMap<AbsoluteAddr, AbsoluteAddr>,
2396 expanded: &HashMap<InstancePath, InstanceId>,
2397 instance_modules: &HashMap<InstanceId, ModuleId>,
2398 modules: &HashMap<ModuleId, SimModule>,
2399) -> (Vec<AbsoluteAddr>, BTreeSet<AbsoluteAddr>) {
2400 let mut clock_deps: BTreeMap<AbsoluteAddr, BTreeSet<AbsoluteAddr>> = BTreeMap::new();
2402 let mut unique_clocks: BTreeSet<AbsoluteAddr> = BTreeSet::new();
2403
2404 let mut ff_outputs: BTreeSet<AbsoluteAddr> = BTreeSet::new();
2406 unique_clocks.extend(eval_apply_ffs.keys().copied());
2407
2408 for id in expanded.values() {
2412 let module_id = &instance_modules[id];
2413 let sim_module = &modules[module_id];
2414 for (var_id, var) in &sim_module.variables {
2415 let kind = var.metadata.kind;
2416 if !matches!(
2417 kind,
2418 DomainKind::ClockPosedge
2419 | DomainKind::ClockNegedge
2420 | DomainKind::ResetAsyncHigh
2421 | DomainKind::ResetAsyncLow
2422 ) {
2423 continue;
2424 }
2425 let addr = AbsoluteAddr {
2426 instance_id: *id,
2427 var_id: *var_id,
2428 };
2429 let canonical = clock_domains.get(&addr).copied().unwrap_or(addr);
2430 unique_clocks.insert(canonical);
2431 eval_apply_ffs.entry(canonical).or_default();
2432 eval_only_ffs.entry(canonical).or_default();
2433 apply_ffs.entry(canonical).or_default();
2434 }
2435 }
2436
2437 for (domain_clock, eus) in &*eval_apply_ffs {
2438 for eu in eus {
2439 for bb in eu.blocks.values() {
2440 for inst in &bb.instructions {
2441 if let SIRInstruction::Store(target_addr, ..) = inst {
2442 let abs = target_addr.absolute_addr();
2444 let canonical_target = clock_domains.get(&abs).copied().unwrap_or(abs);
2445
2446 ff_outputs.insert(abs);
2447
2448 if canonical_target != *domain_clock
2449 && unique_clocks.contains(&canonical_target)
2450 {
2451 clock_deps
2452 .entry(canonical_target)
2453 .or_default()
2454 .insert(*domain_clock);
2455 }
2456 }
2457 }
2458 }
2459 }
2460 }
2461
2462 let acd_timing = tracing::enabled!(tracing::Level::DEBUG);
2464 let acd_start = acd_timing.then(std::time::Instant::now);
2465 let mut comb_deps: BTreeMap<AbsoluteAddr, BTreeSet<AbsoluteAddr>> = BTreeMap::new();
2466 for path in comb_blocks {
2467 let Some(target) = path.target.var() else {
2468 continue;
2469 };
2470 let target_abs = target.id;
2471 let mut sources = HashSet::default();
2472 crate::flattening::collect_inputs(path.expr, arena, &mut sources);
2473 for source in sources {
2474 comb_deps.entry(target_abs).or_default().insert(source.id);
2475 }
2476 }
2477 if let Some(s) = acd_start {
2478 tracing::debug!(
2479 "[acd] comb_deps build ({} blocks): {:?}",
2480 comb_deps.len(),
2481 s.elapsed()
2482 );
2483 }
2484
2485 fn collect_upstream_clocks(
2489 node: AbsoluteAddr,
2490 target_clock: AbsoluteAddr,
2491 comb_deps: &BTreeMap<AbsoluteAddr, BTreeSet<AbsoluteAddr>>,
2492 clock_domains: &HashMap<AbsoluteAddr, AbsoluteAddr>,
2493 clocks: &BTreeSet<AbsoluteAddr>,
2494 visited: &mut BTreeSet<AbsoluteAddr>,
2495 found: &mut BTreeSet<AbsoluteAddr>,
2496 ) {
2497 if !visited.insert(node) {
2498 return;
2499 }
2500 let Some(sources) = comb_deps.get(&node) else {
2501 return;
2502 };
2503 for source in sources {
2504 let canonical = clock_domains.get(source).copied().unwrap_or(*source);
2505 if canonical != target_clock && clocks.contains(&canonical) {
2506 found.insert(canonical);
2507 }
2508 collect_upstream_clocks(
2509 *source,
2510 target_clock,
2511 comb_deps,
2512 clock_domains,
2513 clocks,
2514 visited,
2515 found,
2516 );
2517 }
2518 }
2519
2520 for target_clock in &unique_clocks {
2521 let mut sources = BTreeSet::new();
2522 collect_upstream_clocks(
2523 *target_clock,
2524 *target_clock,
2525 &comb_deps,
2526 clock_domains,
2527 &unique_clocks,
2528 &mut BTreeSet::new(),
2529 &mut sources,
2530 );
2531 if !sources.is_empty() {
2532 clock_deps.entry(*target_clock).or_default().extend(sources);
2533 }
2534 }
2535
2536 let fp_start = acd_timing.then(std::time::Instant::now);
2538 let mut derived_from_ff: BTreeSet<AbsoluteAddr> = ff_outputs.clone();
2539 let mut changed = true;
2540 let mut fp_rounds = 0u32;
2541 while changed {
2542 changed = false;
2543 fp_rounds += 1;
2544 for (target, sources) in &comb_deps {
2545 if !derived_from_ff.contains(target) {
2546 if sources.iter().any(|s| derived_from_ff.contains(s)) {
2548 derived_from_ff.insert(*target);
2549 changed = true;
2550 }
2551 }
2552 }
2553 }
2554 if let Some(s) = fp_start {
2555 tracing::debug!(
2556 "[acd] fixpoint: {fp_rounds} rounds, {} entries, {:?}",
2557 comb_deps.len(),
2558 s.elapsed()
2559 );
2560 }
2561
2562 for clk in &unique_clocks {
2565 if derived_from_ff.contains(clk) {
2566 clock_deps.entry(*clk).or_default().insert(*clk);
2568 }
2569 }
2570
2571 let mut topological_clocks = Vec::new();
2574 let mut visited = BTreeSet::new();
2575 let mut temp_visited = BTreeSet::new();
2576
2577 fn topo_visit(
2578 node: AbsoluteAddr,
2579 deps: &BTreeMap<AbsoluteAddr, BTreeSet<AbsoluteAddr>>,
2580 visited: &mut BTreeSet<AbsoluteAddr>,
2581 temp_visited: &mut BTreeSet<AbsoluteAddr>,
2582 result: &mut Vec<AbsoluteAddr>,
2583 ) {
2584 if visited.contains(&node) {
2585 return;
2586 }
2587 if temp_visited.contains(&node) {
2588 return;
2590 }
2591 temp_visited.insert(node);
2592
2593 if let Some(node_deps) = deps.get(&node) {
2594 for &dep in node_deps {
2595 topo_visit(dep, deps, visited, temp_visited, result);
2596 }
2597 }
2598
2599 temp_visited.remove(&node);
2600 visited.insert(node);
2601 result.push(node);
2602 }
2603
2604 for &clk in &unique_clocks {
2606 if !visited.contains(&clk) {
2607 topo_visit(
2608 clk,
2609 &clock_deps,
2610 &mut visited,
2611 &mut temp_visited,
2612 &mut topological_clocks,
2613 );
2614 }
2615 }
2616
2617 for id in expanded.values() {
2619 let module_id = &instance_modules[id];
2620 let sim_module = &modules[module_id];
2621 for (var_id, var) in &sim_module.variables {
2622 let kind = var.metadata.kind;
2623 let is_trigger = matches!(
2624 kind,
2625 DomainKind::ClockPosedge
2626 | DomainKind::ClockNegedge
2627 | DomainKind::ResetAsyncHigh
2628 | DomainKind::ResetAsyncLow
2629 );
2630 if is_trigger {
2631 let addr = AbsoluteAddr {
2632 instance_id: *id,
2633 var_id: *var_id,
2634 };
2635 let canonical = clock_domains.get(&addr).copied().unwrap_or(addr);
2636 eval_apply_ffs.entry(canonical).or_default();
2638 eval_only_ffs.entry(canonical).or_default();
2639 apply_ffs.entry(canonical).or_default();
2640
2641 if !visited.contains(&canonical) {
2642 topo_visit(
2643 canonical,
2644 &clock_deps,
2645 &mut visited,
2646 &mut temp_visited,
2647 &mut topological_clocks,
2648 );
2649 }
2650 }
2651 }
2652 }
2653
2654 let mut cascaded_clocks: BTreeSet<AbsoluteAddr> = BTreeSet::new();
2655 for (target, sources) in &clock_deps {
2656 cascaded_clocks.insert(*target);
2657 for source in sources {
2658 cascaded_clocks.insert(*source);
2659 }
2660 }
2661
2662 (topological_clocks, cascaded_clocks)
2663}