From 062b9a826735ede37d7899e66911b54abf603930 Mon Sep 17 00:00:00 2001 From: Nathaniel Navarro Date: Tue, 10 Oct 2023 09:12:15 -0700 Subject: [PATCH] Create IDL Backend (#1729) * 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 --- Cargo.lock | 2 + Cargo.toml | 1 + calyx-backend/Cargo.toml | 1 + calyx-backend/src/backend_opt.rs | 3 + calyx-backend/src/lib.rs | 2 + calyx-backend/src/xilinx/toplevel.rs | 75 +++-------- calyx-backend/src/yxi.rs | 73 +++++++++++ calyx-ir/src/lib.rs | 2 + calyx-ir/src/utils.rs | 94 ++++++++++++++ calyx-utils/Cargo.toml | 2 + calyx-utils/src/errors.rs | 6 + src/cmdline.rs | 5 + tests/backend/yxi/dot-product.expect | 20 +++ tests/backend/yxi/dot-product.futil | 93 +++++++++++++ tests/backend/yxi/seq-mem-d4-add.expect | 20 +++ tests/backend/yxi/seq-mem-d4-add.futil | 166 ++++++++++++++++++++++++ 16 files changed, 508 insertions(+), 57 deletions(-) create mode 100644 calyx-backend/src/yxi.rs create mode 100644 calyx-ir/src/utils.rs create mode 100644 tests/backend/yxi/dot-product.expect create mode 100644 tests/backend/yxi/dot-product.futil create mode 100644 tests/backend/yxi/seq-mem-d4-add.expect create mode 100644 tests/backend/yxi/seq-mem-d4-add.futil diff --git a/Cargo.lock b/Cargo.lock index 3b80102a42..613ad2c27d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,6 +208,7 @@ dependencies = [ "petgraph", "quick-xml", "serde", + "serde_json", "serde_sexpr", "serde_with 3.3.0", "smallvec", @@ -279,6 +280,7 @@ dependencies = [ "itertools 0.11.0", "petgraph", "serde", + "serde_json", "string-interner", "symbol_table", ] diff --git a/Cargo.toml b/Cargo.toml index b4fbd9044e..3d4328718d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/calyx-backend/Cargo.toml b/calyx-backend/Cargo.toml index dfe6c4b917..9ccd175e33 100644 --- a/calyx-backend/Cargo.toml +++ b/calyx-backend/Cargo.toml @@ -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 diff --git a/calyx-backend/src/backend_opt.rs b/calyx-backend/src/backend_opt.rs index fec1144cf9..f07df18a4b 100644 --- a/calyx-backend/src/backend_opt.rs +++ b/calyx-backend/src/backend_opt.rs @@ -12,6 +12,7 @@ pub enum BackendOpt { Mlir, Resources, Sexp, + Yxi, None, } @@ -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), ] } @@ -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", } diff --git a/calyx-backend/src/lib.rs b/calyx-backend/src/lib.rs index 04efe3577b..3d102da8c7 100644 --- a/calyx-backend/src/lib.rs +++ b/calyx-backend/src/lib.rs @@ -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; diff --git a/calyx-backend/src/xilinx/toplevel.rs b/calyx-backend/src/xilinx/toplevel.rs index 5437541238..5f98fa3c53 100644 --- a/calyx-backend/src/xilinx/toplevel.rs +++ b/calyx-backend/src/xilinx/toplevel.rs @@ -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; @@ -37,14 +38,14 @@ 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() { @@ -52,7 +53,7 @@ impl Backend for XilinxInterfaceBackend { &format!("SINGLE_PORT_BRAM_{}", i), mem.width, mem.size, - mem.idx_size, + mem.idx_sizes[0], )) } @@ -71,7 +72,7 @@ impl Backend for XilinxInterfaceBackend { 64, mem.width, mem.size, - mem.idx_size, + mem.idx_sizes[0], )) } @@ -93,61 +94,21 @@ impl Backend for XilinxInterfaceBackend { } } -fn external_memories_cells(comp: &ir::Component) -> Vec> { - 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 { - 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 of memory names -fn external_memories(comp: &ir::Component) -> Vec { - 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> { + 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"); @@ -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)); diff --git a/calyx-backend/src/yxi.rs b/calyx-backend/src/yxi.rs new file mode 100644 index 0000000000..5b7f4af9bc --- /dev/null +++ b/calyx-backend/src/yxi.rs @@ -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>, +} + +#[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_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(()) + } +} diff --git a/calyx-ir/src/lib.rs b/calyx-ir/src/lib.rs index 265d7a02c0..abafbef09b 100644 --- a/calyx-ir/src/lib.rs +++ b/calyx-ir/src/lib.rs @@ -57,3 +57,5 @@ mod macros; /// Serializer methods for IR nodes. pub mod serializers; + +pub mod utils; diff --git a/calyx-ir/src/utils.rs b/calyx-ir/src/utils.rs new file mode 100644 index 0000000000..f93f99bb2d --- /dev/null +++ b/calyx-ir/src/utils.rs @@ -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 of memory names +pub fn external_memories_names(comp: &Component) -> Vec { + 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> { + 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, +} + +// 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; +} + +impl GetMemInfo for Vec> { + fn get_mem_info(&self) -> Vec { + self.iter() + .map(|cr| { + let mem = cr.borrow(); + let mem_size: u64; + let mut idx_sizes: Vec = 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 { + external_memories_cells(self).get_mem_info() + } +} diff --git a/calyx-utils/Cargo.toml b/calyx-utils/Cargo.toml index 65fb0b640c..b8e445d917 100644 --- a/calyx-utils/Cargo.toml +++ b/calyx-utils/Cargo.toml @@ -10,6 +10,7 @@ repository.workspace = true homepage.workspace = true categories.workspace = true readme.workspace = true +serde_json.workspace = true [features] default = [] @@ -17,6 +18,7 @@ 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 diff --git a/calyx-utils/src/errors.rs b/calyx-utils/src/errors.rs index 86a5948b7e..6aeb9fb539 100644 --- a/calyx-utils/src/errors.rs +++ b/calyx-utils/src/errors.rs @@ -197,3 +197,9 @@ impl From for Error { Error::write_error(format!("IO Error: {}", e)) } } + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::write_error(format!("serde_json Error: {}", e)) + } +} diff --git a/src/cmdline.rs b/src/cmdline.rs index 067f321b42..c15b141772 100644 --- a/src/cmdline.rs +++ b/src/cmdline.rs @@ -5,6 +5,7 @@ use calyx_backend::SexpBackend; use calyx_backend::{ xilinx::{XilinxInterfaceBackend, XilinxXmlBackend}, Backend, BackendOpt, MlirBackend, ResourcesBackend, VerilogBackend, + YxiBackend, }; use calyx_ir as ir; use calyx_utils::{CalyxResult, Error, OutputFile}; @@ -145,6 +146,10 @@ impl Opts { let backend = XilinxXmlBackend; backend.run(context, self.output) } + BackendOpt::Yxi => { + let backend = YxiBackend; + backend.run(context, self.output) + } BackendOpt::Calyx => { ir::Printer::write_context( &context, diff --git a/tests/backend/yxi/dot-product.expect b/tests/backend/yxi/dot-product.expect new file mode 100644 index 0000000000..2a4e1221f0 --- /dev/null +++ b/tests/backend/yxi/dot-product.expect @@ -0,0 +1,20 @@ +{ + "toplevel": "main", + "memories": [ + { + "name": "A0", + "width": 32, + "size": 8 + }, + { + "name": "B0", + "width": 32, + "size": 8 + }, + { + "name": "v0", + "width": 32, + "size": 1 + } + ] +} \ No newline at end of file diff --git a/tests/backend/yxi/dot-product.futil b/tests/backend/yxi/dot-product.futil new file mode 100644 index 0000000000..abf815854f --- /dev/null +++ b/tests/backend/yxi/dot-product.futil @@ -0,0 +1,93 @@ +// -b yxi +import "primitives/core.futil"; +import "primitives/binary_operators.futil"; +component main() -> () { + cells { + @external(1) A0 = std_mem_d1(32,8,4); + A_read0_0 = std_reg(32); + @external(1) B0 = std_mem_d1(32,8,4); + B_read0_0 = std_reg(32); + add0 = std_add(32); + add1 = std_add(4); + bin_read0_0 = std_reg(32); + const0 = std_const(4,0); + const1 = std_const(4,7); + const2 = std_const(1,0); + const3 = std_const(4,1); + dot_0 = std_reg(32); + i0 = std_reg(4); + le0 = std_le(4); + mult_pipe0 = std_mult_pipe(32); + @external(1) v0 = std_mem_d1(32,1,1); + } + wires { + comb group cond0 { + le0.left = i0.out; + le0.right = const1.out; + } + group let0<"static"=1> { + i0.in = const0.out; + i0.write_en = 1'd1; + let0[done] = i0.done; + } + group let1<"static"=4> { + bin_read0_0.in = mult_pipe0.out; + bin_read0_0.write_en = mult_pipe0.done; + let1[done] = bin_read0_0.done; + mult_pipe0.left = A_read0_0.out; + mult_pipe0.right = B_read0_0.out; + mult_pipe0.go = !mult_pipe0.done ? 1'd1; + } + group let2<"static"=1> { + dot_0.in = bin_read0_0.out; + dot_0.write_en = 1'd1; + let2[done] = dot_0.done; + } + group upd0<"static"=1> { + A_read0_0.write_en = 1'd1; + A0.addr0 = i0.out; + A_read0_0.in = 1'd1 ? A0.read_data; + upd0[done] = A_read0_0.done ? 1'd1; + } + group upd1<"static"=1> { + B_read0_0.write_en = 1'd1; + B0.addr0 = i0.out; + B_read0_0.in = 1'd1 ? B0.read_data; + upd1[done] = B_read0_0.done ? 1'd1; + } + group upd2<"static"=1> { + v0.write_en = 1'd1; + add0.left = v0.read_data; + add0.right = dot_0.out; + v0.addr0 = const2.out; + v0.write_data = 1'd1 ? add0.out; + upd2[done] = v0.done ? 1'd1; + } + group upd3<"static"=1> { + i0.write_en = 1'd1; + add1.left = i0.out; + add1.right = const3.out; + i0.in = 1'd1 ? add1.out; + upd3[done] = i0.done ? 1'd1; + } + } + control { + // ANCHOR: control + seq { + let0; + while le0.out with cond0 { + seq { + par { + upd0; + upd1; + } + let1; + let2; + upd2; + upd3; + } + } + } + // ANCHOR_END: control + } +} diff --git a/tests/backend/yxi/seq-mem-d4-add.expect b/tests/backend/yxi/seq-mem-d4-add.expect new file mode 100644 index 0000000000..9e6e49644c --- /dev/null +++ b/tests/backend/yxi/seq-mem-d4-add.expect @@ -0,0 +1,20 @@ +{ + "toplevel": "main", + "memories": [ + { + "name": "in1", + "width": 32, + "size": 24 + }, + { + "name": "in2", + "width": 32, + "size": 24 + }, + { + "name": "out", + "width": 32, + "size": 24 + } + ] +} \ No newline at end of file diff --git a/tests/backend/yxi/seq-mem-d4-add.futil b/tests/backend/yxi/seq-mem-d4-add.futil new file mode 100644 index 0000000000..7f7ff4c7ff --- /dev/null +++ b/tests/backend/yxi/seq-mem-d4-add.futil @@ -0,0 +1,166 @@ +// -b yxi +import "primitives/core.futil"; +import "primitives/binary_operators.futil"; +import "primitives/memories.futil"; + +component main() -> () { + cells { + @external in1 = seq_mem_d4(32,3,2,1,4,2,2,1,3); + @external in2 = seq_mem_d4(32,3,2,1,4,2,2,1,3); + @external out = seq_mem_d4(32,3,2,1,4,2,2,1,3); + + // Compute primitives + add = std_add(32); + in1_reg = std_reg(32); + in2_reg = std_reg(32); + in3_reg = std_reg(32); + in4_reg = std_reg(32); + + // Counter + i = std_reg(2); + j = std_reg(2); + k = std_reg(1); + l = std_reg(3); + + lt = std_lt(2); + lt2 = std_lt(2); + lt3 = std_lt(1); + lt4 = std_lt(3); + + + add_i = std_add(2); + add_j = std_add(2); + add_k = std_add(1); + add_l = std_add(3); + } + wires { + group init_i{ + i.write_en = 1'd1; + i.in = 2'd0; + init_i[done] = i.done; + } + group init_j{ + j.write_en = 1'd1; + j.in = 2'd0; + init_j[done] = j.done; + } + group init_k{ + k.write_en = 1'd1; + k.in = 1'd0; + init_k[done] = k.done; + } + group init_l{ + l.write_en = 1'd1; + l.in = 3'd0; + init_l[done] = l.done; + } + comb group i_lt { + lt.left = i.out; + lt.right = 2'd3; + } + comb group j_lt { + lt2.left = j.out; + lt2.right = 2'd2; + } + comb group k_lt { + lt3.left = k.out; + lt3.right = 1'd1; + } + comb group l_lt { + lt4.left = l.out; + lt4.right = 3'd4; + } + group read_in1{ + in1.addr0 = i.out; + in1.addr1 = j.out; + in1.addr2 = k.out; + in1.addr3 = l.out; + in1.read_en = 1'd1; + in1_reg.write_en = in1.read_done; + in1_reg.in = in1.read_data; + read_in1[done] = in1_reg.done; + } + group read_in2{ + in2.addr0 = i.out; + in2.addr1 = j.out; + in2.addr2 = k.out; + in2.addr3 = l.out; + in2.read_en = 1'd1; + in2_reg.write_en = in2.read_done; + in2_reg.in = in2.read_data; + read_in2[done] = in2_reg.done; + } + group update_val { + add.left = in1_reg.out; + add.right = in2_reg.out; + out.addr0 = i.out; + out.addr1 = j.out; + out.addr2 = k.out; + out.addr3 = l.out; + out.write_en = 1'd1; + out.write_data = add.out; + update_val[done] = out.write_done; + } + group incr_i { + add_i.left = i.out; + add_i.right = 2'd1; + i.write_en = 1'd1; + i.in = add_i.out; + incr_i[done] = i.done; + } + group incr_j { + add_j.left = j.out; + add_j.right = 2'd1; + j.write_en = 1'd1; + j.in = add_j.out; + incr_j[done] = j.done; + } + group incr_k { + add_k.left = k.out; + add_k.right = 1'd1; + k.write_en = 1'd1; + k.in = add_k.out; + incr_k[done] = k.done; + } + group incr_l { + add_l.left = l.out; + add_l.right = 3'd1; + l.write_en = 1'd1; + l.in = add_l.out; + incr_l[done] = l.done; + } + } + control { + seq { + init_i; + while lt.out with i_lt { + seq{ + init_j; + while lt2.out with j_lt{ + seq{ + init_k; + while lt3.out with k_lt{ + seq{ + init_l; + while lt4.out with l_lt{ + seq{ + par{ + read_in1; + read_in2; + } + update_val; + incr_l; + } + } + incr_k; + } + } + incr_j; + } + } + incr_i; + } + } + } + } +}