From 2439f795942ea2eb1bafb60db9e7286991465307 Mon Sep 17 00:00:00 2001 From: Griffin Berlstein Date: Wed, 12 Jun 2024 14:50:22 -0400 Subject: [PATCH 1/9] [Cider 2] fud2 fixes (#2140) * rename `interp-flat` to `cider` * fix the current tests * now you can run with flags * janky fix to disable logging by default for cider * janky fix to disable logging by default for cider * Fix the snapshots --- fud2/src/lib.rs | 42 ++++++++++++++++--- .../snapshots/tests__emit@calyx_debug.snap | 5 ++- ...sts__emit@calyx_firrtl_verilog-refmem.snap | 3 ++ .../tests__emit@calyx_icarus_dat.snap | 3 ++ .../tests__emit@calyx_icarus_vcd.snap | 3 ++ .../tests__emit@calyx_interp_dat.snap | 5 ++- .../tests__emit@calyx_verilator_dat.snap | 3 ++ .../tests__emit@calyx_verilator_vcd.snap | 3 ++ .../snapshots/tests__emit@calyx_verilog.snap | 3 ++ .../tests__emit@calyx_xrt-trace_vcd.snap | 3 ++ .../snapshots/tests__emit@calyx_xrt_dat.snap | 3 ++ interp/cider2-tests/runt.toml | 5 ++- .../src/flatten/structures/environment/env.rs | 2 +- 13 files changed, 73 insertions(+), 10 deletions(-) diff --git a/fud2/src/lib.rs b/fud2/src/lib.rs index d9f922a14d..345d274b7d 100644 --- a/fud2/src/lib.rs +++ b/fud2/src/lib.rs @@ -25,6 +25,14 @@ fn setup_calyx( "calyx-pass", "$calyx-exe -l $calyx-base -p $pass $args $in > $out", )?; + + e.config_var_or("flags", "calyx.flags", "-p none")?; + + e.rule( + "calyx-with-flags", + "$calyx-exe -l $calyx-base $flags $args $in > $out", + )?; + Ok(()) }); bld.op( @@ -500,6 +508,9 @@ pub fn build_driver(bld: &mut DriverBuilder) { // Interpreter. let debug = bld.state("debug", &[]); // A pseudo-state. + // A pseudo-state for cider input + let cider_state = bld.state("cider", &[]); + let cider_setup = bld.setup("Cider interpreter", |e| { e.config_var_or( "cider-exe", @@ -537,7 +548,7 @@ pub fn build_driver(bld: &mut DriverBuilder) { )?; e.rule( - "cider2", + "run-cider", "$cider-exe -l $calyx-base --data data.dump $in flat > $out", )?; @@ -552,6 +563,22 @@ pub fn build_driver(bld: &mut DriverBuilder) { Ok(()) }); + bld.op( + "calyx-to-cider", + &[sim_setup, calyx_setup], + calyx, + cider_state, + |e, input, _output| { + e.build_cmd( + &["cider-input.futil"], + "calyx-with-flags", + &[input], + &[], + )?; + Ok(()) + }, + ); + bld.op( "interp", &[ @@ -575,13 +602,18 @@ pub fn build_driver(bld: &mut DriverBuilder) { }, ); bld.op( - "interp-flat", + "cider", &[sim_setup, calyx_setup, cider_setup], - calyx, + cider_state, dat, - |e, input, output| { + |e, _input, output| { let out_file = "interp_out.dump"; - e.build_cmd(&[out_file], "cider2", &[input], &["data.dump"])?; + e.build_cmd( + &[out_file], + "run-cider", + &["cider-input.futil"], + &["data.dump"], + )?; e.build_cmd( &[output], "interp-to-dump", diff --git a/fud2/tests/snapshots/tests__emit@calyx_debug.snap b/fud2/tests/snapshots/tests__emit@calyx_debug.snap index 87bde0344b..a9dad091f0 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_debug.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_debug.snap @@ -31,6 +31,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # Cider interpreter cider-exe = $calyx-base/target/debug/cider @@ -47,7 +50,7 @@ rule dat-to-interp rule interp-to-dat command = $python interp-dat.py --from-interp $in $sim_data > $out build data.json: dat-to-interp $sim_data | interp-dat.py -rule cider2 +rule run-cider command = $cider-exe -l $calyx-base --data data.dump $in flat > $out rule dump-to-interp command = $cider-converter --to cider $in > $out diff --git a/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog-refmem.snap b/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog-refmem.snap index 464045381e..0846dbf5d1 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog-refmem.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_firrtl_verilog-refmem.snap @@ -14,6 +14,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # Custom Testbench Setup rule ref-to-external diff --git a/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap index 7c3278eefc..92d4e82f0b 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_icarus_dat.snap @@ -14,6 +14,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # RTL simulation python = python3 diff --git a/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap b/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap index e96ecc4384..c88fa1bebd 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_icarus_vcd.snap @@ -14,6 +14,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # RTL simulation python = python3 diff --git a/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap index 6097c4c2b9..ba67e07e4d 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_interp_dat.snap @@ -31,6 +31,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # Cider interpreter cider-exe = $calyx-base/target/debug/cider @@ -47,7 +50,7 @@ rule dat-to-interp rule interp-to-dat command = $python interp-dat.py --from-interp $in $sim_data > $out build data.json: dat-to-interp $sim_data | interp-dat.py -rule cider2 +rule run-cider command = $cider-exe -l $calyx-base --data data.dump $in flat > $out rule dump-to-interp command = $cider-converter --to cider $in > $out diff --git a/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap index bf595d2de8..d93a5518e4 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_verilator_dat.snap @@ -14,6 +14,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # RTL simulation python = python3 diff --git a/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap b/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap index 50ba7adf62..e501a2cb0b 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_verilator_vcd.snap @@ -14,6 +14,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # RTL simulation python = python3 diff --git a/fud2/tests/snapshots/tests__emit@calyx_verilog.snap b/fud2/tests/snapshots/tests__emit@calyx_verilog.snap index de7b7f66f2..839b5af13c 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_verilog.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_verilog.snap @@ -14,6 +14,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # build targets build stdin.sv: calyx stdin 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 a0e57b2fef..4f1c8297c1 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_xrt-trace_vcd.snap @@ -14,6 +14,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # Xilinx tools vivado-dir = /test/xilinx/vivado diff --git a/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap b/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap index 0be68b7fb4..b76a8f864f 100644 --- a/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap +++ b/fud2/tests/snapshots/tests__emit@calyx_xrt_dat.snap @@ -14,6 +14,9 @@ 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 +flags = -p none +rule calyx-with-flags + command = $calyx-exe -l $calyx-base $flags $args $in > $out # Xilinx tools vivado-dir = /test/xilinx/vivado diff --git a/interp/cider2-tests/runt.toml b/interp/cider2-tests/runt.toml index 3cc518f2d6..e884ad1c4e 100644 --- a/interp/cider2-tests/runt.toml +++ b/interp/cider2-tests/runt.toml @@ -67,7 +67,7 @@ expect_dir = "control" name = "invoke" paths = ["../tests/control/invoke/*.futil"] cmd = """ -fud2 {} --from calyx --to dat --through interp-flat -s sim.data={}.data | jq --sort-keys +fud2 {} --from calyx --to dat --through cider -s sim.data={}.data -s calyx.args="--log off" | jq --sort-keys """ timeout = 10 expect_dir = "invoke" @@ -106,8 +106,9 @@ name = "correctness dynamic" paths = ["../../tests/correctness/*.futil"] cmd = """ fud2 --from calyx --to dat \ - --through interp-flat \ + --through cider \ -s sim.data={}.data \ + -s calyx.args="--log off" \ {} | jq --sort-keys """ diff --git a/interp/src/flatten/structures/environment/env.rs b/interp/src/flatten/structures/environment/env.rs index 8adfa602c5..b620a6a750 100644 --- a/interp/src/flatten/structures/environment/env.rs +++ b/interp/src/flatten/structures/environment/env.rs @@ -1296,7 +1296,7 @@ impl<'a> Simulator<'a> { has_changed |= changed.as_bool(); } else if self.env.ports[dest].is_def() { - todo!("Raise an error here since this assignment is undefining things: {}", self.env.ctx.printer().print_assignment(ledger.comp_id, assign_idx)) + todo!("Raise an error here since this assignment is undefining things: {}. Port currently has value: {}", self.env.ctx.printer().print_assignment(ledger.comp_id, assign_idx), &self.env.ports[dest]) } } } From cb3fad3d7e1ff654329807f2eb6bf4df1ee2d84a Mon Sep 17 00:00:00 2001 From: Anshuman Mohan <10830208+anshumanmohan@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:17:35 -0400 Subject: [PATCH 2/9] Docs: Add fud registration instructions for Interpreter (#2138) * Add fud registration instructions * Put invoke tip in debugger page too * Ref cells and not invokes are cider's problem --- docs/debug/cider.md | 18 ++++++++++++++---- docs/running-calyx/interpreter.md | 10 +++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/debug/cider.md b/docs/debug/cider.md index f7b3bce11a..5f67aa5354 100644 --- a/docs/debug/cider.md +++ b/docs/debug/cider.md @@ -6,7 +6,10 @@ debugging Calyx programs. ## Getting Started -If you are using [`fud`][fud] getting started with the debugger is easy. +If you are using [`fud`][fud], getting started with the debugger is easy. + +First, follow [these instructions][interp-fud] to build the interpreter and register it as a `fud` stage. + Assuming you are trying to debug a program called `my_program.futil` with data file `my_program.futil.data`, invoke the debugger with the following command: @@ -14,9 +17,14 @@ file `my_program.futil.data`, invoke the debugger with the following command: fud e --to debugger -q my_program.futil -s verilog.data my_program.futil.data ``` -This will open the target program in the interactive debugger. Note that `fud` -uses **the quiet flag**, `-q`, here. This prevents the printing from the `fud` tool -from conflicting the debugger as both tools interact with standard out. +This will open the target program in the interactive debugger. Note that we use `fud`'s *quiet* flag, `-q`, here. This avoids clashes between `fud`'s outputs and the debugger's outputs, since both tools interact with `stdout`. + +The interpreter does not currently support [`ref` cells][ref-cells]. +To run the debugger on a program that uses these, you must first compile them away by running the `compile-invoke` pass: + +``` +fud e --to debugger -s calyx.flags '-p compile-invoke' -q my_program.futil -s verilog.data my_program.futil.data +``` ## Advancing Program execution @@ -280,3 +288,5 @@ Use `help` to see all commands. Use `exit` to exit the debugger. [fud]: ../running-calyx/fud/index.md [gdb]: https://sourceware.org/gdb/ [interp]: ../running-calyx/interpreter.md +[interp-fud]: ../running-calyx/interpreter.md#interpreting-via-fud +[ref-cells]: ../lang/memories-by-reference.md#the-easy-way-ref-cells \ No newline at end of file diff --git a/docs/running-calyx/interpreter.md b/docs/running-calyx/interpreter.md index b2fd00dafb..0440a79bad 100644 --- a/docs/running-calyx/interpreter.md +++ b/docs/running-calyx/interpreter.md @@ -22,7 +22,11 @@ The interpreter is available as a stage in [fud][], which lets you provide stand You'll want to build the interpreter first: - cd interp && cargo build + cd interp && cargo build && cd .. + +Now register the interpreter as a fud stage: + + fud config stages.interpreter.exec /target/debug/cider Here's how to run a Calyx program: @@ -42,4 +46,8 @@ For example, to fully lower the Calyx program before interpreting it: -s calyx.flags '-p all' \ interp/tests/control/if.futil +In particular, the interpreter does not currently support [`ref` cells][ref-cells]. +To run the interpreter on a program that uses these, you must first compile them away by running the `compile-invoke` pass. + [fud]: fud/index.md +[ref-cells]: ../lang/memories-by-reference.md#the-easy-way-ref-cells From 250a6b2d7ce1b1ae55f9a56ee2b60230c2ef3db7 Mon Sep 17 00:00:00 2001 From: Akash Dhiraj Date: Wed, 12 Jun 2024 17:04:18 -0400 Subject: [PATCH 3/9] Add error handling to width inference for `invoke` (#2142) * Add error handling to width inference for invoke * Typo * Add more helpful error message * Tweak error message a tad --------- Co-authored-by: Anshuman Mohan --- calyx-py/calyx/builder.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/calyx-py/calyx/builder.py b/calyx-py/calyx/builder.py index 3c2eea81eb..55c0835249 100644 --- a/calyx-py/calyx/builder.py +++ b/calyx-py/calyx/builder.py @@ -408,7 +408,10 @@ def comb_mem_d2( """Generate a StdMemD2 cell.""" self.prog.import_("primitives/memories/comb.futil") return self.cell( - name, ast.Stdlib.comb_mem_d2(bitwidth, len0, len1, idx_size0, idx_size1), is_external, is_ref + name, + ast.Stdlib.comb_mem_d2(bitwidth, len0, len1, idx_size0, idx_size1), + is_external, + is_ref, ) def seq_mem_d1( @@ -440,7 +443,10 @@ def seq_mem_d2( """Generate a SeqMemD2 cell.""" self.prog.import_("primitives/memories/seq.futil") return self.cell( - name, ast.Stdlib.seq_mem_d2(bitwidth, len0, len1, idx_size0, idx_size1), is_external, is_ref + name, + ast.Stdlib.seq_mem_d2(bitwidth, len0, len1, idx_size0, idx_size1), + is_external, + is_ref, ) def binary( @@ -787,7 +793,7 @@ def reg_store(self, reg, val, groupname=None): def mem_load_d1(self, mem, i, reg, groupname): """Inserts wiring into `self` to perform `reg := mem[i]`, - where `mem` is a seq_d1 memory or a comb_mem_d1 memory + where `mem` is a seq_d1 memory or a comb_mem_d1 memory """ assert mem.is_seq_mem_d1() or mem.is_comb_mem_d1() is_comb = mem.is_comb_mem_d1() @@ -805,7 +811,7 @@ def mem_load_d1(self, mem, i, reg, groupname): def mem_load_d2(self, mem, i, j, reg, groupname): """Inserts wiring into `self` to perform `reg := mem[i]`, - where `mem` is a seq_d2 memory or a comb_mem_d2 memory + where `mem` is a seq_d2 memory or a comb_mem_d2 memory """ assert mem.is_seq_mem_d2() or mem.is_comb_mem_d2() is_comb = mem.is_comb_mem_d2() @@ -824,7 +830,7 @@ def mem_load_d2(self, mem, i, j, reg, groupname): def mem_store_d1(self, mem, i, val, groupname): """Inserts wiring into `self` to perform `mem[i] := val`, - where `mem` is a seq_d1 memory or a comb_mem_d1 memory + where `mem` is a seq_d1 memory or a comb_mem_d1 memory """ assert mem.is_seq_mem_d1() or mem.is_comb_mem_d1() is_comb = mem.is_comb_mem_d1() @@ -839,7 +845,7 @@ def mem_store_d1(self, mem, i, val, groupname): def mem_store_d2(self, mem, i, j, val, groupname): """Inserts wiring into `self` to perform `mem[i] := val`, - where `mem` is a seq_d2 memory or a comb_mem_d2 memory + where `mem` is a seq_d2 memory or a comb_mem_d2 memory """ assert mem.is_seq_mem_d2() or mem.is_comb_mem_d2() is_comb = mem.is_comb_mem_d2() @@ -1092,6 +1098,16 @@ def invoke(cell: CellBuilder, **kwargs) -> ast.Invoke: The keyword arguments should have the form `in_*`, `out_*`, or `ref_*`, where `*` is the name of an input port, output port, or ref cell on the invoked cell. """ + + def try_infer_width(x): + width = cell.infer_width(x) + if not width: + raise WidthInferenceError( + f"Could not infer width of input '{x}' when invoking cell '{cell.name}'. " + "Consider using `const(width, value)` instead of `value`." + ) + return width + return ast.Invoke( cell._cell.id, [ @@ -1099,7 +1115,7 @@ def invoke(cell: CellBuilder, **kwargs) -> ast.Invoke: k[3:], ( ( - const(cell.infer_width(k[3:]), v).expr + const(try_infer_width(k[3:]), v).expr if isinstance(v, int) else ExprBuilder.unwrap(v) ) From 9f0a0adc7bb3f56cde717491dd502926d2559c4e Mon Sep 17 00:00:00 2001 From: Samuel Thomas Date: Wed, 12 Jun 2024 17:16:24 -0500 Subject: [PATCH 4/9] show op/state source in debug mode. useful for writing scripts (#2129) --- fud2/fud-core/Cargo.toml | 2 +- fud2/fud-core/src/exec/data.rs | 5 ++++ fud2/fud-core/src/exec/driver.rs | 23 +++++++++++++-- fud2/fud-core/src/script/plugin.rs | 46 +++++++++++++++++++++++++++--- 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/fud2/fud-core/Cargo.toml b/fud2/fud-core/Cargo.toml index 8cb27186d0..9c482a1e4c 100644 --- a/fud2/fud-core/Cargo.toml +++ b/fud2/fud-core/Cargo.toml @@ -19,7 +19,7 @@ camino = "1.1.6" anyhow.workspace = true log.workspace = true env_logger.workspace = true -rhai = "1.18.0" +rhai = { version = "1.18.0", features = ["internals"] } once_cell = "1.19.0" ariadne = "0.4.1" itertools.workspace = true diff --git a/fud2/fud-core/src/exec/data.rs b/fud2/fud-core/src/exec/data.rs index 6bb64ca6f3..8dbda63860 100644 --- a/fud2/fud-core/src/exec/data.rs +++ b/fud2/fud-core/src/exec/data.rs @@ -13,6 +13,9 @@ pub struct State { /// Pseudo-states can only be final outputs; they are appropraite for representing actions that /// interact directly with the user, for example. pub extensions: Vec, + + /// Describes where this operation was defined. + pub source: Option, } impl State { @@ -39,6 +42,8 @@ pub struct Operation { pub output: StateRef, pub setups: Vec, pub emit: Box, + /// Describes where this operation was defined. + pub source: Option, } /// A reference to an Operation. diff --git a/fud2/fud-core/src/exec/driver.rs b/fud2/fud-core/src/exec/driver.rs index 738c58a91d..94581989a5 100644 --- a/fud2/fud-core/src/exec/driver.rs +++ b/fud2/fud-core/src/exec/driver.rs @@ -223,17 +223,26 @@ impl Driver { for ext in &state.extensions { print!(" .{}", ext); } + if let Some(src) = &state.source { + print!(" ({src})") + } println!(); } println!(); println!("Operations:"); for (_, op) in self.ops.iter() { + let dev_info = op + .source + .as_ref() + .map(|src| format!(" ({src})")) + .unwrap_or_default(); println!( - " {}: {} -> {}", + " {}: {} -> {}{}", op.name, self.states[op.input].name, - self.states[op.output].name + self.states[op.output].name, + dev_info ); } } @@ -289,9 +298,14 @@ impl DriverBuilder { self.states.push(State { name: name.to_string(), extensions: extensions.iter().map(|s| s.to_string()).collect(), + source: None, }) } + pub fn state_source(&mut self, state: StateRef, src: S) { + self.states[state].source = Some(src.to_string()); + } + pub fn find_state(&self, needle: &str) -> Result { self.states .iter() @@ -337,6 +351,7 @@ impl DriverBuilder { input, output, emit: Box::new(emit), + source: None, }) } @@ -351,6 +366,10 @@ impl DriverBuilder { self.add_op(name, setups, input, output, build) } + pub fn op_source(&mut self, op: OpRef, src: S) { + self.ops[op].source = Some(src.to_string()); + } + pub fn rule( &mut self, setups: &[SetupRef], diff --git a/fud2/fud-core/src/script/plugin.rs b/fud2/fud-core/src/script/plugin.rs index c55ef00efd..cc0f782056 100644 --- a/fud2/fud-core/src/script/plugin.rs +++ b/fud2/fud-core/src/script/plugin.rs @@ -119,10 +119,32 @@ impl ScriptRunner { let bld = Rc::clone(&self.builder); self.engine.register_fn( "state", - move |name: &str, extensions: rhai::Array| { + move |ctx: rhai::NativeCallContext, + name: &str, + extensions: rhai::Array| { let v = to_str_slice(&extensions); let v = v.iter().map(|x| &**x).collect::>(); - bld.borrow_mut().state(name, &v) + let state = bld.borrow_mut().state(name, &v); + + #[cfg(not(debug_assertions))] + // use ctx when we build in release mode + // so that we don't get a warning + { + _ = ctx; + } + + // try to set state source + #[cfg(debug_assertions)] + if let Some(src) = + ctx.global_runtime_state().source().and_then(|p| { + PathBuf::from(p) + .file_name() + .map(|s| s.to_string_lossy().to_string()) + }) + { + bld.borrow_mut().state_source(state, src); + } + state }, ); } @@ -153,7 +175,15 @@ impl ScriptRunner { output: StateRef, rule_name: &str| { let setups = sctx.setups_array(&ctx, setups)?; - Ok(bld.borrow_mut().rule(&setups, input, output, rule_name)) + let op = + bld.borrow_mut().rule(&setups, input, output, rule_name); + + // try to set op source + #[cfg(debug_assertions)] + if let Some(name) = sctx.path.file_name() { + bld.borrow_mut().op_source(op, name.to_string_lossy()); + } + Ok(op) }, ); } @@ -174,7 +204,15 @@ impl ScriptRunner { ast: Rc::new(sctx.ast.clone_functions_only()), name: build.fn_name().to_string(), }; - Ok(bld.borrow_mut().add_op(name, &setups, input, output, rctx)) + let op = + bld.borrow_mut().add_op(name, &setups, input, output, rctx); + + // try to set op source + #[cfg(debug_assertions)] + if let Some(name) = sctx.path.file_name() { + bld.borrow_mut().op_source(op, name.to_string_lossy()); + } + Ok(op) }, ); } From e18da1b6a54ee2101d1964f16a582df403847356 Mon Sep 17 00:00:00 2001 From: Anshuman Mohan <10830208+anshumanmohan@users.noreply.github.com> Date: Thu, 13 Jun 2024 09:41:18 -0400 Subject: [PATCH 5/9] eDSL, docs: new eDSL example showing the usage of guards, and a correction in syntax in docs (#2133) * Get the builder to generate ops between ports * Small example showing various forms of guards * Tweak & and | docs for guards --- calyx-py/calyx/builder.py | 16 ++++++++++ calyx-py/calyx/py_ast.py | 36 +++++++++++++++++++++++ calyx-py/test/correctness/guards.data | 12 ++++++++ calyx-py/test/correctness/guards.expect | 5 ++++ calyx-py/test/correctness/guards.py | 39 +++++++++++++++++++++++++ docs/lang/ref.md | 10 +++---- 6 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 calyx-py/test/correctness/guards.data create mode 100644 calyx-py/test/correctness/guards.expect create mode 100644 calyx-py/test/correctness/guards.py diff --git a/calyx-py/calyx/builder.py b/calyx-py/calyx/builder.py index 55c0835249..8eab3f17e2 100644 --- a/calyx-py/calyx/builder.py +++ b/calyx-py/calyx/builder.py @@ -1235,6 +1235,22 @@ def __ne__(self, other: ExprBuilder): """Construct an inequality comparison with ==.""" return ExprBuilder(ast.Neq(self.expr, other.expr)) + def __lt__(self, other: ExprBuilder): + """Construct a less-than comparison with <.""" + return ExprBuilder(ast.Lt(self.expr, other.expr)) + + def __le__(self, other: ExprBuilder): + """Construct a less-than-or-equal comparison with <.""" + return ExprBuilder(ast.Lte(self.expr, other.expr)) + + def __gt__(self, other: ExprBuilder): + """Construct a greater-than comparison with <.""" + return ExprBuilder(ast.Gt(self.expr, other.expr)) + + def __ge__(self, other: ExprBuilder): + """Construct a greater-than-or-equal comparison with <.""" + return ExprBuilder(ast.Gte(self.expr, other.expr)) + @property def name(self): """Get the name of the expression.""" diff --git a/calyx-py/calyx/py_ast.py b/calyx-py/calyx/py_ast.py index 834d1da6dd..80fd3c32ab 100644 --- a/calyx-py/calyx/py_ast.py +++ b/calyx-py/calyx/py_ast.py @@ -400,6 +400,42 @@ def doc(self) -> str: return f"({self.left.doc()} != {self.right.doc()})" +@dataclass +class Lt(GuardExpr): + left: GuardExpr + right: GuardExpr + + def doc(self) -> str: + return f"({self.left.doc()} < {self.right.doc()})" + + +@dataclass +class Lte(GuardExpr): + left: GuardExpr + right: GuardExpr + + def doc(self) -> str: + return f"({self.left.doc()} <= {self.right.doc()})" + + +@dataclass +class Gt(GuardExpr): + left: GuardExpr + right: GuardExpr + + def doc(self) -> str: + return f"({self.left.doc()} > {self.right.doc()})" + + +@dataclass +class Gte(GuardExpr): + left: GuardExpr + right: GuardExpr + + def doc(self) -> str: + return f"({self.left.doc()} >= {self.right.doc()})" + + # Control @dataclass class Control(Emittable): diff --git a/calyx-py/test/correctness/guards.data b/calyx-py/test/correctness/guards.data new file mode 100644 index 0000000000..e9975bfde8 --- /dev/null +++ b/calyx-py/test/correctness/guards.data @@ -0,0 +1,12 @@ +{ + "mem": { + "data": [ + 0 + ], + "format": { + "numeric_type": "bitnum", + "is_signed": false, + "width": 32 + } + } +} \ No newline at end of file diff --git a/calyx-py/test/correctness/guards.expect b/calyx-py/test/correctness/guards.expect new file mode 100644 index 0000000000..e5e16d65cc --- /dev/null +++ b/calyx-py/test/correctness/guards.expect @@ -0,0 +1,5 @@ +{ + "mem": [ + 42 + ] +} diff --git a/calyx-py/test/correctness/guards.py b/calyx-py/test/correctness/guards.py new file mode 100644 index 0000000000..e398e87d7f --- /dev/null +++ b/calyx-py/test/correctness/guards.py @@ -0,0 +1,39 @@ +# pylint: disable=import-error +import calyx.builder as cb + + +def insert_main_component(prog): + """Insert the main component into the program. + This component will invoke the `muler`, `abs_diff`, `mux`, and `map` components. + """ + + comp = prog.component("main") + + mem = comp.seq_mem_d1("mem", 32, 1, 32, is_external=True) + + mul = comp.mult_pipe(32) + zero = cb.const(32, 0) + one = cb.const(32, 1) + + with comp.group("well-guarded_group") as wgg: + mul.left = zero @ 4 # This will never be executed + mul.left = (one <= zero) @ 5 # This will never be executed + mul.left = ~zero @ 6 # This will work + mul.right = (zero | one) @ 7 # This will work + mul.right = (zero & one) @ 8 # This will never be executed + mul.go = cb.HI + wgg.done = mul.done + + put_ans_in_mem = comp.mem_store_d1(mem, 0, mul.out, "store") + + comp.control += [wgg, put_ans_in_mem] + + +def build(): + prog = cb.Builder() + insert_main_component(prog) + return prog.program + + +if __name__ == "__main__": + build().emit() diff --git a/docs/lang/ref.md b/docs/lang/ref.md index f32ba0c91a..89dac0784b 100644 --- a/docs/lang/ref.md +++ b/docs/lang/ref.md @@ -221,13 +221,11 @@ port. Omitting a guard expression is equivalent to using `1'd1` (a constant "true") as the guard. Guards can use the following constructs: -- `port`: A port access on a defined cell -- `port op port`: A comparison between values on two ports. Valid `op` are: `>`, `<`, `>=`, `<=`, `==` +- `port`: A port access on a defined cell, such as `cond.out`, or a literal, such as `3'd2`. +- `port op port`: A comparison between values on two ports. Valid instances of `op` are: `>`, `<`, `>=`, `<=`, `==` - `!guard`: Logical negation of a guard value -- `guard || guard`: Disjunction between two guards -- `guard && guard`: Conjunction of two guards - -In the context of guards, a port can also be a literal (i.e., `counter.out == 3'd2` is a valid guard). +- `guard | guard`: Disjunction between two guards +- `guard & guard`: Conjunction of two guards > **Well-formedness**: For each input port on the LHS, only one guard should be active in any given cycle during the execution of a Calyx program. From 87f846249cad01d1112be1406ddba7035d04fbfd Mon Sep 17 00:00:00 2001 From: Anshuman Mohan <10830208+anshumanmohan@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:03:21 -0400 Subject: [PATCH 6/9] No more guard conjunction to declare group `done` signals (#2131) * One less guard disjunct in gen_exp * No guard conj in sdn.py * No more & in ntt * No more guard conj in tuple * No more group conj in gen_exp * Stray expect file --- calyx-py/calyx/gen_exp.py | 27 ++++++--- calyx-py/test/correctness/queues/sdn.py | 9 ++- calyx-py/test/correctness/tuple.py | 10 +++- frontends/ntt-pipeline/gen-ntt-pipeline.py | 22 ++++++-- tests/correctness/exp/any-base-1.expect | 2 +- tests/correctness/exp/any-base-2.expect | 2 +- tests/correctness/exp/any-base-3.expect | 2 +- tests/correctness/exp/degree-4-signed.expect | 2 +- .../correctness/exp/degree-4-unsigned.expect | 2 +- tests/correctness/exp/degree-8-signed.expect | 2 +- .../correctness/exp/degree-8-unsigned.expect | 2 +- tests/correctness/exp/neg-base.expect | 2 +- .../ntt-pipeline/ntt-16-reduced-4.expect | 2 +- tests/correctness/ntt-pipeline/ntt-16.expect | 2 +- tests/correctness/ntt-pipeline/ntt-8.expect | 2 +- tests/frontend/exp/degree-2-unsigned.expect | 28 +++++++--- tests/frontend/exp/degree-4-signed.expect | 28 +++++++--- tests/frontend/exp/degree-4-unsigned.expect | 28 +++++++--- .../ntt-pipeline/ntt-4-reduced-2.expect | 56 +++++++++++++------ tests/frontend/ntt-pipeline/ntt-4.expect | 56 +++++++++++++------ tests/frontend/relay/softmax.expect | 28 +++++++--- 21 files changed, 220 insertions(+), 94 deletions(-) diff --git a/calyx-py/calyx/gen_exp.py b/calyx-py/calyx/gen_exp.py index 560d542c05..92c738b5a6 100644 --- a/calyx-py/calyx/gen_exp.py +++ b/calyx-py/calyx/gen_exp.py @@ -49,14 +49,17 @@ def generate_fp_pow_component( ) # groups - with comp.group("init") as init: + with comp.group("init_pow") as init_pow: pow.in_ = FixedPoint( "1.0", width, int_width, is_signed=is_signed ).unsigned_integer() pow.write_en = 1 + init_pow.done = pow.done + + with comp.group("init_count") as init_count: count.in_ = 0 count.write_en = 1 - init.done = (pow.done & count.done) @ 1 + init_count.done = count.done with comp.group("execute_mul") as execute_mul: mul.left = comp.this().base @@ -73,7 +76,10 @@ def generate_fp_pow_component( with comp.continuous: comp.this().out = pow.out - comp.control += [init, while_with(cond, par(execute_mul, incr_count))] + comp.control += [ + par(init_pow, init_count), + while_with(cond, par(execute_mul, incr_count)), + ] return comp.component @@ -312,18 +318,20 @@ def generate_groups( int_x = comp.get_cell("int_x") frac_x = comp.get_cell("frac_x") one = comp.get_cell("one") - with comp.group("split_bits") as split_bits: + with comp.group("split_bits_int_x") as split_bits_int_x: and0.left = input.out and0.right = const(width, 2**width - 2**frac_width) rsh.left = and0.out rsh.right = const(width, frac_width) + int_x.write_en = 1 + int_x.in_ = rsh.out + split_bits_int_x.done = int_x.done + with comp.group("split_bits_frac_x") as split_bits_frac_x: and1.left = input.out and1.right = const(width, (2**frac_width) - 1) - int_x.write_en = 1 frac_x.write_en = 1 - int_x.in_ = rsh.out frac_x.in_ = and1.out - split_bits.done = (int_x.done & frac_x.done) @ 1 + split_bits_frac_x.done = frac_x.done if is_signed: mult_pipe = comp.get_cell("mult_pipe1") @@ -411,7 +419,8 @@ def generate_control(comp: ComponentBuilder, degree: int, is_signed: bool): if is_signed: lt = comp.get_cell("lt") init = comp.get_group("init") - split_bits = comp.get_group("split_bits") + split_bits_int_x = comp.get_group("split_bits_int_x") + split_bits_frac_x = comp.get_group("split_bits_frac_x") # TODO (griffin): This is a hack to avoid inserting empty seqs. Maybe worth # moving into the add method of ControlBuilder? @@ -430,7 +439,7 @@ def generate_control(comp: ComponentBuilder, degree: int, is_signed: bool): if is_signed else [] ), - split_bits, + par(split_bits_int_x, split_bits_frac_x), pow_invokes, consume_pow, mult_by_reciprocal, diff --git a/calyx-py/test/correctness/queues/sdn.py b/calyx-py/test/correctness/queues/sdn.py index a89372cd7d..4d73ed8843 100644 --- a/calyx-py/test/correctness/queues/sdn.py +++ b/calyx-py/test/correctness/queues/sdn.py @@ -79,15 +79,18 @@ def insert_controller(prog, name, stats_component): count_0 = controller.reg(32) count_1 = controller.reg(32) - with controller.group("get_data_locally") as get_data_locally: + with controller.group("get_data_locally_count0") as get_data_locally_count0: count_0.in_ = stats.count_0 count_0.write_en = 1 + get_data_locally_count0.done = count_0.done + + with controller.group("get_data_locally_count1") as get_data_locally_count1: count_1.in_ = stats.count_1 count_1.write_en = 1 - get_data_locally.done = (count_0.done & count_1.done) @ 1 + get_data_locally_count1.done = count_1.done # The main logic. - controller.control += get_data_locally + controller.control += cb.par(get_data_locally_count0, get_data_locally_count1) # Great, now I have the data around locally. return controller diff --git a/calyx-py/test/correctness/tuple.py b/calyx-py/test/correctness/tuple.py index aaeb34add8..acfa7d56ee 100644 --- a/calyx-py/test/correctness/tuple.py +++ b/calyx-py/test/correctness/tuple.py @@ -78,19 +78,23 @@ def insert_main(prog): mem1.content_en = cb.HI run_tuplify.done = mem1.done - with comp.group("run_untuplify") as run_untuplify: + with comp.group("run_untuplify_fst") as run_untuplify_fst: untuplify.tup = cb.const(64, 17179869186) mem2.addr0 = cb.const(1, 0) mem2.write_en = cb.HI mem2.write_data = untuplify.fst mem2.content_en = cb.HI + run_untuplify_fst.done = mem2.done + + with comp.group("run_untuplify_snd") as run_untuplify_snd: + untuplify.tup = cb.const(64, 17179869186) mem3.addr0 = cb.const(1, 0) mem3.write_en = cb.HI mem3.write_data = untuplify.snd mem3.content_en = cb.HI - run_untuplify.done = (mem2.done & mem3.done) @ cb.HI + run_untuplify_snd.done = mem3.done - comp.control += cb.par(run_tuplify, run_untuplify) + comp.control += cb.par(run_tuplify, run_untuplify_fst, run_untuplify_snd) return comp diff --git a/frontends/ntt-pipeline/gen-ntt-pipeline.py b/frontends/ntt-pipeline/gen-ntt-pipeline.py index 237edc5ebe..d035b024bb 100755 --- a/frontends/ntt-pipeline/gen-ntt-pipeline.py +++ b/frontends/ntt-pipeline/gen-ntt-pipeline.py @@ -204,14 +204,16 @@ def preamble_group(comp: cb.ComponentBuilder, row): phi = comp.get_cell(f"phi{row}") input = comp.get_cell("a") phis = comp.get_cell("phis") - with main.group(f"preamble_{row}") as preamble: + with main.group(f"preamble_{row}_reg") as preamble_reg: input.addr0 = row - phis.addr0 = row reg.write_en = 1 reg.in_ = input.read_data + preamble_reg.done = reg.done + with main.group(f"preamble_{row}_phi") as preamble_phi: + phis.addr0 = row phi.write_en = 1 phi.in_ = phis.read_data - preamble.done = (reg.done & phi.done) @ 1 + preamble_phi.done = phi.done def epilogue_group(comp: cb.ComponentBuilder, row): input = comp.get_cell("a") @@ -250,7 +252,19 @@ def wires(main: cb.ComponentBuilder): epilogue_group(main, r) def control(): - preambles = [ast.SeqComp([ast.Enable(f"preamble_{r}") for r in range(n)])] + preambles = [ + ast.SeqComp( + [ + ast.ParComp( + [ + ast.Enable(f"preamble_{r}_reg"), + ast.Enable(f"preamble_{r}_phi"), + ] + ) + for r in range(n) + ] + ) + ] epilogues = [ast.SeqComp([ast.Enable(f"epilogue_{r}") for r in range(n)])] ntt_stages = [] diff --git a/tests/correctness/exp/any-base-1.expect b/tests/correctness/exp/any-base-1.expect index 04633a5641..61bc1487c6 100644 --- a/tests/correctness/exp/any-base-1.expect +++ b/tests/correctness/exp/any-base-1.expect @@ -1,5 +1,5 @@ { - "cycles": 213, + "cycles": 212, "memories": { "b": [ "4.5" diff --git a/tests/correctness/exp/any-base-2.expect b/tests/correctness/exp/any-base-2.expect index d415bab291..684b83ce03 100644 --- a/tests/correctness/exp/any-base-2.expect +++ b/tests/correctness/exp/any-base-2.expect @@ -1,5 +1,5 @@ { - "cycles": 208, + "cycles": 207, "memories": { "b": [ "7.5" diff --git a/tests/correctness/exp/any-base-3.expect b/tests/correctness/exp/any-base-3.expect index 721f012ced..30fcd7e6a4 100644 --- a/tests/correctness/exp/any-base-3.expect +++ b/tests/correctness/exp/any-base-3.expect @@ -1,5 +1,5 @@ { - "cycles": 304, + "cycles": 303, "memories": { "b": [ "0.75" diff --git a/tests/correctness/exp/degree-4-signed.expect b/tests/correctness/exp/degree-4-signed.expect index b09674cdcc..8b3e2d6a23 100644 --- a/tests/correctness/exp/degree-4-signed.expect +++ b/tests/correctness/exp/degree-4-signed.expect @@ -1,5 +1,5 @@ { - "cycles": 56, + "cycles": 55, "memories": { "ret": [ "2.7182769775390625" diff --git a/tests/correctness/exp/degree-4-unsigned.expect b/tests/correctness/exp/degree-4-unsigned.expect index 2bffa80517..19c96047e9 100644 --- a/tests/correctness/exp/degree-4-unsigned.expect +++ b/tests/correctness/exp/degree-4-unsigned.expect @@ -1,5 +1,5 @@ { - "cycles": 49, + "cycles": 48, "memories": { "ret": [ "2.7182769775390625" diff --git a/tests/correctness/exp/degree-8-signed.expect b/tests/correctness/exp/degree-8-signed.expect index 5caebdbb6a..846a5d869e 100644 --- a/tests/correctness/exp/degree-8-signed.expect +++ b/tests/correctness/exp/degree-8-signed.expect @@ -1,5 +1,5 @@ { - "cycles": 134, + "cycles": 133, "memories": { "ret": [ "0.0001068115234375" diff --git a/tests/correctness/exp/degree-8-unsigned.expect b/tests/correctness/exp/degree-8-unsigned.expect index 0dbc85dd24..ce68145f7e 100644 --- a/tests/correctness/exp/degree-8-unsigned.expect +++ b/tests/correctness/exp/degree-8-unsigned.expect @@ -1,5 +1,5 @@ { - "cycles": 75, + "cycles": 74, "memories": { "ret": [ "9181.710357666015625" diff --git a/tests/correctness/exp/neg-base.expect b/tests/correctness/exp/neg-base.expect index 1a659a4e2c..9d05a7ffa1 100644 --- a/tests/correctness/exp/neg-base.expect +++ b/tests/correctness/exp/neg-base.expect @@ -1,5 +1,5 @@ { - "cycles": 221, + "cycles": 220, "memories": { "b": [ "-2.600006103515625" diff --git a/tests/correctness/ntt-pipeline/ntt-16-reduced-4.expect b/tests/correctness/ntt-pipeline/ntt-16-reduced-4.expect index fecc29a73d..2174700c0a 100644 --- a/tests/correctness/ntt-pipeline/ntt-16-reduced-4.expect +++ b/tests/correctness/ntt-pipeline/ntt-16-reduced-4.expect @@ -1,5 +1,5 @@ { - "cycles": 663, + "cycles": 647, "memories": { "a": [ 7371, diff --git a/tests/correctness/ntt-pipeline/ntt-16.expect b/tests/correctness/ntt-pipeline/ntt-16.expect index 32d889a457..098e88d012 100644 --- a/tests/correctness/ntt-pipeline/ntt-16.expect +++ b/tests/correctness/ntt-pipeline/ntt-16.expect @@ -1,5 +1,5 @@ { - "cycles": 216, + "cycles": 200, "memories": { "a": [ 7371, diff --git a/tests/correctness/ntt-pipeline/ntt-8.expect b/tests/correctness/ntt-pipeline/ntt-8.expect index f152c8118d..64c920f515 100644 --- a/tests/correctness/ntt-pipeline/ntt-8.expect +++ b/tests/correctness/ntt-pipeline/ntt-8.expect @@ -1,5 +1,5 @@ { - "cycles": 150, + "cycles": 142, "memories": { "a": [ 5390, diff --git a/tests/frontend/exp/degree-2-unsigned.expect b/tests/frontend/exp/degree-2-unsigned.expect index a322142f58..f8c466918a 100644 --- a/tests/frontend/exp/degree-2-unsigned.expect +++ b/tests/frontend/exp/degree-2-unsigned.expect @@ -29,18 +29,21 @@ component exp(x: 32) -> (out: 32) { exponent_value.in = x; init[done] = exponent_value.done; } - group split_bits { + group split_bits_int_x { and0.left = exponent_value.out; and0.right = 32'd4294901760; rsh.left = and0.out; rsh.right = 32'd16; + int_x.write_en = 1'd1; + int_x.in = rsh.out; + split_bits_int_x[done] = int_x.done; + } + group split_bits_frac_x { and1.left = exponent_value.out; and1.right = 32'd65535; - int_x.write_en = 1'd1; frac_x.write_en = 1'd1; - int_x.in = rsh.out; frac_x.in = and1.out; - split_bits[done] = (int_x.done & frac_x.done) ? 1'd1; + split_bits_frac_x[done] = frac_x.done; } group consume_pow2<"promotable"=1> { p2.write_en = 1'd1; @@ -82,7 +85,10 @@ component exp(x: 32) -> (out: 32) { control { seq { init; - split_bits; + par { + split_bits_int_x; + split_bits_frac_x; + } par { invoke pow1(base=e.out, integer_exp=int_x.out)(); invoke pow2(base=frac_x.out, integer_exp=c2.out)(); @@ -110,12 +116,15 @@ component fp_pow(base: 32, integer_exp: 32) -> (out: 32) { lt_1 = std_lt(32); } wires { - group init { + group init_pow { pow.in = 32'd65536; pow.write_en = 1'd1; + init_pow[done] = pow.done; + } + group init_count { count.in = 32'd0; count.write_en = 1'd1; - init[done] = (pow.done & count.done) ? 1'd1; + init_count[done] = count.done; } group execute_mul { mul.left = base; @@ -140,7 +149,10 @@ component fp_pow(base: 32, integer_exp: 32) -> (out: 32) { } control { seq { - init; + par { + init_pow; + init_count; + } while lt_1.out with lt_1_group { par { execute_mul; diff --git a/tests/frontend/exp/degree-4-signed.expect b/tests/frontend/exp/degree-4-signed.expect index 2d4bf16b3f..619f76d207 100644 --- a/tests/frontend/exp/degree-4-signed.expect +++ b/tests/frontend/exp/degree-4-signed.expect @@ -46,18 +46,21 @@ component exp(x: 16) -> (out: 16) { exponent_value.in = x; init[done] = exponent_value.done; } - group split_bits { + group split_bits_int_x { and0.left = exponent_value.out; and0.right = 16'd65280; rsh.left = and0.out; rsh.right = 16'd8; + int_x.write_en = 1'd1; + int_x.in = rsh.out; + split_bits_int_x[done] = int_x.done; + } + group split_bits_frac_x { and1.left = exponent_value.out; and1.right = 16'd255; - int_x.write_en = 1'd1; frac_x.write_en = 1'd1; - int_x.in = rsh.out; frac_x.in = and1.out; - split_bits[done] = (int_x.done & frac_x.done) ? 1'd1; + split_bits_frac_x[done] = frac_x.done; } group negate { mult_pipe1.left = exponent_value.out; @@ -162,7 +165,10 @@ component exp(x: 16) -> (out: 16) { if lt.out with is_negative { negate; } - split_bits; + par { + split_bits_int_x; + split_bits_frac_x; + } par { invoke pow1(base=e.out, integer_exp=int_x.out)(); invoke pow2(base=frac_x.out, integer_exp=c2.out)(); @@ -203,12 +209,15 @@ component fp_pow(base: 16, integer_exp: 16) -> (out: 16) { lt_1 = std_slt(16); } wires { - group init { + group init_pow { pow.in = 16'd256; pow.write_en = 1'd1; + init_pow[done] = pow.done; + } + group init_count { count.in = 16'd0; count.write_en = 1'd1; - init[done] = (pow.done & count.done) ? 1'd1; + init_count[done] = count.done; } group execute_mul { mul.left = base; @@ -233,7 +242,10 @@ component fp_pow(base: 16, integer_exp: 16) -> (out: 16) { } control { seq { - init; + par { + init_pow; + init_count; + } while lt_1.out with lt_1_group { par { execute_mul; diff --git a/tests/frontend/exp/degree-4-unsigned.expect b/tests/frontend/exp/degree-4-unsigned.expect index 4e0c1f85d8..4ce6a5d1e7 100644 --- a/tests/frontend/exp/degree-4-unsigned.expect +++ b/tests/frontend/exp/degree-4-unsigned.expect @@ -43,18 +43,21 @@ component exp(x: 16) -> (out: 16) { exponent_value.in = x; init[done] = exponent_value.done; } - group split_bits { + group split_bits_int_x { and0.left = exponent_value.out; and0.right = 16'd65280; rsh.left = and0.out; rsh.right = 16'd8; + int_x.write_en = 1'd1; + int_x.in = rsh.out; + split_bits_int_x[done] = int_x.done; + } + group split_bits_frac_x { and1.left = exponent_value.out; and1.right = 16'd255; - int_x.write_en = 1'd1; frac_x.write_en = 1'd1; - int_x.in = rsh.out; frac_x.in = and1.out; - split_bits[done] = (int_x.done & frac_x.done) ? 1'd1; + split_bits_frac_x[done] = frac_x.done; } group consume_pow2<"promotable"=1> { p2.write_en = 1'd1; @@ -136,7 +139,10 @@ component exp(x: 16) -> (out: 16) { control { seq { init; - split_bits; + par { + split_bits_int_x; + split_bits_frac_x; + } par { invoke pow1(base=e.out, integer_exp=int_x.out)(); invoke pow2(base=frac_x.out, integer_exp=c2.out)(); @@ -174,12 +180,15 @@ component fp_pow(base: 16, integer_exp: 16) -> (out: 16) { lt_1 = std_lt(16); } wires { - group init { + group init_pow { pow.in = 16'd256; pow.write_en = 1'd1; + init_pow[done] = pow.done; + } + group init_count { count.in = 16'd0; count.write_en = 1'd1; - init[done] = (pow.done & count.done) ? 1'd1; + init_count[done] = count.done; } group execute_mul { mul.left = base; @@ -204,7 +213,10 @@ component fp_pow(base: 16, integer_exp: 16) -> (out: 16) { } control { seq { - init; + par { + init_pow; + init_count; + } while lt_1.out with lt_1_group { par { execute_mul; diff --git a/tests/frontend/ntt-pipeline/ntt-4-reduced-2.expect b/tests/frontend/ntt-pipeline/ntt-4-reduced-2.expect index afbf82d6cc..815607be46 100644 --- a/tests/frontend/ntt-pipeline/ntt-4-reduced-2.expect +++ b/tests/frontend/ntt-pipeline/ntt-4-reduced-2.expect @@ -39,41 +39,53 @@ component main() -> () { sub1 = std_ssub(32); } wires { - group preamble_0 { + group preamble_0_reg { a.addr0 = 3'd0; - phis.addr0 = 3'd0; r0.write_en = 1'd1; r0.in = a.read_data; + preamble_0_reg[done] = r0.done; + } + group preamble_0_phi { + phis.addr0 = 3'd0; phi0.write_en = 1'd1; phi0.in = phis.read_data; - preamble_0[done] = (r0.done & phi0.done) ? 1'd1; + preamble_0_phi[done] = phi0.done; } - group preamble_1 { + group preamble_1_reg { a.addr0 = 3'd1; - phis.addr0 = 3'd1; r1.write_en = 1'd1; r1.in = a.read_data; + preamble_1_reg[done] = r1.done; + } + group preamble_1_phi { + phis.addr0 = 3'd1; phi1.write_en = 1'd1; phi1.in = phis.read_data; - preamble_1[done] = (r1.done & phi1.done) ? 1'd1; + preamble_1_phi[done] = phi1.done; } - group preamble_2 { + group preamble_2_reg { a.addr0 = 3'd2; - phis.addr0 = 3'd2; r2.write_en = 1'd1; r2.in = a.read_data; + preamble_2_reg[done] = r2.done; + } + group preamble_2_phi { + phis.addr0 = 3'd2; phi2.write_en = 1'd1; phi2.in = phis.read_data; - preamble_2[done] = (r2.done & phi2.done) ? 1'd1; + preamble_2_phi[done] = phi2.done; } - group preamble_3 { + group preamble_3_reg { a.addr0 = 3'd3; - phis.addr0 = 3'd3; r3.write_en = 1'd1; r3.in = a.read_data; + preamble_3_reg[done] = r3.done; + } + group preamble_3_phi { + phis.addr0 = 3'd3; phi3.write_en = 1'd1; phi3.in = phis.read_data; - preamble_3[done] = (r3.done & phi3.done) ? 1'd1; + preamble_3_phi[done] = phi3.done; } group precursor_0 { r0.in = A0.out; @@ -227,10 +239,22 @@ component main() -> () { control { seq { seq { - preamble_0; - preamble_1; - preamble_2; - preamble_3; + par { + preamble_0_reg; + preamble_0_phi; + } + par { + preamble_1_reg; + preamble_1_phi; + } + par { + preamble_2_reg; + preamble_2_phi; + } + par { + preamble_3_reg; + preamble_3_phi; + } } par { s0_mul0; diff --git a/tests/frontend/ntt-pipeline/ntt-4.expect b/tests/frontend/ntt-pipeline/ntt-4.expect index 5f23d7d0b8..e711716bd4 100644 --- a/tests/frontend/ntt-pipeline/ntt-4.expect +++ b/tests/frontend/ntt-pipeline/ntt-4.expect @@ -39,41 +39,53 @@ component main() -> () { sub1 = std_ssub(32); } wires { - group preamble_0 { + group preamble_0_reg { a.addr0 = 3'd0; - phis.addr0 = 3'd0; r0.write_en = 1'd1; r0.in = a.read_data; + preamble_0_reg[done] = r0.done; + } + group preamble_0_phi { + phis.addr0 = 3'd0; phi0.write_en = 1'd1; phi0.in = phis.read_data; - preamble_0[done] = (r0.done & phi0.done) ? 1'd1; + preamble_0_phi[done] = phi0.done; } - group preamble_1 { + group preamble_1_reg { a.addr0 = 3'd1; - phis.addr0 = 3'd1; r1.write_en = 1'd1; r1.in = a.read_data; + preamble_1_reg[done] = r1.done; + } + group preamble_1_phi { + phis.addr0 = 3'd1; phi1.write_en = 1'd1; phi1.in = phis.read_data; - preamble_1[done] = (r1.done & phi1.done) ? 1'd1; + preamble_1_phi[done] = phi1.done; } - group preamble_2 { + group preamble_2_reg { a.addr0 = 3'd2; - phis.addr0 = 3'd2; r2.write_en = 1'd1; r2.in = a.read_data; + preamble_2_reg[done] = r2.done; + } + group preamble_2_phi { + phis.addr0 = 3'd2; phi2.write_en = 1'd1; phi2.in = phis.read_data; - preamble_2[done] = (r2.done & phi2.done) ? 1'd1; + preamble_2_phi[done] = phi2.done; } - group preamble_3 { + group preamble_3_reg { a.addr0 = 3'd3; - phis.addr0 = 3'd3; r3.write_en = 1'd1; r3.in = a.read_data; + preamble_3_reg[done] = r3.done; + } + group preamble_3_phi { + phis.addr0 = 3'd3; phi3.write_en = 1'd1; phi3.in = phis.read_data; - preamble_3[done] = (r3.done & phi3.done) ? 1'd1; + preamble_3_phi[done] = phi3.done; } group precursor_0 { r0.in = A0.out; @@ -227,10 +239,22 @@ component main() -> () { control { seq { seq { - preamble_0; - preamble_1; - preamble_2; - preamble_3; + par { + preamble_0_reg; + preamble_0_phi; + } + par { + preamble_1_reg; + preamble_1_phi; + } + par { + preamble_2_reg; + preamble_2_phi; + } + par { + preamble_3_reg; + preamble_3_phi; + } } par { s0_mul0; diff --git a/tests/frontend/relay/softmax.expect b/tests/frontend/relay/softmax.expect index 37c9b68721..b6c96ec92a 100644 --- a/tests/frontend/relay/softmax.expect +++ b/tests/frontend/relay/softmax.expect @@ -369,18 +369,21 @@ component exp(x: 32) -> (out: 32) { exponent_value.in = x; init[done] = exponent_value.done; } - group split_bits { + group split_bits_int_x { and0.left = exponent_value.out; and0.right = 32'd4294901760; rsh.left = and0.out; rsh.right = 32'd16; + int_x.write_en = 1'd1; + int_x.in = rsh.out; + split_bits_int_x[done] = int_x.done; + } + group split_bits_frac_x { and1.left = exponent_value.out; and1.right = 32'd65535; - int_x.write_en = 1'd1; frac_x.write_en = 1'd1; - int_x.in = rsh.out; frac_x.in = and1.out; - split_bits[done] = (int_x.done & frac_x.done) ? 1'd1; + split_bits_frac_x[done] = frac_x.done; } group negate { mult_pipe1.left = exponent_value.out; @@ -565,7 +568,10 @@ component exp(x: 32) -> (out: 32) { if lt.out with is_negative { negate; } - split_bits; + par { + split_bits_int_x; + split_bits_frac_x; + } par { invoke pow1(base=e.out, integer_exp=int_x.out)(); invoke pow2(base=frac_x.out, integer_exp=c2.out)(); @@ -624,12 +630,15 @@ component fp_pow(base: 32, integer_exp: 32) -> (out: 32) { lt_1 = std_slt(32); } wires { - group init { + group init_pow { pow.in = 32'd65536; pow.write_en = 1'd1; + init_pow[done] = pow.done; + } + group init_count { count.in = 32'd0; count.write_en = 1'd1; - init[done] = (pow.done & count.done) ? 1'd1; + init_count[done] = count.done; } group execute_mul { mul.left = base; @@ -654,7 +663,10 @@ component fp_pow(base: 32, integer_exp: 32) -> (out: 32) { } control { seq { - init; + par { + init_pow; + init_count; + } while lt_1.out with lt_1_group { par { execute_mul; From 5250bf96cf202ebf4a567a0bb7052a332cf96d55 Mon Sep 17 00:00:00 2001 From: Anshuman Mohan <10830208+anshumanmohan@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:19:06 -0400 Subject: [PATCH 7/9] Prefer `seq_mem`s to `comb_mem`s all over (#2127) * Port Python code to seq mems * Update expect files to deal with new seq mems * Fix gen_exp to use seq mems * Catch up expect files to seq mems * Trigger Build * Silly mistake * Expect files chasing after silly mistake --- calyx-py/calyx/gen_exp.py | 15 +++--- frontends/ntt-pipeline/gen-ntt-pipeline.py | 14 +++--- tests/correctness/exp/any-base-1.expect | 2 +- tests/correctness/exp/any-base-2.expect | 2 +- tests/correctness/exp/any-base-3.expect | 2 +- tests/correctness/exp/degree-4-signed.expect | 2 +- .../correctness/exp/degree-4-unsigned.expect | 2 +- tests/correctness/exp/degree-8-signed.expect | 2 +- .../correctness/exp/degree-8-unsigned.expect | 2 +- tests/correctness/exp/neg-base.expect | 2 +- .../ntt-pipeline/ntt-16-reduced-4.expect | 2 +- tests/correctness/ntt-pipeline/ntt-16.expect | 2 +- tests/correctness/ntt-pipeline/ntt-8.expect | 2 +- tests/frontend/exp/degree-2-unsigned.expect | 12 +++-- tests/frontend/exp/degree-4-signed.expect | 12 +++-- tests/frontend/exp/degree-4-unsigned.expect | 12 +++-- .../ntt-pipeline/ntt-4-reduced-2.expect | 50 ++++++++++++------- tests/frontend/ntt-pipeline/ntt-4.expect | 50 ++++++++++++------- 18 files changed, 110 insertions(+), 77 deletions(-) diff --git a/calyx-py/calyx/gen_exp.py b/calyx-py/calyx/gen_exp.py index 92c738b5a6..95d1c880b7 100644 --- a/calyx-py/calyx/gen_exp.py +++ b/calyx-py/calyx/gen_exp.py @@ -704,9 +704,9 @@ def build_base_not_e(degree, width, int_width, is_signed) -> Program: main = builder.component("main") base_reg = main.reg(width, "base_reg") exp_reg = main.reg(width, "exp_reg") - x = main.comb_mem_d1("x", width, 1, 1, is_external=True) - b = main.comb_mem_d1("b", width, 1, 1, is_external=True) - ret = main.comb_mem_d1("ret", width, 1, 1, is_external=True) + x = main.seq_mem_d1("x", width, 1, 1, is_external=True) + b = main.seq_mem_d1("b", width, 1, 1, is_external=True) + ret = main.seq_mem_d1("ret", width, 1, 1, is_external=True) f = main.comp_instance("f", "fp_pow_full") read_base = main.mem_load_d1(b, 0, base_reg, "read_base") @@ -740,14 +740,15 @@ def build_base_is_e(degree, width, int_width, is_signed) -> Program: main = builder.component("main") t = main.reg(width, "t") - x = main.comb_mem_d1("x", width, 1, 1, is_external=True) - ret = main.comb_mem_d1("ret", width, 1, 1, is_external=True) + x = main.seq_mem_d1("x", width, 1, 1, is_external=True) + ret = main.seq_mem_d1("ret", width, 1, 1, is_external=True) e = main.comp_instance("e", "exp") with main.group("init") as init: x.addr0 = 0 - t.in_ = x.read_data - t.write_en = 1 + x.content_en = 1 + t.in_ = x.done @ x.read_data + t.write_en = x.done @ 1 init.done = t.done write_to_memory = main.mem_store_d1(ret, 0, e.out, "write_to_memory") diff --git a/frontends/ntt-pipeline/gen-ntt-pipeline.py b/frontends/ntt-pipeline/gen-ntt-pipeline.py index d035b024bb..0b1261583f 100755 --- a/frontends/ntt-pipeline/gen-ntt-pipeline.py +++ b/frontends/ntt-pipeline/gen-ntt-pipeline.py @@ -206,13 +206,15 @@ def preamble_group(comp: cb.ComponentBuilder, row): phis = comp.get_cell("phis") with main.group(f"preamble_{row}_reg") as preamble_reg: input.addr0 = row - reg.write_en = 1 - reg.in_ = input.read_data + input.content_en = 1 + reg.write_en = input.done @ 1 + reg.in_ = input.done @ input.read_data preamble_reg.done = reg.done with main.group(f"preamble_{row}_phi") as preamble_phi: phis.addr0 = row - phi.write_en = 1 - phi.in_ = phis.read_data + phis.content_en = 1 + phi.write_en = phis.done @ 1 + phi.in_ = phis.done @ phis.read_data preamble_phi.done = phi.done def epilogue_group(comp: cb.ComponentBuilder, row): @@ -222,8 +224,8 @@ def epilogue_group(comp: cb.ComponentBuilder, row): def insert_cells(comp: cb.ComponentBuilder): # memories - comp.comb_mem_d1("a", input_bitwidth, n, bitwidth, is_external=True) - comp.comb_mem_d1("phis", input_bitwidth, n, bitwidth, is_external=True) + comp.seq_mem_d1("a", input_bitwidth, n, bitwidth, is_external=True) + comp.seq_mem_d1("phis", input_bitwidth, n, bitwidth, is_external=True) for r in range(n): comp.reg(input_bitwidth, f"r{r}") # r_regs diff --git a/tests/correctness/exp/any-base-1.expect b/tests/correctness/exp/any-base-1.expect index 61bc1487c6..7f1d9e0fde 100644 --- a/tests/correctness/exp/any-base-1.expect +++ b/tests/correctness/exp/any-base-1.expect @@ -1,5 +1,5 @@ { - "cycles": 212, + "cycles": 216, "memories": { "b": [ "4.5" diff --git a/tests/correctness/exp/any-base-2.expect b/tests/correctness/exp/any-base-2.expect index 684b83ce03..0ca5bf67a5 100644 --- a/tests/correctness/exp/any-base-2.expect +++ b/tests/correctness/exp/any-base-2.expect @@ -1,5 +1,5 @@ { - "cycles": 207, + "cycles": 211, "memories": { "b": [ "7.5" diff --git a/tests/correctness/exp/any-base-3.expect b/tests/correctness/exp/any-base-3.expect index 30fcd7e6a4..71f36e7fe6 100644 --- a/tests/correctness/exp/any-base-3.expect +++ b/tests/correctness/exp/any-base-3.expect @@ -1,5 +1,5 @@ { - "cycles": 303, + "cycles": 307, "memories": { "b": [ "0.75" diff --git a/tests/correctness/exp/degree-4-signed.expect b/tests/correctness/exp/degree-4-signed.expect index 8b3e2d6a23..b09674cdcc 100644 --- a/tests/correctness/exp/degree-4-signed.expect +++ b/tests/correctness/exp/degree-4-signed.expect @@ -1,5 +1,5 @@ { - "cycles": 55, + "cycles": 56, "memories": { "ret": [ "2.7182769775390625" diff --git a/tests/correctness/exp/degree-4-unsigned.expect b/tests/correctness/exp/degree-4-unsigned.expect index 19c96047e9..2bffa80517 100644 --- a/tests/correctness/exp/degree-4-unsigned.expect +++ b/tests/correctness/exp/degree-4-unsigned.expect @@ -1,5 +1,5 @@ { - "cycles": 48, + "cycles": 49, "memories": { "ret": [ "2.7182769775390625" diff --git a/tests/correctness/exp/degree-8-signed.expect b/tests/correctness/exp/degree-8-signed.expect index 846a5d869e..5caebdbb6a 100644 --- a/tests/correctness/exp/degree-8-signed.expect +++ b/tests/correctness/exp/degree-8-signed.expect @@ -1,5 +1,5 @@ { - "cycles": 133, + "cycles": 134, "memories": { "ret": [ "0.0001068115234375" diff --git a/tests/correctness/exp/degree-8-unsigned.expect b/tests/correctness/exp/degree-8-unsigned.expect index ce68145f7e..0dbc85dd24 100644 --- a/tests/correctness/exp/degree-8-unsigned.expect +++ b/tests/correctness/exp/degree-8-unsigned.expect @@ -1,5 +1,5 @@ { - "cycles": 74, + "cycles": 75, "memories": { "ret": [ "9181.710357666015625" diff --git a/tests/correctness/exp/neg-base.expect b/tests/correctness/exp/neg-base.expect index 9d05a7ffa1..9208893975 100644 --- a/tests/correctness/exp/neg-base.expect +++ b/tests/correctness/exp/neg-base.expect @@ -1,5 +1,5 @@ { - "cycles": 220, + "cycles": 224, "memories": { "b": [ "-2.600006103515625" diff --git a/tests/correctness/ntt-pipeline/ntt-16-reduced-4.expect b/tests/correctness/ntt-pipeline/ntt-16-reduced-4.expect index 2174700c0a..d8543b98d3 100644 --- a/tests/correctness/ntt-pipeline/ntt-16-reduced-4.expect +++ b/tests/correctness/ntt-pipeline/ntt-16-reduced-4.expect @@ -1,5 +1,5 @@ { - "cycles": 647, + "cycles": 695, "memories": { "a": [ 7371, diff --git a/tests/correctness/ntt-pipeline/ntt-16.expect b/tests/correctness/ntt-pipeline/ntt-16.expect index 098e88d012..f1c644447e 100644 --- a/tests/correctness/ntt-pipeline/ntt-16.expect +++ b/tests/correctness/ntt-pipeline/ntt-16.expect @@ -1,5 +1,5 @@ { - "cycles": 200, + "cycles": 248, "memories": { "a": [ 7371, diff --git a/tests/correctness/ntt-pipeline/ntt-8.expect b/tests/correctness/ntt-pipeline/ntt-8.expect index 64c920f515..cd9101f29c 100644 --- a/tests/correctness/ntt-pipeline/ntt-8.expect +++ b/tests/correctness/ntt-pipeline/ntt-8.expect @@ -1,5 +1,5 @@ { - "cycles": 142, + "cycles": 166, "memories": { "a": [ 5390, diff --git a/tests/frontend/exp/degree-2-unsigned.expect b/tests/frontend/exp/degree-2-unsigned.expect index f8c466918a..cee7ca2c19 100644 --- a/tests/frontend/exp/degree-2-unsigned.expect +++ b/tests/frontend/exp/degree-2-unsigned.expect @@ -1,6 +1,6 @@ import "primitives/core.futil"; import "primitives/binary_operators.futil"; -import "primitives/memories/comb.futil"; +import "primitives/memories/seq.futil"; component exp(x: 32) -> (out: 32) { cells { exponent_value = std_reg(32); @@ -165,15 +165,16 @@ component fp_pow(base: 32, integer_exp: 32) -> (out: 32) { component main() -> () { cells { t = std_reg(32); - @external x = comb_mem_d1(32, 1, 1); - @external ret = comb_mem_d1(32, 1, 1); + @external x = seq_mem_d1(32, 1, 1); + @external ret = seq_mem_d1(32, 1, 1); e = exp(); } wires { group init { x.addr0 = 1'd0; - t.in = x.read_data; - t.write_en = 1'd1; + x.content_en = 1'd1; + t.in = x.done ? x.read_data; + t.write_en = x.done ? 1'd1; init[done] = t.done; } group write_to_memory { @@ -181,6 +182,7 @@ component main() -> () { ret.write_en = 1'd1; ret.write_data = e.out; write_to_memory[done] = ret.done; + ret.content_en = 1'd1; } } control { diff --git a/tests/frontend/exp/degree-4-signed.expect b/tests/frontend/exp/degree-4-signed.expect index 619f76d207..1d6722b04d 100644 --- a/tests/frontend/exp/degree-4-signed.expect +++ b/tests/frontend/exp/degree-4-signed.expect @@ -1,6 +1,6 @@ import "primitives/core.futil"; import "primitives/binary_operators.futil"; -import "primitives/memories/comb.futil"; +import "primitives/memories/seq.futil"; component exp(x: 16) -> (out: 16) { cells { exponent_value = std_reg(16); @@ -258,15 +258,16 @@ component fp_pow(base: 16, integer_exp: 16) -> (out: 16) { component main() -> () { cells { t = std_reg(16); - @external x = comb_mem_d1(16, 1, 1); - @external ret = comb_mem_d1(16, 1, 1); + @external x = seq_mem_d1(16, 1, 1); + @external ret = seq_mem_d1(16, 1, 1); e = exp(); } wires { group init { x.addr0 = 1'd0; - t.in = x.read_data; - t.write_en = 1'd1; + x.content_en = 1'd1; + t.in = x.done ? x.read_data; + t.write_en = x.done ? 1'd1; init[done] = t.done; } group write_to_memory { @@ -274,6 +275,7 @@ component main() -> () { ret.write_en = 1'd1; ret.write_data = e.out; write_to_memory[done] = ret.done; + ret.content_en = 1'd1; } } control { diff --git a/tests/frontend/exp/degree-4-unsigned.expect b/tests/frontend/exp/degree-4-unsigned.expect index 4ce6a5d1e7..6922e988a7 100644 --- a/tests/frontend/exp/degree-4-unsigned.expect +++ b/tests/frontend/exp/degree-4-unsigned.expect @@ -1,6 +1,6 @@ import "primitives/core.futil"; import "primitives/binary_operators.futil"; -import "primitives/memories/comb.futil"; +import "primitives/memories/seq.futil"; component exp(x: 16) -> (out: 16) { cells { exponent_value = std_reg(16); @@ -229,15 +229,16 @@ component fp_pow(base: 16, integer_exp: 16) -> (out: 16) { component main() -> () { cells { t = std_reg(16); - @external x = comb_mem_d1(16, 1, 1); - @external ret = comb_mem_d1(16, 1, 1); + @external x = seq_mem_d1(16, 1, 1); + @external ret = seq_mem_d1(16, 1, 1); e = exp(); } wires { group init { x.addr0 = 1'd0; - t.in = x.read_data; - t.write_en = 1'd1; + x.content_en = 1'd1; + t.in = x.done ? x.read_data; + t.write_en = x.done ? 1'd1; init[done] = t.done; } group write_to_memory { @@ -245,6 +246,7 @@ component main() -> () { ret.write_en = 1'd1; ret.write_data = e.out; write_to_memory[done] = ret.done; + ret.content_en = 1'd1; } } control { diff --git a/tests/frontend/ntt-pipeline/ntt-4-reduced-2.expect b/tests/frontend/ntt-pipeline/ntt-4-reduced-2.expect index 815607be46..0ee6b23a4e 100644 --- a/tests/frontend/ntt-pipeline/ntt-4-reduced-2.expect +++ b/tests/frontend/ntt-pipeline/ntt-4-reduced-2.expect @@ -7,12 +7,12 @@ // | 3 | a[1] - a[3] * phis[1] | a[2] - a[3] * phis[3] | // +---+-----------------------+-----------------------+ import "primitives/core.futil"; -import "primitives/memories/comb.futil"; +import "primitives/memories/seq.futil"; import "primitives/binary_operators.futil"; component main() -> () { cells { - @external a = comb_mem_d1(32, 4, 3); - @external phis = comb_mem_d1(32, 4, 3); + @external a = seq_mem_d1(32, 4, 3); + @external phis = seq_mem_d1(32, 4, 3); r0 = std_reg(32); A0 = std_reg(32); phi0 = std_reg(32); @@ -41,50 +41,58 @@ component main() -> () { wires { group preamble_0_reg { a.addr0 = 3'd0; - r0.write_en = 1'd1; - r0.in = a.read_data; + a.content_en = 1'd1; + r0.write_en = a.done ? 1'd1; + r0.in = a.done ? a.read_data; preamble_0_reg[done] = r0.done; } group preamble_0_phi { phis.addr0 = 3'd0; - phi0.write_en = 1'd1; - phi0.in = phis.read_data; + phis.content_en = 1'd1; + phi0.write_en = phis.done ? 1'd1; + phi0.in = phis.done ? phis.read_data; preamble_0_phi[done] = phi0.done; } group preamble_1_reg { a.addr0 = 3'd1; - r1.write_en = 1'd1; - r1.in = a.read_data; + a.content_en = 1'd1; + r1.write_en = a.done ? 1'd1; + r1.in = a.done ? a.read_data; preamble_1_reg[done] = r1.done; } group preamble_1_phi { phis.addr0 = 3'd1; - phi1.write_en = 1'd1; - phi1.in = phis.read_data; + phis.content_en = 1'd1; + phi1.write_en = phis.done ? 1'd1; + phi1.in = phis.done ? phis.read_data; preamble_1_phi[done] = phi1.done; } group preamble_2_reg { a.addr0 = 3'd2; - r2.write_en = 1'd1; - r2.in = a.read_data; + a.content_en = 1'd1; + r2.write_en = a.done ? 1'd1; + r2.in = a.done ? a.read_data; preamble_2_reg[done] = r2.done; } group preamble_2_phi { phis.addr0 = 3'd2; - phi2.write_en = 1'd1; - phi2.in = phis.read_data; + phis.content_en = 1'd1; + phi2.write_en = phis.done ? 1'd1; + phi2.in = phis.done ? phis.read_data; preamble_2_phi[done] = phi2.done; } group preamble_3_reg { a.addr0 = 3'd3; - r3.write_en = 1'd1; - r3.in = a.read_data; + a.content_en = 1'd1; + r3.write_en = a.done ? 1'd1; + r3.in = a.done ? a.read_data; preamble_3_reg[done] = r3.done; } group preamble_3_phi { phis.addr0 = 3'd3; - phi3.write_en = 1'd1; - phi3.in = phis.read_data; + phis.content_en = 1'd1; + phi3.write_en = phis.done ? 1'd1; + phi3.in = phis.done ? phis.read_data; preamble_3_phi[done] = phi3.done; } group precursor_0 { @@ -216,24 +224,28 @@ component main() -> () { a.write_en = 1'd1; a.write_data = A0.out; epilogue_0[done] = a.done; + a.content_en = 1'd1; } group epilogue_1 { a.addr0 = 3'd1; a.write_en = 1'd1; a.write_data = A1.out; epilogue_1[done] = a.done; + a.content_en = 1'd1; } group epilogue_2 { a.addr0 = 3'd2; a.write_en = 1'd1; a.write_data = A2.out; epilogue_2[done] = a.done; + a.content_en = 1'd1; } group epilogue_3 { a.addr0 = 3'd3; a.write_en = 1'd1; a.write_data = A3.out; epilogue_3[done] = a.done; + a.content_en = 1'd1; } } control { diff --git a/tests/frontend/ntt-pipeline/ntt-4.expect b/tests/frontend/ntt-pipeline/ntt-4.expect index e711716bd4..a8f6cc1957 100644 --- a/tests/frontend/ntt-pipeline/ntt-4.expect +++ b/tests/frontend/ntt-pipeline/ntt-4.expect @@ -7,12 +7,12 @@ // | 3 | a[1] - a[3] * phis[1] | a[2] - a[3] * phis[3] | // +---+-----------------------+-----------------------+ import "primitives/core.futil"; -import "primitives/memories/comb.futil"; +import "primitives/memories/seq.futil"; import "primitives/binary_operators.futil"; component main() -> () { cells { - @external a = comb_mem_d1(32, 4, 3); - @external phis = comb_mem_d1(32, 4, 3); + @external a = seq_mem_d1(32, 4, 3); + @external phis = seq_mem_d1(32, 4, 3); r0 = std_reg(32); A0 = std_reg(32); phi0 = std_reg(32); @@ -41,50 +41,58 @@ component main() -> () { wires { group preamble_0_reg { a.addr0 = 3'd0; - r0.write_en = 1'd1; - r0.in = a.read_data; + a.content_en = 1'd1; + r0.write_en = a.done ? 1'd1; + r0.in = a.done ? a.read_data; preamble_0_reg[done] = r0.done; } group preamble_0_phi { phis.addr0 = 3'd0; - phi0.write_en = 1'd1; - phi0.in = phis.read_data; + phis.content_en = 1'd1; + phi0.write_en = phis.done ? 1'd1; + phi0.in = phis.done ? phis.read_data; preamble_0_phi[done] = phi0.done; } group preamble_1_reg { a.addr0 = 3'd1; - r1.write_en = 1'd1; - r1.in = a.read_data; + a.content_en = 1'd1; + r1.write_en = a.done ? 1'd1; + r1.in = a.done ? a.read_data; preamble_1_reg[done] = r1.done; } group preamble_1_phi { phis.addr0 = 3'd1; - phi1.write_en = 1'd1; - phi1.in = phis.read_data; + phis.content_en = 1'd1; + phi1.write_en = phis.done ? 1'd1; + phi1.in = phis.done ? phis.read_data; preamble_1_phi[done] = phi1.done; } group preamble_2_reg { a.addr0 = 3'd2; - r2.write_en = 1'd1; - r2.in = a.read_data; + a.content_en = 1'd1; + r2.write_en = a.done ? 1'd1; + r2.in = a.done ? a.read_data; preamble_2_reg[done] = r2.done; } group preamble_2_phi { phis.addr0 = 3'd2; - phi2.write_en = 1'd1; - phi2.in = phis.read_data; + phis.content_en = 1'd1; + phi2.write_en = phis.done ? 1'd1; + phi2.in = phis.done ? phis.read_data; preamble_2_phi[done] = phi2.done; } group preamble_3_reg { a.addr0 = 3'd3; - r3.write_en = 1'd1; - r3.in = a.read_data; + a.content_en = 1'd1; + r3.write_en = a.done ? 1'd1; + r3.in = a.done ? a.read_data; preamble_3_reg[done] = r3.done; } group preamble_3_phi { phis.addr0 = 3'd3; - phi3.write_en = 1'd1; - phi3.in = phis.read_data; + phis.content_en = 1'd1; + phi3.write_en = phis.done ? 1'd1; + phi3.in = phis.done ? phis.read_data; preamble_3_phi[done] = phi3.done; } group precursor_0 { @@ -216,24 +224,28 @@ component main() -> () { a.write_en = 1'd1; a.write_data = A0.out; epilogue_0[done] = a.done; + a.content_en = 1'd1; } group epilogue_1 { a.addr0 = 3'd1; a.write_en = 1'd1; a.write_data = A1.out; epilogue_1[done] = a.done; + a.content_en = 1'd1; } group epilogue_2 { a.addr0 = 3'd2; a.write_en = 1'd1; a.write_data = A2.out; epilogue_2[done] = a.done; + a.content_en = 1'd1; } group epilogue_3 { a.addr0 = 3'd3; a.write_en = 1'd1; a.write_data = A3.out; epilogue_3[done] = a.done; + a.content_en = 1'd1; } } control { From 885b3e484b8d2a001c1fe7633e3439e4f4d1761a Mon Sep 17 00:00:00 2001 From: Griffin Berlstein Date: Fri, 14 Jun 2024 12:15:24 -0400 Subject: [PATCH 8/9] [Cider 2] Multi-comp bug and more tests (#2148) * enable the invoke comp test suite * ensure done ports are being set low appropriately * add in the ref cells and fully structural * add TCAM and reconfigure MRXL stuff * add the "complex" tests --- .../cider2-tests/complex/unsigned-div.expect | 8 + .../complex/unsigned-dot-product.expect | 63 +++++++ interp/cider2-tests/runt.toml | 154 +++++++++--------- .../src/flatten/structures/environment/env.rs | 26 ++- 4 files changed, 174 insertions(+), 77 deletions(-) create mode 100644 interp/cider2-tests/complex/unsigned-div.expect create mode 100644 interp/cider2-tests/complex/unsigned-dot-product.expect diff --git a/interp/cider2-tests/complex/unsigned-div.expect b/interp/cider2-tests/complex/unsigned-div.expect new file mode 100644 index 0000000000..6ed76fa238 --- /dev/null +++ b/interp/cider2-tests/complex/unsigned-div.expect @@ -0,0 +1,8 @@ +{ + "reg_q": [ + 11 + ], + "reg_r": [ + 1 + ] +} diff --git a/interp/cider2-tests/complex/unsigned-dot-product.expect b/interp/cider2-tests/complex/unsigned-dot-product.expect new file mode 100644 index 0000000000..8149168761 --- /dev/null +++ b/interp/cider2-tests/complex/unsigned-dot-product.expect @@ -0,0 +1,63 @@ +{ + "counter": [ + 4 + ], + "mem0": [ + [ + 12, + 0, + 0, + 0 + ], + [ + 10, + 0, + 0, + 0 + ], + [ + 20, + 0, + 0, + 0 + ], + [ + 34, + 0, + 0, + 0 + ] + ], + "mem1": [ + [ + 16, + 0, + 0, + 0 + ], + [ + 32, + 0, + 0, + 0 + ], + [ + 8, + 0, + 0, + 0 + ], + [ + 24, + 0, + 0, + 0 + ] + ], + "r_2": [ + 1376 + ], + "t": [ + 1488 + ] +} diff --git a/interp/cider2-tests/runt.toml b/interp/cider2-tests/runt.toml index e884ad1c4e..a61f56d3b6 100644 --- a/interp/cider2-tests/runt.toml +++ b/interp/cider2-tests/runt.toml @@ -27,14 +27,15 @@ timeout = 10 # """ # timeout = 10 -# [[tests]] -# name = "complex" -# paths = ["tests/complex/*.futil"] +[[tests]] +name = "complex" +paths = ["../tests/complex/*.futil"] -# cmd = """ -# ../target/debug/cider {} | jq . --sort-keys -# """ -# timeout = 10 +cmd = """ + ../../target/debug/cider {} -l ../../ flat --dump-registers | ../../target/debug/cider-data-converter --to json | jq --sort-keys +""" +timeout = 10 +expect_dir = "complex" [[tests]] name = "primitives" @@ -72,25 +73,26 @@ fud2 {} --from calyx --to dat --through cider -s sim.data={}.data -s calyx.args= timeout = 10 expect_dir = "invoke" -# [[tests]] -# name = "invoke comp" -# paths = ["../tests/control/invoke/*.futil"] -# cmd = """ -# fud2 {} --from calyx --to dat --through interp-flat -s calyx.flags=" -p compile-invoke" -s sim.data={}.data | jq --sort-keys -# """ +[[tests]] +name = "invoke comp" +paths = ["../tests/control/invoke/*.futil"] +cmd = """ +fud2 {} --from calyx --to dat --through cider -s calyx.flags=" -p compile-invoke" -s sim.data={}.data -s calyx.args="--log off" | jq --sort-keys +""" +expect_dir = "invoke" -# [[tests]] -# name = "fully structural" -# paths = [ -# "tests/control/*.futil", -# # Disabled iteration tests due to bug -# # "tests/control/iteration/*.futil" -# ] -# cmd = """ -# ../target/debug/calyx {} -d pre-opt -d post-opt -p simplify-with-control -l ../ | ../target/debug/cider | jq .memories --sort-keys -# """ -# expect_dir = "tests/lowered/" -# # timeout = 10 +[[tests]] +name = "fully structural" +paths = [ + "../tests/control/*.futil", + # Disabled iteration tests due to bug + "../tests/control/iteration/*.futil", +] +cmd = """ +../../target/debug/calyx {} -d pre-opt -d post-opt -p simplify-with-control -l ../../ --log off | ../../target/debug/cider -l ../../ flat --dump-registers | ../../target/debug/cider-data-converter --to json | jq --sort-keys +""" +expect_dir = "control" +# timeout = 10 [[tests]] name = "fully structural static" @@ -112,49 +114,54 @@ fud2 --from calyx --to dat \ {} | jq --sort-keys """ -# [[tests]] -# name = "correctness ref cells" -# paths = ["../tests/correctness/ref-cells/*.futil"] -# cmd = """ -# fud exec --from calyx --to jq \ -# --through interpreter-out \ -# -s calyx.flags "-p compile-invoke" \ -# -s verilog.data {}.data \ -# -s interpreter.flags " --raw" \ -# -s jq.expr ".main" \ -# -s jq.flags "--sort-keys " \ -# {} -q -# """ +[[tests]] +name = "correctness ref cells" +paths = ["../../tests/correctness/ref-cells/*.futil"] +cmd = """ +fud2 --from calyx --to dat \ + --through cider \ + -s sim.data={}.data \ + -s calyx.args="--log off" \ + {} | jq --sort-keys +""" -# [[tests]] -# name = "numeric types correctness and parsing" -# paths = [ -# "../tests/correctness/numeric-types/parsing/*.futil", -# "../tests/correctness/numeric-types/bitnum/*.futil", -# "../tests/correctness/numeric-types/fixed-point/*.futil", -# ] -# cmd = """ -# fud exec --from calyx --to jq \ -# --through interpreter-out \ -# -s interpreter.flags "--raw " \ -# -s verilog.data {}.data \ -# -s jq.expr ".main" \ -# -s jq.flags "--sort-keys " \ -# {} -q -# """ +[[tests]] +name = "correctness ref cells compiled" +paths = ["../../tests/correctness/ref-cells/*.futil"] +cmd = """ +fud2 --from calyx --to dat \ + --through cider \ + -s sim.data={}.data \ + -s calyx.args="--log off" \ + -s calyx.flags=" -p compile-invoke" \ + {} | jq --sort-keys +""" -# [[tests]] -# name = "[frontend] tcam testing" -# paths = ["../tests/correctness/tcam/*.futil"] -# cmd = """ -# fud exec --from calyx --to jq \ -# --through interpreter-out \ -# -s interpreter.flags "--raw " \ -# -s verilog.data {}.data \ -# -s jq.expr ".main" \ -# -s jq.flags "--sort-keys " \ -# {} -q -# """ +[[tests]] +name = "numeric types correctness and parsing" +paths = [ + "../../tests/correctness/numeric-types/parsing/*.futil", + "../../tests/correctness/numeric-types/bitnum/*.futil", + "../../tests/correctness/numeric-types/fixed-point/*.futil", +] +cmd = """ +fud2 --from calyx --to dat \ + --through cider \ + -s sim.data={}.data \ + -s calyx.args="--log off" \ + {} | jq --sort-keys +""" + +[[tests]] +name = "[frontend] tcam testing" +paths = ["../../tests/correctness/tcam/*.futil"] +cmd = """ +fud2 --from calyx --to dat \ + --through cider \ + -s calyx.args="--log off" \ + -s sim.data={}.data \ + {} | jq --sort-keys +""" # [[tests]] # name = "[frontend] systolic array correctness" @@ -183,16 +190,17 @@ fud2 --from calyx --to dat \ # expect_dir = "tests/ntt-results/" +# The MRXL tests cannot run because their data is in a different format than the +# others and there is no tool to convert it # [[tests]] # name = "[frontend] mrxl correctness" -# paths = ["../frontends/mrxl/test/*.mrxl"] +# paths = ["../../frontends/mrxl/test/*.mrxl"] # cmd = """ -# fud e -q {} --from mrxl --to jq \ -# --through interpreter-out \ -# -s interpreter.flags "--raw " \ -# -s verilog.data {}.data \ -# -s jq.flags "--sort-keys " \ -# -s jq.expr ".main" +# fud2 {} --from mrxl --to dat \ +# --through cider \ +# -s sim.data={}.data \ +# -s calyx.args="--log off" \ +# | jq --sort-keys # """ # [[tests]] diff --git a/interp/src/flatten/structures/environment/env.rs b/interp/src/flatten/structures/environment/env.rs index b620a6a750..226eb6565e 100644 --- a/interp/src/flatten/structures/environment/env.rs +++ b/interp/src/flatten/structures/environment/env.rs @@ -28,6 +28,8 @@ use crate::{ serialization::data_dump::{DataDump, Dimensions}, values::Value, }; +use ahash::HashSet; +use ahash::HashSetExt; use itertools::Itertools; use std::{fmt::Debug, iter::once}; @@ -895,7 +897,8 @@ impl<'a> Simulator<'a> { // buffers. Can pick anything from zero to the number of nodes in the // program counter as the size let mut leaf_nodes = vec![]; - let mut set_done = vec![]; + let mut set_done_high = vec![]; + let mut set_done_low: HashSet = HashSet::new(); let mut new_nodes = vec![]; let (mut vecs, mut par_map, mut with_map) = self.env.pc.take_fields(); @@ -905,8 +908,15 @@ impl<'a> Simulator<'a> { vecs.retain_mut(|node| { let comp_go = self.env.get_comp_go(node.comp); let comp_done = self.env.get_comp_done(node.comp); + + // if the done is not high & defined, we need to set it to low + if !self.env.ports[comp_done].as_bool().unwrap_or_default() { + set_done_low.insert(comp_done); + } + if !self.env.ports[comp_go].as_bool().unwrap_or_default() || self.env.ports[comp_done].as_bool().unwrap_or_default() { - // if the go port is low or the done port is high, we skip the node without doing anything + // if the go port is low or the done port is high, we skip the + // node without doing anything return true; } @@ -1073,7 +1083,10 @@ impl<'a> Simulator<'a> { // either we are not a par node, or we are the last par node (!matches!(&self.env.ctx.primary[node.control_node_idx], ControlNode::Par(_)) || !par_map.contains_key(node)) { - set_done.push(self.env.get_comp_done(node.comp)); + let done_port = self.env.get_comp_done(node.comp); + set_done_high.push(done_port); + // make sure we don't set this port low + set_done_low.remove(&done_port); let comp_ledger = self.env.cells[node.comp].unwrap_comp(); *node = node.new_retain_comp(self.env.ctx.primary[comp_ledger.comp_id].control.unwrap()); true @@ -1090,10 +1103,14 @@ impl<'a> Simulator<'a> { self.undef_all_ports(); self.set_root_go_high(); - for port in set_done { + for port in set_done_high { self.env.ports[port] = PortValue::new_implicit(Value::bit_high()); } + for port in set_done_low { + self.env.ports[port] = PortValue::new_implicit(Value::bit_low()); + } + for node in &leaf_nodes { match &self.env.ctx.primary[node.control_node_idx] { ControlNode::Enable(e) => { @@ -1154,6 +1171,7 @@ impl<'a> Simulator<'a> { /// Evaluate the entire program pub fn run_program(&mut self) -> InterpreterResult<()> { while !self.is_done() { + // self._print_env(); self.step()? } Ok(()) From 02d64d5a41564df4e78e7a3e16b17d20ce8a27c8 Mon Sep 17 00:00:00 2001 From: Nathaniel Navarro Date: Sat, 15 Jun 2024 18:09:10 -0400 Subject: [PATCH 9/9] Introduce ability to pass in subtypes as ref cells in `invoke` statements (#2085) * WIP: attempt at implementing subtyping. TBD if this works * typo in structure * change error for wellformedness subtyping runt test * compile_invoke now compiles, tbd if actually works * some clippy fixes * wip compile invoke changes * added debugging stuff to invoke, still a WIP * WIP: Need to do clean up but also this might allow subtyping???? Hope so * add comments to well-formed * maybe works? * seems to be working! * update runt tests * make clippy happy * formatting * update comments * more runt tests * replace hashed map with linkedhashmap for determinism * fix bug where iterating over too many ports in comp_ports in compile_invoke * more runt tests for subtyping * derive partialeq and eq for InLineAttributes * extract `get_concrete_port` as helper function * compile_invoke formatting * WIP: extract require_subtype. TODO: allow it to take in StaticInvoke as well * factor out subtyping check * elaborate on some comments in well_formed * improve comments * add some info about subtyping to the language reference --- calyx-frontend/src/attribute.rs | 2 +- calyx-frontend/src/attributes.rs | 14 ++ calyx-ir/src/rewriter.rs | 4 +- calyx-ir/src/structure.rs | 13 +- calyx-opt/src/passes/compile_invoke.rs | 73 ++++++--- calyx-opt/src/passes/component_iniliner.rs | 6 +- calyx-opt/src/passes/discover_external.rs | 4 +- calyx-opt/src/passes/well_formed.rs | 154 ++++++++++-------- docs/lang/ref.md | 65 +++++++- tests/passes/subtyping.expect | 93 +++++++++++ tests/passes/subtyping.futil | 95 +++++++++++ .../well-formed/ref-type-mismatch.expect | 2 +- 12 files changed, 427 insertions(+), 98 deletions(-) create mode 100644 tests/passes/subtyping.expect create mode 100644 tests/passes/subtyping.futil diff --git a/calyx-frontend/src/attribute.rs b/calyx-frontend/src/attribute.rs index 79e5c76042..e50ece67d5 100644 --- a/calyx-frontend/src/attribute.rs +++ b/calyx-frontend/src/attribute.rs @@ -195,7 +195,7 @@ impl FromStr for Attribute { } } -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] /// Inline storage for boolean attributes. pub(super) struct InlineAttributes { /// Boolean attributes stored in a 16-bit number. diff --git a/calyx-frontend/src/attributes.rs b/calyx-frontend/src/attributes.rs index 16fed2626d..230011b468 100644 --- a/calyx-frontend/src/attributes.rs +++ b/calyx-frontend/src/attributes.rs @@ -169,6 +169,20 @@ impl Attributes { } } +impl PartialEq for Attributes { + fn eq(&self, other: &Self) -> bool { + self.inl == other.inl + && self.hinfo.attrs.len() == other.hinfo.attrs.len() + && self + .hinfo + .attrs + .iter() + .all(|(k, v)| other.hinfo.attrs.get(k) == Some(v)) + } +} + +impl Eq for Attributes {} + #[cfg(feature = "serialize")] impl serde::Serialize for HeapAttrInfo { fn serialize(&self, ser: S) -> Result diff --git a/calyx-ir/src/rewriter.rs b/calyx-ir/src/rewriter.rs index d3815d9265..624923de10 100644 --- a/calyx-ir/src/rewriter.rs +++ b/calyx-ir/src/rewriter.rs @@ -1,3 +1,5 @@ +use linked_hash_map::LinkedHashMap; + use crate::control::StaticInvoke; use crate::{self as ir, RRC}; use std::borrow::BorrowMut; @@ -10,7 +12,7 @@ pub type RewriteMap = HashMap>; /// Map to rewrite port uses. Maps the canonical name of an old port (generated using /// [ir::Port::canonical]) to the new [ir::Port] instance. -pub type PortRewriteMap = HashMap>; +pub type PortRewriteMap = LinkedHashMap>; #[derive(Default)] /// A structure to track rewrite maps for ports. Stores both cell rewrites and direct port diff --git a/calyx-ir/src/structure.rs b/calyx-ir/src/structure.rs index 7633eb0f87..42d10eae29 100644 --- a/calyx-ir/src/structure.rs +++ b/calyx-ir/src/structure.rs @@ -126,6 +126,15 @@ impl Port { { self.get_attributes().has(attr) } + + /// Returns true if the widths, name, direction, and attributes of 2 ports match. + /// This is different than `==` equivalence, which only looks at parent and name. + pub fn type_equivalent(&self, other: &Port) -> bool { + self.width == other.width + && self.direction == other.direction + && self.name == other.name + && self.attributes == other.attributes + } } impl GetAttributes for Port { @@ -215,7 +224,7 @@ pub enum CellType { } impl CellType { - /// Return the name associated with this CellType is present + /// Return the name associated with this CellType if present pub fn get_name(&self) -> Option { match self { CellType::Primitive { name, .. } | CellType::Component { name } => { @@ -244,7 +253,7 @@ impl CellType { } /// Represents an instantiated cell. -#[derive(Debug)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(serde::Serialize))] pub struct Cell { /// Name of this cell. diff --git a/calyx-opt/src/passes/compile_invoke.rs b/calyx-opt/src/passes/compile_invoke.rs index 32251e350f..5b1d14f05f 100644 --- a/calyx-opt/src/passes/compile_invoke.rs +++ b/calyx-opt/src/passes/compile_invoke.rs @@ -6,6 +6,7 @@ use calyx_ir::{self as ir, Attributes, LibrarySignatures}; use calyx_utils::{CalyxResult, Error}; use ir::{Assignment, RRC, WRC}; use itertools::Itertools; +use linked_hash_map::LinkedHashMap; use std::collections::HashMap; use std::rc::Rc; @@ -54,13 +55,13 @@ fn build_assignments( /// Map for storing added ports for each ref cell /// level of Hashmap represents: /// HashMap<-component name-, Hashmap<(-ref cell name-,-port name-), port>>; -struct RefPortMap(HashMap>>); +struct RefPortMap(HashMap>>); impl RefPortMap { fn insert( &mut self, comp_name: ir::Id, - ports: HashMap>, + ports: LinkedHashMap>, ) { self.0.insert(comp_name, ports); } @@ -68,7 +69,7 @@ impl RefPortMap { fn get( &self, comp_name: &ir::Id, - ) -> Option<&HashMap>> { + ) -> Option<&LinkedHashMap>> { self.0.get(comp_name) } @@ -91,7 +92,7 @@ pub struct CompileInvoke { port_names: RefPortMap, /// Mapping from the ports of cells that were removed to the new port on the /// component signature. - removed: HashMap>, + removed: LinkedHashMap>, /// Ref cells in the component. We hold onto these so that our references don't get invalidated ref_cells: Vec>, } @@ -103,7 +104,7 @@ impl ConstructVisitor for CompileInvoke { { Ok(CompileInvoke { port_names: RefPortMap::default(), - removed: HashMap::new(), + removed: LinkedHashMap::new(), ref_cells: Vec::new(), }) } @@ -131,6 +132,7 @@ impl CompileInvoke { /// /// Since this pass eliminates all ref cells in post order, we expect that /// invoked component already had all of its ref cells removed. + fn ref_cells_to_ports( &mut self, inv_cell: RRC, @@ -138,31 +140,37 @@ impl CompileInvoke { ) -> Vec> { let inv_comp = inv_cell.borrow().type_name().unwrap(); let mut assigns = Vec::new(); - for (ref_cell_name, cell) in ref_cells { + for (ref_cell_name, concrete_cell) in ref_cells { log::debug!( "Removing ref cell `{}` with {} ports", ref_cell_name, - cell.borrow().ports.len() + concrete_cell.borrow().ports.len() ); // Mapping from canonical names of the ports of the ref cell to the - // new port defined on the signature of the component + // new port defined on the signature of the component. This has name of ref cell, not arg cell let Some(comp_ports) = self.port_names.get(&inv_comp) else { unreachable!("component `{}` invoked but not already visited by the pass", inv_comp) }; - // The type of the cell is the same as the ref cell so we can - // iterate over its ports and generate bindings for the ref cell. - for pr in &cell.borrow().ports { - let port = pr.borrow(); - if port.has_attribute(ir::BoolAttr::Clk) - || port.has_attribute(ir::BoolAttr::Reset) + // We expect each canonical port in `comp_ports` to exactly match with a port in + //`concrete_cell` based on well-formedness subtype checks. + for canon in comp_ports.keys() { + //only interested in ports attached to the ref cell + if canon.cell != ref_cell_name { + continue; + } + // The given port of the actual, concrete cell passed in + let concrete_port = + Self::get_concrete_port(concrete_cell.clone(), &canon.port); + + if concrete_port.borrow().has_attribute(ir::BoolAttr::Clk) + || concrete_port.borrow().has_attribute(ir::BoolAttr::Reset) { continue; } - let canon = ir::Canonical::new(ref_cell_name, port.name); - let Some(comp_port) = comp_ports.get(&canon) else { + let Some(comp_port) = comp_ports.get(canon) else { unreachable!("port `{}` not found in the signature of {}. Known ports are: {}", canon, inv_comp, @@ -173,7 +181,7 @@ impl CompileInvoke { let ref_port = inv_cell.borrow().get(comp_port.borrow().name); log::debug!("Port `{}` -> `{}`", canon, ref_port.borrow().name); - let old_port = pr.borrow().canonical(); + let old_port = concrete_port.borrow().canonical(); // If the port has been removed already, get the new port from the component's signature let arg_port = if let Some(sig_pr) = self.removed.get(&old_port) { @@ -184,10 +192,10 @@ impl CompileInvoke { ); Rc::clone(sig_pr) } else { - Rc::clone(pr) + Rc::clone(&concrete_port) }; - match port.direction { + match concrete_port.borrow().direction { ir::Direction::Output => { log::debug!( "constructing: {} = {}", @@ -213,11 +221,34 @@ impl CompileInvoke { _ => { unreachable!("Cell should have inout ports"); } - } + }; } } assigns } + + /// Takes in a concrete cell (aka an in_cell/what is passed in to a ref cell at invocation) + /// and returns the concrete port based on just the port of a canonical id. + fn get_concrete_port( + concrete_cell: RRC, + canonical_port: &ir::Id, + ) -> RRC { + let concrete_cell = concrete_cell.borrow(); + concrete_cell + .ports + .iter() + .find(|&concrete_cell_port| { + concrete_cell_port.borrow().name == canonical_port + }) + .unwrap_or_else(|| { + unreachable!( + "port `{}` not found in the cell `{}`", + canonical_port, + concrete_cell.name() + ) + }) + .clone() + } } impl Visitor for CompileInvoke { @@ -287,6 +318,7 @@ impl Visitor for CompileInvoke { // Assigns representing the ref cell connections invoke_group.borrow_mut().assignments.extend( self.ref_cells_to_ports(Rc::clone(&s.comp), s.ref_cells.drain(..)), + // ), //the clone here is questionable? but lets things type check? Maybe change ref_cells_to_ports to expect a reference? ); // comp.go = 1'd1; @@ -361,7 +393,6 @@ impl Visitor for CompileInvoke { _comps: &[ir::Component], ) -> VisResult { let mut builder = ir::Builder::new(comp, ctx); - let invoke_group = builder.add_static_group("static_invoke", s.latency); invoke_group.borrow_mut().assignments.extend( diff --git a/calyx-opt/src/passes/component_iniliner.rs b/calyx-opt/src/passes/component_iniliner.rs index 43fc3e7f9e..0dc605447f 100644 --- a/calyx-opt/src/passes/component_iniliner.rs +++ b/calyx-opt/src/passes/component_iniliner.rs @@ -7,6 +7,7 @@ use calyx_ir::{self as ir, rewriter, GetAttributes, LibrarySignatures, RRC}; use calyx_utils::Error; use ir::Nothing; use itertools::Itertools; +use linked_hash_map::LinkedHashMap; use std::collections::{HashMap, HashSet}; use std::rc::Rc; @@ -71,7 +72,7 @@ impl ComponentInliner { always_inline, new_fsms, control_map: HashMap::default(), - interface_rewrites: HashMap::default(), + interface_rewrites: LinkedHashMap::default(), inlined_cells: Vec::default(), } } @@ -442,7 +443,8 @@ impl Visitor for ComponentInliner { .collect::>(); // Rewrites for the interface ports of inlined cells. - let mut interface_rewrites: rewriter::PortRewriteMap = HashMap::new(); + let mut interface_rewrites: rewriter::PortRewriteMap = + LinkedHashMap::new(); // Track names of cells that were inlined. let mut inlined_cells = HashSet::new(); let mut builder = ir::Builder::new(comp, sigs); diff --git a/calyx-opt/src/passes/discover_external.rs b/calyx-opt/src/passes/discover_external.rs index 467d2428e4..f28371bca8 100644 --- a/calyx-opt/src/passes/discover_external.rs +++ b/calyx-opt/src/passes/discover_external.rs @@ -4,7 +4,7 @@ use calyx_utils::CalyxResult; use ir::RRC; use itertools::Itertools; use linked_hash_map::LinkedHashMap; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; /// A pass to detect cells that have been inlined into the top-level component /// and turn them into real cells marked with [ir::BoolAttr::External]. @@ -241,7 +241,7 @@ impl Visitor for DiscoverExternal { } // Rewrite the ports mentioned in the component signature and remove them - let mut rewrites: ir::rewriter::PortRewriteMap = HashMap::new(); + let mut rewrites: ir::rewriter::PortRewriteMap = LinkedHashMap::new(); for (pre, ports) in port_map { // let prim = sigs.get_primitive(pre_to_prim[&pre]); let cr = pre_to_cells[&pre].clone(); diff --git a/calyx-opt/src/passes/well_formed.rs b/calyx-opt/src/passes/well_formed.rs index ddc9ba06e5..6b05827f54 100644 --- a/calyx-opt/src/passes/well_formed.rs +++ b/calyx-opt/src/passes/well_formed.rs @@ -1,6 +1,6 @@ use crate::traversal::{Action, ConstructVisitor, Named, VisResult, Visitor}; use calyx_ir::{ - self as ir, CellType, Component, GetAttributes, LibrarySignatures, + self as ir, Cell, CellType, Component, GetAttributes, LibrarySignatures, RESERVED_NAMES, }; use calyx_utils::{CalyxResult, Error, WithPos}; @@ -80,12 +80,71 @@ pub struct WellFormed { used_groups: HashSet, /// Names of combinational groups used in the control. used_comb_groups: HashSet, - /// ref cell types of components used in the control. - ref_cell_types: HashMap>, + /// ref cells of components used in the control. Used for type checking. + ref_cells: HashMap>, /// Stack of currently active combinational groups active_comb: ActiveAssignments, } +enum Invoke<'a> { + StaticInvoke(&'a ir::StaticInvoke), + Invoke(&'a ir::Invoke), +} + +impl Invoke<'_> { + fn get_ref_cells(&self) -> &Vec<(ir::Id, ir::RRC)> { + match self { + Invoke::StaticInvoke(s) => &s.ref_cells, + Invoke::Invoke(s) => &s.ref_cells, + } + } + + fn get_attributes(&self) -> &ir::Attributes { + match self { + Invoke::StaticInvoke(s) => s.get_attributes(), + Invoke::Invoke(s) => s.get_attributes(), + } + } +} + +fn require_subtype( + invoke: Invoke, + self_ref_cells: &HashMap>, + id: &ir::Id, +) -> CalyxResult<()> { + let cell_map = &self_ref_cells[id]; + let mut mentioned_cells = HashSet::new(); + for (outcell, incell) in invoke.get_ref_cells().iter() { + if let Some(oc) = cell_map.get(outcell) { + if !subtype(oc, &incell.borrow()) { + return Err(Error::malformed_control(format!( + "The type passed in `{}` is not a subtype of the expected type `{}`.", + incell.borrow().prototype.surface_name().unwrap(), + oc.prototype.surface_name().unwrap() + )) + .with_pos(invoke.get_attributes())); + } else { + mentioned_cells.insert(outcell); + } + } else { + return Err(Error::malformed_control(format!( + "{} does not have ref cell named {}", + id, outcell + ))); + } + } + for id in cell_map.keys() { + if !mentioned_cells.contains(id) { + return Err(Error::malformed_control(format!( + "unmentioned ref cell: {}", + id + )) + .with_pos(invoke.get_attributes())); + } + } + Ok(()) +} + impl ConstructVisitor for WellFormed { fn from(ctx: &ir::Context) -> CalyxResult where @@ -94,10 +153,10 @@ impl ConstructVisitor for WellFormed { let reserved_names = RESERVED_NAMES.iter().map(|s| ir::Id::from(*s)).collect(); - let mut ref_cell_types = HashMap::new(); + let mut ref_cells = HashMap::new(); for comp in ctx.components.iter() { // Non-main components cannot use @external attribute - let cellmap: LinkedHashMap = comp + let cellmap: LinkedHashMap = comp .cells .iter() .filter_map(|cr| { @@ -108,20 +167,20 @@ impl ConstructVisitor for WellFormed { { Some(Err(Error::malformed_structure("Cell cannot be marked `@external` in non-entrypoint component").with_pos(&cell.attributes))) } else if cell.is_reference() { - Some(Ok((cell.name(), cell.prototype.clone()))) + Some(Ok((cell.name(), cell.clone()))) } else { None } }) .collect::>()?; - ref_cell_types.insert(comp.name, cellmap); + ref_cells.insert(comp.name, cellmap); } let w_f = WellFormed { reserved_names, used_groups: HashSet::new(), used_comb_groups: HashSet::new(), - ref_cell_types, + ref_cells, active_comb: ActiveAssignments::default(), }; @@ -191,16 +250,25 @@ where Ok(()) } -fn same_type(proto_out: &CellType, proto_in: &CellType) -> CalyxResult<()> { - if proto_out != proto_in { - Err(Error::malformed_control(format!( - "Unexpected type for ref cell. Expected `{}`, received `{}`", - proto_out.surface_name().unwrap(), - proto_in.surface_name().unwrap(), - ))) - } else { - Ok(()) +/// Returns true if `cell_in` is a subtype of `cell_out`. +/// Currenly this only checks for [`type_equivalence`](#method.calyx_ir::structure::Port::type_equivalent) +/// between ports. It does not fully examine the cells +/// for subtype compatability for things like nested ref cells. +// XXX(nate): Cells don't contain information about their own `ref` cells so we'd need to extract it from `ir:Component` I think? +fn subtype(cell_out: &Cell, cell_in: &Cell) -> bool { + for port in cell_out.ports() { + match cell_in.find(port.borrow().name) { + Some(port_in) => { + if !port.borrow().type_equivalent(&port_in.borrow()) { + return false; + } + } + None => { + return false; + } + } } + true } impl Visitor for WellFormed { @@ -560,32 +628,8 @@ impl Visitor for WellFormed { let cell = s.comp.borrow(); if let CellType::Component { name: id } = &cell.prototype { - let cellmap = &self.ref_cell_types[id]; - let mut mentioned_cells = HashSet::new(); - for (outcell, incell) in s.ref_cells.iter() { - if let Some(t) = cellmap.get(outcell) { - let proto = incell.borrow().prototype.clone(); - same_type(t, &proto) - .map_err(|err| err.with_pos(&s.attributes))?; - mentioned_cells.insert(outcell); - } else { - return Err(Error::malformed_control(format!( - "{} does not have ref cell named {}", - id, outcell - ))); - } - } - for id in cellmap.keys() { - if mentioned_cells.get(id).is_none() { - return Err(Error::malformed_control(format!( - "unmentioned ref cell: {}", - id - )) - .with_pos(&s.attributes)); - } - } + require_subtype(Invoke::Invoke(s), &self.ref_cells, id)?; } - Ok(Action::Continue) } @@ -600,32 +644,8 @@ impl Visitor for WellFormed { let cell = s.comp.borrow(); if let CellType::Component { name: id } = &cell.prototype { - let cellmap = &self.ref_cell_types[id]; - let mut mentioned_cells = HashSet::new(); - for (outcell, incell) in s.ref_cells.iter() { - if let Some(t) = cellmap.get(outcell) { - let proto = incell.borrow().prototype.clone(); - same_type(t, &proto) - .map_err(|err| err.with_pos(&s.attributes))?; - mentioned_cells.insert(outcell); - } else { - return Err(Error::malformed_control(format!( - "{} does not have ref cell named {}", - id, outcell - ))); - } - } - for id in cellmap.keys() { - if mentioned_cells.get(id).is_none() { - return Err(Error::malformed_control(format!( - "unmentioned ref cell: {}", - id - )) - .with_pos(&s.attributes)); - } - } + require_subtype(Invoke::StaticInvoke(s), &self.ref_cells, id)?; } - Ok(Action::Continue) } diff --git a/docs/lang/ref.md b/docs/lang/ref.md index 89dac0784b..965ad6d780 100644 --- a/docs/lang/ref.md +++ b/docs/lang/ref.md @@ -461,7 +461,7 @@ component update_memory() -> () { } ``` -When invoking such a component, the calling component must provide a binding for each defined cell: +When invoking[invoke] such a component, the calling component must provide a binding for each defined cell: ``` component main() -> () { cells { @@ -479,8 +479,70 @@ component main() -> () { } ``` As the example shows, each invocation can take different bindings for each `ref` cell. +In the first invocation, we pass in the concrete cell `m1` while in the second we pass +in `m2`. See [the tutorial][ref-tut] for longer example on how to use this feature. +### Subtyping for `ref` cells + +When providing bindings for [`ref` cells][ref], one must provide a concrete cell that is a +*subtype* of the `ref` cell. A cell `a` is a subtype of cell `b` if the [component][components] +defining `a` has *at least* the same ports as the component defining `b.` + +Consider the following component definitions: +``` +component b(in_1 : 1) -> (out_1 : 1) { + // cells, wires, and control blocks +} + +component a(in_1 : 1, in_2 : 1) -> (out_1 : 1, out_2 : 1){ + // cells, wires, and control blocks +} +``` + +Because the component definition of `a` has the ports `in_1` and `out_1`, a concrete cell of `a` can be bound to a `ref` cell of component `b`: +``` +//Expects a `ref` cell of component `b` +component c() -> () { + cells{ + ref b1 = b(); + } + wires{...} + control{...} +} + + +component main() -> () { + cells { + c_cell = c(); + b_cell = b(); + a_cell = a(); //recall `a` is a subtype of `b` + } + wires { ... } + control { + seq { + // Pass `b_cell` by reference. Both are `b1` and `b_cell` are defined by the component `b` + invoke c[b1=b_cell]()(); + // Pass `a_cell` by reference. The `ref` cell and concrete cell are defined by different components, + // but this is allowed because `a` is a subtype of `b`. + invoke c[b1=a_cell]()(); + } + } +} +``` + +Ports are considered to be equal with respect to subtyping if they have the same +name, width, direction, and attributes. + +> **Note:** The notion of subtyping described above, that only checks for port equivalence between components, is incomplete. +> A complete, correct definition of subtyping would require that for `a` to be a subtype +> of `b`, for every `ref` cell expected in `a`, component `b` must expect a `ref` +> cell that is a subtype of the associated `ref` cell in `a` (note that the relationship between +these nested `ref` cells is opposite the relationship of `a` and `b`). +> +> Because nested `ref` cells are not currently allowed in Calyx, this is not a problem in practice. + + [attributes]: ./attributes.md [components]: #calyx-components [cells]: #cells @@ -490,6 +552,7 @@ See [the tutorial][ref-tut] for longer example on how to use this feature. [continuous]: #continuous-assignments [control]: #the-control-operators [ref]: #ref-cells +[invoke]: #invoke [godoneattr]: ./attributes.md#go-done-clk-and-reset [clkreset]: ./attributes.md#go-done-clk-and-reset [ref-tut]: ./memories-by-reference.md diff --git a/tests/passes/subtyping.expect b/tests/passes/subtyping.expect new file mode 100644 index 0000000000..f8d74d3956 --- /dev/null +++ b/tests/passes/subtyping.expect @@ -0,0 +1,93 @@ +import "primitives/core.futil"; +component small_comp(in_1: 32, @go go: 1, @clk clk: 1, @reset reset: 1) -> (out_1: 64, @done done: 1) { + cells { + add = std_add(32); + pad = std_pad(32, 64); + my_reg = std_reg(64); + } + wires { + group double { + my_reg.write_en = 1'd1; + add.right = in_1; + add.left = in_1; + pad.in = add.out; + my_reg.in = pad.out; + double[done] = my_reg.done; + } + out_1 = my_reg.out; + } + control { + double; + } +} +component big_comp(in_1: 32, in_2: 1, @go go: 1, @clk clk: 1, @reset reset: 1) -> (out_1: 64, out_2: 2, @done done: 1) { + cells { + add = std_add(32); + pad = std_pad(32, 64); + my_reg = std_reg(64); + add2 = std_add(2); + pad2 = std_pad(1, 2); + reg2 = std_reg(2); + } + wires { + group double { + my_reg.write_en = 1'd1; + add.right = in_1; + add.left = in_1; + pad.in = add.out; + my_reg.in = pad.out; + double[done] = my_reg.done; + } + group incr { + reg2.write_en = 1'd1; + add2.right = 2'd1; + pad2.in = in_2; + add2.left = pad2.out; + reg2.in = add2.out; + incr[done] = reg2.done; + } + out_2 = reg2.out; + out_1 = my_reg.out; + } + control { + seq { + incr; + double; + } + } +} +component ref_comp(@go go: 1, @clk clk: 1, @reset reset: 1, small_ref_cell_out_1: 64, small_ref_cell_done: 1) -> (out_ref: 64, @done done: 1, small_ref_cell_in_1: 32, small_ref_cell_go: 1) { + cells { + } + wires { + group invoke0 { + small_ref_cell_go = 1'd1; + invoke0[done] = small_ref_cell_done; + small_ref_cell_in_1 = 32'd10; + out_ref = small_ref_cell_out_1; + } + } + control { + invoke0; + } +} +component main(@go go: 1, @clk clk: 1, @reset reset: 1) -> (out_main: 64, @done done: 1) { + cells { + my_ref_cell = ref_comp(); + big_cell = big_comp(); + } + wires { + group invoke0 { + big_cell.in_1 = my_ref_cell.small_ref_cell_in_1; + my_ref_cell.small_ref_cell_out_1 = big_cell.out_1; + big_cell.go = my_ref_cell.small_ref_cell_go; + my_ref_cell.small_ref_cell_done = big_cell.done; + my_ref_cell.go = 1'd1; + invoke0[done] = my_ref_cell.done; + out_main = my_ref_cell.out_ref; + } + } + control { + invoke0; + } +} diff --git a/tests/passes/subtyping.futil b/tests/passes/subtyping.futil new file mode 100644 index 0000000000..e55006c0f0 --- /dev/null +++ b/tests/passes/subtyping.futil @@ -0,0 +1,95 @@ +// -p well-formed -p validate -p compile-invoke +import "primitives/core.futil"; + +//Toy program which has a small_comp and a big_comp. +//big_comp is a subtype of small_comp w.r.t port names, and bit widths. +component small_comp(in_1 : 32) -> (out_1: 64) { + cells { + add = std_add(32); + pad = std_pad(32,64); + my_reg = std_reg(64); + } + + wires{ + out_1 = my_reg.out; + group double{ + add.left = in_1; + add.right = in_1; + pad.in = add.out; + my_reg.in = pad.out; + my_reg.write_en = 1'b1; + double[done] = my_reg.done; + } + } + + control{ + double; + } +} + + +component big_comp(in_1 : 32, in_2: 1) -> (out_1: 64, out_2: 2) { + cells { + add = std_add(32); + pad = std_pad(32,64); + my_reg = std_reg(64); + + add2 = std_add(2); + pad2 = std_pad(1,2); + reg2 = std_reg(2); + } + + wires{ + out_1 = my_reg.out; + out_2 = reg2.out; + group double{ + add.left = in_1; + add.right = in_1; + pad.in = add.out; + my_reg.in = pad.out; + my_reg.write_en = 1'b1; + double[done] = my_reg.done; + } + + group incr{ + pad2.in = in_2; + add2.left = pad2.out; + add2.right = 2'b1; + reg2.in = add2.out; + reg2.write_en = 1'b1; + incr[done] = reg2.done; + } + } + + control{ + incr; + double; + } +} + +component ref_comp() -> (out_ref:64) { + cells{ + ref small_ref_cell = small_comp(); // (in_1) -> (out_1) + } + + wires{ + } + + control{ + invoke small_ref_cell(in_1 = 32'd10)(out_1 = out_ref); + } +} + +component main() -> (out_main:64){ + cells{ + my_ref_cell = ref_comp(); + big_cell = big_comp(); + } + + wires{ + } + + control{ + invoke my_ref_cell[small_ref_cell=big_cell]()(out_ref = out_main); //tries to pass a big_comp to a small_comp + } +} diff --git a/tests/passes/well-formed/ref-type-mismatch.expect b/tests/passes/well-formed/ref-type-mismatch.expect index bc687225af..dd1bc65566 100644 --- a/tests/passes/well-formed/ref-type-mismatch.expect +++ b/tests/passes/well-formed/ref-type-mismatch.expect @@ -1,4 +1,4 @@ ---STDERR--- Error: tests/passes/well-formed/ref-type-mismatch.futil 33 | invoke f[m = k1]()(); - | ^^^^^^^^^^^^^^^^^^^^^ Malformed Control: Unexpected type for ref cell. Expected `std_reg(32)`, received `std_reg(16)` + | ^^^^^^^^^^^^^^^^^^^^^ Malformed Control: The type passed in `std_reg(16)` is not a subtype of the expected type `std_reg(32)`.