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
15mod closures;
16pub use closures::*;
17
18#[derive(Debug)]
19enum ProcessState {
20 Combinational {
21 assigns: Vec<Assign>,
22 path_assigned: Vec<HashSet<String>>,
24 pending_branches: Vec<(HashSet<String>, bool)>,
26 span: Span,
27 },
28 Sequential {
29 assigns: Vec<Assign>,
30 span: Span,
31 },
32}
33
34pub struct ElaborateSession {
36 hir: BuilderOwnedHir,
37 current: Option<Module>,
38 signals: HashMap<String, SignalKind>,
40 widths: HashMap<String, u32>,
42 domains: HashMap<String, u32>,
44 cdc_bridges: HashSet<String>,
46 clock_port: Option<String>,
47 reset_port: Option<String>,
48 process: Option<ProcessState>,
49 errors: Diagnostics,
50}
51
52impl ElaborateSession {
53 pub fn new(circuit_name: impl Into<String>) -> Self {
54 Self {
55 hir: BuilderOwnedHir::new(circuit_name),
56 current: None,
57 signals: HashMap::new(),
58 widths: HashMap::new(),
59 domains: HashMap::new(),
60 cdc_bridges: HashSet::new(),
61 clock_port: None,
62 reset_port: None,
63 process: None,
64 errors: Diagnostics::default(),
65 }
66 }
67
68 fn push_err(&mut self, d: Diagnostic) {
69 self.errors.push(d);
70 }
71
72 pub fn begin_module(&mut self, name: impl Into<String>, span: Span) {
73 self.signals.clear();
74 self.widths.clear();
75 self.domains.clear();
76 self.cdc_bridges.clear();
77 self.clock_port = None;
78 self.reset_port = None;
79 self.process = None;
80 self.current = Some(Module {
81 name: name.into(),
82 ports: Vec::new(),
83 body: Vec::new(),
84 span,
85 });
86 }
87
88 pub fn bind_domain(&mut self, name: impl Into<String>, domain: u32) {
90 self.domains.insert(name.into(), domain);
91 }
92
93 pub fn mark_cdc_bridge(&mut self, name: impl Into<String>) {
95 self.cdc_bridges.insert(name.into());
96 }
97
98 pub fn declare_double_flop_stages(
108 &mut self,
109 stem: impl Into<String>,
110 ty: GroundType,
111 dst_domain: u32,
112 span: Span,
113 ) -> (String, String) {
114 let stem = stem.into();
115 let ff0 = format!("{stem}_ff0");
116 let ff1 = format!("{stem}_ff1");
117 self.declare_reg(ff0.clone(), ty.clone(), span);
118 self.declare_reg(ff1.clone(), ty, span);
119 self.bind_domain(&ff0, dst_domain);
120 self.bind_domain(&ff1, dst_domain);
121 self.mark_cdc_bridge(&ff0);
122 (ff0, ff1)
123 }
124
125 pub fn connect_double_flop(
130 &mut self,
131 ff0: impl Into<String>,
132 ff1: impl Into<String>,
133 din: impl Into<String>,
134 span: Span,
135 ) {
136 let ff0 = ff0.into();
137 let ff1 = ff1.into();
138 let din = din.into();
139 self.assign_reg_d_from(&ff0, &din, span);
140 self.assign_reg_d_from(&ff1, &ff0, span);
141 }
142
143 fn reject_illegal_cdc(&mut self, from: &str, to: &str, span: Span) -> bool {
144 let src_dom = self.domains.get(from).copied().unwrap_or(0);
145 let dst_dom = self.domains.get(to).copied().unwrap_or(0);
146 if src_dom != dst_dom && !self.cdc_bridges.contains(to) && !self.cdc_bridges.contains(from)
147 {
148 self.push_err(Diagnostic {
149 span,
150 code: "rhdl::E0220".into(),
151 en: format!(
152 "illegal clock-domain crossing '{from}'(D{src_dom}) → '{to}'(D{dst_dom}); use DoubleFlop/SyncFIFO"
153 ),
154 zh: format!(
155 "非法跨时钟域:'{from}'(D{src_dom}) → '{to}'(D{dst_dom});请用 DoubleFlop/SyncFIFO"
156 ),
157 });
158 return true;
159 }
160 false
161 }
162
163 fn record_width(&mut self, name: &str, ty: &GroundType) {
164 let w = match ty {
165 GroundType::UInt { width } | GroundType::SInt { width } => *width,
166 GroundType::Clock | GroundType::Reset | GroundType::Bool | GroundType::Analog => 1,
167 };
168 self.widths.insert(name.to_string(), w);
169 }
170
171 fn ensure_fresh_signal_name(&mut self, name: &str, span: Span) -> bool {
173 if self.signals.contains_key(name) {
174 self.push_err(Diagnostic {
175 span,
176 code: "rhdl::E0152".into(),
177 en: format!(
178 "flattened leaf/port name '{name}' collides with an existing signal (rename Bundle members or fields so `{{field}}_{{member}}` / `{{field}}_{{i}}` stay unique)"
179 ),
180 zh: format!(
181 "展平叶/端口名 '{name}' 与已有信号冲突(请调整 Bundle 成员或字段名,保证 `{{field}}_{{member}}` / `{{field}}_{{i}}` 唯一)"
182 ),
183 });
184 return false;
185 }
186 true
187 }
188
189 pub fn add_input(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
190 let name = name.into();
191 if !self.ensure_fresh_signal_name(&name, span) {
192 return;
193 }
194 if matches!(ty, GroundType::Clock) {
195 self.clock_port = Some(name.clone());
196 }
197 if matches!(ty, GroundType::Reset) {
198 self.reset_port = Some(name.clone());
199 }
200 self.signals.insert(name.clone(), SignalKind::Input);
201 self.record_width(&name, &ty);
202 if let Some(m) = self.current.as_mut() {
203 m.ports.push(Port {
204 name,
205 direction: PortDirection::Input,
206 ty,
207 span,
208 });
209 }
210 }
211
212 pub fn add_output(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
213 let name = name.into();
214 if !self.ensure_fresh_signal_name(&name, span) {
215 return;
216 }
217 self.signals.insert(name.clone(), SignalKind::Output);
218 self.record_width(&name, &ty);
219 if let Some(m) = self.current.as_mut() {
220 m.ports.push(Port {
221 name,
222 direction: PortDirection::Output,
223 ty,
224 span,
225 });
226 }
227 }
228
229 pub fn add_inout(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
231 let name = name.into();
232 if !self.ensure_fresh_signal_name(&name, span) {
233 return;
234 }
235 self.signals.insert(name.clone(), SignalKind::Wire);
236 self.record_width(&name, &ty);
237 if let Some(m) = self.current.as_mut() {
238 m.ports.push(Port {
239 name,
240 direction: PortDirection::InOut,
241 ty,
242 span,
243 });
244 }
245 }
246
247 pub fn declare_wire(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
248 let name = name.into();
249 self.signals.insert(name.clone(), SignalKind::Wire);
250 self.record_width(&name, &ty);
251 if let Some(m) = self.current.as_mut() {
252 m.body.push(Stmt::WireDecl { name, ty, span });
253 }
254 }
255
256 pub fn declare_reg(&mut self, name: impl Into<String>, ty: GroundType, span: Span) {
257 let name = name.into();
258 let (Some(clock), Some(reset)) = (self.clock_port.clone(), self.reset_port.clone()) else {
259 self.push_err(Diagnostic {
260 span,
261 code: "rhdl::E0124".into(),
262 en: "cannot declare Reg before Clock and Reset ports are declared".into(),
263 zh: "声明寄存器前必须先有 Clock 与 Reset 端口".into(),
264 });
265 return;
266 };
267 self.signals.insert(name.clone(), SignalKind::Reg);
268 self.record_width(&name, &ty);
269 if let Some(m) = self.current.as_mut() {
270 m.body.push(Stmt::RegDecl {
271 name,
272 ty,
273 clock,
274 reset,
275 async_reset: false,
276 has_enable: false,
277 span,
278 });
279 }
280 }
281
282 pub fn declare_reg_ex(
284 &mut self,
285 name: impl Into<String>,
286 ty: GroundType,
287 async_reset: bool,
288 has_enable: bool,
289 span: Span,
290 ) {
291 let name = name.into();
292 let (Some(clock), Some(reset)) = (self.clock_port.clone(), self.reset_port.clone()) else {
293 self.push_err(Diagnostic {
294 span,
295 code: "rhdl::E0124".into(),
296 en: "cannot declare Reg before Clock and Reset ports are declared".into(),
297 zh: "声明寄存器前必须先有 Clock 与 Reset 端口".into(),
298 });
299 return;
300 };
301 self.signals.insert(name.clone(), SignalKind::Reg);
302 self.record_width(&name, &ty);
303 if let Some(m) = self.current.as_mut() {
304 m.body.push(Stmt::RegDecl {
305 name,
306 ty,
307 clock,
308 reset,
309 async_reset,
310 has_enable,
311 span,
312 });
313 }
314 }
315
316 pub fn declare_sync_read_mem(
318 &mut self,
319 name: impl Into<String>,
320 depth: u32,
321 width: u32,
322 span: Span,
323 ) {
324 self.declare_mem_inner(name, depth, width, true, None, span);
325 }
326
327 pub fn declare_mem(&mut self, name: impl Into<String>, depth: u32, width: u32, span: Span) {
329 self.declare_mem_inner(name, depth, width, false, None, span);
330 }
331
332 pub fn declare_mem_with_init(
335 &mut self,
336 name: impl Into<String>,
337 depth: u32,
338 width: u32,
339 init: Vec<u64>,
340 span: Span,
341 ) {
342 self.declare_mem_inner(name, depth, width, false, Some(init), span);
343 }
344
345 pub fn declare_sync_read_mem_with_init(
347 &mut self,
348 name: impl Into<String>,
349 depth: u32,
350 width: u32,
351 init: Vec<u64>,
352 span: Span,
353 ) {
354 self.declare_mem_inner(name, depth, width, true, Some(init), span);
355 }
356
357 pub fn declare_mem_with_init_fn<F>(
365 &mut self,
366 name: impl Into<String>,
367 depth: u32,
368 width: u32,
369 f: F,
370 span: Span,
371 ) where
372 F: Fn(usize) -> u64,
373 {
374 let init = generate_mem_init_words(depth, width, f);
375 self.declare_mem_inner(name, depth, width, false, Some(init), span);
376 }
377
378 pub fn declare_sync_read_mem_with_init_fn<F>(
380 &mut self,
381 name: impl Into<String>,
382 depth: u32,
383 width: u32,
384 f: F,
385 span: Span,
386 ) where
387 F: Fn(usize) -> u64,
388 {
389 let init = generate_mem_init_words(depth, width, f);
390 self.declare_mem_inner(name, depth, width, true, Some(init), span);
391 }
392
393 fn declare_mem_inner(
394 &mut self,
395 name: impl Into<String>,
396 depth: u32,
397 width: u32,
398 sync_read: bool,
399 init: Option<Vec<u64>>,
400 span: Span,
401 ) {
402 let name = name.into();
403 if depth == 0 || width == 0 {
404 self.push_err(Diagnostic {
405 span,
406 code: "rhdl::E0210".into(),
407 en: "Mem depth and width must be non-zero".into(),
408 zh: "Mem 的 depth 与 width 必须非零".into(),
409 });
410 return;
411 }
412 let init = match init {
413 None => None,
414 Some(words) => {
415 if width > 64 {
416 self.push_err(Diagnostic {
417 span,
418 code: "rhdl::E0211".into(),
419 en: "Mem init path supports width ≤ 64 for this MVP".into(),
420 zh: "本 MVP 的 Mem 初值路径仅支持 width ≤ 64".into(),
421 });
422 return;
423 }
424 if words.len() != depth as usize {
425 self.push_err(Diagnostic {
426 span,
427 code: "rhdl::E0212".into(),
428 en: format!(
429 "Mem init length {} does not match depth {depth}",
430 words.len()
431 ),
432 zh: format!("Mem 初值长度 {} 与 depth {depth} 不一致", words.len()),
433 });
434 return;
435 }
436 Some(words.into_iter().map(|w| mask_mem_word(w, width)).collect())
437 }
438 };
439 self.signals.insert(name.clone(), SignalKind::Wire);
440 self.widths.insert(name.clone(), width);
441 if let Some(m) = self.current.as_mut() {
442 m.body.push(Stmt::MemDecl {
443 name,
444 depth,
445 width,
446 sync_read,
447 init,
448 span,
449 });
450 }
451 }
452
453 pub fn check_add(&mut self, lhs: &str, rhs: &str, span: Span) -> Option<u32> {
455 let lw = self.widths.get(lhs).copied();
456 let rw = self.widths.get(rhs).copied();
457 match (lw, rw) {
458 (Some(a), Some(b)) if a == b => Some(a),
459 (Some(a), Some(b)) => {
460 self.push_err(Diagnostic {
461 span,
462 code: "rhdl::E0130".into(),
463 en: format!(
464 "add requires same width; '{lhs}' is {a}, '{rhs}' is {b} (use pad/trunc)"
465 ),
466 zh: format!("加法要求同位宽;'{lhs}' 为 {a},'{rhs}' 为 {b}(请用 pad/trunc)"),
467 });
468 None
469 }
470 _ => {
471 self.push_err(Diagnostic {
472 span,
473 code: "rhdl::E0113".into(),
474 en: format!("unknown signal in add ('{lhs}', '{rhs}')"),
475 zh: format!("加法中有未知信号('{lhs}', '{rhs}')"),
476 });
477 None
478 }
479 }
480 }
481
482 pub fn check_connect(&mut self, lhs: &str, rhs: &str, span: Span) -> Option<u32> {
483 let lw = self.widths.get(lhs).copied();
484 let rw = self.widths.get(rhs).copied();
485 match (lw, rw) {
486 (Some(a), Some(b)) if a == b => Some(a),
487 (Some(a), Some(b)) => {
488 self.push_err(Diagnostic {
489 span,
490 code: "rhdl::E0131".into(),
491 en: format!(
492 "connect requires same width; '{lhs}' is {a}, '{rhs}' is {b} (use pad/trunc)"
493 ),
494 zh: format!(
495 "连接要求同位宽;'{lhs}' 为 {a},'{rhs}' 为 {b}(请用 pad/trunc)"
496 ),
497 });
498 None
499 }
500 _ => {
501 self.push_err(Diagnostic {
502 span,
503 code: "rhdl::E0113".into(),
504 en: format!("unknown signal in connect ('{lhs}', '{rhs}')"),
505 zh: format!("连接中有未知信号('{lhs}', '{rhs}')"),
506 });
507 None
508 }
509 }
510 }
511
512 pub fn pad_to(
514 &mut self,
515 src: &str,
516 to_width: u32,
517 dest: impl Into<String>,
518 span: Span,
519 ) -> bool {
520 let Some(from) = self.widths.get(src).copied() else {
521 self.push_err(Diagnostic {
522 span,
523 code: "rhdl::E0113".into(),
524 en: format!("unknown signal '{src}' in pad"),
525 zh: format!("pad 中未知信号 '{src}'"),
526 });
527 return false;
528 };
529 if to_width <= from {
530 self.push_err(Diagnostic {
531 span,
532 code: "rhdl::E0132".into(),
533 en: format!("pad requires to_width > from_width ({to_width} <= {from})"),
534 zh: format!("pad 要求目标位宽大于源位宽({to_width} <= {from})"),
535 });
536 return false;
537 }
538 let dest = dest.into();
539 self.declare_wire(dest, GroundType::UInt { width: to_width }, span);
540 let _ = bitloom_hir::Expr::Pad {
541 from_width: from,
542 to_width,
543 span,
544 };
545 true
546 }
547
548 pub fn trunc_to(
549 &mut self,
550 src: &str,
551 to_width: u32,
552 dest: impl Into<String>,
553 span: Span,
554 ) -> bool {
555 let Some(from) = self.widths.get(src).copied() else {
556 self.push_err(Diagnostic {
557 span,
558 code: "rhdl::E0113".into(),
559 en: format!("unknown signal '{src}' in trunc"),
560 zh: format!("trunc 中未知信号 '{src}'"),
561 });
562 return false;
563 };
564 if to_width >= from {
565 self.push_err(Diagnostic {
566 span,
567 code: "rhdl::E0133".into(),
568 en: format!("trunc requires to_width < from_width ({to_width} >= {from})"),
569 zh: format!("trunc 要求目标位宽小于源位宽({to_width} >= {from})"),
570 });
571 return false;
572 }
573 let dest = dest.into();
574 self.declare_wire(dest, GroundType::UInt { width: to_width }, span);
575 let _ = bitloom_hir::Expr::Trunc {
576 from_width: from,
577 to_width,
578 span,
579 };
580 true
581 }
582
583 pub fn begin_combinational(&mut self, span: Span) {
584 if self.process.is_some() {
585 self.push_err(Diagnostic {
586 span,
587 code: "rhdl::E0101".into(),
588 en: "nested processes are not allowed".into(),
589 zh: "不允许嵌套硬件过程".into(),
590 });
591 return;
592 }
593 self.process = Some(ProcessState::Combinational {
594 assigns: Vec::new(),
595 path_assigned: vec![HashSet::new()],
596 pending_branches: Vec::new(),
597 span,
598 });
599 }
600
601 pub fn begin_sequential(&mut self, span: Span) {
602 if self.process.is_some() {
603 self.push_err(Diagnostic {
604 span,
605 code: "rhdl::E0101".into(),
606 en: "nested processes are not allowed".into(),
607 zh: "不允许嵌套硬件过程".into(),
608 });
609 return;
610 }
611 self.process = Some(ProcessState::Sequential {
612 assigns: Vec::new(),
613 span,
614 });
615 }
616
617 pub fn begin_then(&mut self, span: Span) {
619 let err = match self.process.as_mut() {
620 Some(ProcessState::Combinational {
621 path_assigned,
622 pending_branches,
623 ..
624 }) => {
625 pending_branches.push((HashSet::new(), false));
626 path_assigned.push(HashSet::new());
627 None
628 }
629 Some(ProcessState::Sequential { .. }) => Some(Diagnostic {
630 span,
631 code: "rhdl::E0102".into(),
632 en: "branch tracking for latch checks is only valid in combinational processes"
633 .into(),
634 zh: "仅组合过程支持 if/else 赋值完整性检查".into(),
635 }),
636 None => Some(Diagnostic {
637 span,
638 code: "rhdl::E0103".into(),
639 en: "assignment control outside a marked combinational/sequential process".into(),
640 zh: "在未标注的 comb/seq 过程外使用分支".into(),
641 }),
642 };
643 if let Some(d) = err {
644 self.push_err(d);
645 }
646 }
647
648 pub fn begin_else(&mut self, span: Span) {
649 let err = match self.process.as_mut() {
650 Some(ProcessState::Combinational {
651 path_assigned,
652 pending_branches,
653 ..
654 }) => {
655 let then_set = path_assigned.pop().unwrap_or_default();
656 if let Some(last) = pending_branches.last_mut() {
657 last.0 = then_set;
658 last.1 = true;
659 path_assigned.push(HashSet::new());
660 None
661 } else {
662 Some(Diagnostic {
663 span,
664 code: "rhdl::E0102".into(),
665 en: "else without an open combinational then-branch".into(),
666 zh: "else 没有对应的组合 then 分支".into(),
667 })
668 }
669 }
670 _ => Some(Diagnostic {
671 span,
672 code: "rhdl::E0102".into(),
673 en: "else without an open combinational then-branch".into(),
674 zh: "else 没有对应的组合 then 分支".into(),
675 }),
676 };
677 if let Some(d) = err {
678 self.push_err(d);
679 }
680 }
681
682 pub fn end_if(&mut self, span: Span) {
683 let mut latch_errs = Vec::new();
684 let err = match self.process.as_mut() {
685 Some(ProcessState::Combinational {
686 path_assigned,
687 pending_branches,
688 ..
689 }) => {
690 let current = path_assigned.pop().unwrap_or_default();
691 let Some((stored_then, had_else)) = pending_branches.pop() else {
692 latch_errs.push(Diagnostic {
693 span,
694 code: "rhdl::E0102".into(),
695 en: "end_if without begin_then".into(),
696 zh: "end_if 缺少 begin_then".into(),
697 });
698 for d in latch_errs {
699 self.push_err(d);
700 }
701 return;
702 };
703
704 let (then_set, else_set) = if had_else {
705 (stored_then, current)
706 } else {
707 (current, HashSet::new())
708 };
709
710 let union: HashSet<_> = then_set.union(&else_set).cloned().collect();
711 let inter: HashSet<_> = then_set.intersection(&else_set).cloned().collect();
712 for name in union.difference(&inter) {
713 latch_errs.push(Diagnostic {
714 span,
715 code: "rhdl::E0110".into(),
716 en: format!(
717 "incomplete combinational assignment to '{name}' (would infer a latch)"
718 ),
719 zh: format!("组合赋值不完整:'{name}'(会推断成 latch)"),
720 });
721 }
722
723 if let Some(parent) = path_assigned.last_mut() {
724 for n in inter {
725 parent.insert(n);
726 }
727 }
728 None
729 }
730 _ => Some(Diagnostic {
731 span,
732 code: "rhdl::E0102".into(),
733 en: "end_if outside combinational process".into(),
734 zh: "end_if 不在组合过程中".into(),
735 }),
736 };
737 for d in latch_errs {
738 self.push_err(d);
739 }
740 if let Some(d) = err {
741 self.push_err(d);
742 }
743 }
744
745 pub fn assign_add(
747 &mut self,
748 dst: impl Into<String>,
749 lhs: impl Into<String>,
750 rhs: impl Into<String>,
751 span: Span,
752 ) {
753 let dst = dst.into();
754 let lhs = lhs.into();
755 let rhs = rhs.into();
756 if self.check_add(&lhs, &rhs, span).is_none() {
757 return;
758 }
759 let kind = self.signals.get(&dst).copied();
761 let process_kind = match &self.process {
762 Some(ProcessState::Combinational { .. }) => Some(ProcessKind::Combinational),
763 Some(ProcessState::Sequential { .. }) => Some(ProcessKind::Sequential),
764 None => None,
765 };
766 match process_kind {
767 Some(ProcessKind::Combinational) => {
768 match kind {
769 Some(SignalKind::Wire | SignalKind::Output) => {}
770 Some(SignalKind::Reg) => {
771 self.push_err(Diagnostic {
772 span,
773 code: "rhdl::E0111".into(),
774 en: format!("combinational process must not drive Reg '{dst}'"),
775 zh: format!("组合过程不能驱动寄存器 '{dst}'"),
776 });
777 return;
778 }
779 Some(SignalKind::Input) => {
780 self.push_err(Diagnostic {
781 span,
782 code: "rhdl::E0112".into(),
783 en: format!("cannot assign to input port '{dst}'"),
784 zh: format!("不能给输入端口 '{dst}' 赋值"),
785 });
786 return;
787 }
788 None => {
789 self.push_err(Diagnostic {
790 span,
791 code: "rhdl::E0113".into(),
792 en: format!("unknown signal '{dst}'"),
793 zh: format!("未知信号 '{dst}'"),
794 });
795 return;
796 }
797 }
798 if let Some(ProcessState::Combinational {
799 assigns,
800 path_assigned,
801 ..
802 }) = self.process.as_mut()
803 {
804 assigns.push(Assign {
805 target: AssignTarget::Net(dst.clone()),
806 expr: AssignExpr::Add(lhs, rhs),
807 span,
808 });
809 if let Some(path) = path_assigned.last_mut() {
810 path.insert(dst);
811 }
812 }
813 }
814 Some(ProcessKind::Sequential) => {
815 self.push_err(Diagnostic {
816 span,
817 code: "rhdl::E0114".into(),
818 en: format!("sequential process must not drive combinational net '{dst}'"),
819 zh: format!("时序过程不能驱动组合网 '{dst}'"),
820 });
821 }
822 None => {
823 self.push_err(Diagnostic {
824 span,
825 code: "rhdl::E0103".into(),
826 en: "assignment outside a marked combinational/sequential process".into(),
827 zh: "在未标注的 comb/seq 过程外赋值".into(),
828 });
829 }
830 }
831 }
832
833 pub fn assign_lit(&mut self, dst: impl Into<String>, lit: u64, span: Span) {
835 self.push_comb_net_expr(dst.into(), AssignExpr::Lit(lit), span);
836 }
837
838 pub fn assign_eq(
840 &mut self,
841 dst: impl Into<String>,
842 lhs: impl Into<String>,
843 rhs: impl Into<String>,
844 span: Span,
845 ) {
846 self.push_comb_net_expr(dst.into(), AssignExpr::Eq(lhs.into(), rhs.into()), span);
847 }
848
849 pub fn assign_mux(
851 &mut self,
852 dst: impl Into<String>,
853 sel: impl Into<String>,
854 t: impl Into<String>,
855 f: impl Into<String>,
856 span: Span,
857 ) {
858 self.push_comb_net_expr(
859 dst.into(),
860 AssignExpr::Mux {
861 sel: sel.into(),
862 t: t.into(),
863 f: f.into(),
864 },
865 span,
866 );
867 }
868
869 pub fn assign_sub(
871 &mut self,
872 dst: impl Into<String>,
873 lhs: impl Into<String>,
874 rhs: impl Into<String>,
875 span: Span,
876 ) {
877 self.push_comb_net_expr(dst.into(), AssignExpr::Sub(lhs.into(), rhs.into()), span);
878 }
879
880 pub fn assign_and(
882 &mut self,
883 dst: impl Into<String>,
884 lhs: impl Into<String>,
885 rhs: impl Into<String>,
886 span: Span,
887 ) {
888 self.push_comb_net_expr(dst.into(), AssignExpr::And(lhs.into(), rhs.into()), span);
889 }
890
891 pub fn assign_or(
893 &mut self,
894 dst: impl Into<String>,
895 lhs: impl Into<String>,
896 rhs: impl Into<String>,
897 span: Span,
898 ) {
899 self.push_comb_net_expr(dst.into(), AssignExpr::Or(lhs.into(), rhs.into()), span);
900 }
901
902 pub fn assign_xor(
904 &mut self,
905 dst: impl Into<String>,
906 lhs: impl Into<String>,
907 rhs: impl Into<String>,
908 span: Span,
909 ) {
910 self.push_comb_net_expr(dst.into(), AssignExpr::Xor(lhs.into(), rhs.into()), span);
911 }
912
913 pub fn assign_shl(
915 &mut self,
916 dst: impl Into<String>,
917 lhs: impl Into<String>,
918 rhs: impl Into<String>,
919 span: Span,
920 ) {
921 self.push_comb_net_expr(dst.into(), AssignExpr::Shl(lhs.into(), rhs.into()), span);
922 }
923
924 pub fn assign_shr(
926 &mut self,
927 dst: impl Into<String>,
928 lhs: impl Into<String>,
929 rhs: impl Into<String>,
930 span: Span,
931 ) {
932 self.push_comb_net_expr(dst.into(), AssignExpr::Shr(lhs.into(), rhs.into()), span);
933 }
934
935 fn push_comb_net_expr(&mut self, dst: String, expr: AssignExpr, span: Span) {
936 let kind = self.signals.get(&dst).copied();
937 let process_kind = match &self.process {
938 Some(ProcessState::Combinational { .. }) => Some(ProcessKind::Combinational),
939 Some(ProcessState::Sequential { .. }) => Some(ProcessKind::Sequential),
940 None => None,
941 };
942 match process_kind {
943 Some(ProcessKind::Combinational) => {
944 match kind {
945 Some(SignalKind::Wire | SignalKind::Output) => {}
946 Some(SignalKind::Reg) => {
947 self.push_err(Diagnostic {
948 span,
949 code: "rhdl::E0111".into(),
950 en: format!("combinational process must not drive Reg '{dst}'"),
951 zh: format!("组合过程不能驱动寄存器 '{dst}'"),
952 });
953 return;
954 }
955 Some(SignalKind::Input) => {
956 self.push_err(Diagnostic {
957 span,
958 code: "rhdl::E0112".into(),
959 en: format!("cannot assign to input port '{dst}'"),
960 zh: format!("不能给输入端口 '{dst}' 赋值"),
961 });
962 return;
963 }
964 None => {
965 self.push_err(Diagnostic {
966 span,
967 code: "rhdl::E0113".into(),
968 en: format!("unknown signal '{dst}'"),
969 zh: format!("未知信号 '{dst}'"),
970 });
971 return;
972 }
973 }
974 if let Some(ProcessState::Combinational {
975 assigns,
976 path_assigned,
977 ..
978 }) = self.process.as_mut()
979 {
980 assigns.push(Assign {
981 target: AssignTarget::Net(dst.clone()),
982 expr,
983 span,
984 });
985 if let Some(path) = path_assigned.last_mut() {
986 path.insert(dst);
987 }
988 }
989 }
990 Some(ProcessKind::Sequential) => {
991 self.push_err(Diagnostic {
992 span,
993 code: "rhdl::E0114".into(),
994 en: format!("sequential process must not drive combinational net '{dst}'"),
995 zh: format!("时序过程不能驱动组合网 '{dst}'"),
996 });
997 }
998 None => {
999 self.push_err(Diagnostic {
1000 span,
1001 code: "rhdl::E0103".into(),
1002 en: "assignment outside a marked combinational/sequential process".into(),
1003 zh: "在未标注的 comb/seq 过程外赋值".into(),
1004 });
1005 }
1006 }
1007 }
1008
1009 pub fn assign_net(&mut self, name: impl Into<String>, from: impl Into<String>, span: Span) {
1011 let name = name.into();
1012 let from = from.into();
1013 if self.reject_illegal_cdc(&from, &name, span) {
1014 return;
1015 }
1016 if self.check_connect(&name, &from, span).is_none() {
1018 return;
1019 }
1020 let kind = self.signals.get(&name).copied();
1021 let process_kind = match &self.process {
1022 Some(ProcessState::Combinational { .. }) => Some(ProcessKind::Combinational),
1023 Some(ProcessState::Sequential { .. }) => Some(ProcessKind::Sequential),
1024 None => None,
1025 };
1026
1027 match process_kind {
1028 Some(ProcessKind::Combinational) => {
1029 match kind {
1030 Some(SignalKind::Reg) => {
1031 self.push_err(Diagnostic {
1032 span,
1033 code: "rhdl::E0111".into(),
1034 en: format!(
1035 "combinational process must not drive Reg '{name}' (use Reg.d in sequential)"
1036 ),
1037 zh: format!(
1038 "组合过程不能驱动寄存器 '{name}'(请在时序过程写 Reg.d)"
1039 ),
1040 });
1041 return;
1042 }
1043 Some(SignalKind::Input) => {
1044 self.push_err(Diagnostic {
1045 span,
1046 code: "rhdl::E0112".into(),
1047 en: format!("cannot assign to input port '{name}'"),
1048 zh: format!("不能给输入端口 '{name}' 赋值"),
1049 });
1050 return;
1051 }
1052 Some(SignalKind::Wire | SignalKind::Output) => {}
1053 None => {
1054 self.push_err(Diagnostic {
1055 span,
1056 code: "rhdl::E0113".into(),
1057 en: format!("unknown signal '{name}'"),
1058 zh: format!("未知信号 '{name}'"),
1059 });
1060 return;
1061 }
1062 }
1063 if let Some(ProcessState::Combinational {
1064 assigns,
1065 path_assigned,
1066 ..
1067 }) = self.process.as_mut()
1068 {
1069 assigns.push(Assign {
1070 target: AssignTarget::Net(name.clone()),
1071 expr: AssignExpr::Ref(from.clone()),
1072 span,
1073 });
1074 if let Some(path) = path_assigned.last_mut() {
1075 path.insert(name);
1076 }
1077 }
1078 }
1079 Some(ProcessKind::Sequential) => {
1080 self.push_err(Diagnostic {
1081 span,
1082 code: "rhdl::E0114".into(),
1083 en: format!("sequential process must not drive combinational net '{name}'"),
1084 zh: format!("时序过程不能驱动组合网 '{name}'"),
1085 });
1086 }
1087 None => {
1088 self.push_err(Diagnostic {
1089 span,
1090 code: "rhdl::E0103".into(),
1091 en: "assignment outside a marked combinational/sequential process".into(),
1092 zh: "在未标注的 comb/seq 过程外赋值".into(),
1093 });
1094 }
1095 }
1096 }
1097
1098 pub fn assign_reg_d_inc(&mut self, name: impl Into<String>, span: Span) {
1100 self.assign_reg_d_expr(name, None, span);
1101 }
1102
1103 pub fn assign_reg_d_from(
1105 &mut self,
1106 name: impl Into<String>,
1107 from: impl Into<String>,
1108 span: Span,
1109 ) {
1110 self.assign_reg_d_expr(name, Some(from.into()), span);
1111 }
1112
1113 pub fn assign_reg_d_mux(
1117 &mut self,
1118 name: impl Into<String>,
1119 sel: impl Into<String>,
1120 t: impl Into<String>,
1121 f: impl Into<String>,
1122 span: Span,
1123 ) {
1124 let name = name.into();
1125 let t = t.into();
1126 let f = f.into();
1127 if self.check_connect(&name, &t, span).is_none()
1128 || self.check_connect(&name, &f, span).is_none()
1129 {
1130 return;
1131 }
1132 self.push_reg_d_assign(
1133 name,
1134 AssignExpr::Mux {
1135 sel: sel.into(),
1136 t,
1137 f,
1138 },
1139 span,
1140 );
1141 }
1142
1143 pub fn assign_mem_write(
1145 &mut self,
1146 mem: impl Into<String>,
1147 addr: impl Into<String>,
1148 data: impl Into<String>,
1149 span: Span,
1150 ) {
1151 self.assign_mem_write_inner(mem.into(), addr.into(), data.into(), None, span);
1152 }
1153
1154 pub fn assign_mem_write_en(
1156 &mut self,
1157 mem: impl Into<String>,
1158 addr: impl Into<String>,
1159 data: impl Into<String>,
1160 we: impl Into<String>,
1161 span: Span,
1162 ) {
1163 self.assign_mem_write_inner(mem.into(), addr.into(), data.into(), Some(we.into()), span);
1164 }
1165
1166 fn assign_mem_write_inner(
1167 &mut self,
1168 mem: String,
1169 addr: String,
1170 data: String,
1171 we: Option<String>,
1172 span: Span,
1173 ) {
1174 match &self.process {
1175 Some(ProcessState::Sequential { .. }) => {
1176 if let Some(ProcessState::Sequential { assigns, .. }) = self.process.as_mut() {
1177 assigns.push(Assign {
1178 target: AssignTarget::MemWrite { mem, addr, we },
1179 expr: AssignExpr::Ref(data),
1180 span,
1181 });
1182 }
1183 }
1184 _ => {
1185 self.push_err(Diagnostic {
1186 span,
1187 code: "rhdl::E0211".into(),
1188 en: "mem write must be inside a sequential process".into(),
1189 zh: "mem 写必须在 sequential 过程内".into(),
1190 });
1191 }
1192 }
1193 }
1194
1195 pub fn assign_reg_d_mem_read(
1197 &mut self,
1198 reg: impl Into<String>,
1199 mem: impl Into<String>,
1200 addr: impl Into<String>,
1201 span: Span,
1202 ) {
1203 let reg = reg.into();
1204 let mem = mem.into();
1205 let addr = addr.into();
1206 match &self.process {
1207 Some(ProcessState::Sequential { .. }) => {
1208 if let Some(ProcessState::Sequential { assigns, .. }) = self.process.as_mut() {
1209 assigns.push(Assign {
1210 target: AssignTarget::RegD(reg),
1211 expr: AssignExpr::MemRead { mem, addr },
1212 span,
1213 });
1214 }
1215 }
1216 _ => {
1217 self.push_err(Diagnostic {
1218 span,
1219 code: "rhdl::E0212".into(),
1220 en: "sync mem read into Reg must be inside a sequential process".into(),
1221 zh: "SyncReadMem 读入寄存器必须在 sequential 过程内".into(),
1222 });
1223 }
1224 }
1225 }
1226
1227 fn assign_reg_d_expr(&mut self, name: impl Into<String>, from: Option<String>, span: Span) {
1228 let name = name.into();
1229 if let Some(ref src) = from {
1230 if self.reject_illegal_cdc(src, &name, span) {
1231 return;
1232 }
1233 if self.check_connect(&name, src, span).is_none() {
1234 return;
1235 }
1236 }
1237 let expr = match from {
1238 Some(src) => AssignExpr::Ref(src),
1239 None => AssignExpr::Inc(name.clone()),
1240 };
1241 self.push_reg_d_assign(name, expr, span);
1242 }
1243
1244 pub fn end_process(&mut self) {
1245 let Some(state) = self.process.take() else {
1246 return;
1247 };
1248 match state {
1249 ProcessState::Combinational {
1250 assigns,
1251 pending_branches,
1252 span,
1253 ..
1254 } => {
1255 if !pending_branches.is_empty() {
1256 self.push_err(Diagnostic {
1257 span,
1258 code: "rhdl::E0102".into(),
1259 en: "unclosed if/else in combinational process".into(),
1260 zh: "组合过程中有未关闭的 if/else".into(),
1261 });
1262 }
1263 if let Some(m) = self.current.as_mut() {
1264 m.body.push(Stmt::Process(Process {
1265 kind: ProcessKind::Combinational,
1266 assigns,
1267 span,
1268 }));
1269 }
1270 }
1271 ProcessState::Sequential { assigns, span } => {
1272 if let Some(m) = self.current.as_mut() {
1273 m.body.push(Stmt::Process(Process {
1274 kind: ProcessKind::Sequential,
1275 assigns,
1276 span,
1277 }));
1278 }
1279 }
1280 }
1281 }
1282
1283 pub fn end_module(&mut self) {
1284 if self.process.is_some() {
1285 self.end_process();
1286 }
1287 if let Some(m) = self.current.take() {
1288 self.hir.add_module(m);
1289 }
1290 self.signals.clear();
1291 self.widths.clear();
1292 self.clock_port = None;
1293 self.reset_port = None;
1294 }
1295
1296 pub fn finish(self) -> Result<FrozenHir, Diagnostics> {
1297 if !self.errors.is_empty() {
1298 return Err(self.errors);
1299 }
1300 bitloom_hir::seal_from_builder(self.hir)
1301 }
1302
1303 pub fn reject_unsynthesizable(&mut self, construct: &str, span: Span) {
1306 self.push_err(Diagnostic {
1307 span,
1308 code: "rhdl::E0141".into(),
1309 en: format!(
1310 "unsynthesizable construct '{construct}' is not allowed on the cycle-accurate path"
1311 ),
1312 zh: format!("周期精确路径不允许不可综合构造 '{construct}'"),
1313 });
1314 }
1315
1316 pub fn reject_hw_capture(&mut self, capture: &HwCaptureRef, span: Span) {
1322 let kind = capture.kind_label();
1323 self.push_err(Diagnostic {
1324 span,
1325 code: "rhdl::E0142".into(),
1326 en: format!(
1327 "illegal capture of hardware {kind} '{}' into elaborate-time generator closure; \
1328 only non-capturing Fn that dissolves before freeze is allowed (FR73 / NFR35 / AD-18)",
1329 capture.name
1330 ),
1331 zh: format!(
1332 "不允许将硬件 {kind} '{}' 捕获进 elaborate-time 生成器闭包;\
1333 仅允许冻前消解的非捕获 Fn(FR73 / NFR35 / AD-18)",
1334 capture.name
1335 ),
1336 });
1337 }
1338
1339 pub fn assert_no_hw_capture(&mut self, captures: &[HwCaptureRef], span: Span) {
1345 for c in captures {
1346 self.reject_hw_capture(c, span);
1347 }
1348 }
1349
1350 pub fn reject_unsynthesizable_closure(
1356 &mut self,
1357 violation: &SynthesizableClosureViolation,
1358 span: Span,
1359 ) {
1360 for d in diagnose_synthesizable_closure_violations(std::slice::from_ref(violation), span).0
1361 {
1362 self.push_err(d);
1363 }
1364 }
1365
1366 pub fn check_synthesizable_closure(
1372 &mut self,
1373 violations: &[SynthesizableClosureViolation],
1374 span: Span,
1375 ) {
1376 for v in violations {
1377 self.reject_unsynthesizable_closure(v, span);
1378 }
1379 }
1380
1381 pub fn check_synthesizable_closure_marker<C: SynthesizableClosure>(
1384 &mut self,
1385 closure: &C,
1386 span: Span,
1387 ) {
1388 let vs = closure.synthesizable_closure_violations();
1389 self.check_synthesizable_closure(&vs, span);
1390 }
1391
1392 pub fn inline_comb_fn<F>(
1413 &mut self,
1414 dst: impl Into<String>,
1415 args: &[&str],
1416 violations: &[SynthesizableClosureViolation],
1417 span: Span,
1418 f: F,
1419 ) where
1420 F: FnOnce(&[&str]) -> CombInline,
1421 {
1422 self.check_synthesizable_closure(violations, span);
1423 if !violations.is_empty() {
1424 return;
1425 }
1426 let inline = f(args);
1427 self.apply_comb_inline(dst.into(), inline, span);
1428 }
1429
1430 pub fn inline_comb_fn_marker<C, F>(
1432 &mut self,
1433 dst: impl Into<String>,
1434 args: &[&str],
1435 marker: &C,
1436 span: Span,
1437 f: F,
1438 ) where
1439 C: SynthesizableClosure,
1440 F: FnOnce(&[&str]) -> CombInline,
1441 {
1442 let vs = marker.synthesizable_closure_violations();
1443 self.inline_comb_fn(dst, args, &vs, span, f);
1444 }
1445
1446 fn apply_comb_inline(&mut self, dst: String, inline: CombInline, span: Span) {
1447 match inline {
1448 CombInline::Ref(src) => self.assign_net(dst, src, span),
1449 CombInline::Lit(v) => self.assign_lit(dst, v, span),
1450 CombInline::Add(l, r) => self.assign_add(dst, l, r, span),
1451 CombInline::Sub(l, r) => self.assign_sub(dst, l, r, span),
1452 CombInline::And(l, r) => self.assign_and(dst, l, r, span),
1453 CombInline::Or(l, r) => self.assign_or(dst, l, r, span),
1454 CombInline::Xor(l, r) => self.assign_xor(dst, l, r, span),
1455 CombInline::Eq(l, r) => self.assign_eq(dst, l, r, span),
1456 CombInline::Mux { sel, t, f } => self.assign_mux(dst, sel, t, f, span),
1457 }
1458 }
1459
1460 pub fn reject_seq_ownership_violation(
1462 &mut self,
1463 violation: &SeqOwnershipViolation,
1464 span: Span,
1465 ) {
1466 for d in diagnose_seq_ownership_violations(std::slice::from_ref(violation), span).0 {
1467 self.push_err(d);
1468 }
1469 }
1470
1471 pub fn check_seq_ownership(&mut self, violations: &[SeqOwnershipViolation], span: Span) {
1474 for v in violations {
1475 self.reject_seq_ownership_violation(v, span);
1476 }
1477 }
1478
1479 pub fn inline_seq_fn<F>(
1500 &mut self,
1501 dst_reg: impl Into<String>,
1502 args: &[&str],
1503 synth_violations: &[SynthesizableClosureViolation],
1504 ownership_violations: &[SeqOwnershipViolation],
1505 span: Span,
1506 f: F,
1507 ) where
1508 F: FnOnce(&[&str]) -> SeqInline,
1509 {
1510 self.check_synthesizable_closure(synth_violations, span);
1511 self.check_seq_ownership(ownership_violations, span);
1512 let dst = dst_reg.into();
1513 let already = self.seq_reg_d_already_assigned(&dst);
1514 if already {
1515 self.reject_seq_ownership_violation(
1516 &SeqOwnershipViolation::illegal_mutable_borrow(format!(
1517 "Reg.d '{dst}' already assigned in this sequential process"
1518 )),
1519 span,
1520 );
1521 }
1522 if !synth_violations.is_empty() || !ownership_violations.is_empty() || already {
1523 return;
1524 }
1525 let inline = f(args);
1526 self.apply_seq_inline(dst, inline, span);
1527 }
1528
1529 pub fn inline_seq_fn_marker<C, F>(
1531 &mut self,
1532 dst_reg: impl Into<String>,
1533 args: &[&str],
1534 marker: &C,
1535 ownership_violations: &[SeqOwnershipViolation],
1536 span: Span,
1537 f: F,
1538 ) where
1539 C: SynthesizableClosure,
1540 F: FnOnce(&[&str]) -> SeqInline,
1541 {
1542 let vs = marker.synthesizable_closure_violations();
1543 self.inline_seq_fn(dst_reg, args, &vs, ownership_violations, span, f);
1544 }
1545
1546 fn seq_reg_d_already_assigned(&self, name: &str) -> bool {
1547 match &self.process {
1548 Some(ProcessState::Sequential { assigns, .. }) => assigns
1549 .iter()
1550 .any(|a| matches!(&a.target, AssignTarget::RegD(n) if n == name)),
1551 _ => false,
1552 }
1553 }
1554
1555 fn apply_seq_inline(&mut self, dst: String, inline: SeqInline, span: Span) {
1556 let expr = match inline {
1557 SeqInline::Inc => AssignExpr::Inc(dst.clone()),
1558 SeqInline::Comb(CombInline::Ref(src)) => {
1559 if self.check_connect(&dst, &src, span).is_none() {
1560 return;
1561 }
1562 AssignExpr::Ref(src)
1563 }
1564 SeqInline::Comb(CombInline::Lit(v)) => AssignExpr::Lit(v),
1565 SeqInline::Comb(CombInline::Add(l, r)) => {
1566 if self.check_add(&l, &r, span).is_none() {
1567 return;
1568 }
1569 AssignExpr::Add(l, r)
1570 }
1571 SeqInline::Comb(CombInline::Sub(l, r)) => {
1572 if self.check_add(&l, &r, span).is_none() {
1573 return;
1574 }
1575 AssignExpr::Sub(l, r)
1576 }
1577 SeqInline::Comb(CombInline::And(l, r)) => {
1578 if self.check_add(&l, &r, span).is_none() {
1579 return;
1580 }
1581 AssignExpr::And(l, r)
1582 }
1583 SeqInline::Comb(CombInline::Or(l, r)) => {
1584 if self.check_add(&l, &r, span).is_none() {
1585 return;
1586 }
1587 AssignExpr::Or(l, r)
1588 }
1589 SeqInline::Comb(CombInline::Xor(l, r)) => {
1590 if self.check_add(&l, &r, span).is_none() {
1591 return;
1592 }
1593 AssignExpr::Xor(l, r)
1594 }
1595 SeqInline::Comb(CombInline::Eq(l, r)) => {
1596 if self.check_add(&l, &r, span).is_none() {
1597 return;
1598 }
1599 AssignExpr::Eq(l, r)
1600 }
1601 SeqInline::Comb(CombInline::Mux { sel, t, f }) => {
1602 if self.check_connect(&dst, &t, span).is_none()
1603 || self.check_connect(&dst, &f, span).is_none()
1604 {
1605 return;
1606 }
1607 AssignExpr::Mux { sel, t, f }
1608 }
1609 };
1610 self.push_reg_d_assign(dst, expr, span);
1611 }
1612
1613 fn push_reg_d_assign(&mut self, name: String, expr: AssignExpr, span: Span) {
1615 let kind = self.signals.get(&name).copied();
1616 let process_kind = match &self.process {
1617 Some(ProcessState::Combinational { .. }) => Some(ProcessKind::Combinational),
1618 Some(ProcessState::Sequential { .. }) => Some(ProcessKind::Sequential),
1619 None => None,
1620 };
1621
1622 match process_kind {
1623 Some(ProcessKind::Sequential) => match kind {
1624 Some(SignalKind::Reg) => {
1625 if let Some(ProcessState::Sequential { assigns, .. }) = self.process.as_mut() {
1626 assigns.push(Assign {
1627 target: AssignTarget::RegD(name),
1628 expr,
1629 span,
1630 });
1631 }
1632 }
1633 Some(_) => {
1634 self.push_err(Diagnostic {
1635 span,
1636 code: "rhdl::E0115".into(),
1637 en: format!("'{name}' is not a Reg; Reg.d requires a register"),
1638 zh: format!("'{name}' 不是寄存器,不能写 Reg.d"),
1639 });
1640 }
1641 None => {
1642 self.push_err(Diagnostic {
1643 span,
1644 code: "rhdl::E0113".into(),
1645 en: format!("unknown signal '{name}'"),
1646 zh: format!("未知信号 '{name}'"),
1647 });
1648 }
1649 },
1650 Some(ProcessKind::Combinational) => {
1651 self.push_err(Diagnostic {
1652 span,
1653 code: "rhdl::E0116".into(),
1654 en: format!("combinational process must not write Reg.d for '{name}'"),
1655 zh: format!("组合过程不能写 '{name}' 的 Reg.d"),
1656 });
1657 }
1658 None => {
1659 self.push_err(Diagnostic {
1660 span,
1661 code: "rhdl::E0103".into(),
1662 en: "assignment outside a marked combinational/sequential process".into(),
1663 zh: "在未标注的 comb/seq 过程外赋值".into(),
1664 });
1665 }
1666 }
1667 }
1668
1669 pub fn add_instance(
1671 &mut self,
1672 name: impl Into<String>,
1673 module: impl Into<String>,
1674 connects: Vec<(String, String)>,
1675 params: Vec<(String, u32)>,
1676 span: Span,
1677 ) {
1678 use bitloom_hir::{Instance, PortConnect};
1679 let connects = connects
1680 .into_iter()
1681 .map(|(child_port, parent_net)| PortConnect {
1682 child_port,
1683 parent_net,
1684 span,
1685 dangling: false,
1686 })
1687 .collect();
1688 if let Some(m) = self.current.as_mut() {
1689 m.body.push(Stmt::Instance(Instance {
1690 name: name.into(),
1691 module: module.into(),
1692 connects,
1693 params,
1694 span,
1695 }));
1696 }
1697 }
1698
1699 pub fn generate_instances<F>(&mut self, count: usize, mut f: F)
1726 where
1727 F: FnMut(usize, &mut Self),
1728 {
1729 for i in 0..count {
1730 f(i, self);
1731 }
1732 }
1733
1734 pub fn generate_instances_from<F>(&mut self, count: usize, f: F, span: Span)
1739 where
1740 F: Fn(usize) -> GeneratedInstance,
1741 {
1742 for i in 0..count {
1743 let g = f(i);
1744 self.add_instance(g.name, g.module, g.connects, g.params, span);
1745 }
1746 }
1747
1748 pub fn add_dangling_input(
1749 &mut self,
1750 instance: &str,
1751 child_port: impl Into<String>,
1752 span: Span,
1753 ) {
1754 use bitloom_hir::PortConnect;
1755 if let Some(m) = self.current.as_mut() {
1756 for stmt in &mut m.body {
1757 if let Stmt::Instance(inst) = stmt {
1758 if inst.name == instance {
1759 inst.connects.push(PortConnect {
1760 child_port: child_port.into(),
1761 parent_net: String::new(),
1762 span,
1763 dangling: true,
1764 });
1765 return;
1766 }
1767 }
1768 }
1769 }
1770 self.push_err(Diagnostic {
1771 span,
1772 code: "rhdl::E0201".into(),
1773 en: format!("unknown instance '{instance}' for dangling mark"),
1774 zh: format!("悬空标记找不到实例 '{instance}'"),
1775 });
1776 }
1777}
1778
1779pub trait Elaboratable {
1781 fn elaborate() -> Result<FrozenHir, Diagnostics>;
1782}
1783
1784#[cfg(test)]
1785mod tests {
1786 use super::*;
1787
1788 fn base_ports(s: &mut ElaborateSession) {
1789 s.begin_module("M", Span::default());
1790 s.add_input("clk", GroundType::Clock, Span::default());
1791 s.add_input("rst", GroundType::Reset, Span::default());
1792 s.add_input("data_in", GroundType::UInt { width: 8 }, Span::default());
1793 s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
1794 }
1795
1796 #[test]
1797 fn complete_comb_assign_ok() {
1798 let mut s = ElaborateSession::new("t");
1799 base_ports(&mut s);
1800 s.begin_combinational(Span::default());
1801 s.assign_net("data_out", "data_in", Span::default());
1802 s.end_process();
1803 s.end_module();
1804 assert!(s.finish().is_ok());
1805 }
1806
1807 #[test]
1808 fn incomplete_branch_is_latch_error() {
1809 let mut s = ElaborateSession::new("t");
1810 base_ports(&mut s);
1811 s.begin_combinational(Span::default());
1812 s.begin_then(Span::default());
1813 s.assign_net("data_out", "data_in", Span::default());
1814 s.begin_else(Span::default());
1815 s.end_if(Span::default());
1817 s.end_process();
1818 s.end_module();
1819 let err = s.finish().unwrap_err();
1820 assert!(
1821 err.0.iter().any(|d| d.code == "rhdl::E0110"),
1822 "expected latch diagnostic, got {err}"
1823 );
1824 }
1825
1826 #[test]
1827 fn both_branches_assign_ok() {
1828 let mut s = ElaborateSession::new("t");
1829 base_ports(&mut s);
1830 s.begin_combinational(Span::default());
1831 s.begin_then(Span::default());
1832 s.assign_net("data_out", "data_in", Span::default());
1833 s.begin_else(Span::default());
1834 s.assign_net("data_out", "data_in", Span::default());
1835 s.end_if(Span::default());
1836 s.end_process();
1837 s.end_module();
1838 let r = s.finish();
1839 assert!(r.is_ok(), "{:?}", r.err());
1840 }
1841
1842 #[test]
1843 fn comb_cannot_write_reg_d() {
1844 let mut s = ElaborateSession::new("t");
1845 base_ports(&mut s);
1846 s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
1847 s.begin_combinational(Span::default());
1848 s.assign_reg_d_inc("count", Span::default());
1849 s.end_process();
1850 s.end_module();
1851 let err = s.finish().unwrap_err();
1852 assert!(err.0.iter().any(|d| d.code == "rhdl::E0116"));
1853 }
1854
1855 #[test]
1856 fn seq_cannot_drive_comb_net() {
1857 let mut s = ElaborateSession::new("t");
1858 base_ports(&mut s);
1859 s.begin_sequential(Span::default());
1860 s.assign_net("data_out", "data_in", Span::default());
1861 s.end_process();
1862 s.end_module();
1863 let err = s.finish().unwrap_err();
1864 assert!(err.0.iter().any(|d| d.code == "rhdl::E0114"));
1865 }
1866
1867 #[test]
1868 fn assign_outside_process_rejected() {
1869 let mut s = ElaborateSession::new("t");
1870 base_ports(&mut s);
1871 s.assign_net("data_out", "data_in", Span::default());
1872 s.end_module();
1873 let err = s.finish().unwrap_err();
1874 assert!(err.0.iter().any(|d| d.code == "rhdl::E0103"));
1875 }
1876
1877 #[test]
1878 fn seq_reg_d_ok() {
1879 let mut s = ElaborateSession::new("t");
1880 base_ports(&mut s);
1881 s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
1882 s.begin_combinational(Span::default());
1883 s.assign_net("data_out", "count", Span::default());
1884 s.end_process();
1885 s.begin_sequential(Span::default());
1886 s.assign_reg_d_inc("count", Span::default());
1887 s.end_process();
1888 s.end_module();
1889 assert!(s.finish().is_ok());
1890 }
1891
1892 #[test]
1893 fn missing_clock_rejected() {
1894 let mut s = ElaborateSession::new("t");
1895 s.begin_module("M", Span::default());
1896 s.add_input("rst", GroundType::Reset, Span::default());
1897 s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
1898 s.end_module();
1899 let err = s.finish().unwrap_err();
1900 assert!(err.0.iter().any(|d| d.code == "rhdl::E0120"));
1901 }
1902
1903 #[test]
1904 fn missing_reset_rejected() {
1905 let mut s = ElaborateSession::new("t");
1906 s.begin_module("M", Span::default());
1907 s.add_input("clk", GroundType::Clock, Span::default());
1908 s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
1909 s.end_module();
1910 let err = s.finish().unwrap_err();
1911 assert!(err.0.iter().any(|d| d.code == "rhdl::E0121"));
1912 }
1913
1914 #[test]
1915 fn mismatched_add_width_rejected() {
1916 let mut s = ElaborateSession::new("t");
1917 base_ports(&mut s);
1918 s.declare_wire("a", GroundType::UInt { width: 8 }, Span::default());
1919 s.declare_wire("b", GroundType::UInt { width: 16 }, Span::default());
1920 assert!(s.check_add("a", "b", Span::default()).is_none());
1921 s.end_module();
1922 let err = s.finish().unwrap_err();
1923 assert!(err.0.iter().any(|d| d.code == "rhdl::E0130"));
1924 }
1925
1926 #[test]
1927 fn mismatched_assign_net_width_rejected() {
1928 let mut s = ElaborateSession::new("t");
1929 base_ports(&mut s);
1930 s.add_output("narrow", GroundType::UInt { width: 4 }, Span::default());
1931 s.begin_combinational(Span::default());
1932 s.assign_net("narrow", "data_in", Span::default());
1933 s.end_process();
1934 s.end_module();
1935 let err = s.finish().unwrap_err();
1936 assert!(
1937 err.0.iter().any(|d| d.code == "rhdl::E0131"),
1938 "expected E0131, got {err}"
1939 );
1940 }
1941
1942 #[test]
1943 fn mismatched_assign_reg_d_width_rejected() {
1944 let mut s = ElaborateSession::new("t");
1945 base_ports(&mut s);
1946 s.declare_reg("q_narrow", GroundType::UInt { width: 4 }, Span::default());
1947 s.begin_sequential(Span::default());
1948 s.assign_reg_d_from("q_narrow", "data_in", Span::default());
1949 s.end_process();
1950 s.end_module();
1951 let err = s.finish().unwrap_err();
1952 assert!(
1953 err.0.iter().any(|d| d.code == "rhdl::E0131"),
1954 "expected E0131 on Reg.d path, got {err}"
1955 );
1956 }
1957
1958 #[test]
1959 fn pad_then_add_ok() {
1960 let mut s = ElaborateSession::new("t");
1961 base_ports(&mut s);
1962 s.declare_wire("a", GroundType::UInt { width: 8 }, Span::default());
1963 s.declare_wire("b", GroundType::UInt { width: 16 }, Span::default());
1964 assert!(s.pad_to("a", 16, "a_pad", Span::default()));
1965 assert_eq!(s.check_add("a_pad", "b", Span::default()), Some(16));
1966 s.begin_combinational(Span::default());
1967 s.assign_net("data_out", "data_in", Span::default());
1968 s.end_process();
1969 s.end_module();
1970 assert!(s.finish().is_ok());
1971 }
1972
1973 #[test]
1974 fn multi_drive_rejected() {
1975 let mut s = ElaborateSession::new("t");
1976 base_ports(&mut s);
1977 s.begin_combinational(Span::default());
1978 s.assign_net("data_out", "data_in", Span::default());
1979 s.end_process();
1980 s.begin_combinational(Span::default());
1981 s.assign_net("data_out", "data_in", Span::default());
1982 s.end_process();
1983 s.end_module();
1984 let err = s.finish().unwrap_err();
1985 assert!(err.0.iter().any(|d| d.code == "rhdl::E0140"));
1986 }
1987
1988 #[test]
1989 fn parameterized_widths_w8_and_w16() {
1990 fn elaborate_w(w: u32) -> bitloom_hir::FrozenHir {
1991 let mut s = ElaborateSession::new("t");
1992 s.begin_module(format!("Add{w}"), Span::default());
1993 s.add_input("clk", GroundType::Clock, Span::default());
1994 s.add_input("rst", GroundType::Reset, Span::default());
1995 s.add_input("a", GroundType::UInt { width: w }, Span::default());
1996 s.add_input("b", GroundType::UInt { width: w }, Span::default());
1997 s.add_output("y", GroundType::UInt { width: w }, Span::default());
1998 s.begin_combinational(Span::default());
1999 s.assign_net("y", "a", Span::default());
2000 s.end_process();
2001 s.end_module();
2002 s.finish().unwrap()
2003 }
2004 let h8 = elaborate_w(8);
2005 let h16 = elaborate_w(16);
2006 assert!(matches!(
2007 h8.circuit().modules[0].ports[2].ty,
2008 GroundType::UInt { width: 8 }
2009 ));
2010 assert!(matches!(
2011 h16.circuit().modules[0].ports[2].ty,
2012 GroundType::UInt { width: 16 }
2013 ));
2014 }
2015
2016 #[test]
2017 fn hierarchy_instance_preserved() {
2018 let mut s = ElaborateSession::new("t");
2019 s.begin_module("Child", Span::default());
2020 s.add_input("clk", GroundType::Clock, Span::default());
2021 s.add_input("rst", GroundType::Reset, Span::default());
2022 s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
2023 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2024 s.begin_combinational(Span::default());
2025 s.assign_net("y", "x", Span::default());
2026 s.end_process();
2027 s.end_module();
2028
2029 s.begin_module("Parent", Span::default());
2030 s.add_input("clk", GroundType::Clock, Span::default());
2031 s.add_input("rst", GroundType::Reset, Span::default());
2032 s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
2033 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2034 s.add_instance(
2035 "u0",
2036 "Child",
2037 vec![
2038 ("clk".into(), "clk".into()),
2039 ("rst".into(), "rst".into()),
2040 ("x".into(), "x".into()),
2041 ("y".into(), "y".into()),
2042 ],
2043 vec![("W".into(), 8)],
2044 Span::default(),
2045 );
2046 s.end_module();
2047 let frozen = s.finish().unwrap();
2048 assert_eq!(frozen.circuit().modules.len(), 2);
2049 assert!(frozen.circuit().modules[1].body.iter().any(|st| matches!(
2050 st,
2051 bitloom_hir::Stmt::Instance(i) if i.name == "u0" && i.module == "Child"
2052 )));
2053 }
2054
2055 #[test]
2056 fn generate_instances_factory_batches_children() {
2057 let mut s = ElaborateSession::new("t");
2058 s.begin_module("Lane", Span::default());
2059 s.add_input("clk", GroundType::Clock, Span::default());
2060 s.add_input("rst", GroundType::Reset, Span::default());
2061 s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
2062 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2063 s.begin_combinational(Span::default());
2064 s.assign_net("y", "x", Span::default());
2065 s.end_process();
2066 s.end_module();
2067
2068 s.begin_module("Parent", Span::default());
2069 s.add_input("clk", GroundType::Clock, Span::default());
2070 s.add_input("rst", GroundType::Reset, Span::default());
2071 for i in 0..3 {
2072 s.add_input(
2073 format!("x{i}"),
2074 GroundType::UInt { width: 8 },
2075 Span::default(),
2076 );
2077 s.add_output(
2078 format!("y{i}"),
2079 GroundType::UInt { width: 8 },
2080 Span::default(),
2081 );
2082 }
2083 s.generate_instances(3, |i, sess| {
2084 sess.add_instance(
2085 format!("u{i}"),
2086 "Lane",
2087 vec![
2088 ("clk".into(), "clk".into()),
2089 ("rst".into(), "rst".into()),
2090 ("x".into(), format!("x{i}")),
2091 ("y".into(), format!("y{i}")),
2092 ],
2093 vec![],
2094 Span::default(),
2095 );
2096 });
2097 s.end_module();
2098 let frozen = s.finish().unwrap();
2099 let parent = frozen
2100 .circuit()
2101 .modules
2102 .iter()
2103 .find(|m| m.name == "Parent")
2104 .unwrap();
2105 let instances: Vec<_> = parent
2106 .body
2107 .iter()
2108 .filter_map(|st| match st {
2109 bitloom_hir::Stmt::Instance(i) => Some(i.name.as_str()),
2110 _ => None,
2111 })
2112 .collect();
2113 assert_eq!(instances, ["u0", "u1", "u2"]);
2114 }
2115
2116 #[test]
2117 fn generate_instances_from_returns_plain_specs() {
2118 let mut s = ElaborateSession::new("t");
2119 s.begin_module("Lane", Span::default());
2120 s.add_input("clk", GroundType::Clock, Span::default());
2121 s.add_input("rst", GroundType::Reset, Span::default());
2122 s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
2123 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2124 s.end_module();
2125 s.begin_module("Parent", Span::default());
2126 s.add_input("clk", GroundType::Clock, Span::default());
2127 s.add_input("rst", GroundType::Reset, Span::default());
2128 s.add_input("x0", GroundType::UInt { width: 8 }, Span::default());
2129 s.add_output("y0", GroundType::UInt { width: 8 }, Span::default());
2130 s.generate_instances_from(
2131 1,
2132 |_| {
2133 GeneratedInstance::new(
2134 "u0",
2135 "Lane",
2136 vec![
2137 ("clk".into(), "clk".into()),
2138 ("rst".into(), "rst".into()),
2139 ("x".into(), "x0".into()),
2140 ("y".into(), "y0".into()),
2141 ],
2142 vec![],
2143 )
2144 },
2145 Span::default(),
2146 );
2147 s.end_module();
2148 assert!(s.finish().is_ok());
2149 }
2150
2151 #[test]
2152 fn undriven_child_input_rejected() {
2153 let mut s = ElaborateSession::new("t");
2154 s.begin_module("Child", Span::default());
2155 s.add_input("clk", GroundType::Clock, Span::default());
2156 s.add_input("rst", GroundType::Reset, Span::default());
2157 s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
2158 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2159 s.end_module();
2160 s.begin_module("Parent", Span::default());
2161 s.add_input("clk", GroundType::Clock, Span::default());
2162 s.add_input("rst", GroundType::Reset, Span::default());
2163 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2164 s.add_instance(
2165 "u0",
2166 "Child",
2167 vec![
2168 ("clk".into(), "clk".into()),
2169 ("rst".into(), "rst".into()),
2170 ("y".into(), "y".into()),
2171 ],
2172 vec![],
2173 Span::default(),
2174 );
2175 s.end_module();
2176 let err = s.finish().unwrap_err();
2177 assert!(err.0.iter().any(|d| d.code == "rhdl::E0202"));
2178 }
2179
2180 #[test]
2181 fn sync_read_mem_declares_and_emits() {
2182 let mut s = ElaborateSession::new("t");
2183 s.begin_module("MemTop", Span::default());
2184 s.add_input("clk", GroundType::Clock, Span::default());
2185 s.add_input("rst", GroundType::Reset, Span::default());
2186 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2187 s.declare_sync_read_mem("ram", 16, 8, Span::default());
2188 s.begin_combinational(Span::default());
2189 s.assign_net("y", "ram", Span::default());
2190 s.end_process();
2191 s.end_module();
2192 let frozen = s.finish().unwrap();
2193 assert!(frozen.circuit().modules[0].body.iter().any(|st| matches!(
2194 st,
2195 bitloom_hir::Stmt::MemDecl {
2196 sync_read: true,
2197 ..
2198 }
2199 )));
2200 }
2201
2202 #[test]
2203 fn mem_with_init_fn_stores_plain_words() {
2204 let mut s = ElaborateSession::new("t");
2205 s.begin_module("Lut", Span::default());
2206 s.add_input("clk", GroundType::Clock, Span::default());
2207 s.add_input("rst", GroundType::Reset, Span::default());
2208 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2209 s.declare_mem_with_init_fn("rom", 4, 8, |i| ((i * i) & 0xff) as u64, Span::default());
2210 s.begin_combinational(Span::default());
2211 s.assign_net("y", "rom", Span::default());
2212 s.end_process();
2213 s.end_module();
2214 let frozen = s.finish().unwrap();
2215 let init = frozen.circuit().modules[0]
2216 .body
2217 .iter()
2218 .find_map(|st| match st {
2219 bitloom_hir::Stmt::MemDecl {
2220 name,
2221 init: Some(words),
2222 ..
2223 } if name == "rom" => Some(words.clone()),
2224 _ => None,
2225 })
2226 .expect("rom init present");
2227 assert_eq!(init, vec![0, 1, 4, 9]);
2228 }
2229
2230 #[test]
2231 fn mem_init_len_mismatch_fails() {
2232 let mut s = ElaborateSession::new("t");
2233 s.begin_module("Bad", Span::default());
2234 s.add_input("clk", GroundType::Clock, Span::default());
2235 s.add_input("rst", GroundType::Reset, Span::default());
2236 s.declare_mem_with_init("rom", 4, 8, vec![1, 2], Span::default());
2237 s.end_module();
2238 let err = s.finish().unwrap_err();
2239 assert!(err.0.iter().any(|d| d.code == "rhdl::E0212"));
2240 }
2241
2242 #[test]
2243 fn async_reset_and_enable_flags() {
2244 let mut s = ElaborateSession::new("t");
2245 s.begin_module("M", Span::default());
2246 s.add_input("clk", GroundType::Clock, Span::default());
2247 s.add_input("rst", GroundType::Reset, Span::default());
2248 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2249 s.declare_reg_ex(
2250 "q",
2251 GroundType::UInt { width: 8 },
2252 true,
2253 true,
2254 Span::default(),
2255 );
2256 s.begin_combinational(Span::default());
2257 s.assign_net("y", "q", Span::default());
2258 s.end_process();
2259 s.begin_sequential(Span::default());
2260 s.assign_reg_d_inc("q", Span::default());
2261 s.end_process();
2262 s.end_module();
2263 let frozen = s.finish().unwrap();
2264 assert!(frozen.circuit().modules[0].body.iter().any(|st| matches!(
2265 st,
2266 bitloom_hir::Stmt::RegDecl {
2267 async_reset: true,
2268 has_enable: true,
2269 ..
2270 }
2271 )));
2272 }
2273
2274 #[test]
2275 fn illegal_domain_crossing_rejected() {
2276 let mut s = ElaborateSession::new("t");
2277 s.begin_module("Cdc", Span::default());
2278 s.add_input("clk", GroundType::Clock, Span::default());
2279 s.add_input("rst", GroundType::Reset, Span::default());
2280 s.add_input("a", GroundType::UInt { width: 8 }, Span::default());
2281 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2282 s.bind_domain("a", 0);
2283 s.bind_domain("y", 1);
2284 s.begin_combinational(Span::default());
2285 s.assign_net("y", "a", Span::default());
2286 s.end_process();
2287 s.end_module();
2288 let err = s.finish().unwrap_err();
2289 assert!(err.0.iter().any(|d| d.code == "rhdl::E0220"), "{err}");
2290 }
2291
2292 #[test]
2293 fn cdc_bridge_allows_crossing() {
2294 let mut s = ElaborateSession::new("t");
2295 s.begin_module("CdcOk", Span::default());
2296 s.add_input("clk", GroundType::Clock, Span::default());
2297 s.add_input("rst", GroundType::Reset, Span::default());
2298 s.add_input("a", GroundType::UInt { width: 8 }, Span::default());
2299 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2300 s.bind_domain("a", 0);
2301 s.bind_domain("y", 1);
2302 s.mark_cdc_bridge("y");
2303 s.begin_combinational(Span::default());
2304 s.assign_net("y", "a", Span::default());
2305 s.end_process();
2306 s.end_module();
2307 assert!(s.finish().is_ok());
2308 }
2309
2310 #[test]
2311 fn double_flop_stages_allow_reg_d_crossing() {
2312 let mut s = ElaborateSession::new("t");
2313 s.begin_module("Df", Span::default());
2314 s.add_input("clk", GroundType::Clock, Span::default());
2315 s.add_input("rst", GroundType::Reset, Span::default());
2316 s.add_input("din", GroundType::UInt { width: 1 }, Span::default());
2317 s.add_output("dout", GroundType::UInt { width: 1 }, Span::default());
2318 s.bind_domain("din", 0);
2319 s.bind_domain("dout", 1);
2320 let (ff0, ff1) =
2321 s.declare_double_flop_stages("sync", GroundType::UInt { width: 1 }, 1, Span::default());
2322 s.begin_combinational(Span::default());
2323 s.assign_net("dout", &ff1, Span::default());
2324 s.end_process();
2325 s.begin_sequential(Span::default());
2326 s.connect_double_flop(&ff0, &ff1, "din", Span::default());
2327 s.end_process();
2328 s.end_module();
2329 assert!(s.finish().is_ok());
2330 }
2331
2332 #[test]
2333 fn assign_reg_d_cross_domain_without_bridge_rejected() {
2334 let mut s = ElaborateSession::new("t");
2335 s.begin_module("Bad", Span::default());
2336 s.add_input("clk", GroundType::Clock, Span::default());
2337 s.add_input("rst", GroundType::Reset, Span::default());
2338 s.add_input("din", GroundType::UInt { width: 1 }, Span::default());
2339 s.declare_reg("q", GroundType::UInt { width: 1 }, Span::default());
2340 s.bind_domain("din", 0);
2341 s.bind_domain("q", 1);
2342 s.begin_sequential(Span::default());
2343 s.assign_reg_d_from("q", "din", Span::default());
2344 s.end_process();
2345 s.end_module();
2346 let err = s.finish().unwrap_err();
2347 assert!(err.0.iter().any(|d| d.code == "rhdl::E0220"), "{err}");
2348 }
2349
2350 #[test]
2351 fn unknown_parent_net_rejected() {
2352 let mut s = ElaborateSession::new("t");
2353 s.begin_module("Child", Span::default());
2354 s.add_input("clk", GroundType::Clock, Span::default());
2355 s.add_input("rst", GroundType::Reset, Span::default());
2356 s.add_input("x", GroundType::UInt { width: 8 }, Span::default());
2357 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2358 s.end_module();
2359 s.begin_module("Parent", Span::default());
2360 s.add_input("clk", GroundType::Clock, Span::default());
2361 s.add_input("rst", GroundType::Reset, Span::default());
2362 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
2363 s.add_instance(
2364 "u0",
2365 "Child",
2366 vec![
2367 ("clk".into(), "clk".into()),
2368 ("rst".into(), "rst".into()),
2369 ("x".into(), "no_such_net".into()),
2370 ("y".into(), "y".into()),
2371 ],
2372 vec![],
2373 Span::default(),
2374 );
2375 s.end_module();
2376 let err = s.finish().unwrap_err();
2377 assert!(err.0.iter().any(|d| d.code == "rhdl::E0204"), "{err}");
2378 }
2379
2380 #[test]
2381 fn hw_capture_wire_rejected_e0142() {
2382 let mut s = ElaborateSession::new("t");
2383 base_ports(&mut s);
2384 s.declare_wire("w", GroundType::UInt { width: 8 }, Span::default());
2385 s.assert_no_hw_capture(&[HwCaptureRef::wire("w")], Span::default());
2387 s.begin_combinational(Span::default());
2388 s.assign_net("data_out", "data_in", Span::default());
2389 s.end_process();
2390 s.end_module();
2391 let err = s.finish().unwrap_err();
2392 assert!(
2393 err.0.iter().any(|d| d.code == "rhdl::E0142"),
2394 "expected E0142, got {err}"
2395 );
2396 assert!(
2397 err.0
2398 .iter()
2399 .any(|d| d.en.contains("Wire") && d.en.contains("w")),
2400 "diagnostic should name Wire 'w': {err}"
2401 );
2402 }
2403
2404 #[test]
2405 fn hw_capture_reg_rejected_e0142() {
2406 let mut s = ElaborateSession::new("t");
2407 base_ports(&mut s);
2408 s.declare_reg("r", GroundType::UInt { width: 8 }, Span::default());
2409 s.reject_hw_capture(&HwCaptureRef::reg("r"), Span::default());
2410 s.begin_combinational(Span::default());
2411 s.assign_net("data_out", "data_in", Span::default());
2412 s.end_process();
2413 s.end_module();
2414 let err = s.finish().unwrap_err();
2415 assert!(
2416 err.0
2417 .iter()
2418 .any(|d| d.code == "rhdl::E0142" && d.en.contains("Reg")),
2419 "expected E0142 Reg, got {err}"
2420 );
2421 }
2422
2423 #[test]
2424 fn assert_no_hw_capture_empty_ok() {
2425 let mut s = ElaborateSession::new("t");
2426 base_ports(&mut s);
2427 s.assert_no_hw_capture(&[], Span::default());
2428 s.declare_mem_with_init_fn("rom", 2, 8, |i| i as u64, Span::default());
2429 s.begin_combinational(Span::default());
2430 s.assign_net("data_out", "data_in", Span::default());
2431 s.end_process();
2432 s.end_module();
2433 assert!(s.finish().is_ok());
2434 }
2435
2436 #[test]
2437 fn fr16_capturing_closure_still_e0141() {
2438 let mut s = ElaborateSession::new("t");
2439 base_ports(&mut s);
2440 s.reject_unsynthesizable("capturing closure", Span::default());
2441 s.begin_combinational(Span::default());
2442 s.assign_net("data_out", "data_in", Span::default());
2443 s.end_process();
2444 s.end_module();
2445 let err = s.finish().unwrap_err();
2446 assert!(
2447 err.0.iter().any(|d| d.code == "rhdl::E0141"),
2448 "FR16 capturing closure must stay E0141, got {err}"
2449 );
2450 }
2451
2452 #[test]
2453 fn synthesizable_closure_heap_e0143() {
2454 let mut s = ElaborateSession::new("t");
2455 base_ports(&mut s);
2456 s.reject_unsynthesizable_closure(
2457 &SynthesizableClosureViolation::heap("Box<u8> in body"),
2458 Span::default(),
2459 );
2460 s.begin_combinational(Span::default());
2461 s.assign_net("data_out", "data_in", Span::default());
2462 s.end_process();
2463 s.end_module();
2464 let err = s.finish().unwrap_err();
2465 assert!(
2466 err.0.iter().any(|d| d.code == "rhdl::E0143"),
2467 "expected E0143, got {err}"
2468 );
2469 }
2470
2471 #[test]
2472 fn synthesizable_closure_capture_state_e0144() {
2473 let mut s = ElaborateSession::new("t");
2474 base_ports(&mut s);
2475 s.check_synthesizable_closure(
2476 &[SynthesizableClosureViolation::runtime_capture_state(
2477 "captures local threshold",
2478 )],
2479 Span::default(),
2480 );
2481 s.begin_combinational(Span::default());
2482 s.assign_net("data_out", "data_in", Span::default());
2483 s.end_process();
2484 s.end_module();
2485 let err = s.finish().unwrap_err();
2486 assert!(
2487 err.0.iter().any(|d| d.code == "rhdl::E0144"),
2488 "expected E0144, got {err}"
2489 );
2490 }
2491
2492 #[test]
2493 fn synthesizable_closure_impure_e0145() {
2494 let mut s = ElaborateSession::new("t");
2495 base_ports(&mut s);
2496 s.reject_unsynthesizable_closure(
2497 &SynthesizableClosureViolation::impure("file I/O"),
2498 Span::default(),
2499 );
2500 s.begin_combinational(Span::default());
2501 s.assign_net("data_out", "data_in", Span::default());
2502 s.end_process();
2503 s.end_module();
2504 let err = s.finish().unwrap_err();
2505 assert!(
2506 err.0.iter().any(|d| d.code == "rhdl::E0145"),
2507 "expected E0145, got {err}"
2508 );
2509 }
2510
2511 #[test]
2512 fn legal_empty_and_simple_synthesizable_closure_pass() {
2513 let mut s = ElaborateSession::new("t");
2514 base_ports(&mut s);
2515 s.check_synthesizable_closure(&[], Span::default());
2516 s.check_synthesizable_closure_marker(&LegalEmptyClosure, Span::default());
2517 s.check_synthesizable_closure_marker(&LegalSimpleClosure, Span::default());
2518 s.begin_combinational(Span::default());
2519 s.assign_net("data_out", "data_in", Span::default());
2520 s.end_process();
2521 s.end_module();
2522 assert!(s.finish().is_ok(), "legal empty/simple must pass");
2523 }
2524
2525 #[test]
2526 fn diagnose_free_fn_cap_r60() {
2527 let diags = diagnose_synthesizable_closure_violations(
2528 &[SynthesizableClosureViolation::heap("String")],
2529 Span::default(),
2530 );
2531 assert!(diags.0.iter().any(|d| d.code == "rhdl::E0143"));
2532 }
2533
2534 #[test]
2535 fn inline_comb_fn_expands_to_ordinary_assign() {
2536 let mut s = ElaborateSession::new("t");
2537 base_ports(&mut s);
2538 s.add_input("b", GroundType::UInt { width: 8 }, Span::default());
2539 s.declare_wire("sum", GroundType::UInt { width: 8 }, Span::default());
2540 s.begin_combinational(Span::default());
2541 s.inline_comb_fn("sum", &["data_in", "b"], &[], Span::default(), |args| {
2542 CombInline::Add(args[0].into(), args[1].into())
2543 });
2544 s.inline_comb_fn_marker(
2545 "data_out",
2546 &["sum"],
2547 &LegalSimpleClosure,
2548 Span::default(),
2549 |args| CombInline::Ref(args[0].into()),
2550 );
2551 s.end_process();
2552 s.end_module();
2553 let hir = s.finish().expect("legal inline must finish");
2554 let body = &hir.circuit().modules[0].body;
2555 let procs: Vec<_> = body
2556 .iter()
2557 .filter_map(|st| match st {
2558 Stmt::Process(p) => Some(p),
2559 _ => None,
2560 })
2561 .collect();
2562 assert_eq!(procs.len(), 1);
2563 assert_eq!(procs[0].assigns.len(), 2);
2564 assert!(matches!(
2565 &procs[0].assigns[0].expr,
2566 AssignExpr::Add(l, r) if l == "data_in" && r == "b"
2567 ));
2568 assert!(matches!(
2569 &procs[0].assigns[1].expr,
2570 AssignExpr::Ref(n) if n == "sum"
2571 ));
2572 let dump = format!("{body:?}");
2574 assert!(!dump.contains("CombInline"));
2575 assert!(!dump.to_lowercase().contains("closure"));
2576 }
2577
2578 #[test]
2579 fn inline_comb_fn_violation_skips_expand() {
2580 let mut s = ElaborateSession::new("t");
2581 base_ports(&mut s);
2582 s.begin_combinational(Span::default());
2583 s.inline_comb_fn(
2584 "data_out",
2585 &["data_in"],
2586 &[SynthesizableClosureViolation::heap("Box in transform")],
2587 Span::default(),
2588 |_args| CombInline::Ref("data_in".into()),
2589 );
2590 s.end_process();
2591 s.end_module();
2592 let err = s.finish().expect_err("heap must fail");
2593 assert!(err.0.iter().any(|d| d.code == "rhdl::E0143"));
2594 }
2595
2596 #[test]
2597 fn inline_comb_fn_incomplete_branch_still_latch() {
2598 let mut s = ElaborateSession::new("t");
2599 base_ports(&mut s);
2600 s.begin_combinational(Span::default());
2601 s.begin_then(Span::default());
2602 s.inline_comb_fn("data_out", &["data_in"], &[], Span::default(), |args| {
2603 CombInline::Ref(args[0].into())
2604 });
2605 s.begin_else(Span::default());
2606 s.end_if(Span::default());
2608 s.end_process();
2609 s.end_module();
2610 let err = s.finish().unwrap_err();
2611 assert!(
2612 err.0.iter().any(|d| d.code == "rhdl::E0110"),
2613 "expected latch diagnostic after inline, got {err}"
2614 );
2615 }
2616
2617 #[test]
2618 fn inline_seq_fn_expands_to_ordinary_reg_d() {
2619 let mut s = ElaborateSession::new("t");
2620 base_ports(&mut s);
2621 s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
2622 s.begin_sequential(Span::default());
2623 s.inline_seq_fn("count", &[], &[], &[], Span::default(), |_args| {
2624 SeqInline::Inc
2625 });
2626 s.end_process();
2627 s.begin_combinational(Span::default());
2628 s.assign_net("data_out", "count", Span::default());
2629 s.end_process();
2630 s.end_module();
2631 let hir = s.finish().expect("legal seq inline must finish");
2632 let body = &hir.circuit().modules[0].body;
2633 let seq = body.iter().find_map(|st| match st {
2634 Stmt::Process(p) if matches!(p.kind, ProcessKind::Sequential) => Some(p),
2635 _ => None,
2636 });
2637 let seq = seq.expect("sequential process");
2638 assert_eq!(seq.assigns.len(), 1);
2639 assert!(matches!(
2640 &seq.assigns[0],
2641 Assign {
2642 target: AssignTarget::RegD(n),
2643 expr: AssignExpr::Inc(i),
2644 ..
2645 } if n == "count" && i == "count"
2646 ));
2647 let dump = format!("{body:?}");
2648 assert!(!dump.contains("SeqInline"));
2649 assert!(!dump.to_lowercase().contains("closure"));
2650 }
2651
2652 #[test]
2653 fn inline_seq_fn_cap_r70_blocks_second_reg_d() {
2654 let mut s = ElaborateSession::new("t");
2655 base_ports(&mut s);
2656 s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
2657 s.begin_sequential(Span::default());
2658 s.assign_reg_d_inc("count", Span::default());
2659 s.inline_seq_fn("count", &["data_in"], &[], &[], Span::default(), |args| {
2660 CombInline::Ref(args[0].into()).into()
2661 });
2662 s.end_process();
2663 s.begin_combinational(Span::default());
2664 s.assign_net("data_out", "count", Span::default());
2665 s.end_process();
2666 s.end_module();
2667 let err = s.finish().expect_err("second Reg.d must fail Cap-R-70");
2668 assert!(
2669 err.0.iter().any(|d| d.code == "rhdl::E0146"),
2670 "expected E0146, got {err}"
2671 );
2672 }
2673
2674 #[test]
2675 fn inline_seq_fn_ownership_token_skips_expand() {
2676 let mut s = ElaborateSession::new("t");
2677 base_ports(&mut s);
2678 s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
2679 s.begin_sequential(Span::default());
2680 s.inline_seq_fn(
2681 "count",
2682 &[],
2683 &[],
2684 &[SeqOwnershipViolation::illegal_mutable_borrow(
2685 "&mut count captured",
2686 )],
2687 Span::default(),
2688 |_args| SeqInline::Inc,
2689 );
2690 s.end_process();
2691 s.begin_combinational(Span::default());
2692 s.assign_net("data_out", "count", Span::default());
2693 s.end_process();
2694 s.end_module();
2695 let err = s.finish().expect_err("ownership token must fail");
2696 assert!(err.0.iter().any(|d| d.code == "rhdl::E0146"));
2697 }
2698
2699 #[test]
2700 fn inline_seq_fn_multi_drive_still_e0140() {
2701 let mut s = ElaborateSession::new("t");
2702 base_ports(&mut s);
2703 s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
2704 s.begin_sequential(Span::default());
2705 s.inline_seq_fn("count", &[], &[], &[], Span::default(), |_args| {
2706 SeqInline::Inc
2707 });
2708 s.end_process();
2709 s.begin_sequential(Span::default());
2710 s.assign_reg_d_from("count", "data_in", Span::default());
2711 s.end_process();
2712 s.begin_combinational(Span::default());
2713 s.assign_net("data_out", "count", Span::default());
2714 s.end_process();
2715 s.end_module();
2716 let err = s.finish().expect_err("cross-process multi-drive");
2717 assert!(
2718 err.0.iter().any(|d| d.code == "rhdl::E0140"),
2719 "expected E0140, got {err}"
2720 );
2721 }
2722
2723 #[test]
2724 fn diagnose_seq_ownership_free_fn() {
2725 let diags = diagnose_seq_ownership_violations(
2726 &[SeqOwnershipViolation::illegal_mutable_borrow("x")],
2727 Span::default(),
2728 );
2729 assert!(diags.0.iter().any(|d| d.code == "rhdl::E0146"));
2730 }
2731}