calyx_backend/
firrtl.rs

1//! FIRRTL backend for the Calyx compiler.
2//!
3//! Transforms an [`ir::Context`](crate::ir::Context) into a formatted string that represents a
4//! valid FIRRTL program.
5
6use crate::{traits::Backend, VerilogBackend};
7use calyx_ir::{self as ir, Binding, RRC};
8use calyx_utils::{CalyxResult, Id, OutputFile};
9use ir::Port;
10use std::collections::HashSet;
11use std::io;
12
13pub(super) const SPACING: &str = "    ";
14
15/// Implements a simple FIRRTL backend. The backend only accepts Calyx programs with no control
16/// and no groups.
17#[derive(Default)]
18pub struct FirrtlBackend;
19
20impl Backend for FirrtlBackend {
21    fn name(&self) -> &'static str {
22        "firrtl"
23    }
24
25    fn link_externs(
26        _prog: &calyx_ir::Context,
27        _write: &mut calyx_utils::OutputFile,
28    ) -> calyx_utils::CalyxResult<()> {
29        Ok(()) // FIXME: Need to implement
30    }
31
32    fn validate(prog: &calyx_ir::Context) -> calyx_utils::CalyxResult<()> {
33        VerilogBackend::validate(prog) // FIXME: would this work if we wanted to check for the same things?
34    }
35
36    fn emit(ctx: &ir::Context, file: &mut OutputFile) -> CalyxResult<()> {
37        let out = &mut file.get_write();
38        writeln!(out, "circuit {}:", ctx.entrypoint)?;
39        if ctx.bc.emit_primitive_extmodules {
40            emit_extmodules(ctx, out)?;
41        }
42        for comp in ctx.components.iter() {
43            emit_component(comp, out)?
44        }
45        Ok(())
46    }
47}
48
49fn emit_extmodules<F: io::Write>(
50    ctx: &ir::Context,
51    out: &mut F,
52) -> Result<(), calyx_utils::Error> {
53    let mut extmodule_set: HashSet<String> = HashSet::new();
54    for comp in &ctx.components {
55        for cell in comp.cells.iter() {
56            let cell_borrowed = cell.as_ref().borrow();
57            if let ir::CellType::Primitive {
58                name,
59                param_binding,
60                ..
61            } = &cell_borrowed.prototype
62            {
63                let curr_module_name =
64                    get_primitive_module_name(name, param_binding);
65                if extmodule_set.insert(curr_module_name.clone()) {
66                    emit_primitive_extmodule(
67                        cell.borrow().ports(),
68                        &curr_module_name,
69                        name,
70                        param_binding,
71                        out,
72                    )?;
73                }
74            };
75        }
76    }
77    Ok(())
78}
79
80// TODO: Ask about the other backend configurations in verilog.rs and see if I need any of it
81fn emit_component<F: io::Write>(
82    comp: &ir::Component,
83    f: &mut F,
84) -> io::Result<()> {
85    writeln!(f, "{}module {}:", SPACING, comp.name)?;
86
87    // Inputs and Outputs
88    let sig = comp.signature.borrow();
89    for port_ref in &sig.ports {
90        let port = port_ref.borrow();
91        emit_port(port, true, f)?;
92    }
93
94    // Add a COMPONENT START: <name> anchor before any code in the component
95    writeln!(f, "{}; COMPONENT START: {}", SPACING.repeat(2), comp.name)?;
96
97    // Cells
98    for cell in comp.cells.iter() {
99        let cell_borrowed = cell.as_ref().borrow();
100        if cell_borrowed.type_name().is_some() {
101            let module_name = match &cell_borrowed.prototype {
102                ir::CellType::Primitive {
103                    name,
104                    param_binding,
105                    is_comb: _,
106                    latency: _,
107                } => get_primitive_module_name(name, param_binding),
108                ir::CellType::Component { name } => name.to_string(),
109                _ => unreachable!(),
110            };
111            writeln!(
112                f,
113                "{}inst {} of {}",
114                SPACING.repeat(2),
115                cell_borrowed.name(),
116                module_name
117            )?;
118        }
119    }
120
121    let mut dst_set: HashSet<ir::Canonical> = HashSet::new();
122    // Emit assignments
123    for asgn in &comp.continuous_assignments {
124        match asgn.guard.as_ref() {
125            ir::Guard::True => {
126                // Simple assignment with no guard
127                let _ = write_assignment(asgn, f, 2);
128            }
129            _ => {
130                let dst_canonical = asgn.dst.as_ref().borrow().canonical();
131                if !dst_set.contains(&dst_canonical) {
132                    // if we don't have a "is invalid" statement yet, then we have to write one.
133                    // an alternative "eager" approach would be to instantiate all possible ports
134                    // (our output ports + all children's input ports) up front.
135                    write_invalid_initialization(&asgn.dst, f)?;
136                    dst_set.insert(dst_canonical);
137                }
138                // need to write out the guard.
139                let guard_string = get_guard_string(asgn.guard.as_ref());
140                writeln!(f, "{}when {}:", SPACING.repeat(2), guard_string)?;
141                write_assignment(asgn, f, 3)?;
142            }
143        }
144    }
145
146    // Add COMPONENT END: <name> anchor
147    writeln!(f, "{}; COMPONENT END: {}\n", SPACING.repeat(2), comp.name)?;
148
149    Ok(())
150}
151
152// creates a FIRRTL module name given the internal information of a primitive.
153fn get_primitive_module_name(name: &Id, param_binding: &Binding) -> String {
154    let mut primitive_string = name.to_string();
155    for (_, size) in param_binding.as_ref().iter() {
156        primitive_string.push('_');
157        primitive_string.push_str(&size.to_string());
158    }
159    primitive_string
160}
161
162fn emit_primitive_extmodule<F: io::Write>(
163    ports: &[RRC<Port>],
164    curr_module_name: &String,
165    name: &Id,
166    param_binding: &Binding,
167    f: &mut F,
168) -> io::Result<()> {
169    writeln!(f, "{}extmodule {}:", SPACING, curr_module_name)?;
170    for port in ports {
171        let port_borrowed = port.borrow();
172        emit_port(port_borrowed, false, f)?;
173    }
174    writeln!(f, "{}defname = {}", SPACING.repeat(2), name)?;
175    for (id, size) in param_binding.as_ref().iter() {
176        writeln!(f, "{}parameter {} = {}", SPACING.repeat(2), id, size)?;
177    }
178    writeln!(f)?;
179    Ok(())
180}
181
182fn emit_port<F: io::Write>(
183    port: std::cell::Ref<'_, Port>,
184    reverse_direction: bool,
185    f: &mut F,
186) -> Result<(), io::Error> {
187    let direction_string = match port.direction {
188        calyx_frontend::Direction::Input => {
189            if reverse_direction {
190                "output"
191            } else {
192                "input"
193            }
194        }
195        calyx_frontend::Direction::Output => {
196            if reverse_direction {
197                "input"
198            } else {
199                "output"
200            }
201        }
202        calyx_frontend::Direction::Inout => {
203            panic!("Unexpected Inout port on Component: {}", port.name)
204        }
205    };
206    if port.has_attribute(ir::BoolAttr::Clk) {
207        writeln!(
208            f,
209            "{}{} {}: Clock",
210            SPACING.repeat(2),
211            direction_string,
212            port.name
213        )?;
214    } else {
215        writeln!(
216            f,
217            "{}{} {}: UInt<{}>",
218            SPACING.repeat(2),
219            direction_string,
220            port.name,
221            port.width
222        )?;
223    };
224    Ok(())
225}
226
227// fn create_primitive_extmodule() {}
228
229// recursive function that writes the FIRRTL representation for a guard.
230fn get_guard_string(guard: &ir::Guard<ir::Nothing>) -> String {
231    match guard {
232        ir::Guard::Or(l, r) => {
233            let l_str = get_guard_string(l.as_ref());
234            let r_str = get_guard_string(r.as_ref());
235            format!("or({}, {})", l_str, r_str)
236        }
237        ir::Guard::And(l, r) => {
238            let l_str = get_guard_string(l.as_ref());
239            let r_str = get_guard_string(r.as_ref());
240            format!("and({}, {})", l_str, r_str)
241        }
242        ir::Guard::Not(g) => {
243            let g_str = get_guard_string(g);
244            format!("not({})", g_str)
245        }
246        ir::Guard::True => String::from(""),
247        ir::Guard::CompOp(op, l, r) => {
248            let l_str = get_port_string(&l.borrow(), false);
249            let r_str = get_port_string(&r.borrow(), false);
250            let op_str = match op {
251                ir::PortComp::Eq => "eq",
252                ir::PortComp::Neq => "neq",
253                ir::PortComp::Gt => "gt",
254                ir::PortComp::Lt => "lt",
255                ir::PortComp::Geq => "geq",
256                ir::PortComp::Leq => "leq",
257            };
258            format!("{}({}, {})", op_str, l_str, r_str)
259        }
260        ir::Guard::Port(port) => get_port_string(&port.borrow(), false),
261        ir::Guard::Info(_) => {
262            panic!("guard should not have info")
263        }
264    }
265}
266
267// returns the FIRRTL translation of a port.
268// if is_dst is true, then the port is a destination of an assignment, and shouldn't be a constant.
269fn get_port_string(port: &calyx_ir::Port, is_dst: bool) -> String {
270    match &port.parent {
271        ir::PortParent::Cell(cell) => {
272            let parent_ref = cell.upgrade();
273            let parent = parent_ref.borrow();
274            match parent.prototype {
275                ir::CellType::Constant { val, width: _ } => {
276                    if !is_dst {
277                        format!("UInt({})", val)
278                    } else {
279                        unreachable!()
280                    }
281                }
282                ir::CellType::ThisComponent => String::from(port.name.as_ref()),
283                _ => {
284                    format!("{}.{}", parent.name().as_ref(), port.name.as_ref())
285                }
286            }
287        }
288        _ => {
289            unreachable!("Groups should not be parents as this backend takes place after compiler passes.")
290        }
291    }
292}
293
294// variables that get set in assignments should get initialized to avoid the FIRRTL compiler from erroring.
295fn write_invalid_initialization<F: io::Write>(
296    port: &RRC<ir::Port>,
297    f: &mut F,
298) -> io::Result<()> {
299    let default_initialization_str = "; default initialization";
300    let dst_string = get_port_string(&port.borrow(), true);
301    if port.borrow().attributes.has(ir::BoolAttr::Control) {
302        writeln!(
303            f,
304            "{}{} <= UInt(0) {}",
305            SPACING.repeat(2),
306            dst_string,
307            default_initialization_str
308        )
309    } else {
310        writeln!(
311            f,
312            "{}{} is invalid {}",
313            SPACING.repeat(2),
314            dst_string,
315            default_initialization_str
316        )?;
317        writeln!(f, "{}{} <= UInt(0)", SPACING.repeat(2), dst_string)
318    }
319}
320
321// Writes a FIRRTL assignment
322fn write_assignment<F: io::Write>(
323    asgn: &ir::Assignment<ir::Nothing>,
324    f: &mut F,
325    num_indent: usize,
326) -> io::Result<()> {
327    let dest_port = asgn.dst.borrow();
328    let dest_string = get_port_string(&dest_port, true);
329    let source_port = asgn.src.borrow();
330    let src_string = get_port_string(&source_port, false);
331    writeln!(
332        f,
333        "{}{} <= {}",
334        SPACING.repeat(num_indent),
335        dest_string,
336        src_string
337    )?;
338    Ok(())
339}