Skip to main content

celox_frontend_core/
sdk.rs

1//! Projection from the stable frontend SDK into Celox symbolic internals.
2
3use celox_design::{
4    BinaryOp, BitAccess, DomainKind, InitialStateData, InitialStateValue, ModuleId, PortTypeKind,
5    RegionedVarAddrBase, STABLE_REGION, TriggerSet, UnaryOp, VarAtomBase, VariableMetadata,
6    WORKING_REGION,
7};
8use celox_frontend_sdk::{
9    ActiveLevel, Direction, Edge, ExprId, ExprNode, FrontendArtifact, SignalId, SignalSlice,
10    ValueType,
11};
12use celox_sir::{
13    BlockId, ExecutionUnit, RegisterId, SIRBuilder, SIRInstruction, SIROffset, SIRTerminator,
14    SIRValue, merge_sir_eus,
15};
16use celox_slt::{LogicPath, LogicPathTarget, NodeId, SLTNode, SLTNodeArena};
17use thiserror::Error;
18
19use crate::symbolic::artifact::{
20    ExternalHierarchy, ExternalModule, SimModule, SymbolicRtl, SymbolicVariable,
21};
22use crate::symbolic::width::coerce_node_width;
23use crate::{HashMap, HashSet, SourceVarId, VariableKind};
24
25type RegionedSourceAddr = RegionedVarAddrBase<SourceVarId>;
26
27/// Internal artifacts derived from one stable SDK value.
28///
29/// `symbolic` compiles the artifact as a standalone design. `external` lets a
30/// Veryl native testbench instantiate the same module without teaching the SDK
31/// about Veryl syntax.
32pub struct LoweredFrontendArtifact {
33    pub symbolic: SymbolicRtl,
34    pub external: ExternalHierarchy,
35}
36
37#[derive(Debug, Error)]
38pub enum FrontendArtifactError {
39    #[error("invalid frontend artifact: {0}")]
40    Validation(#[from] celox_frontend_sdk::BuildError),
41    #[error("frontend artifact references unknown signal {0}")]
42    UnknownSignal(u32),
43    #[error("frontend artifact references unknown expression {0}")]
44    UnknownExpression(u32),
45    #[error("unsupported frontend SDK expression or operation")]
46    UnsupportedOperation,
47    #[error("signal `{signal}` is used with conflicting clock/reset roles")]
48    ConflictingSignalRole { signal: String },
49    #[error(
50        "async reset `{reset}` is shared by distinct clock domains `{first_clock}` and `{second_clock}`"
51    )]
52    SharedResetAcrossClocks {
53        reset: String,
54        first_clock: String,
55        second_clock: String,
56    },
57    #[error("frontend SDK expression is invalid: {0}")]
58    InvalidExpression(#[from] celox_slt::SLTNodeFactsError),
59}
60
61fn source_id(id: SignalId) -> SourceVarId {
62    SourceVarId(id.index())
63}
64
65fn signal_atom(slice: SignalSlice) -> VarAtomBase<SourceVarId> {
66    VarAtomBase::new(
67        source_id(slice.signal()),
68        slice.lsb(),
69        slice.lsb() + slice.width() - 1,
70    )
71}
72
73fn signal_slice_type(
74    artifact: &FrontendArtifact,
75    slice: SignalSlice,
76) -> Result<ValueType, FrontendArtifactError> {
77    let signal_type = artifact
78        .signal(slice.signal())
79        .ok_or(FrontendArtifactError::UnknownSignal(slice.signal().index()))?
80        .value_type();
81    Ok(ValueType::new(
82        slice.width(),
83        signal_type.is_signed() && slice.width() == signal_type.width(),
84        signal_type.is_four_state(),
85    )?)
86}
87
88fn binary_op(op: celox_frontend_sdk::BinaryOp) -> Result<BinaryOp, FrontendArtifactError> {
89    Ok(match op {
90        celox_frontend_sdk::BinaryOp::Add => BinaryOp::Add,
91        celox_frontend_sdk::BinaryOp::Sub => BinaryOp::Sub,
92        celox_frontend_sdk::BinaryOp::Mul => BinaryOp::Mul,
93        celox_frontend_sdk::BinaryOp::DivUnsigned => BinaryOp::DivU,
94        celox_frontend_sdk::BinaryOp::DivSigned => BinaryOp::DivS,
95        celox_frontend_sdk::BinaryOp::RemUnsigned => BinaryOp::RemU,
96        celox_frontend_sdk::BinaryOp::RemSigned => BinaryOp::RemS,
97        celox_frontend_sdk::BinaryOp::And => BinaryOp::And,
98        celox_frontend_sdk::BinaryOp::Or => BinaryOp::Or,
99        celox_frontend_sdk::BinaryOp::Xor => BinaryOp::Xor,
100        celox_frontend_sdk::BinaryOp::ShiftLeft => BinaryOp::Shl,
101        celox_frontend_sdk::BinaryOp::ShiftRight => BinaryOp::Shr,
102        celox_frontend_sdk::BinaryOp::ArithmeticShiftRight => BinaryOp::Sar,
103        celox_frontend_sdk::BinaryOp::Equal => BinaryOp::Eq,
104        celox_frontend_sdk::BinaryOp::NotEqual => BinaryOp::Ne,
105        celox_frontend_sdk::BinaryOp::CaseEqual => BinaryOp::EqCase,
106        celox_frontend_sdk::BinaryOp::CaseNotEqual => BinaryOp::NeCase,
107        celox_frontend_sdk::BinaryOp::LessUnsigned => BinaryOp::LtU,
108        celox_frontend_sdk::BinaryOp::LessSigned => BinaryOp::LtS,
109        celox_frontend_sdk::BinaryOp::LessEqualUnsigned => BinaryOp::LeU,
110        celox_frontend_sdk::BinaryOp::LessEqualSigned => BinaryOp::LeS,
111        celox_frontend_sdk::BinaryOp::GreaterUnsigned => BinaryOp::GtU,
112        celox_frontend_sdk::BinaryOp::GreaterSigned => BinaryOp::GtS,
113        celox_frontend_sdk::BinaryOp::GreaterEqualUnsigned => BinaryOp::GeU,
114        celox_frontend_sdk::BinaryOp::GreaterEqualSigned => BinaryOp::GeS,
115        celox_frontend_sdk::BinaryOp::LogicAnd => BinaryOp::LogicAnd,
116        celox_frontend_sdk::BinaryOp::LogicOr => BinaryOp::LogicOr,
117        _ => return Err(FrontendArtifactError::UnsupportedOperation),
118    })
119}
120
121fn unary_op(op: celox_frontend_sdk::UnaryOp) -> Result<UnaryOp, FrontendArtifactError> {
122    Ok(match op {
123        celox_frontend_sdk::UnaryOp::ToTwoState => UnaryOp::ToTwoState,
124        celox_frontend_sdk::UnaryOp::Negate => UnaryOp::Minus,
125        celox_frontend_sdk::UnaryOp::BitNot => UnaryOp::BitNot,
126        celox_frontend_sdk::UnaryOp::LogicNot => UnaryOp::LogicNot,
127        celox_frontend_sdk::UnaryOp::ReduceAnd => UnaryOp::And,
128        celox_frontend_sdk::UnaryOp::ReduceOr => UnaryOp::Or,
129        celox_frontend_sdk::UnaryOp::ReduceXor => UnaryOp::Xor,
130        celox_frontend_sdk::UnaryOp::PopCount => UnaryOp::PopCount,
131        celox_frontend_sdk::UnaryOp::CountLeadingZeros => UnaryOp::CountLeadingZeros,
132        celox_frontend_sdk::UnaryOp::CountTrailingZeros => UnaryOp::CountTrailingZeros,
133        _ => return Err(FrontendArtifactError::UnsupportedOperation),
134    })
135}
136
137fn expression_sources(
138    artifact: &FrontendArtifact,
139    id: ExprId,
140    sources: &mut HashSet<VarAtomBase<SourceVarId>>,
141    visited: &mut HashSet<ExprId>,
142) -> Result<(), FrontendArtifactError> {
143    if !visited.insert(id) {
144        return Ok(());
145    }
146    let expression = artifact
147        .expression(id)
148        .ok_or(FrontendArtifactError::UnknownExpression(id.index()))?;
149    match expression.node() {
150        ExprNode::Signal(slice) => {
151            sources.insert(signal_atom(*slice));
152        }
153        ExprNode::Constant(_) => {}
154        ExprNode::Binary { lhs, rhs, .. } => {
155            expression_sources(artifact, *lhs, sources, visited)?;
156            expression_sources(artifact, *rhs, sources, visited)?;
157        }
158        ExprNode::Unary { input, .. } | ExprNode::Slice { input, .. } => {
159            expression_sources(artifact, *input, sources, visited)?;
160        }
161        ExprNode::Mux {
162            condition,
163            then_expr,
164            else_expr,
165        } => {
166            expression_sources(artifact, *condition, sources, visited)?;
167            expression_sources(artifact, *then_expr, sources, visited)?;
168            expression_sources(artifact, *else_expr, sources, visited)?;
169        }
170        ExprNode::Concat(parts) => {
171            for part in parts {
172                expression_sources(artifact, *part, sources, visited)?;
173            }
174        }
175        _ => return Err(FrontendArtifactError::UnsupportedOperation),
176    }
177    Ok(())
178}
179
180fn coerce_slt_expression(
181    artifact: &FrontendArtifact,
182    id: ExprId,
183    target_width: usize,
184    arena: &mut SLTNodeArena<SourceVarId>,
185    cache: &mut HashMap<ExprId, NodeId>,
186) -> Result<NodeId, FrontendArtifactError> {
187    let value_type = artifact
188        .expression(id)
189        .ok_or(FrontendArtifactError::UnknownExpression(id.index()))?
190        .value_type();
191    let node = lower_slt_expression(artifact, id, arena, cache)?;
192    Ok(coerce_node_width(
193        arena,
194        node,
195        Some(target_width),
196        value_type.is_signed(),
197    )?)
198}
199
200fn coerce_slt_expression_to_type(
201    artifact: &FrontendArtifact,
202    id: ExprId,
203    target_type: ValueType,
204    arena: &mut SLTNodeArena<SourceVarId>,
205    cache: &mut HashMap<ExprId, NodeId>,
206) -> Result<NodeId, FrontendArtifactError> {
207    let value_type = artifact
208        .expression(id)
209        .ok_or(FrontendArtifactError::UnknownExpression(id.index()))?
210        .value_type();
211    let node = lower_slt_expression(artifact, id, arena, cache)?;
212    if value_type == target_type {
213        Ok(node)
214    } else {
215        finish_slt_expression(arena, node, target_type)
216    }
217}
218
219fn finish_slt_expression(
220    arena: &mut SLTNodeArena<SourceVarId>,
221    node: NodeId,
222    value_type: ValueType,
223) -> Result<NodeId, FrontendArtifactError> {
224    let node = coerce_node_width(
225        arena,
226        node,
227        Some(value_type.width()),
228        value_type.is_signed(),
229    )?;
230    if value_type.is_four_state() {
231        Ok(node)
232    } else {
233        Ok(arena.alloc(SLTNode::Unary(UnaryOp::ToTwoState, node))?)
234    }
235}
236
237fn lower_slt_expression(
238    artifact: &FrontendArtifact,
239    id: ExprId,
240    arena: &mut SLTNodeArena<SourceVarId>,
241    cache: &mut HashMap<ExprId, NodeId>,
242) -> Result<NodeId, FrontendArtifactError> {
243    if let Some(node) = cache.get(&id) {
244        return Ok(*node);
245    }
246    let expression = artifact
247        .expression(id)
248        .ok_or(FrontendArtifactError::UnknownExpression(id.index()))?;
249    let node = match expression.node() {
250        ExprNode::Signal(slice) => SLTNode::Input {
251            variable: source_id(slice.signal()),
252            signed: expression.value_type().is_signed(),
253            index: Vec::new(),
254            access: BitAccess::new(slice.lsb(), slice.lsb() + slice.width() - 1),
255        },
256        ExprNode::Constant(value) => SLTNode::Constant(
257            value.payload().clone(),
258            value.mask().clone(),
259            value.value_type().width(),
260            value.value_type().is_signed(),
261        ),
262        ExprNode::Binary { op, lhs, rhs } => {
263            use celox_frontend_sdk::BinaryOp as SdkBinaryOp;
264
265            let lhs_type = artifact
266                .expression(*lhs)
267                .ok_or(FrontendArtifactError::UnknownExpression(lhs.index()))?
268                .value_type();
269            let rhs_type = artifact
270                .expression(*rhs)
271                .ok_or(FrontendArtifactError::UnknownExpression(rhs.index()))?
272                .value_type();
273            let (lhs, rhs) = match op {
274                SdkBinaryOp::ShiftLeft
275                | SdkBinaryOp::ShiftRight
276                | SdkBinaryOp::ArithmeticShiftRight => (
277                    coerce_slt_expression(
278                        artifact,
279                        *lhs,
280                        expression.value_type().width(),
281                        arena,
282                        cache,
283                    )?,
284                    lower_slt_expression(artifact, *rhs, arena, cache)?,
285                ),
286                SdkBinaryOp::Equal
287                | SdkBinaryOp::NotEqual
288                | SdkBinaryOp::CaseEqual
289                | SdkBinaryOp::CaseNotEqual
290                | SdkBinaryOp::LessUnsigned
291                | SdkBinaryOp::LessSigned
292                | SdkBinaryOp::LessEqualUnsigned
293                | SdkBinaryOp::LessEqualSigned
294                | SdkBinaryOp::GreaterUnsigned
295                | SdkBinaryOp::GreaterSigned
296                | SdkBinaryOp::GreaterEqualUnsigned
297                | SdkBinaryOp::GreaterEqualSigned => {
298                    let operand_width = lhs_type.width().max(rhs_type.width());
299                    (
300                        coerce_slt_expression(artifact, *lhs, operand_width, arena, cache)?,
301                        coerce_slt_expression(artifact, *rhs, operand_width, arena, cache)?,
302                    )
303                }
304                SdkBinaryOp::LogicAnd | SdkBinaryOp::LogicOr => (
305                    lower_slt_expression(artifact, *lhs, arena, cache)?,
306                    lower_slt_expression(artifact, *rhs, arena, cache)?,
307                ),
308                _ => (
309                    coerce_slt_expression(
310                        artifact,
311                        *lhs,
312                        expression.value_type().width(),
313                        arena,
314                        cache,
315                    )?,
316                    coerce_slt_expression(
317                        artifact,
318                        *rhs,
319                        expression.value_type().width(),
320                        arena,
321                        cache,
322                    )?,
323                ),
324            };
325            SLTNode::Binary(lhs, binary_op(*op)?, rhs)
326        }
327        ExprNode::Unary { op, input } => {
328            let input = match op {
329                celox_frontend_sdk::UnaryOp::Negate | celox_frontend_sdk::UnaryOp::BitNot => {
330                    coerce_slt_expression(
331                        artifact,
332                        *input,
333                        expression.value_type().width(),
334                        arena,
335                        cache,
336                    )?
337                }
338                _ => lower_slt_expression(artifact, *input, arena, cache)?,
339            };
340            SLTNode::Unary(unary_op(*op)?, input)
341        }
342        ExprNode::Mux {
343            condition,
344            then_expr,
345            else_expr,
346        } => SLTNode::Mux {
347            cond: lower_slt_expression(artifact, *condition, arena, cache)?,
348            then_expr: coerce_slt_expression(
349                artifact,
350                *then_expr,
351                expression.value_type().width(),
352                arena,
353                cache,
354            )?,
355            else_expr: coerce_slt_expression(
356                artifact,
357                *else_expr,
358                expression.value_type().width(),
359                arena,
360                cache,
361            )?,
362        },
363        ExprNode::Concat(parts) => SLTNode::Concat(
364            parts
365                .iter()
366                .map(|part| {
367                    let expression = artifact
368                        .expression(*part)
369                        .ok_or(FrontendArtifactError::UnknownExpression(part.index()))?;
370                    Ok((
371                        lower_slt_expression(artifact, *part, arena, cache)?,
372                        expression.value_type().width(),
373                    ))
374                })
375                .collect::<Result<Vec<_>, FrontendArtifactError>>()?,
376        ),
377        ExprNode::Slice { input, lsb } => SLTNode::Slice {
378            expr: lower_slt_expression(artifact, *input, arena, cache)?,
379            access: BitAccess::new(*lsb, *lsb + expression.value_type().width() - 1),
380        },
381        _ => return Err(FrontendArtifactError::UnsupportedOperation),
382    };
383    let node = arena.alloc(node)?;
384    let node = finish_slt_expression(arena, node, expression.value_type())?;
385    cache.insert(id, node);
386    Ok(node)
387}
388
389fn alloc_register(
390    builder: &mut SIRBuilder<RegionedSourceAddr>,
391    ty: celox_frontend_sdk::ValueType,
392) -> RegisterId {
393    if ty.is_four_state() {
394        builder.alloc_logic(ty.width())
395    } else {
396        builder.alloc_bit(ty.width(), ty.is_signed())
397    }
398}
399
400fn coerce_sir_register(
401    builder: &mut SIRBuilder<RegionedSourceAddr>,
402    input: RegisterId,
403    input_type: ValueType,
404    target_type: ValueType,
405) -> Result<RegisterId, FrontendArtifactError> {
406    let mut current = input;
407    let mut current_type = input_type;
408
409    if current_type.width() > target_type.width() {
410        let narrowed_type = ValueType::new(
411            target_type.width(),
412            current_type.is_signed(),
413            current_type.is_four_state(),
414        )?;
415        let narrowed = alloc_register(builder, narrowed_type);
416        builder.emit(SIRInstruction::Slice(
417            narrowed,
418            current,
419            0,
420            target_type.width(),
421        ));
422        current = narrowed;
423        current_type = narrowed_type;
424    } else if current_type.width() < target_type.width() {
425        let extension_width = target_type.width() - current_type.width();
426        let extension_type = ValueType::new(extension_width, false, current_type.is_four_state())?;
427        let extension = alloc_register(builder, extension_type);
428        if current_type.is_signed() {
429            let sign_type = ValueType::new(1, false, current_type.is_four_state())?;
430            let sign = alloc_register(builder, sign_type);
431            builder.emit(SIRInstruction::Slice(
432                sign,
433                current,
434                current_type.width() - 1,
435                1,
436            ));
437            builder.emit(SIRInstruction::Concat(
438                extension,
439                vec![sign; extension_width],
440            ));
441        } else {
442            builder.emit(SIRInstruction::Imm(extension, SIRValue::new(0u8)));
443        }
444        let widened_type = ValueType::new(
445            target_type.width(),
446            current_type.is_signed(),
447            current_type.is_four_state(),
448        )?;
449        let widened = alloc_register(builder, widened_type);
450        builder.emit(SIRInstruction::Concat(widened, vec![extension, current]));
451        current = widened;
452        current_type = widened_type;
453    }
454
455    if current_type.is_four_state() && !target_type.is_four_state() {
456        let converted = alloc_register(builder, target_type);
457        builder.emit(SIRInstruction::Unary(
458            converted,
459            UnaryOp::ToTwoState,
460            current,
461        ));
462        return Ok(converted);
463    }
464
465    if current_type.is_four_state() != target_type.is_four_state()
466        || (!target_type.is_four_state() && current_type.is_signed() != target_type.is_signed())
467    {
468        let converted = alloc_register(builder, target_type);
469        builder.emit(SIRInstruction::Unary(converted, UnaryOp::Ident, current));
470        return Ok(converted);
471    }
472
473    Ok(current)
474}
475
476fn coerce_sir_expression(
477    artifact: &FrontendArtifact,
478    id: ExprId,
479    target_type: ValueType,
480    builder: &mut SIRBuilder<RegionedSourceAddr>,
481    cache: &mut HashMap<ExprId, RegisterId>,
482) -> Result<RegisterId, FrontendArtifactError> {
483    let input_type = artifact
484        .expression(id)
485        .ok_or(FrontendArtifactError::UnknownExpression(id.index()))?
486        .value_type();
487    let input = lower_sir_expression(artifact, id, builder, cache)?;
488    coerce_sir_register(builder, input, input_type, target_type)
489}
490
491fn lower_sir_expression(
492    artifact: &FrontendArtifact,
493    id: ExprId,
494    builder: &mut SIRBuilder<RegionedSourceAddr>,
495    cache: &mut HashMap<ExprId, RegisterId>,
496) -> Result<RegisterId, FrontendArtifactError> {
497    if let Some(register) = cache.get(&id) {
498        return Ok(*register);
499    }
500    let expression = artifact
501        .expression(id)
502        .ok_or(FrontendArtifactError::UnknownExpression(id.index()))?;
503    let result = match expression.node() {
504        ExprNode::Signal(slice) => {
505            let result = alloc_register(builder, expression.value_type());
506            builder.emit(SIRInstruction::Load(
507                result,
508                RegionedSourceAddr {
509                    region: STABLE_REGION,
510                    var_id: source_id(slice.signal()),
511                },
512                SIROffset::Static(slice.lsb()),
513                slice.width(),
514            ));
515            result
516        }
517        ExprNode::Constant(value) => {
518            let result = alloc_register(builder, expression.value_type());
519            builder.emit(SIRInstruction::Imm(
520                result,
521                SIRValue::new_four_state(value.payload().clone(), value.mask().clone()),
522            ));
523            result
524        }
525        ExprNode::Binary { op, lhs, rhs } => {
526            use celox_frontend_sdk::BinaryOp as SdkBinaryOp;
527
528            let lhs_type = artifact
529                .expression(*lhs)
530                .ok_or(FrontendArtifactError::UnknownExpression(lhs.index()))?
531                .value_type();
532            let rhs_type = artifact
533                .expression(*rhs)
534                .ok_or(FrontendArtifactError::UnknownExpression(rhs.index()))?
535                .value_type();
536            let (lhs, rhs) = match op {
537                SdkBinaryOp::ShiftLeft
538                | SdkBinaryOp::ShiftRight
539                | SdkBinaryOp::ArithmeticShiftRight => (
540                    coerce_sir_expression(
541                        artifact,
542                        *lhs,
543                        ValueType::new(
544                            expression.value_type().width(),
545                            lhs_type.is_signed(),
546                            lhs_type.is_four_state(),
547                        )?,
548                        builder,
549                        cache,
550                    )?,
551                    lower_sir_expression(artifact, *rhs, builder, cache)?,
552                ),
553                SdkBinaryOp::Equal
554                | SdkBinaryOp::NotEqual
555                | SdkBinaryOp::CaseEqual
556                | SdkBinaryOp::CaseNotEqual
557                | SdkBinaryOp::LessUnsigned
558                | SdkBinaryOp::LessSigned
559                | SdkBinaryOp::LessEqualUnsigned
560                | SdkBinaryOp::LessEqualSigned
561                | SdkBinaryOp::GreaterUnsigned
562                | SdkBinaryOp::GreaterSigned
563                | SdkBinaryOp::GreaterEqualUnsigned
564                | SdkBinaryOp::GreaterEqualSigned => {
565                    let width = lhs_type.width().max(rhs_type.width());
566                    (
567                        coerce_sir_expression(
568                            artifact,
569                            *lhs,
570                            ValueType::new(width, lhs_type.is_signed(), lhs_type.is_four_state())?,
571                            builder,
572                            cache,
573                        )?,
574                        coerce_sir_expression(
575                            artifact,
576                            *rhs,
577                            ValueType::new(width, rhs_type.is_signed(), rhs_type.is_four_state())?,
578                            builder,
579                            cache,
580                        )?,
581                    )
582                }
583                SdkBinaryOp::LogicAnd | SdkBinaryOp::LogicOr => (
584                    lower_sir_expression(artifact, *lhs, builder, cache)?,
585                    lower_sir_expression(artifact, *rhs, builder, cache)?,
586                ),
587                _ => (
588                    coerce_sir_expression(
589                        artifact,
590                        *lhs,
591                        ValueType::new(
592                            expression.value_type().width(),
593                            lhs_type.is_signed(),
594                            lhs_type.is_four_state(),
595                        )?,
596                        builder,
597                        cache,
598                    )?,
599                    coerce_sir_expression(
600                        artifact,
601                        *rhs,
602                        ValueType::new(
603                            expression.value_type().width(),
604                            rhs_type.is_signed(),
605                            rhs_type.is_four_state(),
606                        )?,
607                        builder,
608                        cache,
609                    )?,
610                ),
611            };
612            let is_boolean = matches!(
613                op,
614                SdkBinaryOp::Equal
615                    | SdkBinaryOp::NotEqual
616                    | SdkBinaryOp::CaseEqual
617                    | SdkBinaryOp::CaseNotEqual
618                    | SdkBinaryOp::LessUnsigned
619                    | SdkBinaryOp::LessSigned
620                    | SdkBinaryOp::LessEqualUnsigned
621                    | SdkBinaryOp::LessEqualSigned
622                    | SdkBinaryOp::GreaterUnsigned
623                    | SdkBinaryOp::GreaterSigned
624                    | SdkBinaryOp::GreaterEqualUnsigned
625                    | SdkBinaryOp::GreaterEqualSigned
626                    | SdkBinaryOp::LogicAnd
627                    | SdkBinaryOp::LogicOr
628            );
629            let case_equality = matches!(op, SdkBinaryOp::CaseEqual | SdkBinaryOp::CaseNotEqual);
630            let operation_four_state = expression.value_type().is_four_state()
631                || (!case_equality && (lhs_type.is_four_state() || rhs_type.is_four_state()));
632            let operation_type = ValueType::new(
633                if is_boolean {
634                    1
635                } else {
636                    expression.value_type().width()
637                },
638                !is_boolean && expression.value_type().is_signed(),
639                operation_four_state,
640            )?;
641            let operation_result = alloc_register(builder, operation_type);
642            builder.emit(SIRInstruction::Binary(
643                operation_result,
644                lhs,
645                binary_op(*op)?,
646                rhs,
647            ));
648            coerce_sir_register(
649                builder,
650                operation_result,
651                operation_type,
652                expression.value_type(),
653            )?
654        }
655        ExprNode::Unary { op, input } => {
656            use celox_frontend_sdk::UnaryOp as SdkUnaryOp;
657
658            let input_type = artifact
659                .expression(*input)
660                .ok_or(FrontendArtifactError::UnknownExpression(input.index()))?
661                .value_type();
662            let (input, operation_type) = match op {
663                SdkUnaryOp::Negate | SdkUnaryOp::BitNot => (
664                    coerce_sir_expression(
665                        artifact,
666                        *input,
667                        ValueType::new(
668                            expression.value_type().width(),
669                            input_type.is_signed(),
670                            input_type.is_four_state(),
671                        )?,
672                        builder,
673                        cache,
674                    )?,
675                    ValueType::new(
676                        expression.value_type().width(),
677                        expression.value_type().is_signed(),
678                        expression.value_type().is_four_state() || input_type.is_four_state(),
679                    )?,
680                ),
681                SdkUnaryOp::LogicNot
682                | SdkUnaryOp::ReduceAnd
683                | SdkUnaryOp::ReduceOr
684                | SdkUnaryOp::ReduceXor => (
685                    lower_sir_expression(artifact, *input, builder, cache)?,
686                    ValueType::new(
687                        1,
688                        false,
689                        expression.value_type().is_four_state() || input_type.is_four_state(),
690                    )?,
691                ),
692                SdkUnaryOp::ToTwoState => (
693                    lower_sir_expression(artifact, *input, builder, cache)?,
694                    ValueType::new(input_type.width(), input_type.is_signed(), false)?,
695                ),
696                SdkUnaryOp::PopCount
697                | SdkUnaryOp::CountLeadingZeros
698                | SdkUnaryOp::CountTrailingZeros => (
699                    lower_sir_expression(artifact, *input, builder, cache)?,
700                    ValueType::new(
701                        unary_op(*op)?.result_width(input_type.width()),
702                        false,
703                        expression.value_type().is_four_state() || input_type.is_four_state(),
704                    )?,
705                ),
706                _ => return Err(FrontendArtifactError::UnsupportedOperation),
707            };
708            let operation_result = alloc_register(builder, operation_type);
709            builder.emit(SIRInstruction::Unary(
710                operation_result,
711                unary_op(*op)?,
712                input,
713            ));
714            coerce_sir_register(
715                builder,
716                operation_result,
717                operation_type,
718                expression.value_type(),
719            )?
720        }
721        ExprNode::Mux {
722            condition,
723            then_expr,
724            else_expr,
725        } => {
726            let condition_type = artifact
727                .expression(*condition)
728                .ok_or(FrontendArtifactError::UnknownExpression(condition.index()))?
729                .value_type();
730            let condition = lower_sir_expression(artifact, *condition, builder, cache)?;
731            let then_type = artifact
732                .expression(*then_expr)
733                .ok_or(FrontendArtifactError::UnknownExpression(then_expr.index()))?
734                .value_type();
735            let else_type = artifact
736                .expression(*else_expr)
737                .ok_or(FrontendArtifactError::UnknownExpression(else_expr.index()))?
738                .value_type();
739            let then_expr = coerce_sir_expression(
740                artifact,
741                *then_expr,
742                ValueType::new(
743                    expression.value_type().width(),
744                    then_type.is_signed(),
745                    then_type.is_four_state(),
746                )?,
747                builder,
748                cache,
749            )?;
750            let else_expr = coerce_sir_expression(
751                artifact,
752                *else_expr,
753                ValueType::new(
754                    expression.value_type().width(),
755                    else_type.is_signed(),
756                    else_type.is_four_state(),
757                )?,
758                builder,
759                cache,
760            )?;
761            let operation_type = ValueType::new(
762                expression.value_type().width(),
763                expression.value_type().is_signed(),
764                expression.value_type().is_four_state()
765                    || condition_type.is_four_state()
766                    || then_type.is_four_state()
767                    || else_type.is_four_state(),
768            )?;
769            let operation_result = alloc_register(builder, operation_type);
770            builder.emit(SIRInstruction::Mux(
771                operation_result,
772                condition,
773                then_expr,
774                else_expr,
775            ));
776            coerce_sir_register(
777                builder,
778                operation_result,
779                operation_type,
780                expression.value_type(),
781            )?
782        }
783        ExprNode::Concat(parts) => {
784            let parts = parts
785                .iter()
786                .map(|part| lower_sir_expression(artifact, *part, builder, cache))
787                .collect::<Result<Vec<_>, _>>()?;
788            let result = alloc_register(builder, expression.value_type());
789            builder.emit(SIRInstruction::Concat(result, parts));
790            result
791        }
792        ExprNode::Slice { input, lsb } => {
793            let input = lower_sir_expression(artifact, *input, builder, cache)?;
794            let result = alloc_register(builder, expression.value_type());
795            builder.emit(SIRInstruction::Slice(
796                result,
797                input,
798                *lsb,
799                expression.value_type().width(),
800            ));
801            result
802        }
803        _ => return Err(FrontendArtifactError::UnsupportedOperation),
804    };
805    cache.insert(id, result);
806    Ok(result)
807}
808
809fn lower_control(
810    artifact: &FrontendArtifact,
811    signal: SignalId,
812    active: ActiveLevel,
813    builder: &mut SIRBuilder<RegionedSourceAddr>,
814) -> Result<RegisterId, FrontendArtifactError> {
815    let signal_info = artifact
816        .signal(signal)
817        .ok_or(FrontendArtifactError::UnknownSignal(signal.index()))?;
818    let loaded = alloc_register(builder, signal_info.value_type());
819    builder.emit(SIRInstruction::Load(
820        loaded,
821        RegionedSourceAddr {
822            region: STABLE_REGION,
823            var_id: source_id(signal),
824        },
825        SIROffset::Static(0),
826        1,
827    ));
828    let polarized = if active == ActiveLevel::Low {
829        let inverted = alloc_register(
830            builder,
831            ValueType::new(1, false, signal_info.value_type().is_four_state())?,
832        );
833        builder.emit(SIRInstruction::Unary(inverted, UnaryOp::LogicNot, loaded));
834        inverted
835    } else {
836        loaded
837    };
838    if signal_info.value_type().is_four_state() {
839        let result = builder.alloc_bit(1, false);
840        builder.emit(SIRInstruction::Unary(
841            result,
842            UnaryOp::ToTwoState,
843            polarized,
844        ));
845        Ok(result)
846    } else {
847        Ok(polarized)
848    }
849}
850
851fn seal_builder(mut builder: SIRBuilder<RegionedSourceAddr>) -> ExecutionUnit<RegionedSourceAddr> {
852    builder.seal_block(SIRTerminator::Return);
853    let (blocks, register_map, _) = builder.drain();
854    ExecutionUnit {
855        entry_block_id: BlockId(0),
856        blocks,
857        register_map,
858    }
859}
860
861fn insert_or_merge(
862    blocks: &mut HashMap<TriggerSet<SourceVarId>, ExecutionUnit<RegionedSourceAddr>>,
863    trigger: TriggerSet<SourceVarId>,
864    unit: ExecutionUnit<RegionedSourceAddr>,
865) {
866    if let Some(existing) = blocks.remove(&trigger) {
867        blocks.insert(trigger, merge_sir_eus(&[existing, unit]).0);
868    } else {
869        blocks.insert(trigger, unit);
870    }
871}
872
873fn lower_registers(
874    artifact: &FrontendArtifact,
875    eval_only: &mut HashMap<TriggerSet<SourceVarId>, ExecutionUnit<RegionedSourceAddr>>,
876    apply: &mut HashMap<TriggerSet<SourceVarId>, ExecutionUnit<RegionedSourceAddr>>,
877    eval_apply: &mut HashMap<TriggerSet<SourceVarId>, ExecutionUnit<RegionedSourceAddr>>,
878    reset_clock_map: &mut HashMap<SourceVarId, SourceVarId>,
879) -> Result<(), FrontendArtifactError> {
880    for register in artifact.registers() {
881        let target = register.target();
882        let target_id = source_id(target.signal());
883        let trigger = TriggerSet {
884            clock: source_id(register.clock()),
885            resets: register
886                .async_reset()
887                .into_iter()
888                .map(|reset| source_id(reset.signal()))
889                .collect(),
890        };
891        if let Some(reset) = register.async_reset() {
892            let reset_id = source_id(reset.signal());
893            let clock_id = source_id(register.clock());
894            if let Some(first_clock_id) = reset_clock_map.get(&reset_id)
895                && *first_clock_id != clock_id
896            {
897                let signal_name = |id: SignalId| {
898                    artifact
899                        .signal(id)
900                        .map(|signal| signal.name().to_string())
901                        .ok_or(FrontendArtifactError::UnknownSignal(id.index()))
902                };
903                let first_clock = artifact
904                    .signals()
905                    .get(first_clock_id.0 as usize)
906                    .ok_or(FrontendArtifactError::UnknownSignal(first_clock_id.0))?;
907                return Err(FrontendArtifactError::SharedResetAcrossClocks {
908                    reset: signal_name(reset.signal())?,
909                    first_clock: first_clock.name().to_string(),
910                    second_clock: signal_name(register.clock())?,
911                });
912            }
913            reset_clock_map.insert(reset_id, clock_id);
914        }
915
916        let build_eval =
917            |commit: bool| -> Result<ExecutionUnit<RegionedSourceAddr>, FrontendArtifactError> {
918                let mut builder = SIRBuilder::new();
919                let target_info = artifact.signal(target.signal()).ok_or(
920                    FrontendArtifactError::UnknownSignal(target.signal().index()),
921                )?;
922                let target_type = target_info.value_type();
923                builder.emit(SIRInstruction::Commit(
924                    RegionedSourceAddr {
925                        region: STABLE_REGION,
926                        var_id: target_id,
927                    },
928                    RegionedSourceAddr {
929                        region: WORKING_REGION,
930                        var_id: target_id,
931                    },
932                    SIROffset::Static(0),
933                    target.width(),
934                    Vec::new(),
935                ));
936                let mut cache = HashMap::default();
937                let mut next = coerce_sir_expression(
938                    artifact,
939                    register.next(),
940                    target_type,
941                    &mut builder,
942                    &mut cache,
943                )?;
944                if let Some(enable) = register.enable() {
945                    let condition =
946                        lower_control(artifact, enable.signal(), enable.active(), &mut builder)?;
947                    let current = alloc_register(&mut builder, target_type);
948                    builder.emit(SIRInstruction::Load(
949                        current,
950                        RegionedSourceAddr {
951                            region: STABLE_REGION,
952                            var_id: target_id,
953                        },
954                        SIROffset::Static(0),
955                        target.width(),
956                    ));
957                    let selected = alloc_register(&mut builder, target_type);
958                    builder.emit(SIRInstruction::Mux(selected, condition, next, current));
959                    next = selected;
960                }
961                if let Some(reset) = register.async_reset() {
962                    let condition =
963                        lower_control(artifact, reset.signal(), reset.active(), &mut builder)?;
964                    let reset_value = coerce_sir_expression(
965                        artifact,
966                        reset.value(),
967                        target_type,
968                        &mut builder,
969                        &mut cache,
970                    )?;
971                    let selected = alloc_register(&mut builder, target_type);
972                    builder.emit(SIRInstruction::Mux(selected, condition, reset_value, next));
973                    next = selected;
974                }
975                builder.emit(SIRInstruction::Store(
976                    RegionedSourceAddr {
977                        region: WORKING_REGION,
978                        var_id: target_id,
979                    },
980                    SIROffset::Static(0),
981                    target.width(),
982                    next,
983                    Vec::new(),
984                    Vec::new(),
985                ));
986                if commit {
987                    builder.emit(SIRInstruction::Commit(
988                        RegionedSourceAddr {
989                            region: WORKING_REGION,
990                            var_id: target_id,
991                        },
992                        RegionedSourceAddr {
993                            region: STABLE_REGION,
994                            var_id: target_id,
995                        },
996                        SIROffset::Static(0),
997                        target.width(),
998                        Vec::new(),
999                    ));
1000                }
1001                Ok(seal_builder(builder))
1002            };
1003
1004        let mut apply_builder = SIRBuilder::new();
1005        apply_builder.emit(SIRInstruction::Commit(
1006            RegionedSourceAddr {
1007                region: WORKING_REGION,
1008                var_id: target_id,
1009            },
1010            RegionedSourceAddr {
1011                region: STABLE_REGION,
1012                var_id: target_id,
1013            },
1014            SIROffset::Static(0),
1015            target.width(),
1016            Vec::new(),
1017        ));
1018        insert_or_merge(eval_only, trigger.clone(), build_eval(false)?);
1019        insert_or_merge(apply, trigger.clone(), seal_builder(apply_builder));
1020        insert_or_merge(eval_apply, trigger, build_eval(true)?);
1021    }
1022    Ok(())
1023}
1024
1025fn set_role(
1026    roles: &mut HashMap<SignalId, (DomainKind, PortTypeKind)>,
1027    artifact: &FrontendArtifact,
1028    signal: SignalId,
1029    role: (DomainKind, PortTypeKind),
1030) -> Result<(), FrontendArtifactError> {
1031    if let Some(existing) = roles.get(&signal) {
1032        if *existing != role {
1033            let signal = artifact
1034                .signal(signal)
1035                .ok_or(FrontendArtifactError::UnknownSignal(signal.index()))?;
1036            return Err(FrontendArtifactError::ConflictingSignalRole {
1037                signal: signal.name().to_string(),
1038            });
1039        }
1040    } else {
1041        roles.insert(signal, role);
1042    }
1043    Ok(())
1044}
1045
1046/// Validate and project a stable SDK artifact into Celox symbolic structures.
1047pub fn lower_frontend_artifact(
1048    artifact: &FrontendArtifact,
1049) -> Result<LoweredFrontendArtifact, FrontendArtifactError> {
1050    artifact.validate()?;
1051    let mut roles = HashMap::default();
1052    for register in artifact.registers() {
1053        set_role(
1054            &mut roles,
1055            artifact,
1056            register.clock(),
1057            match register.edge() {
1058                Edge::Posedge => (DomainKind::ClockPosedge, PortTypeKind::Clock),
1059                Edge::Negedge => (DomainKind::ClockNegedge, PortTypeKind::Clock),
1060            },
1061        )?;
1062        if let Some(reset) = register.async_reset() {
1063            set_role(
1064                &mut roles,
1065                artifact,
1066                reset.signal(),
1067                match reset.active() {
1068                    ActiveLevel::High => (DomainKind::ResetAsyncHigh, PortTypeKind::ResetAsyncHigh),
1069                    ActiveLevel::Low => (DomainKind::ResetAsyncLow, PortTypeKind::ResetAsyncLow),
1070                },
1071            )?;
1072        }
1073    }
1074
1075    let variables = artifact
1076        .signals()
1077        .iter()
1078        .map(|signal| {
1079            let (kind, type_kind) = roles.get(&signal.id()).copied().unwrap_or((
1080                DomainKind::Other,
1081                if signal.value_type().is_four_state() {
1082                    PortTypeKind::Logic
1083                } else {
1084                    PortTypeKind::Bit
1085                },
1086            ));
1087            let variable_kind = match signal.direction() {
1088                Direction::Input => VariableKind::Input,
1089                Direction::Output => VariableKind::Output,
1090                Direction::Inout => VariableKind::Inout,
1091                Direction::Internal => VariableKind::Variable,
1092                _ => VariableKind::Variable,
1093            };
1094            (
1095                source_id(signal.id()),
1096                SymbolicVariable {
1097                    path: vec![signal.name().to_string()],
1098                    kind: variable_kind,
1099                    signed: signal.value_type().is_signed(),
1100                    metadata: VariableMetadata {
1101                        width: signal.value_type().width(),
1102                        is_4state: signal.value_type().is_four_state(),
1103                        kind,
1104                        type_kind,
1105                        array_dims: Vec::new(),
1106                    },
1107                    packed_dims: vec![signal.value_type().width()],
1108                    source: None,
1109                    module_affiliated: true,
1110                },
1111            )
1112        })
1113        .collect();
1114
1115    let mut arena = SLTNodeArena::new();
1116    let mut node_cache = HashMap::default();
1117    let mut comb_blocks = Vec::new();
1118    for assignment in artifact.assignments() {
1119        let target_type = signal_slice_type(artifact, assignment.target())?;
1120        let mut sources = HashSet::default();
1121        let mut visited = HashSet::default();
1122        expression_sources(artifact, assignment.value(), &mut sources, &mut visited)?;
1123        comb_blocks.push(LogicPath {
1124            target: LogicPathTarget::Var(signal_atom(assignment.target())),
1125            sources,
1126            previous_sources: HashSet::default(),
1127            address_sources: HashSet::default(),
1128            local_inputs: Vec::new(),
1129            order_before: HashSet::default(),
1130            comb_capture_enable_sites: Vec::new(),
1131            comb_capture_enable_always: false,
1132            pre_lower_nodes: Vec::new(),
1133            expr: coerce_slt_expression_to_type(
1134                artifact,
1135                assignment.value(),
1136                target_type,
1137                &mut arena,
1138                &mut node_cache,
1139            )?,
1140        });
1141    }
1142
1143    let mut eval_only_ff_blocks = HashMap::default();
1144    let mut apply_ff_blocks = HashMap::default();
1145    let mut eval_apply_ff_blocks = HashMap::default();
1146    let mut reset_clock_map = HashMap::default();
1147    lower_registers(
1148        artifact,
1149        &mut eval_only_ff_blocks,
1150        &mut apply_ff_blocks,
1151        &mut eval_apply_ff_blocks,
1152        &mut reset_clock_map,
1153    )?;
1154
1155    let initial_memory_values = artifact
1156        .signals()
1157        .iter()
1158        .filter_map(|signal| {
1159            signal.initial().map(|initial| InitialStateValue {
1160                address: source_id(signal.id()),
1161                data: InitialStateData::Packed {
1162                    value: initial.payload().clone(),
1163                    mask: initial.mask().clone(),
1164                    written_mask: (num_bigint::BigUint::from(1u8) << signal.value_type().width())
1165                        - num_bigint::BigUint::from(1u8),
1166                },
1167            })
1168        })
1169        .collect();
1170
1171    let module_id = ModuleId(0);
1172    let sim_module = SimModule {
1173        name: artifact.module_name().to_string(),
1174        variables,
1175        ff_access_summaries: HashMap::default(),
1176        eval_only_ff_blocks,
1177        apply_ff_blocks,
1178        eval_apply_ff_blocks,
1179        glue_blocks: HashMap::default(),
1180        indexed_instance_names: HashSet::default(),
1181        comb_blocks,
1182        comb_observers: Vec::new(),
1183        runtime_errors: HashMap::default(),
1184        runtime_event_sites: Vec::new(),
1185        initial_memory_values,
1186        comb_boundaries: HashMap::default(),
1187        arena,
1188        reset_clock_map,
1189    };
1190    let symbolic = SymbolicRtl {
1191        modules: [(module_id, sim_module.clone())].into_iter().collect(),
1192        module_names: [(module_id, artifact.module_name().to_string())]
1193            .into_iter()
1194            .collect(),
1195        root_id: module_id,
1196    };
1197    let external = ExternalHierarchy {
1198        modules: [(
1199            module_id,
1200            ExternalModule {
1201                sim_module,
1202                port_order: artifact
1203                    .port_order()
1204                    .iter()
1205                    .map(|signal| source_id(*signal))
1206                    .collect(),
1207                unresolved_instances: Vec::new(),
1208            },
1209        )]
1210        .into_iter()
1211        .collect(),
1212        roots: [(artifact.module_name().to_string(), module_id)]
1213            .into_iter()
1214            .collect(),
1215    };
1216    Ok(LoweredFrontendArtifact { symbolic, external })
1217}