aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore4
-rw-r--r--rtl/alu.sv72
-rw-r--r--tb/alu/alu_add_sub_tb.sv188
-rw-r--r--tb/alu/alu_logic_tb.sv134
4 files changed, 398 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index ad3001c..fe0d8c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,10 @@
.vscode
.claude/
+# iverilog build artifacts
+*.vvp
+obj_dir/
+
# SymbiYosys output
verif/formal/simple_alu/simple_alu/
verif/formal/simple_alu/simple_alu_bmc/
diff --git a/rtl/alu.sv b/rtl/alu.sv
new file mode 100644
index 0000000..2966135
--- /dev/null
+++ b/rtl/alu.sv
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: CERN-OHL-P-2.0 */
+
+`include "types.svh"
+
+module alu #(
+ parameter int unsigned XLEN = config_pkg::XLEN,
+ parameter int unsigned SLICE_WIDTH = config_pkg::SLICE_WIDTH,
+
+ localparam int unsigned SLICE_ADDR_WIDTH = $clog2(XLEN / SLICE_WIDTH)
+) (
+ input logic clk_i,
+ input logic rst_ni,
+ input fu_op_t alu_op_i,
+
+ input logic [SLICE_ADDR_WIDTH-1:0] count_i,
+
+ input slice_t a_i,
+ input slice_t b_i,
+
+ output slice_t result_o,
+ output logic carry_o
+);
+
+ logic negate_b;
+
+ /* We used Karnaugh maps here. If the encoding for fu_op_t changes, this has to be updated. */
+ assign negate_b = alu_op_i[3] || (!alu_op_i[2] && alu_op_i[1]);
+
+ /* Carry signals. */
+ logic carry, carry_d, carry_q;
+
+ /* For the first slice, carry-in must be driven to zero for addition
+ * and one for subtraction or comparisons. All other slices use the
+ * carry-out from the previous slice */
+ assign carry = (count_i == '0) ? negate_b : carry_q;
+
+ /* Adder width is SLICE_WIDTH + 1 to account for carry-out. */
+ logic [SLICE_WIDTH:0] adder_a, adder_b, adder_result;
+
+ assign adder_a = {1'b0, a_i};
+ assign adder_b = {1'b0, (b_i ^ {SLICE_WIDTH{negate_b}})};
+
+ /* ADD, SUB, SLT, SLTU use the same adder to computer their results*/
+ assign adder_result = adder_a + adder_b + (SLICE_WIDTH + 1)'(carry);
+
+ assign carry_o = adder_result[SLICE_WIDTH];
+ assign carry_d = carry_o;
+
+ /* Combinational arithmetic/logic core. */
+ always_comb begin : alu_core
+ unique casez (alu_op_i[2:0])
+ /* ADD, SUB, SLT, SLTU. */
+ 3'b0??: result_o = adder_result[SLICE_WIDTH-1:0];
+ /* XOR. */
+ 3'b100: result_o = a_i ^ b_i;
+ /* OR. */
+ 3'b110: result_o = a_i | b_i;
+ /* AND. */
+ 3'b111: result_o = a_i & b_i;
+ default: result_o = '0;
+ endcase
+ end : alu_core
+
+ always_ff @(posedge clk_i) begin : carry_ff
+ if (!rst_ni) begin
+ carry_q <= '0;
+ end else begin
+ carry_q <= carry_d;
+ end
+ end : carry_ff
+
+endmodule : alu
diff --git a/tb/alu/alu_add_sub_tb.sv b/tb/alu/alu_add_sub_tb.sv
new file mode 100644
index 0000000..aac976b
--- /dev/null
+++ b/tb/alu/alu_add_sub_tb.sv
@@ -0,0 +1,188 @@
+/* Testbench for ALU ADD and SUB operations.
+ *
+ * Functional requirements verified:
+ * FR-ALU-ADD-010 to FR-ALU-ADD-040
+ * FR-ALU-SUB-010 to FR-ALU-SUB-040
+ * FR-ALU-CMN-010 to FR-ALU-CMN-020
+ */
+
+module alu_add_sub_tb;
+ import montreal_pkg::*;
+
+ logic clk_i;
+ logic rst_ni;
+ fu_op_t alu_op_i;
+ logic [1:0] count_i;
+ slice_t a_i, b_i;
+ slice_t result_o;
+ logic carry_o;
+
+ alu dut (
+ .clk_i (clk_i),
+ .rst_ni (rst_ni),
+ .alu_op_i (alu_op_i),
+ .count_i (count_i),
+ .a_i (a_i),
+ .b_i (b_i),
+ .result_o (result_o),
+ .carry_o (carry_o)
+ );
+
+ /* 10ns clock period. */
+ initial clk_i = 0;
+ always #5 clk_i = ~clk_i;
+
+ int pass_count;
+ int fail_count;
+
+ /* Run one 32-bit ADD or SUB operation across 4 slices.
+ * Inputs are presented on negedge so they are stable at the following posedge,
+ * where carry_q updates. Result is sampled before the posedge. */
+ task automatic run_op(
+ input fu_op_t op,
+ input logic [31:0] a,
+ input logic [31:0] b,
+ output logic [31:0] result,
+ output logic final_carry
+ );
+ for (int i = 0; i < 4; i++) begin
+ @(negedge clk_i);
+ alu_op_i = op;
+ count_i = i[1:0];
+ a_i = a[i*8 +: 8];
+ b_i = b[i*8 +: 8];
+
+ #1; /* Wait for combinational outputs to settle. */
+ result[i*8 +: 8] = result_o;
+ if (i == 3) final_carry = carry_o;
+
+ @(posedge clk_i); /* Clock edge registers carry_d into carry_q for next slice. */
+ end
+ endtask
+
+ /* Check result and print pass/fail. */
+ task automatic check(
+ input string name,
+ input logic [31:0] a,
+ input logic [31:0] b,
+ input logic [31:0] got,
+ input logic [31:0] expected
+ );
+ if (got === expected) begin
+ $display(" PASS %s: %h op %h = %h", name, a, b, got);
+ pass_count++;
+ end else begin
+ $display(" FAIL %s: %h op %h = %h (expected %h)", name, a, b, got, expected);
+ fail_count++;
+ end
+ endtask
+
+ task automatic check_carry(
+ input string name,
+ input logic [31:0] a,
+ input logic [31:0] b,
+ input logic got,
+ input logic expected
+ );
+ if (got === expected) begin
+ $display(" PASS %s carry: %h op %h -> carry_o = %b", name, a, b, got);
+ pass_count++;
+ end else begin
+ $display(" FAIL %s carry: %h op %h -> carry_o = %b (expected %b)",
+ name, a, b, got, expected);
+ fail_count++;
+ end
+ endtask
+
+ task automatic test_add(input logic [31:0] a, input logic [31:0] b);
+ logic [31:0] result;
+ logic final_carry;
+ run_op(ADD, a, b, result, final_carry);
+ check("ADD", a, b, result, a + b);
+ endtask
+
+ task automatic test_sub(input logic [31:0] a, input logic [31:0] b);
+ logic [31:0] result;
+ logic final_carry;
+ run_op(SUB, a, b, result, final_carry);
+ check("SUB", a, b, result, a - b);
+ endtask
+
+ task automatic test_add_carry(
+ input logic [31:0] a, input logic [31:0] b, input logic expected_carry
+ );
+ logic [31:0] result;
+ logic final_carry;
+ run_op(ADD, a, b, result, final_carry);
+ check("ADD", a, b, result, a + b);
+ check_carry("ADD", a, b, final_carry, expected_carry);
+ endtask
+
+ task automatic test_sub_carry(
+ input logic [31:0] a, input logic [31:0] b, input logic expected_carry
+ );
+ logic [31:0] result;
+ logic final_carry;
+ run_op(SUB, a, b, result, final_carry);
+ check("SUB", a, b, result, a - b);
+ check_carry("SUB", a, b, final_carry, expected_carry);
+ endtask
+
+ initial begin
+ pass_count = 0;
+ fail_count = 0;
+
+ /* FR-ALU-CMN-020: reset clears carry_q. */
+ rst_ni = 0;
+ alu_op_i = ADD;
+ count_i = 0;
+ a_i = 0;
+ b_i = 0;
+ @(posedge clk_i);
+ @(posedge clk_i);
+ rst_ni = 1;
+
+ $display("=== ADD tests ===");
+
+ /* FR-ALU-ADD-010/020: basic add, no carry. */
+ test_add(32'h0000_0001, 32'h0000_0001); // 1 + 1 = 2
+
+ /* FR-ALU-ADD-030: carry propagates from slice 0 to slice 1. */
+ test_add(32'h0000_00FF, 32'h0000_0001); // 255 + 1 = 256
+
+ /* FR-ALU-ADD-030: carry propagates across all 4 slices. */
+ test_add(32'hFFFF_FFFF, 32'h0000_0001); // -1 + 1 = 0 (wraps)
+
+ /* FR-ALU-ADD-040: carry-out on final slice (overflow cases). */
+ test_add_carry(32'hFFFF_FFFF, 32'hFFFF_FFFF, 1'b1); // max + max → overflow
+ test_add_carry(32'hFFFF_FFFF, 32'h0000_0001, 1'b1); // -1 + 1 = 0 → overflow
+ test_add_carry(32'h0000_0001, 32'h0000_0001, 1'b0); // no overflow
+
+ /* FR-ALU-CMN-010: multi-byte result assembled correctly. */
+ test_add(32'h1234_5678, 32'h8765_4321); // mixed bytes
+
+ test_add(32'h0000_0000, 32'h0000_0000); // 0 + 0 = 0
+
+ $display("\n=== SUB tests ===");
+
+ /* FR-ALU-SUB-010/020: basic sub, no borrow. */
+ test_sub(32'h0000_0005, 32'h0000_0003); // 5 - 3 = 2
+
+ /* FR-ALU-SUB-030: borrow propagates from slice 0 to slice 1. */
+ test_sub(32'h0000_0100, 32'h0000_0001); // 256 - 1 = 255
+
+ /* FR-ALU-SUB-030: borrow propagates across all 4 slices. */
+ test_sub_carry(32'h0000_0000, 32'h0000_0001, 1'b0); // 0 - 1 wraps, carry_o = 0 (borrow)
+ test_sub_carry(32'h0000_0005, 32'h0000_0003, 1'b1); // 5 - 3, no borrow → carry_o = 1
+
+ /* FR-ALU-CMN-010: multi-byte result assembled correctly. */
+ test_sub(32'hFFFF_FFFF, 32'h0000_0001); // max - 1
+
+ test_sub(32'h0000_0000, 32'h0000_0000); // 0 - 0 = 0
+
+ $display("\n=== Results: %0d passed, %0d failed ===", pass_count, fail_count);
+
+ $finish;
+ end
+
+endmodule : alu_add_sub_tb
diff --git a/tb/alu/alu_logic_tb.sv b/tb/alu/alu_logic_tb.sv
new file mode 100644
index 0000000..7d70bda
--- /dev/null
+++ b/tb/alu/alu_logic_tb.sv
@@ -0,0 +1,134 @@
+/* Testbench for ALU XOR, OR, AND operations.
+ *
+ * Logical operations are purely combinational per slice — no carry propagation.
+ * carry_o is NOT valid for logical operations and MUST NOT be used by the
+ * caller. It is not checked in this testbench.
+ */
+
+module alu_logic_tb;
+ import montreal_pkg::*;
+
+ logic clk_i;
+ logic rst_ni;
+ fu_op_t alu_op_i;
+ logic [1:0] count_i;
+ slice_t a_i, b_i;
+ slice_t result_o;
+ logic carry_o;
+
+ alu dut (
+ .clk_i (clk_i),
+ .rst_ni (rst_ni),
+ .alu_op_i (alu_op_i),
+ .count_i (count_i),
+ .a_i (a_i),
+ .b_i (b_i),
+ .result_o (result_o),
+ .carry_o (carry_o)
+ );
+
+ initial clk_i = 0;
+ always #5 clk_i = ~clk_i;
+
+ int pass_count;
+ int fail_count;
+
+ task automatic run_op(
+ input fu_op_t op,
+ input logic [31:0] a,
+ input logic [31:0] b,
+ output logic [31:0] result,
+ output logic final_carry
+ );
+ for (int i = 0; i < 4; i++) begin
+ @(negedge clk_i);
+ alu_op_i = op;
+ count_i = i[1:0];
+ a_i = a[i*8 +: 8];
+ b_i = b[i*8 +: 8];
+
+ #1;
+ result[i*8 +: 8] = result_o;
+ if (i == 3) final_carry = carry_o;
+
+ @(posedge clk_i);
+ end
+ endtask
+
+ task automatic check(
+ input string name,
+ input logic [31:0] a,
+ input logic [31:0] b,
+ input logic [31:0] got,
+ input logic [31:0] expected
+ );
+ if (got === expected) begin
+ $display(" PASS %s: %h op %h = %h", name, a, b, got);
+ pass_count++;
+ end else begin
+ $display(" FAIL %s: %h op %h = %h (expected %h)", name, a, b, got, expected);
+ fail_count++;
+ end
+ endtask
+
+ task automatic test_xor(input logic [31:0] a, input logic [31:0] b);
+ logic [31:0] result;
+ logic final_carry;
+ run_op(XOR, a, b, result, final_carry);
+ check("XOR", a, b, result, a ^ b);
+ endtask
+
+ task automatic test_or(input logic [31:0] a, input logic [31:0] b);
+ logic [31:0] result;
+ logic final_carry;
+ run_op(OR, a, b, result, final_carry);
+ check("OR", a, b, result, a | b);
+ endtask
+
+ task automatic test_and(input logic [31:0] a, input logic [31:0] b);
+ logic [31:0] result;
+ logic final_carry;
+ run_op(AND, a, b, result, final_carry);
+ check("AND", a, b, result, a & b);
+ endtask
+
+ initial begin
+ pass_count = 0;
+ fail_count = 0;
+
+ rst_ni = 0;
+ alu_op_i = XOR;
+ count_i = 0;
+ a_i = 0;
+ b_i = 0;
+ @(posedge clk_i);
+ @(posedge clk_i);
+ rst_ni = 1;
+
+ $display("=== XOR tests ===");
+ test_xor(32'hFFFF_FFFF, 32'hFFFF_FFFF); // all 1s XOR all 1s = 0
+ test_xor(32'hFFFF_FFFF, 32'h0000_0000); // all 1s XOR 0 = all 1s
+ test_xor(32'hAAAA_AAAA, 32'h5555_5555); // alternating bits
+ test_xor(32'h1234_5678, 32'h8765_4321); // mixed
+ test_xor(32'h0000_0000, 32'h0000_0000); // 0 XOR 0 = 0
+
+ $display("\n=== OR tests ===");
+ test_or(32'hFFFF_FFFF, 32'h0000_0000); // all 1s OR 0 = all 1s
+ test_or(32'h0000_0000, 32'h0000_0000); // 0 OR 0 = 0
+ test_or(32'hAAAA_AAAA, 32'h5555_5555); // alternating → all 1s
+ test_or(32'h1234_5678, 32'h8765_4321); // mixed
+ test_or(32'hFFFF_FFFF, 32'hFFFF_FFFF); // all 1s OR all 1s = all 1s
+
+ $display("\n=== AND tests ===");
+ test_and(32'hFFFF_FFFF, 32'hFFFF_FFFF); // all 1s AND all 1s = all 1s
+ test_and(32'hFFFF_FFFF, 32'h0000_0000); // all 1s AND 0 = 0
+ test_and(32'hAAAA_AAAA, 32'h5555_5555); // alternating → 0
+ test_and(32'h1234_5678, 32'h8765_4321); // mixed
+ test_and(32'h0000_0000, 32'h0000_0000); // 0 AND 0 = 0
+
+ $display("\n=== Results: %0d passed, %0d failed ===", pass_count, fail_count);
+
+ $finish;
+ end
+
+endmodule : alu_logic_tb