Skip to content

Commit

Permalink
Add dyn_mems as primitives (#2111)
Browse files Browse the repository at this point in the history
* first pass at adding dynamic memories

* add dyn-mems to primitve library

* add note that dyn.sv implementation is a short term solution

* Update `yxi` to support `dyn_mem`s (#2112)

* add yxi support for dyn-mems in calyx-ir-utils

* make clippy happy

* add yxi test for dyn-mems

* formatting
  • Loading branch information
nathanielnrn authored Jun 7, 2024
1 parent a1d99c2 commit bb73c16
Show file tree
Hide file tree
Showing 6 changed files with 518 additions and 37 deletions.
73 changes: 37 additions & 36 deletions calyx-ir/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Helpers used to examine calyx programs. Used in Xilinx and Yxi backends among others.
use super::{BoolAttr, Cell, Component, RRC};
use calyx_utils::Id;
#[cfg(feature = "yxi")]
use serde::Serialize;

Expand Down Expand Up @@ -29,6 +30,7 @@ pub fn external_and_ref_memories_cells(comp: &Component) -> Vec<RRC<Cell>> {
pub enum MemoryType {
Combinational,
Sequential,
Dynamic,
}

/// Parameters for std memories
Expand All @@ -51,53 +53,36 @@ pub trait GetMemInfo {

impl GetMemInfo for Vec<RRC<Cell>> {
fn get_mem_info(&self) -> Vec<MemInfo> {
//Params of dimensions for multi dimensional memories. d1 memories use `"SIZE"`.
let dimension_params = ["D0_SIZE", "D1_SIZE", "D2_SIZE", "D3_SIZE"];
self.iter()
.map(|cr| {
let mem = cr.borrow();
let mut dimension_sizes: Vec<u64> = Vec::new();
let mut idx_sizes: Vec<u64> = Vec::new();
let dimensions: u64;
let mem_cell_type = mem.prototype.get_name().unwrap(); //i.e. "comb_mem_d1"
let mem_type : MemoryType = if mem_cell_type.to_string().contains("comb") {
MemoryType::Combinational
} else {
MemoryType::Sequential
};
MemoryType::Combinational
} else if mem_cell_type.to_string().contains("seq") {
MemoryType::Sequential
} else {
MemoryType::Dynamic
};

match mem_cell_type.as_ref() {
"comb_mem_d1" | "seq_mem_d1" => {
let dimensions = dimension_count(mem_cell_type);
if dimensions == 1{
dimension_sizes.push(mem.get_parameter("SIZE").unwrap());
dimensions = 1;
}
"comb_mem_d2" | "seq_mem_d2" => {
dimension_sizes.push(mem.get_parameter("D0_SIZE").unwrap());
dimension_sizes.push(mem.get_parameter("D1_SIZE").unwrap());
dimensions = 2;
idx_sizes.push(mem.get_parameter("IDX_SIZE").unwrap());
}
"comb_mem_d3" | "seq_mem_d3" => {
dimension_sizes.push(mem.get_parameter("D0_SIZE").unwrap());
dimension_sizes.push(mem.get_parameter("D1_SIZE").unwrap());
dimension_sizes.push(mem.get_parameter("D2_SIZE").unwrap());
dimensions = 3;
else if dimensions > 1 && dimensions <= 4{
for i in 0..dimensions {
dimension_sizes.push(mem.get_parameter(dimension_params[i as usize]).unwrap());
idx_sizes.push(mem.get_parameter(format!("D{}_IDX_SIZE",i)).unwrap());
}
}
"comb_mem_d4" | "seq_mem_d4" => {
dimension_sizes.push(mem.get_parameter("D0_SIZE").unwrap());
dimension_sizes.push(mem.get_parameter("D1_SIZE").unwrap());
dimension_sizes.push(mem.get_parameter("D2_SIZE").unwrap());
dimension_sizes.push(mem.get_parameter("D3_SIZE").unwrap());
dimensions = 4;
}
_ => {
panic!("cell `{}' marked with `@external' but is not a memory primitive.", mem.name())
}
};
if dimensions == 1 {
idx_sizes.push(mem.get_parameter("IDX_SIZE").unwrap());
} else {
for i in 0..dimensions {
idx_sizes.push(mem.get_parameter(format!("D{}_IDX_SIZE",i)).unwrap());
}
}
else{
unreachable!("It is not expected for memory primitives to have more than 4 dimensions.");
};
let total_size = dimension_sizes.clone().iter().product();
MemInfo {
memory_type: mem_type,
Expand All @@ -117,3 +102,19 @@ impl GetMemInfo for Component {
external_and_ref_memories_cells(self).get_mem_info()
}
}

fn dimension_count(mem_id: Id) -> u64 {
let mem_name = mem_id.as_ref();

if mem_name.contains("d1") {
1
} else if mem_name.contains("d2") {
2
} else if mem_name.contains("d3") {
3
} else if mem_name.contains("d4") {
4
} else {
panic!("Cell {} does not seem to be a memory primitive. Memory primitives are expected to have 1-4 dimensions inclusive.", mem_name);
}
}
4 changes: 3 additions & 1 deletion calyx-stdlib/src/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ load_prims! { BINARY_OPERATORS, "binary_operators.futil", "binary_operators.sv"
load_prims! { MATH, "math.futil", "math.sv" }
load_prims! { COMB_MEMORIES, "memories/comb.futil", "memories/comb.sv" }
load_prims! { SEQ_MEMORIES, "memories/seq.futil", "memories/seq.sv" }
load_prims! { DYN_MEMORIES, "memories/dyn.futil", "memories/dyn.sv" }
load_prims! { PIPELINED, "pipelined.futil", "pipelined.sv" }
load_prims! { STALLABLE, "stallable.futil", "stallable.sv" }
load_prims! { SYNC, "sync.futil", "sync.sv" }
Expand All @@ -32,12 +33,13 @@ load_prims! { SYNC, "sync.futil", "sync.sv" }
pub const COMPILE_LIB: (&str, &str) =
("compile.futil", include_str!("../primitives/compile.futil"));

pub const KNOWN_LIBS: [(&str, [(&str, &str); 2]); 8] = [
pub const KNOWN_LIBS: [(&str, [(&str, &str); 2]); 9] = [
("core", CORE),
("binary_operators", BINARY_OPERATORS),
("math", MATH),
("comb_memories", COMB_MEMORIES),
("seq_memories", SEQ_MEMORIES),
("dyn_memories", DYN_MEMORIES),
("pipelined", PIPELINED),
("stallable", STALLABLE),
("sync", SYNC),
Expand Down
60 changes: 60 additions & 0 deletions primitives/memories/dyn.futil
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Single-ported memories with dynamic read and write latencies.
extern "dyn.sv" {
primitive dyn_mem_d1[WIDTH, SIZE, IDX_SIZE](
@clk clk: 1,
@reset reset: 1,
@write_together(1) @data addr0: IDX_SIZE,
@write_together(1) @go(1) content_en: 1,
// Write ports
@write_together(2) write_en: 1,
@write_together(2) @data write_data: WIDTH
) -> (
@stable read_data: WIDTH,
@done(1) done: 1
);

primitive dyn_mem_d2[WIDTH, D0_SIZE, D1_SIZE, D0_IDX_SIZE, D1_IDX_SIZE](
@clk clk: 1,
@reset reset: 1,
@write_together(1) @data addr0: D0_IDX_SIZE,
@write_together(1) @data addr1: D1_IDX_SIZE,
@write_together(1) @go(1) content_en: 1,
// Write ports
@write_together(2) write_en: 1,
@write_together(2) @data write_data: WIDTH
) -> (
@stable read_data: WIDTH,
@done(1) done: 1
);

primitive dyn_mem_d3[WIDTH, D0_SIZE, D1_SIZE, D2_SIZE, D0_IDX_SIZE, D1_IDX_SIZE, D2_IDX_SIZE](
@clk clk: 1,
@reset reset: 1,
@write_together(1) @data addr0: D0_IDX_SIZE,
@write_together(1) @data addr1: D1_IDX_SIZE,
@write_together(1) @data addr2: D2_IDX_SIZE,
@write_together(1) @go(1) content_en: 1,
// Write ports
@write_together(2) write_en: 1,
@write_together(2) @data write_data: WIDTH
) -> (
@stable read_data: WIDTH,
@done(1) done: 1
);

primitive dyn_mem_d4[WIDTH, D0_SIZE, D1_SIZE, D2_SIZE, D3_SIZE, D0_IDX_SIZE, D1_IDX_SIZE, D2_IDX_SIZE, D3_IDX_SIZE](
@clk clk: 1,
@reset reset: 1,
@write_together(1) @data addr0: D0_IDX_SIZE,
@write_together(1) @data addr1: D1_IDX_SIZE,
@write_together(1) @data addr2: D2_IDX_SIZE,
@write_together(1) @data addr3: D3_IDX_SIZE,
@write_together(1) @go(1) content_en: 1,
// Write ports
@write_together(2) write_en: 1,
@write_together(2) @data write_data: WIDTH
) -> (
@stable read_data: WIDTH,
@done(1) done: 1
);
}
188 changes: 188 additions & 0 deletions primitives/memories/dyn.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/**
Implements a memory with sequential reads and writes.
- Both reads and writes are not guaranteed to have a given latency.
- Attempting to read and write at the same time is an error.
- The out signal is registered to the last value requested by the read_en signal.
- The out signal is undefined once write_en is asserted.
NOTE(nate): In practice we expect this implementation to be single cycle,
but should not be relied on as such.
In particular we probably eventually want to have `dyn_mems` exist as "virtual operators."
Which have a flexible latency, where the compiler can decide upon actual latency.
See #2111 (PR introducing this: https://github.com/calyxir/calyx/pull/2111)
and a more in depth discussion #1151 (https://github.com/calyxir/calyx/issues/1151)
*/
module dyn_mem_d1 #(
parameter WIDTH = 32,
parameter SIZE = 16,
parameter IDX_SIZE = 4
) (
// Common signals
input wire logic clk,
input wire logic reset,
input wire logic [IDX_SIZE-1:0] addr0,
input wire logic content_en,
output logic done,

// Read signal
output logic [ WIDTH-1:0] read_data,

// Write signals
input wire logic [ WIDTH-1:0] write_data,
input wire logic write_en
);
// Internal memory
logic [WIDTH-1:0] mem[SIZE-1:0];

// Register for the read output
logic [WIDTH-1:0] read_out;
assign read_data = read_out;

// Read value from the memory
always_ff @(posedge clk) begin
if (reset) begin
read_out <= '0;
end else if (content_en && !write_en) begin
/* verilator lint_off WIDTH */
read_out <= mem[addr0];
end else if (content_en && write_en) begin
// Explicitly clobber the read output when a write is performed
read_out <= 'x;
end else begin
read_out <= read_out;
end
end

// Propagate the done signal
always_ff @(posedge clk) begin
if (reset) begin
done <= '0;
end else if (content_en) begin
done <= '1;
end else begin
done <= '0;
end
end

// Write value to the memory
always_ff @(posedge clk) begin
if (!reset && content_en && write_en)
mem[addr0] <= write_data;
end

// Check for out of bounds access
`ifdef VERILATOR
always_comb begin
if (content_en && !write_en)
if (addr0 >= SIZE)
$error(
"comb_mem_d1: Out of bounds access\n",
"addr0: %0d\n", addr0,
"SIZE: %0d", SIZE
);
end
`endif
endmodule

module dyn_mem_d2 #(
parameter WIDTH = 32,
parameter D0_SIZE = 16,
parameter D1_SIZE = 16,
parameter D0_IDX_SIZE = 4,
parameter D1_IDX_SIZE = 4
) (
// Common signals
input wire logic clk,
input wire logic reset,
input wire logic [D0_IDX_SIZE-1:0] addr0,
input wire logic [D1_IDX_SIZE-1:0] addr1,
input wire logic content_en,
output logic done,

// Read signal
output logic [WIDTH-1:0] read_data,

// Write signals
input wire logic write_en,
input wire logic [ WIDTH-1:0] write_data
);
wire [D0_IDX_SIZE+D1_IDX_SIZE-1:0] addr;
assign addr = addr0 * D1_SIZE + addr1;

dyn_mem_d1 #(.WIDTH(WIDTH), .SIZE(D0_SIZE * D1_SIZE), .IDX_SIZE(D0_IDX_SIZE+D1_IDX_SIZE)) mem
(.clk(clk), .reset(reset), .addr0(addr),
.content_en(content_en), .read_data(read_data), .write_data(write_data), .write_en(write_en),
.done(done));
endmodule

module dyn_mem_d3 #(
parameter WIDTH = 32,
parameter D0_SIZE = 16,
parameter D1_SIZE = 16,
parameter D2_SIZE = 16,
parameter D0_IDX_SIZE = 4,
parameter D1_IDX_SIZE = 4,
parameter D2_IDX_SIZE = 4
) (
// Common signals
input wire logic clk,
input wire logic reset,
input wire logic [D0_IDX_SIZE-1:0] addr0,
input wire logic [D1_IDX_SIZE-1:0] addr1,
input wire logic [D2_IDX_SIZE-1:0] addr2,
input wire logic content_en,
output logic done,

// Read signal
output logic [WIDTH-1:0] read_data,

// Write signals
input wire logic write_en,
input wire logic [ WIDTH-1:0] write_data
);
wire [D0_IDX_SIZE+D1_IDX_SIZE+D2_IDX_SIZE-1:0] addr;
assign addr = addr0 * (D1_SIZE * D2_SIZE) + addr1 * (D2_SIZE) + addr2;

dyn_mem_d1 #(.WIDTH(WIDTH), .SIZE(D0_SIZE * D1_SIZE * D2_SIZE), .IDX_SIZE(D0_IDX_SIZE+D1_IDX_SIZE+D2_IDX_SIZE)) mem
(.clk(clk), .reset(reset), .addr0(addr),
.content_en(content_en), .read_data(read_data), .write_data(write_data), .write_en(write_en),
.done(done));
endmodule

module dyn_mem_d4 #(
parameter WIDTH = 32,
parameter D0_SIZE = 16,
parameter D1_SIZE = 16,
parameter D2_SIZE = 16,
parameter D3_SIZE = 16,
parameter D0_IDX_SIZE = 4,
parameter D1_IDX_SIZE = 4,
parameter D2_IDX_SIZE = 4,
parameter D3_IDX_SIZE = 4
) (
// Common signals
input wire logic clk,
input wire logic reset,
input wire logic [D0_IDX_SIZE-1:0] addr0,
input wire logic [D1_IDX_SIZE-1:0] addr1,
input wire logic [D2_IDX_SIZE-1:0] addr2,
input wire logic [D3_IDX_SIZE-1:0] addr3,
input wire logic content_en,
output logic done,

// Read signal
output logic [WIDTH-1:0] read_data,

// Write signals
input wire logic write_en,
input wire logic [ WIDTH-1:0] write_data
);
wire [D0_IDX_SIZE+D1_IDX_SIZE+D2_IDX_SIZE+D3_IDX_SIZE-1:0] addr;
assign addr = addr0 * (D1_SIZE * D2_SIZE * D3_SIZE) + addr1 * (D2_SIZE * D3_SIZE) + addr2 * (D3_SIZE) + addr3;

dyn_mem_d1 #(.WIDTH(WIDTH), .SIZE(D0_SIZE * D1_SIZE * D2_SIZE * D3_SIZE), .IDX_SIZE(D0_IDX_SIZE+D1_IDX_SIZE+D2_IDX_SIZE+D3_IDX_SIZE)) mem
(.clk(clk), .reset(reset), .addr0(addr),
.content_en(content_en), .read_data(read_data), .write_data(write_data), .write_en(write_en),
.done(done));
endmodule
Loading

0 comments on commit bb73c16

Please sign in to comment.