Skip to main content

bitloom_builder/
lib.rs

1//! Builder session: the only public mutation path into HIR (AD-13).
2
3use std::collections::{HashMap, HashSet};
4
5use bitloom_hir::{
6    Assign, AssignExpr, AssignTarget, BuilderOwnedHir, Module, Port, PortDirection, Process,
7    ProcessKind, Stmt,
8};
9
10pub use bitloom_hir::{
11    Diagnostic, Diagnostics, Diagnostics as HirDiagnostics, FrozenHir, FrozenHir as Frozen,
12    GroundType, SignalKind, Span,
13};
14
15#[derive(Debug)]
16enum ProcessState {
17    Combinational {
18        assigns: Vec<Assign>,
19        /// Assignment sets along the current path stack for latch analysis.
20        path_assigned: Vec<HashSet<String>>,
21        /// Open branches: then-set collected so far, optional else placeholder.
22        pending_branches: Vec<(HashSet<String>, bool)>,
23        span: Span,
24    },
25    Sequential {
26        assigns: Vec<Assign>,
27        span: Span,
28    },
29}
30
31/// Session token holding the unfrozen circuit (AD-13).
32pub struct ElaborateSession {
33    hir: BuilderOwnedHir,
34    current: Option<Module>,
35    /// name -> kind for the current module
36    signals: HashMap<String, SignalKind>,
37    /// name -> bit width for UInt/SInt (Clock/Reset/Bool use 1)
38    widths: HashMap<String, u32>,
39    /// Phantom clock-domain id per signal (AD-22); default 0.
40    domains: HashMap<String, u32>,
41    /// Signals that may legally cross domains (DoubleFlop/SyncFIFO bridges).
42    cdc_bridges: HashSet<String>,
43    clock_port: Option<String>,
44    reset_port: Option<String>,
45    process: Option<ProcessState>,
46    errors: Diagnostics,
47}
48
49impl ElaborateSession {
50    pub fn new(circuit_name: impl Into<String>) -> Self {
51        Self {
52            hir: BuilderOwnedHir::new(circuit_name),
53            current: None,
54            signals: HashMap::new(),
55            widths: HashMap::new(),
56            domains: HashMap::new(),
57            cdc_bridges: HashSet::new(),
58            clock_port: None,
59            reset_port: None,
60            process: None,
61            errors: Diagnostics::default(),
62        }
63    }
64
65    fn push_err(&mut self, d: Diagnostic) {
66        self.errors.push(d);
67    }
68
69    pub fn begin_module(&mut self, name: impl Into<String>, span: Span) {
70        self.signals.clear();
71        self.widths.clear();
72        self.domains.clear();
73        self.cdc_bridges.clear();
74        self.clock_port = None;
75        self.reset_port = None;
76        self.process = None;
77        self.current = Some(Module {
78            name: name.into(),
79            ports: Vec::new(),
80            body: Vec::new(),
81            span,
82        });
83    }
84
85    /// Bind a phantom clock-domain id to a signal (AD-22).
86    pub fn bind_domain(&mut self, name: impl Into<String>, domain: u32) {
87        self.domains.insert(name.into(), domain);
88    }
89
90    /// Mark a CDC bridge signal that may legally cross domains.
91    pub fn mark_cdc_bridge(&mut self, name: impl Into<String>) {
92        self.cdc_bridges.insert(name.into());
93    }
94
95    fn record_width(&mut self, name: &str, ty: &GroundType) {
96        let w = match ty {
97            GroundType::UInt { width } | GroundType::SInt { width } => *width,
98            GroundType::Clock | GroundType::Reset | GroundType::Bool | GroundType::Analog => 1,
99        };
100        self.widths.insert(name.to_string(), w);
101    }
102
103    pub fn add_input(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
104        let name = name.into();
105        if matches!(ty, GroundType::Clock) {
106            self.clock_port = Some(name.clone());
107        }
108        if matches!(ty, GroundType::Reset) {
109            self.reset_port = Some(name.clone());
110        }
111        self.signals.insert(name.clone(), SignalKind::Input);
112        self.record_width(&name, &ty);
113        if let Some(m) = self.current.as_mut() {
114            m.ports.push(Port {
115                name,
116                direction: PortDirection::Input,
117                ty,
118                span,
119            });
120        }
121    }
122
123    pub fn add_output(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
124        let name = name.into();
125        self.signals.insert(name.clone(), SignalKind::Output);
126        self.record_width(&name, &ty);
127        if let Some(m) = self.current.as_mut() {
128            m.ports.push(Port {
129                name,
130                direction: PortDirection::Output,
131                ty,
132                span,
133            });
134        }
135    }
136
137    /// Top-level InOut / Analog IO (FR27). Non-top uses are rejected at freeze.
138    pub fn add_inout(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
139        let name = name.into();
140        self.signals.insert(name.clone(), SignalKind::Wire);
141        self.record_width(&name, &ty);
142        if let Some(m) = self.current.as_mut() {
143            m.ports.push(Port {
144                name,
145                direction: PortDirection::InOut,
146                ty,
147                span,
148            });
149        }
150    }
151
152    pub fn declare_wire(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
153        let name = name.into();
154        self.signals.insert(name.clone(), SignalKind::Wire);
155        self.record_width(&name, &ty);
156        if let Some(m) = self.current.as_mut() {
157            m.body.push(Stmt::WireDecl { name, ty, span });
158        }
159    }
160
161    pub fn declare_reg(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
162        let name = name.into();
163        let (Some(clock), Some(reset)) = (self.clock_port.clone(), self.reset_port.clone()) else {
164            self.push_err(Diagnostic {
165                span,
166                code: "rhdl::E0124".into(),
167                en: "cannot declare Reg before Clock and Reset ports are declared".into(),
168                zh: "声明寄存器前必须先有 Clock 与 Reset 端口".into(),
169            });
170            return;
171        };
172        self.signals.insert(name.clone(), SignalKind::Reg);
173        self.record_width(&name, &ty);
174        if let Some(m) = self.current.as_mut() {
175            m.body.push(Stmt::RegDecl {
176                name,
177                ty,
178                clock,
179                reset,
180                async_reset: false,
181                has_enable: false,
182                span,
183            });
184        }
185    }
186
187    /// Declare a register with optional async reset / clock enable (AD-23).
188    pub fn declare_reg_ex(
189        &mut self,
190        name: impl Into<String>,
191        ty: GroundType,
192        async_reset: bool,
193        has_enable: bool,
194        span: Span,
195    ) {
196        let name = name.into();
197        let (Some(clock), Some(reset)) = (self.clock_port.clone(), self.reset_port.clone()) else {
198            self.push_err(Diagnostic {
199                span,
200                code: "rhdl::E0124".into(),
201                en: "cannot declare Reg before Clock and Reset ports are declared".into(),
202                zh: "声明寄存器前必须先有 Clock 与 Reset 端口".into(),
203            });
204            return;
205        };
206        self.signals.insert(name.clone(), SignalKind::Reg);
207        self.record_width(&name, &ty);
208        if let Some(m) = self.current.as_mut() {
209            m.body.push(Stmt::RegDecl {
210                name,
211                ty,
212                clock,
213                reset,
214                async_reset,
215                has_enable,
216                span,
217            });
218        }
219    }
220
221    /// Declare SyncReadMem (CHIRRTL-friendly; sync_read=true).
222    pub fn declare_sync_read_mem(
223        &mut self,
224        name: impl Into<String>,
225        depth: u32,
226        width: u32,
227        span: Span,
228    ) {
229        self.declare_mem_inner(name, depth, width, true, span);
230    }
231
232    /// Declare Mem (async-read / reg-file style; sync_read=false).
233    pub fn declare_mem(&mut self, name: impl Into<String>, depth: u32, width: u32, span: Span) {
234        self.declare_mem_inner(name, depth, width, false, span);
235    }
236
237    fn declare_mem_inner(
238        &mut self,
239        name: impl Into<String>,
240        depth: u32,
241        width: u32,
242        sync_read: bool,
243        span: Span,
244    ) {
245        let name = name.into();
246        if depth == 0 || width == 0 {
247            self.push_err(Diagnostic {
248                span,
249                code: "rhdl::E0210".into(),
250                en: "Mem depth and width must be non-zero".into(),
251                zh: "Mem 的 depth 与 width 必须非零".into(),
252            });
253            return;
254        }
255        self.signals.insert(name.clone(), SignalKind::Wire);
256        self.widths.insert(name.clone(), width);
257        if let Some(m) = self.current.as_mut() {
258            m.body.push(Stmt::MemDecl {
259                name,
260                depth,
261                width,
262                sync_read,
263                span,
264            });
265        }
266    }
267
268    /// Same-width binary add. Returns Err diagnostic via session if widths differ.
269    pub fn check_add(&mut self, lhs: &str, rhs: &str, span: Span) -> Option<u32> {
270        let lw = self.widths.get(lhs).copied();
271        let rw = self.widths.get(rhs).copied();
272        match (lw, rw) {
273            (Some(a), Some(b)) if a == b => Some(a),
274            (Some(a), Some(b)) => {
275                self.push_err(Diagnostic {
276                    span,
277                    code: "rhdl::E0130".into(),
278                    en: format!(
279                        "add requires same width; '{lhs}' is {a}, '{rhs}' is {b} (use pad/trunc)"
280                    ),
281                    zh: format!("加法要求同位宽;'{lhs}' 为 {a},'{rhs}' 为 {b}(请用 pad/trunc)"),
282                });
283                None
284            }
285            _ => {
286                self.push_err(Diagnostic {
287                    span,
288                    code: "rhdl::E0113".into(),
289                    en: format!("unknown signal in add ('{lhs}', '{rhs}')"),
290                    zh: format!("加法中有未知信号('{lhs}', '{rhs}')"),
291                });
292                None
293            }
294        }
295    }
296
297    pub fn check_connect(&mut self, lhs: &str, rhs: &str, span: Span) -> Option<u32> {
298        let lw = self.widths.get(lhs).copied();
299        let rw = self.widths.get(rhs).copied();
300        match (lw, rw) {
301            (Some(a), Some(b)) if a == b => Some(a),
302            (Some(a), Some(b)) => {
303                self.push_err(Diagnostic {
304                    span,
305                    code: "rhdl::E0131".into(),
306                    en: format!(
307                        "connect requires same width; '{lhs}' is {a}, '{rhs}' is {b} (use pad/trunc)"
308                    ),
309                    zh: format!(
310                        "连接要求同位宽;'{lhs}' 为 {a},'{rhs}' 为 {b}(请用 pad/trunc)"
311                    ),
312                });
313                None
314            }
315            _ => {
316                self.push_err(Diagnostic {
317                    span,
318                    code: "rhdl::E0113".into(),
319                    en: format!("unknown signal in connect ('{lhs}', '{rhs}')"),
320                    zh: format!("连接中有未知信号('{lhs}', '{rhs}')"),
321                });
322                None
323            }
324        }
325    }
326
327    /// Explicit zero-extend / sign-pad to a wider width; records result as a temp wire name.
328    pub fn pad_to(
329        &mut self,
330        src: &str,
331        to_width: u32,
332        dest: impl Into<String>,
333        span: Span,
334    ) -> bool {
335        let Some(from) = self.widths.get(src).copied() else {
336            self.push_err(Diagnostic {
337                span,
338                code: "rhdl::E0113".into(),
339                en: format!("unknown signal '{src}' in pad"),
340                zh: format!("pad 中未知信号 '{src}'"),
341            });
342            return false;
343        };
344        if to_width <= from {
345            self.push_err(Diagnostic {
346                span,
347                code: "rhdl::E0132".into(),
348                en: format!("pad requires to_width > from_width ({to_width} <= {from})"),
349                zh: format!("pad 要求目标位宽大于源位宽({to_width} <= {from})"),
350            });
351            return false;
352        }
353        let dest = dest.into();
354        self.declare_wire(dest, GroundType::UInt { width: to_width }, span);
355        let _ = bitloom_hir::Expr::Pad {
356            from_width: from,
357            to_width,
358            span,
359        };
360        true
361    }
362
363    pub fn trunc_to(
364        &mut self,
365        src: &str,
366        to_width: u32,
367        dest: impl Into<String>,
368        span: Span,
369    ) -> bool {
370        let Some(from) = self.widths.get(src).copied() else {
371            self.push_err(Diagnostic {
372                span,
373                code: "rhdl::E0113".into(),
374                en: format!("unknown signal '{src}' in trunc"),
375                zh: format!("trunc 中未知信号 '{src}'"),
376            });
377            return false;
378        };
379        if to_width >= from {
380            self.push_err(Diagnostic {
381                span,
382                code: "rhdl::E0133".into(),
383                en: format!("trunc requires to_width < from_width ({to_width} >= {from})"),
384                zh: format!("trunc 要求目标位宽小于源位宽({to_width} >= {from})"),
385            });
386            return false;
387        }
388        let dest = dest.into();
389        self.declare_wire(dest, GroundType::UInt { width: to_width }, span);
390        let _ = bitloom_hir::Expr::Trunc {
391            from_width: from,
392            to_width,
393            span,
394        };
395        true
396    }
397
398    pub fn begin_combinational(&mut self, span: Span) {
399        if self.process.is_some() {
400            self.push_err(Diagnostic {
401                span,
402                code: "rhdl::E0101".into(),
403                en: "nested processes are not allowed".into(),
404                zh: "不允许嵌套硬件过程".into(),
405            });
406            return;
407        }
408        self.process = Some(ProcessState::Combinational {
409            assigns: Vec::new(),
410            path_assigned: vec![HashSet::new()],
411            pending_branches: Vec::new(),
412            span,
413        });
414    }
415
416    pub fn begin_sequential(&mut self, span: Span) {
417        if self.process.is_some() {
418            self.push_err(Diagnostic {
419                span,
420                code: "rhdl::E0101".into(),
421                en: "nested processes are not allowed".into(),
422                zh: "不允许嵌套硬件过程".into(),
423            });
424            return;
425        }
426        self.process = Some(ProcessState::Sequential {
427            assigns: Vec::new(),
428            span,
429        });
430    }
431
432    /// Start an if-branch inside a combinational process (latch analysis).
433    pub fn begin_then(&mut self, span: Span) {
434        let err = match self.process.as_mut() {
435            Some(ProcessState::Combinational {
436                path_assigned,
437                pending_branches,
438                ..
439            }) => {
440                pending_branches.push((HashSet::new(), false));
441                path_assigned.push(HashSet::new());
442                None
443            }
444            Some(ProcessState::Sequential { .. }) => Some(Diagnostic {
445                span,
446                code: "rhdl::E0102".into(),
447                en: "branch tracking for latch checks is only valid in combinational processes"
448                    .into(),
449                zh: "仅组合过程支持 if/else 赋值完整性检查".into(),
450            }),
451            None => Some(Diagnostic {
452                span,
453                code: "rhdl::E0103".into(),
454                en: "assignment control outside a marked combinational/sequential process".into(),
455                zh: "在未标注的 comb/seq 过程外使用分支".into(),
456            }),
457        };
458        if let Some(d) = err {
459            self.push_err(d);
460        }
461    }
462
463    pub fn begin_else(&mut self, span: Span) {
464        let err = match self.process.as_mut() {
465            Some(ProcessState::Combinational {
466                path_assigned,
467                pending_branches,
468                ..
469            }) => {
470                let then_set = path_assigned.pop().unwrap_or_default();
471                if let Some(last) = pending_branches.last_mut() {
472                    last.0 = then_set;
473                    last.1 = true;
474                    path_assigned.push(HashSet::new());
475                    None
476                } else {
477                    Some(Diagnostic {
478                        span,
479                        code: "rhdl::E0102".into(),
480                        en: "else without an open combinational then-branch".into(),
481                        zh: "else 没有对应的组合 then 分支".into(),
482                    })
483                }
484            }
485            _ => Some(Diagnostic {
486                span,
487                code: "rhdl::E0102".into(),
488                en: "else without an open combinational then-branch".into(),
489                zh: "else 没有对应的组合 then 分支".into(),
490            }),
491        };
492        if let Some(d) = err {
493            self.push_err(d);
494        }
495    }
496
497    pub fn end_if(&mut self, span: Span) {
498        let mut latch_errs = Vec::new();
499        let err = match self.process.as_mut() {
500            Some(ProcessState::Combinational {
501                path_assigned,
502                pending_branches,
503                ..
504            }) => {
505                let current = path_assigned.pop().unwrap_or_default();
506                let Some((stored_then, had_else)) = pending_branches.pop() else {
507                    latch_errs.push(Diagnostic {
508                        span,
509                        code: "rhdl::E0102".into(),
510                        en: "end_if without begin_then".into(),
511                        zh: "end_if 缺少 begin_then".into(),
512                    });
513                    for d in latch_errs {
514                        self.push_err(d);
515                    }
516                    return;
517                };
518
519                let (then_set, else_set) = if had_else {
520                    (stored_then, current)
521                } else {
522                    (current, HashSet::new())
523                };
524
525                let union: HashSet<_> = then_set.union(&else_set).cloned().collect();
526                let inter: HashSet<_> = then_set.intersection(&else_set).cloned().collect();
527                for name in union.difference(&inter) {
528                    latch_errs.push(Diagnostic {
529                        span,
530                        code: "rhdl::E0110".into(),
531                        en: format!(
532                            "incomplete combinational assignment to '{name}' (would infer a latch)"
533                        ),
534                        zh: format!("组合赋值不完整:'{name}'(会推断成 latch)"),
535                    });
536                }
537
538                if let Some(parent) = path_assigned.last_mut() {
539                    for n in inter {
540                        parent.insert(n);
541                    }
542                }
543                None
544            }
545            _ => Some(Diagnostic {
546                span,
547                code: "rhdl::E0102".into(),
548                en: "end_if outside combinational process".into(),
549                zh: "end_if 不在组合过程中".into(),
550            }),
551        };
552        for d in latch_errs {
553            self.push_err(d);
554        }
555        if let Some(d) = err {
556            self.push_err(d);
557        }
558    }
559
560    /// Combinational assign `dst = lhs + rhs` (same-width required).
561    pub fn assign_add(
562        &mut self,
563        dst: impl Into<String>,
564        lhs: impl Into<String>,
565        rhs: impl Into<String>,
566        span: Span,
567    ) {
568        let dst = dst.into();
569        let lhs = lhs.into();
570        let rhs = rhs.into();
571        if self.check_add(&lhs, &rhs, span).is_none() {
572            return;
573        }
574        // Reuse assign_net permission checks by temporarily pushing Add.
575        let kind = self.signals.get(&dst).copied();
576        let process_kind = match &self.process {
577            Some(ProcessState::Combinational { .. }) => Some(ProcessKind::Combinational),
578            Some(ProcessState::Sequential { .. }) => Some(ProcessKind::Sequential),
579            None => None,
580        };
581        match process_kind {
582            Some(ProcessKind::Combinational) => {
583                match kind {
584                    Some(SignalKind::Wire | SignalKind::Output) => {}
585                    Some(SignalKind::Reg) => {
586                        self.push_err(Diagnostic {
587                            span,
588                            code: "rhdl::E0111".into(),
589                            en: format!("combinational process must not drive Reg '{dst}'"),
590                            zh: format!("组合过程不能驱动寄存器 '{dst}'"),
591                        });
592                        return;
593                    }
594                    Some(SignalKind::Input) => {
595                        self.push_err(Diagnostic {
596                            span,
597                            code: "rhdl::E0112".into(),
598                            en: format!("cannot assign to input port '{dst}'"),
599                            zh: format!("不能给输入端口 '{dst}' 赋值"),
600                        });
601                        return;
602                    }
603                    None => {
604                        self.push_err(Diagnostic {
605                            span,
606                            code: "rhdl::E0113".into(),
607                            en: format!("unknown signal '{dst}'"),
608                            zh: format!("未知信号 '{dst}'"),
609                        });
610                        return;
611                    }
612                }
613                if let Some(ProcessState::Combinational {
614                    assigns,
615                    path_assigned,
616                    ..
617                }) = self.process.as_mut()
618                {
619                    assigns.push(Assign {
620                        target: AssignTarget::Net(dst.clone()),
621                        expr: AssignExpr::Add(lhs, rhs),
622                        span,
623                    });
624                    if let Some(path) = path_assigned.last_mut() {
625                        path.insert(dst);
626                    }
627                }
628            }
629            Some(ProcessKind::Sequential) => {
630                self.push_err(Diagnostic {
631                    span,
632                    code: "rhdl::E0114".into(),
633                    en: format!("sequential process must not drive combinational net '{dst}'"),
634                    zh: format!("时序过程不能驱动组合网 '{dst}'"),
635                });
636            }
637            None => {
638                self.push_err(Diagnostic {
639                    span,
640                    code: "rhdl::E0103".into(),
641                    en: "assignment outside a marked combinational/sequential process".into(),
642                    zh: "在未标注的 comb/seq 过程外赋值".into(),
643                });
644            }
645        }
646    }
647
648    /// Assign a combinational net / output / wire from `from`.
649    pub fn assign_net(&mut self, name: impl Into<String>, from: impl Into<String>, span: Span) {
650        let name = name.into();
651        let from = from.into();
652        let src_dom = self.domains.get(&from).copied().unwrap_or(0);
653        let dst_dom = self.domains.get(&name).copied().unwrap_or(0);
654        if src_dom != dst_dom
655            && !self.cdc_bridges.contains(&name)
656            && !self.cdc_bridges.contains(&from)
657        {
658            self.push_err(Diagnostic {
659                span,
660                code: "rhdl::E0220".into(),
661                en: format!(
662                    "illegal clock-domain crossing '{from}'(D{src_dom}) → '{name}'(D{dst_dom}); use DoubleFlop/SyncFIFO"
663                ),
664                zh: format!(
665                    "非法跨时钟域:'{from}'(D{src_dom}) → '{name}'(D{dst_dom});请用 DoubleFlop/SyncFIFO"
666                ),
667            });
668            return;
669        }
670        let kind = self.signals.get(&name).copied();
671        let process_kind = match &self.process {
672            Some(ProcessState::Combinational { .. }) => Some(ProcessKind::Combinational),
673            Some(ProcessState::Sequential { .. }) => Some(ProcessKind::Sequential),
674            None => None,
675        };
676
677        match process_kind {
678            Some(ProcessKind::Combinational) => {
679                match kind {
680                    Some(SignalKind::Reg) => {
681                        self.push_err(Diagnostic {
682                            span,
683                            code: "rhdl::E0111".into(),
684                            en: format!(
685                                "combinational process must not drive Reg '{name}' (use Reg.d in sequential)"
686                            ),
687                            zh: format!(
688                                "组合过程不能驱动寄存器 '{name}'(请在时序过程写 Reg.d)"
689                            ),
690                        });
691                        return;
692                    }
693                    Some(SignalKind::Input) => {
694                        self.push_err(Diagnostic {
695                            span,
696                            code: "rhdl::E0112".into(),
697                            en: format!("cannot assign to input port '{name}'"),
698                            zh: format!("不能给输入端口 '{name}' 赋值"),
699                        });
700                        return;
701                    }
702                    Some(SignalKind::Wire | SignalKind::Output) => {}
703                    None => {
704                        self.push_err(Diagnostic {
705                            span,
706                            code: "rhdl::E0113".into(),
707                            en: format!("unknown signal '{name}'"),
708                            zh: format!("未知信号 '{name}'"),
709                        });
710                        return;
711                    }
712                }
713                if let Some(ProcessState::Combinational {
714                    assigns,
715                    path_assigned,
716                    ..
717                }) = self.process.as_mut()
718                {
719                    assigns.push(Assign {
720                        target: AssignTarget::Net(name.clone()),
721                        expr: AssignExpr::Ref(from.clone()),
722                        span,
723                    });
724                    if let Some(path) = path_assigned.last_mut() {
725                        path.insert(name);
726                    }
727                }
728            }
729            Some(ProcessKind::Sequential) => {
730                self.push_err(Diagnostic {
731                    span,
732                    code: "rhdl::E0114".into(),
733                    en: format!("sequential process must not drive combinational net '{name}'"),
734                    zh: format!("时序过程不能驱动组合网 '{name}'"),
735                });
736            }
737            None => {
738                self.push_err(Diagnostic {
739                    span,
740                    code: "rhdl::E0103".into(),
741                    en: "assignment outside a marked combinational/sequential process".into(),
742                    zh: "在未标注的 comb/seq 过程外赋值".into(),
743                });
744            }
745        }
746    }
747
748    /// Assign `Reg.d` next-state as `name + 1` (wrapping).
749    pub fn assign_reg_d_inc(&mut self, name: impl Into<String>, span: Span) {
750        self.assign_reg_d_expr(name, None, span);
751    }
752
753    /// Assign `Reg.d` from another signal.
754    pub fn assign_reg_d_from(
755        &mut self,
756        name: impl Into<String>,
757        from: impl Into<String>,
758        span: Span,
759    ) {
760        self.assign_reg_d_expr(name, Some(from.into()), span);
761    }
762
763    /// Sequential SyncReadMem / Mem write: `mem[addr] <= data`.
764    pub fn assign_mem_write(
765        &mut self,
766        mem: impl Into<String>,
767        addr: impl Into<String>,
768        data: impl Into<String>,
769        span: Span,
770    ) {
771        let mem = mem.into();
772        let addr = addr.into();
773        let data = data.into();
774        match &self.process {
775            Some(ProcessState::Sequential { .. }) => {
776                if let Some(ProcessState::Sequential { assigns, .. }) = self.process.as_mut() {
777                    assigns.push(Assign {
778                        target: AssignTarget::MemWrite { mem, addr },
779                        expr: AssignExpr::Ref(data),
780                        span,
781                    });
782                }
783            }
784            _ => {
785                self.push_err(Diagnostic {
786                    span,
787                    code: "rhdl::E0211".into(),
788                    en: "mem write must be inside a sequential process".into(),
789                    zh: "mem 写必须在 sequential 过程内".into(),
790                });
791            }
792        }
793    }
794
795    /// Sequential SyncReadMem read into a register (latency 1 on tick).
796    pub fn assign_reg_d_mem_read(
797        &mut self,
798        reg: impl Into<String>,
799        mem: impl Into<String>,
800        addr: impl Into<String>,
801        span: Span,
802    ) {
803        let reg = reg.into();
804        let mem = mem.into();
805        let addr = addr.into();
806        match &self.process {
807            Some(ProcessState::Sequential { .. }) => {
808                if let Some(ProcessState::Sequential { assigns, .. }) = self.process.as_mut() {
809                    assigns.push(Assign {
810                        target: AssignTarget::RegD(reg),
811                        expr: AssignExpr::MemRead { mem, addr },
812                        span,
813                    });
814                }
815            }
816            _ => {
817                self.push_err(Diagnostic {
818                    span,
819                    code: "rhdl::E0212".into(),
820                    en: "sync mem read into Reg must be inside a sequential process".into(),
821                    zh: "SyncReadMem 读入寄存器必须在 sequential 过程内".into(),
822                });
823            }
824        }
825    }
826
827    fn assign_reg_d_expr(&mut self, name: impl Into<String>, from: Option<String>, span: Span) {
828        let name = name.into();
829        let kind = self.signals.get(&name).copied();
830        let process_kind = match &self.process {
831            Some(ProcessState::Combinational { .. }) => Some(ProcessKind::Combinational),
832            Some(ProcessState::Sequential { .. }) => Some(ProcessKind::Sequential),
833            None => None,
834        };
835
836        match process_kind {
837            Some(ProcessKind::Sequential) => match kind {
838                Some(SignalKind::Reg) => {
839                    if let Some(ProcessState::Sequential { assigns, .. }) = self.process.as_mut() {
840                        let expr = match from {
841                            Some(src) => AssignExpr::Ref(src),
842                            None => AssignExpr::Inc(name.clone()),
843                        };
844                        assigns.push(Assign {
845                            target: AssignTarget::RegD(name),
846                            expr,
847                            span,
848                        });
849                    }
850                }
851                Some(_) => {
852                    self.push_err(Diagnostic {
853                        span,
854                        code: "rhdl::E0115".into(),
855                        en: format!("'{name}' is not a Reg; Reg.d requires a register"),
856                        zh: format!("'{name}' 不是寄存器,不能写 Reg.d"),
857                    });
858                }
859                None => {
860                    self.push_err(Diagnostic {
861                        span,
862                        code: "rhdl::E0113".into(),
863                        en: format!("unknown signal '{name}'"),
864                        zh: format!("未知信号 '{name}'"),
865                    });
866                }
867            },
868            Some(ProcessKind::Combinational) => {
869                self.push_err(Diagnostic {
870                    span,
871                    code: "rhdl::E0116".into(),
872                    en: format!("combinational process must not write Reg.d for '{name}'"),
873                    zh: format!("组合过程不能写 '{name}' 的 Reg.d"),
874                });
875            }
876            None => {
877                self.push_err(Diagnostic {
878                    span,
879                    code: "rhdl::E0103".into(),
880                    en: "assignment outside a marked combinational/sequential process".into(),
881                    zh: "在未标注的 comb/seq 过程外赋值".into(),
882                });
883            }
884        }
885    }
886
887    pub fn end_process(&mut self) {
888        let Some(state) = self.process.take() else {
889            return;
890        };
891        match state {
892            ProcessState::Combinational {
893                assigns,
894                pending_branches,
895                span,
896                ..
897            } => {
898                if !pending_branches.is_empty() {
899                    self.push_err(Diagnostic {
900                        span,
901                        code: "rhdl::E0102".into(),
902                        en: "unclosed if/else in combinational process".into(),
903                        zh: "组合过程中有未关闭的 if/else".into(),
904                    });
905                }
906                if let Some(m) = self.current.as_mut() {
907                    m.body.push(Stmt::Process(Process {
908                        kind: ProcessKind::Combinational,
909                        assigns,
910                        span,
911                    }));
912                }
913            }
914            ProcessState::Sequential { assigns, span } => {
915                if let Some(m) = self.current.as_mut() {
916                    m.body.push(Stmt::Process(Process {
917                        kind: ProcessKind::Sequential,
918                        assigns,
919                        span,
920                    }));
921                }
922            }
923        }
924    }
925
926    pub fn end_module(&mut self) {
927        if self.process.is_some() {
928            self.end_process();
929        }
930        if let Some(m) = self.current.take() {
931            self.hir.add_module(m);
932        }
933        self.signals.clear();
934        self.widths.clear();
935        self.clock_port = None;
936        self.reset_port = None;
937    }
938
939    pub fn finish(self) -> Result<FrozenHir, Diagnostics> {
940        if !self.errors.is_empty() {
941            return Err(self.errors);
942        }
943        bitloom_hir::seal_from_builder(self.hir)
944    }
945
946    /// Record a synthesizable-path violation (heap, threads, f64, …) as a structured diagnostic.
947    pub fn reject_unsynthesizable(&mut self, construct: &str, span: Span) {
948        self.push_err(Diagnostic {
949            span,
950            code: "rhdl::E0141".into(),
951            en: format!(
952                "unsynthesizable construct '{construct}' is not allowed on the cycle-accurate path"
953            ),
954            zh: format!("周期精确路径不允许不可综合构造 '{construct}'"),
955        });
956    }
957
958    /// Hierarchical instance (Story 2.2); not flattened at elaborate.
959    pub fn add_instance(
960        &mut self,
961        name: impl Into<String>,
962        module: impl Into<String>,
963        connects: Vec<(String, String)>,
964        params: Vec<(String, u32)>,
965        span: Span,
966    ) {
967        use bitloom_hir::{Instance, PortConnect};
968        let connects = connects
969            .into_iter()
970            .map(|(child_port, parent_net)| PortConnect {
971                child_port,
972                parent_net,
973                span,
974                dangling: false,
975            })
976            .collect();
977        if let Some(m) = self.current.as_mut() {
978            m.body.push(Stmt::Instance(Instance {
979                name: name.into(),
980                module: module.into(),
981                connects,
982                params,
983                span,
984            }));
985        }
986    }
987
988    pub fn add_dangling_input(
989        &mut self,
990        instance: &str,
991        child_port: impl Into<String>,
992        span: Span,
993    ) {
994        use bitloom_hir::PortConnect;
995        if let Some(m) = self.current.as_mut() {
996            for stmt in &mut m.body {
997                if let Stmt::Instance(inst) = stmt {
998                    if inst.name == instance {
999                        inst.connects.push(PortConnect {
1000                            child_port: child_port.into(),
1001                            parent_net: String::new(),
1002                            span,
1003                            dangling: true,
1004                        });
1005                        return;
1006                    }
1007                }
1008            }
1009        }
1010        self.push_err(Diagnostic {
1011            span,
1012            code: "rhdl::E0201".into(),
1013            en: format!("unknown instance '{instance}' for dangling mark"),
1014            zh: format!("悬空标记找不到实例 '{instance}'"),
1015        });
1016    }
1017}
1018
1019/// Trait implemented by `#[rhdl::top]` / design modules (AD-19 partial for 1.1).
1020pub trait Elaboratable {
1021    fn elaborate() -> Result<FrozenHir, Diagnostics>;
1022}
1023
1024#[cfg(test)]
1025mod tests {
1026    use super::*;
1027
1028    fn base_ports(s: &mut ElaborateSession) {
1029        s.begin_module("M", Span::default());
1030        s.add_input("clk", GroundType::Clock, Span::default());
1031        s.add_input("rst", GroundType::Reset, Span::default());
1032        s.add_input("data_in", GroundType::UInt { width: 8 }, Span::default());
1033        s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
1034    }
1035
1036    #[test]
1037    fn complete_comb_assign_ok() {
1038        let mut s = ElaborateSession::new("t");
1039        base_ports(&mut s);
1040        s.begin_combinational(Span::default());
1041        s.assign_net("data_out", "data_in", Span::default());
1042        s.end_process();
1043        s.end_module();
1044        assert!(s.finish().is_ok());
1045    }
1046
1047    #[test]
1048    fn incomplete_branch_is_latch_error() {
1049        let mut s = ElaborateSession::new("t");
1050        base_ports(&mut s);
1051        s.begin_combinational(Span::default());
1052        s.begin_then(Span::default());
1053        s.assign_net("data_out", "data_in", Span::default());
1054        s.begin_else(Span::default());
1055        // else does not assign data_out
1056        s.end_if(Span::default());
1057        s.end_process();
1058        s.end_module();
1059        let err = s.finish().unwrap_err();
1060        assert!(
1061            err.0.iter().any(|d| d.code == "rhdl::E0110"),
1062            "expected latch diagnostic, got {err}"
1063        );
1064    }
1065
1066    #[test]
1067    fn both_branches_assign_ok() {
1068        let mut s = ElaborateSession::new("t");
1069        base_ports(&mut s);
1070        s.begin_combinational(Span::default());
1071        s.begin_then(Span::default());
1072        s.assign_net("data_out", "data_in", Span::default());
1073        s.begin_else(Span::default());
1074        s.assign_net("data_out", "data_in", Span::default());
1075        s.end_if(Span::default());
1076        s.end_process();
1077        s.end_module();
1078        let r = s.finish();
1079        assert!(r.is_ok(), "{:?}", r.err());
1080    }
1081
1082    #[test]
1083    fn comb_cannot_write_reg_d() {
1084        let mut s = ElaborateSession::new("t");
1085        base_ports(&mut s);
1086        s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
1087        s.begin_combinational(Span::default());
1088        s.assign_reg_d_inc("count", Span::default());
1089        s.end_process();
1090        s.end_module();
1091        let err = s.finish().unwrap_err();
1092        assert!(err.0.iter().any(|d| d.code == "rhdl::E0116"));
1093    }
1094
1095    #[test]
1096    fn seq_cannot_drive_comb_net() {
1097        let mut s = ElaborateSession::new("t");
1098        base_ports(&mut s);
1099        s.begin_sequential(Span::default());
1100        s.assign_net("data_out", "data_in", Span::default());
1101        s.end_process();
1102        s.end_module();
1103        let err = s.finish().unwrap_err();
1104        assert!(err.0.iter().any(|d| d.code == "rhdl::E0114"));
1105    }
1106
1107    #[test]
1108    fn assign_outside_process_rejected() {
1109        let mut s = ElaborateSession::new("t");
1110        base_ports(&mut s);
1111        s.assign_net("data_out", "data_in", Span::default());
1112        s.end_module();
1113        let err = s.finish().unwrap_err();
1114        assert!(err.0.iter().any(|d| d.code == "rhdl::E0103"));
1115    }
1116
1117    #[test]
1118    fn seq_reg_d_ok() {
1119        let mut s = ElaborateSession::new("t");
1120        base_ports(&mut s);
1121        s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
1122        s.begin_combinational(Span::default());
1123        s.assign_net("data_out", "count", Span::default());
1124        s.end_process();
1125        s.begin_sequential(Span::default());
1126        s.assign_reg_d_inc("count", Span::default());
1127        s.end_process();
1128        s.end_module();
1129        assert!(s.finish().is_ok());
1130    }
1131
1132    #[test]
1133    fn missing_clock_rejected() {
1134        let mut s = ElaborateSession::new("t");
1135        s.begin_module("M", Span::default());
1136        s.add_input("rst", GroundType::Reset, Span::default());
1137        s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
1138        s.end_module();
1139        let err = s.finish().unwrap_err();
1140        assert!(err.0.iter().any(|d| d.code == "rhdl::E0120"));
1141    }
1142
1143    #[test]
1144    fn missing_reset_rejected() {
1145        let mut s = ElaborateSession::new("t");
1146        s.begin_module("M", Span::default());
1147        s.add_input("clk", GroundType::Clock, Span::default());
1148        s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
1149        s.end_module();
1150        let err = s.finish().unwrap_err();
1151        assert!(err.0.iter().any(|d| d.code == "rhdl::E0121"));
1152    }
1153
1154    #[test]
1155    fn mismatched_add_width_rejected() {
1156        let mut s = ElaborateSession::new("t");
1157        base_ports(&mut s);
1158        s.declare_wire("a", GroundType::UInt { width: 8 }, Span::default());
1159        s.declare_wire("b", GroundType::UInt { width: 16 }, Span::default());
1160        assert!(s.check_add("a", "b", Span::default()).is_none());
1161        s.end_module();
1162        let err = s.finish().unwrap_err();
1163        assert!(err.0.iter().any(|d| d.code == "rhdl::E0130"));
1164    }
1165
1166    #[test]
1167    fn pad_then_add_ok() {
1168        let mut s = ElaborateSession::new("t");
1169        base_ports(&mut s);
1170        s.declare_wire("a", GroundType::UInt { width: 8 }, Span::default());
1171        s.declare_wire("b", GroundType::UInt { width: 16 }, Span::default());
1172        assert!(s.pad_to("a", 16, "a_pad", Span::default()));
1173        assert_eq!(s.check_add("a_pad", "b", Span::default()), Some(16));
1174        s.begin_combinational(Span::default());
1175        s.assign_net("data_out", "data_in", Span::default());
1176        s.end_process();
1177        s.end_module();
1178        assert!(s.finish().is_ok());
1179    }
1180
1181    #[test]
1182    fn multi_drive_rejected() {
1183        let mut s = ElaborateSession::new("t");
1184        base_ports(&mut s);
1185        s.begin_combinational(Span::default());
1186        s.assign_net("data_out", "data_in", Span::default());
1187        s.end_process();
1188        s.begin_combinational(Span::default());
1189        s.assign_net("data_out", "data_in", Span::default());
1190        s.end_process();
1191        s.end_module();
1192        let err = s.finish().unwrap_err();
1193        assert!(err.0.iter().any(|d| d.code == "rhdl::E0140"));
1194    }
1195
1196    #[test]
1197    fn parameterized_widths_w8_and_w16() {
1198        fn elaborate_w(w: u32) -> bitloom_hir::FrozenHir {
1199            let mut s = ElaborateSession::new("t");
1200            s.begin_module(format!("Add{w}"), Span::default());
1201            s.add_input("clk", GroundType::Clock, Span::default());
1202            s.add_input("rst", GroundType::Reset, Span::default());
1203            s.add_input("a", GroundType::UInt { width: w }, Span::default());
1204            s.add_input("b", GroundType::UInt { width: w }, Span::default());
1205            s.add_output("y", GroundType::UInt { width: w }, Span::default());
1206            s.begin_combinational(Span::default());
1207            s.assign_net("y", "a", Span::default());
1208            s.end_process();
1209            s.end_module();
1210            s.finish().unwrap()
1211        }
1212        let h8 = elaborate_w(8);
1213        let h16 = elaborate_w(16);
1214        assert!(matches!(
1215            h8.circuit().modules[0].ports[2].ty,
1216            GroundType::UInt { width: 8 }
1217        ));
1218        assert!(matches!(
1219            h16.circuit().modules[0].ports[2].ty,
1220            GroundType::UInt { width: 16 }
1221        ));
1222    }
1223
1224    #[test]
1225    fn hierarchy_instance_preserved() {
1226        let mut s = ElaborateSession::new("t");
1227        s.begin_module("Child", Span::default());
1228        s.add_input("clk", GroundType::Clock, Span::default());
1229        s.add_input("rst", GroundType::Reset, Span::default());
1230        s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
1231        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1232        s.begin_combinational(Span::default());
1233        s.assign_net("y", "x", Span::default());
1234        s.end_process();
1235        s.end_module();
1236
1237        s.begin_module("Parent", Span::default());
1238        s.add_input("clk", GroundType::Clock, Span::default());
1239        s.add_input("rst", GroundType::Reset, Span::default());
1240        s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
1241        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1242        s.add_instance(
1243            "u0",
1244            "Child",
1245            vec![
1246                ("clk".into(), "clk".into()),
1247                ("rst".into(), "rst".into()),
1248                ("x".into(), "x".into()),
1249                ("y".into(), "y".into()),
1250            ],
1251            vec![("W".into(), 8)],
1252            Span::default(),
1253        );
1254        s.end_module();
1255        let frozen = s.finish().unwrap();
1256        assert_eq!(frozen.circuit().modules.len(), 2);
1257        assert!(frozen.circuit().modules[1].body.iter().any(|st| matches!(
1258            st,
1259            bitloom_hir::Stmt::Instance(i) if i.name == "u0" && i.module == "Child"
1260        )));
1261    }
1262
1263    #[test]
1264    fn undriven_child_input_rejected() {
1265        let mut s = ElaborateSession::new("t");
1266        s.begin_module("Child", Span::default());
1267        s.add_input("clk", GroundType::Clock, Span::default());
1268        s.add_input("rst", GroundType::Reset, Span::default());
1269        s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
1270        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1271        s.end_module();
1272        s.begin_module("Parent", Span::default());
1273        s.add_input("clk", GroundType::Clock, Span::default());
1274        s.add_input("rst", GroundType::Reset, Span::default());
1275        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1276        s.add_instance(
1277            "u0",
1278            "Child",
1279            vec![
1280                ("clk".into(), "clk".into()),
1281                ("rst".into(), "rst".into()),
1282                ("y".into(), "y".into()),
1283            ],
1284            vec![],
1285            Span::default(),
1286        );
1287        s.end_module();
1288        let err = s.finish().unwrap_err();
1289        assert!(err.0.iter().any(|d| d.code == "rhdl::E0202"));
1290    }
1291
1292    #[test]
1293    fn sync_read_mem_declares_and_emits() {
1294        let mut s = ElaborateSession::new("t");
1295        s.begin_module("MemTop", Span::default());
1296        s.add_input("clk", GroundType::Clock, Span::default());
1297        s.add_input("rst", GroundType::Reset, Span::default());
1298        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1299        s.declare_sync_read_mem("ram", 16, 8, Span::default());
1300        s.begin_combinational(Span::default());
1301        s.assign_net("y", "ram", Span::default());
1302        s.end_process();
1303        s.end_module();
1304        let frozen = s.finish().unwrap();
1305        assert!(frozen.circuit().modules[0].body.iter().any(|st| matches!(
1306            st,
1307            bitloom_hir::Stmt::MemDecl {
1308                sync_read: true,
1309                ..
1310            }
1311        )));
1312    }
1313
1314    #[test]
1315    fn async_reset_and_enable_flags() {
1316        let mut s = ElaborateSession::new("t");
1317        s.begin_module("M", Span::default());
1318        s.add_input("clk", GroundType::Clock, Span::default());
1319        s.add_input("rst", GroundType::Reset, Span::default());
1320        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1321        s.declare_reg_ex(
1322            "q",
1323            GroundType::UInt { width: 8 },
1324            true,
1325            true,
1326            Span::default(),
1327        );
1328        s.begin_combinational(Span::default());
1329        s.assign_net("y", "q", Span::default());
1330        s.end_process();
1331        s.begin_sequential(Span::default());
1332        s.assign_reg_d_inc("q", Span::default());
1333        s.end_process();
1334        s.end_module();
1335        let frozen = s.finish().unwrap();
1336        assert!(frozen.circuit().modules[0].body.iter().any(|st| matches!(
1337            st,
1338            bitloom_hir::Stmt::RegDecl {
1339                async_reset: true,
1340                has_enable: true,
1341                ..
1342            }
1343        )));
1344    }
1345
1346    #[test]
1347    fn illegal_domain_crossing_rejected() {
1348        let mut s = ElaborateSession::new("t");
1349        s.begin_module("Cdc", Span::default());
1350        s.add_input("clk", GroundType::Clock, Span::default());
1351        s.add_input("rst", GroundType::Reset, Span::default());
1352        s.add_input("a", GroundType::UInt { width: 8 }, Span::default());
1353        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1354        s.bind_domain("a", 0);
1355        s.bind_domain("y", 1);
1356        s.begin_combinational(Span::default());
1357        s.assign_net("y", "a", Span::default());
1358        s.end_process();
1359        s.end_module();
1360        let err = s.finish().unwrap_err();
1361        assert!(err.0.iter().any(|d| d.code == "rhdl::E0220"), "{err}");
1362    }
1363
1364    #[test]
1365    fn cdc_bridge_allows_crossing() {
1366        let mut s = ElaborateSession::new("t");
1367        s.begin_module("CdcOk", Span::default());
1368        s.add_input("clk", GroundType::Clock, Span::default());
1369        s.add_input("rst", GroundType::Reset, Span::default());
1370        s.add_input("a", GroundType::UInt { width: 8 }, Span::default());
1371        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1372        s.bind_domain("a", 0);
1373        s.bind_domain("y", 1);
1374        s.mark_cdc_bridge("y");
1375        s.begin_combinational(Span::default());
1376        s.assign_net("y", "a", Span::default());
1377        s.end_process();
1378        s.end_module();
1379        assert!(s.finish().is_ok());
1380    }
1381
1382    #[test]
1383    fn unknown_parent_net_rejected() {
1384        let mut s = ElaborateSession::new("t");
1385        s.begin_module("Child", Span::default());
1386        s.add_input("clk", GroundType::Clock, Span::default());
1387        s.add_input("rst", GroundType::Reset, Span::default());
1388        s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
1389        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1390        s.end_module();
1391        s.begin_module("Parent", Span::default());
1392        s.add_input("clk", GroundType::Clock, Span::default());
1393        s.add_input("rst", GroundType::Reset, Span::default());
1394        s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
1395        s.add_instance(
1396            "u0",
1397            "Child",
1398            vec![
1399                ("clk".into(), "clk".into()),
1400                ("rst".into(), "rst".into()),
1401                ("x".into(), "no_such_net".into()),
1402                ("y".into(), "y".into()),
1403            ],
1404            vec![],
1405            Span::default(),
1406        );
1407        s.end_module();
1408        let err = s.finish().unwrap_err();
1409        assert!(err.0.iter().any(|d| d.code == "rhdl::E0204"), "{err}");
1410    }
1411}