use crate::{traits::Backend, VerilogBackend};
use calyx_ir::{self as ir, Binding, RRC};
use calyx_utils::{CalyxResult, Id, OutputFile};
use ir::Port;
use std::collections::HashSet;
use std::io;
pub(super) const SPACING: &str = " ";
#[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(()) }
fn validate(prog: &calyx_ir::Context) -> calyx_utils::CalyxResult<()> {
VerilogBackend::validate(prog) }
fn emit(ctx: &ir::Context, file: &mut OutputFile) -> CalyxResult<()> {
let out = &mut file.get_write();
writeln!(out, "circuit {}:", ctx.entrypoint)?;
if ctx.bc.emit_primitive_extmodules {
emit_extmodules(ctx, out)?;
}
for comp in ctx.components.iter() {
emit_component(comp, out)?
}
Ok(())
}
}
fn emit_extmodules<F: io::Write>(
ctx: &ir::Context,
out: &mut F,
) -> Result<(), calyx_utils::Error> {
let mut extmodule_set: HashSet<String> = HashSet::new();
for comp in &ctx.components {
for cell in comp.cells.iter() {
let cell_borrowed = cell.as_ref().borrow();
if let ir::CellType::Primitive {
name,
param_binding,
..
} = &cell_borrowed.prototype
{
let curr_module_name =
get_primitive_module_name(name, param_binding);
if extmodule_set.insert(curr_module_name.clone()) {
emit_primitive_extmodule(
cell.borrow().ports(),
&curr_module_name,
name,
param_binding,
out,
)?;
}
};
}
}
Ok(())
}
fn emit_component<F: io::Write>(
comp: &ir::Component,
f: &mut F,
) -> io::Result<()> {
writeln!(f, "{}module {}:", SPACING, comp.name)?;
let sig = comp.signature.borrow();
for port_ref in &sig.ports {
let port = port_ref.borrow();
emit_port(port, true, f)?;
}
writeln!(f, "{}; COMPONENT START: {}", SPACING.repeat(2), comp.name)?;
for cell in comp.cells.iter() {
let cell_borrowed = cell.as_ref().borrow();
if cell_borrowed.type_name().is_some() {
let module_name = match &cell_borrowed.prototype {
ir::CellType::Primitive {
name,
param_binding,
is_comb: _,
latency: _,
} => get_primitive_module_name(name, param_binding),
ir::CellType::Component { name } => name.to_string(),
_ => unreachable!(),
};
writeln!(
f,
"{}inst {} of {}",
SPACING.repeat(2),
cell_borrowed.name(),
module_name
)?;
}
}
let mut dst_set: HashSet<ir::Canonical> = HashSet::new();
for asgn in &comp.continuous_assignments {
match asgn.guard.as_ref() {
ir::Guard::True => {
let _ = write_assignment(asgn, f, 2);
}
_ => {
let dst_canonical = asgn.dst.as_ref().borrow().canonical();
if !dst_set.contains(&dst_canonical) {
write_invalid_initialization(&asgn.dst, f)?;
dst_set.insert(dst_canonical);
}
let guard_string = get_guard_string(asgn.guard.as_ref());
writeln!(f, "{}when {}:", SPACING.repeat(2), guard_string)?;
write_assignment(asgn, f, 3)?;
}
}
}
writeln!(f, "{}; COMPONENT END: {}\n", SPACING.repeat(2), comp.name)?;
Ok(())
}
fn get_primitive_module_name(name: &Id, param_binding: &Binding) -> String {
let mut primitive_string = name.to_string();
for (_, size) in param_binding.as_ref().iter() {
primitive_string.push('_');
primitive_string.push_str(&size.to_string());
}
primitive_string
}
fn emit_primitive_extmodule<F: io::Write>(
ports: &[RRC<Port>],
curr_module_name: &String,
name: &Id,
param_binding: &Binding,
f: &mut F,
) -> io::Result<()> {
writeln!(f, "{}extmodule {}:", SPACING, curr_module_name)?;
for port in ports {
let port_borrowed = port.borrow();
emit_port(port_borrowed, false, f)?;
}
writeln!(f, "{}defname = {}", SPACING.repeat(2), name)?;
for (id, size) in param_binding.as_ref().iter() {
writeln!(f, "{}parameter {} = {}", SPACING.repeat(2), id, size)?;
}
writeln!(f)?;
Ok(())
}
fn emit_port<F: io::Write>(
port: std::cell::Ref<'_, Port>,
reverse_direction: bool,
f: &mut F,
) -> Result<(), io::Error> {
let direction_string = match port.direction {
calyx_frontend::Direction::Input => {
if reverse_direction {
"output"
} else {
"input"
}
}
calyx_frontend::Direction::Output => {
if reverse_direction {
"input"
} else {
"output"
}
}
calyx_frontend::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
)?;
};
Ok(())
}
fn get_guard_string(guard: &ir::Guard<ir::Nothing>) -> String {
match guard {
ir::Guard::Or(l, r) => {
let l_str = get_guard_string(l.as_ref());
let r_str = get_guard_string(r.as_ref());
format!("or({}, {})", l_str, r_str)
}
ir::Guard::And(l, r) => {
let l_str = get_guard_string(l.as_ref());
let r_str = get_guard_string(r.as_ref());
format!("and({}, {})", l_str, r_str)
}
ir::Guard::Not(g) => {
let g_str = get_guard_string(g);
format!("not({})", g_str)
}
ir::Guard::True => String::from(""),
ir::Guard::CompOp(op, l, r) => {
let l_str = get_port_string(&l.borrow(), false);
let r_str = get_port_string(&r.borrow(), false);
let op_str = match op {
ir::PortComp::Eq => "eq",
ir::PortComp::Neq => "neq",
ir::PortComp::Gt => "gt",
ir::PortComp::Lt => "lt",
ir::PortComp::Geq => "geq",
ir::PortComp::Leq => "leq",
};
format!("{}({}, {})", op_str, l_str, r_str)
}
ir::Guard::Port(port) => get_port_string(&port.borrow(), false),
ir::Guard::Info(_) => {
panic!("guard should not have info")
}
}
}
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.")
}
}
}
fn write_invalid_initialization<F: io::Write>(
port: &RRC<ir::Port>,
f: &mut F,
) -> io::Result<()> {
let default_initialization_str = "; default initialization";
let dst_string = get_port_string(&port.borrow(), true);
if port.borrow().attributes.has(ir::BoolAttr::Control) {
writeln!(
f,
"{}{} <= UInt(0) {}",
SPACING.repeat(2),
dst_string,
default_initialization_str
)
} else {
writeln!(
f,
"{}{} is invalid {}",
SPACING.repeat(2),
dst_string,
default_initialization_str
)?;
writeln!(f, "{}{} <= UInt(0)", SPACING.repeat(2), dst_string)
}
}
fn write_assignment<F: io::Write>(
asgn: &ir::Assignment<ir::Nothing>,
f: &mut F,
num_indent: usize,
) -> io::Result<()> {
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(num_indent),
dest_string,
src_string
)?;
Ok(())
}