1use 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 path_assigned: Vec<HashSet<String>>,
21 pending_branches: Vec<(HashSet<String>, bool)>,
23 span: Span,
24 },
25 Sequential {
26 assigns: Vec<Assign>,
27 span: Span,
28 },
29}
30
31pub struct ElaborateSession {
33 hir: BuilderOwnedHir,
34 current: Option<Module>,
35 signals: HashMap<String, SignalKind>,
37 widths: HashMap<String, u32>,
39 domains: HashMap<String, u32>,
41 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 pub fn bind_domain(&mut self, name: impl Into<String>, domain: u32) {
87 self.domains.insert(name.into(), domain);
88 }
89
90 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
1019pub 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 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}