From 156f64d075529db7c4bb9d311e02deeeccd04a61 Mon Sep 17 00:00:00 2001 From: Ayaka Yorihiro <36107281+ayakayorihiro@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:43:16 -0500 Subject: [PATCH] Initial code and tests for Calyx-to-FIRRTL backend (#1806) * Initial commit: added barebones firrtl.rs as a backend option. * Fix formatting errors and try to leverage VerilogBackend * Fix formatting * first pass on inputs and outputs * fix CI formatting issue * more CI formatting fixes * temporary commit before fixing up starter code * Clean up starter firrtl translation code * Fix CI errors * Barebones test containing input/output ports and single assignment * Fix test failure caused by whitespace * clean up unnecessary comments * Address PR comments * Add error message for ports that have groups as parents --- calyx-backend/src/backend_opt.rs | 3 + calyx-backend/src/firrtl.rs | 149 ++++++++++++++++++++++ calyx-backend/src/lib.rs | 2 + src/cmdline.rs | 8 +- tests/backend/firrtl/basic-program.expect | 12 ++ tests/backend/firrtl/basic-program.futil | 9 ++ 6 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 calyx-backend/src/firrtl.rs create mode 100644 tests/backend/firrtl/basic-program.expect create mode 100644 tests/backend/firrtl/basic-program.futil diff --git a/calyx-backend/src/backend_opt.rs b/calyx-backend/src/backend_opt.rs index f07df18a4b..166189fb1a 100644 --- a/calyx-backend/src/backend_opt.rs +++ b/calyx-backend/src/backend_opt.rs @@ -13,6 +13,7 @@ pub enum BackendOpt { Resources, Sexp, Yxi, + Firrtl, None, } @@ -28,6 +29,7 @@ fn backends() -> Vec<(&'static str, BackendOpt)> { ("resources", BackendOpt::Resources), ("sexp", BackendOpt::Sexp), ("yxi", BackendOpt::Yxi), + ("firrtl", BackendOpt::Firrtl), ("none", BackendOpt::None), ] } @@ -71,6 +73,7 @@ impl ToString for BackendOpt { Self::XilinxXml => "xilinx-xml", Self::Yxi => "yxi", Self::Calyx => "calyx", + Self::Firrtl => "firrtl", Self::None => "none", } .to_string() diff --git a/calyx-backend/src/firrtl.rs b/calyx-backend/src/firrtl.rs new file mode 100644 index 0000000000..06903f198c --- /dev/null +++ b/calyx-backend/src/firrtl.rs @@ -0,0 +1,149 @@ +//! FIRRTL backend for the Calyx compiler. +//! +//! Transforms an [`ir::Context`](crate::ir::Context) into a formatted string that represents a +//! valid FIRRTL program. + +use crate::{traits::Backend, VerilogBackend}; +use calyx_ir::{self as ir}; +use calyx_utils::{CalyxResult, OutputFile}; +use std::io; + +pub(super) const SPACING: &str = " "; + +/// Implements a simple FIRRTL backend. The backend only accepts Calyx programs with no control +/// and no groups. +#[derive(Default)] +pub struct FirrtlBackend; + +impl Backend for FirrtlBackend { + fn name(&self) -> &'static str { + "firrtl" + } + + fn link_externs( + _prog: &calyx_ir::Context, + _write: &mut calyx_utils::OutputFile, + ) -> calyx_utils::CalyxResult<()> { + Ok(()) // FIXME: Need to implement + } + + fn validate(prog: &calyx_ir::Context) -> calyx_utils::CalyxResult<()> { + VerilogBackend::validate(prog) // FIXME: would this work if we wanted to check for the same things? + } + + fn emit(ctx: &ir::Context, file: &mut OutputFile) -> CalyxResult<()> { + let out = &mut file.get_write(); + for comp in ctx.components.iter() { + emit_component(comp, out)? + } + Ok(()) + } +} + +// TODO: Ask about the other backend configurations in verilog.rs and see if I need any of it +fn emit_component( + comp: &ir::Component, + f: &mut F, +) -> io::Result<()> { + writeln!(f, "circuit {}:", comp.name)?; + writeln!(f, "{}module {}:", SPACING, comp.name)?; + + // Inputs and Outputs + let sig = comp.signature.borrow(); + for (_idx, port_ref) in sig.ports.iter().enumerate() { + let port = port_ref.borrow(); + let direction_string = + // NOTE: The signature port definitions are reversed inside the component. + match port.direction { + ir::Direction::Input => {"output"} + ir::Direction::Output => {"input"} + ir::Direction::Inout => { + panic!("Unexpected Inout port on Component: {}", port.name) + } + }; + if port.has_attribute(ir::BoolAttr::Clk) { + writeln!( + f, + "{}{} {}: Clock", + SPACING.repeat(2), + direction_string, + port.name + )?; + } else { + writeln!( + f, + "{}{} {}: UInt<{}>", + SPACING.repeat(2), + direction_string, + port.name, + port.width + )?; + } + } + + // Add a COMPONENT START: anchor before any code in the component + writeln!(f, "{}; COMPONENT START: {}", SPACING.repeat(2), comp.name)?; + + // TODO: Cells. NOTE: leaving this one for last + + for asgn in &comp.continuous_assignments { + // TODO: guards + match asgn.guard.as_ref() { + ir::Guard::Or(_, _) => todo!(), + ir::Guard::And(_, _) => todo!(), + ir::Guard::Not(_) => todo!(), + ir::Guard::True => { + // Simple assignment with no guard + let _ = write_assignment(asgn, f); + } + ir::Guard::CompOp(_, _, _) => todo!(), + ir::Guard::Port(_) => {} + ir::Guard::Info(_) => todo!(), + } + } + + // Add COMPONENT END: anchor + writeln!(f, "{}; COMPONENT END: {}", SPACING.repeat(2), comp.name)?; + + Ok(()) +} + +// Writes a FIRRTL assignment +fn write_assignment( + asgn: &ir::Assignment, + f: &mut F, +) -> CalyxResult<()> { + let dest_port = asgn.dst.borrow(); + let dest_string = get_port_string(&dest_port, true); + let source_port = asgn.src.borrow(); + let src_string = get_port_string(&source_port, false); + writeln!(f, "{}{} <= {}", SPACING.repeat(2), dest_string, src_string)?; + Ok(()) +} + +// returns the FIRRTL translation of a port. +// if is_dst is true, then the port is a destination of an assignment, and shouldn't be a constant. +fn get_port_string(port: &calyx_ir::Port, is_dst: bool) -> String { + match &port.parent { + ir::PortParent::Cell(cell) => { + let parent_ref = cell.upgrade(); + let parent = parent_ref.borrow(); + match parent.prototype { + ir::CellType::Constant { val, width: _ } => { + if !is_dst { + format!("UInt({})", val) + } else { + unreachable!() + } + } + ir::CellType::ThisComponent => String::from(port.name.as_ref()), + _ => { + format!("{}.{}", parent.name().as_ref(), port.name.as_ref()) + } + } + } + _ => { + unreachable!("Groups should not be parents as this backend takes place after compiler passes.") + } + } +} diff --git a/calyx-backend/src/lib.rs b/calyx-backend/src/lib.rs index 3d102da8c7..2701920dbd 100644 --- a/calyx-backend/src/lib.rs +++ b/calyx-backend/src/lib.rs @@ -1,10 +1,12 @@ //! Backends for the Calyx compiler. mod backend_opt; +mod firrtl; mod traits; mod verilog; mod yxi; pub use backend_opt::BackendOpt; +pub use firrtl::FirrtlBackend; pub use traits::Backend; pub use verilog::VerilogBackend; pub use yxi::YxiBackend; diff --git a/src/cmdline.rs b/src/cmdline.rs index c15b141772..c65fdde418 100644 --- a/src/cmdline.rs +++ b/src/cmdline.rs @@ -4,8 +4,8 @@ use argh::FromArgs; use calyx_backend::SexpBackend; use calyx_backend::{ xilinx::{XilinxInterfaceBackend, XilinxXmlBackend}, - Backend, BackendOpt, MlirBackend, ResourcesBackend, VerilogBackend, - YxiBackend, + Backend, BackendOpt, FirrtlBackend, MlirBackend, ResourcesBackend, + VerilogBackend, YxiBackend, }; use calyx_ir as ir; use calyx_utils::{CalyxResult, Error, OutputFile}; @@ -150,6 +150,10 @@ impl Opts { let backend = YxiBackend; backend.run(context, self.output) } + BackendOpt::Firrtl => { + let backend = FirrtlBackend; + backend.run(context, self.output) + } BackendOpt::Calyx => { ir::Printer::write_context( &context, diff --git a/tests/backend/firrtl/basic-program.expect b/tests/backend/firrtl/basic-program.expect new file mode 100644 index 0000000000..73af9081fa --- /dev/null +++ b/tests/backend/firrtl/basic-program.expect @@ -0,0 +1,12 @@ +circuit main: + module main: + input in: UInt<32> + output out: UInt<32> + input go: UInt<1> + input clk: Clock + input reset: UInt<1> + output done: UInt<1> + ; COMPONENT START: main + done <= UInt(1) + out <= in + ; COMPONENT END: main diff --git a/tests/backend/firrtl/basic-program.futil b/tests/backend/firrtl/basic-program.futil new file mode 100644 index 0000000000..e6d546ad31 --- /dev/null +++ b/tests/backend/firrtl/basic-program.futil @@ -0,0 +1,9 @@ +// -b firrtl +component main(in : 32) -> (out : 32) { + cells {} + wires { + out = in; + done = 1'd1; + } + control {} +}