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