calyx_opt/passes/
synthesis_papercut.rs

1use 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/// Pass to check common synthesis issues.
14/// 1. If a memory is only read-from or written-to, synthesis tools will optimize it away. Add
15///    @external attribute to the cell definition to make it an interface memory.
16#[derive(Debug)]
17pub struct SynthesisPapercut {
18    /// Names of memory primitives
19    memories: HashSet<ir::Id>,
20    /// Diagnostic context for reporting multiple errors
21    diag: DiagnosticContext,
22}
23
24// impl Default for SynthesisPapercut {
25//     fn default() -> Self {
26//     }
27// }
28
29impl 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        // Get all the memory cells.
78        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        // Early return if there are no memory cells.
97        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        // we don't need to traverse the rest of the component
129        Ok(Action::Stop)
130    }
131}