calyx_opt/passes/
synthesis_papercut.rs1use crate::analysis::GraphAnalysis;
2use crate::traversal::{
3 Action, ConstructVisitor, DiagnosticContext, DiagnosticPass, Named,
4 VisResult, Visitor,
5};
6use calyx_ir::{self as ir, LibrarySignatures};
7use calyx_utils::{CalyxResult, Error};
8use std::collections::HashSet;
9
10const READ_PORT: &str = "read_data";
11const WRITE_PORT: &str = "write_data";
12
13#[derive(Debug)]
17pub struct SynthesisPapercut {
18 memories: HashSet<ir::Id>,
20 diag: DiagnosticContext,
22}
23
24impl SynthesisPapercut {
30 fn default_memories() -> impl Iterator<Item = ir::Id> {
31 ["comb_mem_d1", "comb_mem_d2", "comb_mem_d3", "comb_mem_d4"]
32 .iter()
33 .map(|&mem| mem.into())
34 }
35}
36
37impl ConstructVisitor for SynthesisPapercut {
38 fn from(_ctx: &ir::Context) -> CalyxResult<Self>
39 where
40 Self: Sized,
41 {
42 let memories = Self::default_memories().collect();
43 Ok(SynthesisPapercut {
44 memories,
45 diag: DiagnosticContext::default(),
46 })
47 }
48
49 fn clear_data(&mut self) {
50 self.memories = Self::default_memories().collect();
51 }
52}
53
54impl Named for SynthesisPapercut {
55 fn name() -> &'static str {
56 "synthesis-papercut"
57 }
58
59 fn description() -> &'static str {
60 "Detect common problems when targeting synthesis backends"
61 }
62}
63
64impl DiagnosticPass for SynthesisPapercut {
65 fn diagnostics(&self) -> &DiagnosticContext {
66 &self.diag
67 }
68}
69
70impl Visitor for SynthesisPapercut {
71 fn start(
72 &mut self,
73 comp: &mut ir::Component,
74 _ctx: &LibrarySignatures,
75 _comps: &[ir::Component],
76 ) -> VisResult {
77 let memory_cells = comp
79 .cells
80 .iter()
81 .filter_map(|cell| {
82 let cell = &cell.borrow();
83 if let Some(ref parent) = cell.type_name() {
84 if self.memories.contains(parent) {
85 let has_external =
86 cell.get_attribute(ir::BoolAttr::External);
87 if has_external.is_none() && !cell.is_reference() {
88 return Some(cell.name());
89 }
90 }
91 }
92 None
93 })
94 .collect::<HashSet<_>>();
95
96 if memory_cells.is_empty() {
98 return Ok(Action::Stop);
99 }
100
101 let has_mem_parent =
102 |p: &ir::Port| memory_cells.contains(&p.get_parent_name());
103 let analysis =
104 GraphAnalysis::from(&*comp).edge_induced_subgraph(|p1, p2| {
105 has_mem_parent(p1) || has_mem_parent(p2)
106 });
107
108 for mem in memory_cells {
109 let cell = comp.find_cell(mem).unwrap();
110 let read_port = cell.borrow().get(READ_PORT);
111 if analysis.reads_from(&read_port.borrow()).next().is_none() {
112 self.diag.err(Error::papercut(
113 format!(
114 "Only writes performed on memory `{mem}'. Synthesis tools will remove this memory. Add @external to cell to turn this into an interface memory.",
115 ),
116 ).with_pos(&cell.borrow().attributes));
117 }
118 let write_port = cell.borrow().get(WRITE_PORT);
119 if analysis.writes_to(&write_port.borrow()).next().is_none() {
120 self.diag.err(Error::papercut(
121 format!(
122 "Only reads performed on memory `{mem}'. Synthesis tools will remove this memory. Add @external to cell to turn this into an interface memory.",
123 ),
124 ).with_pos(&cell.borrow().attributes));
125 }
126 }
127
128 Ok(Action::Stop)
130 }
131}