1use std::path::{Path, PathBuf};
10
11use celox_design::{
12 BinaryOp, BitAccess, DomainKind, InitialStateData, InitialStateValue, ModuleId, PortTypeKind,
13 RegionedVarAddrBase, RuntimeErrorInfo, STABLE_REGION, TriggerSet, UnaryOp, VarAtomBase,
14 WORKING_REGION,
15};
16use celox_frontend_core::symbolic::artifact::{
17 ExternalHierarchy, ExternalModule, SimModule, SymbolicGlueAddr as GlueAddr, SymbolicRtl,
18 SymbolicVariable,
19};
20use celox_frontend_core::{
21 FrontendTrace, FrontendTraceOptions, LoweringPhase, ParserError, ScheduledRtlOutput,
22 SourceLocation, SourceVarId, VariableKind, symbolic::width::coerce_node_width,
23};
24use celox_sir::{
25 BlockId, ExecutionUnit, RegisterType, SIRBuilder, SIRInstruction, SIROffset, SIRTerminator,
26 SIRValue, merge_sir_eus,
27};
28use celox_slt::{
29 CombObserver, GlueBlockBase, LogicPath, LogicPathTarget, NodeId, SLTIndex, SLTIndexKind,
30 SLTNode, SLTNodeArena,
31};
32use celox_sv_analyzer as sv;
33use fxhash::{FxHashMap as HashMap, FxHashSet as HashSet};
34use num_bigint::BigUint;
35
36type RegionedVarAddr = RegionedVarAddrBase<SourceVarId>;
37type GlueBlock = GlueBlockBase<SourceVarId>;
38const MAX_SV_SPECIALIZATIONS_PER_MODULE: usize = 64;
39
40#[derive(Debug, thiserror::Error)]
41pub enum FrontendError {
42 #[error(transparent)]
43 Analyzer(#[from] sv::AnalyzerError),
44 #[error(transparent)]
45 Lowering(#[from] ParserError),
46}
47
48#[derive(Clone)]
49struct SvVariable {
50 path: Vec<String>,
51 width: usize,
52 signed: bool,
53 is_4state: bool,
54 is_net: bool,
55 packed_ranges: Vec<(i128, i128)>,
56 array_dims: Vec<usize>,
57 domain_kind: DomainKind,
58 kind: VariableKind,
59 type_kind: PortTypeKind,
60 source: Option<SourceLocation>,
61}
62
63impl SvVariable {
64 fn to_symbolic_variable(&self) -> SymbolicVariable {
65 SymbolicVariable {
66 path: self.path.clone(),
67 kind: self.kind,
68 signed: self.signed,
69 metadata: celox_design::VariableMetadata {
70 width: self.width,
71 is_4state: self.is_4state,
72 kind: self.domain_kind,
73 type_kind: self.type_kind,
74 array_dims: self.array_dims.clone(),
75 },
76 packed_dims: self
77 .packed_ranges
78 .iter()
79 .map(|(left, right)| left.abs_diff(*right) as usize + 1)
80 .collect(),
81 source: self.source.clone(),
82 module_affiliated: true,
83 }
84 }
85}
86
87#[derive(Clone)]
88pub(crate) struct LoweredSvModule {
89 source: sv::ir::Module,
90 implicit_nets_allowed: bool,
91 pub sim_module: SimModule,
92 variables: HashMap<SourceVarId, SvVariable>,
93 pub port_order: Vec<SourceVarId>,
94 pub signal_names: HashMap<String, SourceVarId>,
95 constants: HashMap<String, i128>,
96 parameter_types: HashMap<String, (usize, bool)>,
97 pub instances: Vec<LoweredSvInstance>,
98}
99
100#[derive(Clone)]
101struct AnalyzedSvModule {
102 name: String,
103 source_code: String,
104 source_path: PathBuf,
105 implicit_nets_allowed: bool,
106}
107
108#[derive(Clone)]
109pub(crate) struct LoweredSvInstance {
110 pub module_name: String,
111 pub instance_name: String,
112 pub parameter_overrides: Vec<LoweredSvParameterOverride>,
113 pub port_connections: Vec<LoweredSvPortConnection>,
114}
115
116#[derive(Clone, Debug, PartialEq, Eq, Hash)]
117pub(crate) struct LoweredSvParameterOverride {
118 pub name: String,
119 pub value: Option<sv::ir::ConstExpr>,
120}
121
122#[derive(Clone)]
123pub(crate) struct LoweredSvPortConnection {
124 pub formal: String,
125 pub actual: String,
126 pub actual_expr: Option<sv::ir::Expr>,
127}
128
129#[derive(Clone, Debug, PartialEq, Eq, Hash)]
130pub(crate) struct LoweredSvModuleKey {
131 pub name: String,
132 pub parameter_overrides: Vec<LoweredSvParameterOverride>,
133}
134
135impl LoweredSvModuleKey {
136 pub fn base(name: String) -> Self {
137 Self {
138 name,
139 parameter_overrides: Vec::new(),
140 }
141 }
142
143 pub fn instance_key(instance: &LoweredSvInstance) -> Self {
144 let mut parameter_overrides = instance.parameter_overrides.clone();
145 parameter_overrides.sort_by(|left, right| left.name.cmp(&right.name));
146 Self {
147 name: instance.module_name.clone(),
148 parameter_overrides,
149 }
150 }
151}
152
153fn analyze_sources(
154 sources: &[(&str, &Path)],
155) -> Result<HashMap<String, AnalyzedSvModule>, sv::AnalyzerError> {
156 let mut modules = HashMap::default();
157 for (code, path) in sources {
158 let implicit_net_permissions = sv::source_module_implicit_net_permissions(code, path)?;
159 for module_name in sv::source_module_names(code, path)? {
160 let name = module_name.clone();
161 if modules.contains_key(&name) {
162 return Err(sv::AnalyzerError::DuplicateModule { name: module_name });
163 }
164 modules.insert(
165 name,
166 AnalyzedSvModule {
167 implicit_nets_allowed: implicit_net_permissions
168 .iter()
169 .find_map(|(name, allowed)| (name == &module_name).then_some(*allowed))
170 .unwrap_or(true),
171 name: module_name,
172 source_code: (*code).to_string(),
173 source_path: (*path).to_path_buf(),
174 },
175 );
176 }
177 }
178 Ok(modules)
179}
180
181fn validate_specialized_instance_net_drivers(
182 module_ids: &HashMap<LoweredSvModuleKey, ModuleId>,
183 modules: &HashMap<ModuleId, LoweredSvModule>,
184) -> Result<(), sv::AnalyzerError> {
185 for module in modules.values() {
186 for port in module
187 .source
188 .ports()
189 .iter()
190 .filter(|port| port.direction() == sv::ir::PortDirection::Input)
191 {
192 if !child_output_driver_ranges(module, port.name(), module_ids, modules).is_empty() {
193 return Err(sv::AnalyzerError::Unsupported(format!(
194 "write to input port `{}`",
195 port.name()
196 )));
197 }
198 }
199
200 let net_names = module
201 .source
202 .signals()
203 .iter()
204 .filter(|signal| signal.is_net())
205 .map(|signal| (signal.name(), true))
206 .chain(
207 module
208 .source
209 .ports()
210 .iter()
211 .filter(|port| port.is_net())
212 .map(|port| (port.name(), false)),
213 );
214 for (signal_name, require_driver) in net_names {
215 let child_driver_ranges =
216 child_output_driver_ranges(module, signal_name, module_ids, modules);
217 validate_net_driver_ranges(module, signal_name, &child_driver_ranges, require_driver)?;
218 }
219
220 let variable_names = module
221 .source
222 .signals()
223 .iter()
224 .filter(|signal| !signal.is_net())
225 .map(|signal| signal.name())
226 .chain(
227 module
228 .source
229 .ports()
230 .iter()
231 .filter(|port| !port.is_net())
232 .map(|port| port.name()),
233 );
234 for signal_name in variable_names {
235 let child_driver_ranges =
236 child_output_driver_ranges(module, signal_name, module_ids, modules);
237 let local_drivers = local_driver_ranges(
238 &module.source,
239 signal_name,
240 &module.constants,
241 &module.parameter_types,
242 );
243 let child_overlaps = driver_ranges_overlap(&child_driver_ranges);
244 let child_local_overlap = child_driver_ranges.iter().any(|(_, child_range)| {
245 local_drivers
246 .iter()
247 .any(|(_, local_range)| net_driver_ranges_overlap(*child_range, *local_range))
248 });
249 if child_overlaps || child_local_overlap {
250 return Err(sv::AnalyzerError::Unsupported(format!(
251 "multiple variable drivers for `{signal_name}`"
252 )));
253 }
254 }
255 }
256 Ok(())
257}
258
259fn child_output_driver_ranges(
260 module: &LoweredSvModule,
261 signal_name: &str,
262 module_ids: &HashMap<LoweredSvModuleKey, ModuleId>,
263 modules: &HashMap<ModuleId, LoweredSvModule>,
264) -> Vec<(usize, Option<(i128, i128)>)> {
265 let Some(signal_id) = module.signal_names.get(signal_name).copied() else {
266 return Vec::new();
267 };
268 let mut drivers = Vec::new();
269 for instance in &module.instances {
270 let key = LoweredSvModuleKey::instance_key(instance);
271 let Some(child_id) = module_ids.get(&key).copied() else {
272 continue;
273 };
274 let Some(child) = modules.get(&child_id) else {
275 continue;
276 };
277 for connection in &instance.port_connections {
278 if !child.source.ports().iter().any(|port| {
279 port.name() == connection.formal
280 && matches!(
281 port.direction(),
282 sv::ir::PortDirection::Output | sv::ir::PortDirection::Inout
283 )
284 }) {
285 continue;
286 }
287 let Some(actual_expr) = connection.actual_expr.as_ref() else {
288 continue;
289 };
290 let Some(accesses) = output_lvalue_accesses(
291 actual_expr,
292 &module.variables,
293 &module.signal_names,
294 &module.constants,
295 &module.parameter_types,
296 ) else {
297 if output_connection_targets_signal(actual_expr, signal_name) {
298 drivers.push((drivers.len(), None));
299 }
300 continue;
301 };
302 for (actual_id, access) in accesses {
303 if actual_id == signal_id {
304 drivers.push((
305 drivers.len(),
306 Some((access.lsb as i128, access.msb as i128)),
307 ));
308 }
309 }
310 }
311 }
312 drivers
313}
314
315fn output_connection_targets_signal(expr: &sv::ir::Expr, signal_name: &str) -> bool {
316 match expr {
317 sv::ir::Expr::Ident(name) => name == signal_name,
318 sv::ir::Expr::Select { expr, .. } | sv::ir::Expr::Resize { expr, .. } => {
319 output_connection_targets_signal(expr, signal_name)
320 }
321 sv::ir::Expr::Concat(parts) => parts
322 .iter()
323 .any(|part| output_connection_targets_signal(part, signal_name)),
324 _ => false,
325 }
326}
327
328fn validate_net_driver_ranges(
329 module: &LoweredSvModule,
330 signal_name: &str,
331 child_driver_ranges: &[(usize, Option<(i128, i128)>)],
332 require_driver: bool,
333) -> Result<(), sv::AnalyzerError> {
334 let local_drivers = local_driver_ranges(
335 &module.source,
336 signal_name,
337 &module.constants,
338 &module.parameter_types,
339 );
340 let overlapping_local_drivers = local_drivers.iter().enumerate().any(|(index, left)| {
341 local_drivers[index + 1..]
342 .iter()
343 .any(|right| left.0 != right.0 && net_driver_ranges_overlap(left.1, right.1))
344 });
345 let child_local_overlap = child_driver_ranges.iter().any(|(_, child_range)| {
346 local_drivers
347 .iter()
348 .any(|(_, local_range)| net_driver_ranges_overlap(*child_range, *local_range))
349 });
350 if driver_ranges_overlap(child_driver_ranges)
351 || child_local_overlap
352 || overlapping_local_drivers
353 {
354 return Err(sv::AnalyzerError::Unsupported(format!(
355 "multiple net drivers for `{signal_name}`"
356 )));
357 }
358 if require_driver && child_driver_ranges.is_empty() && local_drivers.is_empty() {
359 return Err(sv::AnalyzerError::Unsupported(format!(
360 "undriven net declaration `{signal_name}`"
361 )));
362 }
363 Ok(())
364}
365
366fn driver_ranges_overlap(drivers: &[(usize, Option<(i128, i128)>)]) -> bool {
367 drivers.iter().enumerate().any(|(index, left)| {
368 drivers[index + 1..]
369 .iter()
370 .any(|right| net_driver_ranges_overlap(left.1, right.1))
371 })
372}
373
374fn validate_variable_driver_ranges(
375 module: &sv::ir::Module,
376 constants: &HashMap<String, i128>,
377 parameter_types: &HashMap<String, (usize, bool)>,
378) -> Result<(), sv::AnalyzerError> {
379 for port in module
380 .ports()
381 .iter()
382 .filter(|port| port.direction() == sv::ir::PortDirection::Input)
383 {
384 if !local_driver_ranges(module, port.name(), constants, parameter_types).is_empty() {
385 return Err(sv::AnalyzerError::Unsupported(format!(
386 "write to input port `{}`",
387 port.name()
388 )));
389 }
390 }
391
392 let variable_names = module
393 .signals()
394 .iter()
395 .filter(|signal| !signal.is_net())
396 .map(|signal| signal.name())
397 .chain(
398 module
399 .ports()
400 .iter()
401 .filter(|port| !port.is_net())
402 .map(|port| port.name()),
403 );
404 for signal_name in variable_names {
405 let drivers = local_driver_ranges(module, signal_name, constants, parameter_types);
406 let has_overlap = drivers.iter().enumerate().any(|(index, left)| {
407 drivers[index + 1..]
408 .iter()
409 .any(|right| left.0 != right.0 && net_driver_ranges_overlap(left.1, right.1))
410 });
411 if has_overlap {
412 return Err(sv::AnalyzerError::Unsupported(format!(
413 "multiple variable drivers for `{signal_name}`"
414 )));
415 }
416 }
417 Ok(())
418}
419
420fn local_driver_ranges(
421 module: &sv::ir::Module,
422 signal_name: &str,
423 constants: &HashMap<String, i128>,
424 parameter_types: &HashMap<String, (usize, bool)>,
425) -> Vec<(usize, Option<(i128, i128)>)> {
426 let mut drivers = Vec::new();
427 let mut driver_id = 0;
428 for process in module.comb_processes() {
429 let active = process.condition().is_none_or(|condition| {
430 sv::typecheck::eval_const_expr_with_types(condition, constants, parameter_types)
431 .is_none_or(|value| value != 0)
432 });
433 if active {
434 for assignment in process.assignments() {
435 if assignment.lhs() == signal_name {
436 drivers.push((
437 driver_id,
438 net_lvalue_range(assignment.lhs_value(), constants, parameter_types),
439 ));
440 }
441 if process.kind() == sv::ir::CombProcessKind::ContinuousAssign {
442 driver_id += 1;
443 }
444 }
445 if process.kind() == sv::ir::CombProcessKind::AlwaysComb {
446 driver_id += 1;
447 }
448 } else {
449 driver_id += 1;
450 }
451 }
452 for process in module.ff_processes() {
453 drivers.extend(
454 process
455 .assignments()
456 .iter()
457 .map(|assignment| assignment.assignment())
458 .filter(|assignment| assignment.lhs() == signal_name)
459 .map(|assignment| {
460 (
461 driver_id,
462 net_lvalue_range(assignment.lhs_value(), constants, parameter_types),
463 )
464 }),
465 );
466 driver_id += 1;
467 }
468 drivers
469}
470
471fn net_lvalue_range(
472 lvalue: &sv::ir::LValue,
473 constants: &HashMap<String, i128>,
474 parameter_types: &HashMap<String, (usize, bool)>,
475) -> Option<(i128, i128)> {
476 let sv::ir::LValue::Select { msb, lsb, .. } = lvalue else {
477 return None;
478 };
479 let msb = sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)?;
480 let lsb = sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types)?;
481 Some((msb.min(lsb), msb.max(lsb)))
482}
483
484fn net_driver_ranges_overlap(left: Option<(i128, i128)>, right: Option<(i128, i128)>) -> bool {
485 match (left, right) {
486 (Some((left_start, left_end)), Some((right_start, right_end))) => {
487 left_start <= right_end && right_start <= left_end
488 }
489 _ => true,
490 }
491}
492
493pub fn prepare_external_hierarchy(
497 sources: &[(&str, &Path)],
498 root_names: &HashSet<String>,
499 four_state: bool,
500) -> Result<ExternalHierarchy, FrontendError> {
501 let analyzed = analyze_sources(sources)?;
502 let mut names = root_names
503 .iter()
504 .filter(|&name| analyzed.contains_key(name))
505 .cloned()
506 .collect::<Vec<_>>();
507 names.sort();
508
509 let mut module_ids = HashMap::default();
510 let mut module_specialization_counts = HashMap::default();
511 let mut queue = Vec::new();
512 for name in names {
513 let key = LoweredSvModuleKey::base(name.clone());
514 let module_id = ModuleId(module_ids.len());
515 module_ids.insert(key.clone(), module_id);
516 module_specialization_counts.insert(name.clone(), 1usize);
517 queue.push(key);
518 }
519
520 let mut index = 0;
521 while index < queue.len() {
522 let key = queue[index].clone();
523 index += 1;
524 let base = analyzed
525 .get(&key.name)
526 .ok_or_else(|| unsupported_sv_instance(key.name.clone()))?;
527 let lowered = specialize_module(base, &key, four_state)?;
528 for instance in &lowered.instances {
529 let child_key = LoweredSvModuleKey::instance_key(instance);
530 if !analyzed.contains_key(&child_key.name) {
531 continue;
532 }
533 if !module_ids.contains_key(&child_key) {
534 let specialization_count = module_specialization_counts
535 .entry(child_key.name.clone())
536 .or_insert(0);
537 if *specialization_count >= MAX_SV_SPECIALIZATIONS_PER_MODULE {
538 return Err(sv_specialization_limit_error(child_key.name.clone()).into());
539 }
540 *specialization_count += 1;
541 let child_id = ModuleId(module_ids.len());
542 module_ids.insert(child_key.clone(), child_id);
543 queue.push(child_key);
544 }
545 }
546 }
547
548 let lowered_modules = module_ids
549 .iter()
550 .map(|(key, &module_id)| {
551 let base = analyzed
552 .get(&key.name)
553 .ok_or_else(|| unsupported_sv_instance(key.name.clone()))?;
554 Ok((module_id, specialize_module(base, key, four_state)?))
555 })
556 .collect::<Result<HashMap<_, _>, FrontendError>>()?;
557 validate_specialized_instance_net_drivers(&module_ids, &lowered_modules)?;
558 let mut modules = HashMap::default();
559 for (key, &module_id) in &module_ids {
560 let lowered = &lowered_modules[&module_id];
561 let mut sim_module = lowered.sim_module.clone();
562 let unresolved_instances: Vec<String> = lowered
563 .instances
564 .iter()
565 .filter_map(|instance| {
566 (!module_ids.contains_key(&LoweredSvModuleKey::instance_key(instance)))
567 .then_some(instance.module_name.clone())
568 })
569 .collect();
570 let mut resolved = lowered.clone();
571 resolved.instances.retain(|instance| {
572 module_ids.contains_key(&LoweredSvModuleKey::instance_key(instance))
573 });
574 attach_instance_glue(
575 &mut sim_module,
576 &resolved,
577 key,
578 &module_ids,
579 &lowered_modules,
580 four_state,
581 )?;
582 modules.insert(
583 module_id,
584 ExternalModule {
585 sim_module,
586 port_order: lowered.port_order.clone(),
587 unresolved_instances,
588 },
589 );
590 }
591 let roots = module_ids
592 .iter()
593 .filter(|(key, _)| key.parameter_overrides.is_empty())
594 .map(|(key, &module_id)| (key.name.clone(), module_id))
595 .collect();
596 Ok(ExternalHierarchy { modules, roots })
597}
598
599pub fn schedule_sources(
602 sources: &[(&str, &Path)],
603 top: &str,
604 parameter_overrides: &[(String, u64)],
605 ignored_loops: &[(
606 (Vec<(String, usize)>, Vec<String>),
607 (Vec<(String, usize)>, Vec<String>),
608 )],
609 true_loops: &[(
610 (Vec<(String, usize)>, Vec<String>),
611 (Vec<(String, usize)>, Vec<String>),
612 usize,
613 )],
614 four_state: bool,
615 trace_options: &FrontendTraceOptions,
616 trace: Option<&mut FrontendTrace>,
617) -> Result<ScheduledRtlOutput, FrontendError> {
618 let analyzed = analyze_sources(sources)?;
619 let top = top.to_string();
620 let root_key = LoweredSvModuleKey {
621 name: top.clone(),
622 parameter_overrides: parameter_overrides
623 .iter()
624 .map(|(name, value)| LoweredSvParameterOverride {
625 name: name.clone(),
626 value: Some(sv::ir::ConstExpr::Literal(value.to_string())),
627 })
628 .collect(),
629 };
630 if !analyzed.contains_key(&top) {
631 return Err(sv_top_not_found(top).into());
632 }
633
634 let root_id = ModuleId(0);
635 let mut module_ids = HashMap::default();
636 module_ids.insert(root_key.clone(), root_id);
637 let mut module_specialization_counts = HashMap::default();
638 module_specialization_counts.insert(root_key.name.clone(), 1usize);
639 let mut queue = vec![root_key.clone()];
640 let mut index = 0;
641 while index < queue.len() {
642 let key = queue[index].clone();
643 index += 1;
644 let base = analyzed
645 .get(&key.name)
646 .ok_or_else(|| unsupported_sv_instance(key.name.clone()))?;
647 let lowered = specialize_module(base, &key, four_state)?;
648 for instance in &lowered.instances {
649 let child_key = LoweredSvModuleKey::instance_key(instance);
650 if !analyzed.contains_key(&child_key.name) {
651 return Err(unsupported_sv_instance(child_key.name.clone()).into());
652 }
653 if !module_ids.contains_key(&child_key) {
654 let specialization_count = module_specialization_counts
655 .entry(child_key.name.clone())
656 .or_insert(0);
657 if *specialization_count >= MAX_SV_SPECIALIZATIONS_PER_MODULE {
658 return Err(sv_specialization_limit_error(child_key.name.clone()).into());
659 }
660 *specialization_count += 1;
661 let child_id = ModuleId(module_ids.len());
662 module_ids.insert(child_key.clone(), child_id);
663 queue.push(child_key);
664 }
665 }
666 }
667
668 let lowered_modules = module_ids
669 .iter()
670 .map(|(key, &module_id)| {
671 let base = analyzed
672 .get(&key.name)
673 .ok_or_else(|| unsupported_sv_instance(key.name.clone()))?;
674 let lowered = specialize_module(base, key, four_state).map_err(FrontendError::from)?;
675 Ok((module_id, lowered))
676 })
677 .collect::<Result<HashMap<_, _>, FrontendError>>()?;
678 validate_specialized_instance_net_drivers(&module_ids, &lowered_modules)?;
679 let root = &lowered_modules[&root_id];
680 if let Some(port) = root
681 .port_order
682 .iter()
683 .map(|port_id| &root.variables[port_id])
684 .find(|port| port.kind == VariableKind::Inout)
685 {
686 return Err(unsupported_sv_inout(port.path.join(".")).into());
687 }
688 validate_sv_module_graph(
689 &root_key,
690 &module_ids,
691 &lowered_modules,
692 &mut HashSet::default(),
693 &mut HashSet::default(),
694 )?;
695
696 let mut modules = HashMap::default();
697 let mut module_names = HashMap::default();
698 for (key, &module_id) in &module_ids {
699 let lowered = &lowered_modules[&module_id];
700 let mut sim_module = lowered.sim_module.clone();
701 attach_instance_glue(
702 &mut sim_module,
703 lowered,
704 key,
705 &module_ids,
706 &lowered_modules,
707 four_state,
708 )?;
709 module_names.insert(module_id, key.name.clone());
710 modules.insert(module_id, sim_module);
711 }
712
713 let symbolic = SymbolicRtl {
714 modules,
715 module_names,
716 root_id,
717 };
718 celox_frontend_core::symbolic::assembly::schedule_symbolic_rtl(
719 symbolic,
720 None,
721 ignored_loops,
722 true_loops,
723 four_state,
724 trace_options,
725 trace,
726 )
727 .map_err(FrontendError::from)
728}
729
730fn sv_specialization_limit_error(name: String) -> ParserError {
731 ParserError::unsupported(
732 64,
733 LoweringPhase::SimulatorParser,
734 "systemverilog module specialization limit exceeded (possible recursive instantiation)",
735 name,
736 None,
737 )
738}
739
740fn validate_sv_module_graph(
741 key: &LoweredSvModuleKey,
742 module_ids: &HashMap<LoweredSvModuleKey, ModuleId>,
743 lowered_modules: &HashMap<ModuleId, LoweredSvModule>,
744 active: &mut HashSet<LoweredSvModuleKey>,
745 complete: &mut HashSet<LoweredSvModuleKey>,
746) -> Result<(), ParserError> {
747 if complete.contains(key) {
748 return Ok(());
749 }
750 if !active.insert(key.clone()) {
751 return Err(ParserError::unsupported(
752 64,
753 LoweringPhase::SimulatorParser,
754 "recursive systemverilog module instantiation",
755 key.name.clone(),
756 None,
757 ));
758 }
759 let module_id = module_ids
760 .get(key)
761 .copied()
762 .ok_or_else(|| unsupported_sv_instance(key.name.clone()))?;
763 let module = lowered_modules
764 .get(&module_id)
765 .ok_or_else(|| unsupported_sv_instance(key.name.clone()))?;
766 for instance in &module.instances {
767 validate_sv_module_graph(
768 &LoweredSvModuleKey::instance_key(instance),
769 module_ids,
770 lowered_modules,
771 active,
772 complete,
773 )?;
774 }
775 active.remove(key);
776 complete.insert(key.clone());
777 Ok(())
778}
779
780fn specialize_module(
781 module: &AnalyzedSvModule,
782 key: &LoweredSvModuleKey,
783 four_state: bool,
784) -> Result<LoweredSvModule, sv::AnalyzerError> {
785 let overrides = evaluated_parameter_overrides(&key.parameter_overrides)?;
786 let ir = sv::analyze_source_module_with_parameter_expr_overrides(
787 &module.source_code,
788 &module.source_path,
789 &module.name,
790 &overrides,
791 )?;
792 let specialized = ir
793 .modules()
794 .iter()
795 .find(|candidate| candidate.name() == module.name)
796 .ok_or_else(|| sv::AnalyzerError::Unsupported(format!("module `{}`", module.name)))?;
797 lower_module(specialized, four_state, module.implicit_nets_allowed)
798}
799
800fn lower_module(
801 module: &sv::ir::Module,
802 four_state: bool,
803 implicit_nets_allowed: bool,
804) -> Result<LoweredSvModule, sv::AnalyzerError> {
805 lower_module_with_overrides(module, &[], four_state, implicit_nets_allowed)
806}
807
808fn lower_module_with_overrides(
809 module: &sv::ir::Module,
810 parameter_overrides: &[LoweredSvParameterOverride],
811 four_state: bool,
812 implicit_nets_allowed: bool,
813) -> Result<LoweredSvModule, sv::AnalyzerError> {
814 let name = module.name().to_string();
815 let mut next_id = SourceVarId::default();
816 let mut variables = HashMap::default();
817 let mut name_to_id = HashMap::default();
818 let mut port_order = Vec::new();
819 let mut initial_memory_values = Vec::new();
820 let parameter_types = module
821 .parameters()
822 .iter()
823 .filter_map(|parameter| {
824 Some((
825 parameter.name().to_string(),
826 (
827 parameter.resolved_width()?,
828 parameter.resolved_signed().unwrap_or(false),
829 ),
830 ))
831 })
832 .collect();
833 let constants = module_constants_with_overrides(module, parameter_overrides);
834 validate_variable_driver_ranges(module, &constants, ¶meter_types)?;
835
836 for port in module.ports() {
837 if name_to_id.contains_key(port.name()) {
838 return Err(sv::AnalyzerError::Unsupported(format!(
839 "duplicate port name `{}`",
840 port.name()
841 )));
842 }
843 let id = next_var_id(&mut next_id);
844 let type_info = signal_type_from_sv(port.r#type(), &constants, ¶meter_types)?;
845 let path = vec![port.name().to_string()];
846 let kind = signal_kind_from_port_direction(port.direction())?;
847 let variable = SvVariable {
848 path,
849 width: type_info.width,
850 signed: type_info.signed,
851 is_4state: type_info.is_4state,
852 is_net: port.is_net(),
853 packed_ranges: type_info.packed_ranges,
854 array_dims: type_info.array_dims,
855 domain_kind: DomainKind::Other,
856 kind,
857 type_kind: type_info.type_kind,
858 source: None,
859 };
860 name_to_id.insert(port.name().to_string(), id);
861 port_order.push(id);
862 if port.is_net() || type_info.is_4state {
863 let written_mask = (BigUint::from(1u8) << type_info.width) - BigUint::from(1u8);
864 let value = if port.is_net() {
865 BigUint::default()
866 } else {
867 written_mask.clone()
868 };
869 initial_memory_values.push(InitialStateValue {
870 address: id,
871 data: InitialStateData::Packed {
872 value,
873 mask: written_mask.clone(),
874 written_mask,
875 },
876 });
877 }
878 variables.insert(id, variable);
879 }
880
881 for signal in module.signals() {
882 if name_to_id.contains_key(signal.name()) {
883 return Err(sv::AnalyzerError::Unsupported(format!(
884 "duplicate port or signal name `{}`",
885 signal.name()
886 )));
887 }
888 let id = next_var_id(&mut next_id);
889 let type_info = signal_type_from_sv(signal.r#type(), &constants, ¶meter_types)?;
890 let path = vec![signal.name().to_string()];
891 let variable = SvVariable {
892 path,
893 width: type_info.width,
894 signed: type_info.signed,
895 is_4state: type_info.is_4state,
896 is_net: signal.is_net(),
897 packed_ranges: type_info.packed_ranges,
898 array_dims: type_info.array_dims,
899 domain_kind: DomainKind::Other,
900 kind: VariableKind::Variable,
901 type_kind: type_info.type_kind,
902 source: None,
903 };
904 name_to_id.insert(signal.name().to_string(), id);
905 if signal.is_net() || type_info.is_4state {
906 let written_mask = (BigUint::from(1u8) << type_info.width) - BigUint::from(1u8);
907 let value = if signal.is_net() {
908 BigUint::default()
909 } else {
910 written_mask.clone()
911 };
912 initial_memory_values.push(InitialStateValue {
913 address: id,
914 data: InitialStateData::Packed {
915 value,
916 mask: written_mask.clone(),
917 written_mask,
918 },
919 });
920 }
921 variables.insert(id, variable);
922 }
923
924 let (eval_only_ff_blocks, apply_ff_blocks, eval_apply_ff_blocks, reset_clock_map) =
925 lower_ff_processes(
926 module,
927 &variables,
928 &name_to_id,
929 &constants,
930 ¶meter_types,
931 four_state,
932 )?;
933 mark_ff_event_domains(module, &mut variables, &name_to_id);
934
935 let shared_variables = variables
936 .iter()
937 .map(|(&id, variable)| (id, variable.to_symbolic_variable()))
938 .collect();
939 let mut instances = Vec::new();
940 for instance in module.instances() {
941 if let Some(condition) = instance.condition() {
942 let condition =
943 sv::typecheck::eval_const_expr_with_types(condition, &constants, ¶meter_types)
944 .ok_or_else(|| {
945 sv::AnalyzerError::Unsupported(
946 "unknown conditional-generate condition".to_string(),
947 )
948 })?;
949 if condition == 0 {
950 continue;
951 }
952 }
953 instances.push(LoweredSvInstance {
954 module_name: instance.module_name().to_string(),
955 instance_name: instance.name().to_string(),
956 parameter_overrides: lower_parameter_overrides(instance, &constants, ¶meter_types),
957 port_connections: instance
958 .port_connections()
959 .iter()
960 .map(|connection| LoweredSvPortConnection {
961 formal: connection.formal().to_string(),
962 actual: connection.actual().to_string(),
963 actual_expr: connection.actual_expr().cloned(),
964 })
965 .collect(),
966 });
967 }
968
969 Ok(LoweredSvModule {
970 source: module.clone(),
971 implicit_nets_allowed,
972 sim_module: SimModule {
973 name,
974 variables: shared_variables,
975 ff_access_summaries: HashMap::default(),
976 eval_only_ff_blocks,
977 apply_ff_blocks,
978 eval_apply_ff_blocks,
979 glue_blocks: HashMap::default(),
980 indexed_instance_names: HashSet::default(),
981 comb_blocks: Vec::new(),
982 comb_observers: Vec::<CombObserver<SourceVarId>>::new(),
983 runtime_errors: HashMap::<i64, RuntimeErrorInfo<SourceVarId>>::default(),
984 runtime_event_sites: Vec::new(),
985 initial_memory_values,
986 comb_boundaries: HashMap::default(),
987 arena: SLTNodeArena::new(),
988 reset_clock_map,
989 },
990 variables,
991 port_order,
992 signal_names: name_to_id,
993 constants: constants.clone(),
994 parameter_types,
995 instances,
996 })
997}
998
999fn mark_ff_event_domains(
1000 module: &sv::ir::Module,
1001 variables: &mut HashMap<SourceVarId, SvVariable>,
1002 name_to_id: &HashMap<String, SourceVarId>,
1003) {
1004 for process in module.ff_processes() {
1005 let Some(clock) = clock_event_from_ff_process(process) else {
1006 continue;
1007 };
1008 if let Some(id) = name_to_id.get(clock.signal()).copied()
1009 && let Some(variable) = variables.get_mut(&id)
1010 {
1011 variable.domain_kind = match clock.edge() {
1012 sv::ir::FfEdge::Pos => DomainKind::ClockPosedge,
1013 sv::ir::FfEdge::Neg => DomainKind::ClockNegedge,
1014 };
1015 variable.type_kind = PortTypeKind::Clock;
1016 }
1017 for event in process
1018 .events()
1019 .iter()
1020 .filter(|event| event.signal() != clock.signal())
1021 {
1022 if let Some(id) = name_to_id.get(event.signal()).copied()
1023 && let Some(variable) = variables.get_mut(&id)
1024 {
1025 variable.domain_kind = match event.edge() {
1026 sv::ir::FfEdge::Pos => DomainKind::ResetAsyncHigh,
1027 sv::ir::FfEdge::Neg => DomainKind::ResetAsyncLow,
1028 };
1029 variable.type_kind = match event.edge() {
1030 sv::ir::FfEdge::Pos => PortTypeKind::ResetAsyncHigh,
1031 sv::ir::FfEdge::Neg => PortTypeKind::ResetAsyncLow,
1032 };
1033 }
1034 }
1035 }
1036}
1037
1038fn evaluated_parameter_overrides(
1039 parameter_overrides: &[LoweredSvParameterOverride],
1040) -> Result<HashMap<String, sv::ir::ConstExpr>, sv::AnalyzerError> {
1041 let constants = HashMap::default();
1042 let mut evaluated = HashMap::default();
1043 for parameter in parameter_overrides {
1044 let Some(value) = parameter.value.as_ref() else {
1045 continue;
1046 };
1047 sv::typecheck::eval_const_expr(value, &constants).ok_or_else(|| {
1048 sv::AnalyzerError::Unsupported(format!(
1049 "non-integer module parameter override `{}`",
1050 parameter.name
1051 ))
1052 })?;
1053 evaluated.insert(parameter.name.clone(), value.clone());
1054 }
1055 Ok(evaluated)
1056}
1057
1058fn lower_parameter_overrides(
1059 instance: &sv::ir::Instance,
1060 constants: &HashMap<String, i128>,
1061 parameter_types: &HashMap<String, (usize, bool)>,
1062) -> Vec<LoweredSvParameterOverride> {
1063 instance
1064 .parameter_overrides()
1065 .iter()
1066 .map(|parameter| {
1067 let value = parameter.value().cloned().map(|value| {
1068 let value =
1069 sv::typecheck::substitute_typed_constants(value, constants, parameter_types);
1070 if const_expr_references_identifier(&value) {
1071 sv::typecheck::eval_const_expr_with_types(&value, constants, parameter_types)
1072 .map(const_expr_from_i128)
1073 .unwrap_or(value)
1074 } else {
1075 value
1076 }
1077 });
1078 LoweredSvParameterOverride {
1079 name: parameter.name().to_string(),
1080 value,
1081 }
1082 })
1083 .collect()
1084}
1085
1086fn const_expr_references_identifier(expr: &sv::ir::ConstExpr) -> bool {
1087 match expr {
1088 sv::ir::ConstExpr::Ident(_) => true,
1089 sv::ir::ConstExpr::Literal(_) => false,
1090 sv::ir::ConstExpr::Select { expr, bit } => {
1091 const_expr_references_identifier(expr) || const_expr_references_identifier(bit)
1092 }
1093 sv::ir::ConstExpr::Function { args, .. } => {
1094 args.iter().any(const_expr_references_identifier)
1095 }
1096 sv::ir::ConstExpr::Unary { expr, .. } => const_expr_references_identifier(expr),
1097 sv::ir::ConstExpr::Binary { left, right, .. } => {
1098 const_expr_references_identifier(left) || const_expr_references_identifier(right)
1099 }
1100 sv::ir::ConstExpr::Mux {
1101 condition,
1102 then_expr,
1103 else_expr,
1104 } => {
1105 const_expr_references_identifier(condition)
1106 || const_expr_references_identifier(then_expr)
1107 || const_expr_references_identifier(else_expr)
1108 }
1109 }
1110}
1111
1112fn const_expr_from_i128(value: i128) -> sv::ir::ConstExpr {
1113 if value < 0 {
1114 sv::ir::ConstExpr::Unary {
1115 op: sv::ir::UnaryOp::Minus,
1116 expr: Box::new(sv::ir::ConstExpr::Literal(value.unsigned_abs().to_string())),
1117 }
1118 } else {
1119 sv::ir::ConstExpr::Literal(value.to_string())
1120 }
1121}
1122
1123fn parameter_value_bits(value: i128, width: usize) -> BigUint {
1124 let modulus = BigUint::from(1u8) << width;
1125 if value >= 0 {
1126 BigUint::from(value as u128) % modulus
1127 } else {
1128 let remainder = BigUint::from(value.unsigned_abs()) % &modulus;
1129 if remainder == BigUint::default() {
1130 remainder
1131 } else {
1132 modulus - remainder
1133 }
1134 }
1135}
1136
1137pub(crate) fn attach_instance_glue(
1138 module: &mut SimModule,
1139 lowered: &LoweredSvModule,
1140 current_key: &LoweredSvModuleKey,
1141 module_ids: &HashMap<LoweredSvModuleKey, ModuleId>,
1142 lowered_modules: &HashMap<ModuleId, LoweredSvModule>,
1143 four_state: bool,
1144) -> Result<(), ParserError> {
1145 let mut signal_names = lowered.signal_names.clone();
1146 let mut parent_variables = lowered.variables.clone();
1147 let mut implicit_output_signals = HashSet::default();
1148 let mut resolved_instances = Vec::new();
1149 for instance in &lowered.instances {
1150 let child_key = LoweredSvModuleKey::instance_key(instance);
1151 let Some(child_id) = module_ids.get(&child_key).copied() else {
1152 return Err(unsupported_sv_instance(instance.module_name.clone()));
1153 };
1154 if &child_key == current_key {
1155 return Err(ParserError::unsupported(
1156 64,
1157 LoweringPhase::SimulatorParser,
1158 "recursive systemverilog module instantiation",
1159 instance.module_name.clone(),
1160 None,
1161 ));
1162 }
1163 let Some(child) = lowered_modules.get(&child_id) else {
1164 return Err(unsupported_sv_instance(instance.module_name.clone()));
1165 };
1166 ensure_parent_output_signals(
1167 module,
1168 &mut parent_variables,
1169 &mut signal_names,
1170 &mut implicit_output_signals,
1171 lowered.implicit_nets_allowed,
1172 &lowered.source,
1173 &lowered.constants,
1174 &lowered.parameter_types,
1175 child,
1176 &instance.port_connections,
1177 )?;
1178 resolved_instances.push((instance, child_id, child));
1179 }
1180 let (comb_blocks, arena) = lower_comb_processes(
1181 &lowered.source,
1182 &parent_variables,
1183 &signal_names,
1184 &lowered.constants,
1185 &lowered.parameter_types,
1186 four_state,
1187 )
1188 .map_err(|error| {
1189 ParserError::unsupported(
1190 64,
1191 LoweringPhase::SimulatorParser,
1192 "systemverilog combinational process lowering",
1193 error.to_string(),
1194 None,
1195 )
1196 })?;
1197 module.comb_blocks = comb_blocks;
1198 module.arena = arena;
1199 for (instance, child_id, child) in resolved_instances {
1200 let glue = build_instance_glue(
1201 &parent_variables,
1202 &signal_names,
1203 &lowered.constants,
1204 &lowered.parameter_types,
1205 child,
1206 &instance.port_connections,
1207 four_state,
1208 )?;
1209 module
1210 .glue_blocks
1211 .entry(instance.instance_name.clone())
1212 .or_default()
1213 .push(GlueBlock {
1214 module_id: child_id,
1215 input_ports: glue.0,
1216 output_ports: glue.1,
1217 arena: glue.2,
1218 });
1219 }
1220 Ok(())
1221}
1222
1223fn expr_for_state_mode(expr: &sv::ir::Expr, four_state: bool) -> sv::ir::Expr {
1224 match expr {
1225 sv::ir::Expr::Mux {
1226 then_expr,
1227 else_expr,
1228 ..
1229 } if matches!(
1230 &**then_expr,
1231 sv::ir::Expr::Literal(literal)
1232 if literal == sv::DIV_ZERO_UNKNOWN_LITERAL
1233 ) =>
1234 {
1235 if four_state {
1236 let sv::ir::Expr::Mux {
1237 condition,
1238 else_expr,
1239 ..
1240 } = expr
1241 else {
1242 unreachable!()
1243 };
1244 sv::ir::Expr::Mux {
1245 condition: Box::new(expr_for_state_mode(condition, four_state)),
1246 then_expr: Box::new(sv::ir::Expr::Literal("'x".to_string())),
1247 else_expr: Box::new(expr_for_state_mode(else_expr, four_state)),
1248 }
1249 } else {
1250 expr_for_state_mode(else_expr, four_state)
1251 }
1252 }
1253 sv::ir::Expr::Literal(literal) if !four_state && expr_is_unknown_literal(expr) => {
1254 if unbased_fill_literal(literal).is_some() {
1255 sv::ir::Expr::Literal("'0".to_string())
1256 } else {
1257 sv::ir::Expr::Unary {
1258 op: sv::ir::UnaryOp::ToTwoState,
1259 expr: Box::new(expr.clone()),
1260 }
1261 }
1262 }
1263 sv::ir::Expr::Ident(_) | sv::ir::Expr::Literal(_) => expr.clone(),
1264 sv::ir::Expr::Select {
1265 expr,
1266 msb,
1267 lsb,
1268 signed,
1269 } => sv::ir::Expr::Select {
1270 expr: Box::new(expr_for_state_mode(expr, four_state)),
1271 msb: msb.clone(),
1272 lsb: lsb.clone(),
1273 signed: *signed,
1274 },
1275 sv::ir::Expr::Concat(parts) => sv::ir::Expr::Concat(
1276 parts
1277 .iter()
1278 .map(|part| expr_for_state_mode(part, four_state))
1279 .collect(),
1280 ),
1281 sv::ir::Expr::RepeatConcat { count, parts } => sv::ir::Expr::RepeatConcat {
1282 count: count.clone(),
1283 parts: parts
1284 .iter()
1285 .map(|part| expr_for_state_mode(part, four_state))
1286 .collect(),
1287 },
1288 sv::ir::Expr::Resize {
1289 expr,
1290 width,
1291 signed,
1292 } => sv::ir::Expr::Resize {
1293 expr: Box::new(expr_for_state_mode(expr, four_state)),
1294 width: *width,
1295 signed: *signed,
1296 },
1297 sv::ir::Expr::Unary { op, expr } => sv::ir::Expr::Unary {
1298 op: *op,
1299 expr: Box::new(expr_for_state_mode(expr, four_state)),
1300 },
1301 sv::ir::Expr::Binary { left, op, right } => sv::ir::Expr::Binary {
1302 left: Box::new(expr_for_state_mode(left, four_state)),
1303 op: *op,
1304 right: Box::new(expr_for_state_mode(right, four_state)),
1305 },
1306 sv::ir::Expr::Mux {
1307 condition,
1308 then_expr,
1309 else_expr,
1310 } => sv::ir::Expr::Mux {
1311 condition: Box::new(expr_for_state_mode(condition, four_state)),
1312 then_expr: Box::new(expr_for_state_mode(then_expr, four_state)),
1313 else_expr: Box::new(expr_for_state_mode(else_expr, four_state)),
1314 },
1315 sv::ir::Expr::Call { name, args } => sv::ir::Expr::Call {
1316 name: name.clone(),
1317 args: args
1318 .iter()
1319 .map(|arg| expr_for_state_mode(arg, four_state))
1320 .collect(),
1321 },
1322 }
1323}
1324
1325fn lower_comb_processes(
1326 module: &sv::ir::Module,
1327 variables: &HashMap<SourceVarId, SvVariable>,
1328 name_to_id: &HashMap<String, SourceVarId>,
1329 constants: &HashMap<String, i128>,
1330 parameter_types: &HashMap<String, (usize, bool)>,
1331 four_state: bool,
1332) -> Result<(Vec<LogicPath<SourceVarId>>, SLTNodeArena<SourceVarId>), sv::AnalyzerError> {
1333 let mut arena = SLTNodeArena::new();
1334 let mut comb_blocks = Vec::new();
1335 for process in module.comb_processes() {
1336 if let Some(condition) = process.condition() {
1337 let condition =
1338 sv::typecheck::eval_const_expr_with_types(condition, constants, parameter_types)
1339 .ok_or_else(|| {
1340 sv::AnalyzerError::Unsupported(
1341 "unknown conditional-generate condition".to_string(),
1342 )
1343 })?;
1344 if condition == 0 {
1345 continue;
1346 }
1347 }
1348 comb_blocks.extend(lower_comb_process(
1349 process,
1350 variables,
1351 name_to_id,
1352 constants,
1353 parameter_types,
1354 &mut arena,
1355 four_state,
1356 )?);
1357 }
1358 Ok((comb_blocks, arena))
1359}
1360
1361fn ensure_parent_output_signals(
1362 parent: &mut SimModule,
1363 parent_variables: &mut HashMap<SourceVarId, SvVariable>,
1364 parent_signal_names: &mut HashMap<String, SourceVarId>,
1365 implicit_output_signals: &mut HashSet<String>,
1366 implicit_nets_allowed: bool,
1367 parent_source: &sv::ir::Module,
1368 parent_constants: &HashMap<String, i128>,
1369 parent_parameter_types: &HashMap<String, (usize, bool)>,
1370 child: &LoweredSvModule,
1371 connections: &[LoweredSvPortConnection],
1372) -> Result<(), ParserError> {
1373 for child_port_id in &child.port_order {
1374 let child_var = &child.variables[child_port_id];
1375 if child_var.kind != VariableKind::Output {
1376 continue;
1377 }
1378 let formal = child_var.path.join(".");
1379 let Some(connection) = connections
1380 .iter()
1381 .find(|connection| connection.formal == formal)
1382 else {
1383 continue;
1384 };
1385 let Some(actual) = connection
1386 .actual_expr
1387 .as_ref()
1388 .and_then(simple_output_lvalue_ident)
1389 else {
1390 continue;
1391 };
1392 if parent_signal_names.contains_key(actual) {
1393 if implicit_output_signals.contains(actual) {
1394 return Err(ParserError::illegal_context(
1395 "systemverilog output port connection",
1396 format!("multiple child outputs drive implicit net `{actual}`"),
1397 None,
1398 ));
1399 }
1400 continue;
1401 }
1402 if parent_constants.contains_key(actual) {
1403 return Err(ParserError::illegal_context(
1404 "systemverilog output port connection",
1405 format!("cannot drive parameter `{actual}`"),
1406 None,
1407 ));
1408 }
1409 if !implicit_nets_allowed {
1410 return Err(ParserError::illegal_context(
1411 "systemverilog output port connection",
1412 format!("implicit net `{actual}` disabled by `default_nettype none"),
1413 None,
1414 ));
1415 }
1416 if !local_driver_ranges(
1417 parent_source,
1418 actual,
1419 parent_constants,
1420 parent_parameter_types,
1421 )
1422 .is_empty()
1423 {
1424 return Err(ParserError::illegal_context(
1425 "systemverilog output port connection",
1426 format!("multiple net drivers for `{actual}`"),
1427 None,
1428 ));
1429 }
1430 let mut next_id = SourceVarId::default();
1431 while parent.variables.contains_key(&next_id) {
1432 next_id.0 += 1;
1433 }
1434 parent_signal_names.insert(actual.to_string(), next_id);
1435 implicit_output_signals.insert(actual.to_string());
1436 let variable = SvVariable {
1437 path: vec![actual.to_string()],
1438 width: 1,
1439 signed: false,
1440 is_4state: true,
1441 is_net: true,
1442 packed_ranges: Vec::new(),
1443 array_dims: Vec::new(),
1444 domain_kind: DomainKind::Other,
1445 kind: VariableKind::Variable,
1446 type_kind: PortTypeKind::Logic,
1447 source: None,
1448 };
1449 parent
1450 .variables
1451 .insert(next_id, variable.to_symbolic_variable());
1452 parent_variables.insert(next_id, variable);
1453 }
1454 Ok(())
1455}
1456
1457type SvGlue = (
1458 Vec<(Vec<SourceVarId>, LogicPath<GlueAddr>)>,
1459 Vec<(Vec<SourceVarId>, LogicPath<GlueAddr>)>,
1460 SLTNodeArena<GlueAddr>,
1461);
1462
1463fn build_instance_glue(
1464 parent_variables: &HashMap<SourceVarId, SvVariable>,
1465 parent_signal_names: &HashMap<String, SourceVarId>,
1466 parent_constants: &HashMap<String, i128>,
1467 parent_parameter_types: &HashMap<String, (usize, bool)>,
1468 child: &LoweredSvModule,
1469 connections: &[LoweredSvPortConnection],
1470 four_state: bool,
1471) -> Result<SvGlue, ParserError> {
1472 let mut input_ports = Vec::new();
1473 let mut output_ports = Vec::new();
1474 let mut arena = SLTNodeArena::<GlueAddr>::new();
1475
1476 let mut connected_formals = HashSet::default();
1477 for connection in connections {
1478 let matches = child
1479 .port_order
1480 .iter()
1481 .filter(|port_id| child.variables[port_id].path.join(".") == connection.formal)
1482 .count();
1483 if matches != 1 || !connected_formals.insert(connection.formal.clone()) {
1484 return Err(ParserError::unsupported(
1485 64,
1486 LoweringPhase::SimulatorParser,
1487 "unknown or duplicate systemverilog child port connection",
1488 connection.formal.clone(),
1489 None,
1490 ));
1491 }
1492 }
1493
1494 for child_port_id in &child.port_order {
1495 let child_var = &child.variables[child_port_id];
1496 let formal = child_var.path.join(".");
1497 let connection = connections
1498 .iter()
1499 .find(|connection| connection.formal == formal);
1500 let width = child_var.width;
1501 match child_var.kind {
1502 VariableKind::Input => {
1503 let collapse_unknown_literal = !four_state
1504 && connection
1505 .and_then(|item| item.actual_expr.as_ref())
1506 .is_some_and(expr_is_unknown_literal);
1507 let (mut expr, sources, source_ids) = if let Some(actual_expr) =
1508 connection.and_then(|item| item.actual_expr.as_ref())
1509 {
1510 let actual_expr = expr_for_state_mode(actual_expr, four_state);
1511 let actual = connection.map_or("", |item| item.actual.as_str());
1512 let (expr, sources, source_ids) = lower_glue_parent_expr(
1513 &actual_expr,
1514 parent_variables,
1515 parent_signal_names,
1516 parent_constants,
1517 parent_parameter_types,
1518 &mut arena,
1519 Some(width),
1520 Some(sv_glue_expr_is_signed(
1521 &actual_expr,
1522 parent_variables,
1523 parent_signal_names,
1524 parent_parameter_types,
1525 )),
1526 )
1527 .ok_or_else(|| {
1528 ParserError::unsupported(
1529 64,
1530 LoweringPhase::SimulatorParser,
1531 "systemverilog input port connection",
1532 format!("{formal} -> {actual}"),
1533 None,
1534 )
1535 })?;
1536 let expr = coerce_node_width(
1537 &mut arena,
1538 expr,
1539 Some(width),
1540 sv_glue_expr_is_signed(
1541 &actual_expr,
1542 parent_variables,
1543 parent_signal_names,
1544 parent_parameter_types,
1545 ),
1546 )?;
1547 (expr, sources, source_ids)
1548 } else {
1549 let unknown_mask = (BigUint::from(1u8) << width) - BigUint::from(1u8);
1550 (
1551 arena.alloc(SLTNode::Constant(
1552 BigUint::default(),
1553 unknown_mask,
1554 width,
1555 false,
1556 ))?,
1557 HashSet::default(),
1558 Vec::new(),
1559 )
1560 };
1561 if !child_var.is_4state || collapse_unknown_literal {
1562 expr = arena.alloc(SLTNode::Unary(UnaryOp::ToTwoState, expr))?;
1563 }
1564 input_ports.push((
1565 source_ids,
1566 LogicPath {
1567 target: LogicPathTarget::Var(VarAtomBase::new(
1568 GlueAddr::Child(*child_port_id),
1569 0,
1570 width - 1,
1571 )),
1572 expr,
1573 sources,
1574 address_sources: HashSet::default(),
1575 previous_sources: HashSet::default(),
1576 local_inputs: Vec::new(),
1577 order_before: HashSet::default(),
1578 comb_capture_enable_sites: Vec::new(),
1579 comb_capture_enable_always: false,
1580 pre_lower_nodes: Vec::new(),
1581 },
1582 ));
1583 }
1584 VariableKind::Output => {
1585 let Some(connection) = connection else {
1586 continue;
1587 };
1588 let actual = connection.actual.as_str();
1589 let Some(actual_expr) = connection.actual_expr.as_ref() else {
1590 continue;
1591 };
1592 if let Some(dynamic_output) = lower_dynamic_output_glue(
1593 actual_expr,
1594 parent_variables,
1595 parent_signal_names,
1596 parent_constants,
1597 parent_parameter_types,
1598 *child_port_id,
1599 child_var,
1600 &mut arena,
1601 &formal,
1602 actual,
1603 )? {
1604 output_ports.push(dynamic_output);
1605 continue;
1606 }
1607 let Some(accesses) = output_lvalue_accesses(
1608 actual_expr,
1609 parent_variables,
1610 parent_signal_names,
1611 parent_constants,
1612 parent_parameter_types,
1613 ) else {
1614 return Err(ParserError::unsupported(
1615 64,
1616 LoweringPhase::SimulatorParser,
1617 "systemverilog output port lvalue connection",
1618 format!("{formal} -> {actual}: {actual_expr:?}"),
1619 None,
1620 ));
1621 };
1622 let target_width = accesses.iter().try_fold(0usize, |width, (_, access)| {
1623 width.checked_add(access.msb - access.lsb + 1)
1624 });
1625 let Some(target_width) = target_width.filter(|target_width| *target_width != 0)
1626 else {
1627 return Err(ParserError::unsupported(
1628 64,
1629 LoweringPhase::SimulatorParser,
1630 "systemverilog output port lvalue connection",
1631 format!("{formal} -> {actual}: {actual_expr:?}"),
1632 None,
1633 ));
1634 };
1635 let child_input = arena.alloc(SLTNode::Input {
1636 variable: GlueAddr::Child(*child_port_id),
1637 signed: child_var.signed,
1638 index: Vec::new(),
1639 access: BitAccess::new(0, width - 1),
1640 })?;
1641 let child_node = coerce_node_width(
1642 &mut arena,
1643 child_input,
1644 Some(target_width),
1645 child_var.signed,
1646 )?;
1647 let mut child_lsb = target_width;
1648 for (parent_signal_id, access) in accesses {
1649 let parent_var = &parent_variables[&parent_signal_id];
1650 let part_width = access.msb - access.lsb + 1;
1651 child_lsb -= part_width;
1652 let child_expr = if child_lsb == 0 && part_width == target_width {
1653 child_node
1654 } else {
1655 arena.alloc(SLTNode::Slice {
1656 expr: child_node,
1657 access: BitAccess::new(child_lsb, child_lsb + part_width - 1),
1658 })?
1659 };
1660 let mut expr = coerce_node_width(
1661 &mut arena,
1662 child_expr,
1663 Some(part_width),
1664 child_var.signed,
1665 )?;
1666 if !parent_var.is_4state {
1667 expr = arena.alloc(SLTNode::Unary(UnaryOp::ToTwoState, expr))?;
1668 }
1669 let mut sources = HashSet::default();
1670 sources.insert(VarAtomBase::new(
1671 GlueAddr::Child(*child_port_id),
1672 0,
1673 width - 1,
1674 ));
1675 output_ports.push((
1676 vec![parent_signal_id],
1677 LogicPath {
1678 target: LogicPathTarget::Var(VarAtomBase::new(
1679 GlueAddr::Parent(parent_signal_id),
1680 access.lsb,
1681 access.msb,
1682 )),
1683 expr,
1684 sources,
1685 address_sources: HashSet::default(),
1686 previous_sources: HashSet::default(),
1687 local_inputs: Vec::new(),
1688 order_before: HashSet::default(),
1689 comb_capture_enable_sites: Vec::new(),
1690 comb_capture_enable_always: false,
1691 pre_lower_nodes: Vec::new(),
1692 },
1693 ));
1694 }
1695 }
1696 VariableKind::Inout => {
1697 return Err(unsupported_sv_inout(child_var.path.join(".")));
1698 }
1699 _ => {}
1700 }
1701 }
1702
1703 Ok((input_ports, output_ports, arena))
1704}
1705
1706fn lower_dynamic_output_glue(
1707 actual_expr: &sv::ir::Expr,
1708 parent_variables: &HashMap<SourceVarId, SvVariable>,
1709 parent_signal_names: &HashMap<String, SourceVarId>,
1710 parent_constants: &HashMap<String, i128>,
1711 parent_parameter_types: &HashMap<String, (usize, bool)>,
1712 child_port_id: SourceVarId,
1713 child_var: &SvVariable,
1714 arena: &mut SLTNodeArena<GlueAddr>,
1715 formal: &str,
1716 actual: &str,
1717) -> Result<Option<(Vec<SourceVarId>, LogicPath<GlueAddr>)>, ParserError> {
1718 let sv::ir::Expr::Select { expr, msb, lsb, .. } = actual_expr else {
1719 return Ok(None);
1720 };
1721 let Some((parent_signal_id, element_width, access)) = dynamic_array_element_subselection(
1722 expr,
1723 msb,
1724 lsb,
1725 parent_variables,
1726 parent_signal_names,
1727 parent_constants,
1728 parent_parameter_types,
1729 ) else {
1730 return Ok(None);
1731 };
1732 let parent_var = &parent_variables[&parent_signal_id];
1733 if parent_var.is_net {
1734 return Err(ParserError::unsupported(
1735 64,
1736 LoweringPhase::SimulatorParser,
1737 "dynamic child output connection to a net",
1738 format!("{formal} -> {actual}: {actual_expr:?}"),
1739 None,
1740 ));
1741 }
1742 let (offset, index_sources, index_source_ids) = lower_dynamic_array_element_index_glue(
1743 lsb,
1744 parent_variables,
1745 parent_signal_names,
1746 parent_constants,
1747 parent_parameter_types,
1748 arena,
1749 element_width,
1750 )
1751 .ok_or_else(|| {
1752 ParserError::unsupported(
1753 64,
1754 LoweringPhase::SimulatorParser,
1755 "systemverilog output port lvalue connection",
1756 format!("{formal} -> {actual}: {actual_expr:?}"),
1757 None,
1758 )
1759 })?;
1760 let element_count = parent_var.width / element_width;
1761 let child_node = arena.alloc(SLTNode::Input {
1762 variable: GlueAddr::Child(child_port_id),
1763 signed: child_var.signed,
1764 index: Vec::new(),
1765 access: BitAccess::new(0, child_var.width - 1),
1766 })?;
1767 let target_width = access.msb - access.lsb + 1;
1768 let child_expr = coerce_node_width(arena, child_node, Some(target_width), child_var.signed)?;
1769 let old = arena.alloc(SLTNode::Input {
1770 variable: GlueAddr::Parent(parent_signal_id),
1771 signed: parent_var.signed,
1772 index: Vec::new(),
1773 access: BitAccess::new(0, parent_var.width - 1),
1774 })?;
1775 let mut parts = Vec::with_capacity(element_count);
1776 for element in (0..element_count).rev() {
1777 let lsb = element * element_width;
1778 let old_element = arena.alloc(SLTNode::Slice {
1779 expr: old,
1780 access: BitAccess::new(lsb, lsb + element_width - 1),
1781 })?;
1782 let element_literal = arena.alloc(SLTNode::Constant(
1783 BigUint::from(element),
1784 BigUint::default(),
1785 64,
1786 false,
1787 ))?;
1788 let condition = arena.alloc(SLTNode::Binary(offset, BinaryOp::EqCase, element_literal))?;
1789 let Some(updated_element) = replace_slt_slice(
1790 arena,
1791 old_element,
1792 child_expr,
1793 access.lsb,
1794 target_width,
1795 element_width,
1796 ) else {
1797 return Ok(None);
1798 };
1799 let updated = arena.alloc(SLTNode::Mux {
1800 cond: condition,
1801 then_expr: updated_element,
1802 else_expr: old_element,
1803 })?;
1804 parts.push((updated, element_width));
1805 }
1806 let mut expr = if parts.len() == 1 {
1807 parts[0].0
1808 } else {
1809 arena.alloc(SLTNode::Concat(parts))?
1810 };
1811 if !parent_var.is_4state {
1812 expr = arena.alloc(SLTNode::Unary(UnaryOp::ToTwoState, expr))?;
1813 }
1814 let mut sources = index_sources.clone();
1815 sources.insert(VarAtomBase::new(
1816 GlueAddr::Child(child_port_id),
1817 0,
1818 child_var.width - 1,
1819 ));
1820 let previous_sources = [VarAtomBase::new(
1821 GlueAddr::Parent(parent_signal_id),
1822 0,
1823 parent_var.width - 1,
1824 )]
1825 .into_iter()
1826 .collect();
1827 let mut source_ids = index_source_ids;
1828 source_ids.push(parent_signal_id);
1829 source_ids.sort();
1830 source_ids.dedup();
1831 Ok(Some((
1832 source_ids,
1833 LogicPath {
1834 target: LogicPathTarget::Var(VarAtomBase::new(
1835 GlueAddr::Parent(parent_signal_id),
1836 0,
1837 parent_var.width - 1,
1838 )),
1839 expr,
1840 sources,
1841 address_sources: index_sources,
1842 previous_sources,
1843 local_inputs: Vec::new(),
1844 order_before: HashSet::default(),
1845 comb_capture_enable_sites: Vec::new(),
1846 comb_capture_enable_always: false,
1847 pre_lower_nodes: Vec::new(),
1848 },
1849 )))
1850}
1851
1852fn simple_output_lvalue_ident(expr: &sv::ir::Expr) -> Option<&str> {
1853 match expr {
1854 sv::ir::Expr::Ident(name) => Some(name),
1855 sv::ir::Expr::Resize { expr, .. } => simple_output_lvalue_ident(expr),
1856 _ => None,
1857 }
1858}
1859
1860fn output_lvalue_access(
1861 expr: &sv::ir::Expr,
1862 variables: &HashMap<SourceVarId, SvVariable>,
1863 name_to_id: &HashMap<String, SourceVarId>,
1864 constants: &HashMap<String, i128>,
1865 parameter_types: &HashMap<String, (usize, bool)>,
1866) -> Option<(SourceVarId, BitAccess)> {
1867 match expr {
1868 sv::ir::Expr::Ident(name) => {
1869 let id = *name_to_id.get(name)?;
1870 let variable = variables.get(&id)?;
1871 Some((id, BitAccess::new(0, variable.width.checked_sub(1)?)))
1872 }
1873 sv::ir::Expr::Resize { expr, .. } => {
1874 output_lvalue_access(expr, variables, name_to_id, constants, parameter_types)
1875 }
1876 sv::ir::Expr::Select { expr, msb, lsb, .. } => {
1877 let sv::ir::Expr::Ident(name) = &**expr else {
1878 return None;
1879 };
1880 let id = *name_to_id.get(name)?;
1881 let variable = variables.get(&id)?;
1882 if variable.array_dims.is_empty() {
1883 return None;
1884 }
1885 let msb = sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)?;
1886 let lsb = sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types)?;
1887 let (msb, lsb) = packed_expr_select_offsets(expr, msb, lsb, variables, name_to_id)?;
1888 let access = BitAccess::new(msb.min(lsb), msb.max(lsb));
1889 (access.msb < variable.width).then_some((id, access))
1890 }
1891 _ => None,
1892 }
1893}
1894
1895fn output_lvalue_accesses(
1896 expr: &sv::ir::Expr,
1897 variables: &HashMap<SourceVarId, SvVariable>,
1898 name_to_id: &HashMap<String, SourceVarId>,
1899 constants: &HashMap<String, i128>,
1900 parameter_types: &HashMap<String, (usize, bool)>,
1901) -> Option<Vec<(SourceVarId, BitAccess)>> {
1902 match expr {
1903 sv::ir::Expr::Concat(parts) if !parts.is_empty() => {
1904 let mut accesses = Vec::new();
1905 for part in parts {
1906 accesses.extend(output_lvalue_accesses(
1907 part,
1908 variables,
1909 name_to_id,
1910 constants,
1911 parameter_types,
1912 )?);
1913 }
1914 Some(accesses)
1915 }
1916 sv::ir::Expr::Resize { expr, .. } => {
1917 output_lvalue_accesses(expr, variables, name_to_id, constants, parameter_types)
1918 }
1919 _ => output_lvalue_access(expr, variables, name_to_id, constants, parameter_types)
1920 .map(|access| vec![access]),
1921 }
1922}
1923
1924fn lower_glue_parent_expr(
1925 expr: &sv::ir::Expr,
1926 variables: &HashMap<SourceVarId, SvVariable>,
1927 name_to_id: &HashMap<String, SourceVarId>,
1928 constants: &HashMap<String, i128>,
1929 parameter_types: &HashMap<String, (usize, bool)>,
1930 arena: &mut SLTNodeArena<GlueAddr>,
1931 context_width: Option<usize>,
1932 context_signed: Option<bool>,
1933) -> Option<(
1934 celox_slt::NodeId,
1935 HashSet<VarAtomBase<GlueAddr>>,
1936 Vec<SourceVarId>,
1937)> {
1938 match expr {
1939 sv::ir::Expr::Ident(name) => {
1940 let Some(id) = name_to_id.get(name).copied() else {
1941 let value = constants.get(name)?;
1942 let (width, signed) = parameter_types.get(name).copied().unwrap_or((32, false));
1943 let node = arena
1944 .alloc(SLTNode::Constant(
1945 parameter_value_bits(*value, width),
1946 BigUint::from(0u32),
1947 width,
1948 signed,
1949 ))
1950 .ok()?;
1951 return Some((
1952 coerce_node_width(arena, node, context_width, context_signed.unwrap_or(signed))
1953 .ok()?,
1954 HashSet::default(),
1955 Vec::new(),
1956 ));
1957 };
1958 let var = variables.get(&id)?;
1959 let width = var.width;
1960 let node = arena
1961 .alloc(SLTNode::Input {
1962 variable: GlueAddr::Parent(id),
1963 signed: var.signed,
1964 index: Vec::new(),
1965 access: BitAccess::new(0, width - 1),
1966 })
1967 .ok()?;
1968 let mut sources = HashSet::default();
1969 sources.insert(VarAtomBase::new(GlueAddr::Parent(id), 0, width - 1));
1970 Some((
1971 coerce_node_width(
1972 arena,
1973 node,
1974 context_width,
1975 context_signed.unwrap_or(var.signed),
1976 )
1977 .ok()?,
1978 sources,
1979 vec![id],
1980 ))
1981 }
1982 sv::ir::Expr::Select {
1983 expr,
1984 msb,
1985 lsb,
1986 signed,
1987 } => {
1988 if let Some((id, element_width, access)) = dynamic_array_element_subselection(
1989 expr,
1990 msb,
1991 lsb,
1992 variables,
1993 name_to_id,
1994 constants,
1995 parameter_types,
1996 ) {
1997 let (offset, mut sources, mut source_ids) = lower_dynamic_array_element_index_glue(
1998 lsb,
1999 variables,
2000 name_to_id,
2001 constants,
2002 parameter_types,
2003 arena,
2004 element_width,
2005 )?;
2006 let variable = variables.get(&id)?;
2007 let element_count = variable.width.checked_div(element_width)?;
2008 let (offset, valid) = dynamic_array_index_guard_slt(arena, offset, element_count)?;
2009 let node = lower_dynamic_array_selection_slt(
2010 arena,
2011 GlueAddr::Parent(id),
2012 *signed,
2013 offset,
2014 access,
2015 element_width,
2016 variable,
2017 )?;
2018 let node = guard_dynamic_array_read_slt(
2019 arena,
2020 valid,
2021 node,
2022 access.msb - access.lsb + 1,
2023 variable.is_4state,
2024 )?;
2025 sources.insert(VarAtomBase::new(
2026 GlueAddr::Parent(id),
2027 0,
2028 variable.width.checked_sub(1)?,
2029 ));
2030 source_ids.push(id);
2031 source_ids.sort();
2032 source_ids.dedup();
2033 return Some((
2034 coerce_node_width(
2035 arena,
2036 node,
2037 context_width,
2038 context_signed.unwrap_or(*signed),
2039 )
2040 .ok()?,
2041 sources,
2042 source_ids,
2043 ));
2044 }
2045 let (inner, sources, source_ids) = lower_glue_parent_expr(
2046 expr,
2047 variables,
2048 name_to_id,
2049 constants,
2050 parameter_types,
2051 arena,
2052 None,
2053 None,
2054 )?;
2055 let msb_value =
2056 sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)?;
2057 let lsb_value =
2058 sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types)?;
2059 let (msb, lsb) =
2060 packed_expr_select_offsets(expr, msb_value, lsb_value, variables, name_to_id)?;
2061 let access = BitAccess::new(msb.min(lsb), msb.max(lsb));
2062 let node = arena
2063 .alloc(SLTNode::Slice {
2064 expr: inner,
2065 access,
2066 })
2067 .ok()?;
2068 let sources = select_sources(expr, sources, access)?;
2069 Some((
2070 coerce_node_width(
2071 arena,
2072 node,
2073 context_width,
2074 context_signed.unwrap_or(*signed),
2075 )
2076 .ok()?,
2077 sources,
2078 source_ids,
2079 ))
2080 }
2081 sv::ir::Expr::Concat(parts) => {
2082 let mut nodes = Vec::new();
2083 let mut sources = HashSet::default();
2084 let mut source_ids = Vec::new();
2085 for part in parts {
2086 let (node, part_sources, part_source_ids) =
2087 if let Some(fill) = expr_unbased_fill_literal(part) {
2088 (
2089 lower_unbased_fill_literal_slt(arena, fill, 1)?,
2090 HashSet::default(),
2091 Vec::new(),
2092 )
2093 } else {
2094 lower_glue_parent_expr(
2095 part,
2096 variables,
2097 name_to_id,
2098 constants,
2099 parameter_types,
2100 arena,
2101 None,
2102 None,
2103 )?
2104 };
2105 let width = celox_slt::get_width(node, arena);
2106 nodes.push((node, width));
2107 sources.extend(part_sources);
2108 source_ids.extend(part_source_ids);
2109 }
2110 source_ids.sort();
2111 source_ids.dedup();
2112 let node = arena.alloc(SLTNode::Concat(nodes)).ok()?;
2113 Some((
2114 coerce_node_width(arena, node, context_width, context_signed.unwrap_or(false))
2115 .ok()?,
2116 sources,
2117 source_ids,
2118 ))
2119 }
2120 sv::ir::Expr::RepeatConcat { count, parts } => {
2121 let count =
2122 sv::typecheck::eval_const_expr_with_types(count, constants, parameter_types)?;
2123 let count = usize::try_from(count).ok()?;
2124 let mut nodes = Vec::new();
2125 let mut sources = HashSet::default();
2126 let mut source_ids = Vec::new();
2127 for _ in 0..count {
2128 for part in parts {
2129 let (node, part_sources, part_source_ids) =
2130 if let Some(fill) = expr_unbased_fill_literal(part) {
2131 (
2132 lower_unbased_fill_literal_slt(arena, fill, 1)?,
2133 HashSet::default(),
2134 Vec::new(),
2135 )
2136 } else {
2137 lower_glue_parent_expr(
2138 part,
2139 variables,
2140 name_to_id,
2141 constants,
2142 parameter_types,
2143 arena,
2144 None,
2145 None,
2146 )?
2147 };
2148 let width = celox_slt::get_width(node, arena);
2149 nodes.push((node, width));
2150 sources.extend(part_sources);
2151 source_ids.extend(part_source_ids);
2152 }
2153 }
2154 source_ids.sort();
2155 source_ids.dedup();
2156 let node = arena.alloc(SLTNode::Concat(nodes)).ok()?;
2157 Some((
2158 coerce_node_width(arena, node, context_width, context_signed.unwrap_or(false))
2159 .ok()?,
2160 sources,
2161 source_ids,
2162 ))
2163 }
2164 sv::ir::Expr::Resize {
2165 expr,
2166 width,
2167 signed,
2168 } => {
2169 let (inner, sources, source_ids) = lower_glue_parent_expr(
2170 expr,
2171 variables,
2172 name_to_id,
2173 constants,
2174 parameter_types,
2175 arena,
2176 Some(*width),
2177 Some(*signed),
2178 )?;
2179 let resized = coerce_node_width(arena, inner, Some(*width), *signed).ok()?;
2180 Some((
2181 coerce_node_width(
2182 arena,
2183 resized,
2184 context_width,
2185 context_signed.unwrap_or(*signed),
2186 )
2187 .ok()?,
2188 sources,
2189 source_ids,
2190 ))
2191 }
2192 sv::ir::Expr::Literal(literal) => {
2193 if let Some(width) = context_width
2194 && let Some(fill) = unbased_fill_literal(literal)
2195 {
2196 return Some((
2197 lower_unbased_fill_literal_slt(arena, fill, width)?,
2198 HashSet::default(),
2199 Vec::new(),
2200 ));
2201 }
2202 let literal = sv::typecheck::parse_integral_literal(literal)?;
2203 let signed = literal.signed;
2204 let node = arena
2205 .alloc(SLTNode::Constant(
2206 literal.value,
2207 literal.mask,
2208 literal.width,
2209 signed,
2210 ))
2211 .ok()?;
2212 Some((
2213 coerce_node_width(arena, node, context_width, context_signed.unwrap_or(signed))
2214 .ok()?,
2215 HashSet::default(),
2216 Vec::new(),
2217 ))
2218 }
2219 sv::ir::Expr::Unary { op, expr } => {
2220 let one_bit_result = matches!(
2221 op,
2222 sv::ir::UnaryOp::LogicNot
2223 | sv::ir::UnaryOp::RedAnd
2224 | sv::ir::UnaryOp::RedOr
2225 | sv::ir::UnaryOp::RedXor
2226 );
2227 let operand_context = (!one_bit_result).then_some(context_width).flatten();
2228 let operand_signed = context_signed.or_else(|| {
2229 Some(sv_glue_expr_is_signed(
2230 expr,
2231 variables,
2232 name_to_id,
2233 parameter_types,
2234 ))
2235 });
2236 let (inner, sources, source_ids) = lower_glue_parent_expr(
2237 expr,
2238 variables,
2239 name_to_id,
2240 constants,
2241 parameter_types,
2242 arena,
2243 operand_context,
2244 operand_signed,
2245 )?;
2246 Some((
2247 arena
2248 .alloc(SLTNode::Unary(unary_op_from_sv(*op)?, inner))
2249 .ok()?,
2250 sources,
2251 source_ids,
2252 ))
2253 }
2254 sv::ir::Expr::Binary { left, op, right } => {
2255 let left_signed = sv_glue_expr_is_signed(left, variables, name_to_id, parameter_types);
2256 let operands_signed = left_signed
2257 && sv_glue_expr_is_signed(right, variables, name_to_id, parameter_types);
2258 let operator_signed = if matches!(op, sv::ir::BinaryOp::Sar) {
2259 left_signed
2260 } else {
2261 operands_signed
2262 };
2263 let comparison = matches!(
2264 op,
2265 sv::ir::BinaryOp::Eq
2266 | sv::ir::BinaryOp::Ne
2267 | sv::ir::BinaryOp::EqCase
2268 | sv::ir::BinaryOp::NeCase
2269 | sv::ir::BinaryOp::EqWildcard
2270 | sv::ir::BinaryOp::NeWildcard
2271 | sv::ir::BinaryOp::Lt
2272 | sv::ir::BinaryOp::Le
2273 | sv::ir::BinaryOp::Gt
2274 | sv::ir::BinaryOp::Ge
2275 );
2276 let shift = matches!(
2277 op,
2278 sv::ir::BinaryOp::Shl | sv::ir::BinaryOp::Shr | sv::ir::BinaryOp::Sar
2279 );
2280 let context_determined = !comparison
2281 && !matches!(op, sv::ir::BinaryOp::LogicAnd | sv::ir::BinaryOp::LogicOr);
2282 let operation_context = context_width.map(|context_width| {
2283 context_width.max(
2284 sv_expr_natural_width(expr, variables, name_to_id, constants, parameter_types)
2285 .unwrap_or(context_width),
2286 )
2287 });
2288 let comparison_context = comparison
2289 .then(|| {
2290 sv_comparison_operand_width(
2291 left,
2292 right,
2293 variables,
2294 name_to_id,
2295 constants,
2296 parameter_types,
2297 )
2298 })
2299 .flatten();
2300 let left_context = if comparison {
2301 comparison_context
2302 } else {
2303 context_determined.then_some(operation_context).flatten()
2304 };
2305 let right_context = if comparison {
2306 comparison_context
2307 } else {
2308 (context_determined && !shift)
2309 .then_some(operation_context)
2310 .flatten()
2311 };
2312 let left_context_signed = Some(if shift { left_signed } else { operands_signed });
2313 let right_context_signed = Some(operands_signed);
2314 let context_sized_comparison = comparison;
2315 let left_fill = (context_sized_comparison || shift)
2316 .then(|| expr_unbased_fill_literal(left))
2317 .flatten();
2318 let right_fill = (context_sized_comparison || shift)
2319 .then(|| expr_unbased_fill_literal(right))
2320 .flatten();
2321 let (
2322 (mut left, mut sources, mut source_ids),
2323 (mut right, right_sources, right_source_ids),
2324 ) = match (left_fill, right_fill) {
2325 (Some(left_fill), Some(right_fill)) => {
2326 let left_width = if shift { left_context.unwrap_or(1) } else { 1 };
2327 (
2328 (
2329 lower_unbased_fill_literal_slt(arena, left_fill, left_width)?,
2330 HashSet::default(),
2331 Vec::new(),
2332 ),
2333 (
2334 lower_unbased_fill_literal_slt(arena, right_fill, 1)?,
2335 HashSet::default(),
2336 Vec::new(),
2337 ),
2338 )
2339 }
2340 (Some(fill), None) => {
2341 let right = lower_glue_parent_expr(
2342 right,
2343 variables,
2344 name_to_id,
2345 constants,
2346 parameter_types,
2347 arena,
2348 right_context,
2349 right_context_signed,
2350 )?;
2351 let width = if shift {
2352 left_context.unwrap_or(1)
2353 } else {
2354 celox_slt::get_width(right.0, arena)
2355 };
2356 (
2357 (
2358 lower_unbased_fill_literal_slt(arena, fill, width)?,
2359 HashSet::default(),
2360 Vec::new(),
2361 ),
2362 right,
2363 )
2364 }
2365 (None, Some(fill)) => {
2366 let left = lower_glue_parent_expr(
2367 left,
2368 variables,
2369 name_to_id,
2370 constants,
2371 parameter_types,
2372 arena,
2373 left_context,
2374 left_context_signed,
2375 )?;
2376 let width = if shift {
2377 1
2378 } else {
2379 celox_slt::get_width(left.0, arena)
2380 };
2381 (
2382 left,
2383 (
2384 lower_unbased_fill_literal_slt(arena, fill, width)?,
2385 HashSet::default(),
2386 Vec::new(),
2387 ),
2388 )
2389 }
2390 (None, None) => (
2391 lower_glue_parent_expr(
2392 left,
2393 variables,
2394 name_to_id,
2395 constants,
2396 parameter_types,
2397 arena,
2398 left_context,
2399 left_context_signed,
2400 )?,
2401 lower_glue_parent_expr(
2402 right,
2403 variables,
2404 name_to_id,
2405 constants,
2406 parameter_types,
2407 arena,
2408 right_context,
2409 right_context_signed,
2410 )?,
2411 ),
2412 };
2413 sources.extend(right_sources);
2414 source_ids.extend(right_source_ids);
2415 source_ids.sort();
2416 source_ids.dedup();
2417 if context_sized_comparison {
2418 let common_width =
2419 celox_slt::get_width(left, arena).max(celox_slt::get_width(right, arena));
2420 left = coerce_node_width(arena, left, Some(common_width), operands_signed).ok()?;
2421 right =
2422 coerce_node_width(arena, right, Some(common_width), operands_signed).ok()?;
2423 }
2424 Some((
2425 arena
2426 .alloc(SLTNode::Binary(
2427 left,
2428 binary_op_from_sv(*op, operator_signed),
2429 right,
2430 ))
2431 .ok()?,
2432 sources,
2433 source_ids,
2434 ))
2435 }
2436 sv::ir::Expr::Mux {
2437 condition,
2438 then_expr,
2439 else_expr,
2440 } => {
2441 let arms_signed =
2442 sv_glue_expr_is_signed(then_expr, variables, name_to_id, parameter_types)
2443 && sv_glue_expr_is_signed(else_expr, variables, name_to_id, parameter_types);
2444 let arm_context =
2445 sv_expr_natural_width(expr, variables, name_to_id, constants, parameter_types)
2446 .map(|natural_width| {
2447 context_width.map_or(natural_width, |width| width.max(natural_width))
2448 })
2449 .or(context_width);
2450 let (condition, mut sources, mut source_ids) = lower_glue_parent_expr(
2451 condition,
2452 variables,
2453 name_to_id,
2454 constants,
2455 parameter_types,
2456 arena,
2457 None,
2458 None,
2459 )?;
2460 let (mut then_expr, then_sources, then_source_ids) = lower_glue_parent_expr(
2461 then_expr,
2462 variables,
2463 name_to_id,
2464 constants,
2465 parameter_types,
2466 arena,
2467 arm_context,
2468 Some(arms_signed),
2469 )?;
2470 let (mut else_expr, else_sources, else_source_ids) = lower_glue_parent_expr(
2471 else_expr,
2472 variables,
2473 name_to_id,
2474 constants,
2475 parameter_types,
2476 arena,
2477 arm_context,
2478 Some(arms_signed),
2479 )?;
2480 sources.extend(then_sources);
2481 sources.extend(else_sources);
2482 source_ids.extend(then_source_ids);
2483 source_ids.extend(else_source_ids);
2484 source_ids.sort();
2485 source_ids.dedup();
2486 let width =
2487 celox_slt::get_width(then_expr, arena).max(celox_slt::get_width(else_expr, arena));
2488 then_expr = coerce_node_width(arena, then_expr, Some(width), arms_signed).ok()?;
2489 else_expr = coerce_node_width(arena, else_expr, Some(width), arms_signed).ok()?;
2490 Some((
2491 arena
2492 .alloc(SLTNode::Mux {
2493 cond: condition,
2494 then_expr,
2495 else_expr,
2496 })
2497 .ok()?,
2498 sources,
2499 source_ids,
2500 ))
2501 }
2502 sv::ir::Expr::Call { .. } => None,
2503 }
2504}
2505
2506fn lower_dynamic_array_element_index_glue(
2507 offset: &sv::ir::ConstExpr,
2508 variables: &HashMap<SourceVarId, SvVariable>,
2509 name_to_id: &HashMap<String, SourceVarId>,
2510 constants: &HashMap<String, i128>,
2511 parameter_types: &HashMap<String, (usize, bool)>,
2512 arena: &mut SLTNodeArena<GlueAddr>,
2513 element_width: usize,
2514) -> Option<(NodeId, HashSet<VarAtomBase<GlueAddr>>, Vec<SourceVarId>)> {
2515 let offset_expr = expr_from_const_expr(offset)?;
2516 let (offset, sources, source_ids) = lower_glue_parent_expr(
2517 &offset_expr,
2518 variables,
2519 name_to_id,
2520 constants,
2521 parameter_types,
2522 arena,
2523 None,
2524 None,
2525 )?;
2526 let element_index = if element_width == 1 {
2527 offset
2528 } else {
2529 let divisor = arena
2530 .alloc(SLTNode::Constant(
2531 BigUint::from(element_width),
2532 BigUint::default(),
2533 64,
2534 false,
2535 ))
2536 .ok()?;
2537 arena
2538 .alloc(SLTNode::Binary(offset, BinaryOp::DivU, divisor))
2539 .ok()?
2540 };
2541 Some((element_index, sources, source_ids))
2542}
2543
2544fn next_var_id(next_id: &mut SourceVarId) -> SourceVarId {
2545 let id = *next_id;
2546 next_id.0 += 1;
2547 id
2548}
2549
2550fn signal_kind_from_port_direction(
2551 direction: sv::ir::PortDirection,
2552) -> Result<VariableKind, sv::AnalyzerError> {
2553 Ok(match direction {
2554 sv::ir::PortDirection::Input => VariableKind::Input,
2555 sv::ir::PortDirection::Output => VariableKind::Output,
2556 sv::ir::PortDirection::Inout => VariableKind::Inout,
2557 sv::ir::PortDirection::Ref => {
2558 return Err(sv::AnalyzerError::Unsupported(
2559 "ref port direction".to_string(),
2560 ));
2561 }
2562 sv::ir::PortDirection::Unspecified => VariableKind::Variable,
2563 })
2564}
2565
2566struct SvSignalType {
2567 width: usize,
2568 signed: bool,
2569 is_4state: bool,
2570 packed_ranges: Vec<(i128, i128)>,
2571 array_dims: Vec<usize>,
2572 type_kind: PortTypeKind,
2573}
2574
2575fn signal_type_from_sv(
2576 typ: &sv::ir::Type,
2577 constants: &HashMap<String, i128>,
2578 parameter_types: &HashMap<String, (usize, bool)>,
2579) -> Result<SvSignalType, sv::AnalyzerError> {
2580 let packed_width = if typ.packed_ranges().is_empty() {
2581 1
2582 } else {
2583 typ.packed_ranges()
2584 .iter()
2585 .try_fold(1usize, |acc, range| {
2586 let left = sv::typecheck::eval_const_expr_with_types(
2587 range.left(),
2588 constants,
2589 parameter_types,
2590 )?;
2591 let right = sv::typecheck::eval_const_expr_with_types(
2592 range.right(),
2593 constants,
2594 parameter_types,
2595 )?;
2596 let width = usize::try_from(left.abs_diff(right)).ok()?.checked_add(1)?;
2597 acc.checked_mul(width)
2598 })
2599 .or_else(|| typ.resolved_width())
2600 .ok_or_else(|| {
2601 sv::AnalyzerError::Unsupported("unresolved explicit packed width".to_string())
2602 })?
2603 .max(1)
2604 };
2605 let array_dims = typ
2606 .unpacked_ranges()
2607 .iter()
2608 .map(|range| {
2609 let left = sv::typecheck::eval_const_expr_with_types(
2610 range.left(),
2611 constants,
2612 parameter_types,
2613 )?;
2614 let right = sv::typecheck::eval_const_expr_with_types(
2615 range.right(),
2616 constants,
2617 parameter_types,
2618 )?;
2619 usize::try_from(left.abs_diff(right))
2620 .ok()
2621 .and_then(|width| width.checked_add(1))
2622 })
2623 .collect::<Option<Vec<_>>>()
2624 .ok_or_else(|| {
2625 sv::AnalyzerError::Unsupported("unresolved unpacked array dimension".to_string())
2626 })?;
2627 let element_count = array_dims
2628 .iter()
2629 .copied()
2630 .try_fold(1usize, usize::checked_mul)
2631 .ok_or_else(|| sv::AnalyzerError::Unsupported("signal width overflow".to_string()))?;
2632 let width = packed_width
2633 .checked_mul(element_count)
2634 .ok_or_else(|| sv::AnalyzerError::Unsupported("signal width overflow".to_string()))?;
2635 let signed = typ.is_signed();
2636 let is_4state = !matches!(typ.kind(), sv::ir::TypeKind::Bit);
2637 let packed_ranges = typ
2638 .packed_ranges()
2639 .iter()
2640 .filter_map(|range| {
2641 let left = sv::typecheck::eval_const_expr_with_types(
2642 range.left(),
2643 constants,
2644 parameter_types,
2645 )?;
2646 let right = sv::typecheck::eval_const_expr_with_types(
2647 range.right(),
2648 constants,
2649 parameter_types,
2650 )?;
2651 Some((left, right))
2652 })
2653 .collect();
2654 let type_kind = match typ.kind() {
2655 sv::ir::TypeKind::Bit => PortTypeKind::Bit,
2656 sv::ir::TypeKind::Logic | sv::ir::TypeKind::Reg | sv::ir::TypeKind::Implicit => {
2657 PortTypeKind::Logic
2658 }
2659 };
2660 Ok(SvSignalType {
2661 width,
2662 signed,
2663 is_4state,
2664 packed_ranges,
2665 array_dims,
2666 type_kind,
2667 })
2668}
2669
2670struct PreviousArrayValue {
2671 expr: NodeId,
2672 sources: HashSet<VarAtomBase<SourceVarId>>,
2673 previous_sources: HashSet<VarAtomBase<SourceVarId>>,
2674 address_sources: HashSet<VarAtomBase<SourceVarId>>,
2675}
2676
2677fn lower_previous_array_value(
2678 id: SourceVarId,
2679 width: usize,
2680 paths: &[LogicPath<SourceVarId>],
2681) -> Result<Option<PreviousArrayValue>, sv::AnalyzerError> {
2682 let mut matching = paths
2683 .iter()
2684 .filter(|path| path.target.var().is_some_and(|target| target.id == id));
2685 let Some(path) = matching.next() else {
2686 return Ok(None);
2687 };
2688 let Some(target) = path.target.var() else {
2689 unreachable!("matching path must have a variable target");
2690 };
2691 if target.access
2692 != BitAccess::new(
2693 0,
2694 width.checked_sub(1).ok_or_else(|| {
2695 sv::AnalyzerError::Unsupported("zero-width unpacked array".to_string())
2696 })?,
2697 )
2698 {
2699 return Err(sv::AnalyzerError::Unsupported(
2700 "dynamic unpacked-array assignment after an earlier partial assignment to the same array is unsupported"
2701 .to_string(),
2702 ));
2703 }
2704 Ok(Some(PreviousArrayValue {
2705 expr: path.expr,
2706 sources: path.sources.clone(),
2707 previous_sources: path.previous_sources.clone(),
2708 address_sources: path.address_sources.clone(),
2709 }))
2710}
2711
2712fn lower_comb_process(
2713 process: &sv::ir::CombProcess,
2714 variables: &HashMap<SourceVarId, SvVariable>,
2715 name_to_id: &HashMap<String, SourceVarId>,
2716 constants: &HashMap<String, i128>,
2717 parameter_types: &HashMap<String, (usize, bool)>,
2718 arena: &mut SLTNodeArena<SourceVarId>,
2719 four_state: bool,
2720) -> Result<Vec<LogicPath<SourceVarId>>, sv::AnalyzerError> {
2721 let assignments = process.assignments();
2722 if process.kind() == sv::ir::CombProcessKind::AlwaysComb {
2723 for (index, assignment) in assignments.iter().enumerate() {
2724 if assignments[index + 1..].iter().any(|later| {
2725 later.lhs_value() != assignment.lhs_value()
2726 && expr_references_ident(assignment.rhs(), later.lhs())
2727 }) {
2728 return Err(sv::AnalyzerError::Unsupported(
2729 "read-before-write dependency inside always_comb".to_string(),
2730 ));
2731 }
2732 for later_index in index + 1..assignments.len() {
2733 if assignments[later_index].lhs() != assignment.lhs() {
2734 continue;
2735 }
2736 if assignments[index + 1..=later_index]
2737 .iter()
2738 .any(|later| expr_references_ident(later.rhs(), assignment.lhs()))
2739 {
2740 return Err(sv::AnalyzerError::Unsupported(
2741 "dependent repeated assignment inside always_comb".to_string(),
2742 ));
2743 }
2744 }
2745 }
2746 }
2747 let mut paths = Vec::new();
2748 for (index, assignment) in assignments.iter().enumerate() {
2749 if process.kind() == sv::ir::CombProcessKind::AlwaysComb
2750 && assignments[index + 1..]
2751 .iter()
2752 .any(|later| later.lhs_value() == assignment.lhs_value())
2753 {
2754 continue;
2755 }
2756 let allow_dynamic_array_write = process.kind() == sv::ir::CombProcessKind::AlwaysComb;
2757 let previous_array = if allow_dynamic_array_write {
2758 if let Some((id, _, _, _)) = dynamic_array_element_lvalue(
2759 assignment.lhs_value(),
2760 variables,
2761 name_to_id,
2762 constants,
2763 parameter_types,
2764 ) {
2765 let width = variables
2766 .get(&id)
2767 .map(|variable| variable.width)
2768 .ok_or_else(|| {
2769 sv::AnalyzerError::Unsupported(
2770 "dynamic unpacked-array assignment target".to_string(),
2771 )
2772 })?;
2773 lower_previous_array_value(id, width, &paths)?
2774 } else {
2775 None
2776 }
2777 } else {
2778 None
2779 };
2780 let path = lower_assignment(
2781 assignment,
2782 variables,
2783 name_to_id,
2784 constants,
2785 parameter_types,
2786 arena,
2787 four_state,
2788 allow_dynamic_array_write,
2789 previous_array.as_ref(),
2790 )?;
2791 merge_overlapping_comb_path(&mut paths, path, arena)?;
2792 }
2793 Ok(paths)
2794}
2795
2796fn merge_overlapping_comb_path(
2797 paths: &mut Vec<LogicPath<SourceVarId>>,
2798 mut later: LogicPath<SourceVarId>,
2799 arena: &mut SLTNodeArena<SourceVarId>,
2800) -> Result<(), sv::AnalyzerError> {
2801 let mut index = 0;
2802 while index < paths.len() {
2803 let Some(previous_target) = paths[index].target.var() else {
2804 index += 1;
2805 continue;
2806 };
2807 let Some(later_target) = later.target.var() else {
2808 break;
2809 };
2810 if previous_target.id != later_target.id
2811 || !previous_target.access.overlaps(&later_target.access)
2812 {
2813 index += 1;
2814 continue;
2815 }
2816 let previous = paths.remove(index);
2817 later = overlay_comb_paths(previous, later, arena)?;
2818 }
2819 paths.push(later);
2820 Ok(())
2821}
2822
2823fn overlay_comb_paths(
2824 previous: LogicPath<SourceVarId>,
2825 later: LogicPath<SourceVarId>,
2826 arena: &mut SLTNodeArena<SourceVarId>,
2827) -> Result<LogicPath<SourceVarId>, sv::AnalyzerError> {
2828 let previous_target = previous.target.var().expect("variable path target");
2829 let later_target = later.target.var().expect("variable path target");
2830 debug_assert_eq!(previous_target.id, later_target.id);
2831 debug_assert!(previous_target.access.overlaps(&later_target.access));
2832
2833 let access = BitAccess::new(
2834 previous_target.access.lsb.min(later_target.access.lsb),
2835 previous_target.access.msb.max(later_target.access.msb),
2836 );
2837 let end = access.msb.checked_add(1).ok_or_else(|| {
2838 sv::AnalyzerError::Unsupported("overlapping always_comb assignment width".to_string())
2839 })?;
2840 let previous_end = previous_target.access.msb.checked_add(1).ok_or_else(|| {
2841 sv::AnalyzerError::Unsupported("overlapping always_comb assignment width".to_string())
2842 })?;
2843 let later_end = later_target.access.msb.checked_add(1).ok_or_else(|| {
2844 sv::AnalyzerError::Unsupported("overlapping always_comb assignment width".to_string())
2845 })?;
2846 let mut boundaries = vec![
2847 access.lsb,
2848 end,
2849 previous_target.access.lsb,
2850 previous_end,
2851 later_target.access.lsb,
2852 later_end,
2853 ];
2854 boundaries.sort_unstable();
2855 boundaries.dedup();
2856
2857 let mut nodes = Vec::new();
2858 let mut uses_previous = false;
2859 let mut uses_later = false;
2860 for bounds in boundaries.windows(2).rev() {
2861 let segment = BitAccess::new(bounds[0], bounds[1] - 1);
2862 let (path, target) =
2863 if later_target.access.lsb <= segment.lsb && segment.msb <= later_target.access.msb {
2864 uses_later = true;
2865 (&later, later_target)
2866 } else {
2867 uses_previous = true;
2868 (&previous, previous_target)
2869 };
2870 let relative = BitAccess::new(
2871 segment.lsb - target.access.lsb,
2872 segment.msb - target.access.lsb,
2873 );
2874 let node = if relative == BitAccess::new(0, target.access.msb - target.access.lsb) {
2875 path.expr
2876 } else {
2877 arena
2878 .alloc(SLTNode::Slice {
2879 expr: path.expr,
2880 access: relative,
2881 })
2882 .map_err(|error| {
2883 sv::AnalyzerError::Unsupported(format!(
2884 "overlapping always_comb assignment: {error}"
2885 ))
2886 })?
2887 };
2888 nodes.push((node, segment.msb - segment.lsb + 1));
2889 }
2890 let expr = if nodes.len() == 1 {
2891 nodes[0].0
2892 } else {
2893 arena.alloc(SLTNode::Concat(nodes)).map_err(|error| {
2894 sv::AnalyzerError::Unsupported(format!("overlapping always_comb assignment: {error}"))
2895 })?
2896 };
2897 let mut sources = HashSet::default();
2898 if uses_previous {
2899 sources.extend(previous.sources);
2900 }
2901 if uses_later {
2902 sources.extend(later.sources);
2903 }
2904 let mut previous_sources = HashSet::default();
2905 if uses_previous {
2906 previous_sources.extend(previous.previous_sources);
2907 }
2908 if uses_later {
2909 previous_sources.extend(later.previous_sources);
2910 }
2911 let mut address_sources = HashSet::default();
2912 if uses_previous {
2913 address_sources.extend(previous.address_sources);
2914 }
2915 if uses_later {
2916 address_sources.extend(later.address_sources);
2917 }
2918 Ok(LogicPath {
2919 target: LogicPathTarget::Var(VarAtomBase::new(previous_target.id, access.lsb, access.msb)),
2920 expr,
2921 sources,
2922 address_sources,
2923 previous_sources,
2924 local_inputs: Vec::new(),
2925 order_before: HashSet::default(),
2926 comb_capture_enable_sites: Vec::new(),
2927 comb_capture_enable_always: false,
2928 pre_lower_nodes: Vec::new(),
2929 })
2930}
2931
2932fn expr_references_ident(expr: &sv::ir::Expr, name: &str) -> bool {
2933 match expr {
2934 sv::ir::Expr::Ident(ident) => ident == name,
2935 sv::ir::Expr::Literal(_) => false,
2936 sv::ir::Expr::Select { expr, .. }
2937 | sv::ir::Expr::Resize { expr, .. }
2938 | sv::ir::Expr::Unary { expr, .. } => expr_references_ident(expr, name),
2939 sv::ir::Expr::Concat(parts) | sv::ir::Expr::RepeatConcat { parts, .. } => {
2940 parts.iter().any(|part| expr_references_ident(part, name))
2941 }
2942 sv::ir::Expr::Binary { left, right, .. } => {
2943 expr_references_ident(left, name) || expr_references_ident(right, name)
2944 }
2945 sv::ir::Expr::Mux {
2946 condition,
2947 then_expr,
2948 else_expr,
2949 } => {
2950 expr_references_ident(condition, name)
2951 || expr_references_ident(then_expr, name)
2952 || expr_references_ident(else_expr, name)
2953 }
2954 sv::ir::Expr::Call { args, .. } => args.iter().any(|arg| expr_references_ident(arg, name)),
2955 }
2956}
2957
2958fn lower_dynamic_array_write_expr(
2959 lvalue: &sv::ir::LValue,
2960 rhs: &sv::ir::Expr,
2961 variables: &HashMap<SourceVarId, SvVariable>,
2962 name_to_id: &HashMap<String, SourceVarId>,
2963 constants: &HashMap<String, i128>,
2964 parameter_types: &HashMap<String, (usize, bool)>,
2965 arena: &mut SLTNodeArena<SourceVarId>,
2966 previous_array: Option<&PreviousArrayValue>,
2967) -> Option<(
2968 LogicPathTarget<SourceVarId>,
2969 celox_slt::NodeId,
2970 HashSet<VarAtomBase<SourceVarId>>,
2971 HashSet<VarAtomBase<SourceVarId>>,
2972)> {
2973 let (id, element_width, offset, access) =
2974 dynamic_array_element_lvalue(lvalue, variables, name_to_id, constants, parameter_types)?;
2975 let variable = variables.get(&id)?;
2976 let element_count = variable.width.checked_div(element_width)?;
2977 if element_count == 0 {
2978 return None;
2979 }
2980 let array_width = variable.width;
2981 let target_width = access.msb - access.lsb + 1;
2982 let (rhs_node, mut sources) = if let sv::ir::Expr::Literal(literal) = rhs
2983 && let Some(fill) = unbased_fill_literal(literal)
2984 {
2985 (
2986 lower_unbased_fill_literal_slt(arena, fill, target_width)?,
2987 HashSet::default(),
2988 )
2989 } else {
2990 lower_expr_with_context(
2991 rhs,
2992 variables,
2993 name_to_id,
2994 constants,
2995 parameter_types,
2996 arena,
2997 Some(target_width),
2998 Some(sv_expr_is_signed_with_parameters(
2999 rhs,
3000 variables,
3001 name_to_id,
3002 parameter_types,
3003 )),
3004 )?
3005 };
3006 let rhs_node = coerce_node_width(
3007 arena,
3008 rhs_node,
3009 Some(target_width),
3010 sv_expr_is_signed_with_parameters(rhs, variables, name_to_id, parameter_types),
3011 )
3012 .ok()?;
3013 let (element_index, index_sources) = lower_dynamic_array_element_index_slt(
3014 &offset,
3015 variables,
3016 name_to_id,
3017 constants,
3018 parameter_types,
3019 arena,
3020 element_width,
3021 )?;
3022 sources.extend(index_sources);
3023 let (old, previous_sources) = if let Some(previous_array) = previous_array {
3024 sources.extend(previous_array.sources.iter().copied());
3025 sources.extend(previous_array.address_sources.iter().copied());
3026 (previous_array.expr, previous_array.previous_sources.clone())
3027 } else {
3028 let previous_sources = [VarAtomBase::new(id, 0, array_width.checked_sub(1)?)]
3029 .into_iter()
3030 .collect();
3031 let old = arena
3032 .alloc(SLTNode::Input {
3033 variable: id,
3034 signed: variable.signed,
3035 index: Vec::new(),
3036 access: BitAccess::new(0, array_width - 1),
3037 })
3038 .ok()?;
3039 (old, previous_sources)
3040 };
3041 let mut parts = Vec::with_capacity(element_count);
3042 for element in (0..element_count).rev() {
3043 let lsb = element.checked_mul(element_width)?;
3044 let old_element = arena
3045 .alloc(SLTNode::Slice {
3046 expr: old,
3047 access: BitAccess::new(lsb, lsb + element_width - 1),
3048 })
3049 .ok()?;
3050 let element_literal = arena
3051 .alloc(SLTNode::Constant(
3052 BigUint::from(element),
3053 BigUint::default(),
3054 64,
3055 false,
3056 ))
3057 .ok()?;
3058 let condition = arena
3059 .alloc(SLTNode::Binary(
3060 element_index,
3061 BinaryOp::EqCase,
3062 element_literal,
3063 ))
3064 .ok()?;
3065 let updated_element = replace_slt_slice(
3066 arena,
3067 old_element,
3068 rhs_node,
3069 access.lsb,
3070 target_width,
3071 element_width,
3072 )?;
3073 let updated = arena
3074 .alloc(SLTNode::Mux {
3075 cond: condition,
3076 then_expr: updated_element,
3077 else_expr: old_element,
3078 })
3079 .ok()?;
3080 parts.push((updated, element_width));
3081 }
3082 let expr = if parts.len() == 1 {
3083 parts[0].0
3084 } else {
3085 arena.alloc(SLTNode::Concat(parts)).ok()?
3086 };
3087 Some((
3088 LogicPathTarget::Var(VarAtomBase::new(id, 0, array_width - 1)),
3089 expr,
3090 sources,
3091 previous_sources,
3092 ))
3093}
3094
3095fn replace_slt_slice<A: std::hash::Hash + Eq + Clone>(
3096 arena: &mut SLTNodeArena<A>,
3097 current: NodeId,
3098 replacement: NodeId,
3099 lsb: usize,
3100 replacement_width: usize,
3101 total_width: usize,
3102) -> Option<NodeId> {
3103 if lsb == 0 && replacement_width == total_width {
3104 return Some(replacement);
3105 }
3106 let end = lsb.checked_add(replacement_width)?;
3107 if end > total_width {
3108 return None;
3109 }
3110
3111 let mut parts = Vec::with_capacity(3);
3112 if end < total_width {
3113 let upper_width = total_width - end;
3114 let upper = arena
3115 .alloc(SLTNode::Slice {
3116 expr: current,
3117 access: BitAccess::new(end, total_width - 1),
3118 })
3119 .ok()?;
3120 parts.push((upper, upper_width));
3121 }
3122 parts.push((replacement, replacement_width));
3123 if lsb != 0 {
3124 let lower = arena
3125 .alloc(SLTNode::Slice {
3126 expr: current,
3127 access: BitAccess::new(0, lsb - 1),
3128 })
3129 .ok()?;
3130 parts.push((lower, lsb));
3131 }
3132 arena.alloc(SLTNode::Concat(parts)).ok()
3133}
3134
3135fn permute_reversed_lvalue_rhs_slt(
3136 lvalue: &sv::ir::LValue,
3137 expr: NodeId,
3138 target_width: usize,
3139 constants: &HashMap<String, i128>,
3140 parameter_types: &HashMap<String, (usize, bool)>,
3141 arena: &mut SLTNodeArena<SourceVarId>,
3142) -> Option<NodeId> {
3143 let sv::ir::LValue::Select {
3144 array_slice_width: Some(array_slice_width),
3145 array_slice_reversed: true,
3146 ..
3147 } = lvalue
3148 else {
3149 return Some(expr);
3150 };
3151 let element_width = usize::try_from(sv::typecheck::eval_const_expr_with_types(
3152 array_slice_width,
3153 constants,
3154 parameter_types,
3155 )?)
3156 .ok()
3157 .filter(|width| *width != 0)?;
3158 if !target_width.is_multiple_of(element_width) {
3159 return None;
3160 }
3161 let element_count = target_width / element_width;
3162 if element_count <= 1 {
3163 return Some(expr);
3164 }
3165 let mut parts = Vec::with_capacity(element_count);
3166 for lsb in (0..target_width).step_by(element_width) {
3167 let msb = lsb.checked_add(element_width)?.checked_sub(1)?;
3168 let part = arena
3169 .alloc(SLTNode::Slice {
3170 expr,
3171 access: BitAccess::new(lsb, msb),
3172 })
3173 .ok()?;
3174 parts.push((part, element_width));
3175 }
3176 arena.alloc(SLTNode::Concat(parts)).ok()
3177}
3178
3179fn lower_assignment(
3180 assignment: &sv::ir::Assignment,
3181 variables: &HashMap<SourceVarId, SvVariable>,
3182 name_to_id: &HashMap<String, SourceVarId>,
3183 constants: &HashMap<String, i128>,
3184 parameter_types: &HashMap<String, (usize, bool)>,
3185 arena: &mut SLTNodeArena<SourceVarId>,
3186 four_state: bool,
3187 allow_dynamic_array_write: bool,
3188 previous_array: Option<&PreviousArrayValue>,
3189) -> Result<LogicPath<SourceVarId>, sv::AnalyzerError> {
3190 let rhs = expr_for_state_mode(assignment.rhs(), four_state);
3191 if allow_dynamic_array_write
3192 && let Some((target, expr, sources, previous_sources)) = lower_dynamic_array_write_expr(
3193 assignment.lhs_value(),
3194 &rhs,
3195 variables,
3196 name_to_id,
3197 constants,
3198 parameter_types,
3199 arena,
3200 previous_array,
3201 )
3202 {
3203 let target_width = target
3204 .var()
3205 .map(|target| target.access.msb - target.access.lsb + 1)
3206 .ok_or_else(|| {
3207 sv::AnalyzerError::Unsupported(format!(
3208 "combinational assignment target `{}`",
3209 assignment.lhs()
3210 ))
3211 })?;
3212 let mut expr = coerce_node_width(
3213 arena,
3214 expr,
3215 Some(target_width),
3216 sv_expr_is_signed_with_parameters(&rhs, variables, name_to_id, parameter_types),
3217 )
3218 .map_err(|error| {
3219 sv::AnalyzerError::Unsupported(format!(
3220 "combinational assignment width coercion for `{}`: {error}",
3221 assignment.lhs()
3222 ))
3223 })?;
3224 let target_is_two_state = target
3225 .var()
3226 .and_then(|target| variables.get(&target.id))
3227 .is_some_and(|variable| !variable.is_4state);
3228 if target_is_two_state || (!four_state && expr_is_unknown_literal(&rhs)) {
3229 expr = arena
3230 .alloc(SLTNode::Unary(UnaryOp::ToTwoState, expr))
3231 .map_err(|error| {
3232 sv::AnalyzerError::Unsupported(format!(
3233 "two-state conversion for `{}`: {error}",
3234 assignment.lhs()
3235 ))
3236 })?;
3237 }
3238 return Ok(LogicPath {
3239 target,
3240 expr,
3241 sources,
3242 address_sources: HashSet::default(),
3243 previous_sources,
3244 local_inputs: Vec::new(),
3245 order_before: HashSet::default(),
3246 comb_capture_enable_sites: Vec::new(),
3247 comb_capture_enable_always: false,
3248 pre_lower_nodes: Vec::new(),
3249 });
3250 }
3251 let target = lower_lvalue_target(
3252 assignment.lhs_value(),
3253 variables,
3254 name_to_id,
3255 constants,
3256 parameter_types,
3257 )
3258 .ok_or_else(|| {
3259 sv::AnalyzerError::Unsupported(format!(
3260 "combinational assignment target `{}`",
3261 assignment.lhs()
3262 ))
3263 })?;
3264 let target_width = target
3265 .var()
3266 .map(|target| target.access.msb - target.access.lsb + 1)
3267 .ok_or_else(|| {
3268 sv::AnalyzerError::Unsupported(format!(
3269 "combinational assignment target `{}`",
3270 assignment.lhs()
3271 ))
3272 })?;
3273 let (expr, sources) = if let sv::ir::Expr::Literal(literal) = &rhs
3274 && let Some(fill) = unbased_fill_literal(literal)
3275 {
3276 (
3277 lower_unbased_fill_literal_slt(arena, fill, target_width).ok_or_else(|| {
3278 sv::AnalyzerError::Unsupported(format!("combinational expression `{literal}`"))
3279 })?,
3280 HashSet::default(),
3281 )
3282 } else {
3283 lower_expr_with_context(
3284 &rhs,
3285 variables,
3286 name_to_id,
3287 constants,
3288 parameter_types,
3289 arena,
3290 Some(target_width),
3291 Some(sv_expr_is_signed_with_parameters(
3292 &rhs,
3293 variables,
3294 name_to_id,
3295 parameter_types,
3296 )),
3297 )
3298 .ok_or_else(|| {
3299 sv::AnalyzerError::Unsupported(format!(
3300 "combinational expression assigned to `{}`",
3301 assignment.lhs()
3302 ))
3303 })?
3304 };
3305 let mut expr = coerce_node_width(
3306 arena,
3307 expr,
3308 Some(target_width),
3309 sv_expr_is_signed_with_parameters(&rhs, variables, name_to_id, parameter_types),
3310 )
3311 .map_err(|error| {
3312 sv::AnalyzerError::Unsupported(format!(
3313 "combinational assignment width coercion for `{}`: {error}",
3314 assignment.lhs()
3315 ))
3316 })?;
3317 expr = permute_reversed_lvalue_rhs_slt(
3318 assignment.lhs_value(),
3319 expr,
3320 target_width,
3321 constants,
3322 parameter_types,
3323 arena,
3324 )
3325 .ok_or_else(|| {
3326 sv::AnalyzerError::Unsupported(format!(
3327 "combinational assignment lvalue order for `{}`",
3328 assignment.lhs()
3329 ))
3330 })?;
3331 let target_is_two_state = target
3332 .var()
3333 .and_then(|target| variables.get(&target.id))
3334 .is_some_and(|variable| !variable.is_4state);
3335 if target_is_two_state || (!four_state && expr_is_unknown_literal(&rhs)) {
3336 expr = arena
3337 .alloc(SLTNode::Unary(UnaryOp::ToTwoState, expr))
3338 .map_err(|error| {
3339 sv::AnalyzerError::Unsupported(format!(
3340 "two-state conversion for `{}`: {error}",
3341 assignment.lhs()
3342 ))
3343 })?;
3344 }
3345 Ok(LogicPath {
3346 target,
3347 expr,
3348 sources,
3349 address_sources: HashSet::default(),
3350 previous_sources: HashSet::default(),
3351 local_inputs: Vec::new(),
3352 order_before: HashSet::default(),
3353 comb_capture_enable_sites: Vec::new(),
3354 comb_capture_enable_always: false,
3355 pre_lower_nodes: Vec::new(),
3356 })
3357}
3358
3359fn lower_lvalue_target(
3360 lvalue: &sv::ir::LValue,
3361 variables: &HashMap<SourceVarId, SvVariable>,
3362 name_to_id: &HashMap<String, SourceVarId>,
3363 constants: &HashMap<String, i128>,
3364 parameter_types: &HashMap<String, (usize, bool)>,
3365) -> Option<LogicPathTarget<SourceVarId>> {
3366 let target_id = *name_to_id.get(lvalue.name())?;
3367 let target_width = variables.get(&target_id)?.width;
3368 let (lsb, msb) = match lvalue {
3369 sv::ir::LValue::Ident(_) => (0, target_width.checked_sub(1)?),
3370 sv::ir::LValue::Select { msb, lsb, .. } => {
3371 let msb = sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)?;
3372 let lsb = sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types)?;
3373 let variable = variables.get(&target_id)?;
3374 let msb = packed_index_offset(variable, msb)?;
3375 let lsb = packed_index_offset(variable, lsb)?;
3376 (lsb.min(msb), lsb.max(msb))
3377 }
3378 };
3379 (lsb <= msb && msb < target_width)
3380 .then(|| LogicPathTarget::Var(VarAtomBase::new(target_id, lsb, msb)))
3381}
3382
3383fn lower_expr(
3384 expr: &sv::ir::Expr,
3385 variables: &HashMap<SourceVarId, SvVariable>,
3386 name_to_id: &HashMap<String, SourceVarId>,
3387 constants: &HashMap<String, i128>,
3388 parameter_types: &HashMap<String, (usize, bool)>,
3389 arena: &mut SLTNodeArena<SourceVarId>,
3390) -> Option<(celox_slt::NodeId, HashSet<VarAtomBase<SourceVarId>>)> {
3391 lower_expr_with_context(
3392 expr,
3393 variables,
3394 name_to_id,
3395 constants,
3396 parameter_types,
3397 arena,
3398 None,
3399 None,
3400 )
3401}
3402
3403fn lower_expr_with_context(
3404 expr: &sv::ir::Expr,
3405 variables: &HashMap<SourceVarId, SvVariable>,
3406 name_to_id: &HashMap<String, SourceVarId>,
3407 constants: &HashMap<String, i128>,
3408 parameter_types: &HashMap<String, (usize, bool)>,
3409 arena: &mut SLTNodeArena<SourceVarId>,
3410 context_width: Option<usize>,
3411 context_signed: Option<bool>,
3412) -> Option<(celox_slt::NodeId, HashSet<VarAtomBase<SourceVarId>>)> {
3413 match expr {
3414 sv::ir::Expr::Ident(name) => {
3415 let Some(id) = name_to_id.get(name).copied() else {
3416 let value = constants.get(name)?;
3417 let (width, signed) = parameter_types.get(name).copied().unwrap_or((32, false));
3418 let node = arena
3419 .alloc(SLTNode::Constant(
3420 parameter_value_bits(*value, width),
3421 BigUint::from(0u32),
3422 width,
3423 signed,
3424 ))
3425 .ok()?;
3426 return Some((
3427 coerce_node_width(arena, node, context_width, context_signed.unwrap_or(signed))
3428 .ok()?,
3429 HashSet::default(),
3430 ));
3431 };
3432 let var = variables.get(&id)?;
3433 let width = var.width;
3434 let node = arena
3435 .alloc(SLTNode::Input {
3436 variable: id,
3437 signed: var.signed,
3438 index: Vec::new(),
3439 access: BitAccess::new(0, width - 1),
3440 })
3441 .ok()?;
3442 let mut sources = HashSet::default();
3443 sources.insert(VarAtomBase::new(id, 0, width - 1));
3444 Some((
3445 coerce_node_width(
3446 arena,
3447 node,
3448 context_width,
3449 context_signed.unwrap_or(var.signed),
3450 )
3451 .ok()?,
3452 sources,
3453 ))
3454 }
3455 sv::ir::Expr::Select {
3456 expr,
3457 msb,
3458 lsb,
3459 signed,
3460 } => {
3461 if let Some((id, element_width, access)) = dynamic_array_element_subselection(
3462 expr,
3463 msb,
3464 lsb,
3465 variables,
3466 name_to_id,
3467 constants,
3468 parameter_types,
3469 ) {
3470 let (offset, mut sources) = lower_dynamic_array_element_index_slt(
3471 lsb,
3472 variables,
3473 name_to_id,
3474 constants,
3475 parameter_types,
3476 arena,
3477 element_width,
3478 )?;
3479 let variable = variables.get(&id)?;
3480 let element_count = variable.width.checked_div(element_width)?;
3481 let (offset, valid) = dynamic_array_index_guard_slt(arena, offset, element_count)?;
3482 let node = lower_dynamic_array_selection_slt(
3483 arena,
3484 id,
3485 *signed,
3486 offset,
3487 access,
3488 element_width,
3489 variable,
3490 )?;
3491 let node = guard_dynamic_array_read_slt(
3492 arena,
3493 valid,
3494 node,
3495 access.msb - access.lsb + 1,
3496 variable.is_4state,
3497 )?;
3498 sources.insert(VarAtomBase::new(id, 0, variable.width.checked_sub(1)?));
3499 return Some((
3500 coerce_node_width(
3501 arena,
3502 node,
3503 context_width,
3504 context_signed.unwrap_or(*signed),
3505 )
3506 .ok()?,
3507 sources,
3508 ));
3509 }
3510 let (inner, mut sources) = lower_expr(
3511 expr,
3512 variables,
3513 name_to_id,
3514 constants,
3515 parameter_types,
3516 arena,
3517 )?;
3518 let msb_value =
3519 sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)?;
3520 let lsb_value =
3521 sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types)?;
3522 let (msb, lsb) = if let sv::ir::Expr::Ident(name) = &**expr {
3523 let variable = name_to_id.get(name).and_then(|id| variables.get(id))?;
3524 (
3525 packed_index_offset(variable, msb_value)?,
3526 packed_index_offset(variable, lsb_value)?,
3527 )
3528 } else {
3529 (
3530 usize::try_from(msb_value).ok()?,
3531 usize::try_from(lsb_value).ok()?,
3532 )
3533 };
3534 let access = BitAccess::new(msb.min(lsb), msb.max(lsb));
3535 let node = arena
3536 .alloc(SLTNode::Slice {
3537 expr: inner,
3538 access,
3539 })
3540 .ok()?;
3541 sources = select_sources(expr, sources, access)?;
3542 Some((
3543 coerce_node_width(
3544 arena,
3545 node,
3546 context_width,
3547 context_signed.unwrap_or(*signed),
3548 )
3549 .ok()?,
3550 sources,
3551 ))
3552 }
3553 sv::ir::Expr::Concat(parts) => {
3554 let mut nodes = Vec::new();
3555 let mut sources = HashSet::default();
3556 for part in parts {
3557 let (node, part_sources) = lower_expr_with_context(
3558 part,
3559 variables,
3560 name_to_id,
3561 constants,
3562 parameter_types,
3563 arena,
3564 expr_unbased_fill_literal(part).map(|_| 1),
3565 None,
3566 )?;
3567 let width = celox_slt::get_width(node, arena);
3568 nodes.push((node, width));
3569 sources.extend(part_sources);
3570 }
3571 Some((arena.alloc(SLTNode::Concat(nodes)).ok()?, sources))
3572 }
3573 sv::ir::Expr::RepeatConcat { count, parts } => {
3574 let count =
3575 sv::typecheck::eval_const_expr_with_types(count, constants, parameter_types)?;
3576 let count = usize::try_from(count).ok()?;
3577 let mut repeated = Vec::new();
3578 let mut sources = HashSet::default();
3579 for _ in 0..count {
3580 for part in parts {
3581 let (node, part_sources) = lower_expr_with_context(
3582 part,
3583 variables,
3584 name_to_id,
3585 constants,
3586 parameter_types,
3587 arena,
3588 expr_unbased_fill_literal(part).map(|_| 1),
3589 None,
3590 )?;
3591 let width = celox_slt::get_width(node, arena);
3592 repeated.push((node, width));
3593 sources.extend(part_sources);
3594 }
3595 }
3596 Some((arena.alloc(SLTNode::Concat(repeated)).ok()?, sources))
3597 }
3598 sv::ir::Expr::Literal(literal) => {
3599 if let Some(width) = context_width
3600 && let Some(fill) = unbased_fill_literal(literal)
3601 {
3602 return Some((
3603 lower_unbased_fill_literal_slt(arena, fill, width)?,
3604 HashSet::default(),
3605 ));
3606 }
3607 let literal = sv::typecheck::parse_integral_literal(literal)?;
3608 let signed = literal.signed;
3609 let node = arena
3610 .alloc(SLTNode::Constant(
3611 literal.value,
3612 literal.mask,
3613 literal.width,
3614 signed,
3615 ))
3616 .ok()?;
3617 Some((
3618 coerce_node_width(arena, node, context_width, context_signed.unwrap_or(signed))
3619 .ok()?,
3620 HashSet::default(),
3621 ))
3622 }
3623 sv::ir::Expr::Unary { op, expr } => {
3624 let one_bit_result = matches!(
3625 op,
3626 sv::ir::UnaryOp::LogicNot
3627 | sv::ir::UnaryOp::RedAnd
3628 | sv::ir::UnaryOp::RedOr
3629 | sv::ir::UnaryOp::RedXor
3630 );
3631 let operand_context = (!one_bit_result).then_some(context_width).flatten();
3632 let (inner, sources) = lower_expr_with_context(
3633 expr,
3634 variables,
3635 name_to_id,
3636 constants,
3637 parameter_types,
3638 arena,
3639 operand_context,
3640 context_signed,
3641 )?;
3642 Some((
3643 arena
3644 .alloc(SLTNode::Unary(unary_op_from_sv(*op)?, inner))
3645 .ok()?,
3646 sources,
3647 ))
3648 }
3649 sv::ir::Expr::Resize {
3650 expr,
3651 width,
3652 signed,
3653 } => {
3654 let (inner, sources) = lower_expr_with_context(
3655 expr,
3656 variables,
3657 name_to_id,
3658 constants,
3659 parameter_types,
3660 arena,
3661 Some(*width),
3662 Some(*signed),
3663 )?;
3664 let resized = coerce_node_width(arena, inner, Some(*width), *signed).ok()?;
3665 Some((
3666 coerce_node_width(
3667 arena,
3668 resized,
3669 context_width,
3670 context_signed.unwrap_or(*signed),
3671 )
3672 .ok()?,
3673 sources,
3674 ))
3675 }
3676 sv::ir::Expr::Binary { left, op, right } => {
3677 let left_signed =
3678 sv_expr_is_signed_with_parameters(left, variables, name_to_id, parameter_types);
3679 let operands_signed = left_signed
3680 && sv_expr_is_signed_with_parameters(right, variables, name_to_id, parameter_types);
3681 let operator_signed = if matches!(op, sv::ir::BinaryOp::Sar) {
3682 left_signed
3683 } else {
3684 operands_signed
3685 };
3686 let comparison = matches!(
3687 op,
3688 sv::ir::BinaryOp::Eq
3689 | sv::ir::BinaryOp::Ne
3690 | sv::ir::BinaryOp::EqCase
3691 | sv::ir::BinaryOp::NeCase
3692 | sv::ir::BinaryOp::EqWildcard
3693 | sv::ir::BinaryOp::NeWildcard
3694 | sv::ir::BinaryOp::Lt
3695 | sv::ir::BinaryOp::Le
3696 | sv::ir::BinaryOp::Gt
3697 | sv::ir::BinaryOp::Ge
3698 );
3699 let shift = matches!(
3700 op,
3701 sv::ir::BinaryOp::Shl | sv::ir::BinaryOp::Shr | sv::ir::BinaryOp::Sar
3702 );
3703 let context_determined = !comparison
3704 && !matches!(op, sv::ir::BinaryOp::LogicAnd | sv::ir::BinaryOp::LogicOr);
3705 let operation_context = context_width.map(|context_width| {
3706 context_width.max(
3707 sv_expr_natural_width(expr, variables, name_to_id, constants, parameter_types)
3708 .unwrap_or(context_width),
3709 )
3710 });
3711 let comparison_context = comparison
3712 .then(|| {
3713 sv_comparison_operand_width(
3714 left,
3715 right,
3716 variables,
3717 name_to_id,
3718 constants,
3719 parameter_types,
3720 )
3721 })
3722 .flatten();
3723 let left_context = if comparison {
3724 comparison_context
3725 } else {
3726 context_determined.then_some(operation_context).flatten()
3727 };
3728 let right_context = if comparison {
3729 comparison_context
3730 } else {
3731 (context_determined && !shift)
3732 .then_some(operation_context)
3733 .flatten()
3734 };
3735 let left_fill = (comparison || shift)
3736 .then(|| expr_unbased_fill_literal(left))
3737 .flatten();
3738 let right_fill = (comparison || shift)
3739 .then(|| expr_unbased_fill_literal(right))
3740 .flatten();
3741 let ((mut left, mut sources), (mut right, right_sources)) =
3742 match (left_fill, right_fill) {
3743 (Some(left_fill), Some(right_fill)) => {
3744 let left_width = if shift { left_context.unwrap_or(1) } else { 1 };
3745 (
3746 (
3747 lower_unbased_fill_literal_slt(arena, left_fill, left_width)?,
3748 HashSet::default(),
3749 ),
3750 (
3751 lower_unbased_fill_literal_slt(arena, right_fill, 1)?,
3752 HashSet::default(),
3753 ),
3754 )
3755 }
3756 (Some(fill), None) => {
3757 let right = lower_expr_with_context(
3758 right,
3759 variables,
3760 name_to_id,
3761 constants,
3762 parameter_types,
3763 arena,
3764 right_context,
3765 Some(operands_signed),
3766 )?;
3767 let width = if shift {
3768 left_context.unwrap_or(1)
3769 } else {
3770 celox_slt::get_width(right.0, arena)
3771 };
3772 (
3773 (
3774 lower_unbased_fill_literal_slt(arena, fill, width)?,
3775 HashSet::default(),
3776 ),
3777 right,
3778 )
3779 }
3780 (None, Some(fill)) => {
3781 let left = lower_expr_with_context(
3782 left,
3783 variables,
3784 name_to_id,
3785 constants,
3786 parameter_types,
3787 arena,
3788 left_context,
3789 Some(if shift { left_signed } else { operands_signed }),
3790 )?;
3791 let width = if shift {
3792 1
3793 } else {
3794 celox_slt::get_width(left.0, arena)
3795 };
3796 (
3797 left,
3798 (
3799 lower_unbased_fill_literal_slt(arena, fill, width)?,
3800 HashSet::default(),
3801 ),
3802 )
3803 }
3804 (None, None) => (
3805 lower_expr_with_context(
3806 left,
3807 variables,
3808 name_to_id,
3809 constants,
3810 parameter_types,
3811 arena,
3812 left_context,
3813 Some(if shift { left_signed } else { operands_signed }),
3814 )?,
3815 lower_expr_with_context(
3816 right,
3817 variables,
3818 name_to_id,
3819 constants,
3820 parameter_types,
3821 arena,
3822 right_context,
3823 Some(operands_signed),
3824 )?,
3825 ),
3826 };
3827 sources.extend(right_sources);
3828 if comparison {
3829 let common_width =
3830 celox_slt::get_width(left, arena).max(celox_slt::get_width(right, arena));
3831 left = coerce_node_width(arena, left, Some(common_width), operands_signed).ok()?;
3832 right =
3833 coerce_node_width(arena, right, Some(common_width), operands_signed).ok()?;
3834 }
3835 Some((
3836 arena
3837 .alloc(SLTNode::Binary(
3838 left,
3839 binary_op_from_sv(*op, operator_signed),
3840 right,
3841 ))
3842 .ok()?,
3843 sources,
3844 ))
3845 }
3846 sv::ir::Expr::Mux {
3847 condition,
3848 then_expr,
3849 else_expr,
3850 } => {
3851 let arms_signed = sv_expr_is_signed_with_parameters(
3852 then_expr,
3853 variables,
3854 name_to_id,
3855 parameter_types,
3856 ) && sv_expr_is_signed_with_parameters(
3857 else_expr,
3858 variables,
3859 name_to_id,
3860 parameter_types,
3861 );
3862 let arm_context =
3863 sv_expr_natural_width(expr, variables, name_to_id, constants, parameter_types)
3864 .map(|natural_width| {
3865 context_width.map_or(natural_width, |width| width.max(natural_width))
3866 })
3867 .or(context_width);
3868 let (condition, mut sources) = lower_expr(
3869 condition,
3870 variables,
3871 name_to_id,
3872 constants,
3873 parameter_types,
3874 arena,
3875 )?;
3876 let (mut then_expr, then_sources) = lower_expr_with_context(
3877 then_expr,
3878 variables,
3879 name_to_id,
3880 constants,
3881 parameter_types,
3882 arena,
3883 arm_context,
3884 Some(arms_signed),
3885 )?;
3886 let (mut else_expr, else_sources) = lower_expr_with_context(
3887 else_expr,
3888 variables,
3889 name_to_id,
3890 constants,
3891 parameter_types,
3892 arena,
3893 arm_context,
3894 Some(arms_signed),
3895 )?;
3896 sources.extend(then_sources);
3897 sources.extend(else_sources);
3898 let width =
3899 celox_slt::get_width(then_expr, arena).max(celox_slt::get_width(else_expr, arena));
3900 then_expr = coerce_node_width(arena, then_expr, Some(width), arms_signed).ok()?;
3901 else_expr = coerce_node_width(arena, else_expr, Some(width), arms_signed).ok()?;
3902 Some((
3903 arena
3904 .alloc(SLTNode::Mux {
3905 cond: condition,
3906 then_expr,
3907 else_expr,
3908 })
3909 .ok()?,
3910 sources,
3911 ))
3912 }
3913 sv::ir::Expr::Call { .. } => None,
3914 }
3915}
3916
3917fn select_can_narrow_source_ranges(expr: &sv::ir::Expr) -> bool {
3918 match expr {
3919 sv::ir::Expr::Ident(_) => true,
3920 sv::ir::Expr::Select { expr, .. } => select_can_narrow_source_ranges(expr),
3921 _ => false,
3922 }
3923}
3924
3925fn select_sources<A: std::hash::Hash + Eq + Clone>(
3926 expr: &sv::ir::Expr,
3927 sources: HashSet<VarAtomBase<A>>,
3928 access: BitAccess,
3929) -> Option<HashSet<VarAtomBase<A>>> {
3930 if !select_can_narrow_source_ranges(expr) {
3931 return Some(sources);
3932 }
3933 sources
3934 .into_iter()
3935 .map(|source| {
3936 Some(VarAtomBase::new(
3937 source.id,
3938 source.access.lsb.checked_add(access.lsb)?,
3939 source.access.lsb.checked_add(access.msb)?,
3940 ))
3941 })
3942 .collect()
3943}
3944
3945fn packed_index_offset(variable: &SvVariable, index: i128) -> Option<usize> {
3946 if !variable.array_dims.is_empty() {
3947 return usize::try_from(index)
3948 .ok()
3949 .filter(|offset| *offset < variable.width);
3950 }
3951 let offset = match variable.packed_ranges.as_slice() {
3952 [(left, right)] if left >= right => index.checked_sub(*right)?,
3953 [(_, right)] => right.checked_sub(index)?,
3954 _ => index,
3955 };
3956 usize::try_from(offset)
3957 .ok()
3958 .filter(|offset| *offset < variable.width)
3959}
3960
3961fn unpacked_element_width(variable: &SvVariable) -> Option<usize> {
3962 if variable.array_dims.is_empty() {
3963 return None;
3964 }
3965 let element_count = variable
3966 .array_dims
3967 .iter()
3968 .copied()
3969 .try_fold(1usize, usize::checked_mul)?;
3970 (element_count != 0).then(|| variable.width.checked_div(element_count))?
3971}
3972
3973fn dynamic_array_selection_width(
3974 variable: &SvVariable,
3975 access: BitAccess,
3976 packed_element_width: usize,
3977) -> Option<usize> {
3978 let access_width = access.msb.checked_sub(access.lsb)?.checked_add(1)?;
3979 let element_width = packed_element_width.max(access_width);
3980 (element_width.is_multiple_of(packed_element_width)
3981 && variable.width.is_multiple_of(element_width)
3982 && access.msb < element_width)
3983 .then_some(element_width)
3984}
3985
3986fn dynamic_array_index_kind(variable: &SvVariable, element_width: usize) -> SLTIndexKind {
3987 if unpacked_element_width(variable) == Some(element_width) {
3988 SLTIndexKind::Unpacked { element_width }
3989 } else {
3990 SLTIndexKind::Packed
3991 }
3992}
3993
3994fn lower_dynamic_array_selection_slt<A: std::hash::Hash + Eq + Clone>(
3995 arena: &mut SLTNodeArena<A>,
3996 variable: A,
3997 signed: bool,
3998 index: NodeId,
3999 access: BitAccess,
4000 element_width: usize,
4001 variable_info: &SvVariable,
4002) -> Option<NodeId> {
4003 let packed_element_width = unpacked_element_width(variable_info)?;
4004 if element_width == packed_element_width {
4005 return arena
4006 .alloc(SLTNode::Input {
4007 variable,
4008 signed,
4009 index: vec![SLTIndex {
4010 node: index,
4011 stride: element_width,
4012 kind: dynamic_array_index_kind(variable_info, element_width),
4013 }],
4014 access,
4015 })
4016 .ok();
4017 }
4018 if access.lsb != 0 || access.msb.checked_add(1)? != element_width {
4019 return None;
4020 }
4021 let inner_count = element_width.checked_div(packed_element_width)?;
4022 let inner_count_literal = arena
4023 .alloc(SLTNode::Constant(
4024 BigUint::from(inner_count),
4025 BigUint::default(),
4026 64,
4027 false,
4028 ))
4029 .ok()?;
4030 let scaled_index = arena
4031 .alloc(SLTNode::Binary(index, BinaryOp::Mul, inner_count_literal))
4032 .ok()?;
4033 let mut nodes = Vec::with_capacity(inner_count);
4034 for inner_index in (0..inner_count).rev() {
4035 let node = if inner_index == 0 {
4036 scaled_index
4037 } else {
4038 let inner_index_literal = arena
4039 .alloc(SLTNode::Constant(
4040 BigUint::from(inner_index),
4041 BigUint::default(),
4042 64,
4043 false,
4044 ))
4045 .ok()?;
4046 arena
4047 .alloc(SLTNode::Binary(
4048 scaled_index,
4049 BinaryOp::Add,
4050 inner_index_literal,
4051 ))
4052 .ok()?
4053 };
4054 let node = arena
4055 .alloc(SLTNode::Input {
4056 variable: variable.clone(),
4057 signed,
4058 index: vec![SLTIndex {
4059 node,
4060 stride: packed_element_width,
4061 kind: SLTIndexKind::Unpacked {
4062 element_width: packed_element_width,
4063 },
4064 }],
4065 access: BitAccess::new(0, packed_element_width - 1),
4066 })
4067 .ok()?;
4068 nodes.push((node, packed_element_width));
4069 }
4070 arena.alloc(SLTNode::Concat(nodes)).ok()
4071}
4072
4073fn sv_memory_offset(variable: &SvVariable, bit_offset: usize, width: usize) -> SIROffset {
4074 match unpacked_element_width(variable) {
4075 Some(element_width)
4076 if element_width != 0
4077 && width > element_width
4078 && bit_offset.is_multiple_of(element_width)
4079 && width.is_multiple_of(element_width) =>
4080 {
4081 SIROffset::PackedElements {
4082 bit_offset,
4083 element_width,
4084 }
4085 }
4086 _ => SIROffset::Static(bit_offset),
4087 }
4088}
4089
4090fn packed_expr_select_offsets(
4091 expr: &sv::ir::Expr,
4092 msb: i128,
4093 lsb: i128,
4094 variables: &HashMap<SourceVarId, SvVariable>,
4095 name_to_id: &HashMap<String, SourceVarId>,
4096) -> Option<(usize, usize)> {
4097 if let sv::ir::Expr::Ident(name) = expr {
4098 if let Some(variable) = name_to_id.get(name).and_then(|id| variables.get(id)) {
4099 return Some((
4100 packed_index_offset(variable, msb)?,
4101 packed_index_offset(variable, lsb)?,
4102 ));
4103 }
4104 }
4105 Some((usize::try_from(msb).ok()?, usize::try_from(lsb).ok()?))
4106}
4107
4108fn expr_from_const_expr(expr: &sv::ir::ConstExpr) -> Option<sv::ir::Expr> {
4109 Some(match expr {
4110 sv::ir::ConstExpr::Literal(value) => sv::ir::Expr::Literal(value.clone()),
4111 sv::ir::ConstExpr::Ident(name) => sv::ir::Expr::Ident(name.clone()),
4112 sv::ir::ConstExpr::Select { expr, bit } => sv::ir::Expr::Select {
4113 expr: Box::new(expr_from_const_expr(expr)?),
4114 msb: (**bit).clone(),
4115 lsb: (**bit).clone(),
4116 signed: false,
4117 },
4118 sv::ir::ConstExpr::Function { .. } => return None,
4119 sv::ir::ConstExpr::Unary { op, expr } => sv::ir::Expr::Unary {
4120 op: *op,
4121 expr: Box::new(expr_from_const_expr(expr)?),
4122 },
4123 sv::ir::ConstExpr::Binary { left, op, right } => sv::ir::Expr::Binary {
4124 left: Box::new(expr_from_const_expr(left)?),
4125 op: *op,
4126 right: Box::new(expr_from_const_expr(right)?),
4127 },
4128 sv::ir::ConstExpr::Mux {
4129 condition,
4130 then_expr,
4131 else_expr,
4132 } => sv::ir::Expr::Mux {
4133 condition: Box::new(expr_from_const_expr(condition)?),
4134 then_expr: Box::new(expr_from_const_expr(then_expr)?),
4135 else_expr: Box::new(expr_from_const_expr(else_expr)?),
4136 },
4137 })
4138}
4139
4140fn dynamic_array_element_subselection(
4141 expr: &sv::ir::Expr,
4142 msb: &sv::ir::ConstExpr,
4143 lsb: &sv::ir::ConstExpr,
4144 variables: &HashMap<SourceVarId, SvVariable>,
4145 name_to_id: &HashMap<String, SourceVarId>,
4146 constants: &HashMap<String, i128>,
4147 parameter_types: &HashMap<String, (usize, bool)>,
4148) -> Option<(SourceVarId, usize, BitAccess)> {
4149 let sv::ir::Expr::Ident(name) = expr else {
4150 return None;
4151 };
4152 let id = *name_to_id.get(name)?;
4153 let variable = variables.get(&id)?;
4154 let packed_element_width = unpacked_element_width(variable).filter(|width| *width != 0)?;
4155 let is_dynamic = sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)
4156 .is_none()
4157 || sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types).is_none();
4158 if !is_dynamic {
4159 return None;
4160 }
4161 let (msb_base, msb_offset) = split_dynamic_array_offset(msb, constants, parameter_types)?;
4162 let (lsb_base, lsb_offset) = split_dynamic_array_offset(lsb, constants, parameter_types)?;
4163 if msb_base != lsb_base {
4164 return None;
4165 }
4166 let msb = usize::try_from(msb_offset).ok()?;
4167 let lsb = usize::try_from(lsb_offset).ok()?;
4168 let access = BitAccess::new(msb.min(lsb), msb.max(lsb));
4169 let element_width = dynamic_array_selection_width(variable, access, packed_element_width)?;
4170 if element_width > 1
4171 && !dynamic_array_base_has_stride(
4172 msb_base,
4173 i128::try_from(element_width).ok()?,
4174 constants,
4175 parameter_types,
4176 )
4177 {
4178 return None;
4179 }
4180 Some((id, element_width, access))
4181}
4182
4183fn split_dynamic_array_offset<'a>(
4184 expr: &'a sv::ir::ConstExpr,
4185 constants: &HashMap<String, i128>,
4186 parameter_types: &HashMap<String, (usize, bool)>,
4187) -> Option<(&'a sv::ir::ConstExpr, i128)> {
4188 if sv::typecheck::eval_const_expr_with_types(expr, constants, parameter_types).is_some() {
4189 return None;
4190 }
4191 if let sv::ir::ConstExpr::Binary { left, op, right } = expr
4192 && *op == sv::ir::BinaryOp::Add
4193 {
4194 if let Some(offset) =
4195 sv::typecheck::eval_const_expr_with_types(right, constants, parameter_types)
4196 && sv::typecheck::eval_const_expr_with_types(left, constants, parameter_types).is_none()
4197 {
4198 return Some((left, offset));
4199 }
4200 if let Some(offset) =
4201 sv::typecheck::eval_const_expr_with_types(left, constants, parameter_types)
4202 && sv::typecheck::eval_const_expr_with_types(right, constants, parameter_types)
4203 .is_none()
4204 {
4205 return Some((right, offset));
4206 }
4207 }
4208 Some((expr, 0))
4209}
4210
4211fn dynamic_array_base_has_stride(
4212 expr: &sv::ir::ConstExpr,
4213 element_width: i128,
4214 constants: &HashMap<String, i128>,
4215 parameter_types: &HashMap<String, (usize, bool)>,
4216) -> bool {
4217 match expr {
4218 sv::ir::ConstExpr::Binary { left, op, right } => {
4219 if *op == sv::ir::BinaryOp::Mul {
4220 let left_value =
4221 sv::typecheck::eval_const_expr_with_types(left, constants, parameter_types);
4222 let right_value =
4223 sv::typecheck::eval_const_expr_with_types(right, constants, parameter_types);
4224 if left_value.is_some_and(|value| value > 0 && value % element_width == 0)
4225 && right_value.is_none()
4226 {
4227 return true;
4228 }
4229 if right_value.is_some_and(|value| value > 0 && value % element_width == 0)
4230 && left_value.is_none()
4231 {
4232 return true;
4233 }
4234 }
4235 dynamic_array_base_has_stride(left, element_width, constants, parameter_types)
4236 || dynamic_array_base_has_stride(right, element_width, constants, parameter_types)
4237 }
4238 sv::ir::ConstExpr::Mux {
4239 then_expr,
4240 else_expr,
4241 ..
4242 } => {
4243 dynamic_array_base_has_stride(then_expr, element_width, constants, parameter_types)
4244 || dynamic_array_base_has_stride(
4245 else_expr,
4246 element_width,
4247 constants,
4248 parameter_types,
4249 )
4250 }
4251 _ => false,
4252 }
4253}
4254
4255fn dynamic_array_element_lvalue(
4256 lvalue: &sv::ir::LValue,
4257 variables: &HashMap<SourceVarId, SvVariable>,
4258 name_to_id: &HashMap<String, SourceVarId>,
4259 constants: &HashMap<String, i128>,
4260 parameter_types: &HashMap<String, (usize, bool)>,
4261) -> Option<(SourceVarId, usize, sv::ir::ConstExpr, BitAccess)> {
4262 let sv::ir::LValue::Select { name, msb, lsb, .. } = lvalue else {
4263 return None;
4264 };
4265 let id = *name_to_id.get(name)?;
4266 let variable = variables.get(&id)?;
4267 let packed_element_width = unpacked_element_width(variable).filter(|width| *width != 0)?;
4268 let is_dynamic = sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)
4269 .is_none()
4270 || sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types).is_none();
4271 if !is_dynamic {
4272 return None;
4273 }
4274 let (msb_base, msb_offset) = split_dynamic_array_offset(msb, constants, parameter_types)?;
4275 let (lsb_base, lsb_offset) = split_dynamic_array_offset(lsb, constants, parameter_types)?;
4276 if msb_base != lsb_base {
4277 return None;
4278 }
4279 let offset = lsb.clone();
4280 let msb = usize::try_from(msb_offset).ok()?;
4281 let lsb = usize::try_from(lsb_offset).ok()?;
4282 let access = BitAccess::new(msb.min(lsb), msb.max(lsb));
4283 let element_width = dynamic_array_selection_width(variable, access, packed_element_width)?;
4284 if element_width > 1
4285 && !dynamic_array_base_has_stride(
4286 msb_base,
4287 i128::try_from(element_width).ok()?,
4288 constants,
4289 parameter_types,
4290 )
4291 {
4292 return None;
4293 }
4294 (access.msb < element_width).then_some((id, element_width, offset, access))
4295}
4296
4297fn lower_dynamic_array_element_index_slt(
4298 offset: &sv::ir::ConstExpr,
4299 variables: &HashMap<SourceVarId, SvVariable>,
4300 name_to_id: &HashMap<String, SourceVarId>,
4301 constants: &HashMap<String, i128>,
4302 parameter_types: &HashMap<String, (usize, bool)>,
4303 arena: &mut SLTNodeArena<SourceVarId>,
4304 element_width: usize,
4305) -> Option<(celox_slt::NodeId, HashSet<VarAtomBase<SourceVarId>>)> {
4306 let offset_expr = expr_from_const_expr(offset)?;
4307 let (offset, sources) = lower_expr_with_context(
4308 &offset_expr,
4309 variables,
4310 name_to_id,
4311 constants,
4312 parameter_types,
4313 arena,
4314 None,
4315 None,
4316 )?;
4317 let element_index = if element_width == 1 {
4318 offset
4319 } else {
4320 let divisor = arena
4321 .alloc(SLTNode::Constant(
4322 BigUint::from(element_width),
4323 BigUint::default(),
4324 64,
4325 false,
4326 ))
4327 .ok()?;
4328 arena
4329 .alloc(SLTNode::Binary(offset, BinaryOp::DivU, divisor))
4330 .ok()?
4331 };
4332 Some((element_index, sources))
4333}
4334
4335fn dynamic_array_index_guard_slt<A: std::hash::Hash + Eq + Clone>(
4336 arena: &mut SLTNodeArena<A>,
4337 index: NodeId,
4338 element_count: usize,
4339) -> Option<(NodeId, NodeId)> {
4340 let element_count = BigUint::from(element_count);
4341 let two_state_index = arena
4342 .alloc(SLTNode::Unary(UnaryOp::ToTwoState, index))
4343 .ok()?;
4344 let known = arena
4345 .alloc(SLTNode::Binary(index, BinaryOp::EqCase, two_state_index))
4346 .ok()?;
4347 let bound = arena
4348 .alloc(SLTNode::Constant(
4349 element_count,
4350 BigUint::default(),
4351 64,
4352 false,
4353 ))
4354 .ok()?;
4355 let in_range = arena
4356 .alloc(SLTNode::Binary(index, BinaryOp::LtU, bound))
4357 .ok()?;
4358 let valid = arena
4359 .alloc(SLTNode::Binary(known, BinaryOp::LogicAnd, in_range))
4360 .ok()?;
4361 let zero = arena
4362 .alloc(SLTNode::Constant(
4363 BigUint::default(),
4364 BigUint::default(),
4365 64,
4366 false,
4367 ))
4368 .ok()?;
4369 let safe_index = arena
4370 .alloc(SLTNode::Mux {
4371 cond: valid,
4372 then_expr: index,
4373 else_expr: zero,
4374 })
4375 .ok()?;
4376 Some((safe_index, valid))
4377}
4378
4379fn guard_dynamic_array_read_slt<A: std::hash::Hash + Eq + Clone>(
4380 arena: &mut SLTNodeArena<A>,
4381 valid: NodeId,
4382 value: NodeId,
4383 value_width: usize,
4384 is_4state: bool,
4385) -> Option<NodeId> {
4386 let unknown_mask = if is_4state {
4387 (BigUint::from(1u8) << value_width) - BigUint::from(1u8)
4388 } else {
4389 BigUint::default()
4390 };
4391 let unknown = arena
4392 .alloc(SLTNode::Constant(
4393 BigUint::default(),
4394 unknown_mask,
4395 value_width,
4396 false,
4397 ))
4398 .ok()?;
4399 arena
4400 .alloc(SLTNode::Mux {
4401 cond: valid,
4402 then_expr: value,
4403 else_expr: unknown,
4404 })
4405 .ok()
4406}
4407
4408fn lower_dynamic_array_element_index(
4409 builder: &mut SIRBuilder<RegionedVarAddr>,
4410 offset: &sv::ir::ConstExpr,
4411 variables: &HashMap<SourceVarId, SvVariable>,
4412 name_to_id: &HashMap<String, SourceVarId>,
4413 constants: &HashMap<String, i128>,
4414 parameter_types: &HashMap<String, (usize, bool)>,
4415 element_width: usize,
4416) -> Option<celox_sir::RegisterId> {
4417 let offset_expr = expr_from_const_expr(offset)?;
4418 let offset = lower_expr_to_sir_with_context(
4419 builder,
4420 &offset_expr,
4421 variables,
4422 name_to_id,
4423 constants,
4424 parameter_types,
4425 None,
4426 None,
4427 )?;
4428 let offset = resize_sir_register(builder, offset, 64, false)?;
4429 if element_width == 1 {
4430 return Some(offset);
4431 }
4432 let divisor = builder.alloc_bit(64, false);
4433 builder.emit(SIRInstruction::Imm(
4434 divisor,
4435 SIRValue::new(element_width as u64),
4436 ));
4437 let index = builder.alloc_bit(64, false);
4438 builder.emit(SIRInstruction::Binary(
4439 index,
4440 offset,
4441 BinaryOp::DivU,
4442 divisor,
4443 ));
4444 Some(index)
4445}
4446
4447fn lower_dynamic_array_selection_sir(
4448 builder: &mut SIRBuilder<RegionedVarAddr>,
4449 address: RegionedVarAddr,
4450 index: celox_sir::RegisterId,
4451 access: BitAccess,
4452 element_width: usize,
4453 variable: &SvVariable,
4454) -> Option<celox_sir::RegisterId> {
4455 let packed_element_width = unpacked_element_width(variable)?;
4456 let width = access.msb.checked_sub(access.lsb)?.checked_add(1)?;
4457 if element_width == packed_element_width {
4458 let result = builder.alloc_logic(width);
4459 builder.emit(SIRInstruction::Load(
4460 result,
4461 address,
4462 SIROffset::Element {
4463 index,
4464 element_width,
4465 bit_offset: access.lsb,
4466 dynamic_bit_offset: None,
4467 },
4468 width,
4469 ));
4470 return Some(result);
4471 }
4472 if access.lsb != 0 || access.msb.checked_add(1)? != element_width {
4473 return None;
4474 }
4475 let inner_count = element_width.checked_div(packed_element_width)?;
4476 let inner_count_value = builder.alloc_bit(64, false);
4477 builder.emit(SIRInstruction::Imm(
4478 inner_count_value,
4479 SIRValue::new(u64::try_from(inner_count).ok()?),
4480 ));
4481 let scaled_index = builder.alloc_bit(64, false);
4482 builder.emit(SIRInstruction::Binary(
4483 scaled_index,
4484 index,
4485 BinaryOp::Mul,
4486 inner_count_value,
4487 ));
4488 let mut values = Vec::with_capacity(inner_count);
4489 for inner_index in (0..inner_count).rev() {
4490 let element_index = if inner_index == 0 {
4491 scaled_index
4492 } else {
4493 let inner_index_value = builder.alloc_bit(64, false);
4494 builder.emit(SIRInstruction::Imm(
4495 inner_index_value,
4496 SIRValue::new(u64::try_from(inner_index).ok()?),
4497 ));
4498 let element_index = builder.alloc_bit(64, false);
4499 builder.emit(SIRInstruction::Binary(
4500 element_index,
4501 scaled_index,
4502 BinaryOp::Add,
4503 inner_index_value,
4504 ));
4505 element_index
4506 };
4507 let value = builder.alloc_logic(packed_element_width);
4508 builder.emit(SIRInstruction::Load(
4509 value,
4510 address,
4511 SIROffset::Element {
4512 index: element_index,
4513 element_width: packed_element_width,
4514 bit_offset: 0,
4515 dynamic_bit_offset: None,
4516 },
4517 packed_element_width,
4518 ));
4519 values.push(value);
4520 }
4521 let result = builder.alloc_logic(width);
4522 builder.emit(SIRInstruction::Concat(result, values));
4523 Some(result)
4524}
4525
4526fn dynamic_array_index_guard_sir(
4527 builder: &mut SIRBuilder<RegionedVarAddr>,
4528 index: celox_sir::RegisterId,
4529 element_count: usize,
4530) -> Option<(celox_sir::RegisterId, celox_sir::RegisterId)> {
4531 let element_count = u64::try_from(element_count).ok()?;
4532 let two_state_index = builder.alloc_bit(64, false);
4533 builder.emit(SIRInstruction::Unary(
4534 two_state_index,
4535 UnaryOp::ToTwoState,
4536 index,
4537 ));
4538 let known = builder.alloc_bit(1, false);
4539 builder.emit(SIRInstruction::Binary(
4540 known,
4541 index,
4542 BinaryOp::EqCase,
4543 two_state_index,
4544 ));
4545 let bound = builder.alloc_bit(64, false);
4546 builder.emit(SIRInstruction::Imm(bound, SIRValue::new(element_count)));
4547 let in_range = builder.alloc_bit(1, false);
4548 builder.emit(SIRInstruction::Binary(
4549 in_range,
4550 index,
4551 BinaryOp::LtU,
4552 bound,
4553 ));
4554 let valid = builder.alloc_bit(1, false);
4555 builder.emit(SIRInstruction::Binary(
4556 valid,
4557 known,
4558 BinaryOp::LogicAnd,
4559 in_range,
4560 ));
4561 let zero = builder.alloc_bit(64, false);
4562 builder.emit(SIRInstruction::Imm(zero, SIRValue::new(0u8)));
4563 let safe_index = builder.alloc_bit(64, false);
4564 builder.emit(SIRInstruction::Mux(safe_index, valid, index, zero));
4565 Some((safe_index, valid))
4566}
4567
4568fn guard_dynamic_array_read_sir(
4569 builder: &mut SIRBuilder<RegionedVarAddr>,
4570 valid: celox_sir::RegisterId,
4571 value: celox_sir::RegisterId,
4572 value_width: usize,
4573 is_4state: bool,
4574) -> celox_sir::RegisterId {
4575 let unknown = builder.alloc_logic(value_width);
4576 if is_4state {
4577 let unknown_mask = (BigUint::from(1u8) << value_width) - BigUint::from(1u8);
4578 builder.emit(SIRInstruction::Imm(
4579 unknown,
4580 SIRValue::new_four_state(BigUint::default(), unknown_mask),
4581 ));
4582 } else {
4583 builder.emit(SIRInstruction::Imm(unknown, SIRValue::new(0u8)));
4584 }
4585 let guarded = builder.alloc_logic(value_width);
4586 builder.emit(SIRInstruction::Mux(guarded, valid, value, unknown));
4587 guarded
4588}
4589
4590type SvFfBlocks = (
4591 HashMap<TriggerSet<SourceVarId>, ExecutionUnit<RegionedVarAddr>>,
4592 HashMap<TriggerSet<SourceVarId>, ExecutionUnit<RegionedVarAddr>>,
4593 HashMap<TriggerSet<SourceVarId>, ExecutionUnit<RegionedVarAddr>>,
4594 HashMap<SourceVarId, SourceVarId>,
4595);
4596
4597fn lower_ff_processes(
4598 module: &sv::ir::Module,
4599 variables: &HashMap<SourceVarId, SvVariable>,
4600 name_to_id: &HashMap<String, SourceVarId>,
4601 constants: &HashMap<String, i128>,
4602 parameter_types: &HashMap<String, (usize, bool)>,
4603 four_state: bool,
4604) -> Result<SvFfBlocks, sv::AnalyzerError> {
4605 let mut eval_only_ff_blocks = HashMap::default();
4606 let mut apply_ff_blocks = HashMap::default();
4607 let mut eval_apply_ff_blocks = HashMap::default();
4608 let mut reset_clock_map = HashMap::default();
4609 let mut clock_edges = HashMap::default();
4610 let mut reset_edges = HashMap::default();
4611
4612 for process in module.ff_processes() {
4613 let clock = clock_event_from_ff_process(process)
4614 .ok_or_else(|| sv::AnalyzerError::Unsupported("always_ff event control".to_string()))?;
4615 let clock_id = *name_to_id
4616 .get(clock.signal())
4617 .ok_or_else(|| sv::AnalyzerError::Unsupported("always_ff event control".to_string()))?;
4618 if variables
4619 .get(&clock_id)
4620 .is_some_and(|variable| variable.width != 1)
4621 {
4622 return Err(sv::AnalyzerError::Unsupported(
4623 "multi-bit always_ff event signal".to_string(),
4624 ));
4625 }
4626 if four_state
4627 && variables
4628 .get(&clock_id)
4629 .is_some_and(|variable| variable.is_4state)
4630 {
4631 return Err(sv::AnalyzerError::Unsupported(
4632 "four-state always_ff event signal".to_string(),
4633 ));
4634 }
4635 if reset_edges.contains_key(&clock_id) {
4636 return Err(sv::AnalyzerError::Unsupported(
4637 "mixed clock/reset-edge polarities for one signal".to_string(),
4638 ));
4639 }
4640 if clock_edges
4641 .insert(clock_id, clock.edge())
4642 .is_some_and(|edge| edge != clock.edge())
4643 {
4644 return Err(sv::AnalyzerError::Unsupported(
4645 "mixed clock-edge polarities for one signal".to_string(),
4646 ));
4647 }
4648 for reset in process
4649 .events()
4650 .iter()
4651 .filter(|event| event.signal() != clock.signal())
4652 {
4653 let reset_id = *name_to_id.get(reset.signal()).ok_or_else(|| {
4654 sv::AnalyzerError::Unsupported("always_ff event control".to_string())
4655 })?;
4656 if variables
4657 .get(&reset_id)
4658 .is_some_and(|variable| variable.width != 1)
4659 {
4660 return Err(sv::AnalyzerError::Unsupported(
4661 "multi-bit always_ff event signal".to_string(),
4662 ));
4663 }
4664 if four_state
4665 && variables
4666 .get(&reset_id)
4667 .is_some_and(|variable| variable.is_4state)
4668 {
4669 return Err(sv::AnalyzerError::Unsupported(
4670 "four-state always_ff event signal".to_string(),
4671 ));
4672 }
4673 if clock_edges.contains_key(&reset_id) {
4674 return Err(sv::AnalyzerError::Unsupported(
4675 "mixed clock/reset-edge polarities for one signal".to_string(),
4676 ));
4677 }
4678 if reset_edges
4679 .insert(reset_id, reset.edge())
4680 .is_some_and(|edge| edge != reset.edge())
4681 {
4682 return Err(sv::AnalyzerError::Unsupported(
4683 "mixed reset-edge polarities for one signal".to_string(),
4684 ));
4685 }
4686 }
4687 let trigger_set = trigger_set_from_ff_process(process, name_to_id)
4688 .ok_or_else(|| sv::AnalyzerError::Unsupported("always_ff event control".to_string()))?;
4689 for reset in &trigger_set.resets {
4690 if reset_clock_map
4691 .get(reset)
4692 .is_some_and(|clock| *clock != trigger_set.clock)
4693 {
4694 return Err(sv::AnalyzerError::Unsupported(
4695 "shared reset associated with multiple clocks".to_string(),
4696 ));
4697 }
4698 reset_clock_map.insert(*reset, trigger_set.clock);
4699 }
4700 let (eval_only, apply, eval_apply) = lower_ff_process(
4701 process,
4702 &trigger_set,
4703 variables,
4704 name_to_id,
4705 constants,
4706 parameter_types,
4707 four_state,
4708 )
4709 .ok_or_else(|| {
4710 sv::AnalyzerError::Unsupported("always_ff assignment lowering".to_string())
4711 })?;
4712 insert_or_merge_ff_unit(&mut eval_only_ff_blocks, trigger_set.clone(), eval_only);
4713 insert_or_merge_ff_unit(&mut apply_ff_blocks, trigger_set.clone(), apply);
4714 insert_or_merge_ff_unit(&mut eval_apply_ff_blocks, trigger_set, eval_apply);
4715 }
4716
4717 Ok((
4718 eval_only_ff_blocks,
4719 apply_ff_blocks,
4720 eval_apply_ff_blocks,
4721 reset_clock_map,
4722 ))
4723}
4724
4725fn insert_or_merge_ff_unit(
4726 blocks: &mut HashMap<TriggerSet<SourceVarId>, ExecutionUnit<RegionedVarAddr>>,
4727 trigger_set: TriggerSet<SourceVarId>,
4728 unit: ExecutionUnit<RegionedVarAddr>,
4729) {
4730 if let Some(existing) = blocks.remove(&trigger_set) {
4731 blocks.insert(trigger_set, merge_sir_eus(&[existing, unit]).0);
4732 } else {
4733 blocks.insert(trigger_set, unit);
4734 }
4735}
4736
4737fn clock_event_from_ff_process(process: &sv::ir::FfProcess) -> Option<&sv::ir::FfEvent> {
4738 let clock = process.events().first()?;
4739 if process.events().len() == 1 {
4740 return Some(clock);
4741 }
4742
4743 (!ff_event_used_as_condition(process, clock)
4744 && process.events()[1..]
4745 .iter()
4746 .all(|event| ff_event_used_as_condition(process, event)))
4747 .then_some(clock)
4748}
4749
4750fn ff_event_used_as_condition(process: &sv::ir::FfProcess, event: &sv::ir::FfEvent) -> bool {
4751 process.assignments().iter().any(|assignment| {
4752 assignment
4753 .condition()
4754 .is_some_and(|condition| expr_references_ident(condition, event.signal()))
4755 || expr_uses_ident_as_condition(assignment.assignment().rhs(), event.signal())
4756 })
4757}
4758
4759fn expr_uses_ident_as_condition(expr: &sv::ir::Expr, name: &str) -> bool {
4760 match expr {
4761 sv::ir::Expr::Mux {
4762 condition,
4763 then_expr,
4764 else_expr,
4765 } => {
4766 expr_references_ident(condition, name)
4767 || expr_uses_ident_as_condition(then_expr, name)
4768 || expr_uses_ident_as_condition(else_expr, name)
4769 }
4770 sv::ir::Expr::Select { expr, .. }
4771 | sv::ir::Expr::Resize { expr, .. }
4772 | sv::ir::Expr::Unary { expr, .. } => expr_uses_ident_as_condition(expr, name),
4773 sv::ir::Expr::Concat(parts) | sv::ir::Expr::RepeatConcat { parts, .. } => parts
4774 .iter()
4775 .any(|part| expr_uses_ident_as_condition(part, name)),
4776 sv::ir::Expr::Binary { left, right, .. } => {
4777 expr_uses_ident_as_condition(left, name) || expr_uses_ident_as_condition(right, name)
4778 }
4779 sv::ir::Expr::Call { args, .. } => args
4780 .iter()
4781 .any(|arg| expr_uses_ident_as_condition(arg, name)),
4782 sv::ir::Expr::Ident(_) | sv::ir::Expr::Literal(_) => false,
4783 }
4784}
4785
4786fn trigger_set_from_ff_process(
4787 process: &sv::ir::FfProcess,
4788 name_to_id: &HashMap<String, SourceVarId>,
4789) -> Option<TriggerSet<SourceVarId>> {
4790 let clock = clock_event_from_ff_process(process)?;
4791 let clock_id = *name_to_id.get(clock.signal())?;
4792 let resets = process
4793 .events()
4794 .iter()
4795 .filter(|event| event.signal() != clock.signal())
4796 .filter_map(|event| name_to_id.get(event.signal()).copied())
4797 .collect();
4798 Some(TriggerSet {
4799 clock: clock_id,
4800 resets,
4801 })
4802}
4803
4804fn lower_ff_process(
4805 process: &sv::ir::FfProcess,
4806 trigger_set: &TriggerSet<SourceVarId>,
4807 variables: &HashMap<SourceVarId, SvVariable>,
4808 name_to_id: &HashMap<String, SourceVarId>,
4809 constants: &HashMap<String, i128>,
4810 parameter_types: &HashMap<String, (usize, bool)>,
4811 four_state: bool,
4812) -> Option<(
4813 ExecutionUnit<RegionedVarAddr>,
4814 ExecutionUnit<RegionedVarAddr>,
4815 ExecutionUnit<RegionedVarAddr>,
4816)> {
4817 let targets = ff_targets(process, variables, name_to_id, constants, parameter_types)?;
4818 let mut eval_builder = SIRBuilder::new();
4819 emit_ff_seeds(&mut eval_builder, &targets);
4820 emit_ff_assignment_stores(
4821 &mut eval_builder,
4822 process,
4823 &targets,
4824 variables,
4825 name_to_id,
4826 constants,
4827 parameter_types,
4828 four_state,
4829 )?;
4830 let eval_only = seal_builder(eval_builder);
4831
4832 let mut apply_builder = SIRBuilder::new();
4833 emit_ff_commits(&mut apply_builder, &targets);
4834 let apply = seal_builder(apply_builder);
4835
4836 let mut eval_apply_builder = SIRBuilder::new();
4837 emit_ff_seeds(&mut eval_apply_builder, &targets);
4838 emit_ff_assignment_stores(
4839 &mut eval_apply_builder,
4840 process,
4841 &targets,
4842 variables,
4843 name_to_id,
4844 constants,
4845 parameter_types,
4846 four_state,
4847 )?;
4848 emit_ff_commits(&mut eval_apply_builder, &targets);
4849 let eval_apply = seal_builder(eval_apply_builder);
4850
4851 if trigger_set.resets.is_empty() && targets.is_empty() {
4852 return None;
4853 }
4854 Some((eval_only, apply, eval_apply))
4855}
4856
4857fn seal_builder(mut builder: SIRBuilder<RegionedVarAddr>) -> ExecutionUnit<RegionedVarAddr> {
4858 builder.seal_block(SIRTerminator::Return);
4859 let (blocks, register_map, _) = builder.drain();
4860 ExecutionUnit {
4861 entry_block_id: BlockId(0),
4862 blocks,
4863 register_map,
4864 }
4865}
4866
4867fn ff_targets(
4868 process: &sv::ir::FfProcess,
4869 variables: &HashMap<SourceVarId, SvVariable>,
4870 name_to_id: &HashMap<String, SourceVarId>,
4871 constants: &HashMap<String, i128>,
4872 parameter_types: &HashMap<String, (usize, bool)>,
4873) -> Option<Vec<VarAtomBase<SourceVarId>>> {
4874 let mut targets = Vec::new();
4875 for assignment in process.assignments() {
4876 let lvalue = assignment.assignment().lhs_value();
4877 let dynamic =
4878 dynamic_array_element_lvalue(lvalue, variables, name_to_id, constants, parameter_types);
4879 let target = lvalue_atom(lvalue, variables, name_to_id, constants, parameter_types)
4880 .or_else(|| {
4881 dynamic.as_ref().and_then(|(id, _, _, _)| {
4882 variables
4883 .get(id)
4884 .and_then(|variable| variable.width.checked_sub(1))
4885 .map(|msb| VarAtomBase::new(*id, 0, msb))
4886 })
4887 })?;
4888 if !targets.contains(&target) {
4889 targets.push(target);
4890 }
4891 }
4892 Some(targets)
4893}
4894
4895fn emit_ff_seeds(builder: &mut SIRBuilder<RegionedVarAddr>, targets: &[VarAtomBase<SourceVarId>]) {
4896 for target in targets {
4897 builder.emit(SIRInstruction::Commit(
4898 RegionedVarAddrBase {
4899 region: STABLE_REGION,
4900 var_id: target.id,
4901 },
4902 RegionedVarAddrBase {
4903 region: WORKING_REGION,
4904 var_id: target.id,
4905 },
4906 SIROffset::Static(target.access.lsb),
4907 target.access.msb - target.access.lsb + 1,
4908 Vec::new(),
4909 ));
4910 }
4911}
4912
4913fn emit_ff_commits(
4914 builder: &mut SIRBuilder<RegionedVarAddr>,
4915 targets: &[VarAtomBase<SourceVarId>],
4916) {
4917 for target in targets {
4918 builder.emit(SIRInstruction::Commit(
4919 RegionedVarAddrBase {
4920 region: WORKING_REGION,
4921 var_id: target.id,
4922 },
4923 RegionedVarAddrBase {
4924 region: STABLE_REGION,
4925 var_id: target.id,
4926 },
4927 SIROffset::Static(target.access.lsb),
4928 target.access.msb - target.access.lsb + 1,
4929 Vec::new(),
4930 ));
4931 }
4932}
4933
4934fn emit_ff_assignment_stores(
4935 builder: &mut SIRBuilder<RegionedVarAddr>,
4936 process: &sv::ir::FfProcess,
4937 targets: &[VarAtomBase<SourceVarId>],
4938 variables: &HashMap<SourceVarId, SvVariable>,
4939 name_to_id: &HashMap<String, SourceVarId>,
4940 constants: &HashMap<String, i128>,
4941 parameter_types: &HashMap<String, (usize, bool)>,
4942 four_state: bool,
4943) -> Option<()> {
4944 let mut target_ids = Vec::new();
4945 for target in targets {
4946 if !target_ids.contains(&target.id) {
4947 target_ids.push(target.id);
4948 }
4949 }
4950
4951 for target_id in target_ids {
4952 let variable = variables.get(&target_id)?;
4953 let width = variable.width;
4954 let mut value = builder.alloc_logic(width);
4955 builder.emit(SIRInstruction::Load(
4956 value,
4957 RegionedVarAddrBase {
4958 region: WORKING_REGION,
4962 var_id: target_id,
4963 },
4964 sv_memory_offset(variable, 0, width),
4965 width,
4966 ));
4967 let mut value_dirty = false;
4968 for assignment in process.assignments() {
4969 let lvalue = assignment.assignment().lhs_value();
4970 let dynamic = dynamic_array_element_lvalue(
4971 lvalue,
4972 variables,
4973 name_to_id,
4974 constants,
4975 parameter_types,
4976 );
4977 let target = lvalue_atom(lvalue, variables, name_to_id, constants, parameter_types)
4978 .or_else(|| {
4979 dynamic.as_ref().and_then(|(id, _, _, _)| {
4980 variables
4981 .get(id)
4982 .and_then(|variable| variable.width.checked_sub(1))
4983 .map(|msb| VarAtomBase::new(*id, 0, msb))
4984 })
4985 })?;
4986 if target.id != target_id {
4987 continue;
4988 }
4989 let target_width = dynamic.as_ref().map_or_else(
4990 || target.access.msb - target.access.lsb + 1,
4991 |(_, _, _, access)| access.msb - access.lsb + 1,
4992 );
4993 let rhs_expr = expr_for_state_mode(assignment.assignment().rhs(), four_state);
4994 let rhs = match &rhs_expr {
4995 sv::ir::Expr::Literal(literal) => match unbased_fill_literal(literal) {
4996 Some(fill) => lower_unbased_fill_literal(builder, fill, target_width)?,
4997 None => {
4998 let rhs = lower_expr_to_sir_with_context(
4999 builder,
5000 &rhs_expr,
5001 variables,
5002 name_to_id,
5003 constants,
5004 parameter_types,
5005 Some(target_width),
5006 Some(sv_expr_is_signed_with_parameters(
5007 &rhs_expr,
5008 variables,
5009 name_to_id,
5010 parameter_types,
5011 )),
5012 )?;
5013 resize_sir_register(
5014 builder,
5015 rhs,
5016 target_width,
5017 sv_expr_is_signed_with_parameters(
5018 &rhs_expr,
5019 variables,
5020 name_to_id,
5021 parameter_types,
5022 ),
5023 )?
5024 }
5025 },
5026 _ => {
5027 let rhs = lower_expr_to_sir_with_context(
5028 builder,
5029 &rhs_expr,
5030 variables,
5031 name_to_id,
5032 constants,
5033 parameter_types,
5034 Some(target_width),
5035 Some(sv_expr_is_signed_with_parameters(
5036 &rhs_expr,
5037 variables,
5038 name_to_id,
5039 parameter_types,
5040 )),
5041 )?;
5042 resize_sir_register(
5043 builder,
5044 rhs,
5045 target_width,
5046 sv_expr_is_signed_with_parameters(
5047 &rhs_expr,
5048 variables,
5049 name_to_id,
5050 parameter_types,
5051 ),
5052 )?
5053 }
5054 };
5055 let rhs = permute_reversed_lvalue_rhs_sir(
5056 builder,
5057 lvalue,
5058 rhs,
5059 target_width,
5060 constants,
5061 parameter_types,
5062 )?;
5063 let rhs = if variables.get(&target.id)?.is_4state
5064 && (four_state || !expr_is_unknown_literal(&rhs_expr))
5065 {
5066 rhs
5067 } else {
5068 let two_state = builder.alloc_bit(target_width, false);
5069 builder.emit(SIRInstruction::Unary(two_state, UnaryOp::ToTwoState, rhs));
5070 two_state
5071 };
5072 if let Some((_, element_width, offset, access)) = dynamic {
5073 if value_dirty {
5076 builder.emit(SIRInstruction::Store(
5077 RegionedVarAddrBase {
5078 region: WORKING_REGION,
5079 var_id: target_id,
5080 },
5081 sv_memory_offset(variable, 0, width),
5082 width,
5083 value,
5084 Vec::new(),
5085 Vec::new(),
5086 ));
5087 value_dirty = false;
5088 }
5089 let index = lower_dynamic_array_element_index(
5090 builder,
5091 &offset,
5092 variables,
5093 name_to_id,
5094 constants,
5095 parameter_types,
5096 element_width,
5097 )?;
5098 let element_count = variable.width.checked_div(element_width)?;
5099 let (index, valid) = dynamic_array_index_guard_sir(builder, index, element_count)?;
5100 let packed_element_width = unpacked_element_width(variable)?;
5101 if element_width != packed_element_width {
5102 if access.lsb != 0 || target_width != element_width {
5103 return None;
5104 }
5105 let inner_count = element_width.checked_div(packed_element_width)?;
5106 let inner_count_value = builder.alloc_bit(64, false);
5107 builder.emit(SIRInstruction::Imm(
5108 inner_count_value,
5109 SIRValue::new(u64::try_from(inner_count).ok()?),
5110 ));
5111 let scaled_index = builder.alloc_bit(64, false);
5112 builder.emit(SIRInstruction::Binary(
5113 scaled_index,
5114 index,
5115 BinaryOp::Mul,
5116 inner_count_value,
5117 ));
5118 for inner_index in 0..inner_count {
5119 let element_index = if inner_index == 0 {
5120 scaled_index
5121 } else {
5122 let inner_index_value = builder.alloc_bit(64, false);
5123 builder.emit(SIRInstruction::Imm(
5124 inner_index_value,
5125 SIRValue::new(u64::try_from(inner_index).ok()?),
5126 ));
5127 let element_index = builder.alloc_bit(64, false);
5128 builder.emit(SIRInstruction::Binary(
5129 element_index,
5130 scaled_index,
5131 BinaryOp::Add,
5132 inner_index_value,
5133 ));
5134 element_index
5135 };
5136 let old = builder.alloc_logic(packed_element_width);
5137 builder.emit(SIRInstruction::Load(
5138 old,
5139 RegionedVarAddrBase {
5140 region: WORKING_REGION,
5141 var_id: target_id,
5142 },
5143 SIROffset::Element {
5144 index: element_index,
5145 element_width: packed_element_width,
5146 bit_offset: 0,
5147 dynamic_bit_offset: None,
5148 },
5149 packed_element_width,
5150 ));
5151 let rhs_part = builder.alloc_logic(packed_element_width);
5152 builder.emit(SIRInstruction::Slice(
5153 rhs_part,
5154 rhs,
5155 inner_index * packed_element_width,
5156 packed_element_width,
5157 ));
5158 let selected_value = match assignment.condition() {
5159 Some(condition) => {
5160 let condition = lower_procedural_condition(
5161 builder,
5162 condition,
5163 variables,
5164 name_to_id,
5165 constants,
5166 parameter_types,
5167 )?;
5168 let mux = builder.alloc_logic(packed_element_width);
5169 builder.emit(SIRInstruction::Mux(mux, condition, rhs_part, old));
5170 mux
5171 }
5172 None => rhs_part,
5173 };
5174 let store_value = builder.alloc_logic(packed_element_width);
5175 builder.emit(SIRInstruction::Mux(store_value, valid, selected_value, old));
5176 builder.emit(SIRInstruction::Store(
5177 RegionedVarAddrBase {
5178 region: WORKING_REGION,
5179 var_id: target_id,
5180 },
5181 SIROffset::Element {
5182 index: element_index,
5183 element_width: packed_element_width,
5184 bit_offset: 0,
5185 dynamic_bit_offset: None,
5186 },
5187 packed_element_width,
5188 store_value,
5189 Vec::new(),
5190 Vec::new(),
5191 ));
5192 }
5193 value = builder.alloc_logic(width);
5194 builder.emit(SIRInstruction::Load(
5195 value,
5196 RegionedVarAddrBase {
5197 region: WORKING_REGION,
5198 var_id: target_id,
5199 },
5200 sv_memory_offset(variable, 0, width),
5201 width,
5202 ));
5203 continue;
5204 }
5205 let old = builder.alloc_logic(target_width);
5206 builder.emit(SIRInstruction::Load(
5207 old,
5208 RegionedVarAddrBase {
5209 region: WORKING_REGION,
5210 var_id: target_id,
5211 },
5212 SIROffset::Element {
5213 index,
5214 element_width,
5215 bit_offset: access.lsb,
5216 dynamic_bit_offset: None,
5217 },
5218 target_width,
5219 ));
5220 let selected_value = match assignment.condition() {
5221 Some(condition) => {
5222 let condition = lower_procedural_condition(
5223 builder,
5224 condition,
5225 variables,
5226 name_to_id,
5227 constants,
5228 parameter_types,
5229 )?;
5230 let mux = builder.alloc_logic(target_width);
5231 builder.emit(SIRInstruction::Mux(mux, condition, rhs, old));
5232 mux
5233 }
5234 None => rhs,
5235 };
5236 let store_value = builder.alloc_logic(target_width);
5237 builder.emit(SIRInstruction::Mux(store_value, valid, selected_value, old));
5238 builder.emit(SIRInstruction::Store(
5239 RegionedVarAddrBase {
5240 region: WORKING_REGION,
5241 var_id: target_id,
5242 },
5243 SIROffset::Element {
5244 index,
5245 element_width,
5246 bit_offset: access.lsb,
5247 dynamic_bit_offset: None,
5248 },
5249 target_width,
5250 store_value,
5251 Vec::new(),
5252 Vec::new(),
5253 ));
5254 value = builder.alloc_logic(width);
5255 builder.emit(SIRInstruction::Load(
5256 value,
5257 RegionedVarAddrBase {
5258 region: WORKING_REGION,
5259 var_id: target_id,
5260 },
5261 sv_memory_offset(variable, 0, width),
5262 width,
5263 ));
5264 continue;
5265 }
5266 let assigned =
5267 replace_sir_slice(builder, value, rhs, target.access.lsb, target_width, width)?;
5268 value = match assignment.condition() {
5269 Some(condition) => {
5270 let condition = lower_procedural_condition(
5271 builder,
5272 condition,
5273 variables,
5274 name_to_id,
5275 constants,
5276 parameter_types,
5277 )?;
5278 let mux = builder.alloc_logic(width);
5279 builder.emit(SIRInstruction::Mux(mux, condition, assigned, value));
5280 mux
5281 }
5282 None => assigned,
5283 };
5284 value_dirty = true;
5285 }
5286 if !value_dirty {
5287 continue;
5288 }
5289 for target in targets.iter().filter(|target| target.id == target_id) {
5290 let target_width = target.access.msb - target.access.lsb + 1;
5291 let store_value = if target.access.lsb == 0 && target_width == width {
5292 value
5293 } else {
5294 let slice = builder.alloc_logic(target_width);
5295 builder.emit(SIRInstruction::Slice(
5296 slice,
5297 value,
5298 target.access.lsb,
5299 target_width,
5300 ));
5301 slice
5302 };
5303 builder.emit(SIRInstruction::Store(
5304 RegionedVarAddrBase {
5305 region: WORKING_REGION,
5306 var_id: target_id,
5307 },
5308 sv_memory_offset(variable, target.access.lsb, target_width),
5309 target_width,
5310 store_value,
5311 Vec::new(),
5312 Vec::new(),
5313 ));
5314 }
5315 }
5316 Some(())
5317}
5318
5319fn lower_procedural_condition(
5320 builder: &mut SIRBuilder<RegionedVarAddr>,
5321 condition: &sv::ir::Expr,
5322 variables: &HashMap<SourceVarId, SvVariable>,
5323 name_to_id: &HashMap<String, SourceVarId>,
5324 constants: &HashMap<String, i128>,
5325 parameter_types: &HashMap<String, (usize, bool)>,
5326) -> Option<celox_sir::RegisterId> {
5327 let condition = lower_expr_to_sir(
5328 builder,
5329 condition,
5330 variables,
5331 name_to_id,
5332 constants,
5333 parameter_types,
5334 )?;
5335 let width = builder.register(&condition).width();
5336 let two_state = builder.alloc_bit(width, false);
5337 builder.emit(SIRInstruction::Unary(
5338 two_state,
5339 UnaryOp::ToTwoState,
5340 condition,
5341 ));
5342 if width == 1 {
5343 return Some(two_state);
5344 }
5345 let truth = builder.alloc_bit(1, false);
5346 builder.emit(SIRInstruction::Unary(truth, UnaryOp::Or, two_state));
5347 Some(truth)
5348}
5349
5350fn replace_sir_slice(
5351 builder: &mut SIRBuilder<RegionedVarAddr>,
5352 current: celox_sir::RegisterId,
5353 replacement: celox_sir::RegisterId,
5354 lsb: usize,
5355 replacement_width: usize,
5356 total_width: usize,
5357) -> Option<celox_sir::RegisterId> {
5358 if lsb == 0 && replacement_width == total_width {
5359 return Some(replacement);
5360 }
5361 let end = lsb.checked_add(replacement_width)?;
5362 if end > total_width {
5363 return None;
5364 }
5365
5366 let mut parts = Vec::with_capacity(3);
5367 if end < total_width {
5368 let upper_width = total_width - end;
5369 let upper = builder.alloc_logic(upper_width);
5370 builder.emit(SIRInstruction::Slice(upper, current, end, upper_width));
5371 parts.push(upper);
5372 }
5373 parts.push(replacement);
5374 if lsb != 0 {
5375 let lower = builder.alloc_logic(lsb);
5376 builder.emit(SIRInstruction::Slice(lower, current, 0, lsb));
5377 parts.push(lower);
5378 }
5379
5380 let result = builder.alloc_logic(total_width);
5381 builder.emit(SIRInstruction::Concat(result, parts));
5382 Some(result)
5383}
5384
5385fn permute_reversed_lvalue_rhs_sir(
5386 builder: &mut SIRBuilder<RegionedVarAddr>,
5387 lvalue: &sv::ir::LValue,
5388 rhs: celox_sir::RegisterId,
5389 target_width: usize,
5390 constants: &HashMap<String, i128>,
5391 parameter_types: &HashMap<String, (usize, bool)>,
5392) -> Option<celox_sir::RegisterId> {
5393 let sv::ir::LValue::Select {
5394 array_slice_width: Some(array_slice_width),
5395 array_slice_reversed: true,
5396 ..
5397 } = lvalue
5398 else {
5399 return Some(rhs);
5400 };
5401 let element_width = usize::try_from(sv::typecheck::eval_const_expr_with_types(
5402 array_slice_width,
5403 constants,
5404 parameter_types,
5405 )?)
5406 .ok()
5407 .filter(|width| *width != 0)?;
5408 if !target_width.is_multiple_of(element_width) {
5409 return None;
5410 }
5411 let element_count = target_width / element_width;
5412 if element_count <= 1 {
5413 return Some(rhs);
5414 }
5415 let mut parts = Vec::with_capacity(element_count);
5416 for lsb in (0..target_width).step_by(element_width) {
5417 let part = builder.alloc_logic(element_width);
5418 builder.emit(SIRInstruction::Slice(part, rhs, lsb, element_width));
5419 parts.push(part);
5420 }
5421 let result = builder.alloc_logic(target_width);
5422 builder.emit(SIRInstruction::Concat(result, parts));
5423 Some(result)
5424}
5425
5426fn lvalue_atom(
5427 lvalue: &sv::ir::LValue,
5428 variables: &HashMap<SourceVarId, SvVariable>,
5429 name_to_id: &HashMap<String, SourceVarId>,
5430 constants: &HashMap<String, i128>,
5431 parameter_types: &HashMap<String, (usize, bool)>,
5432) -> Option<VarAtomBase<SourceVarId>> {
5433 let id = *name_to_id.get(lvalue.name())?;
5434 let width = variables.get(&id)?.width;
5435 match lvalue {
5436 sv::ir::LValue::Ident(_) => Some(VarAtomBase::new(id, 0, width.checked_sub(1)?)),
5437 sv::ir::LValue::Select { msb, lsb, .. } => {
5438 let msb = sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)?;
5439 let lsb = sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types)?;
5440 let variable = variables.get(&id)?;
5441 let msb = packed_index_offset(variable, msb)?;
5442 let lsb = packed_index_offset(variable, lsb)?;
5443 let high = msb.max(lsb);
5444 let low = msb.min(lsb);
5445 (low <= high && high < width).then(|| VarAtomBase::new(id, low, high))
5446 }
5447 }
5448}
5449
5450fn sv_glue_expr_is_signed(
5451 expr: &sv::ir::Expr,
5452 variables: &HashMap<SourceVarId, SvVariable>,
5453 name_to_id: &HashMap<String, SourceVarId>,
5454 parameter_types: &HashMap<String, (usize, bool)>,
5455) -> bool {
5456 match expr {
5457 sv::ir::Expr::Ident(name) => name_to_id
5458 .get(name)
5459 .and_then(|id| variables.get(id))
5460 .map(|variable| variable.signed)
5461 .or_else(|| parameter_types.get(name).map(|(_, signed)| *signed))
5462 .unwrap_or(false),
5463 sv::ir::Expr::Literal(literal) => {
5464 sv::typecheck::parse_integral_literal(literal).is_some_and(|literal| literal.signed)
5465 }
5466 sv::ir::Expr::Resize { signed, .. } => *signed,
5467 sv::ir::Expr::Select { signed, .. } => *signed,
5468 sv::ir::Expr::Concat(_) | sv::ir::Expr::RepeatConcat { .. } | sv::ir::Expr::Call { .. } => {
5469 false
5470 }
5471 sv::ir::Expr::Unary { op, expr } => {
5472 matches!(
5473 op,
5474 sv::ir::UnaryOp::Plus | sv::ir::UnaryOp::Minus | sv::ir::UnaryOp::BitNot
5475 ) && sv_glue_expr_is_signed(expr, variables, name_to_id, parameter_types)
5476 }
5477 sv::ir::Expr::Binary { left, op, right } => match op {
5478 sv::ir::BinaryOp::Shl | sv::ir::BinaryOp::Shr | sv::ir::BinaryOp::Sar => {
5479 sv_glue_expr_is_signed(left, variables, name_to_id, parameter_types)
5480 }
5481 sv::ir::BinaryOp::Add
5482 | sv::ir::BinaryOp::Sub
5483 | sv::ir::BinaryOp::Mul
5484 | sv::ir::BinaryOp::Div
5485 | sv::ir::BinaryOp::Mod
5486 | sv::ir::BinaryOp::BitAnd
5487 | sv::ir::BinaryOp::BitOr
5488 | sv::ir::BinaryOp::BitXor => {
5489 sv_glue_expr_is_signed(left, variables, name_to_id, parameter_types)
5490 && sv_glue_expr_is_signed(right, variables, name_to_id, parameter_types)
5491 }
5492 _ => false,
5493 },
5494 sv::ir::Expr::Mux {
5495 then_expr,
5496 else_expr,
5497 ..
5498 } => {
5499 sv_glue_expr_is_signed(then_expr, variables, name_to_id, parameter_types)
5500 && sv_glue_expr_is_signed(else_expr, variables, name_to_id, parameter_types)
5501 }
5502 }
5503}
5504
5505fn sv_expr_is_signed_with_parameters(
5506 expr: &sv::ir::Expr,
5507 variables: &HashMap<SourceVarId, SvVariable>,
5508 name_to_id: &HashMap<String, SourceVarId>,
5509 parameter_types: &HashMap<String, (usize, bool)>,
5510) -> bool {
5511 match expr {
5512 sv::ir::Expr::Ident(name) => name_to_id
5513 .get(name)
5514 .and_then(|id| variables.get(id))
5515 .map_or_else(
5516 || parameter_types.get(name).is_some_and(|(_, signed)| *signed),
5517 |variable| variable.signed,
5518 ),
5519 sv::ir::Expr::Literal(literal) => {
5520 sv::typecheck::parse_integral_literal(literal).is_some_and(|literal| literal.signed)
5521 }
5522 sv::ir::Expr::Resize { signed, .. } => *signed,
5523 sv::ir::Expr::Select { signed, .. } => *signed,
5524 sv::ir::Expr::Concat(_) | sv::ir::Expr::RepeatConcat { .. } | sv::ir::Expr::Call { .. } => {
5525 false
5526 }
5527 sv::ir::Expr::Unary { op, expr } => {
5528 matches!(
5529 op,
5530 sv::ir::UnaryOp::Plus | sv::ir::UnaryOp::Minus | sv::ir::UnaryOp::BitNot
5531 ) && sv_expr_is_signed_with_parameters(expr, variables, name_to_id, parameter_types)
5532 }
5533 sv::ir::Expr::Binary { left, op, right } => match op {
5534 sv::ir::BinaryOp::Shl | sv::ir::BinaryOp::Shr | sv::ir::BinaryOp::Sar => {
5535 sv_expr_is_signed_with_parameters(left, variables, name_to_id, parameter_types)
5536 }
5537 sv::ir::BinaryOp::Add
5538 | sv::ir::BinaryOp::Sub
5539 | sv::ir::BinaryOp::Mul
5540 | sv::ir::BinaryOp::Div
5541 | sv::ir::BinaryOp::Mod
5542 | sv::ir::BinaryOp::BitAnd
5543 | sv::ir::BinaryOp::BitOr
5544 | sv::ir::BinaryOp::BitXor => {
5545 sv_expr_is_signed_with_parameters(left, variables, name_to_id, parameter_types)
5546 && sv_expr_is_signed_with_parameters(
5547 right,
5548 variables,
5549 name_to_id,
5550 parameter_types,
5551 )
5552 }
5553 _ => false,
5554 },
5555 sv::ir::Expr::Mux {
5556 then_expr,
5557 else_expr,
5558 ..
5559 } => {
5560 sv_expr_is_signed_with_parameters(then_expr, variables, name_to_id, parameter_types)
5561 && sv_expr_is_signed_with_parameters(
5562 else_expr,
5563 variables,
5564 name_to_id,
5565 parameter_types,
5566 )
5567 }
5568 }
5569}
5570
5571fn resize_sir_register(
5572 builder: &mut SIRBuilder<RegionedVarAddr>,
5573 source: celox_sir::RegisterId,
5574 target_width: usize,
5575 sign_extend: bool,
5576) -> Option<celox_sir::RegisterId> {
5577 let source_type = builder.register(&source).clone();
5578 let source_width = source_type.width();
5579 if source_width == target_width {
5580 return Some(source);
5581 }
5582
5583 let alloc_like = |builder: &mut SIRBuilder<RegionedVarAddr>, width| match &source_type {
5584 RegisterType::Logic { .. } => builder.alloc_logic(width),
5585 RegisterType::Bit { signed, .. } => builder.alloc_bit(width, *signed && sign_extend),
5586 };
5587
5588 if source_width > target_width {
5589 let resized = alloc_like(builder, target_width);
5590 builder.emit(SIRInstruction::Slice(resized, source, 0, target_width));
5591 return Some(resized);
5592 }
5593
5594 let extension_width = target_width - source_width;
5595 let mut parts = Vec::with_capacity(extension_width.saturating_add(1));
5596 if sign_extend {
5597 let sign = alloc_like(builder, 1);
5598 builder.emit(SIRInstruction::Slice(
5599 sign,
5600 source,
5601 source_width.checked_sub(1)?,
5602 1,
5603 ));
5604 parts.extend(std::iter::repeat_n(sign, extension_width));
5605 } else {
5606 let zero = alloc_like(builder, extension_width);
5607 builder.emit(SIRInstruction::Imm(zero, SIRValue::new(0u8)));
5608 parts.push(zero);
5609 }
5610 parts.push(source);
5611 let resized = alloc_like(builder, target_width);
5612 builder.emit(SIRInstruction::Concat(resized, parts));
5613 Some(resized)
5614}
5615
5616fn unbased_fill_literal(literal: &str) -> Option<char> {
5617 let normalized = literal.trim().to_ascii_lowercase();
5618 let mut chars = normalized.chars();
5619 (chars.next()? == '\'' && chars.clone().count() == 1).then_some(chars.next()?)
5620}
5621
5622fn expr_unbased_fill_literal(expr: &sv::ir::Expr) -> Option<char> {
5623 match expr {
5624 sv::ir::Expr::Literal(literal) => unbased_fill_literal(literal),
5625 _ => None,
5626 }
5627}
5628
5629fn expr_is_unknown_literal(expr: &sv::ir::Expr) -> bool {
5630 let sv::ir::Expr::Literal(literal) = expr else {
5631 return false;
5632 };
5633 sv::typecheck::parse_integral_literal(literal)
5634 .is_some_and(|literal| literal.mask != BigUint::default())
5635}
5636
5637fn unbased_fill_value(fill: char, width: usize) -> Option<(BigUint, BigUint)> {
5638 let all_ones = if width == 0 {
5639 BigUint::default()
5640 } else {
5641 (BigUint::from(1u8) << width) - BigUint::from(1u8)
5642 };
5643 match fill {
5644 '0' => Some((BigUint::default(), BigUint::default())),
5645 '1' => Some((all_ones, BigUint::default())),
5646 'x' => Some((all_ones.clone(), all_ones)),
5647 'z' | '?' => Some((BigUint::default(), all_ones)),
5648 _ => None,
5649 }
5650}
5651
5652fn lower_unbased_fill_literal_slt<A: std::hash::Hash + Eq + Clone>(
5653 arena: &mut SLTNodeArena<A>,
5654 fill: char,
5655 width: usize,
5656) -> Option<celox_slt::NodeId> {
5657 let (value, mask) = unbased_fill_value(fill, width)?;
5658 arena
5659 .alloc(SLTNode::Constant(value, mask, width, false))
5660 .ok()
5661}
5662
5663fn lower_unbased_fill_literal(
5664 builder: &mut SIRBuilder<RegionedVarAddr>,
5665 fill: char,
5666 width: usize,
5667) -> Option<celox_sir::RegisterId> {
5668 let (value, mask) = unbased_fill_value(fill, width)?;
5669 let register = builder.alloc_logic(width);
5670 builder.emit(SIRInstruction::Imm(
5671 register,
5672 SIRValue::new_four_state(value, mask),
5673 ));
5674 Some(register)
5675}
5676
5677fn lower_expr_to_sir(
5678 builder: &mut SIRBuilder<RegionedVarAddr>,
5679 expr: &sv::ir::Expr,
5680 variables: &HashMap<SourceVarId, SvVariable>,
5681 name_to_id: &HashMap<String, SourceVarId>,
5682 constants: &HashMap<String, i128>,
5683 parameter_types: &HashMap<String, (usize, bool)>,
5684) -> Option<celox_sir::RegisterId> {
5685 lower_expr_to_sir_with_context(
5686 builder,
5687 expr,
5688 variables,
5689 name_to_id,
5690 constants,
5691 parameter_types,
5692 None,
5693 None,
5694 )
5695}
5696
5697fn sv_expr_natural_width(
5698 expr: &sv::ir::Expr,
5699 variables: &HashMap<SourceVarId, SvVariable>,
5700 name_to_id: &HashMap<String, SourceVarId>,
5701 constants: &HashMap<String, i128>,
5702 parameter_types: &HashMap<String, (usize, bool)>,
5703) -> Option<usize> {
5704 match expr {
5705 sv::ir::Expr::Ident(name) => name_to_id
5706 .get(name)
5707 .and_then(|id| variables.get(id))
5708 .map_or_else(
5709 || {
5710 constants
5711 .contains_key(name)
5712 .then(|| parameter_types.get(name).map_or(32, |(width, _)| *width))
5713 },
5714 |var| Some(var.width),
5715 ),
5716 sv::ir::Expr::Literal(literal) => Some(
5717 unbased_fill_literal(literal)
5718 .map(|_| 1)
5719 .unwrap_or(sv::typecheck::parse_integral_literal(literal)?.width),
5720 ),
5721 sv::ir::Expr::Select { expr, msb, lsb, .. } => {
5722 if let Some((_, _, access)) = dynamic_array_element_subselection(
5723 expr,
5724 msb,
5725 lsb,
5726 variables,
5727 name_to_id,
5728 constants,
5729 parameter_types,
5730 ) {
5731 return Some(access.msb - access.lsb + 1);
5732 }
5733 let msb = sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)?;
5734 let lsb = sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types)?;
5735 usize::try_from(msb.abs_diff(lsb)).ok()?.checked_add(1)
5736 }
5737 sv::ir::Expr::Resize { width, .. } => Some(*width),
5738 sv::ir::Expr::Unary { op, expr } => matches!(
5739 op,
5740 sv::ir::UnaryOp::LogicNot
5741 | sv::ir::UnaryOp::RedAnd
5742 | sv::ir::UnaryOp::RedOr
5743 | sv::ir::UnaryOp::RedXor
5744 )
5745 .then_some(1)
5746 .or_else(|| sv_expr_natural_width(expr, variables, name_to_id, constants, parameter_types)),
5747 sv::ir::Expr::Binary { left, op, right } => {
5748 if matches!(
5749 op,
5750 sv::ir::BinaryOp::LogicAnd
5751 | sv::ir::BinaryOp::LogicOr
5752 | sv::ir::BinaryOp::Eq
5753 | sv::ir::BinaryOp::Ne
5754 | sv::ir::BinaryOp::EqCase
5755 | sv::ir::BinaryOp::NeCase
5756 | sv::ir::BinaryOp::EqWildcard
5757 | sv::ir::BinaryOp::NeWildcard
5758 | sv::ir::BinaryOp::Lt
5759 | sv::ir::BinaryOp::Le
5760 | sv::ir::BinaryOp::Gt
5761 | sv::ir::BinaryOp::Ge
5762 ) {
5763 Some(1)
5764 } else if matches!(
5765 op,
5766 sv::ir::BinaryOp::Shl | sv::ir::BinaryOp::Shr | sv::ir::BinaryOp::Sar
5767 ) {
5768 sv_expr_natural_width(left, variables, name_to_id, constants, parameter_types)
5769 } else {
5770 Some(
5771 sv_expr_natural_width(left, variables, name_to_id, constants, parameter_types)?
5772 .max(sv_expr_natural_width(
5773 right,
5774 variables,
5775 name_to_id,
5776 constants,
5777 parameter_types,
5778 )?),
5779 )
5780 }
5781 }
5782 sv::ir::Expr::Concat(parts) => parts.iter().try_fold(0usize, |width, part| {
5783 width.checked_add(sv_expr_natural_width(
5784 part,
5785 variables,
5786 name_to_id,
5787 constants,
5788 parameter_types,
5789 )?)
5790 }),
5791 sv::ir::Expr::RepeatConcat { count, parts } => {
5792 let count = usize::try_from(sv::typecheck::eval_const_expr_with_types(
5793 count,
5794 constants,
5795 parameter_types,
5796 )?)
5797 .ok()?;
5798 let parts_width = parts.iter().try_fold(0usize, |width, part| {
5799 width.checked_add(sv_expr_natural_width(
5800 part,
5801 variables,
5802 name_to_id,
5803 constants,
5804 parameter_types,
5805 )?)
5806 })?;
5807 count.checked_mul(parts_width)
5808 }
5809 sv::ir::Expr::Mux {
5810 then_expr,
5811 else_expr,
5812 ..
5813 } => Some(
5814 sv_expr_natural_width(then_expr, variables, name_to_id, constants, parameter_types)?
5815 .max(sv_expr_natural_width(
5816 else_expr,
5817 variables,
5818 name_to_id,
5819 constants,
5820 parameter_types,
5821 )?),
5822 ),
5823 sv::ir::Expr::Call { .. } => None,
5824 }
5825}
5826
5827fn sv_comparison_operand_width(
5828 left: &sv::ir::Expr,
5829 right: &sv::ir::Expr,
5830 variables: &HashMap<SourceVarId, SvVariable>,
5831 name_to_id: &HashMap<String, SourceVarId>,
5832 constants: &HashMap<String, i128>,
5833 parameter_types: &HashMap<String, (usize, bool)>,
5834) -> Option<usize> {
5835 Some(
5836 sv_expr_natural_width(left, variables, name_to_id, constants, parameter_types)?.max(
5837 sv_expr_natural_width(right, variables, name_to_id, constants, parameter_types)?,
5838 ),
5839 )
5840}
5841
5842fn lower_expr_to_sir_with_context(
5843 builder: &mut SIRBuilder<RegionedVarAddr>,
5844 expr: &sv::ir::Expr,
5845 variables: &HashMap<SourceVarId, SvVariable>,
5846 name_to_id: &HashMap<String, SourceVarId>,
5847 constants: &HashMap<String, i128>,
5848 parameter_types: &HashMap<String, (usize, bool)>,
5849 context_width: Option<usize>,
5850 context_signed: Option<bool>,
5851) -> Option<celox_sir::RegisterId> {
5852 match expr {
5853 sv::ir::Expr::Ident(name) => {
5854 let Some(id) = name_to_id.get(name).copied() else {
5855 let value = constants.get(name)?;
5856 let (width, signed) = parameter_types.get(name).copied().unwrap_or((32, false));
5857 let reg = builder.alloc_logic(width);
5858 builder.emit(SIRInstruction::Imm(
5859 reg,
5860 SIRValue::new_four_state(parameter_value_bits(*value, width), 0u32),
5861 ));
5862 return resize_sir_register(
5863 builder,
5864 reg,
5865 context_width.unwrap_or(width),
5866 context_signed.unwrap_or(signed),
5867 );
5868 };
5869 let var = variables.get(&id)?;
5870 let reg = if var.is_4state {
5871 builder.alloc_logic(var.width)
5872 } else {
5873 builder.alloc_bit(var.width, var.signed)
5874 };
5875 builder.emit(SIRInstruction::Load(
5876 reg,
5877 RegionedVarAddrBase {
5878 region: STABLE_REGION,
5879 var_id: id,
5880 },
5881 sv_memory_offset(var, 0, var.width),
5882 var.width,
5883 ));
5884 resize_sir_register(
5885 builder,
5886 reg,
5887 context_width.unwrap_or(var.width),
5888 context_signed.unwrap_or(var.signed),
5889 )
5890 }
5891 sv::ir::Expr::Literal(literal) => {
5892 if let Some(width) = context_width
5893 && let Some(fill) = unbased_fill_literal(literal)
5894 {
5895 return lower_unbased_fill_literal(builder, fill, width);
5896 }
5897 let literal = sv::typecheck::parse_integral_literal(literal)?;
5898 let width = literal.width;
5899 let signed = literal.signed;
5900 let reg = builder.alloc_logic(literal.width);
5901 builder.emit(SIRInstruction::Imm(
5902 reg,
5903 SIRValue::new_four_state(literal.value, literal.mask),
5904 ));
5905 resize_sir_register(
5906 builder,
5907 reg,
5908 context_width.unwrap_or(width),
5909 context_signed.unwrap_or(signed),
5910 )
5911 }
5912 sv::ir::Expr::Select {
5913 expr,
5914 msb,
5915 lsb,
5916 signed,
5917 } => {
5918 if let Some((id, element_width, access)) = dynamic_array_element_subselection(
5919 expr,
5920 msb,
5921 lsb,
5922 variables,
5923 name_to_id,
5924 constants,
5925 parameter_types,
5926 ) {
5927 let index = lower_dynamic_array_element_index(
5928 builder,
5929 lsb,
5930 variables,
5931 name_to_id,
5932 constants,
5933 parameter_types,
5934 element_width,
5935 )?;
5936 let width = access.msb - access.lsb + 1;
5937 let variable = variables.get(&id)?;
5938 let element_count = variable.width.checked_div(element_width)?;
5939 let (index, valid) = dynamic_array_index_guard_sir(builder, index, element_count)?;
5940 let reg = lower_dynamic_array_selection_sir(
5941 builder,
5942 RegionedVarAddrBase {
5943 region: STABLE_REGION,
5944 var_id: id,
5945 },
5946 index,
5947 access,
5948 element_width,
5949 variable,
5950 )?;
5951 let reg =
5952 guard_dynamic_array_read_sir(builder, valid, reg, width, variable.is_4state);
5953 return resize_sir_register(
5954 builder,
5955 reg,
5956 context_width.unwrap_or(width),
5957 context_signed.unwrap_or(*signed),
5958 );
5959 }
5960 let msb = sv::typecheck::eval_const_expr_with_types(msb, constants, parameter_types)?;
5961 let lsb = sv::typecheck::eval_const_expr_with_types(lsb, constants, parameter_types)?;
5962 let (msb, lsb) = packed_expr_select_offsets(expr, msb, lsb, variables, name_to_id)?;
5963 let high = msb.max(lsb);
5964 let low = msb.min(lsb);
5965 let width = high - low + 1;
5966 if let sv::ir::Expr::Ident(name) = &**expr
5967 && let Some(var) = name_to_id.get(name).and_then(|id| variables.get(id))
5968 && !var.array_dims.is_empty()
5969 {
5970 let reg = builder.alloc_logic(width);
5971 builder.emit(SIRInstruction::Load(
5972 reg,
5973 RegionedVarAddrBase {
5974 region: STABLE_REGION,
5975 var_id: *name_to_id.get(name)?,
5976 },
5977 sv_memory_offset(var, low, width),
5978 width,
5979 ));
5980 return resize_sir_register(
5981 builder,
5982 reg,
5983 context_width.unwrap_or(width),
5984 context_signed.unwrap_or(*signed),
5985 );
5986 }
5987 let inner = lower_expr_to_sir_with_context(
5988 builder,
5989 expr,
5990 variables,
5991 name_to_id,
5992 constants,
5993 parameter_types,
5994 None,
5995 None,
5996 )?;
5997 let reg = builder.alloc_logic(width);
5998 builder.emit(SIRInstruction::Slice(reg, inner, low, width));
5999 resize_sir_register(
6000 builder,
6001 reg,
6002 context_width.unwrap_or(width),
6003 context_signed.unwrap_or(*signed),
6004 )
6005 }
6006 sv::ir::Expr::Resize {
6007 expr,
6008 width,
6009 signed,
6010 } => {
6011 let inner = lower_expr_to_sir_with_context(
6012 builder,
6013 expr,
6014 variables,
6015 name_to_id,
6016 constants,
6017 parameter_types,
6018 Some(*width),
6019 Some(*signed),
6020 )?;
6021 let resized = resize_sir_register(builder, inner, *width, *signed)?;
6022 resize_sir_register(
6023 builder,
6024 resized,
6025 context_width.unwrap_or(*width),
6026 context_signed.unwrap_or(*signed),
6027 )
6028 }
6029 sv::ir::Expr::Unary { op, expr } => {
6030 let one_bit_result = matches!(
6031 op,
6032 sv::ir::UnaryOp::LogicNot
6033 | sv::ir::UnaryOp::RedAnd
6034 | sv::ir::UnaryOp::RedOr
6035 | sv::ir::UnaryOp::RedXor
6036 );
6037 let inner = lower_expr_to_sir_with_context(
6038 builder,
6039 expr,
6040 variables,
6041 name_to_id,
6042 constants,
6043 parameter_types,
6044 (!one_bit_result).then_some(context_width).flatten(),
6045 context_signed,
6046 )?;
6047 let width = if one_bit_result {
6048 1
6049 } else {
6050 builder.register(&inner).width()
6051 };
6052 let reg = if matches!(op, sv::ir::UnaryOp::ToTwoState) {
6053 builder.alloc_bit(width, false)
6054 } else {
6055 builder.alloc_logic(width)
6056 };
6057 builder.emit(SIRInstruction::Unary(reg, unary_op_from_sv(*op)?, inner));
6058 Some(reg)
6059 }
6060 sv::ir::Expr::Binary { left, op, right } => {
6061 let left_signed =
6062 sv_expr_is_signed_with_parameters(left, variables, name_to_id, parameter_types);
6063 let operands_signed = left_signed
6064 && sv_expr_is_signed_with_parameters(right, variables, name_to_id, parameter_types);
6065 let operator_signed = if matches!(op, sv::ir::BinaryOp::Sar) {
6066 left_signed
6067 } else {
6068 operands_signed
6069 };
6070 let comparison = matches!(
6071 op,
6072 sv::ir::BinaryOp::Eq
6073 | sv::ir::BinaryOp::Ne
6074 | sv::ir::BinaryOp::EqCase
6075 | sv::ir::BinaryOp::NeCase
6076 | sv::ir::BinaryOp::EqWildcard
6077 | sv::ir::BinaryOp::NeWildcard
6078 | sv::ir::BinaryOp::Lt
6079 | sv::ir::BinaryOp::Le
6080 | sv::ir::BinaryOp::Gt
6081 | sv::ir::BinaryOp::Ge
6082 );
6083 let shift = matches!(
6084 op,
6085 sv::ir::BinaryOp::Shl | sv::ir::BinaryOp::Shr | sv::ir::BinaryOp::Sar
6086 );
6087 let context_determined = !comparison
6088 && !matches!(op, sv::ir::BinaryOp::LogicAnd | sv::ir::BinaryOp::LogicOr);
6089 let operation_context = context_width.map(|context_width| {
6090 context_width.max(
6091 sv_expr_natural_width(expr, variables, name_to_id, constants, parameter_types)
6092 .unwrap_or(context_width),
6093 )
6094 });
6095 let comparison_context = comparison
6096 .then(|| {
6097 sv_comparison_operand_width(
6098 left,
6099 right,
6100 variables,
6101 name_to_id,
6102 constants,
6103 parameter_types,
6104 )
6105 })
6106 .flatten();
6107 let left_context = if comparison {
6108 comparison_context
6109 } else {
6110 context_determined.then_some(operation_context).flatten()
6111 };
6112 let right_context = if comparison {
6113 comparison_context
6114 } else {
6115 (context_determined && !shift)
6116 .then_some(operation_context)
6117 .flatten()
6118 };
6119 let right_fill = match &**right {
6120 sv::ir::Expr::Literal(literal) => unbased_fill_literal(literal),
6121 _ => None,
6122 };
6123 let left_fill = match &**left {
6124 sv::ir::Expr::Literal(literal) => unbased_fill_literal(literal),
6125 _ => None,
6126 };
6127 let (mut left, mut right) = if let Some(fill) = right_fill {
6128 let left = lower_expr_to_sir_with_context(
6129 builder,
6130 left,
6131 variables,
6132 name_to_id,
6133 constants,
6134 parameter_types,
6135 left_context,
6136 Some(if shift { left_signed } else { operands_signed }),
6137 )?;
6138 let width = if shift {
6139 1
6140 } else {
6141 builder.register(&left).width()
6142 };
6143 (left, lower_unbased_fill_literal(builder, fill, width)?)
6144 } else if let Some(fill) = left_fill {
6145 let right = lower_expr_to_sir_with_context(
6146 builder,
6147 right,
6148 variables,
6149 name_to_id,
6150 constants,
6151 parameter_types,
6152 right_context,
6153 Some(operands_signed),
6154 )?;
6155 let width = left_context.unwrap_or_else(|| builder.register(&right).width());
6156 (lower_unbased_fill_literal(builder, fill, width)?, right)
6157 } else {
6158 (
6159 lower_expr_to_sir_with_context(
6160 builder,
6161 left,
6162 variables,
6163 name_to_id,
6164 constants,
6165 parameter_types,
6166 left_context,
6167 Some(if shift { left_signed } else { operands_signed }),
6168 )?,
6169 lower_expr_to_sir_with_context(
6170 builder,
6171 right,
6172 variables,
6173 name_to_id,
6174 constants,
6175 parameter_types,
6176 right_context,
6177 Some(operands_signed),
6178 )?,
6179 )
6180 };
6181 if comparison {
6182 let common_width = builder
6183 .register(&left)
6184 .width()
6185 .max(builder.register(&right).width());
6186 left = resize_sir_register(builder, left, common_width, operands_signed)?;
6187 right = resize_sir_register(builder, right, common_width, operands_signed)?;
6188 }
6189 let width = match op {
6190 sv::ir::BinaryOp::LogicAnd
6191 | sv::ir::BinaryOp::LogicOr
6192 | sv::ir::BinaryOp::Eq
6193 | sv::ir::BinaryOp::Ne
6194 | sv::ir::BinaryOp::EqCase
6195 | sv::ir::BinaryOp::NeCase
6196 | sv::ir::BinaryOp::EqWildcard
6197 | sv::ir::BinaryOp::NeWildcard
6198 | sv::ir::BinaryOp::Lt
6199 | sv::ir::BinaryOp::Le
6200 | sv::ir::BinaryOp::Gt
6201 | sv::ir::BinaryOp::Ge => 1,
6202 sv::ir::BinaryOp::Shl | sv::ir::BinaryOp::Shr | sv::ir::BinaryOp::Sar => {
6203 builder.register(&left).width()
6204 }
6205 _ => builder
6206 .register(&left)
6207 .width()
6208 .max(builder.register(&right).width()),
6209 };
6210 let reg = if matches!(op, sv::ir::BinaryOp::EqCase | sv::ir::BinaryOp::NeCase) {
6211 builder.alloc_bit(width, false)
6212 } else {
6213 builder.alloc_logic(width)
6214 };
6215 builder.emit(SIRInstruction::Binary(
6216 reg,
6217 left,
6218 binary_op_from_sv(*op, operator_signed),
6219 right,
6220 ));
6221 Some(reg)
6222 }
6223 sv::ir::Expr::Concat(parts) => {
6224 let mut regs = Vec::new();
6225 for part in parts {
6226 regs.push(lower_expr_to_sir_with_context(
6227 builder,
6228 part,
6229 variables,
6230 name_to_id,
6231 constants,
6232 parameter_types,
6233 expr_unbased_fill_literal(part).map(|_| 1),
6234 None,
6235 )?);
6236 }
6237 let width = regs
6238 .iter()
6239 .map(|reg| builder.register(reg).width())
6240 .sum::<usize>();
6241 let reg = builder.alloc_logic(width);
6242 builder.emit(SIRInstruction::Concat(reg, regs));
6243 Some(reg)
6244 }
6245 sv::ir::Expr::RepeatConcat { count, parts } => {
6246 let count =
6247 sv::typecheck::eval_const_expr_with_types(count, constants, parameter_types)?;
6248 let count = usize::try_from(count).ok()?;
6249 let mut regs = Vec::new();
6250 for _ in 0..count {
6251 for part in parts {
6252 regs.push(lower_expr_to_sir_with_context(
6253 builder,
6254 part,
6255 variables,
6256 name_to_id,
6257 constants,
6258 parameter_types,
6259 expr_unbased_fill_literal(part).map(|_| 1),
6260 None,
6261 )?);
6262 }
6263 }
6264 let width = regs
6265 .iter()
6266 .map(|reg| builder.register(reg).width())
6267 .sum::<usize>();
6268 let reg = builder.alloc_logic(width);
6269 builder.emit(SIRInstruction::Concat(reg, regs));
6270 Some(reg)
6271 }
6272 sv::ir::Expr::Mux {
6273 condition,
6274 then_expr,
6275 else_expr,
6276 } => {
6277 let arms_signed = sv_expr_is_signed_with_parameters(
6278 then_expr,
6279 variables,
6280 name_to_id,
6281 parameter_types,
6282 ) && sv_expr_is_signed_with_parameters(
6283 else_expr,
6284 variables,
6285 name_to_id,
6286 parameter_types,
6287 );
6288 let arm_context =
6289 sv_expr_natural_width(expr, variables, name_to_id, constants, parameter_types)
6290 .map(|natural_width| {
6291 context_width.map_or(natural_width, |width| width.max(natural_width))
6292 })
6293 .or(context_width);
6294 let condition = lower_expr_to_sir_with_context(
6295 builder,
6296 condition,
6297 variables,
6298 name_to_id,
6299 constants,
6300 parameter_types,
6301 None,
6302 None,
6303 )?;
6304 let mut then_expr = lower_expr_to_sir_with_context(
6305 builder,
6306 then_expr,
6307 variables,
6308 name_to_id,
6309 constants,
6310 parameter_types,
6311 arm_context,
6312 Some(arms_signed),
6313 )?;
6314 let mut else_expr = lower_expr_to_sir_with_context(
6315 builder,
6316 else_expr,
6317 variables,
6318 name_to_id,
6319 constants,
6320 parameter_types,
6321 arm_context,
6322 Some(arms_signed),
6323 )?;
6324 let width = builder
6325 .register(&then_expr)
6326 .width()
6327 .max(builder.register(&else_expr).width());
6328 then_expr = resize_sir_register(builder, then_expr, width, arms_signed)?;
6329 else_expr = resize_sir_register(builder, else_expr, width, arms_signed)?;
6330 let reg = builder.alloc_logic(width);
6331 builder.emit(SIRInstruction::Mux(reg, condition, then_expr, else_expr));
6332 Some(reg)
6333 }
6334 sv::ir::Expr::Call { .. } => None,
6335 }
6336}
6337
6338fn module_constants_with_overrides(
6339 module: &sv::ir::Module,
6340 parameter_overrides: &[LoweredSvParameterOverride],
6341) -> HashMap<String, i128> {
6342 let override_values: HashMap<&str, &sv::ir::ConstExpr> = parameter_overrides
6343 .iter()
6344 .filter_map(|parameter| {
6345 parameter
6346 .value
6347 .as_ref()
6348 .map(|value| (parameter.name.as_str(), value))
6349 })
6350 .collect();
6351 let mut constants = HashMap::default();
6352 for parameter in module.parameters() {
6353 let value = if let Some(override_value) = override_values.get(parameter.name()) {
6354 sv::typecheck::eval_const_expr(override_value, &constants)
6355 } else {
6356 parameter.resolved_value().or_else(|| {
6357 parameter
6358 .value()
6359 .and_then(|expr| sv::typecheck::eval_const_expr(expr, &constants))
6360 })
6361 };
6362 if let Some(value) = value {
6363 constants.insert(parameter.name().to_string(), value);
6364 }
6365 }
6366
6367 constants
6368}
6369
6370fn unary_op_from_sv(op: sv::ir::UnaryOp) -> Option<UnaryOp> {
6371 match op {
6372 sv::ir::UnaryOp::Plus => Some(UnaryOp::Ident),
6373 sv::ir::UnaryOp::Minus => Some(UnaryOp::Minus),
6374 sv::ir::UnaryOp::BitNot => Some(UnaryOp::BitNot),
6375 sv::ir::UnaryOp::LogicNot => Some(UnaryOp::LogicNot),
6376 sv::ir::UnaryOp::ToTwoState => Some(UnaryOp::ToTwoState),
6377 sv::ir::UnaryOp::RedAnd => Some(UnaryOp::And),
6378 sv::ir::UnaryOp::RedOr => Some(UnaryOp::Or),
6379 sv::ir::UnaryOp::RedXor => Some(UnaryOp::Xor),
6380 }
6381}
6382
6383fn binary_op_from_sv(op: sv::ir::BinaryOp, operands_signed: bool) -> BinaryOp {
6384 match op {
6385 sv::ir::BinaryOp::Add => BinaryOp::Add,
6386 sv::ir::BinaryOp::Sub => BinaryOp::Sub,
6387 sv::ir::BinaryOp::Mul => BinaryOp::Mul,
6388 sv::ir::BinaryOp::Div if operands_signed => BinaryOp::DivS,
6389 sv::ir::BinaryOp::Div => BinaryOp::DivU,
6390 sv::ir::BinaryOp::Mod if operands_signed => BinaryOp::RemS,
6391 sv::ir::BinaryOp::Mod => BinaryOp::RemU,
6392 sv::ir::BinaryOp::Shl => BinaryOp::Shl,
6393 sv::ir::BinaryOp::Shr => BinaryOp::Shr,
6394 sv::ir::BinaryOp::Sar if operands_signed => BinaryOp::Sar,
6395 sv::ir::BinaryOp::Sar => BinaryOp::Shr,
6396 sv::ir::BinaryOp::BitAnd => BinaryOp::And,
6397 sv::ir::BinaryOp::BitOr => BinaryOp::Or,
6398 sv::ir::BinaryOp::BitXor => BinaryOp::Xor,
6399 sv::ir::BinaryOp::LogicAnd => BinaryOp::LogicAnd,
6400 sv::ir::BinaryOp::LogicOr => BinaryOp::LogicOr,
6401 sv::ir::BinaryOp::Eq => BinaryOp::Eq,
6402 sv::ir::BinaryOp::Ne => BinaryOp::Ne,
6403 sv::ir::BinaryOp::EqCase => BinaryOp::EqCase,
6404 sv::ir::BinaryOp::NeCase => BinaryOp::NeCase,
6405 sv::ir::BinaryOp::EqWildcard => BinaryOp::EqWildcard,
6406 sv::ir::BinaryOp::NeWildcard => BinaryOp::NeWildcard,
6407 sv::ir::BinaryOp::Lt if operands_signed => BinaryOp::LtS,
6408 sv::ir::BinaryOp::Lt => BinaryOp::LtU,
6409 sv::ir::BinaryOp::Le if operands_signed => BinaryOp::LeS,
6410 sv::ir::BinaryOp::Le => BinaryOp::LeU,
6411 sv::ir::BinaryOp::Gt if operands_signed => BinaryOp::GtS,
6412 sv::ir::BinaryOp::Gt => BinaryOp::GtU,
6413 sv::ir::BinaryOp::Ge if operands_signed => BinaryOp::GeS,
6414 sv::ir::BinaryOp::Ge => BinaryOp::GeU,
6415 }
6416}
6417
6418pub(crate) fn sv_top_not_found(name: String) -> ParserError {
6419 ParserError::TopNotFound { name }
6420}
6421
6422pub(crate) fn unsupported_sv_instance(name: String) -> ParserError {
6423 ParserError::unsupported(
6424 64,
6425 LoweringPhase::SimulatorParser,
6426 "systemverilog module instantiation",
6427 format!("name: \"{}\"", name),
6428 None,
6429 )
6430}
6431
6432pub(crate) fn unsupported_sv_inout(path: String) -> ParserError {
6433 ParserError::unsupported(
6434 64,
6435 LoweringPhase::SimulatorParser,
6436 "systemverilog inout port",
6437 path,
6438 None,
6439 )
6440}
6441
6442#[cfg(test)]
6443mod tests {
6444 use super::*;
6445
6446 #[test]
6447 fn select_sources_adds_nested_offsets_and_keeps_computed_dependencies() {
6448 let nested = sv::ir::Expr::Select {
6449 expr: Box::new(sv::ir::Expr::Ident("a".to_string())),
6450 msb: sv::ir::ConstExpr::Literal("15".to_string()),
6451 lsb: sv::ir::ConstExpr::Literal("8".to_string()),
6452 signed: false,
6453 };
6454 let nested_sources = HashSet::from_iter([VarAtomBase::new(1u8, 8, 15)]);
6455 let narrowed = select_sources(&nested, nested_sources, BitAccess::new(0, 0)).unwrap();
6456 assert_eq!(narrowed, HashSet::from_iter([VarAtomBase::new(1u8, 8, 8)]));
6457
6458 let computed = sv::ir::Expr::Binary {
6459 left: Box::new(sv::ir::Expr::Ident("a".to_string())),
6460 op: sv::ir::BinaryOp::Add,
6461 right: Box::new(sv::ir::Expr::Ident("b".to_string())),
6462 };
6463 let computed_sources =
6464 HashSet::from_iter([VarAtomBase::new(1u8, 0, 7), VarAtomBase::new(2u8, 0, 7)]);
6465 assert_eq!(
6466 select_sources(&computed, computed_sources.clone(), BitAccess::new(7, 7)).unwrap(),
6467 computed_sources
6468 );
6469 }
6470}