diff --git a/calyx-backend/src/firrtl.rs b/calyx-backend/src/firrtl.rs index c0b8d4ef04..ab9b11dd98 100644 --- a/calyx-backend/src/firrtl.rs +++ b/calyx-backend/src/firrtl.rs @@ -109,7 +109,15 @@ fn emit_component( // Cells for cell in comp.cells.iter() { let cell_borrowed = cell.as_ref().borrow(); - if cell_borrowed.type_name().is_some() { + let is_external = cell + .borrow() + .get_attribute(ir::BoolAttr::External) + .is_some(); + if is_external { + // The FIRRTL compiler cannot read/write memories, so we must use ref. + panic!("FIRRTL backend only works on ref, not @external!"); + } + if cell_borrowed.type_name().is_some() && !is_external { let module_name = match &cell_borrowed.prototype { ir::CellType::Primitive { name, diff --git a/calyx-ir/src/utils.rs b/calyx-ir/src/utils.rs index 4ea6e254c6..816e9bb5a3 100644 --- a/calyx-ir/src/utils.rs +++ b/calyx-ir/src/utils.rs @@ -57,14 +57,14 @@ impl GetMemInfo for Vec> { let mut dimension_sizes: Vec = Vec::new(); let mut idx_sizes: Vec = 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.is_comb_cell() { + 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 }; - match mem.prototype.get_name().unwrap().as_ref() { + match mem_cell_type.as_ref() { "comb_mem_d1" | "seq_mem_d1" => { dimension_sizes.push(mem.get_parameter("SIZE").unwrap()); dimensions = 1; diff --git a/fud2/rsrc/memories.sv b/fud2/rsrc/memories.sv new file mode 100644 index 0000000000..e9257ae494 --- /dev/null +++ b/fud2/rsrc/memories.sv @@ -0,0 +1,437 @@ +module comb_mem_d1 #( + parameter WIDTH = 32, + parameter SIZE = 16, + parameter IDX_SIZE = 4 +) ( + input wire logic [IDX_SIZE-1:0] addr0, + input wire logic [ WIDTH-1:0] write_data, + output logic [ WIDTH-1:0] read_data, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic done +); + + logic [WIDTH-1:0] mem[SIZE-1:0]; + + /* verilator lint_off WIDTH */ + assign read_data = mem[addr0]; + + always_ff @(posedge clk) begin + if (reset) + done <= '0; + else if (write_en) + done <= '1; + else + done <= '0; + end + + always_ff @(posedge clk) begin + if (!reset && write_en) + mem[addr0] <= write_data; + end + + // Check for out of bounds access + `ifdef VERILATOR + always_comb begin + if (addr0 >= SIZE) + $error( + "comb_mem_d1: Out of bounds access\n", + "addr0: %0d\n", addr0, + "SIZE: %0d", SIZE + ); + end + `endif +endmodule + +module comb_mem_d2 #( + parameter WIDTH = 32, + parameter D0_SIZE = 16, + parameter D1_SIZE = 16, + parameter D0_IDX_SIZE = 4, + parameter D1_IDX_SIZE = 4 +) ( + input wire logic [D0_IDX_SIZE-1:0] addr0, + input wire logic [D1_IDX_SIZE-1:0] addr1, + input wire logic [ WIDTH-1:0] write_data, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [ WIDTH-1:0] read_data, + output logic done +); + + /* verilator lint_off WIDTH */ + logic [WIDTH-1:0] mem[D0_SIZE-1:0][D1_SIZE-1:0]; + + assign read_data = mem[addr0][addr1]; + + always_ff @(posedge clk) begin + if (reset) + done <= '0; + else if (write_en) + done <= '1; + else + done <= '0; + end + + always_ff @(posedge clk) begin + if (!reset && write_en) + mem[addr0][addr1] <= write_data; + end + + // Check for out of bounds access + `ifdef VERILATOR + always_comb begin + if (addr0 >= D0_SIZE) + $error( + "comb_mem_d2: Out of bounds access\n", + "addr0: %0d\n", addr0, + "D0_SIZE: %0d", D0_SIZE + ); + if (addr1 >= D1_SIZE) + $error( + "comb_mem_d2: Out of bounds access\n", + "addr1: %0d\n", addr1, + "D1_SIZE: %0d", D1_SIZE + ); + end + `endif +endmodule + +module comb_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 +) ( + 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 [ WIDTH-1:0] write_data, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [ WIDTH-1:0] read_data, + output logic done +); + + /* verilator lint_off WIDTH */ + logic [WIDTH-1:0] mem[D0_SIZE-1:0][D1_SIZE-1:0][D2_SIZE-1:0]; + + assign read_data = mem[addr0][addr1][addr2]; + + always_ff @(posedge clk) begin + if (reset) + done <= '0; + else if (write_en) + done <= '1; + else + done <= '0; + end + + always_ff @(posedge clk) begin + if (!reset && write_en) + mem[addr0][addr1][addr2] <= write_data; + end + + // Check for out of bounds access + `ifdef VERILATOR + always_comb begin + if (addr0 >= D0_SIZE) + $error( + "comb_mem_d3: Out of bounds access\n", + "addr0: %0d\n", addr0, + "D0_SIZE: %0d", D0_SIZE + ); + if (addr1 >= D1_SIZE) + $error( + "comb_mem_d3: Out of bounds access\n", + "addr1: %0d\n", addr1, + "D1_SIZE: %0d", D1_SIZE + ); + if (addr2 >= D2_SIZE) + $error( + "comb_mem_d3: Out of bounds access\n", + "addr2: %0d\n", addr2, + "D2_SIZE: %0d", D2_SIZE + ); + end + `endif +endmodule + +module comb_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 +) ( + 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 [ WIDTH-1:0] write_data, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [ WIDTH-1:0] read_data, + output logic done +); + + /* verilator lint_off WIDTH */ + logic [WIDTH-1:0] mem[D0_SIZE-1:0][D1_SIZE-1:0][D2_SIZE-1:0][D3_SIZE-1:0]; + + assign read_data = mem[addr0][addr1][addr2][addr3]; + + always_ff @(posedge clk) begin + if (reset) + done <= '0; + else if (write_en) + done <= '1; + else + done <= '0; + end + + always_ff @(posedge clk) begin + if (!reset && write_en) + mem[addr0][addr1][addr2][addr3] <= write_data; + end + + // Check for out of bounds access + `ifdef VERILATOR + always_comb begin + if (addr0 >= D0_SIZE) + $error( + "comb_mem_d4: Out of bounds access\n", + "addr0: %0d\n", addr0, + "D0_SIZE: %0d", D0_SIZE + ); + if (addr1 >= D1_SIZE) + $error( + "comb_mem_d4: Out of bounds access\n", + "addr1: %0d\n", addr1, + "D1_SIZE: %0d", D1_SIZE + ); + if (addr2 >= D2_SIZE) + $error( + "comb_mem_d4: Out of bounds access\n", + "addr2: %0d\n", addr2, + "D2_SIZE: %0d", D2_SIZE + ); + if (addr3 >= D3_SIZE) + $error( + "comb_mem_d4: Out of bounds access\n", + "addr3: %0d\n", addr3, + "D3_SIZE: %0d", D3_SIZE + ); + end + `endif +endmodule + +/** +Implements a memory with sequential reads and writes. +- Both reads and writes take one cycle to perform. +- 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. +*/ +module seq_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, + + // Read signal + input wire logic read_en, + output logic [ WIDTH-1:0] read_data, + output logic read_done, + + // Write signals + input wire logic [ WIDTH-1:0] write_data, + input wire logic write_en, + output logic write_done +); + // Internal memory + (* ram_style = "ultra" *) 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 (read_en) begin + /* verilator lint_off WIDTH */ + read_out <= mem[addr0]; + end else if (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 read_done signal + always_ff @(posedge clk) begin + if (reset) begin + read_done <= '0; + end else if (read_en) begin + read_done <= '1; + end else begin + read_done <= '0; + end + end + + // Write value to the memory + always_ff @(posedge clk) begin + if (!reset && write_en) + mem[addr0] <= write_data; + end + + // Propagate the write_done signal + always_ff @(posedge clk) begin + if (reset) begin + write_done <= '0; + end else if (write_en) begin + write_done <= 1'd1; + end else begin + write_done <= '0; + end + end + + // Check for out of bounds access + `ifdef VERILATOR + always_comb begin + if (read_en) + if (addr0 >= SIZE) + $error( + "comb_mem_d1: Out of bounds access\n", + "addr0: %0d\n", addr0, + "SIZE: %0d", SIZE + ); + end + always_comb begin + if (read_en && write_en) + $error("Simultaneous read and write attempted\n"); + end + `endif +endmodule + +module seq_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, + + // Read signal + input wire logic read_en, + output logic [WIDTH-1:0] read_data, + output logic read_done, + + // Write signals + input wire logic write_en, + input wire logic [ WIDTH-1:0] write_data, + output logic write_done +); + wire [D0_IDX_SIZE+D1_IDX_SIZE-1:0] addr; + assign addr = addr0 * D1_SIZE + addr1; + + seq_mem_d1 #(.WIDTH(WIDTH), .SIZE(D0_SIZE * D1_SIZE), .IDX_SIZE(D0_IDX_SIZE+D1_IDX_SIZE)) mem + (.clk(clk), .reset(reset), .addr0(addr), + .read_en(read_en), .read_data(read_data), .read_done(read_done), .write_data(write_data), .write_en(write_en), + .write_done(write_done)); +endmodule + +module seq_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, + + // Read signal + input wire logic read_en, + output logic [WIDTH-1:0] read_data, + output logic read_done, + + // Write signals + input wire logic write_en, + input wire logic [ WIDTH-1:0] write_data, + output logic write_done +); + wire [D0_IDX_SIZE+D1_IDX_SIZE+D2_IDX_SIZE-1:0] addr; + assign addr = addr0 * (D1_SIZE * D2_SIZE) + addr1 * (D2_SIZE) + addr2; + + seq_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), + .read_en(read_en), .read_data(read_data), .read_done(read_done), .write_data(write_data), .write_en(write_en), + .write_done(write_done)); +endmodule + +module seq_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, + + // Read signal + input wire logic read_en, + output logic [WIDTH-1:0] read_data, + output logic read_done, + + // Write signals + input wire logic write_en, + input wire logic [ WIDTH-1:0] write_data, + output logic write_done +); + 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; + + seq_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), + .read_en(read_en), .read_data(read_data), .read_done(read_done), .write_data(write_data), .write_en(write_en), + .write_done(write_done)); +endmodule diff --git a/fud2/rsrc/primitives-for-firrtl.sv b/fud2/rsrc/primitives-for-firrtl.sv index c92fdfaeec..66b296c6c9 100644 --- a/fud2/rsrc/primitives-for-firrtl.sv +++ b/fud2/rsrc/primitives-for-firrtl.sv @@ -1,56 +1,3 @@ -module comb_mem_d1 #( - parameter WIDTH = 32, - parameter SIZE = 16, - parameter IDX_SIZE = 4 -) ( - input wire logic [IDX_SIZE-1:0] addr0, - input wire logic [ WIDTH-1:0] write_data, - input wire logic write_en, - input wire logic clk, - input wire logic reset, - output logic [ WIDTH-1:0] read_data, - output logic done -); - - logic [WIDTH-1:0] mem[SIZE-1:0]; - - initial begin - $readmemh({"sim_data/mem.dat"}, mem); - end - final begin - $writememh({"sim_data/mem.out"}, mem); - end - - /* verilator lint_off WIDTH */ - assign read_data = mem[addr0]; - - always_ff @(posedge clk) begin - if (reset) - done <= '0; - else if (write_en) - done <= '1; - else - done <= '0; - end - - always_ff @(posedge clk) begin - if (!reset && write_en) - mem[addr0] <= write_data; - end - - // Check for out of bounds access - `ifdef VERILATOR - always_comb begin - if (addr0 >= SIZE) - $error( - "comb_mem_d1: Out of bounds access\n", - "addr0: %0d\n", addr0, - "SIZE: %0d", SIZE - ); - end - `endif -endmodule - /** * Core primitives for Calyx. * Implements core primitives used by the compiler. diff --git a/fud2/src/lib.rs b/fud2/src/lib.rs index 15e673bb0a..06c578f3e1 100644 --- a/fud2/src/lib.rs +++ b/fud2/src/lib.rs @@ -93,9 +93,6 @@ pub fn build_driver(bld: &mut DriverBuilder) { e.rule("hex-data", "$python json-dat.py --from-json $in $out")?; e.rule("json-data", "$python json-dat.py --to-json $out $in")?; - // The Verilog testbench. - e.rsrc("tb.sv")?; - // The input data file. `sim.data` is required. let data_name = e.config_val("sim.data")?; let data_path = e.external_path(data_name.as_ref()); @@ -151,11 +148,56 @@ pub fn build_driver(bld: &mut DriverBuilder) { Ok(()) }); + // The "verilog_refmem" states are variants of the other Verilog states that use the external testbench style. + // "refmem" refers to the fact that their memories are external, meaning that they need to be linked with + // a testbench that will provide those memories. + let verilog_refmem = bld.state("verilog-refmem", &["sv"]); + let verilog_refmem_noverify = bld.state("verilog-refmem-noverify", &["sv"]); + // Icarus Verilog. let verilog_noverify = bld.state("verilog-noverify", &["sv"]); let icarus_setup = bld.setup("Icarus Verilog", |e| { e.var("iverilog", "iverilog")?; - e.rule("icarus-compile", "$iverilog -g2012 -o $out tb.sv $in")?; + e.rule( + "icarus-compile-standalone-tb", + "$iverilog -g2012 -o $out tb.sv $in", + )?; + e.rule( + "icarus-compile-custom-tb", + "$iverilog -g2012 -o $out tb.sv memories.sv $in", + )?; + Ok(()) + }); + // [Default] Setup for using rsrc/tb.sv as testbench (and managing memories within the design) + let standalone_testbench_setup = + bld.setup("Standalone Testbench Setup", |e| { + // Standalone Verilog testbench. + e.rsrc("tb.sv")?; + + Ok(()) + }); + // [Needs YXI backend compiled] Setup for creating a custom testbench (needed for FIRRTL) + let custom_testbench_setup = bld.setup("Custom Testbench Setup", |e| { + // Convert all ref cells to @external (FIXME: YXI should work for both?) + e.rule("ref-to-external", "sed 's/ref /@external /g' $in > $out")?; + + // Convert all @external cells to ref (FIXME: we want to deprecate @external) + e.rule("external-to-ref", "sed 's/@external([0-9]*)/ref/g' $in | sed 's/@external/ref/g' > $out")?; + + e.var( + "gen-testbench-script", + "$calyx-base/tools/firrtl/generate-testbench.py", + )?; + e.rsrc("memories.sv")?; // Memory primitives. + + e.rule( + "generate-refmem-testbench", + "python3 $gen-testbench-script $in > $out", + )?; + + // dummy rule to force ninja to build the testbench + e.rule("dummy", "sh -c 'cat $$0' $in > $out")?; + Ok(()) }); bld.op( @@ -171,29 +213,147 @@ pub fn build_driver(bld: &mut DriverBuilder) { Ok(()) }, ); + bld.op( "icarus", - &[sim_setup, icarus_setup], + &[sim_setup, standalone_testbench_setup, icarus_setup], verilog_noverify, simulator, |e, input, output| { - e.build_cmd(&[output], "icarus-compile", &[input], &["tb.sv"])?; + e.build_cmd( + &[output], + "icarus-compile-standalone-tb", + &[input], + &["tb.sv"], + )?; Ok(()) }, ); + bld.op( + "icarus-refmem", + &[sim_setup, icarus_setup], + verilog_refmem_noverify, + simulator, + |e, input, output| { + e.build_cmd( + &[output], + "icarus-compile-custom-tb", + &[input], + &["tb.sv", "memories.sv"], + )?; + Ok(()) + }, + ); + + // setup for FIRRTL-implemented primitives + let firrtl_primitives_setup = bld.setup("FIRRTL with primitives", |e| { + // Produce FIRRTL with FIRRTL-defined primitives. + e.var( + "gen-firrtl-primitives-script", + "$calyx-base/tools/firrtl/generate-firrtl-with-primitives.py", + )?; + e.rule( + "generate-firrtl-with-primitives", + "python3 $gen-firrtl-primitives-script $in > $out", + )?; + + Ok(()) + }); + + fn calyx_to_firrtl_helper( + e: &mut Emitter, + input: &str, + output: &str, + firrtl_primitives: bool, // Use FIRRTL primitive implementations? + ) -> EmitResult { + // Temporary Calyx where all refs are converted into external (FIXME: fix YXI to emit for ref as well?) + let only_externals_calyx = "external.futil"; + // Temporary Calyx where all externals are converted into refs (for FIRRTL backend) + let only_refs_calyx = "ref.futil"; + // JSON with memory information created by YXI + let memories_json = "memory-info.json"; + // Custom testbench (same name as standalone testbench) + let testbench = "tb.sv"; + // Holds contents of file we want to output. Gets cat-ed via final dummy command + let tmp_out = "tmp-out.fir"; + // Convert ref into external to get YXI working (FIXME: fix YXI to emit for ref as well?) + e.build_cmd(&[only_externals_calyx], "ref-to-external", &[input], &[])?; + // Convert external to ref to get FIRRTL backend working + e.build_cmd(&[only_refs_calyx], "external-to-ref", &[input], &[])?; + + // Get YXI to generate JSON for testbench generation + e.build_cmd(&[memories_json], "calyx", &[only_externals_calyx], &[])?; + e.arg("backend", "yxi")?; + // generate custom testbench + e.build_cmd( + &[testbench], + "generate-refmem-testbench", + &[memories_json], + &[], + )?; + + if firrtl_primitives { + let core_program_firrtl = "core.fir"; + + // Obtain FIRRTL of core program + e.build_cmd( + &[core_program_firrtl], + "calyx", + &[only_refs_calyx], + &[], + )?; + e.arg("backend", "firrtl")?; + e.arg("args", "--synthesis")?; + + // Obtain primitive uses JSON for metaprogramming + let primitive_uses_json = "primitive-uses.json"; + e.build_cmd( + &[primitive_uses_json], + "calyx", + &[only_refs_calyx], + &[], + )?; + e.arg("backend", "primitive-uses")?; + e.arg("args", "--synthesis")?; + + // run metaprogramming script to get FIRRTL with primitives + e.build_cmd( + &[tmp_out], + "generate-firrtl-with-primitives", + &[core_program_firrtl, primitive_uses_json], + &[], + )?; + } else { + // emit extmodule declarations to use Verilog primitive implementations + e.build_cmd(&[tmp_out], "calyx", &[only_refs_calyx], &[])?; + e.arg("backend", "firrtl")?; + e.arg("args", "--emit-primitive-extmodules")?; + } + + // dummy command to make sure custom testbench is created but not emitted as final answer + e.build_cmd(&[output], "dummy", &[tmp_out, testbench], &[])?; + + Ok(()) + } // Calyx to FIRRTL. - let firrtl = bld.state("firrtl", &["fir"]); + let firrtl = bld.state("firrtl", &["fir"]); // using Verilog primitives + let firrtl_with_primitives = bld.state("firrtl-with-primitives", &["fir"]); // using FIRRTL primitives bld.op( + // use Verilog "calyx-to-firrtl", - &[calyx_setup], + &[calyx_setup, custom_testbench_setup], calyx, firrtl, - |e, input, output| { - e.build_cmd(&[output], "calyx", &[input], &[])?; - e.arg("backend", "firrtl")?; - Ok(()) - }, + |e, input, output| calyx_to_firrtl_helper(e, input, output, false), + ); + + bld.op( + "firrtl-with-primitives", + &[calyx_setup, firrtl_primitives_setup, custom_testbench_setup], + calyx, + firrtl_with_primitives, + |e, input, output| calyx_to_firrtl_helper(e, input, output, true), ); // The FIRRTL compiler. @@ -202,37 +362,68 @@ pub fn build_driver(bld: &mut DriverBuilder) { e.rule("firrtl", "$firrtl-exe -i $in -o $out -X sverilog")?; e.rsrc("primitives-for-firrtl.sv")?; + // adding Verilog implementations of primitives to FIRRTL --> Verilog compiled code e.rule( - "add-firrtl-prims", + "add-verilog-primitives", "cat primitives-for-firrtl.sv $in > $out", )?; Ok(()) }); - fn firrtl_compile( + + fn firrtl_compile_helper( e: &mut Emitter, input: &str, output: &str, + firrtl_primitives: bool, ) -> EmitResult { - let tmp_verilog = "partial.sv"; - e.build_cmd(&[tmp_verilog], "firrtl", &[input], &[])?; - e.build_cmd( - &[output], - "add-firrtl-prims", - &[tmp_verilog], - &["primitives-for-firrtl.sv"], - )?; + if firrtl_primitives { + e.build_cmd(&[output], "firrtl", &[input], &[])?; + } else { + let tmp_verilog = "partial.sv"; + e.build_cmd(&[tmp_verilog], "firrtl", &[input], &[])?; + e.build_cmd( + &[output], + "add-verilog-primitives", + &[tmp_verilog], + &["primitives-for-firrtl.sv"], + )?; + } Ok(()) } - bld.op("firrtl", &[firrtl_setup], firrtl, verilog, firrtl_compile); + // FIRRTL --> Verilog compilation using Verilog primitive implementations for Verilator + bld.op( + "firrtl", + &[firrtl_setup], + firrtl, + verilog_refmem, + |e, input, output| firrtl_compile_helper(e, input, output, false), + ); + // FIRRTL --> Verilog compilation using Verilog primitive implementations for Icarus // This is a bit of a hack, but the Icarus-friendly "noverify" state is identical for this path // (since FIRRTL compilation doesn't come with verification). bld.op( "firrtl-noverify", &[firrtl_setup], firrtl, - verilog_noverify, - firrtl_compile, + verilog_refmem_noverify, + |e, input, output| firrtl_compile_helper(e, input, output, false), + ); + // FIRRTL --> Verilog compilation using FIRRTL primitive implementations for Verilator + bld.op( + "firrtl-with-primitives", + &[firrtl_setup], + firrtl_with_primitives, + verilog_refmem, + |e, input, output| firrtl_compile_helper(e, input, output, true), + ); + // FIRRTL --> Verilog compilation using FIRRTL primitive implementations for Icarus + bld.op( + "firrtl-with-primitives-noverify", + &[firrtl_setup], + firrtl_with_primitives, + verilog_refmem_noverify, + |e, input, output| firrtl_compile_helper(e, input, output, true), ); // primitive-uses backend @@ -254,30 +445,58 @@ pub fn build_driver(bld: &mut DriverBuilder) { e.config_var_or("verilator", "verilator.exe", "verilator")?; e.config_var_or("cycle-limit", "sim.cycle_limit", "500000000")?; e.rule( - "verilator-compile", + "verilator-compile-standalone-tb", "$verilator $in tb.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir", )?; + e.rule( + "verilator-compile-custom-tb", + "$verilator $in tb.sv memories.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir", + )?; e.rule("cp", "cp $in $out")?; Ok(()) }); - bld.op( - "verilator", - &[sim_setup, verilator_setup], - verilog, - simulator, - |e, input, output| { - let out_dir = "verilator-out"; - let sim_bin = format!("{}/VTOP", out_dir); + fn verilator_build( + e: &mut Emitter, + input: &str, + output: &str, + standalone_testbench: bool, + ) -> EmitResult { + let out_dir = "verilator-out"; + let sim_bin = format!("{}/VTOP", out_dir); + if standalone_testbench { e.build_cmd( &[&sim_bin], - "verilator-compile", + "verilator-compile-standalone-tb", &[input], &["tb.sv"], )?; - e.arg("out-dir", out_dir)?; - e.build("cp", &sim_bin, output)?; - Ok(()) - }, + } else { + e.build_cmd( + &[&sim_bin], + "verilator-compile-custom-tb", + &[input], + &["tb.sv", "memories.sv"], + )?; + } + e.arg("out-dir", out_dir)?; + e.build("cp", &sim_bin, output)?; + Ok(()) + } + + bld.op( + "verilator", + &[sim_setup, standalone_testbench_setup, verilator_setup], + verilog, + simulator, + |e, input, output| verilator_build(e, input, output, true), + ); + + bld.op( + "verilator-refmem", + &[sim_setup, custom_testbench_setup, verilator_setup], + verilog_refmem, + simulator, + |e, input, output| verilator_build(e, input, output, false), ); // Interpreter. @@ -336,7 +555,12 @@ pub fn build_driver(bld: &mut DriverBuilder) { }); bld.op( "interp", - &[sim_setup, calyx_setup, cider_setup], + &[ + sim_setup, + standalone_testbench_setup, + calyx_setup, + cider_setup, + ], calyx, dat, |e, input, output| { @@ -370,7 +594,12 @@ pub fn build_driver(bld: &mut DriverBuilder) { ); bld.op( "debug", - &[sim_setup, calyx_setup, cider_setup], + &[ + sim_setup, + standalone_testbench_setup, + calyx_setup, + cider_setup, + ], calyx, debug, |e, input, output| { @@ -463,7 +692,12 @@ pub fn build_driver(bld: &mut DriverBuilder) { }); bld.op( "xrt", - &[xilinx_setup, sim_setup, xrt_setup], + &[ + xilinx_setup, + sim_setup, + standalone_testbench_setup, + xrt_setup, + ], xclbin, dat, |e, input, output| { @@ -480,7 +714,12 @@ pub fn build_driver(bld: &mut DriverBuilder) { ); bld.op( "xrt-trace", - &[xilinx_setup, sim_setup, xrt_setup], + &[ + xilinx_setup, + sim_setup, + standalone_testbench_setup, + xrt_setup, + ], xclbin, vcd, |e, input, output| { diff --git a/fud2/tests/snapshots/tests__emit@calyx_debug.snap b/fud2/tests/snapshots/tests__emit@calyx_debug.snap index 2301d912dc..87bde0344b 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_debug.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_debug.snap @@ -13,7 +13,6 @@ rule hex-data command = $python json-dat.py --from-json $in $out rule json-data command = $python json-dat.py --to-json $out $in -build tb.sv: get-rsrc sim_data = /test/data.json datadir = sim_data build $datadir: hex-data $sim_data | json-dat.py @@ -21,6 +20,9 @@ rule sim-run command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out cycle-limit = 500000000 +# Standalone Testbench Setup +build tb.sv: get-rsrc + # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx diff --git a/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog-refmem.snap b/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog-refmem.snap new file mode 100644 index 0000000000..fc3b42190f --- /dev/null +++ b/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog-refmem.snap @@ -0,0 +1,51 @@ +--- +source: fud2/tests/tests.rs +description: emit calyx -> verilog-refmem through firrtl +--- +build-tool = fud2 +rule get-rsrc + command = $build-tool get-rsrc $out + +# Calyx compiler +calyx-base = /test/calyx +calyx-exe = $calyx-base/target/debug/calyx +args = +rule calyx + command = $calyx-exe -l $calyx-base -b $backend $args $in > $out +rule calyx-pass + command = $calyx-exe -l $calyx-base -p $pass $args $in > $out + +# Custom Testbench Setup +rule ref-to-external + command = sed 's/ref /@external /g' $in > $out +rule external-to-ref + command = sed 's/@external([0-9]*)/ref/g' $in | sed 's/@external/ref/g' > $out +gen-testbench-script = $calyx-base/tools/firrtl/generate-testbench.py +build memories.sv: get-rsrc +rule generate-refmem-testbench + command = python3 $gen-testbench-script $in > $out +rule dummy + command = sh -c 'cat $$0' $in > $out + +# Firrtl to Verilog compiler +firrtl-exe = /test/bin/firrtl +rule firrtl + command = $firrtl-exe -i $in -o $out -X sverilog +build primitives-for-firrtl.sv: get-rsrc +rule add-verilog-primitives + command = cat primitives-for-firrtl.sv $in > $out + +# build targets +build external.futil: ref-to-external stdin +build ref.futil: external-to-ref stdin +build memory-info.json: calyx external.futil + backend = yxi +build tb.sv: generate-refmem-testbench memory-info.json +build tmp-out.fir: calyx ref.futil + backend = firrtl + args = --emit-primitive-extmodules +build stdin.fir: dummy tmp-out.fir tb.sv +build partial.sv: firrtl stdin.fir +build stdin.sv: add-verilog-primitives partial.sv | primitives-for-firrtl.sv + +default stdin.sv diff --git a/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog.snap b/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog.snap deleted file mode 100644 index 6feb6af95d..0000000000 --- a/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog.snap +++ /dev/null @@ -1,32 +0,0 @@ ---- -source: fud2/tests/tests.rs -description: emit calyx -> verilog through firrtl ---- -build-tool = fud2 -rule get-rsrc - command = $build-tool get-rsrc $out - -# Calyx compiler -calyx-base = /test/calyx -calyx-exe = $calyx-base/target/debug/calyx -args = -rule calyx - command = $calyx-exe -l $calyx-base -b $backend $args $in > $out -rule calyx-pass - command = $calyx-exe -l $calyx-base -p $pass $args $in > $out - -# Firrtl to Verilog compiler -firrtl-exe = /test/bin/firrtl -rule firrtl - command = $firrtl-exe -i $in -o $out -X sverilog -build primitives-for-firrtl.sv: get-rsrc -rule add-firrtl-prims - command = cat primitives-for-firrtl.sv $in > $out - -# build targets -build stdin.fir: calyx stdin - backend = firrtl -build partial.sv: firrtl stdin.fir -build stdin.sv: add-firrtl-prims partial.sv | primitives-for-firrtl.sv - -default stdin.sv diff --git a/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap index 9ecd9b6e2d..7c3278eefc 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap @@ -22,7 +22,6 @@ rule hex-data command = $python json-dat.py --from-json $in $out rule json-data command = $python json-dat.py --to-json $out $in -build tb.sv: get-rsrc sim_data = /test/data.json datadir = sim_data build $datadir: hex-data $sim_data | json-dat.py @@ -30,16 +29,21 @@ rule sim-run command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out cycle-limit = 500000000 +# Standalone Testbench Setup +build tb.sv: get-rsrc + # Icarus Verilog iverilog = iverilog -rule icarus-compile +rule icarus-compile-standalone-tb command = $iverilog -g2012 -o $out tb.sv $in +rule icarus-compile-custom-tb + command = $iverilog -g2012 -o $out tb.sv memories.sv $in # build targets build stdin.sv: calyx stdin backend = verilog args = --disable-verify -build stdin.exe: icarus-compile stdin.sv | tb.sv +build stdin.exe: icarus-compile-standalone-tb stdin.sv | tb.sv build sim.log: sim-run stdin.exe $datadir bin = stdin.exe args = +NOTRACE=1 diff --git a/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap b/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap index 9b4db5ebff..e96ecc4384 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap @@ -22,7 +22,6 @@ rule hex-data command = $python json-dat.py --from-json $in $out rule json-data command = $python json-dat.py --to-json $out $in -build tb.sv: get-rsrc sim_data = /test/data.json datadir = sim_data build $datadir: hex-data $sim_data | json-dat.py @@ -30,16 +29,21 @@ rule sim-run command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out cycle-limit = 500000000 +# Standalone Testbench Setup +build tb.sv: get-rsrc + # Icarus Verilog iverilog = iverilog -rule icarus-compile +rule icarus-compile-standalone-tb command = $iverilog -g2012 -o $out tb.sv $in +rule icarus-compile-custom-tb + command = $iverilog -g2012 -o $out tb.sv memories.sv $in # build targets build stdin.sv: calyx stdin backend = verilog args = --disable-verify -build stdin.exe: icarus-compile stdin.sv | tb.sv +build stdin.exe: icarus-compile-standalone-tb stdin.sv | tb.sv build sim.log stdin.vcd: sim-run stdin.exe $datadir bin = stdin.exe args = +NOTRACE=0 +OUT=stdin.vcd diff --git a/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap index 0ef04007f1..6097c4c2b9 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap @@ -13,7 +13,6 @@ rule hex-data command = $python json-dat.py --from-json $in $out rule json-data command = $python json-dat.py --to-json $out $in -build tb.sv: get-rsrc sim_data = /test/data.json datadir = sim_data build $datadir: hex-data $sim_data | json-dat.py @@ -21,6 +20,9 @@ rule sim-run command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out cycle-limit = 500000000 +# Standalone Testbench Setup +build tb.sv: get-rsrc + # Calyx compiler calyx-base = /test/calyx calyx-exe = $calyx-base/target/debug/calyx diff --git a/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap index e7a2aaeafc..bf595d2de8 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap @@ -22,7 +22,6 @@ rule hex-data command = $python json-dat.py --from-json $in $out rule json-data command = $python json-dat.py --to-json $out $in -build tb.sv: get-rsrc sim_data = /test/data.json datadir = sim_data build $datadir: hex-data $sim_data | json-dat.py @@ -30,18 +29,23 @@ rule sim-run command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out cycle-limit = 500000000 +# Standalone Testbench Setup +build tb.sv: get-rsrc + # Verilator verilator = verilator cycle-limit = 500000000 -rule verilator-compile +rule verilator-compile-standalone-tb command = $verilator $in tb.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir +rule verilator-compile-custom-tb + command = $verilator $in tb.sv memories.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir rule cp command = cp $in $out # build targets build stdin.sv: calyx stdin backend = verilog -build verilator-out/VTOP: verilator-compile stdin.sv | tb.sv +build verilator-out/VTOP: verilator-compile-standalone-tb stdin.sv | tb.sv out-dir = verilator-out build stdin.exe: cp verilator-out/VTOP build sim.log: sim-run stdin.exe $datadir diff --git a/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap b/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap index 8d4cbc0311..50ba7adf62 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap @@ -22,7 +22,6 @@ rule hex-data command = $python json-dat.py --from-json $in $out rule json-data command = $python json-dat.py --to-json $out $in -build tb.sv: get-rsrc sim_data = /test/data.json datadir = sim_data build $datadir: hex-data $sim_data | json-dat.py @@ -30,18 +29,23 @@ rule sim-run command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out cycle-limit = 500000000 +# Standalone Testbench Setup +build tb.sv: get-rsrc + # Verilator verilator = verilator cycle-limit = 500000000 -rule verilator-compile +rule verilator-compile-standalone-tb command = $verilator $in tb.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir +rule verilator-compile-custom-tb + command = $verilator $in tb.sv memories.sv --trace --binary --top-module TOP -fno-inline -Mdir $out-dir rule cp command = cp $in $out # build targets build stdin.sv: calyx stdin backend = verilog -build verilator-out/VTOP: verilator-compile stdin.sv | tb.sv +build verilator-out/VTOP: verilator-compile-standalone-tb stdin.sv | tb.sv out-dir = verilator-out build stdin.exe: cp verilator-out/VTOP build sim.log stdin.vcd: sim-run stdin.exe $datadir diff --git a/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap b/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap index f38eeb68fb..a0e57b2fef 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap @@ -37,7 +37,6 @@ rule hex-data command = $python json-dat.py --from-json $in $out rule json-data command = $python json-dat.py --to-json $out $in -build tb.sv: get-rsrc sim_data = /test/data.json datadir = sim_data build $datadir: hex-data $sim_data | json-dat.py @@ -45,6 +44,9 @@ rule sim-run command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out cycle-limit = 500000000 +# Standalone Testbench Setup +build tb.sv: get-rsrc + # Xilinx execution via XRT rule emconfig command = $vitis-dir/bin/emconfigutil --platform $platform diff --git a/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap index a98882ad27..0be68b7fb4 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap @@ -37,7 +37,6 @@ rule hex-data command = $python json-dat.py --from-json $in $out rule json-data command = $python json-dat.py --to-json $out $in -build tb.sv: get-rsrc sim_data = /test/data.json datadir = sim_data build $datadir: hex-data $sim_data | json-dat.py @@ -45,6 +44,9 @@ rule sim-run command = ./$bin +DATA=$datadir +CYCLE_LIMIT=$cycle-limit $args > $out cycle-limit = 500000000 +# Standalone Testbench Setup +build tb.sv: get-rsrc + # Xilinx execution via XRT rule emconfig command = $vitis-dir/bin/emconfigutil --platform $platform diff --git a/fud2/tests/tests.rs b/fud2/tests/tests.rs index 7f5e2be170..eafa1e2eef 100644 --- a/fud2/tests/tests.rs +++ b/fud2/tests/tests.rs @@ -92,7 +92,10 @@ fn calyx_to_verilog() { #[test] fn calyx_via_firrtl() { let driver = test_driver(); - test_emit(&driver, request(&driver, "calyx", "verilog", &["firrtl"])); + test_emit( + &driver, + request(&driver, "calyx", "verilog-refmem", &["firrtl"]), + ); } #[test] diff --git a/tools/firrtl/custom_tb_template.sv b/tools/firrtl/custom_tb_template.sv new file mode 100644 index 0000000000..fa24e707f2 --- /dev/null +++ b/tools/firrtl/custom_tb_template.sv @@ -0,0 +1,94 @@ +module TOP; + +// Signals for the main module. +logic go, done, clk, reset; + +// fields for memory controlled externally START +MEMORY_FIELDS +// fields for memory controlled externally END + +// Declaring memory START +MEMORY_DECLS +// Declaring memory END + +// Declaring main module START +MAIN_DECL +// Declaring main module END + +localparam RESET_CYCLES = 3; + +// Cycle counter. Make this signed to catch errors with cycle simulation +// counts. +logic signed [63:0] cycle_count; + +always_ff @(posedge clk) begin + cycle_count <= cycle_count + 1; +end + +always_ff @(posedge clk) begin + // Reset the design for a few cycles + if (cycle_count < RESET_CYCLES) begin + reset <= 1; + go <= 0; + end else begin + reset <= 0; + go <= 1; + end +end + +// Output location of the VCD file +string OUT; +// Disable VCD tracing +int NOTRACE; +// Maximum number of cycles to simulate +longint CYCLE_LIMIT; +// Dummy variable to track value returned by $value$plusargs +int CODE; +// Directory to read/write memory +string DATA; + +initial begin + CODE = $value$plusargs("DATA=%s", DATA); + $display("DATA (path to meminit files): %s", DATA); + // readmemh for each memory BEGIN + READMEMH_STATEMENTS + // readmemh for each memory END + CODE = $value$plusargs("OUT=%s", OUT); + CODE = $value$plusargs("CYCLE_LIMIT=%d", CYCLE_LIMIT); + if (CYCLE_LIMIT != 0) begin + $display("cycle limit set to %d", CYCLE_LIMIT); + end + CODE = $value$plusargs("NOTRACE=%d", NOTRACE); + if (NOTRACE == 0) begin + $display("VCD tracing enabled"); + $dumpfile(OUT); + $dumpvars(0,main); + end else begin + $display("VCD tracing disabled"); + end + + // Initial values + go = 0; + clk = 0; + reset = 1; + cycle_count = 0; + + forever begin + #10 clk = ~clk; + if (cycle_count > RESET_CYCLES && done == 1) begin + // Subtract 1 because the cycle counter is incremented at the end of the + // cycle. + $display("Simulated %d cycles", cycle_count - RESET_CYCLES - 1); + $finish; + end else if (cycle_count != 0 && cycle_count == CYCLE_LIMIT + RESET_CYCLES) begin + $display("reached limit of %d cycles", CYCLE_LIMIT); + $finish; + end + end +end + +// writememh for each memory BEGIN +WRITEMEMH_STATEMENTS +// writememh for each memory END + +endmodule diff --git a/tools/firrtl/generate-firrtl-with-primitives.py b/tools/firrtl/generate-firrtl-with-primitives.py index 7116e0c4ff..c4b23811e2 100644 --- a/tools/firrtl/generate-firrtl-with-primitives.py +++ b/tools/firrtl/generate-firrtl-with-primitives.py @@ -1,3 +1,9 @@ +# Metaprogramming script for generating FIRRTL definitions of primitives used in a Calyx program. +# Inputs: +# FIRRTL_FILE - core program in FIRRTL (generated by the FIRRTL backend) +# PRIMITIVE_USES_JSON - JSON denoting primitive uses in the Calyx program (generated by primitive-uses backend) +# Outputs to stdout a whole FIRRTL program (combining the core program and necessary primitives) + import json import math import os diff --git a/tools/firrtl/generate-testbench.py b/tools/firrtl/generate-testbench.py new file mode 100644 index 0000000000..d50c6fae8f --- /dev/null +++ b/tools/firrtl/generate-testbench.py @@ -0,0 +1,192 @@ +# Writes a custom testbench to stdout based on memories used in the Calyx program. +# Input: YXI_JSON - JSON generated by the YXI backend containing information about memories used + +import json +import os +import sys + +template_file = os.path.join(sys.path[0], "custom_tb_template.sv") + +def generate_addr_field(idx_size, name, index): + if (idx_size > 1): + return f"wire [{idx_size-1}:0] {name}_addr{index};\n" + else: + return f"wire {name}_addr{index};\n" + +def generate_fields(memory_cell_dicts): + out_str = "" + for memory_cell_dict in memory_cell_dicts: + name = memory_cell_dict['cell-name'] + width = memory_cell_dict['WIDTH'] + module_name = memory_cell_dict['module-name'] + if "d1" in module_name: + out_str += generate_addr_field(memory_cell_dict['IDX_SIZE'], name, 0) + elif "d2" in module_name: + out_str += generate_addr_field(memory_cell_dict['D0_IDX_SIZE'], name, 0) + out_str += generate_addr_field(memory_cell_dict['D1_IDX_SIZE'], name, 1) + + out_str += f"wire [{width-1}:0] {name}_write_data;\n" + out_str += f"wire [{width-1}:0] {name}_read_data;\n" + out_str += f"wire {name}_write_en;\n" + if "comb" in module_name: + out_str += f"wire {name}_done;\n\n" + else: # seq has more ports. + out_str += f'''wire {name}_read_en; +wire {name}_write_done; +wire {name}_read_done;\n\n''' + + return out_str + +def generate_memory_dec(memory_cell_dicts): + out_str = "" + for memory_cell_dict in memory_cell_dicts: + module_name = memory_cell_dict['module-name'] + name = memory_cell_dict['cell-name'] + out_str += f"{module_name} # (\n" + if "d1" in module_name: + out_str += f''' .IDX_SIZE({memory_cell_dict["IDX_SIZE"]}), + .SIZE({memory_cell_dict["SIZE"]}), + .WIDTH({memory_cell_dict["WIDTH"]}) + ) {name} (\n''' + elif "d2" in module_name: + out_str += f''' .D0_IDX_SIZE({memory_cell_dict["D0_IDX_SIZE"]}), + .D1_IDX_SIZE({memory_cell_dict["D1_IDX_SIZE"]}), + .D0_SIZE({memory_cell_dict["D0_SIZE"]}), + .D1_SIZE({memory_cell_dict["D1_SIZE"]}), + .WIDTH({memory_cell_dict["WIDTH"]}) + ) {name} ( + .addr1({name}_addr1),\n''' + out_str += f''' .addr0({name}_addr0), + .clk(clk), + .read_data({name}_read_data), + .reset(reset), + .write_data({name}_write_data), + .write_en({name}_write_en), +''' + if "comb" in module_name: + out_str += f" .done({name}_done)\n" + else: + out_str += f''' .read_en({name}_read_en), + .read_done({name}_read_done), + .write_done({name}_write_done) +''' + out_str += ");\n\n" + return out_str + +def generate_main_decl(memory_cell_dicts): + out_str = '''main #() main ( + .go(go), + .clk(clk), + .reset(reset), + .done(done)''' + # Documentation: Need to connect to the wires we initialized above (ex. {name}_addr0) + # and not the ports of the memory (ex. {name},addr0) because verilator would error + # out due to some of the memory ports being input ports. + for memory_cell_dict in memory_cell_dicts: + name = memory_cell_dict["cell-name"] + module_name = memory_cell_dict["module-name"] + out_str += ",\n" + if "d2" in module_name: + out_str += f" .{name}_addr1({name}_addr1),\n" + out_str += f''' .{name}_addr0({name}_addr0), + .{name}_write_data({name}_write_data), + .{name}_read_data({name}_read_data), + .{name}_write_en({name}_write_en), +''' + if "comb" in module_name: + out_str += f" .{name}_done({name}_done)" + else: + out_str += f''' .{name}_read_en({name}_read_en), + .{name}_write_done({name}_write_done), + .{name}_read_done({name}_read_done)''' + out_str += "\n);" + return out_str + +def generate_readmemh(memory_cell_dicts): + out_str = "" + for memory_cell_dict in memory_cell_dicts: + name = memory_cell_dict["cell-name"] + if "d2" in memory_cell_dict["module-name"]: + mem = "mem.mem" + else: + mem = "mem" + out_str += f" $readmemh({{DATA, \"/{name}.dat\"}}, {name}.{mem});\n" + return out_str + +def generate_writememh(memory_cell_dicts): + out_str = "" + if len(memory_cell_dicts) > 0: + out_str += "final begin\n" + for memory_cell_dict in memory_cell_dicts: + name = memory_cell_dict["cell-name"] + if "d2" in memory_cell_dict["module-name"]: + mem = "mem.mem" + else: + mem = "mem" + out_str += f" $writememh({{DATA, \"/{name}.out\"}}, {name}.{mem});\n" + out_str += "end" + return out_str + +def create_memory_cell_dict(memory): + memory_cell_dict = {} + memory_cell_dict["cell-name"] = memory["name"] + dimensions = memory["dimensions"] + if memory["memory_type"] == "Sequential": + memory_cell_dict["module-name"] = f"seq_mem_d{dimensions}" + else: + memory_cell_dict["module-name"] = f"comb_mem_d{dimensions}" + + memory_cell_dict["WIDTH"] = memory["data_width"] + if dimensions == 1: + memory_cell_dict["SIZE"] = memory["dimension_sizes"][0] + memory_cell_dict["IDX_SIZE"] = memory["idx_sizes"][0] + elif dimensions > 2: + print(f"Multidimensional memory yet to be supported found: {memory['name']}") + print("Aborting.") + sys.exit(1) + else: + for i in range(dimensions): + memory_cell_dict[f"D{i}_SIZE"] = memory["dimension_sizes"][i] + memory_cell_dict[f"D{i}_IDX_SIZE"] = memory["idx_sizes"][i] + + return memory_cell_dict + +def get_memory_cells(yxi_json_filepath): + memory_cell_dicts = [] + yxi_json = json.load(open(yxi_json_filepath)) + for memory in yxi_json["memories"]: + memory_cell_dict = create_memory_cell_dict(memory) + memory_cell_dicts.append(memory_cell_dict) + + return memory_cell_dicts + + +def generate(yxi_json): + memory_cell_dicts = get_memory_cells(yxi_json) + with open(template_file) as t: + for line in t: + if line.strip() == "MEMORY_FIELDS": + print(generate_fields(memory_cell_dicts)) + elif line.strip() == "MEMORY_DECLS": + print(generate_memory_dec(memory_cell_dicts)) + elif line.strip() == "MAIN_DECL": + print(generate_main_decl(memory_cell_dicts)) + elif line.strip() == "READMEMH_STATEMENTS": + print(generate_readmemh(memory_cell_dicts)) + elif line.strip() == "WRITEMEMH_STATEMENTS": + print(generate_writememh(memory_cell_dicts)) + else: + print(line.rstrip()) + +def main(): + if len(sys.argv) != 2: + args_desc = [ + "YXI_JSON" + ] + print(f"Usage: {sys.argv[0]} {' '.join(args_desc)}") + return 1 + generate(sys.argv[1]) + + +if __name__ == '__main__': + main() diff --git a/yxi/tests/backend/dot-product.expect b/yxi/tests/backend/dot-product.expect index 23d22850f6..f6a9fae420 100644 --- a/yxi/tests/backend/dot-product.expect +++ b/yxi/tests/backend/dot-product.expect @@ -3,7 +3,7 @@ "memories": [ { "name": "A0", - "memory_type": "Sequential", + "memory_type": "Combinational", "data_width": 32, "dimensions": 1, "dimension_sizes": [ @@ -16,7 +16,7 @@ }, { "name": "B0", - "memory_type": "Sequential", + "memory_type": "Combinational", "data_width": 32, "dimensions": 1, "dimension_sizes": [ @@ -29,7 +29,7 @@ }, { "name": "v0", - "memory_type": "Sequential", + "memory_type": "Combinational", "data_width": 32, "dimensions": 1, "dimension_sizes": [