calyx_opt/passes/
synthesis_papercut.rs

1use crate::analysis::GraphAnalysis;
2use crate::traversal::{Action, Named, VisResult, Visitor};
3use calyx_ir::{self as ir, LibrarySignatures};
4use calyx_utils::Error;
5use std::collections::HashSet;
6
7const READ_PORT: &str = "read_data";
8const WRITE_PORT: &str = "write_data";
9
10/// Pass to check common synthesis issues.
11/// 1. If a memory is only read-from or written-to, synthesis tools will optimize it away. Add
12///    @external attribute to the cell definition to make it an interface memory.
13pub struct SynthesisPapercut {
14    /// Names of memory primitives
15    memories: HashSet<ir::Id>,
16}
17
18impl Default for SynthesisPapercut {
19    fn default() -> Self {
20        let memories =
21            ["comb_mem_d1", "comb_mem_d2", "comb_mem_d3", "comb_mem_d4"]
22                .iter()
23                .map(|&mem| mem.into())
24                .collect();
25        SynthesisPapercut { memories }
26    }
27}
28
29impl Named for SynthesisPapercut {
30    fn name() -> &'static str {
31        "synthesis-papercut"
32    }
33
34    fn description() -> &'static str {
35        "Detect common problems when targeting synthesis backends"
36    }
37}
38
39impl Visitor for SynthesisPapercut {
40    fn start(
41        &mut self,
42        comp: &mut ir::Component,
43        _ctx: &LibrarySignatures,
44        _comps: &[ir::Component],
45    ) -> VisResult {
46        // Get all the memory cells.
47        let memory_cells = comp
48            .cells
49            .iter()
50            .filter_map(|cell| {
51                let cell = &cell.borrow();
52                if let Some(ref parent) = cell.type_name() {
53                    if self.memories.contains(parent) {
54                        let has_external =
55                            cell.get_attribute(ir::BoolAttr::External);
56                        if has_external.is_none() {
57                            return Some(cell.name());
58                        }
59                    }
60                }
61                None
62            })
63            .collect::<HashSet<_>>();
64
65        // Early return if there are no memory cells.
66        if memory_cells.is_empty() {
67            return Ok(Action::Stop);
68        }
69
70        let has_mem_parent =
71            |p: &ir::Port| memory_cells.contains(&p.get_parent_name());
72        let analysis =
73            GraphAnalysis::from(&*comp).edge_induced_subgraph(|p1, p2| {
74                has_mem_parent(p1) || has_mem_parent(p2)
75            });
76
77        for mem in memory_cells {
78            let cell = comp.find_cell(mem).unwrap();
79            let read_port = cell.borrow().get(READ_PORT);
80            if analysis.reads_from(&read_port.borrow()).next().is_none() {
81                return Err(Error::papercut(
82                    format!(
83                        "Only writes performed on memory `{mem}'. Synthesis tools will remove this memory. Add @external to cell to turn this into an interface memory.",
84                    ),
85                ));
86            }
87            let write_port = cell.borrow().get(WRITE_PORT);
88            if analysis.writes_to(&write_port.borrow()).next().is_none() {
89                return Err(Error::papercut(
90                    format!(
91                        "Only reads performed on memory `{mem}'. Synthesis tools will remove this memory. Add @external to cell to turn this into an interface memory.",
92                    ),
93                ));
94            }
95        }
96        Ok(Action::Stop)
97    }
98}