1use crate::HashMap;
2pub use celox_design::PortTypeKind;
3pub(crate) use celox_design::{
4 AbsoluteAddrBase, BitAccess, InstanceId, ModuleId, RegionedAbsoluteAddrBase,
5 RegionedVarAddrBase, RuntimeSchema, SPARSE_WORKING_REGION, STABLE_REGION, VarAtomBase,
6 WORKING_REGION,
7};
8#[cfg(test)]
9pub(crate) use celox_design::{BinaryOp, UnaryOp};
10#[cfg(feature = "host-runtime")]
11pub(crate) use celox_design::{
12 InitialStateData, InitialStateWriteRun, RuntimeEventKind, RuntimeEventSite,
13};
14pub use celox_frontend_core::shared::{
15 FrontendLookup, InstancePath, SourceAddr, SourceVarId, VariableInfo, VariableKind,
16};
17#[cfg(all(
18 feature = "host-runtime",
19 any(
20 target_arch = "x86_64",
21 feature = "arm64-codegen",
22 target_arch = "aarch64"
23 )
24))]
25use celox_runtime::{
26 DesignReflection, ReflectionScope, ReflectionScopeId, ReflectionSignal, ReflectionSignalId,
27 SignalDirection,
28};
29#[cfg(test)]
30pub(crate) use celox_sir::{BasicBlock, SIRValue, inline_single_predecessor_jumps};
31pub(crate) use celox_sir::{
32 BlockId, ExecutionUnit, RegisterId, RegisterType, SIRInstruction, SIROffset, SIRTerminator,
33 collect_exact_zero_registers,
34};
35use celox_testbench::TestbenchProgram;
36use std::{fmt, ops::Deref};
37
38pub type AbsoluteAddr = celox_design::StateAddr;
40pub type RegionedAbsoluteAddr = celox_design::RegionedStateAddr;
42pub type SirProgram = celox_sir::SirProgram<AbsoluteAddr, RegionedAbsoluteAddr>;
43
44#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
50pub struct RuntimeVariable {
51 pub address: AbsoluteAddr,
52 pub source_id: SourceVarId,
53 pub path: Vec<String>,
54 pub var_kind: VariableKind,
55 pub signed: bool,
56 pub packed_dims: Vec<usize>,
57}
58
59#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
61pub struct RuntimeInstance {
62 pub id: InstanceId,
63 pub module_id: ModuleId,
64 pub module_name: String,
65 pub path: InstancePath,
66 pub display_path: Vec<String>,
67 state_addresses: Vec<AbsoluteAddr>,
68 source_variables: HashMap<SourceVarId, AbsoluteAddr>,
69 path_index: HashMap<Vec<String>, Option<AbsoluteAddr>>,
70}
71
72impl RuntimeInstance {
73 pub fn state_addresses(&self) -> &[AbsoluteAddr] {
74 &self.state_addresses
75 }
76
77 pub fn resolves_path_to(&self, path: &[String], address: AbsoluteAddr) -> bool {
78 self.path_index.get(path) == Some(&Some(address))
79 }
80}
81
82#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
88pub struct RuntimeDesign {
89 semantic: celox_design::ElaboratedDesign<AbsoluteAddr>,
90 instances: HashMap<InstanceId, RuntimeInstance>,
91 instance_ids: HashMap<InstancePath, InstanceId>,
92 variables: HashMap<AbsoluteAddr, RuntimeVariable>,
93}
94
95impl std::ops::Deref for RuntimeDesign {
96 type Target = celox_design::ElaboratedDesign<AbsoluteAddr>;
97
98 fn deref(&self) -> &Self::Target {
99 &self.semantic
100 }
101}
102
103impl RuntimeDesign {
104 fn from_projection(
105 semantic: celox_design::ElaboratedDesign<AbsoluteAddr>,
106 frontend: FrontendLookup,
107 ) -> Result<Self, DesignProjectionError> {
108 let expected_count = frontend
109 .instance_module
110 .values()
111 .map(|module_id| frontend.module_variables[module_id].len())
112 .sum::<usize>();
113 if semantic.state_objects.len() != expected_count {
114 return Err(DesignProjectionError::StateObjectCount {
115 design: semantic.state_objects.len(),
116 frontend: expected_count,
117 });
118 }
119
120 let mut instances = HashMap::default();
121 let mut variables = HashMap::default();
122 for (path, &instance_id) in &frontend.instance_ids {
123 let module_id = frontend.instance_module[&instance_id];
124 let module_variables = &frontend.module_variables[&module_id];
125 let display_path = frontend.instance_path_segments(path);
126 let mut state_addresses = Vec::with_capacity(module_variables.len());
127 let mut source_variables = HashMap::default();
128
129 for info in module_variables.values() {
130 let source_address = SourceAddr {
131 instance_id,
132 var_id: info.id,
133 };
134 let Some(address) = frontend.state_address(&source_address) else {
135 return Err(DesignProjectionError::MissingStateProjection { source_address });
136 };
137 let Some(metadata) = semantic.state_objects.get(&address) else {
138 return Err(DesignProjectionError::MissingStateObject { address });
139 };
140 if metadata != &info.metadata {
141 return Err(DesignProjectionError::MetadataMismatch { address });
142 }
143
144 state_addresses.push(address);
145 source_variables.insert(info.id, address);
146 variables.insert(
147 address,
148 RuntimeVariable {
149 address,
150 source_id: info.id,
151 path: info.path.clone(),
152 var_kind: info.var_kind,
153 signed: info.signed,
154 packed_dims: info.packed_dims.clone(),
155 },
156 );
157 }
158
159 state_addresses.sort_unstable();
160 let path_index = frontend.module_var_path_index[&module_id]
161 .iter()
162 .map(|(path, source_id)| {
163 (
164 path.clone(),
165 source_id.and_then(|source_id| source_variables.get(&source_id).copied()),
166 )
167 })
168 .collect();
169 instances.insert(
170 instance_id,
171 RuntimeInstance {
172 id: instance_id,
173 module_id,
174 module_name: frontend
175 .module_names
176 .get(&module_id)
177 .cloned()
178 .unwrap_or_else(|| module_id.to_string()),
179 path: path.clone(),
180 display_path,
181 state_addresses,
182 source_variables,
183 path_index,
184 },
185 );
186 }
187
188 let design = Self {
189 semantic,
190 instances,
191 instance_ids: frontend.instance_ids,
192 variables,
193 };
194 design
195 .validate()
196 .map_err(|reason| DesignProjectionError::InvalidRuntimeDesign { reason })?;
197 Ok(design)
198 }
199
200 pub fn semantic(&self) -> &celox_design::ElaboratedDesign<AbsoluteAddr> {
201 &self.semantic
202 }
203
204 pub fn instances(&self) -> impl Iterator<Item = &RuntimeInstance> {
205 self.instances.values()
206 }
207
208 pub fn instance(&self, id: InstanceId) -> Option<&RuntimeInstance> {
209 self.instances.get(&id)
210 }
211
212 pub fn instance_at_path(&self, path: &InstancePath) -> Option<&RuntimeInstance> {
213 self.instance_ids
214 .get(path)
215 .and_then(|instance_id| self.instances.get(instance_id))
216 }
217
218 pub fn root_instance(&self) -> Option<&RuntimeInstance> {
219 self.instance_at_path(&InstancePath(Vec::new()))
220 }
221
222 pub fn variable(&self, address: &AbsoluteAddr) -> Option<&RuntimeVariable> {
223 self.variables.get(address)
224 }
225
226 pub fn instance_variable(
227 &self,
228 instance_id: InstanceId,
229 source_id: SourceVarId,
230 ) -> Option<&RuntimeVariable> {
231 let address = self
232 .instances
233 .get(&instance_id)?
234 .source_variables
235 .get(&source_id)?;
236 self.variables.get(address)
237 }
238
239 pub fn variable_info(&self, address: &AbsoluteAddr) -> Option<VariableInfo> {
240 let variable = self.variables.get(address)?;
241 Some(VariableInfo {
242 id: variable.source_id,
243 path: variable.path.clone(),
244 var_kind: variable.var_kind,
245 signed: variable.signed,
246 metadata: self.semantic.state_objects.get(address)?.clone(),
247 packed_dims: variable.packed_dims.clone(),
248 })
249 }
250
251 pub fn get_path(&self, address: &AbsoluteAddr) -> String {
252 let Some(variable) = self.variables.get(address) else {
253 return address.to_string();
254 };
255 let Some(instance) = self.instances.get(&address.instance_id) else {
256 return address.to_string();
257 };
258 instance
259 .display_path
260 .iter()
261 .chain(&variable.path)
262 .cloned()
263 .collect::<Vec<_>>()
264 .join(".")
265 }
266
267 pub(crate) fn validate(&self) -> Result<(), String> {
268 if self.variables.len() != self.semantic.state_objects.len() {
269 return Err(format!(
270 "state variable count differs: design={} runtime={}",
271 self.semantic.state_objects.len(),
272 self.variables.len()
273 ));
274 }
275 if self.instance_ids.len() != self.instances.len() {
276 return Err(format!(
277 "instance count differs: paths={} instances={}",
278 self.instance_ids.len(),
279 self.instances.len()
280 ));
281 }
282
283 for (path, instance_id) in &self.instance_ids {
284 let instance = self.instances.get(instance_id).ok_or_else(|| {
285 format!("instance path {path:?} references missing {instance_id}")
286 })?;
287 if instance.path != *path || instance.id != *instance_id {
288 return Err(format!("instance path index disagrees for {instance_id}"));
289 }
290 }
291
292 for (instance_id, instance) in &self.instances {
293 if instance.id != *instance_id {
294 return Err(format!("instance map key disagrees for {instance_id}"));
295 }
296 if self.instance_ids.get(&instance.path) != Some(instance_id) {
297 return Err(format!("missing path index for {instance_id}"));
298 }
299 if instance.state_addresses.len() != instance.source_variables.len() {
300 return Err(format!(
301 "state/source variable count differs for {instance_id}"
302 ));
303 }
304 if instance
305 .state_addresses
306 .windows(2)
307 .any(|addresses| addresses[0] >= addresses[1])
308 {
309 return Err(format!(
310 "state addresses are not strictly sorted for {instance_id}"
311 ));
312 }
313
314 for address in &instance.state_addresses {
315 if address.instance_id != *instance_id {
316 return Err(format!(
317 "state address {address} belongs to another instance"
318 ));
319 }
320 if !self.semantic.state_objects.contains_key(address) {
321 return Err(format!("state address {address} has no semantic metadata"));
322 }
323 let variable = self
324 .variables
325 .get(address)
326 .ok_or_else(|| format!("state address {address} has no runtime variable"))?;
327 if variable.address != *address
328 || instance.source_variables.get(&variable.source_id) != Some(address)
329 {
330 return Err(format!("source index disagrees for {address}"));
331 }
332 }
333
334 for (path, address) in &instance.path_index {
335 let Some(address) = address else {
336 continue;
337 };
338 let variable = self
339 .variables
340 .get(address)
341 .ok_or_else(|| format!("path index references missing {address}"))?;
342 if address.instance_id != *instance_id || variable.path != *path {
343 return Err(format!("path index disagrees for {address}"));
344 }
345 }
346 }
347
348 for address in self.variables.keys() {
349 let instance = self
350 .instances
351 .get(&address.instance_id)
352 .ok_or_else(|| format!("runtime variable {address} references missing instance"))?;
353 if instance.state_addresses.binary_search(address).is_err() {
354 return Err(format!(
355 "runtime variable {address} is not indexed by instance"
356 ));
357 }
358 }
359
360 Ok(())
361 }
362
363 #[cfg(feature = "host-runtime")]
364 pub(crate) fn take_initial_state(
365 &mut self,
366 ) -> Vec<celox_design::InitialStateValue<AbsoluteAddr>> {
367 std::mem::take(&mut self.semantic.initial_state)
368 }
369
370 #[cfg(feature = "host-runtime")]
371 pub(crate) fn restore_initial_state(
372 &mut self,
373 initial_state: Vec<celox_design::InitialStateValue<AbsoluteAddr>>,
374 ) {
375 self.semantic.initial_state = initial_state;
376 }
377}
378
379#[derive(Debug, Clone, thiserror::Error)]
381pub enum AddrLookupError {
382 #[error("Instance not found: {path}")]
383 InstanceNotFound { path: String },
384 #[error("Variable not found: {path}")]
385 VariableNotFound { path: String },
386 #[error("Ambiguous variable path: {path} — multiple variables share this path")]
387 AmbiguousPath { path: String },
388}
389
390#[derive(Debug, Clone, thiserror::Error)]
393pub(crate) enum DesignProjectionError {
394 #[error("state object count differs: design={design} frontend={frontend}")]
395 StateObjectCount { design: usize, frontend: usize },
396 #[error("missing state projection for {source_address}")]
397 MissingStateProjection { source_address: SourceAddr },
398 #[error("missing flattened state object {address}")]
399 MissingStateObject { address: AbsoluteAddr },
400 #[error("metadata differs for flattened state object {address}")]
401 MetadataMismatch { address: AbsoluteAddr },
402 #[error("invalid normalized runtime design: {reason}")]
403 InvalidRuntimeDesign { reason: String },
404}
405
406#[cfg(feature = "host-runtime")]
407pub type InitialMemoryWriteRun = InitialStateWriteRun;
408#[cfg(feature = "host-runtime")]
409pub type InitialMemoryData = InitialStateData;
410pub type RuntimeErrorInfo<Addr = AbsoluteAddr> = celox_design::RuntimeErrorInfo<Addr>;
411
412#[derive(Clone)]
417pub struct RuntimeProgram {
418 pub design: RuntimeDesign,
419 pub runtime_schema: RuntimeSchema<AbsoluteAddr>,
420 pub testbench: Option<TestbenchProgram<AbsoluteAddr>>,
421}
422
423#[derive(Clone, Debug)]
425pub struct UnoptimizedSir {
426 pub sir: SirProgram,
427 pub layout_requirements: celox_state_layout::LayoutRequirements<AbsoluteAddr>,
428 pub runtime: RuntimeProgram,
429}
430
431impl UnoptimizedSir {
432 pub(crate) fn new(sir: SirProgram, runtime: RuntimeProgram) -> Self {
433 Self {
434 sir,
435 layout_requirements: Default::default(),
436 runtime,
437 }
438 }
439
440 pub(crate) fn into_optimized(self) -> OptimizedSir {
441 OptimizedSir::new(self.sir, self.runtime, self.layout_requirements)
442 }
443}
444
445impl Deref for UnoptimizedSir {
446 type Target = RuntimeProgram;
447
448 fn deref(&self) -> &Self::Target {
449 &self.runtime
450 }
451}
452
453#[derive(Clone, Debug)]
460pub struct OptimizedSir {
461 pub sir: SirProgram,
462 pub layout_requirements: celox_state_layout::LayoutRequirements<AbsoluteAddr>,
463 pub(crate) runtime: RuntimeProgram,
464}
465
466impl OptimizedSir {
467 pub(crate) fn new(
468 sir: SirProgram,
469 runtime: RuntimeProgram,
470 layout_requirements: celox_state_layout::LayoutRequirements<AbsoluteAddr>,
471 ) -> Self {
472 Self {
473 sir,
474 layout_requirements,
475 runtime,
476 }
477 }
478
479 #[cfg(all(
480 feature = "host-runtime",
481 any(
482 target_arch = "x86_64",
483 feature = "arm64-codegen",
484 target_arch = "aarch64"
485 )
486 ))]
487 pub(crate) fn into_runtime(self) -> RuntimeProgram {
488 self.runtime
489 }
490}
491
492impl Deref for OptimizedSir {
493 type Target = RuntimeProgram;
494
495 fn deref(&self) -> &Self::Target {
496 &self.runtime
497 }
498}
499
500#[derive(Clone, Debug)]
505pub struct LaidOutProgram {
506 pub sir: SirProgram,
507 pub(crate) runtime: RuntimeProgram,
508 layout: crate::backend::MemoryLayout,
509}
510
511impl LaidOutProgram {
512 pub fn layout(&self) -> &crate::backend::MemoryLayout {
513 &self.layout
514 }
515
516 pub fn runtime(&self) -> &RuntimeProgram {
517 &self.runtime
518 }
519
520 pub fn into_runtime(self) -> RuntimeProgram {
521 self.runtime
522 }
523}
524
525impl Deref for LaidOutProgram {
526 type Target = RuntimeProgram;
527
528 fn deref(&self) -> &Self::Target {
529 &self.runtime
530 }
531}
532
533impl fmt::Debug for RuntimeProgram {
534 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535 f.debug_struct("RuntimeProgram")
536 .field("num_events", &self.design.events.len())
537 .finish_non_exhaustive()
538 }
539}
540
541impl OptimizedSir {
542 pub fn into_laid_out(self, four_state: bool) -> LaidOutProgram {
544 self.into_laid_out_with_mode(
545 four_state,
546 crate::backend::memory_layout::MemoryLayoutMode::Packed,
547 )
548 }
549
550 pub fn into_laid_out_with_mode(
551 self,
552 four_state: bool,
553 mode: crate::backend::memory_layout::MemoryLayoutMode,
554 ) -> LaidOutProgram {
555 let mut program = self;
556 if !program.runtime_schema.comb_observers.is_empty()
557 && !program.layout_requirements.is_empty()
558 {
559 let observed_written: crate::HashSet<AbsoluteAddr> = program
560 .runtime_schema
561 .comb_observers
562 .iter()
563 .flat_map(|observer| observer.written_inputs.iter().copied())
564 .collect();
565 program
566 .layout_requirements
567 .state_aliases_mut()
568 .retain(|alias_addr, _| !observed_written.contains(alias_addr));
569 program
570 .layout_requirements
571 .state_aliases_mut()
572 .retain(|alias_addr, _| {
573 !comb_capture_enable_needs_unaliased_old_value(
574 &program.sir.eval_comb,
575 *alias_addr,
576 )
577 });
578 }
579 crate::optimizer::sir::retain_final_identity_aliases(&mut program, four_state);
580 let layout = crate::backend::MemoryLayout::build(&program, four_state, mode);
581
582 if !program.layout_requirements.is_empty() {
584 let aliased: crate::HashMap<AbsoluteAddr, AbsoluteAddr> = program
585 .layout_requirements
586 .state_aliases()
587 .iter()
588 .filter(|(alias_addr, canonical_addr)| {
589 layout
590 .offsets
591 .get(alias_addr)
592 .zip(layout.offsets.get(canonical_addr))
593 .is_some_and(|(a, c)| a == c)
594 })
595 .map(|(&alias, &canonical)| (alias, canonical))
596 .collect();
597 if !aliased.is_empty() {
598 crate::optimizer::sir::remove_final_identity_alias_stores(
599 &mut program,
600 &aliased,
601 four_state,
602 );
603 }
604 }
605 rebuild_rtl_writes(&mut program);
606 program.layout_requirements.clear();
607 let OptimizedSir {
608 sir,
609 runtime,
610 layout_requirements,
611 } = program;
612 debug_assert!(layout_requirements.is_empty());
613 LaidOutProgram {
614 sir,
615 runtime,
616 layout,
617 }
618 }
619}
620
621fn rebuild_rtl_writes(program: &mut OptimizedSir) {
622 let mut rtl_writes = crate::HashSet::default();
623 for unit in program
624 .sir
625 .eval_comb
626 .iter()
627 .chain(program.sir.eval_apply_ffs.values().flatten())
628 .chain(program.sir.eval_comb_apply_ffs.values().flatten())
629 .chain(program.sir.eval_only_ffs.values().flatten())
630 .chain(program.sir.apply_ffs.values().flatten())
631 {
632 for block in unit.blocks.values() {
633 for instruction in &block.instructions {
634 let (address, offset, width) = match instruction {
635 SIRInstruction::Store(address, offset, width, ..)
636 | SIRInstruction::Commit(_, address, offset, width, _) => {
637 (address.absolute_addr(), offset, *width)
638 }
639 _ => continue,
640 };
641 let access = offset
642 .constant_bit_offset()
643 .and_then(|lsb| {
644 width
645 .checked_sub(1)
646 .and_then(|tail| lsb.checked_add(tail))
647 .map(|msb| BitAccess::new(lsb, msb))
648 })
649 .or_else(|| {
650 program
651 .runtime
652 .design
653 .state_objects
654 .get(&address)
655 .and_then(|object| object.width.checked_sub(1))
656 .map(|msb| BitAccess::new(0, msb))
657 });
658 if let Some(access) = access {
659 rtl_writes.insert(VarAtomBase {
660 id: address,
661 access,
662 });
663 }
664 }
665 }
666 }
667 program.runtime.runtime_schema.rtl_writes = rtl_writes;
668}
669
670impl RuntimeProgram {
671 #[cfg(all(
672 feature = "host-runtime",
673 any(
674 target_arch = "x86_64",
675 feature = "arm64-codegen",
676 target_arch = "aarch64"
677 )
678 ))]
679 pub(crate) fn build_design_reflection(
680 &self,
681 layout: &crate::backend::MemoryLayout,
682 ) -> DesignReflection {
683 struct ScopeSource {
684 instance_id: InstanceId,
685 name: String,
686 full_name: String,
687 parent_name: Option<String>,
688 module_name: String,
689 }
690
691 let root_name = self
692 .design
693 .root_instance()
694 .expect("top-level instance exists")
695 .module_name
696 .clone();
697
698 let mut scope_sources = self
699 .design
700 .instances()
701 .map(|instance| {
702 let segments = &instance.display_path;
703 let name = segments
704 .last()
705 .cloned()
706 .unwrap_or_else(|| root_name.clone());
707 let full_name = if segments.is_empty() {
708 root_name.clone()
709 } else {
710 format!("{root_name}.{}", segments.join("."))
711 };
712 let parent_name = (!segments.is_empty()).then(|| {
713 if segments.len() == 1 {
714 root_name.clone()
715 } else {
716 format!("{root_name}.{}", segments[..segments.len() - 1].join("."))
717 }
718 });
719 ScopeSource {
720 instance_id: instance.id,
721 name,
722 full_name,
723 parent_name,
724 module_name: instance.module_name.clone(),
725 }
726 })
727 .collect::<Vec<_>>();
728 scope_sources.sort_by(|left, right| left.full_name.cmp(&right.full_name));
729
730 let scope_ids = scope_sources
731 .iter()
732 .enumerate()
733 .map(|(index, scope)| {
734 (
735 scope.full_name.clone(),
736 ReflectionScopeId(u32::try_from(index).expect("scope count exceeds u32")),
737 )
738 })
739 .collect::<HashMap<_, _>>();
740 let instance_scopes = scope_sources
741 .iter()
742 .enumerate()
743 .map(|(index, scope)| {
744 (
745 scope.instance_id,
746 ReflectionScopeId(u32::try_from(index).expect("scope count exceeds u32")),
747 )
748 })
749 .collect::<HashMap<_, _>>();
750 let mut scopes = scope_sources
751 .iter()
752 .map(|scope| ReflectionScope {
753 name: scope.name.clone(),
754 full_name: scope.full_name.clone(),
755 module_name: scope.module_name.clone(),
756 parent: scope.parent_name.as_ref().map(|parent| scope_ids[parent]),
757 children: Vec::new(),
758 signals: Vec::new(),
759 })
760 .collect::<Vec<_>>();
761 let child_parents = scopes
762 .iter()
763 .enumerate()
764 .filter_map(|(index, scope)| {
765 scope.parent.map(|parent| {
766 (
767 parent,
768 ReflectionScopeId(u32::try_from(index).expect("scope count exceeds u32")),
769 )
770 })
771 })
772 .collect::<Vec<_>>();
773 for (parent, child) in child_parents {
774 scopes[parent.0 as usize].children.push(child);
775 }
776
777 let mut signals = Vec::new();
778 for scope in &scope_sources {
779 let instance = self.design.instance(scope.instance_id).unwrap();
780 for state_address in instance.state_addresses() {
781 let variable = self.design.variable(state_address).unwrap();
782 if matches!(
783 variable.var_kind,
784 VariableKind::Parameter | VariableKind::Constant
785 ) {
786 continue;
787 }
788 if instance.path_index.get(&variable.path) != Some(&Some(*state_address)) {
789 continue;
790 }
791 let metadata = &self.design.state_objects[state_address];
792 let name = variable.path.join(".");
793 let array_layout =
794 layout
795 .unpacked_arrays
796 .get(state_address)
797 .map(|array| SignalArrayLayout {
798 element_width: array.element_width,
799 element_count: array.element_count,
800 element_stride: array.element_stride,
801 plane_size: array.plane_size,
802 });
803 let direction = match variable.var_kind {
804 VariableKind::Input => SignalDirection::Input,
805 VariableKind::Output => SignalDirection::Output,
806 VariableKind::Inout => SignalDirection::Inout,
807 _ => SignalDirection::Internal,
808 };
809 signals.push(ReflectionSignal {
810 full_name: format!("{}.{}", scope.full_name, name),
811 name,
812 parent: instance_scopes[&scope.instance_id],
813 state_address: *state_address,
814 signal: SignalRef {
815 offset: layout.offsets[state_address],
816 width: layout.widths[state_address],
817 is_4state: layout.is_4states[state_address],
818 array_layout,
819 },
820 direction,
821 domain_kind: metadata.kind,
822 signed: variable.signed,
823 packed_dims: variable.packed_dims.clone(),
824 unpacked_dims: metadata.array_dims.clone(),
825 type_kind: metadata.type_kind,
826 });
827 }
828 }
829 signals.sort_by(|left, right| left.full_name.cmp(&right.full_name));
830 for (index, signal) in signals.iter().enumerate() {
831 scopes[signal.parent.0 as usize]
832 .signals
833 .push(ReflectionSignalId(
834 u32::try_from(index).expect("signal count exceeds u32"),
835 ));
836 }
837 let reflection = DesignReflection::new(scopes, signals);
838 debug_assert!(reflection.validate().is_ok());
839 reflection
840 }
841
842 pub(crate) fn from_scheduled(
843 scheduled: celox_frontend_core::ScheduledRtl,
844 ) -> Result<(SirProgram, Self), DesignProjectionError> {
845 let design = RuntimeDesign::from_projection(scheduled.design, scheduled.frontend_lookup)?;
846 Ok((
847 scheduled.sir,
848 Self {
849 design,
850 runtime_schema: scheduled.runtime_schema,
851 testbench: None,
852 },
853 ))
854 }
855
856 pub fn get_addr(
857 &self,
858 instance_path: &[(&str, usize)],
859 var_path: &[&str],
860 ) -> Result<AbsoluteAddr, AddrLookupError> {
861 let instance_path: Vec<(String, usize)> = instance_path
862 .iter()
863 .map(|(name, index)| ((*name).to_string(), *index))
864 .collect();
865 let instance = self
866 .design
867 .instance_at_path(&InstancePath(instance_path.clone()))
868 .ok_or_else(|| AddrLookupError::InstanceNotFound {
869 path: instance_path
870 .iter()
871 .map(|(s, i)| format!("{}[{}]", s, i))
872 .collect::<Vec<_>>()
873 .join("."),
874 })?;
875 let target_path = var_path
876 .iter()
877 .map(|segment| (*segment).to_string())
878 .collect::<Vec<_>>();
879 let path_str = var_path.join(".");
880 let entry = instance.path_index.get(&target_path).ok_or_else(|| {
881 AddrLookupError::VariableNotFound {
882 path: path_str.clone(),
883 }
884 })?;
885 entry
886 .as_ref()
887 .copied()
888 .ok_or(AddrLookupError::AmbiguousPath { path: path_str })
889 }
890
891 pub fn get_path(&self, addr: &AbsoluteAddr) -> String {
892 self.design.get_path(addr)
893 }
894
895 pub fn get_variable_info(&self, addr: &AbsoluteAddr) -> Option<VariableInfo> {
896 self.design.variable_info(addr)
897 }
898
899 pub fn num_events(&self) -> usize {
900 self.design.events.len()
901 }
902}
903
904impl OptimizedSir {
905 pub fn collect_working_region_addrs(&self) -> crate::HashSet<AbsoluteAddr> {
909 let mut addrs = crate::HashSet::default();
910
911 let scan_units =
912 |units: &HashMap<AbsoluteAddr, Vec<ExecutionUnit<RegionedAbsoluteAddr>>>,
913 addrs: &mut crate::HashSet<AbsoluteAddr>| {
914 for eu_list in units.values() {
915 for eu in eu_list {
916 for block in eu.blocks.values() {
917 for inst in &block.instructions {
918 match inst {
919 SIRInstruction::Store(addr, _, _, _, _, _)
920 if addr.region == WORKING_REGION =>
921 {
922 addrs.insert(addr.absolute_addr());
923 }
924 SIRInstruction::Commit(src, dst, _, _, _) => {
925 if src.region == WORKING_REGION {
926 addrs.insert(src.absolute_addr());
927 }
928 if dst.region == WORKING_REGION {
929 addrs.insert(dst.absolute_addr());
930 }
931 }
932 _ => {}
933 }
934 }
935 }
936 }
937 }
938 };
939
940 scan_units(&self.sir.eval_apply_ffs, &mut addrs);
941 scan_units(&self.sir.eval_comb_apply_ffs, &mut addrs);
942 scan_units(&self.sir.eval_only_ffs, &mut addrs);
943 scan_units(&self.sir.apply_ffs, &mut addrs);
944
945 addrs
946 }
947
948 pub fn collect_sparse_working_region_addrs(&self) -> crate::HashSet<AbsoluteAddr> {
949 let mut addrs = crate::HashSet::default();
950 for units in self
951 .sir
952 .eval_apply_ffs
953 .values()
954 .chain(self.sir.eval_comb_apply_ffs.values())
955 .chain(self.sir.eval_only_ffs.values())
956 {
957 for eu in units {
958 for block in eu.blocks.values() {
959 for inst in &block.instructions {
960 if let SIRInstruction::Store(addr, _, _, _, _, _) = inst
961 && addr.region == SPARSE_WORKING_REGION
962 {
963 addrs.insert(addr.absolute_addr());
964 }
965 }
966 }
967 }
968 }
969 addrs
970 }
971}
972
973fn comb_capture_enable_needs_unaliased_old_value(
974 units: &[ExecutionUnit<RegionedAbsoluteAddr>],
975 alias_addr: AbsoluteAddr,
976) -> bool {
977 for eu in units {
978 for block in eu.blocks.values() {
979 let mut last_store = None;
980 for inst in &block.instructions {
981 match inst {
982 SIRInstruction::Store(addr, _, _, _, _, comb_capture_sites) => {
983 let abs = addr.absolute_addr();
984 if abs == alias_addr && !comb_capture_sites.is_empty() {
985 return true;
986 }
987 last_store = Some(abs);
988 }
989 SIRInstruction::CombCaptureEnableIfChanged { sites, .. } => {
990 if !sites.is_empty() && last_store == Some(alias_addr) {
991 return true;
992 }
993 last_store = None;
994 }
995 _ => {
996 last_store = None;
997 }
998 }
999 }
1000 }
1001 }
1002 false
1003}
1004
1005pub(crate) mod verify {
1006 pub(crate) use celox_sir::verify::*;
1007}
1008pub use celox_slt::{GlueAddrBase, GlueBlockBase};
1009
1010pub use celox_frontend_core::TraceSimModule as SimModule;
1011#[cfg(all(
1012 feature = "host-runtime",
1013 any(
1014 target_arch = "x86_64",
1015 feature = "arm64-codegen",
1016 target_arch = "aarch64"
1017 )
1018))]
1019pub(crate) use celox_runtime::SignalArrayLayout;
1020pub use celox_runtime::SignalRef;
1021
1022#[cfg(test)]
1023mod tests {
1024 use super::*;
1025
1026 #[test]
1027 fn exact_zero_analysis_collapses_repeated_concat_dependencies() {
1028 let zero = RegisterId(0);
1029 let wide_zero = RegisterId(1);
1030 let sliced_zero = RegisterId(2);
1031 let nonzero = RegisterId(3);
1032 let mixed = RegisterId(4);
1033 let eu: ExecutionUnit<()> = ExecutionUnit {
1034 entry_block_id: BlockId(0),
1035 blocks: [(
1036 BlockId(0),
1037 BasicBlock {
1038 id: BlockId(0),
1039 params: vec![],
1040 instructions: vec![
1041 SIRInstruction::Imm(zero, SIRValue::new(0u8)),
1042 SIRInstruction::Concat(wide_zero, vec![zero; 4096]),
1043 SIRInstruction::Slice(sliced_zero, wide_zero, 0, 64),
1044 SIRInstruction::Imm(nonzero, SIRValue::new(1u8)),
1045 SIRInstruction::Concat(mixed, vec![zero, nonzero]),
1046 ],
1047 terminator: SIRTerminator::Return,
1048 },
1049 )]
1050 .into_iter()
1051 .collect(),
1052 register_map: HashMap::default(),
1053 };
1054
1055 let zeros = collect_exact_zero_registers(&eu, [sliced_zero, mixed]);
1056 assert!(zeros.contains(&zero));
1057 assert!(zeros.contains(&wide_zero));
1058 assert!(zeros.contains(&sliced_zero));
1059 assert!(!zeros.contains(&nonzero));
1060 assert!(!zeros.contains(&mixed));
1061 }
1062
1063 #[test]
1064 fn test_sirvalue_display() {
1065 let val = SIRValue::new(42u64);
1066 let display = format!("{}", val);
1067 assert!(display.contains("SIRValue"));
1068 assert!(display.contains("0x2a")); }
1070
1071 #[test]
1072 fn test_absoluteaddr_display() {
1073 let addr = AbsoluteAddr {
1074 instance_id: InstanceId(0),
1075 var_id: celox_design::StateObjectId(0),
1076 };
1077 let display = format!("{}", addr);
1078 assert!(display.contains("AbsoluteAddr"));
1079 assert!(display.contains("inst0"));
1080 assert!(display.contains("state0"));
1081 }
1082
1083 #[test]
1084 fn test_glueaddr_display() {
1085 let parent_addr =
1086 celox_frontend_veryl::GlueAddr::Parent(veryl_analyzer::ir::VarId::default());
1087 let parent_display = format!("{}", parent_addr);
1088 assert!(parent_display.contains("GlueAddr::Parent"));
1089 assert!(parent_display.contains("var0"));
1090
1091 let child_addr =
1092 celox_frontend_veryl::GlueAddr::Child(veryl_analyzer::ir::VarId::default());
1093 let child_display = format!("{}", child_addr);
1094 assert!(child_display.contains("GlueAddr::Child"));
1095 assert!(child_display.contains("var0"));
1096 }
1097
1098 #[test]
1099 fn test_instanceid_display() {
1100 let id = InstanceId(42);
1101 let display = format!("{}", id);
1102 assert_eq!(display, "inst42");
1103 }
1104
1105 #[test]
1106 fn test_binaryop_display() {
1107 assert_eq!(format!("{}", BinaryOp::Add), "Add");
1108 assert_eq!(format!("{}", BinaryOp::Sub), "Sub");
1109 assert_eq!(format!("{}", BinaryOp::Mul), "Mul");
1110 assert_eq!(format!("{}", BinaryOp::Xor), "Xor");
1111 }
1112
1113 #[test]
1114 fn test_unaryop_display() {
1115 assert_eq!(format!("{}", UnaryOp::Minus), "Minus");
1116 assert_eq!(format!("{}", UnaryOp::LogicNot), "LogicNot");
1117 assert_eq!(format!("{}", UnaryOp::BitNot), "BitNot");
1118 assert_eq!(format!("{}", UnaryOp::PopCount), "PopCount");
1119 assert_eq!(
1120 format!("{}", UnaryOp::CountLeadingZeros),
1121 "CountLeadingZeros"
1122 );
1123 assert_eq!(
1124 format!("{}", UnaryOp::CountTrailingZeros),
1125 "CountTrailingZeros"
1126 );
1127 }
1128
1129 #[test]
1130 fn bit_count_result_width_represents_operand_width() {
1131 for (operand_width, expected) in [
1132 (0, 0),
1133 (1, 1),
1134 (2, 2),
1135 (3, 2),
1136 (8, 4),
1137 (usize::MAX, usize::BITS as usize),
1138 ] {
1139 for op in [
1140 UnaryOp::PopCount,
1141 UnaryOp::CountLeadingZeros,
1142 UnaryOp::CountTrailingZeros,
1143 ] {
1144 assert_eq!(op.result_width(operand_width), expected, "{op}");
1145 }
1146 }
1147 }
1148
1149 #[test]
1150 fn bit_count_unary_ops_roundtrip_through_serde() {
1151 for op in [
1152 UnaryOp::PopCount,
1153 UnaryOp::CountLeadingZeros,
1154 UnaryOp::CountTrailingZeros,
1155 ] {
1156 let encoded = serde_json::to_string(&op).unwrap();
1157 let decoded: UnaryOp = serde_json::from_str(&encoded).unwrap();
1158 assert_eq!(decoded, op);
1159 }
1160 }
1161
1162 #[test]
1163 fn test_sirinstruction_display() {
1164 let imm: SIRInstruction<i32> = SIRInstruction::Imm(RegisterId(0), SIRValue::new(42u64));
1166 let imm_display = format!("{}", imm);
1167 assert!(imm_display.contains("r0"));
1168 assert!(imm_display.contains("SIRValue"));
1169
1170 let binary: SIRInstruction<i32> =
1172 SIRInstruction::Binary(RegisterId(0), RegisterId(1), BinaryOp::Add, RegisterId(2));
1173 let binary_display = format!("{}", binary);
1174 assert!(binary_display.contains("r0"));
1175 assert!(binary_display.contains("r1"));
1176 assert!(binary_display.contains("r2"));
1177 assert!(binary_display.contains("Add"));
1178
1179 let unary: SIRInstruction<i32> =
1181 SIRInstruction::Unary(RegisterId(0), UnaryOp::Minus, RegisterId(1));
1182 let unary_display = format!("{}", unary);
1183 assert!(unary_display.contains("r0"));
1184 assert!(unary_display.contains("r1"));
1185 assert!(unary_display.contains("Minus"));
1186 }
1187
1188 #[test]
1189 fn test_sirterminator_display() {
1190 let jump = SIRTerminator::Jump(BlockId(1), vec![RegisterId(0), RegisterId(1)]);
1192 let jump_display = format!("{}", jump);
1193 assert!(jump_display.contains("Jump"));
1194 assert!(jump_display.contains("b1"));
1195
1196 let ret = SIRTerminator::Return;
1198 let ret_display = format!("{}", ret);
1199 assert_eq!(ret_display, "Return");
1200
1201 let branch = SIRTerminator::Branch {
1203 cond: RegisterId(0),
1204 true_block: (BlockId(1), vec![]),
1205 false_block: (BlockId(2), vec![]),
1206 };
1207 let branch_display = format!("{}", branch);
1208 assert!(branch_display.contains("Branch"));
1209 assert!(branch_display.contains("b1"));
1210 assert!(branch_display.contains("b2"));
1211 }
1212
1213 #[test]
1214 fn test_basicblock_display() {
1215 let _block: BasicBlock<i32> = BasicBlock {
1216 id: BlockId(0),
1217 params: vec![RegisterId(0), RegisterId(1)],
1218 instructions: vec![
1219 SIRInstruction::Imm(RegisterId(2), SIRValue::new(42u64)),
1220 SIRInstruction::Binary(RegisterId(3), RegisterId(0), BinaryOp::Add, RegisterId(2)),
1221 ],
1222 terminator: SIRTerminator::Return,
1223 };
1224
1225 let block_display = format!("{}", _block);
1226 assert!(block_display.contains("b0:"));
1227 assert!(block_display.contains("params:"));
1228 assert!(block_display.contains("r0"));
1229 assert!(block_display.contains("r1"));
1230 assert!(block_display.contains("Add"));
1231 assert!(block_display.contains("Return"));
1232 }
1233
1234 #[test]
1235 fn single_predecessor_inlining_rewrites_dominated_parameter_uses() {
1236 let mut eu: ExecutionUnit<()> = ExecutionUnit {
1237 entry_block_id: BlockId(0),
1238 blocks: [
1239 BasicBlock {
1240 id: BlockId(0),
1241 params: vec![RegisterId(0)],
1242 instructions: Vec::new(),
1243 terminator: SIRTerminator::Jump(BlockId(1), vec![RegisterId(0)]),
1244 },
1245 BasicBlock {
1246 id: BlockId(1),
1247 params: vec![RegisterId(1)],
1248 instructions: Vec::new(),
1249 terminator: SIRTerminator::Jump(BlockId(2), Vec::new()),
1250 },
1251 BasicBlock {
1252 id: BlockId(2),
1253 params: Vec::new(),
1254 instructions: vec![SIRInstruction::Unary(
1255 RegisterId(2),
1256 UnaryOp::Ident,
1257 RegisterId(1),
1258 )],
1259 terminator: SIRTerminator::Return,
1260 },
1261 ]
1262 .into_iter()
1263 .map(|block| (block.id, block))
1264 .collect(),
1265 register_map: (0..3)
1266 .map(|register| {
1267 (
1268 RegisterId(register),
1269 RegisterType::Bit {
1270 width: 8,
1271 signed: false,
1272 },
1273 )
1274 })
1275 .collect(),
1276 };
1277 eu.verify_result().unwrap();
1278
1279 assert!(inline_single_predecessor_jumps(&mut eu).unwrap());
1280 eu.verify_result().unwrap();
1281 assert_eq!(eu.blocks.len(), 1);
1282 assert!(matches!(
1283 eu.blocks[&BlockId(0)].instructions.as_slice(),
1284 [SIRInstruction::Unary(
1285 RegisterId(2),
1286 UnaryOp::Ident,
1287 RegisterId(0)
1288 )]
1289 ));
1290 }
1291
1292 #[test]
1293 fn single_predecessor_inlining_handles_deep_linear_cfg() {
1294 const BLOCK_COUNT: usize = 20_000;
1295
1296 let mut eu: ExecutionUnit<()> = ExecutionUnit {
1297 entry_block_id: BlockId(0),
1298 blocks: (0..BLOCK_COUNT)
1299 .map(|index| {
1300 let id = BlockId(index);
1301 let terminator = if index + 1 == BLOCK_COUNT {
1302 SIRTerminator::Return
1303 } else {
1304 SIRTerminator::Jump(BlockId(index + 1), Vec::new())
1305 };
1306 (
1307 id,
1308 BasicBlock {
1309 id,
1310 params: Vec::new(),
1311 instructions: Vec::new(),
1312 terminator,
1313 },
1314 )
1315 })
1316 .collect(),
1317 register_map: crate::HashMap::default(),
1318 };
1319 eu.verify_result().unwrap();
1320
1321 assert!(inline_single_predecessor_jumps(&mut eu).unwrap());
1322 assert_eq!(eu.blocks.len(), 1);
1323 assert_eq!(eu.blocks[&BlockId(0)].terminator, SIRTerminator::Return);
1324 eu.verify_result().unwrap();
1325 }
1326}