From c45573370fc52b58037344c9e5faedb5b9169ec8 Mon Sep 17 00:00:00 2001 From: Andrew Butt Date: Fri, 1 Nov 2024 13:20:46 -0400 Subject: [PATCH] Add bypass_reg and skid_buffer primitives to core (#2324) --- docs/libraries/core.md | 51 +++++++++++++- primitives/core.futil | 26 ++++++- primitives/core.sv | 70 ++++++++++++++++++- runt.toml | 1 + .../memory-with-external-attribute.expect | 70 ++++++++++++++++++- .../skid-buffers/bypass-reg.expect | 26 +++++++ .../correctness/skid-buffers/bypass-reg.futil | 64 +++++++++++++++++ .../skid-buffers/bypass-reg.futil.data | 40 +++++++++++ .../skid-buffers/skid-buffer.expect | 26 +++++++ .../skid-buffers/skid-buffer.futil | 67 ++++++++++++++++++ .../skid-buffers/skid-buffer.futil.data | 40 +++++++++++ tests/import/a.expect | 2 + .../dyn-mem-vec-add-axi-wrapped.expect | 70 ++++++++++++++++++- .../seq-mem-vec-add-axi-wrapped.expect | 70 ++++++++++++++++++- 14 files changed, 615 insertions(+), 8 deletions(-) create mode 100644 tests/correctness/skid-buffers/bypass-reg.expect create mode 100644 tests/correctness/skid-buffers/bypass-reg.futil create mode 100644 tests/correctness/skid-buffers/bypass-reg.futil.data create mode 100644 tests/correctness/skid-buffers/skid-buffer.expect create mode 100644 tests/correctness/skid-buffers/skid-buffer.futil create mode 100644 tests/correctness/skid-buffers/skid-buffer.futil.data diff --git a/docs/libraries/core.md b/docs/libraries/core.md index db0f2ad1c6..c1d1499826 100644 --- a/docs/libraries/core.md +++ b/docs/libraries/core.md @@ -13,7 +13,7 @@ such as registers and basic bitwise operations. --- -## Numerical Operators +## State Elements ### `std_reg` @@ -28,11 +28,56 @@ A `WIDTH`-wide register. **Outputs:** - `out: WIDTH` - The value contained in the register. -- `done: 1` - The register's done signal. Set high for one cycle after writing a - new value. +- `done: 1` - The register's done signal. Set high for one cycle after writing + a new value. + +--- + +### `std_skid_buffer` + +A `WIDTH`-wide non-pipelined skid buffer. Used to ensure data is not lost +during handshakes. + +**Inputs:** + +- `in: WIDTH` - An input value to the skid buffer `WIDTH`-bits. +- `i_valid: 1` - The one bit input valid signal. Indicates that the data + provided on the `in` wire is valid. +- `i_ready: 1` - The one bit input ready signal. Indicates that the follower is + ready to recieve data from the `out` wire. + +**Outputs:** + +- `out: WIDTH` - The value contained in the register. +- `o_valid: 1` - The one bit output valid signal. Indicates that the data + provided on the `out` wire is valid. +- `o_ready: 1` - The one bit output ready signal. Indicates that the skid buffer + is ready to recieve data on the `in` wire. --- +### `std_bypass_reg` + +A `WIDTH`-wide bypass register. + +**Inputs:** + +- `in: WIDTH` - An input value to the bypass register `WIDTH`-bits. +- `write_en: 1` - The one bit write enabled signal. Indicates that the bypass + register should store the value on the `in` wire. + +**Outputs:** + +- `out: WIDTH` - The value of the bypass register. When `write_en` is asserted + the value of `in` is bypassed to `out`. Otherwise `out` is equal to the last + value written to the register. +- `done: 1` - The bypass register's done signal. Set high for one cycle after + `write_en` is asserted. + +--- + +## Numerical Operators + ### `std_const` A constant WIDTH-bit value with value VAL. diff --git a/primitives/core.futil b/primitives/core.futil index 99e5127f87..ef1c5195e8 100644 --- a/primitives/core.futil +++ b/primitives/core.futil @@ -8,7 +8,7 @@ extern "core.sv" { comb primitive std_bit_slice<"share"=1>[IN_WIDTH, START_IDX, END_IDX, OUT_WIDTH](@data in: IN_WIDTH) -> (out: OUT_WIDTH); - /// Logical operators + /// Logical Operators comb primitive std_not<"share"=1>[WIDTH](@data in: WIDTH) -> (out: WIDTH); comb primitive std_and<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH); comb primitive std_or<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH); @@ -24,4 +24,28 @@ extern "core.sv" { comb primitive std_le<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1); comb primitive std_rsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH); comb primitive std_mux<"share"=1>[WIDTH](@data cond: 1, @data tru: WIDTH, @data fal: WIDTH) -> (out: WIDTH); + + // Skid Buffers + primitive std_skid_buffer<"share"=1>[WIDTH]( + @data in: WIDTH, + i_valid : 1, + i_ready : 1, + @clk clk: 1, + @reset reset: 1 + ) -> ( + @stable out: WIDTH, + o_valid : 1, + o_ready : 1 + ); + + // Bypass Register + primitive std_bypass_reg<"share"=1>[WIDTH]( + @data in: WIDTH, + @go write_en: 1, + @clk clk: 1, + @reset reset: 1 + ) -> ( + @stable out: WIDTH, + @done done: 1 + ); } diff --git a/primitives/core.sv b/primitives/core.sv index 227fd06046..d9ddc9d7ab 100644 --- a/primitives/core.sv +++ b/primitives/core.sv @@ -215,7 +215,7 @@ module std_bit_slice #( input wire logic [IN_WIDTH-1:0] in, output logic [OUT_WIDTH-1:0] out ); - assign out = in[END_IDX:START_IDX]; + assign out = in[END_IDX:START_IDX]; `ifdef VERILATOR always_comb begin @@ -230,3 +230,71 @@ module std_bit_slice #( `endif endmodule + +module std_skid_buffer #( + parameter WIDTH = 32 +)( + input wire logic [WIDTH-1:0] in, + input wire logic i_valid, + input wire logic i_ready, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic o_valid, + output logic o_ready +); + logic [WIDTH-1:0] val; + logic bypass_rg; + always @(posedge clk) begin + // Reset + if (reset) begin + // Internal Registers + val <= '0; + bypass_rg <= 1'b1; + end + // Out of reset + else begin + // Bypass state + if (bypass_rg) begin + if (!i_ready && i_valid) begin + val <= in; // Data skid happened, store to buffer + bypass_rg <= 1'b0; // To skid mode + end + end + // Skid state + else begin + if (i_ready) begin + bypass_rg <= 1'b1; // Back to bypass mode + end + end + end + end + + assign o_ready = bypass_rg; + assign out = bypass_rg ? in : val; + assign o_valid = bypass_rg ? i_valid : 1'b1; +endmodule + +module std_bypass_reg #( + parameter WIDTH = 32 +)( + input wire logic [WIDTH-1:0] in, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic done +); + logic [WIDTH-1:0] val; + assign out = write_en ? in : val; + + always_ff @(posedge clk) begin + if (reset) begin + val <= 0; + done <= 0; + end else if (write_en) begin + val <= in; + done <= 1'd1; + end else done <= 1'd0; + end +endmodule diff --git a/runt.toml b/runt.toml index 6c1ac6e1fa..610f281a88 100644 --- a/runt.toml +++ b/runt.toml @@ -273,6 +273,7 @@ name = "correctness static timing" paths = [ "tests/correctness/*.futil", "tests/correctness/ref-cells/*.futil", + "tests/correctness/skid-buffers/*.futil", "tests/correctness/static-interface/*.futil", ] cmd = """ diff --git a/tests/backend/verilog/memory-with-external-attribute.expect b/tests/backend/verilog/memory-with-external-attribute.expect index 1d01427827..fcab3307a1 100644 --- a/tests/backend/verilog/memory-with-external-attribute.expect +++ b/tests/backend/verilog/memory-with-external-attribute.expect @@ -453,7 +453,7 @@ module std_bit_slice #( input wire logic [IN_WIDTH-1:0] in, output logic [OUT_WIDTH-1:0] out ); - assign out = in[END_IDX:START_IDX]; + assign out = in[END_IDX:START_IDX]; `ifdef VERILATOR always_comb begin @@ -469,6 +469,74 @@ module std_bit_slice #( endmodule +module std_skid_buffer #( + parameter WIDTH = 32 +)( + input wire logic [WIDTH-1:0] in, + input wire logic i_valid, + input wire logic i_ready, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic o_valid, + output logic o_ready +); + logic [WIDTH-1:0] val; + logic bypass_rg; + always @(posedge clk) begin + // Reset + if (reset) begin + // Internal Registers + val <= '0; + bypass_rg <= 1'b1; + end + // Out of reset + else begin + // Bypass state + if (bypass_rg) begin + if (!i_ready && i_valid) begin + val <= in; // Data skid happened, store to buffer + bypass_rg <= 1'b0; // To skid mode + end + end + // Skid state + else begin + if (i_ready) begin + bypass_rg <= 1'b1; // Back to bypass mode + end + end + end + end + + assign o_ready = bypass_rg; + assign out = bypass_rg ? in : val; + assign o_valid = bypass_rg ? i_valid : 1'b1; +endmodule + +module std_bypass_reg #( + parameter WIDTH = 32 +)( + input wire logic [WIDTH-1:0] in, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic done +); + logic [WIDTH-1:0] val; + assign out = write_en ? in : val; + + always_ff @(posedge clk) begin + if (reset) begin + val <= 0; + done <= 0; + end else if (write_en) begin + val <= in; + done <= 1'd1; + end else done <= 1'd0; + end +endmodule + module undef #( parameter WIDTH = 32 ) ( diff --git a/tests/correctness/skid-buffers/bypass-reg.expect b/tests/correctness/skid-buffers/bypass-reg.expect new file mode 100644 index 0000000000..452b06b031 --- /dev/null +++ b/tests/correctness/skid-buffers/bypass-reg.expect @@ -0,0 +1,26 @@ +{ + "in": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "out": [ + 1, + 2, + 3, + 4, + 5, + 5, + 5, + 8, + 9, + 10 + ] +} diff --git a/tests/correctness/skid-buffers/bypass-reg.futil b/tests/correctness/skid-buffers/bypass-reg.futil new file mode 100644 index 0000000000..5022dd3fef --- /dev/null +++ b/tests/correctness/skid-buffers/bypass-reg.futil @@ -0,0 +1,64 @@ +import "primitives/core.futil"; +import "primitives/memories/seq.futil"; + +component main() -> () { + cells { + @external in = seq_mem_d1(32, 10, 4); + b = std_bypass_reg(32); + i_reg = std_reg(4); + add0 = std_add(4); + neq0 = std_neq(4); + neq1 = std_neq(4); + and0 = std_and(1); + @external out = seq_mem_d1(32, 10, 4); + } + + wires { + static<1> group init { + i_reg.write_en = 1'b1; + i_reg.in = 4'b0; + } + + static<1> group incr { + i_reg.write_en = 1'b1; + i_reg.in = add0.out; + add0.left = i_reg.out; + add0.right = 4'b1; + } + + static<1> group load { + in.addr0 = i_reg.out; + in.content_en = 1'b1; + } + + static<1> group bypass { + neq0.left = i_reg.out; + neq0.right = 4'd5; + neq1.left = i_reg.out; + neq1.right = 4'd6; + and0.left = neq0.out; + and0.right = neq1.out; + b.write_en = and0.out; + b.in = in.read_data; + } + + static<1> group store { + out.write_en = 1'b1; + out.content_en = 1'b1; + out.addr0 = i_reg.out; + out.write_data = b.out; + } + } + + control { + init; + static repeat 10 { + static seq { + load; + bypass; + store; + incr; + } + } + } +} diff --git a/tests/correctness/skid-buffers/bypass-reg.futil.data b/tests/correctness/skid-buffers/bypass-reg.futil.data new file mode 100644 index 0000000000..01e3629795 --- /dev/null +++ b/tests/correctness/skid-buffers/bypass-reg.futil.data @@ -0,0 +1,40 @@ +{ + "in": { + "data": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "format": { + "numeric_type": "bitnum", + "is_signed": false, + "width": 32 + } + }, + "out": { + "data": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "format": { + "numeric_type": "bitnum", + "is_signed": false, + "width": 32 + } + } +} diff --git a/tests/correctness/skid-buffers/skid-buffer.expect b/tests/correctness/skid-buffers/skid-buffer.expect new file mode 100644 index 0000000000..452b06b031 --- /dev/null +++ b/tests/correctness/skid-buffers/skid-buffer.expect @@ -0,0 +1,26 @@ +{ + "in": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "out": [ + 1, + 2, + 3, + 4, + 5, + 5, + 5, + 8, + 9, + 10 + ] +} diff --git a/tests/correctness/skid-buffers/skid-buffer.futil b/tests/correctness/skid-buffers/skid-buffer.futil new file mode 100644 index 0000000000..c41f430165 --- /dev/null +++ b/tests/correctness/skid-buffers/skid-buffer.futil @@ -0,0 +1,67 @@ +import "primitives/core.futil"; +import "primitives/memories/seq.futil"; + +component main() -> () { + cells { + @external in = seq_mem_d1(32, 10, 4); + b = std_skid_buffer(32); + i_reg = std_reg(4); + add0 = std_add(4); + neq0 = std_neq(4); + neq1 = std_neq(4); + and0 = std_and(1); + @external out = seq_mem_d1(32, 10, 4); + } + + wires { + neq0.left = i_reg.out; + neq0.right = 4'd5; + neq1.left = i_reg.out; + neq1.right = 4'd6; + and0.left = neq0.out; + and0.right = neq1.out; + + b.i_ready = and0.out; + b.i_valid = 1'b1; + + static<1> group init { + i_reg.write_en = 1'b1; + i_reg.in = 4'b0; + } + + static<1> group incr { + i_reg.write_en = 1'b1; + i_reg.in = add0.out; + add0.left = i_reg.out; + add0.right = 4'b1; + } + + static<1> group load { + in.addr0 = i_reg.out; + in.content_en = 1'b1; + } + + static<1> group bypass { + b.in = in.read_data; + } + + static<1> group store { + out.write_en = 1'b1; + out.content_en = 1'b1; + out.addr0 = i_reg.out; + out.write_data = b.out; + } + } + + control { + init; + static repeat 10 { + static seq { + load; + bypass; + store; + incr; + } + } + } +} diff --git a/tests/correctness/skid-buffers/skid-buffer.futil.data b/tests/correctness/skid-buffers/skid-buffer.futil.data new file mode 100644 index 0000000000..01e3629795 --- /dev/null +++ b/tests/correctness/skid-buffers/skid-buffer.futil.data @@ -0,0 +1,40 @@ +{ + "in": { + "data": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ], + "format": { + "numeric_type": "bitnum", + "is_signed": false, + "width": 32 + } + }, + "out": { + "data": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "format": { + "numeric_type": "bitnum", + "is_signed": false, + "width": 32 + } + } +} diff --git a/tests/import/a.expect b/tests/import/a.expect index c348a36e76..5db2002bcb 100644 --- a/tests/import/a.expect +++ b/tests/import/a.expect @@ -28,6 +28,8 @@ extern "/calyx/primitives/core.sv" { comb primitive std_le<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: 1); comb primitive std_rsh<"share"=1>[WIDTH](@data left: WIDTH, @data right: WIDTH) -> (out: WIDTH); comb primitive std_mux<"share"=1>[WIDTH](@data cond: 1, @data tru: WIDTH, @data fal: WIDTH) -> (out: WIDTH); + primitive std_skid_buffer<"share"=1>[WIDTH](@data in: WIDTH, i_valid: 1, i_ready: 1, @clk clk: 1, @reset reset: 1) -> (@stable out: WIDTH, o_valid: 1, o_ready: 1); + primitive std_bypass_reg<"share"=1>[WIDTH](@data in: WIDTH, @go write_en: 1, @clk clk: 1, @reset reset: 1) -> (@stable out: WIDTH, @done done: 1); } primitive undef<"share"=1>[WIDTH]() -> (out: WIDTH) { assign out = 'x; diff --git a/yxi/tests/axi/dynamic/dyn-mem-vec-add-axi-wrapped.expect b/yxi/tests/axi/dynamic/dyn-mem-vec-add-axi-wrapped.expect index a39b1c5179..eaad492606 100644 --- a/yxi/tests/axi/dynamic/dyn-mem-vec-add-axi-wrapped.expect +++ b/yxi/tests/axi/dynamic/dyn-mem-vec-add-axi-wrapped.expect @@ -1180,7 +1180,7 @@ module std_bit_slice #( input wire logic [IN_WIDTH-1:0] in, output logic [OUT_WIDTH-1:0] out ); - assign out = in[END_IDX:START_IDX]; + assign out = in[END_IDX:START_IDX]; `ifdef VERILATOR always_comb begin @@ -1196,6 +1196,74 @@ module std_bit_slice #( endmodule +module std_skid_buffer #( + parameter WIDTH = 32 +)( + input wire logic [WIDTH-1:0] in, + input wire logic i_valid, + input wire logic i_ready, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic o_valid, + output logic o_ready +); + logic [WIDTH-1:0] val; + logic bypass_rg; + always @(posedge clk) begin + // Reset + if (reset) begin + // Internal Registers + val <= '0; + bypass_rg <= 1'b1; + end + // Out of reset + else begin + // Bypass state + if (bypass_rg) begin + if (!i_ready && i_valid) begin + val <= in; // Data skid happened, store to buffer + bypass_rg <= 1'b0; // To skid mode + end + end + // Skid state + else begin + if (i_ready) begin + bypass_rg <= 1'b1; // Back to bypass mode + end + end + end + end + + assign o_ready = bypass_rg; + assign out = bypass_rg ? in : val; + assign o_valid = bypass_rg ? i_valid : 1'b1; +endmodule + +module std_bypass_reg #( + parameter WIDTH = 32 +)( + input wire logic [WIDTH-1:0] in, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic done +); + logic [WIDTH-1:0] val; + assign out = write_en ? in : val; + + always_ff @(posedge clk) begin + if (reset) begin + val <= 0; + done <= 0; + end else if (write_en) begin + val <= in; + done <= 1'd1; + end else done <= 1'd0; + end +endmodule + module undef #( parameter WIDTH = 32 ) ( diff --git a/yxi/tests/axi/read-compute-write/seq-mem-vec-add-axi-wrapped.expect b/yxi/tests/axi/read-compute-write/seq-mem-vec-add-axi-wrapped.expect index d6f77e56a9..bf390e5738 100644 --- a/yxi/tests/axi/read-compute-write/seq-mem-vec-add-axi-wrapped.expect +++ b/yxi/tests/axi/read-compute-write/seq-mem-vec-add-axi-wrapped.expect @@ -1172,7 +1172,7 @@ module std_bit_slice #( input wire logic [IN_WIDTH-1:0] in, output logic [OUT_WIDTH-1:0] out ); - assign out = in[END_IDX:START_IDX]; + assign out = in[END_IDX:START_IDX]; `ifdef VERILATOR always_comb begin @@ -1188,6 +1188,74 @@ module std_bit_slice #( endmodule +module std_skid_buffer #( + parameter WIDTH = 32 +)( + input wire logic [WIDTH-1:0] in, + input wire logic i_valid, + input wire logic i_ready, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic o_valid, + output logic o_ready +); + logic [WIDTH-1:0] val; + logic bypass_rg; + always @(posedge clk) begin + // Reset + if (reset) begin + // Internal Registers + val <= '0; + bypass_rg <= 1'b1; + end + // Out of reset + else begin + // Bypass state + if (bypass_rg) begin + if (!i_ready && i_valid) begin + val <= in; // Data skid happened, store to buffer + bypass_rg <= 1'b0; // To skid mode + end + end + // Skid state + else begin + if (i_ready) begin + bypass_rg <= 1'b1; // Back to bypass mode + end + end + end + end + + assign o_ready = bypass_rg; + assign out = bypass_rg ? in : val; + assign o_valid = bypass_rg ? i_valid : 1'b1; +endmodule + +module std_bypass_reg #( + parameter WIDTH = 32 +)( + input wire logic [WIDTH-1:0] in, + input wire logic write_en, + input wire logic clk, + input wire logic reset, + output logic [WIDTH-1:0] out, + output logic done +); + logic [WIDTH-1:0] val; + assign out = write_en ? in : val; + + always_ff @(posedge clk) begin + if (reset) begin + val <= 0; + done <= 0; + end else if (write_en) begin + val <= in; + done <= 1'd1; + end else done <= 1'd0; + end +endmodule + module undef #( parameter WIDTH = 32 ) (