Skip to main content

celox_frontend_sv/
lowering.rs

1//! SystemVerilog lowering adapter for the shared Celox frontend.
2//!
3//! SystemVerilog syntax and semantic analysis belongs in the
4//! `celox-sv-analyzer` crate. This module converts analyzed SV into the shared
5//! symbolic assembly model. It intentionally lives beside that assembly rather
6//! than in the public `celox` facade or in a misleading frontend-to-frontend
7//! dependency.
8
9use 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
493/// Lower the requested SystemVerilog roots and their reachable children into
494/// an embeddable hierarchy. The module IDs in the returned graph are local and
495/// are remapped during mixed-language symbolic hierarchy assembly.
496pub 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
599/// Analyze SystemVerilog sources and lower the selected top through Celox's
600/// shared symbolic scheduling pipeline.
601pub 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, &parameter_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, &parameter_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, &parameter_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            &parameter_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, &parameter_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, &parameter_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                // Each process seeds the slices it owns before evaluating.  Read
4959                // the shared working value here so a later merged always_ff
4960                // process preserves disjoint slices written by an earlier one.
4961                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                // Flush preceding static assignments before a dynamic store so
5074                // the direct element write observes the current working value.
5075                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}