Skip to content

Commit

Permalink
Create IDL Backend (#1729)
Browse files Browse the repository at this point in the history
* WIP, add to backend options

* add serde_json dependency for IDL backend

* WIP work on IDL

* Minimal working version of IDL generator

Some open questions:
Naming and placement. This is probably not xilinx specific so should be
moved up a level into just src. Also, naming is generic and not good and
should be changed

TODO: Only works withs d1 memories, but should not be hard to alter

* Let IDL generator handle multi-dim mems

Previously could only handle 1 dimension mems.
NOTE: This flattens dimensions into a single size variable that is spit
out. However it may be useful to maintain info about shape of memory?
Need to think about this more

* add basic runt tests

* clippy formatting

* rename idl to yxi and make serde_json call shorter

* add error conversion for serde_json for yxi backend

* update runt tests for yxi

* update cargo lock with serde_json

* formatting

* move yxi up a directory

* WIP of extracting utility functions

* Refactor toplevel and yxi util functions

Moved things into calyx-ir/utils. Things like get_mem_info and functions
that get memories marked as external, along with their names

* clippy and formatting
  • Loading branch information
nathanielnrn authored Oct 10, 2023
1 parent 746f6a8 commit 062b9a8
Show file tree
Hide file tree
Showing 16 changed files with 508 additions and 57 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ lazy_static = "1"
linked-hash-map = "0.5"
smallvec = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_sexpr = "0.1.0"
serde_with = "3"
pest = "2"
Expand Down
1 change: 1 addition & 0 deletions calyx-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ string-interner.workspace = true
itertools.workspace = true
linked-hash-map.workspace = true
serde = { workspace = true }
serde_json.workspace = true
serde_with = { workspace = true, optional = true }
serde_sexpr = { workspace = true, optional = true }
smallvec.workspace = true
Expand Down
3 changes: 3 additions & 0 deletions calyx-backend/src/backend_opt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum BackendOpt {
Mlir,
Resources,
Sexp,
Yxi,
None,
}

Expand All @@ -26,6 +27,7 @@ fn backends() -> Vec<(&'static str, BackendOpt)> {
("mlir", BackendOpt::Mlir),
("resources", BackendOpt::Resources),
("sexp", BackendOpt::Sexp),
("yxi", BackendOpt::Yxi),
("none", BackendOpt::None),
]
}
Expand Down Expand Up @@ -67,6 +69,7 @@ impl ToString for BackendOpt {
Self::Verilog => "verilog",
Self::Xilinx => "xilinx",
Self::XilinxXml => "xilinx-xml",
Self::Yxi => "yxi",
Self::Calyx => "calyx",
Self::None => "none",
}
Expand Down
2 changes: 2 additions & 0 deletions calyx-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
mod backend_opt;
mod traits;
mod verilog;
mod yxi;

pub use backend_opt::BackendOpt;
pub use traits::Backend;
pub use verilog::VerilogBackend;
pub use yxi::YxiBackend;

#[cfg(feature = "mlir")]
mod mlir;
Expand Down
75 changes: 18 additions & 57 deletions calyx-backend/src/xilinx/toplevel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::{
};
use crate::traits::Backend;
use calyx_ir as ir;
use calyx_ir::utils::GetMemInfo;
use calyx_utils::{CalyxResult, Error};
use vast::v05::ast as v;

Expand Down Expand Up @@ -37,22 +38,22 @@ impl Backend for XilinxInterfaceBackend {
.find(|c| c.name == prog.entrypoint)
.unwrap();

let memories = external_memories(toplevel);
let memories = ir::utils::external_memories_names(toplevel);
if memories.is_empty() {
return Err(Error::misc(
"Program has no memories marked with attribute @external.".to_owned() +
" Please make sure that at least one memory is marked as @external."));
}

let mem_info = get_mem_info(toplevel);
let mem_info = toplevel.get_mem_info();

let mut modules = vec![top_level(toplevel)];
for (i, mem) in mem_info.iter().enumerate() {
modules.push(bram(
&format!("SINGLE_PORT_BRAM_{}", i),
mem.width,
mem.size,
mem.idx_size,
mem.idx_sizes[0],
))
}

Expand All @@ -71,7 +72,7 @@ impl Backend for XilinxInterfaceBackend {
64,
mem.width,
mem.size,
mem.idx_size,
mem.idx_sizes[0],
))
}

Expand All @@ -93,61 +94,21 @@ impl Backend for XilinxInterfaceBackend {
}
}

fn external_memories_cells(comp: &ir::Component) -> Vec<ir::RRC<ir::Cell>> {
comp.cells
.iter()
// find external memories
.filter(|cell_ref| {
let cell = cell_ref.borrow();
// NOTE(rachit): We only support one dimensional std_mem_d1 memories
if cell.attributes.has(ir::BoolAttr::External) {
if !cell.is_primitive(Some("std_mem_d1")) {
panic!("cell `{}' marked with `@external' but is not a std_mem_d1. The AXI generator currently only supports `std_mem_d1'", cell.name())
} else {
true
}
} else {
false
}
})
.cloned()
.collect()
}

/// Parameters for single dimensional memory
struct MemInfo {
width: u64,
size: u64,
idx_size: u64,
}

// Returns a vector of tuples containing external memory info of [comp] of form:
// [(WIDTH, SIZE, IDX_SIZE)]
fn get_mem_info(comp: &ir::Component) -> Vec<MemInfo> {
external_memories_cells(comp)
.iter()
.map(|cr| {
let cell = cr.borrow();
MemInfo {
width: cell.get_parameter("WIDTH").unwrap(),
size: cell.get_parameter("SIZE").unwrap(),
idx_size: cell.get_parameter("IDX_SIZE").unwrap(),
}
})
.collect()
}

// Returns Vec<String> of memory names
fn external_memories(comp: &ir::Component) -> Vec<String> {
external_memories_cells(comp)
.iter()
.map(|cell_ref| cell_ref.borrow().name().to_string())
.collect()
// Gets all memory cells in top level marked external.
//Panics if not all memories are 1-d
fn external_1d_memories_cells(comp: &ir::Component) -> Vec<ir::RRC<ir::Cell>> {
let memories = ir::utils::external_memories_cells(comp);
for memory in memories.iter() {
if !memory.borrow().is_primitive(Some("std_mem_d1")) {
panic!("cell `{}' marked with `@external' but is not a std_mem_d1. The AXI generator currently only supports `std_mem_d1'", memory.borrow().name())
}
}
memories
}

fn top_level(toplevel: &ir::Component) -> v::Module {
let memories = &external_memories(toplevel);
let mem_info = get_mem_info(toplevel);
let memories = &ir::utils::external_memories_names(toplevel);
let mem_info = &external_1d_memories_cells(toplevel).get_mem_info();
assert!(!memories.is_empty()); // At least 1 memory should exist within the toplevel
let mut module = v::Module::new("Toplevel");

Expand Down Expand Up @@ -232,7 +193,7 @@ fn top_level(toplevel: &ir::Component) -> v::Module {
let width = mem_info[idx].width;
module.add_decl(v::Decl::new_wire(&write_data, width));
module.add_decl(v::Decl::new_wire(&read_data, width));
module.add_decl(v::Decl::new_wire(&addr0, mem_info[idx].idx_size));
module.add_decl(v::Decl::new_wire(&addr0, mem_info[idx].idx_sizes[0]));
module.add_decl(v::Decl::new_wire(&write_en, 1));
module.add_decl(v::Decl::new_wire(&done, 1));

Expand Down
73 changes: 73 additions & 0 deletions calyx-backend/src/yxi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use crate::traits::Backend;
use calyx_ir as ir;
use calyx_ir::utils::GetMemInfo;
use calyx_utils::CalyxResult;
use serde::Serialize;
/// Backend that generates the YXI Interface Definition Language.
/// YXI aims to be a description of toplevel hardware modules that we can then consume
/// to create things like AXI wrappers on arbitrary programs
#[derive(Default)]
pub struct YxiBackend;

#[derive(Serialize)]
struct ProgramInterface<'a> {
toplevel: &'a str,
memories: Vec<Memory<'a>>,
}

#[derive(Serialize)]
struct Memory<'a> {
name: &'a str,
width: u64,
size: u64, //number of cells in memory
}

impl Backend for YxiBackend {
fn name(&self) -> &'static str {
"yxi"
}

fn validate(_ctx: &ir::Context) -> CalyxResult<()> {
Ok(())
}

fn link_externs(
_prog: &ir::Context,
_write: &mut calyx_utils::OutputFile,
) -> CalyxResult<()> {
Ok(())
}

fn emit(
prog: &ir::Context,
file: &mut calyx_utils::OutputFile,
) -> CalyxResult<()> {
let toplevel = prog
.components
.iter()
.find(|comp| comp.name == prog.entrypoint)
.unwrap();

let memory_names = ir::utils::external_memories_names(toplevel);
let mem_infos = toplevel.get_mem_info();

let memories: Vec<Memory> = memory_names
.iter()
.zip(mem_infos.iter())
.map(|(memory_name, mem_info)| Memory {
name: memory_name,
width: mem_info.width,
size: mem_info.size,
})
.collect();

let program_interface = ProgramInterface {
toplevel: toplevel.name.as_ref(),
memories,
};

serde_json::to_writer_pretty(file.get_write(), &program_interface)?;

Ok(())
}
}
2 changes: 2 additions & 0 deletions calyx-ir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ mod macros;

/// Serializer methods for IR nodes.
pub mod serializers;

pub mod utils;
94 changes: 94 additions & 0 deletions calyx-ir/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//! Helpers used to examine calyx programs. Used in Xilinx and Yxi backends among others.
use super::{BoolAttr, Cell, Component, RRC};
// Returns Vec<String> of memory names
pub fn external_memories_names(comp: &Component) -> Vec<String> {
external_memories_cells(comp)
.iter()
.map(|cell_ref| cell_ref.borrow().name().to_string())
.collect()
}

// Gets all memory cells in top level marked external.
pub fn external_memories_cells(comp: &Component) -> Vec<RRC<Cell>> {
comp.cells
.iter()
// find external memories
.filter(|cell_ref| {
let cell = cell_ref.borrow();
cell.attributes.has(BoolAttr::External)
})
.cloned()
.collect()
}

/// Parameters for std memories
pub struct MemInfo {
pub width: u64,
pub size: u64,
//idx port width, in case size is ambiguous
pub idx_sizes: Vec<u64>,
}

// Returns a vector of tuples containing external memory info of [comp] of form:
// [(WIDTH, SIZE, IDX_SIZE)]
pub trait GetMemInfo {
fn get_mem_info(&self) -> Vec<MemInfo>;
}

impl GetMemInfo for Vec<RRC<Cell>> {
fn get_mem_info(&self) -> Vec<MemInfo> {
self.iter()
.map(|cr| {
let mem = cr.borrow();
let mem_size: u64;
let mut idx_sizes: Vec<u64> = Vec::new();
let idx_count: u64;
match mem.prototype.get_name().unwrap().as_ref() {
"std_mem_d1" | "seq_mem_d1" => {
mem_size = mem.get_parameter("SIZE").unwrap();
idx_count = 1;
}
"std_mem_d2" | "seq_mem_d2" => {
mem_size = mem.get_parameter("D0_SIZE").unwrap()
* mem.get_parameter("D1_SIZE").unwrap();
idx_count = 2;
}
"std_mem_d3" | "seq_mem_d3" => {
mem_size = mem.get_parameter("D0_SIZE").unwrap()
* mem.get_parameter("D1_SIZE").unwrap()
* mem.get_parameter("D2_SIZE").unwrap();
idx_count = 3;
}
"std_mem_d4" | "seq_mem_d4" => {
mem_size = mem.get_parameter("D0_SIZE").unwrap()
* mem.get_parameter("D1_SIZE").unwrap()
* mem.get_parameter("D2_SIZE").unwrap()
* mem.get_parameter("D3_SIZE").unwrap();
idx_count = 4;
}
_ => {
panic!("cell `{}' marked with `@external' but is not a memory primitive.", mem.name())
}
};
if idx_count == 1 {
idx_sizes.push(mem.get_parameter("IDX_SIZE").unwrap());
} else {
for i in 1..idx_count {
idx_sizes.push(mem.get_parameter(format!("D{}_IDX_SIZE",i)).unwrap());
}
}
MemInfo {
width: mem.get_parameter("WIDTH").unwrap(),
size: mem_size,
idx_sizes
}
})
.collect()
}
}

impl GetMemInfo for Component {
fn get_mem_info(&self) -> Vec<MemInfo> {
external_memories_cells(self).get_mem_info()
}
}
2 changes: 2 additions & 0 deletions calyx-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ repository.workspace = true
homepage.workspace = true
categories.workspace = true
readme.workspace = true
serde_json.workspace = true

[features]
default = []
serialize = ["dep:serde", "symbol_table/serde"]

[dependencies]
serde = { workspace = true, features = ["derive"], optional = true }
serde_json.workspace = true
atty.workspace = true
string-interner.workspace = true
itertools.workspace = true
Expand Down
6 changes: 6 additions & 0 deletions calyx-utils/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,9 @@ impl From<std::io::Error> for Error {
Error::write_error(format!("IO Error: {}", e))
}
}

impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::write_error(format!("serde_json Error: {}", e))
}
}
Loading

0 comments on commit 062b9a8

Please sign in to comment.