diff --git a/crates/pulsar-backend/resources/import.futil b/crates/pulsar-backend/resources/prelude.futil similarity index 100% rename from crates/pulsar-backend/resources/import.futil rename to crates/pulsar-backend/resources/prelude.futil diff --git a/crates/pulsar-backend/src/calyx.rs b/crates/pulsar-backend/src/calyx.rs new file mode 100644 index 0000000..c390266 --- /dev/null +++ b/crates/pulsar-backend/src/calyx.rs @@ -0,0 +1,520 @@ +// Copyright (C) 2024 Ethan Uppal. All rights reserved. + +use super::PulsarBackend; +use crate::{build_assignments_2, finish_component, Output}; +use builder::{ + CalyxAssignmentContainer, CalyxBuilder, CalyxCell, CalyxCellKind, + CalyxComponent, CalyxControl, Sequential +}; +use calyx_backend::Backend; +use pulsar_frontend::ty::Type; +use pulsar_ir::{ + basic_block::BasicBlockCell, + control_flow_graph::ControlFlowGraph, + generator::GeneratedTopLevel, + label::{Label, LabelName, MAIN_SYMBOL_PREFIX}, + operand::Operand, + variable::Variable, + Ir +}; +use std::{io::stderr, path::PathBuf}; + +pub mod builder; + +// This file contains many examples of BAD software engineering. +// All components are treated very much like functions. They have input ports +// all of width 64 and one output port of width 64. However hardware is a lot +// more flexible than this. See if you can figure out how to better use it. +// +// I realized that a contributing factor to this is that my IR has everything +// has Int64. I should change that + +struct FunctionContext { + ret_cell: Option, + param_env: usize +} + +impl Default for FunctionContext { + fn default() -> Self { + Self { + ret_cell: None, + param_env: 0 + } + } +} + +pub struct CalyxBackend { + builder: CalyxBuilder +} + +impl CalyxBackend { + // TODO: support larger nested arrays + // but arbitrary pointer access needs to be restricted in static analyzer + // when targeting hardware + fn make_cell_for_array( + &self, component: &mut CalyxComponent, var: Variable, + cell_size: usize, length: usize + ) -> CalyxCell { + component.named_mem(var.to_string(), cell_size, length, 64) + } + + /// Builds a constant if the operand is a constant and looks up a variable + /// otherwise. + fn find_operand_cell( + &self, component: &mut CalyxComponent, + operand: &Operand + ) -> CalyxCell { + match &operand { + Operand::Constant(value) => component.constant(*value, 64), + Operand::Variable(var) => component.find(var.to_string()) + } + } + + fn register_func( + &mut self, label: &Label, args: &Vec, ret: &Box + ) { + let mut comp_ports = vec![]; + for (i, arg) in args.iter().enumerate() { + let width = arg.size(); + let name = format!("arg{}", i); + comp_ports.push(calyx_ir::PortDef::new( + name, + (width * 8) as u64, + calyx_ir::Direction::Input, + calyx_ir::Attributes::default() + )); + } + if **ret != Type::Unit { + comp_ports.push(calyx_ir::PortDef::new( + "ret", + (ret.size() * 8) as u64, + calyx_ir::Direction::Output, + calyx_ir::Attributes::default() + )); + } + self.builder + .register_component(label.name.mangle().clone(), comp_ports); + + if label.name.mangle().starts_with(MAIN_SYMBOL_PREFIX) { + self.builder.set_entrypoint(label.name.mangle().clone()); + } + } + + /// A component for a call to `call` instantiated as a cell a single time in + /// the current component if `unique` and instantiated fresh otherwise. + fn cell_for_call( + &self, component: &mut CalyxComponent, + call: &LabelName, unique: bool + ) -> (String, CalyxCell) { + let callee_name = call.mangle().clone(); + let cell_name = format!("call{}", callee_name); + component.component_cell(cell_name, callee_name, unique) + } + + /// A unique cell that is only used for a single instruction and does not + /// need to be referenced elsewhere. + fn new_unnamed_reg( + &self, component: &mut CalyxComponent + ) -> CalyxCell { + component.new_unnamed_cell(CalyxCellKind::Register { size: 64 }) + } + + fn emit_ir( + &self, component: &mut CalyxComponent, + parent: &mut CalyxControl, ir: &Ir + ) { + let signal_out = component.signal_out(); + match ir { + Ir::Add(result, lhs, rhs) => { + let lhs_cell = self.find_operand_cell(component, lhs); + let rhs_cell = self.find_operand_cell(component, rhs); + let result_cell = component.new_reg(result.to_string(), 64); + let adder = component.new_prim("adder", "std_add", vec![64]); + let add_group = component.add_group("add"); + add_group.extend(build_assignments_2!(component; + adder["left"] = ? lhs_cell["out"]; + adder["right"] = ? rhs_cell["out"]; + result_cell["in"] = ? adder["out"]; + result_cell["write_en"] = ? signal_out["out"]; + add_group["done"] = ? result_cell["done"]; + )); + parent.enable_next(&add_group); + } + Ir::Mul(result, lhs, rhs) => { + let lhs_cell = self.find_operand_cell(component, lhs); + let rhs_cell = self.find_operand_cell(component, rhs); + let result_cell = component.new_reg(result.to_string(), 64); + let mult = + component.new_prim("mult", "std_mult_pipe", vec![64]); + let mult_group = component.add_group("multiply"); + mult_group.extend(build_assignments_2!(component; + mult["left"] = ? lhs_cell["out"]; + mult["right"] = ? rhs_cell["out"]; + mult["go"] = ? signal_out["out"]; + result_cell["in"] = ? mult["out"]; + result_cell["write_en"] = ? mult["done"]; + mult_group["done"] = ? result_cell["done"]; + )); + parent.enable_next(&mult_group); + } + Ir::Assign(result, value) => { + let value_cell = self.find_operand_cell(component, value); + if value_cell.kind.is_memory() { + // "copy" pointer + component.alias_cell(result.to_string(), value_cell); + return; + } + let result_cell = component.new_reg(result.to_string(), 64); + let assign_group = component.add_group("assign"); + assign_group.extend(build_assignments_2!(component; + result_cell["in"] = ? value_cell["out"]; + result_cell["write_en"] = ? signal_out["out"]; + assign_group["done"] = ? result_cell["done"]; + )); + parent.enable_next(&assign_group); + } + Ir::GetParam(result) => { + let func = component.signature(); + // TODO: memory refs + let result_cell = component.new_reg(result.to_string(), 64); + let get_param_group = component.add_group("get_param"); + let param_port = + format!("arg{}", component.user_data_ref().param_env); + get_param_group.extend(build_assignments_2!(component; + result_cell["in"] = ? func[param_port]; + result_cell["write_en"] = ? signal_out["out"]; + get_param_group["done"] = ? result_cell["done"]; + )); + parent.enable_next(&get_param_group); + component.user_data_mut().param_env += 1; + } + Ir::Return(value_opt) => { + // TODO: handle generating if/else control to simulate early + // returns, this requires structured IR anyways so doesn't + // matter right now + if let Some(value) = value_opt { + let return_group = component.add_group("return"); + let mut value_cell = + self.find_operand_cell(component, value); + + // We need to use the done port (doesn't exist on constants) + // so if it's a constant we need to make + // a temporary port + if let Operand::Constant(_) = value { + let temp_cell = self.new_unnamed_reg(component); + return_group.extend(build_assignments_2!(component; + temp_cell["in"] = ? value_cell["out"]; + )); + value_cell = temp_cell; + } + + let ret_cell = component + .user_data_ref() + .ret_cell + .as_ref() + .cloned() + .unwrap(); + return_group.extend(build_assignments_2!(component; + ret_cell["in"] = ? value_cell["out"]; + ret_cell["write_en"] = ? signal_out["out"]; + return_group["done"] = ? ret_cell["done"]; + )); + parent.enable_next(&return_group); + } else { + // todo!("I haven't figured out return fully yet") + } + } + Ir::LocalAlloc(result, size, count) => { + self.make_cell_for_array(component, *result, *size * 8, *count); + } + Ir::Store { + result, + value, + index + } => { + let store_group = component.add_group("store"); + let result_cell = component.find(result.to_string()); + let value_cell = self.find_operand_cell(component, value); + let index_cell = self.find_operand_cell(component, index); + assert!( + result_cell.kind.is_memory(), + "Ir::Store should take a memory result cell" + ); + store_group.extend(build_assignments_2!(component; + result_cell["addr0"] = ? index_cell["out"]; + result_cell["write_data"] = ? value_cell["out"]; + result_cell["write_en"] = ? signal_out["out"]; + store_group["done"] = ? result_cell["done"]; + )); + parent.enable_next(&store_group); + } + Ir::Load { + result, + value, + index + } => { + let load_group = component.add_group("load"); + let result_cell = component.new_reg(result.to_string(), 64); + let value_cell = self.find_operand_cell(component, value); + assert!( + value_cell.kind.is_memory(), + "Ir::Load should take a memory result cell" + ); + let index_cell = self.find_operand_cell(component, index); + load_group.extend(build_assignments_2!(component; + value_cell["addr0"] = ? index_cell["out"]; + result_cell["in"] = ? value_cell["read_data"]; + result_cell["write_en"] = ? signal_out["out"]; + load_group["done"] = ? result_cell["done"]; + )); + parent.enable_next(&load_group); + } + Ir::Map { + result, + parallel_factor, + f, + input, + length + } => { + assert!(length % parallel_factor == 0, "parallel_factor must divide length. figure out a better place to assert this, probably in the type checker fix"); + let index_cell = self.new_unnamed_reg(component); + + let init_group = component.add_group("init"); + let zero = component.constant(0, 64); + init_group.extend(build_assignments_2!(component; + index_cell["in"] = ? zero["out"]; + index_cell["write_en"] = ? signal_out["out"]; + init_group["done"] = ? index_cell["done"]; + )); + + let cond_group = component.add_comb_group("cond"); + let array_size_cell = component.constant(*length as i64, 64); + let lt_cell = component.new_prim("lt", "std_lt", vec![64]); + cond_group.extend(build_assignments_2!(component; + lt_cell["left"] = ? index_cell["out"]; + lt_cell["right"] = ? array_size_cell["out"]; + )); + + let read_group = component.add_group("read"); + let write_group = component.add_group("write"); + + let input_cell = self.find_operand_cell(component, input); // also a memory + assert!( + input_cell.kind.is_memory(), + "Ir::Map should take a memory input cell" + ); + let result_cell = component.find(result.to_string()); + assert!( + result_cell.kind.is_memory(), + "Ir::Map should take a memory result cell" + ); + let (_, call_cell) = self.cell_for_call(component, f, true); + + read_group.extend(build_assignments_2!(component; + input_cell["addr0"] = ? index_cell["out"]; + call_cell["arg0"] = ? input_cell["read_data"]; + call_cell["go"] = ? signal_out["out"]; + read_group["done"] = ? call_cell["done"]; + )); + + write_group.extend(build_assignments_2!(component; + result_cell["addr0"] = ? index_cell["out"]; + result_cell["write_data"] = ? call_cell["ret"]; + result_cell["write_en"] = ? call_cell["done"]; + write_group["done"] = ? result_cell["done"]; + )); + + let incr_group = component.add_group("incr"); + let adder = component.new_prim("adder", "std_add", vec![64]); + let one = component.constant(1, 64); + incr_group.extend(build_assignments_2!(component; + adder["left"] = ? index_cell["out"]; + adder["right"] = ? one["out"]; + index_cell["in"] = ? adder["out"]; + index_cell["write_en"] = ? signal_out["out"]; + incr_group["done"] = ? index_cell["done"]; + )); + + parent.enable_next(&init_group); + parent.while_(lt_cell.get("out"), Some(cond_group), |s| { + s.enable_next(&read_group); + s.enable_next(&write_group); + s.enable_next(&incr_group); + }); + } + Ir::Call(result_opt, func_name, args) => { + let (_, call_cell) = + self.cell_for_call(component, func_name, false); + let call_group = component.add_group("call"); + for (i, arg) in args.iter().enumerate() { + let arg_port = + self.find_operand_cell(component, arg).get("out"); + call_group.add(component.with_calyx_builder(|b| { + b.build_assignment( + call_cell.get(&format!("arg{}", i)), + arg_port, + calyx_ir::Guard::True + ) + })); + } + call_group.extend(build_assignments_2!(component; + call_cell["go"] = ? signal_out["out"]; + call_group["done"] = ? call_cell["done"]; + )); + parent.enable_next(&call_group); + + if let Some(result) = result_opt { + let use_call_group = component.add_group("use_call"); + let result_cell = component.new_reg(result.to_string(), 64); + use_call_group.extend(build_assignments_2!(component; + result_cell["in"] = ? call_cell["ret"]; + result_cell["write_en"] = ? signal_out["out"]; + use_call_group["done"] = ? result_cell["done"]; + )); + parent.enable_next(&use_call_group); + } + } + } + } + + fn emit_block( + &self, mut component: &mut CalyxComponent, + parent: &mut CalyxControl, block: BasicBlockCell + ) { + parent.seq(|s| { + for ir in block.as_ref().into_iter() { + self.emit_ir(&mut component, s, ir); + } + }); + } + + fn emit_func( + &mut self, label: &Label, _args: &Vec, ret: &Box, + _is_pure: bool, cfg: &ControlFlowGraph + ) { + let mut component: CalyxComponent = + self.builder.start_component(label.name.mangle().clone()); + + if **ret != Type::Unit { + let func = component.signature(); + let ret_cell = + component.new_unnamed_cell(builder::CalyxCellKind::Register { + size: ret.size() * 8 + }); + component.user_data_mut().ret_cell = Some(ret_cell.clone()); + let always = build_assignments_2!(component; + func["ret"] = ? ret_cell["out"]; + ) + .to_vec(); + component.with_calyx_builder(|b| { + b.add_continuous_assignments(always); + }); + } + + // for block in cfg.blocks() { + // self.emit_block(block); + // } + assert_eq!(1, cfg.size(), "CalyxBackend requires structured IR only in the entry block, but other blocks were found in the CFG"); + let mut root_control = CalyxControl::default(); + self.emit_block(&mut component, &mut root_control, cfg.entry()); + *component.control() = root_control; + + finish_component!(self.builder, component); + } +} + +pub struct CalyxBackendInput { + pub lib_path: PathBuf +} + +impl PulsarBackend for CalyxBackend { + type InitInput = CalyxBackendInput; + type Error = calyx_utils::Error; + + fn new(input: Self::InitInput) -> Self { + let mut prelude_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + prelude_file_path.push("resources"); + prelude_file_path.push("prelude.futil"); + + Self { + builder: CalyxBuilder::new( + Some(prelude_file_path), + input.lib_path, + None, + "_".into() + ) + } + } + + fn run( + mut self, code: Vec, output: Output + ) -> Result<(), Self::Error> { + // Create a calyx program from the IR + // - Step 1: load signatures + for generated_top_level in &code { + match generated_top_level { + GeneratedTopLevel::Function { + label, + args, + ret, + is_pure: _, + cfg: _ + } => { + self.register_func(label, args, ret); + } + } + } + // - Step 2: emit generated IR + for generated_top_level in &code { + match generated_top_level { + GeneratedTopLevel::Function { + label, + args, + ret, + is_pure, + cfg + } => self.emit_func(label, args, ret, *is_pure, cfg) + } + } + + // Obtain the program context + let mut builder = CalyxBuilder::dummy(); + std::mem::swap(&mut builder, &mut self.builder); + let mut calyx_ctx = builder.finalize(); + + // Debug print + calyx_ir::Printer::write_context(&calyx_ctx, false, &mut stderr()) + .unwrap(); + + // Perform optimization passes + let pm = calyx_opt::pass_manager::PassManager::default_passes()?; + let backend_conf = calyx_ir::BackendConf { + synthesis_mode: false, + enable_verification: false, + flat_assign: true, + emit_primitive_extmodules: false + }; + calyx_ctx.bc = backend_conf; + pm.execute_plan( + &mut calyx_ctx, + &["all".to_string()], + &["canonicalize".to_string()], + false + )?; + + // Emit to Verilog + let backend = calyx_backend::VerilogBackend; + backend.run(calyx_ctx, output.into())?; + Ok(()) + } +} + +impl Into for Output { + fn into(self) -> calyx_utils::OutputFile { + match self { + Output::Stdout => calyx_utils::OutputFile::Stdout, + Output::Stderr => calyx_utils::OutputFile::Stderr, + Output::File(path) => calyx_utils::OutputFile::File(path) + } + } +} diff --git a/crates/pulsar-backend/src/calyx/builder.rs b/crates/pulsar-backend/src/calyx/builder.rs new file mode 100644 index 0000000..bdf417d --- /dev/null +++ b/crates/pulsar-backend/src/calyx/builder.rs @@ -0,0 +1,758 @@ +// Copyright (C) 2024 Ethan Uppal. All rights reserved. + +use calyx_ir::RRC; +use pulsar_utils::environment::Environment; +use std::{ + collections::HashMap, fmt::Display, marker::PhantomData, path::PathBuf +}; + +pub mod macros; + +/// Describes the semantics of a cell. +#[derive(Clone, PartialEq, Eq)] +pub enum CalyxCellKind { + /// `Register { size }` is a `"std_reg"` of bit-width `size`. + Register { size: usize }, + + /// `CombMemoryD1 { size, length, address_bits }` is a `"comb_mem_d1"` with + /// cell bit-width `size`, cell count `length`, and address bit-width + /// `address_bits`. + CombMemoryD1 { + size: usize, + length: usize, + address_bits: usize + }, + + /// A calyx primitive other than a register, memory, or constant. + Primitive { name: String, params: Vec }, + + /// `GoDoneComponent { component }` is a cell for a + /// component named `component`. + GoDoneComponent { component: String }, + + /// `Constant { width }` is a `"std_const"` with bit-width `width`. + Constant { width: usize } +} + +impl CalyxCellKind { + /// Whether the cell represents a primitive, in which case + /// [`CalyxCellKind::to_string`] retrieves the name of the primitive in the + /// standard library. + pub fn is_primitive(&self) -> bool { + match &self { + Self::Register { size: _ } + | Self::CombMemoryD1 { + size: _, + length: _, + address_bits: _ + } + | Self::Primitive { name: _, params: _ } => true, + _ => false + } + } + + /// Whether the cell is a memory. + pub fn is_memory(&self) -> bool { + if let Self::CombMemoryD1 { + size: _, + length: _, + address_bits: _ + } = self + { + true + } else { + false + } + } + + /// The parameters associated with the primitive. + /// + /// Requires: `self.is_primitive()`. + pub(crate) fn primitive_params(&self) -> Vec { + match &self { + CalyxCellKind::Register { size } => vec![*size as u64], + CalyxCellKind::CombMemoryD1 { + size, + length, + address_bits + } => vec![*size as u64, *length as u64, *address_bits as u64], + CalyxCellKind::Primitive { name: _, params } => params.clone(), + CalyxCellKind::GoDoneComponent { component: _ } => { + panic!("Cell not a primitive") + } + CalyxCellKind::Constant { width } => vec![*width as u64] + } + } +} + +impl Display for CalyxCellKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + Self::Register { size: _ } => "std_reg", + Self::CombMemoryD1 { + size: _, + length: _, + address_bits: _ + } => "comb_mem_d1", + Self::Primitive { name, params: _ } => name, + Self::GoDoneComponent { component } => &component, + Self::Constant { width: _ } => "std_const" + } + .fmt(f) + } +} + +// might remove these later +pub type CalyxPort = RRC; + +/// A wrapper around [`calyx_ir::Cell`]s containing additional semantic +/// information (see [`CalyxCellKind`]). +#[derive(Clone)] +pub struct CalyxCell { + pub kind: CalyxCellKind, + pub value: RRC +} + +impl CalyxCell { + /// See [`calyx_ir::Cell::get`]. + pub fn get(&self, port: &str) -> CalyxPort { + self.value.borrow().get(port) + } +} + +/// An abstraction over a calyx group for adding assignments. +pub trait CalyxAssignmentContainer { + type AssignmentType; + + /// Inserts a single assignment. + fn add(&self, assignment: calyx_ir::Assignment); + + /// Inserts a set of assignment. + fn extend< + I: IntoIterator> + >( + &self, assignments: I + ) { + assignments.into_iter().for_each(|a| self.add(a)); + } +} + +/// See [`calyx_ir::Group`]. +pub struct CalyxGroup { + pub value: RRC +} + +impl CalyxAssignmentContainer for CalyxGroup { + type AssignmentType = calyx_ir::Nothing; + + fn add(&self, assignment: calyx_ir::Assignment) { + self.value.borrow_mut().assignments.push(assignment); + } +} + +/// See [`calyx_ir::CombGroup`]. +pub struct CalyxCombGroup { + pub value: RRC +} + +impl CalyxAssignmentContainer for CalyxCombGroup { + type AssignmentType = calyx_ir::Nothing; + + fn add(&self, assignment: calyx_ir::Assignment) { + self.value.borrow_mut().assignments.push(assignment); + } +} + +/// A flag for [`CalyxControl`]. +pub trait CalyxControlType {} + +/// Represents sequential [`CalyxControl`]. +pub struct Sequential; +impl CalyxControlType for Sequential {} + +/// Represents parallel [`CalyxControl`]. +pub struct Parallel; +impl CalyxControlType for Parallel {} + +/// A wrapper around [`calyx_ir::Control`] for scoped building. +pub struct CalyxControl { + children: Vec, + phantom: PhantomData +} + +impl CalyxControl { + /// Opens a `seq` context where `f` is called. For instance, + /// ``` + /// fn add_seq(control: &mut CalyxControl, my_group: &CalyxGroup) { + /// control.seq(|s| { + /// s.enable(my_group); + /// }); + /// } + /// ``` + /// produces the following calyx: + /// ``` + /// ... + /// seq { + /// my_group; + /// } + /// ... + /// ``` + pub fn seq(&mut self, f: F) + where + F: FnOnce(&mut CalyxControl) { + let mut child = CalyxControl::::default(); + f(&mut child); + self.children.push(calyx_ir::Control::seq(child.children)); + } + + /// Opens a `par` context. See [`CalyxControl::seq`] for details. + pub fn par(&mut self, f: F) + where + F: FnOnce(&mut CalyxControl) { + let mut child = CalyxControl::::default(); + f(&mut child); + self.children.push(calyx_ir::Control::par(child.children)); + } + + /// Opens an `if` context. See [`CalyxControl::seq`] for details. + pub fn if_( + &mut self, port: CalyxPort, cond: Option, true_f: F, + false_f: F + ) where + F: FnOnce(&mut CalyxControl) { + let mut true_branch = CalyxControl::::default(); + let mut false_branch = CalyxControl::::default(); + true_f(&mut true_branch); + false_f(&mut false_branch); + self.children.push(calyx_ir::Control::if_( + port, + cond.map(|cond| cond.value), + Box::new(true_branch.to_control()), + Box::new(false_branch.to_control()) + )); + } + + /// Opens a `while` context. See [`CalyxControl::seq`] for details. + pub fn while_( + &mut self, port: CalyxPort, cond: Option, f: F + ) where + F: FnOnce(&mut CalyxControl) { + let mut body = CalyxControl::::default(); + f(&mut body); + self.children.push(calyx_ir::Control::while_( + port, + cond.map(|cond| cond.value), + Box::new(body.to_control()) + )); + } + + // TODO: more control +} + +impl Default for CalyxControl { + fn default() -> Self { + Self { + children: vec![], + phantom: PhantomData + } + } +} + +impl CalyxControl { + /// Enables `group` to run in sequence. + pub fn enable_next(&mut self, group: &CalyxGroup) { + self.children + .push(calyx_ir::Control::enable(group.value.clone())); + } + + /// Unwraps the control builder. + pub fn to_control(self) -> calyx_ir::Control { + if self.children.is_empty() { + calyx_ir::Control::empty() + } else { + calyx_ir::Control::seq(self.children) + } + } +} + +impl CalyxControl { + /// Enables `group` to run in parallel. + pub fn enable(&mut self, group: &CalyxGroup) { + self.children + .push(calyx_ir::Control::enable(group.value.clone())); + } + + /// Unwraps the control builder. + pub fn to_control(self) -> calyx_ir::Control { + if self.children.is_empty() { + calyx_ir::Control::empty() + } else { + calyx_ir::Control::par(self.children) + } + } +} + +/// A wrapper for a calyx component that can only be created through +/// [`CalyxBuilder::build_component`], where it must live no longer than the +/// builder that created it. +/// +/// The wrapper maintains cell and control manipulation. Cells can be created +/// through methods such as [`CalyxComponent::named_reg`] or +/// [`CalyxComponent::component_cell`]. It also contains unique per-component +/// data initialized via `ComponentData::default` which can be accessed through +/// appropriate getters. +pub struct CalyxComponent<'a, ComponentData: Default> { + ext_sigs: &'a HashMap>>, + lib_sig: &'a calyx_ir::LibrarySignatures, + env: Environment, + component: calyx_ir::Component, + cell_name_prefix: String, + unique_counter: usize, + user_data: ComponentData, + control_builder: CalyxControl +} + +impl<'a, ComponentData: Default> CalyxComponent<'a, ComponentData> { + fn new( + component: calyx_ir::Component, cell_name_prefix: String, + ext_sigs: &'a HashMap>>, + lib_sig: &'a calyx_ir::LibrarySignatures + ) -> Self { + Self { + ext_sigs, + lib_sig, + env: Environment::new(), + component, + cell_name_prefix, + unique_counter: 0, + user_data: ComponentData::default(), + control_builder: CalyxControl::default() + } + } + + /// The user data associated with the component. + pub fn user_data_ref(&self) -> &ComponentData { + &self.user_data + } + + /// See [`CalyxComponent::user_data_ref`]. + pub fn user_data_mut(&mut self) -> &mut ComponentData { + &mut self.user_data + } + + /// The input/output signature of this component as a cell. + pub fn signature(&mut self) -> CalyxCell { + CalyxCell { + kind: CalyxCellKind::GoDoneComponent { + component: self.component.name.to_string() + }, + value: self.component.signature.clone() + } + } + + /// The control of this component. + pub fn control(&mut self) -> &mut CalyxControl { + &mut self.control_builder + } + + /// Enables direct access to a [`calyx_ir::Builder`] for this component. + pub fn with_calyx_builder(&mut self, f: F) -> T + where + F: FnOnce(&mut calyx_ir::Builder) -> T { + // Creating a calyx_ir::Builder is very cheap (for now). If I can figure + // out a better way, e.g., storing the builder in the struct, I will + // switch to that, but I tried doing that for many hours to no avail. + let mut ir_builder = + calyx_ir::Builder::new(&mut self.component, &self.lib_sig) + .not_generated(); + f(&mut ir_builder) + } + + /// A register cell bound to `name`. + /// + /// Requires: `name` has not been bound. + pub fn new_reg(&mut self, name: String, width: usize) -> CalyxCell { + let mut bind_name = self.cell_name_prefix.clone(); + bind_name.push_str(&name); + self.create_cell(bind_name, CalyxCellKind::Register { size: width }) + } + + /// A memory cell bound to `name`. + /// + /// Requires: `name` has not been bound. + pub fn named_mem( + &mut self, name: String, cell_size: usize, length: usize, + address_bits: usize + ) -> CalyxCell { + let mut bind_name = self.cell_name_prefix.clone(); + bind_name.push_str(&name); + self.create_cell( + bind_name, + CalyxCellKind::CombMemoryD1 { + size: cell_size, + length, + address_bits + } + ) + } + + /// Creates a cell named `name` for a primitive `prim` with parameters + /// `params`. Before using this function, see if + /// [`CalyxComponent::named_reg`] or [`CalyxComponent::named_mem`] are more + /// appropriate. + /// + /// Requires: `name` has not been bound. + pub fn new_prim( + &mut self, name: &str, prim: &str, params: Vec + ) -> CalyxCell { + self.create_cell( + name.into(), + CalyxCellKind::Primitive { + name: prim.into(), + params + } + ) + } + + /// A cell for a component `component` whose name is guaranteed to begin + /// with `prefix`. If `instantiate_new`, then a unique cell will be created. + /// Both the cell and the actual cell name are returned. + pub fn component_cell( + &mut self, prefix: String, component: String, instantiate_new: bool + ) -> (String, CalyxCell) { + let cell_name = if instantiate_new { + format!("{}{}", prefix, self.get_unique_number()) + } else { + prefix + }; + let cell = CalyxCell { + kind: CalyxCellKind::GoDoneComponent { + component: component.clone() + }, + value: self._create_component_cell(cell_name.clone(), component) + }; + (cell_name, cell) + } + + /// An unnamed cell of a given `kind`. + pub fn new_unnamed_cell(&mut self, kind: CalyxCellKind) -> CalyxCell { + let cell_name = format!("t{}", self.get_unique_number()); + self.create_cell(cell_name, kind) + } + + /// A constant cell, that is, a primitive `"std_const"`. + pub fn constant(&mut self, value: i64, width: usize) -> CalyxCell { + CalyxCell { + kind: CalyxCellKind::Constant { width }, + value: self.with_calyx_builder(|b| { + b.add_constant(value as u64, width as u64) + }) + } + } + + /// Equivlane to `constant(1, 1)`. + pub fn signal_out(&mut self) -> CalyxCell { + self.constant(1, 1) + } + + /// Adds `name` as a named alias to refer to `cell`. + /// + /// Requires: `name` has not been previously bound. + pub fn alias_cell(&mut self, name: String, cell: CalyxCell) { + assert!(self + .env + .bind(format!("{}{}", self.cell_name_prefix, name), cell) + .is_none()); + } + + /// Looks up a named cell previously bound to `name`. + /// + /// Requires: `name` has been bound. + pub fn find(&mut self, name: String) -> CalyxCell { + self.env + .find(format!("{}{}", self.cell_name_prefix, name)) + .expect("Did not find cell in component environment") + .clone() + } + + /// See [`Environment::push`]. + pub fn begin_scope(&mut self) { + self.env.push(); + } + + /// See [`Environment::pop`]. + pub fn end_scope(&mut self) -> bool { + self.env.pop() + } + + /// Creates a new group guaranteed to start with `prefix`. + pub fn add_group(&mut self, prefix: &str) -> CalyxGroup { + CalyxGroup { + value: self.with_calyx_builder(|b| b.add_group(prefix)) + } + } + + /// Creates a new combinational group guaranteed to start with `prefix`. + pub fn add_comb_group(&mut self, prefix: &str) -> CalyxCombGroup { + CalyxCombGroup { + value: self.with_calyx_builder(|b| b.add_comb_group(prefix)) + } + } + + /// Yields a [`calyx_ir::Component`]. + pub fn finalize(self) -> calyx_ir::Component { + *self.component.control.borrow_mut() = + self.control_builder.to_control(); + self.component + } + + /// Creates a cell of type `kind` bound to `key`. + /// + /// Requires: `key` has not been bound. + fn create_cell(&mut self, key: String, kind: CalyxCellKind) -> CalyxCell { + let calyx_cell = if kind.is_primitive() { + self._create_primitive( + key.clone(), + kind.to_string(), + kind.primitive_params() + ) + } else if let CalyxCellKind::GoDoneComponent { component } = &kind { + self._create_component_cell(key.clone(), component.clone()) + } else { + panic!("unknown cell kind") + }; + let cell = CalyxCell { + kind, + value: calyx_cell + }; + self.env.bind(key, cell.clone()); + cell + } + + /// A number guaranteed to be unique across all calls to this function for a + /// specific component builder such as `self`. + fn get_unique_number(&mut self) -> usize { + let result = self.unique_counter; + self.unique_counter += 1; + result + } + + /// Creates a [`calyx_ir::Cell`] for a `primitive`. + fn _create_primitive( + &mut self, name: String, primitive: String, params: Vec + ) -> RRC { + self.with_calyx_builder(|b| b.add_primitive(name, primitive, ¶ms)) + } + + /// Creates a [`calyx_ir::Cell`] for a `component`. + fn _create_component_cell( + &mut self, name: String, component: String + ) -> RRC { + let mut port_defs = self.ext_sigs.get(&component).unwrap().clone(); + + let mut go_attr = calyx_ir::Attributes::default(); + go_attr.insert(calyx_ir::Attribute::Num(calyx_ir::NumAttr::Go), 1); + port_defs.push(calyx_ir::PortDef::new( + "go", + 1, + calyx_ir::Direction::Input, + go_attr + )); + + let mut done_attr = calyx_ir::Attributes::default(); + done_attr.insert(calyx_ir::Attribute::Num(calyx_ir::NumAttr::Done), 1); + port_defs.push(calyx_ir::PortDef::new( + "done", + 1, + calyx_ir::Direction::Output, + done_attr + )); + + let mut clk_attr = calyx_ir::Attributes::default(); + clk_attr.insert(calyx_ir::Attribute::Bool(calyx_ir::BoolAttr::Clk), 1); + port_defs.push(calyx_ir::PortDef::new( + "clk", + 1, + calyx_ir::Direction::Input, + clk_attr + )); + + let mut reset_attr = calyx_ir::Attributes::default(); + reset_attr + .insert(calyx_ir::Attribute::Bool(calyx_ir::BoolAttr::Reset), 1); + port_defs.push(calyx_ir::PortDef::new( + "reset", + 1, + calyx_ir::Direction::Input, + reset_attr + )); + + let cell = self._cell_from_signature( + name.clone().into(), + calyx_ir::CellType::Component { + name: component.clone().into() + }, + port_defs + ); + self.component.cells.add(cell.clone()); + cell + } + + /// For some reason, this is private: https://github.com/calyxir/calyx/blob/main/calyx-ir/src/builder.rs#L361 + fn _cell_from_signature( + &self, name: calyx_ir::Id, typ: calyx_ir::CellType, + ports: Vec> + ) -> RRC { + let cell = calyx_ir::rrc(calyx_ir::Cell::new(name, typ)); + ports.into_iter().for_each(|pd| { + let port = calyx_ir::rrc(calyx_ir::Port { + name: pd.name(), + width: pd.width, + direction: pd.direction, + parent: calyx_ir::PortParent::Cell(calyx_ir::WRC::from(&cell)), + attributes: pd.attributes + }); + cell.borrow_mut().ports.push(port); + }); + cell + } +} + +/// A builder for calyx IR optimized for generation from a higher-level AST or +/// IR. +pub struct CalyxBuilder { + /// The calyx program being built. + ctx: calyx_ir::Context, + + /// Component signatures. + sigs: HashMap>>, + + /// Prefix for named cells to avoid collision with unnamed cells. + cell_name_prefix: String +} + +impl CalyxBuilder { + /// Constructs a new calyx builder. See the documentation at + /// [`CalyxBuilder`] for general usage information. + /// + /// - `prelude` is an optional calyx file that will be parsed and inlined in + /// additional to the standard library, which is useful for additional + /// component definitions or imports. + /// + /// - `lib_path` should be the root of the calyx installation location, + /// e.g., the folder generated from cloning the repository from GitHub. + /// + /// - `entrypoint` is the name of the entry component in the program. If + /// `None` is passed, it will default to `"main"`. You can use + /// [`CalyxBuilder::set_entrypoint`] to update it. + /// + /// - `cell_name_prefix` is the non-empty prefix applied to all named cells + /// (e.g., those requested via [`CalyxComponent::named_reg`]) to guarantee + /// no collisions with unnamed cells (e.g., those requested via + /// [`CalyxComponent::unnamed_cell`]). It must be non-empty. + pub fn new( + prelude: Option, lib_path: PathBuf, + entrypoint: Option, cell_name_prefix: String + ) -> Self { + assert!(!cell_name_prefix.is_empty()); + + // A workspace is created for the sole purpose of obtaining standard + // library definitions -- it is immediately turned into a context. + let ws = + calyx_frontend::Workspace::construct(&prelude, &lib_path).unwrap(); + let ctx = calyx_ir::Context { + components: vec![], + lib: ws.lib, + entrypoint: entrypoint.unwrap_or("main".into()).into(), + bc: calyx_ir::BackendConf::default(), + extra_opts: vec![], + metadata: None + }; + + Self { + ctx, + sigs: HashMap::new(), + cell_name_prefix + } + } + + ///
This builder cannot be used.
+ pub fn dummy() -> Self { + Self { + ctx: calyx_ir::Context { + components: vec![], + lib: calyx_ir::LibrarySignatures::default(), + entrypoint: "".into(), + bc: calyx_ir::BackendConf::default(), + extra_opts: vec![], + metadata: None + }, + sigs: HashMap::new(), + cell_name_prefix: "".into() + } + } + + /// Binds a component (named `name`)'s signature to a list of `ports` so it + /// can be constructed or instantiated by another component. + pub fn register_component( + &mut self, name: String, ports: Vec> + ) { + self.sigs.insert(name, ports); + } + + /// Returns a component wrapper for a registered component. Once you are + /// finished with the component builder, call [`finish_component!`]. + /// + /// Requires: [`CalyxBuilder::register_component`] has been issued for + /// `name`. + pub fn start_component( + &self, name: String + ) -> CalyxComponent { + CalyxComponent::new( + calyx_ir::Component::new( + name.clone(), + self.sigs + .get(&name) + .expect("Use `register_component` first") + .clone(), + true, + false, + None + ), + self.cell_name_prefix.clone(), + &self.sigs, + &self.ctx.lib + ) + } + + /// Please use [`finish_component!`] instead. + pub fn _finish_component(&mut self, component: calyx_ir::Component) { + self.ctx.components.push(component); + } + + /// Updates the name of the program entrypoint. + /// + /// Requires: [`CalyxBuilder::register_component`] has been issued for + /// `entrypoint`. + pub fn set_entrypoint(&mut self, entrypoint: String) { + assert!(self.sigs.contains_key(&entrypoint)); + self.ctx.entrypoint = entrypoint.into(); + } + + /// Yields a [`calyx_ir::Context`]. + /// + /// Requires: the entrypoint provided at [`CalyxBuilder::new`] is the name + /// of a component added. + pub fn finalize(self) -> calyx_ir::Context { + self.ctx + } +} + +/// `finish_component!(builder, component)` marks a `component` as finalized in +/// `builder`. +#[macro_export] +macro_rules! finish_component { + ($builder:expr, $component:expr) => { + $builder._finish_component($component.finalize()) + }; +} diff --git a/crates/pulsar-backend/src/calyx/builder/macros.rs b/crates/pulsar-backend/src/calyx/builder/macros.rs new file mode 100644 index 0000000..a29c615 --- /dev/null +++ b/crates/pulsar-backend/src/calyx/builder/macros.rs @@ -0,0 +1,46 @@ +// calyx `build_assignments!` but for `CalyxCell`s. +#[macro_export(local_inner_macros)] +macro_rules! build_assignments_2_aux { + // Unguarded assignment. + (@base $builder:expr; + $dst_node:ident[$dst_port:expr] = ? $src_node:ident[$src_port:expr]) => { + $builder.build_assignment( + $dst_node.value.borrow().get($dst_port), + $src_node.value.borrow().get($src_port), + calyx_ir::Guard::True) + }; + + // Guarded assignment. + (@base $builder:expr; + $dst_node:ident[$dst_port:expr] = + $guard:ident ? + $src_node:ident[$src_port:expr]) => { + $builder.build_assignment( + $dst_node.value.borrow().get($dst_port), + $src_node.value.borrow().get($src_port), + $guard.clone()) + }; + + ($builder:expr; + $($dst_node:ident[$dst_port:expr] = + $($guard:ident)? ? + $src_node:ident[$src_port:expr];)*) => { + [$( + build_assignments_2_aux!(@base $builder; + $dst_node[$dst_port] = $($guard)? ? $src_node[$src_port]) + ),*] + + }; +} + +/// Behaves like [`calyx_ir::build_assignments!`] but takes in a +/// [`CalyxComponent`] instead of a [`calyx_ir::Builder`] and uses +/// [`CalyxCell`]s instead of `RRC`s. +#[macro_export(local_inner_macros)] +macro_rules! build_assignments_2 { + ($component:expr; $($args:tt)*) => { + $component.with_calyx_builder(|builder| { + build_assignments_2_aux!(builder; $($args)*) + }) + } +} diff --git a/crates/pulsar-backend/src/calyx_backend.rs b/crates/pulsar-backend/src/calyx_backend.rs deleted file mode 100644 index 2082c20..0000000 --- a/crates/pulsar-backend/src/calyx_backend.rs +++ /dev/null @@ -1,663 +0,0 @@ -use super::PulsarBackend; -use calyx_backend::Backend; -use calyx_ir::{build_assignments, RRC}; -use pulsar_frontend::ty::Type; -use pulsar_ir::{ - basic_block::BasicBlockCell, - control_flow_graph::ControlFlowGraph, - generator::GeneratedTopLevel, - label::{Label, LabelName}, - operand::Operand, - variable::Variable, - Ir -}; -use pulsar_utils::environment::Environment; -use std::{ - collections::HashMap, - path::{Path, PathBuf} -}; - -// This file contains many examples of BAD software engineering. -// -// One boilerplate I'm noticing is in construction of cells, groups, and control -// -- see if you can find a nicer, more beautiful way to write it -// -// All components are treated very much like functions. They have input ports -// all of width 64 and one output port of width 64. However hardware is a lot -// more flexible than this. See if you can figure out how to better use it. -// -// I realized that a contributing factor to this is that my IR has everything -// has Int64. I should change that - -pub struct CalyxBackend { - sig: HashMap>>, - env: Environment>, - call_env: Environment>, - ret_cell: Option>, - param_env: usize, - temp_count: usize -} - -impl CalyxBackend { - fn stdlib_context(lib_path: String) -> calyx_ir::Context { - let mut import_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - import_file_path.push("resources"); - import_file_path.push("import.futil"); - let ws = calyx_frontend::Workspace::construct( - &Some(import_file_path), - Path::new(&lib_path) - ) - .unwrap(); - calyx_ir::Context { - components: vec![], - lib: ws.lib, - entrypoint: "main".into(), - bc: calyx_ir::BackendConf::default(), - extra_opts: vec![], - metadata: None - } - } - - /// Returns the register associated with `var` in the current component, - /// building it if necessary. - fn cell_for_var( - &mut self, builder: &mut calyx_ir::Builder, var: Variable - ) -> RRC { - if let Some(cell) = self.env.find(var) { - cell.clone() - } else { - let cell = - builder.add_primitive(var.to_string(), "std_reg", &vec![64]); - self.env.bind(var, cell.clone()); - cell - } - } - - // TODO: support larger nested arrays - // but arbitrary pointer access needs to be restricted in static analyzer - // when targeting hardware - fn make_cell_for_pointer( - &mut self, builder: &mut calyx_ir::Builder, var: Variable, - cell_size: usize, length: usize - ) -> RRC { - let cell = builder.add_primitive( - var.to_string(), - "comb_mem_d1", - &vec![cell_size as u64, length as u64, 64] - ); - self.env.bind(var, cell.clone()); - cell - } - - /// Builds a constant if the operand is a constant. See - /// [`CalyxBackend::cell_for_var`] for when the operand is a variable. - fn cell_for_operand( - &mut self, builder: &mut calyx_ir::Builder, operand: &Operand - ) -> RRC { - match &operand { - Operand::Constant(value) => builder.add_constant(*value as u64, 64), - Operand::Variable(var) => self.cell_for_var(builder, *var) - } - } - - fn cache_func_sig( - &mut self, label: &Label, args: &Vec, ret: &Box - ) { - let mut comp_ports = vec![]; - for (i, arg) in args.iter().enumerate() { - let width = arg.size(); - let name = format!("arg{}", i); - comp_ports.push(calyx_ir::PortDef::new( - name, - (width * 8) as u64, - calyx_ir::Direction::Input, - calyx_ir::Attributes::default() - )); - } - if **ret != Type::Unit { - comp_ports.push(calyx_ir::PortDef::new( - "ret", - (ret.size() * 8) as u64, - calyx_ir::Direction::Output, - calyx_ir::Attributes::default() - )); - } - self.sig.insert(label.name.mangle().clone(), comp_ports); - } - - /// A component for a call to `call` instantiated as a cell a single time in - /// the current component. - fn cell_for_call( - &mut self, builder: &mut calyx_ir::Builder, call: &LabelName, - unique: bool - ) -> (String, RRC) { - // For some reason, this is private https://github.com/calyxir/calyx/blob/main/calyx-ir/src/builder.rs#L361 - fn cell_from_signature( - name: calyx_ir::Id, typ: calyx_ir::CellType, - ports: Vec> - ) -> RRC { - let cell = calyx_ir::rrc(calyx_ir::Cell::new(name, typ)); - ports.into_iter().for_each(|pd| { - let port = calyx_ir::rrc(calyx_ir::Port { - name: pd.name(), - width: pd.width, - direction: pd.direction, - parent: calyx_ir::PortParent::Cell(calyx_ir::WRC::from( - &cell - )), - attributes: pd.attributes - }); - cell.borrow_mut().ports.push(port); - }); - cell - } - - let cell_name = format!("call{}", call.mangle()); - if let Some(cell) = - self.call_env.find(cell_name.clone()).filter(|_| !unique) - { - (cell_name, cell.clone()) - } else { - let mut port_defs = self.sig.get(call.mangle()).unwrap().clone(); - let mut go_attr = calyx_ir::Attributes::default(); - go_attr.insert(calyx_ir::Attribute::Num(calyx_ir::NumAttr::Go), 1); - let mut done_attr = calyx_ir::Attributes::default(); - done_attr - .insert(calyx_ir::Attribute::Num(calyx_ir::NumAttr::Done), 1); - let mut clk_attr = calyx_ir::Attributes::default(); - clk_attr - .insert(calyx_ir::Attribute::Bool(calyx_ir::BoolAttr::Clk), 1); - let mut reset_attr = calyx_ir::Attributes::default(); - reset_attr.insert( - calyx_ir::Attribute::Bool(calyx_ir::BoolAttr::Reset), - 1 - ); - port_defs.push(calyx_ir::PortDef::new( - "go", - 1, - calyx_ir::Direction::Input, - go_attr - )); - port_defs.push(calyx_ir::PortDef::new( - "done", - 1, - calyx_ir::Direction::Output, - done_attr - )); - port_defs.push(calyx_ir::PortDef::new( - "clk", - 1, - calyx_ir::Direction::Input, - clk_attr - )); - port_defs.push(calyx_ir::PortDef::new( - "reset", - 1, - calyx_ir::Direction::Input, - reset_attr - )); - let cell = cell_from_signature( - cell_name.clone().into(), - calyx_ir::CellType::Component { - name: call.mangle().clone().into() - }, - port_defs - ); - - builder.component.cells.add(cell.clone()); - self.call_env.bind(cell_name.clone(), cell.clone()); - (cell_name, cell) - } - } - - /// A unique cell that is only used for a single instruction and does not - /// need to be referenced elsewhere. - fn cell_for_temp( - &mut self, builder: &mut calyx_ir::Builder - ) -> RRC { - let i = self.temp_count; - self.temp_count += 1; - builder.add_primitive(format!("t{}", i), "std_reg", &vec![64]) - } - - fn emit_ir( - &mut self, builder: &mut calyx_ir::Builder, seq: &mut calyx_ir::Seq, - ir: &Ir - ) { - match ir { - Ir::Add(result, lhs, rhs) => { - let lhs_cell = self.cell_for_operand(builder, lhs); - let rhs_cell = self.cell_for_operand(builder, rhs); - let result_cell = self.cell_for_var(builder, *result); - let signal_out = builder.add_constant(1, 1); - let adder = - builder.add_primitive("adder", "std_add", &vec![64]); - let add_group = builder.add_group("add"); - let assignments = build_assignments!(builder; - adder["left"] = ? lhs_cell["out"]; - adder["right"] = ? rhs_cell["out"]; - result_cell["in"] = ? adder["out"]; - result_cell["write_en"] = ? signal_out["out"]; - add_group["done"] = ? result_cell["done"]; - ) - .to_vec(); - add_group.borrow_mut().assignments.extend(assignments); - seq.stmts.push(calyx_ir::Control::enable(add_group)); - } - Ir::Mul(result, lhs, rhs) => { - let lhs_cell = self.cell_for_operand(builder, lhs); - let rhs_cell = self.cell_for_operand(builder, rhs); - let result_cell = self.cell_for_var(builder, *result); - let signal_out = builder.add_constant(1, 1); - let mult = - builder.add_primitive("mult", "std_mult_pipe", &vec![64]); - - let mult_group = builder.add_group("multiply"); - let mult_assignments = build_assignments!(builder; - mult["left"] = ? lhs_cell["out"]; - mult["right"] = ? rhs_cell["out"]; - mult["go"] = ? signal_out["out"]; - result_cell["in"] = ? mult["out"]; - result_cell["write_en"] = ? mult["done"]; - mult_group["done"] = ? result_cell["done"]; - ) - .to_vec(); - mult_group.borrow_mut().assignments.extend(mult_assignments); - seq.stmts.push(calyx_ir::Control::enable(mult_group)); - } - Ir::Assign(result, value) => { - let value_cell = self.cell_for_operand(builder, value); - if value_cell.borrow().is_primitive("comb_mem_d1".into()) { - self.env.bind(*result, value_cell.clone()); - return; - } - let result_cell = self.cell_for_var(builder, *result); - let signal_out = builder.add_constant(1, 1); - let assign_group = builder.add_group("assign"); - let assignments = build_assignments!(builder; - result_cell["in"] = ? value_cell["out"]; - result_cell["write_en"] = ? signal_out["out"]; - assign_group["done"] = ? result_cell["done"]; - ) - .to_vec(); - assign_group.borrow_mut().assignments.extend(assignments); - seq.stmts.push(calyx_ir::Control::enable(assign_group)); - } - Ir::GetParam(result) => { - let func = builder.component.signature.clone(); - let result_cell = self.cell_for_var(builder, *result); - let signal_out = builder.add_constant(1, 1); - let get_param_group = builder.add_group("get_param"); - let assignments = build_assignments!(builder; - result_cell["in"] = ? func[format!("arg{}", self.param_env)]; - result_cell["write_en"] = ? signal_out["out"]; - get_param_group["done"] = ? result_cell["done"]; - ) - .to_vec(); - get_param_group.borrow_mut().assignments.extend(assignments); - seq.stmts.push(calyx_ir::Control::enable(get_param_group)); - self.param_env += 1; - } - Ir::Return(value_opt) => { - // TODO: handle generating if/else control to simulate early - // returns, this requires structured IR anyways so doesn't - // matter right now - if let Some(value) = value_opt { - let return_group = builder.add_group("return"); - let mut value_cell = self.cell_for_operand(builder, value); - - // We need to use the done port (doesn't exist on constants) - // so if it's a constant we need to make - // a temporary port - if let Operand::Constant(_) = value { - let temp_cell = self.cell_for_temp(builder); - return_group.borrow_mut().assignments.extend( - build_assignments!(builder; - temp_cell["in"] = ? value_cell["out"]; - ) - ); - value_cell = temp_cell; - } - - let ret_cell = self.ret_cell.clone().unwrap(); - let signal_out = builder.add_constant(1, 1); - let assignments = build_assignments!(builder; - ret_cell["in"] = ? value_cell["out"]; - ret_cell["write_en"] = ? signal_out["out"]; - return_group["done"] = ? ret_cell["done"]; - ) - .to_vec(); - return_group.borrow_mut().assignments.extend(assignments); - seq.stmts.push(calyx_ir::Control::enable(return_group)); - } else { - // todo!("I haven't figured out return fully yet") - } - } - Ir::LocalAlloc(result, size, count) => { - self.make_cell_for_pointer(builder, *result, *size * 8, *count); - } - Ir::Store { - result, - value, - index - } => { - let store_group = builder.add_group("store"); - let result_cell = self.cell_for_var(builder, *result); - let value_cell = self.cell_for_operand(builder, value); - let index_cell = self.cell_for_operand(builder, index); - let signal_out = builder.add_constant(1, 1); - let assignments = build_assignments!(builder; - result_cell["addr0"] = ? index_cell["out"]; - result_cell["write_data"] = ? value_cell["out"]; - result_cell["write_en"] = ? signal_out["out"]; - store_group["done"] = ? result_cell["done"]; - ) - .to_vec(); - store_group.borrow_mut().assignments.extend(assignments); - seq.stmts.push(calyx_ir::Control::enable(store_group)); - } - Ir::Load { - result, - value, - index - } => { - let load_group = builder.add_group("load"); - let result_cell = self.cell_for_var(builder, *result); - let value_cell = self.cell_for_operand(builder, value); - let index_cell = self.cell_for_operand(builder, index); - let signal_out = builder.add_constant(1, 1); - let assignments = build_assignments!(builder; - value_cell["addr0"] = ? index_cell["out"]; - result_cell["in"] = ? value_cell["read_data"]; - result_cell["write_en"] = ? signal_out["out"]; - load_group["done"] = ? result_cell["done"]; - ) - .to_vec(); - load_group.borrow_mut().assignments.extend(assignments); - seq.stmts.push(calyx_ir::Control::enable(load_group)); - } - Ir::Map { - result, - parallel_factor, - f, - input, - length - } => { - assert!(length % parallel_factor == 0, "parallel_factor must divide length. figure out a better place to assert this, probably in the type checker fix"); - let index_cell = self.cell_for_temp(builder); - let signal_out = builder.add_constant(1, 1); - - let init_group = builder.add_group("init"); - let zero = builder.add_constant(0, 64); - let init_assignments = build_assignments!(builder; - index_cell["in"] = ? zero["out"]; - index_cell["write_en"] = ? signal_out["out"]; - init_group["done"] = ? index_cell["done"]; - ) - .to_vec(); - init_group.borrow_mut().assignments.extend(init_assignments); - - let cond_group = builder.add_comb_group("cond"); - let array_size_cell = builder.add_constant(*length as u64, 64); - let lt_cell = builder.add_primitive("lt", "std_lt", &vec![64]); - let cond_assignments = build_assignments!(builder; - lt_cell["left"] = ? index_cell["out"]; - lt_cell["right"] = ? array_size_cell["out"]; - ) - .to_vec(); - cond_group.borrow_mut().assignments.extend(cond_assignments); - - let read_group = builder.add_group("read"); - let write_group = builder.add_group("write"); - - let input_cell = self.cell_for_operand(builder, input); // also a memory - let result_cell = self.cell_for_var(builder, *result); - let (_, call_cell) = self.cell_for_call(builder, f, true); - - let read_assignments = build_assignments!(builder; - input_cell["addr0"] = ? index_cell["out"]; - call_cell["arg0"] = ? input_cell["read_data"]; - call_cell["go"] = ? signal_out["out"]; - read_group["done"] = ? call_cell["done"]; - ) - .to_vec(); - read_group.borrow_mut().assignments.extend(read_assignments); - - let write_assignments = build_assignments!(builder; - result_cell["addr0"] = ? index_cell["out"]; - result_cell["write_data"] = ? call_cell["ret"]; - result_cell["write_en"] = ? call_cell["done"]; - write_group["done"] = ? result_cell["done"]; - ) - .to_vec(); - write_group - .borrow_mut() - .assignments - .extend(write_assignments); - - let incr_group = builder.add_group("incr"); - let adder = - builder.add_primitive("adder", "std_add", &vec![64]); - let one = builder.add_constant(1, 64); - let incr_assignments = build_assignments!(builder; - adder["left"] = ? index_cell["out"]; - adder["right"] = ? one["out"]; - index_cell["in"] = ? adder["out"]; - index_cell["write_en"] = ? signal_out["out"]; - incr_group["done"] = ? index_cell["done"]; - ) - .to_vec(); - incr_group.borrow_mut().assignments.extend(incr_assignments); - - seq.stmts.push(calyx_ir::Control::enable(init_group)); - seq.stmts.push(calyx_ir::Control::while_( - lt_cell.borrow().get("out"), - Some(cond_group), - Box::new(calyx_ir::Control::seq(vec![ - calyx_ir::Control::enable(read_group), - calyx_ir::Control::enable(write_group), - calyx_ir::Control::enable(incr_group), - ])) - )); - } - Ir::Call(result_opt, func_name, args) => { - let (_, call_cell) = - self.cell_for_call(builder, func_name, false); - let signal_out = builder.add_constant(1, 1); - let call_group = builder.add_group("call"); - for (i, arg) in args.iter().enumerate() { - let arg_port = - self.cell_for_operand(builder, arg).borrow().get("out"); - let assignment = builder.build_assignment( - call_cell.borrow().get(format!("arg{}", i)), - arg_port, - calyx_ir::Guard::True - ); - call_group.borrow_mut().assignments.push(assignment); - } - let further_assignments = build_assignments!(builder; - call_cell["go"] = ? signal_out["out"]; - call_group["done"] = ? call_cell["done"]; - ); - call_group - .borrow_mut() - .assignments - .extend(further_assignments); - seq.stmts.push(calyx_ir::Control::enable(call_group)); - - if let Some(result) = result_opt { - let use_call_group = builder.add_group("use_call"); - let result_cell = self.cell_for_var(builder, *result); - let signal_out = builder.add_constant(1, 1); - let use_assignments = build_assignments!(builder; - result_cell["in"] = ? call_cell["ret"]; - result_cell["write_en"] = ? signal_out["out"]; - use_call_group["done"] = ? result_cell["done"]; - ); - use_call_group - .borrow_mut() - .assignments - .extend(use_assignments); - seq.stmts.push(calyx_ir::Control::enable(use_call_group)); - } - } - } - } - - fn emit_block( - &mut self, builder: &mut calyx_ir::Builder, seq: &mut calyx_ir::Seq, - block: BasicBlockCell - ) { - for ir in block.as_ref().into_iter() { - self.emit_ir(builder, seq, ir); - } - } - - fn emit_func( - &mut self, calyx_ctx: &mut calyx_ir::Context, label: &Label, - _args: &Vec, ret: &Box, _is_pure: bool, - cfg: &ControlFlowGraph - ) { - let comp_name = calyx_ir::Id::new(label.name.mangle()); - - let mut comp = calyx_ir::Component::new( - comp_name, - self.sig.get(label.name.mangle()).unwrap().clone(), - true, - false, - None - ); - - let mut builder = - calyx_ir::Builder::new(&mut comp, &calyx_ctx.lib).not_generated(); - - let mut main_seq = calyx_ir::Seq { - stmts: vec![], - attributes: calyx_ir::Attributes::default() - }; - - if **ret != Type::Unit { - let func = builder.component.signature.clone(); - self.ret_cell = Some(self.cell_for_temp(&mut builder)); - let ret_cell = self.ret_cell.clone().unwrap(); - let always: Vec> = - build_assignments!(builder; - func["ret"] = ? ret_cell["out"]; - ) - .to_vec(); - builder.add_continuous_assignments(always); - } - - // for block in cfg.blocks() { - // self.emit_block(block); - // } - assert_eq!(1, cfg.size(), "CalyxBackend requires structured IR only in the entry block, but other blocks were found in the CFG"); - self.env.push(); - self.call_env.push(); - self.param_env = 0; - self.emit_block(&mut builder, &mut main_seq, cfg.entry()); - self.env.pop(); - self.call_env.pop(); - - *comp.control.borrow_mut() = calyx_ir::Control::Seq(main_seq); - - calyx_ctx.components.push(comp); - } -} - -pub struct CalyxBackendInput { - pub lib_path: String, - pub calyx_output: calyx_utils::OutputFile, - pub verilog_output: calyx_utils::OutputFile -} - -impl PulsarBackend for CalyxBackend { - type ExtraInput = CalyxBackendInput; - type Error = calyx_utils::Error; - - fn new() -> Self { - Self { - env: Environment::new(), - call_env: Environment::new(), - sig: HashMap::new(), - ret_cell: None, - param_env: 0, - temp_count: 0 - } - } - - fn run( - &mut self, code: Vec, input: Self::ExtraInput - ) -> Result<(), Self::Error> { - let mut calyx_ctx = CalyxBackend::stdlib_context(input.lib_path); - - // Create a calyx program from the IR - // - Step 1: load signatures - for generated_top_level in &code { - match generated_top_level { - GeneratedTopLevel::Function { - label, - args, - ret, - is_pure: _, - cfg: _ - } => self.cache_func_sig(label, args, ret) - } - } - // - Step 2: emit generated IR - for generated_top_level in &code { - match generated_top_level { - GeneratedTopLevel::Function { - label, - args, - ret, - is_pure, - cfg - } => self.emit_func( - &mut calyx_ctx, - label, - args, - ret, - *is_pure, - cfg - ) - } - } - - // Debug print - calyx_ir::Printer::write_context( - &calyx_ctx, - false, - &mut input.calyx_output.get_write() - ) - .unwrap(); - - calyx_ctx.entrypoint = calyx_ctx - .components - .iter() - .find(|comp| comp.name.to_string().contains("_pulsar_Smain")) - .expect("No main function provided") - .name; - - // Perform optimization passes - let pm = calyx_opt::pass_manager::PassManager::default_passes()?; - let backend_conf = calyx_ir::BackendConf { - synthesis_mode: false, - enable_verification: false, - flat_assign: true, - emit_primitive_extmodules: false - }; - calyx_ctx.bc = backend_conf; - pm.execute_plan( - &mut calyx_ctx, - &["all".to_string()], - &["canonicalize".to_string()], - false - )?; - - // Emit to Verilog - let backend = calyx_backend::VerilogBackend; - backend.run(calyx_ctx, input.verilog_output)?; - Ok(()) - } -} diff --git a/crates/pulsar-backend/src/lib.rs b/crates/pulsar-backend/src/lib.rs index edfe0d1..a0468c2 100644 --- a/crates/pulsar-backend/src/lib.rs +++ b/crates/pulsar-backend/src/lib.rs @@ -5,17 +5,27 @@ //! Copyright (C) 2024 Ethan Uppal. All rights reserved. use pulsar_ir::generator::GeneratedTopLevel; +use std::path::PathBuf; -pub mod calyx_backend; +pub mod calyx; // This interface hasn't been finalized yet, so it is quite sloppy as written +pub enum Output { + Stdout, + Stderr, + File(PathBuf) +} + pub trait PulsarBackend { - type ExtraInput; + type InitInput; type Error; - fn new() -> Self; + /// Initializes the backend. + fn new(input: Self::InitInput) -> Self; + + /// Consumes the backend and produces an output. fn run( - &mut self, code: Vec, input: Self::ExtraInput + self, code: Vec, output: Output ) -> Result<(), Self::Error>; } diff --git a/crates/pulsar-ir/src/label.rs b/crates/pulsar-ir/src/label.rs index f173f9a..faff707 100644 --- a/crates/pulsar-ir/src/label.rs +++ b/crates/pulsar-ir/src/label.rs @@ -2,6 +2,10 @@ use pulsar_frontend::ty::Type; use std::fmt::{Display, Formatter}; +/// If one exists, the start symbol for a pulsar program is guaranteed to begin +/// with the following. +pub const MAIN_SYMBOL_PREFIX: &str = "_pulsar_Smain"; + pub struct LabelName { unmangled: String, mangled: String, diff --git a/src/main.rs b/src/main.rs index 2b2d44c..c964750 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,16 @@ // Copyright (C) 2024 Ethan Uppal. All rights reserved. use pulsar_backend::{ - calyx_backend::{CalyxBackend, CalyxBackendInput}, - PulsarBackend + calyx::{CalyxBackend, CalyxBackendInput}, + Output, PulsarBackend }; use pulsar_frontend::{ lexer::Lexer, parser::Parser, static_analysis::StaticAnalyzer }; use pulsar_ir::generator::Generator; use pulsar_utils::{error::ErrorManager, loc::Source}; -use std::{cell::RefCell, env, fs, io::stdout, process::Command, rc::Rc}; +use std::{ + cell::RefCell, env, fs, io::stdout, path::PathBuf, process::Command, rc::Rc +}; fn handle_errors(error_manager: Rc>) -> Result<(), ()> { if error_manager.borrow().has_errors() { @@ -58,16 +60,11 @@ pub fn main() -> Result<(), ()> { .trim() .to_string(); - let mut calyx_backend = CalyxBackend::new(); + let calyx_backend = CalyxBackend::new(CalyxBackendInput { + lib_path: PathBuf::from(calyx_root) + }); calyx_backend - .run( - generated_code, - CalyxBackendInput { - lib_path: calyx_root, - calyx_output: calyx_utils::OutputFile::Stderr, - verilog_output: calyx_utils::OutputFile::Stdout - } - ) + .run(generated_code, Output::Stdout) .map_err(|err| { println!("{:?}\n", err); })?;