1use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub struct Span {
9 pub start: u32,
10 pub end: u32,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Diagnostic {
15 pub span: Span,
16 pub code: String,
17 pub en: String,
18 pub zh: String,
19}
20
21impl fmt::Display for Diagnostic {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "{}: {} ({})", self.code, self.en, self.zh)
24 }
25}
26
27#[derive(Debug, Clone, Default, PartialEq, Eq)]
28pub struct Diagnostics(pub Vec<Diagnostic>);
29
30impl Diagnostics {
31 pub fn push(&mut self, d: Diagnostic) {
32 self.0.push(d);
33 }
34
35 pub fn is_empty(&self) -> bool {
36 self.0.is_empty()
37 }
38}
39
40impl fmt::Display for Diagnostics {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 for d in &self.0 {
43 writeln!(f, "{d}")?;
44 }
45 Ok(())
46 }
47}
48
49impl std::error::Error for Diagnostics {}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum GroundType {
54 UInt {
55 width: u32,
56 },
57 SInt {
58 width: u32,
59 },
60 Clock,
61 Reset,
62 Bool,
63 Analog,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum PortDirection {
69 Input,
70 Output,
71 InOut,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct Port {
77 pub name: String,
78 pub direction: PortDirection,
79 pub ty: GroundType,
80 pub span: Span,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum ProcessKind {
85 Combinational,
86 Sequential,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum SignalKind {
91 Wire,
92 Reg,
93 Output,
94 Input,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum AssignTarget {
100 Net(String),
102 RegD(String),
104 MemWrite {
107 mem: String,
108 addr: String,
109 we: Option<String>,
110 },
111}
112
113#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum AssignExpr {
116 Ref(String),
118 Lit(u64),
120 Inc(String),
122 Add(String, String),
124 Sub(String, String),
126 And(String, String),
128 Or(String, String),
130 Xor(String, String),
132 Shl(String, String),
134 Shr(String, String),
136 Eq(String, String),
138 Mux { sel: String, t: String, f: String },
140 MemRead { mem: String, addr: String },
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct Assign {
146 pub target: AssignTarget,
147 pub expr: AssignExpr,
148 pub span: Span,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct Process {
153 pub kind: ProcessKind,
154 pub assigns: Vec<Assign>,
155 pub span: Span,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct PortConnect {
160 pub child_port: String,
161 pub parent_net: String,
162 pub span: Span,
163 pub dangling: bool,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub struct Instance {
169 pub name: String,
170 pub module: String,
171 pub connects: Vec<PortConnect>,
172 pub params: Vec<(String, u32)>,
173 pub span: Span,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub enum Stmt {
178 WireDecl {
179 name: String,
180 ty: GroundType,
181 span: Span,
182 },
183 RegDecl {
184 name: String,
185 ty: GroundType,
186 clock: String,
188 reset: String,
190 async_reset: bool,
192 has_enable: bool,
194 span: Span,
195 },
196 Process(Process),
197 Instance(Instance),
198 MemDecl {
200 name: String,
201 depth: u32,
202 width: u32,
203 sync_read: bool,
205 init: Option<Vec<u64>>,
208 span: Span,
209 },
210}
211
212#[derive(Debug, Clone, PartialEq, Eq)]
214pub enum Expr {
215 Add { width: u32, span: Span },
217 Connect { width: u32, span: Span },
219 Pad {
220 from_width: u32,
221 to_width: u32,
222 span: Span,
223 },
224 Trunc {
225 from_width: u32,
226 to_width: u32,
227 span: Span,
228 },
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct Module {
233 pub name: String,
234 pub ports: Vec<Port>,
235 pub body: Vec<Stmt>,
236 pub span: Span,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct Circuit {
241 pub name: String,
242 pub modules: Vec<Module>,
243}
244
245#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct FrozenHir {
248 circuit: Circuit,
249 pub abi_name: String,
250}
251
252impl FrozenHir {
253 pub fn circuit(&self) -> &Circuit {
254 &self.circuit
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Eq)]
260pub struct EmittedFile {
261 pub path: String,
262 pub contents: String,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq)]
267pub struct Artifact {
268 pub files: Vec<EmittedFile>,
269 pub filelist: Vec<String>,
270}
271
272#[derive(Debug, Clone, Default, PartialEq, Eq)]
274pub struct PortValues {
275 pub values: std::collections::BTreeMap<String, u64>,
276}
277
278impl PortValues {
279 pub fn get(&self, name: &str) -> Option<u64> {
280 self.values.get(name).copied()
281 }
282
283 pub fn set(&mut self, name: impl Into<String>, value: u64) {
284 self.values.insert(name.into(), value);
285 }
286}
287
288#[derive(Debug)]
290pub(crate) struct Hir {
291 pub circuit: Circuit,
292}
293
294impl Hir {
295 pub(crate) fn new(name: impl Into<String>) -> Self {
296 Self {
297 circuit: Circuit {
298 name: name.into(),
299 modules: Vec::new(),
300 },
301 }
302 }
303}
304
305pub(crate) fn freeze(hir: Hir) -> Result<FrozenHir, Diagnostics> {
307 if hir.circuit.modules.is_empty() {
308 return Err(Diagnostics(vec![Diagnostic {
309 span: Span::default(),
310 code: "rhdl::E0001".into(),
311 en: "circuit has no modules".into(),
312 zh: "电路没有任何模块".into(),
313 }]));
314 }
315 let mut diags = Diagnostics::default();
316 for m in &hir.circuit.modules {
317 validate_clock_reset(m, &mut diags);
318 validate_unique_drivers(m, &mut diags);
319 validate_instances(&hir.circuit, m, &mut diags);
320 validate_special_io(&hir.circuit, m, &mut diags);
321 }
322 if !diags.is_empty() {
323 return Err(diags);
324 }
325 let top = hir
326 .circuit
327 .modules
328 .first()
329 .map(|m| m.name.clone())
330 .unwrap_or_else(|| hir.circuit.name.clone());
331 Ok(FrozenHir {
332 circuit: hir.circuit,
333 abi_name: top,
334 })
335}
336
337fn validate_special_io(circuit: &Circuit, m: &Module, diags: &mut Diagnostics) {
338 let is_top = m.name == circuit.name;
339 for p in &m.ports {
340 let special =
341 matches!(p.direction, PortDirection::InOut) || matches!(p.ty, GroundType::Analog);
342 if special && !is_top {
343 diags.push(Diagnostic {
344 span: p.span,
345 code: "rhdl::E0270".into(),
346 en: format!(
347 "Analog/InOut port `{}` only allowed on top module `{}`",
348 p.name, circuit.name
349 ),
350 zh: format!(
351 "Analog/InOut 端口 `{}` 仅允许在顶层模块 `{}`",
352 p.name, circuit.name
353 ),
354 });
355 }
356 }
357}
358
359fn validate_clock_reset(m: &Module, diags: &mut Diagnostics) {
360 let clocks: Vec<_> = m
361 .ports
362 .iter()
363 .filter(|p| p.direction == PortDirection::Input && matches!(p.ty, GroundType::Clock))
364 .collect();
365 let resets: Vec<_> = m
366 .ports
367 .iter()
368 .filter(|p| p.direction == PortDirection::Input && matches!(p.ty, GroundType::Reset))
369 .collect();
370 if clocks.len() != 1 {
371 diags.push(Diagnostic {
372 span: m.span,
373 code: "rhdl::E0120".into(),
374 en: format!(
375 "module '{}' must have exactly one Clock input (found {})",
376 m.name,
377 clocks.len()
378 ),
379 zh: format!(
380 "模块 '{}' 必须恰好有一个 Clock 输入(找到 {} 个)",
381 m.name,
382 clocks.len()
383 ),
384 });
385 }
386 if resets.len() != 1 {
387 diags.push(Diagnostic {
388 span: m.span,
389 code: "rhdl::E0121".into(),
390 en: format!(
391 "module '{}' must have exactly one Reset input (found {})",
392 m.name,
393 resets.len()
394 ),
395 zh: format!(
396 "模块 '{}' 必须恰好有一个 Reset 输入(找到 {} 个)",
397 m.name,
398 resets.len()
399 ),
400 });
401 }
402 if clocks.len() == 1 && resets.len() == 1 {
403 let clk = &clocks[0].name;
404 let rst = &resets[0].name;
405 for stmt in &m.body {
406 if let Stmt::RegDecl {
407 name,
408 clock,
409 reset,
410 span,
411 ..
412 } = stmt
413 {
414 if clock != clk {
415 diags.push(Diagnostic {
416 span: *span,
417 code: "rhdl::E0122".into(),
418 en: format!(
419 "Reg '{name}' must bind to module Clock '{clk}', not '{clock}'"
420 ),
421 zh: format!("寄存器 '{name}' 必须绑定模块时钟 '{clk}',而不是 '{clock}'"),
422 });
423 }
424 if reset != rst {
425 diags.push(Diagnostic {
426 span: *span,
427 code: "rhdl::E0123".into(),
428 en: format!(
429 "Reg '{name}' must bind to module Reset '{rst}', not '{reset}'"
430 ),
431 zh: format!("寄存器 '{name}' 必须绑定模块复位 '{rst}',而不是 '{reset}'"),
432 });
433 }
434 }
435 }
436 }
437}
438
439fn validate_unique_drivers(m: &Module, diags: &mut Diagnostics) {
440 use std::collections::HashMap;
441 let mut drivers: HashMap<String, Vec<Span>> = HashMap::new();
443 for stmt in &m.body {
444 if let Stmt::Process(p) = stmt {
445 let mut seen_in_process = std::collections::HashSet::new();
446 for a in &p.assigns {
447 let key = match &a.target {
448 AssignTarget::Net(n) => n.clone(),
449 AssignTarget::RegD(n) => format!("{n}.d"),
450 AssignTarget::MemWrite { mem, addr, we } => match we {
451 Some(en) => format!("{mem}[{addr}] (we={en})"),
452 None => format!("{mem}[{addr}]"),
453 },
454 };
455 if seen_in_process.insert(key.clone()) {
456 drivers.entry(key).or_default().push(a.span);
457 }
458 }
459 }
460 }
461 for (net, spans) in drivers {
462 if spans.len() > 1 {
463 diags.push(Diagnostic {
464 span: spans[1],
465 code: "rhdl::E0140".into(),
466 en: format!("multiple drivers for '{net}' ({} drivers)", spans.len()),
467 zh: format!("'{net}' 有多个驱动({} 个)", spans.len()),
468 });
469 }
470 }
471}
472
473fn validate_instances(circuit: &Circuit, parent: &Module, diags: &mut Diagnostics) {
474 use std::collections::HashMap;
475 let modules: HashMap<&str, &Module> = circuit
476 .modules
477 .iter()
478 .map(|m| (m.name.as_str(), m))
479 .collect();
480 for stmt in &parent.body {
481 let Stmt::Instance(inst) = stmt else {
482 continue;
483 };
484 let Some(child) = modules.get(inst.module.as_str()) else {
485 diags.push(Diagnostic {
486 span: inst.span,
487 code: "rhdl::E0201".into(),
488 en: format!("unknown child module '{}'", inst.module),
489 zh: format!("未知子模块 '{}'", inst.module),
490 });
491 continue;
492 };
493 let connected: HashMap<&str, &PortConnect> = inst
494 .connects
495 .iter()
496 .map(|c| (c.child_port.as_str(), c))
497 .collect();
498 for port in &child.ports {
499 match connected.get(port.name.as_str()) {
500 None if port.direction == PortDirection::Input => {
501 diags.push(Diagnostic {
502 span: inst.span,
503 code: "rhdl::E0202".into(),
504 en: format!(
505 "undriven child input '{}.{}' (mark dangling if intentional)",
506 inst.name, port.name
507 ),
508 zh: format!(
509 "子模块输入 '{}.{}' 未驱动(若故意悬空请标记 dangling)",
510 inst.name, port.name
511 ),
512 });
513 }
514 Some(c) if c.dangling && port.direction == PortDirection::Input => {}
515 Some(c) => {
516 let parent_ty = parent
518 .ports
519 .iter()
520 .find(|p| p.name == c.parent_net)
521 .map(|p| &p.ty)
522 .or_else(|| {
523 parent.body.iter().find_map(|s| match s {
524 Stmt::WireDecl { name, ty, .. }
525 | Stmt::RegDecl { name, ty, .. }
526 if name == &c.parent_net =>
527 {
528 Some(ty)
529 }
530 _ => None,
531 })
532 });
533 if let Some(pty) = parent_ty {
534 let pw = width_of(pty);
535 let cw = width_of(&port.ty);
536 if pw != cw {
537 diags.push(Diagnostic {
538 span: c.span,
539 code: "rhdl::E0203".into(),
540 en: format!(
541 "width mismatch connecting '{}' (parent {pw}) to '{}.{}' (child {cw})",
542 c.parent_net, inst.name, port.name
543 ),
544 zh: format!(
545 "连接位宽不匹配:'{}'(父 {pw})→ '{}.{}'(子 {cw})",
546 c.parent_net, inst.name, port.name
547 ),
548 });
549 }
550 } else if !c.dangling {
551 diags.push(Diagnostic {
552 span: c.span,
553 code: "rhdl::E0204".into(),
554 en: format!(
555 "cannot resolve parent net '{}' when connecting to '{}.{}'",
556 c.parent_net, inst.name, port.name
557 ),
558 zh: format!(
559 "连接 '{}.{}' 时无法解析父网 '{}'",
560 inst.name, port.name, c.parent_net
561 ),
562 });
563 }
564 }
565 None => {}
566 }
567 }
568 }
569}
570
571fn width_of(ty: &GroundType) -> u32 {
572 match ty {
573 GroundType::UInt { width } | GroundType::SInt { width } => *width,
574 GroundType::Clock | GroundType::Reset | GroundType::Bool | GroundType::Analog => 1,
575 }
576}
577
578pub fn seal_from_builder(hir: BuilderOwnedHir) -> Result<FrozenHir, Diagnostics> {
580 freeze(hir.0)
581}
582
583pub struct BuilderOwnedHir(pub(crate) Hir);
585
586impl BuilderOwnedHir {
587 pub fn new(name: impl Into<String>) -> Self {
588 Self(Hir::new(name))
589 }
590
591 pub fn add_module(&mut self, module: Module) {
592 self.0.circuit.modules.push(module);
593 }
594
595 pub fn circuit_mut(&mut self) -> &mut Circuit {
596 &mut self.0.circuit
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603
604 #[test]
605 fn freeze_requires_module() {
606 let hir = BuilderOwnedHir::new("empty");
607 assert!(seal_from_builder(hir).is_err());
608 }
609
610 #[test]
611 fn freeze_simple_module() {
612 let mut hir = BuilderOwnedHir::new("Top");
613 hir.add_module(Module {
614 name: "Top".into(),
615 ports: vec![
616 Port {
617 name: "clk".into(),
618 direction: PortDirection::Input,
619 ty: GroundType::Clock,
620 span: Span::default(),
621 },
622 Port {
623 name: "rst".into(),
624 direction: PortDirection::Input,
625 ty: GroundType::Reset,
626 span: Span::default(),
627 },
628 Port {
629 name: "data_in".into(),
630 direction: PortDirection::Input,
631 ty: GroundType::UInt { width: 8 },
632 span: Span::default(),
633 },
634 Port {
635 name: "data_out".into(),
636 direction: PortDirection::Output,
637 ty: GroundType::UInt { width: 8 },
638 span: Span::default(),
639 },
640 ],
641 body: vec![],
642 span: Span::default(),
643 });
644 let frozen = seal_from_builder(hir).unwrap();
645 assert_eq!(frozen.abi_name, "Top");
646 assert_eq!(frozen.circuit().modules[0].ports.len(), 4);
647 }
648
649 #[test]
650 fn analog_on_top_ok() {
651 let mut hir = BuilderOwnedHir::new("PadTop");
652 hir.add_module(Module {
653 name: "PadTop".into(),
654 ports: vec![
655 Port {
656 name: "clk".into(),
657 direction: PortDirection::Input,
658 ty: GroundType::Clock,
659 span: Span::default(),
660 },
661 Port {
662 name: "rst".into(),
663 direction: PortDirection::Input,
664 ty: GroundType::Reset,
665 span: Span::default(),
666 },
667 Port {
668 name: "pad".into(),
669 direction: PortDirection::InOut,
670 ty: GroundType::Analog,
671 span: Span::default(),
672 },
673 ],
674 body: vec![],
675 span: Span::default(),
676 });
677 assert!(seal_from_builder(hir).is_ok());
678 }
679
680 #[test]
681 fn analog_on_non_top_rejected() {
682 let mut hir = BuilderOwnedHir::new("Top");
683 hir.add_module(Module {
684 name: "Child".into(),
685 ports: vec![
686 Port {
687 name: "clk".into(),
688 direction: PortDirection::Input,
689 ty: GroundType::Clock,
690 span: Span::default(),
691 },
692 Port {
693 name: "rst".into(),
694 direction: PortDirection::Input,
695 ty: GroundType::Reset,
696 span: Span::default(),
697 },
698 Port {
699 name: "pad".into(),
700 direction: PortDirection::InOut,
701 ty: GroundType::Analog,
702 span: Span::default(),
703 },
704 ],
705 body: vec![],
706 span: Span::default(),
707 });
708 let err = seal_from_builder(hir).unwrap_err();
709 assert!(err.0.iter().any(|d| d.code == "rhdl::E0270"));
710 }
711}