This will be first of many things I will submit during my journey of self (re)learning CPU design.
I have ALU implementation for RiscV base instruction. I am not fully sure what I am looking for, but that can include one or more of the following:
- Anything that is a big "No No!"
- Anything I did that is not idiomatic SystemVerilog.
- Anything that most SystemVerilog users would add to their workflow but I didn't.
- Anything related to performance that I am not aware of.
- Anything you would do differently for any reason.
So my ALU is split into basically 4 files.
The rtl/alu.sv
parameter int AluOpWidth = 4;
typedef enum logic [AluOpWidth-1:0] {
Add = AluOpWidth'(0),
Slt = AluOpWidth'(1),
SltU = AluOpWidth'(2),
BitAnd = AluOpWidth'(3),
BitOr = AluOpWidth'(4),
BitXor = AluOpWidth'(5),
Sll = AluOpWidth'(6),
Slr = AluOpWidth'(7),
Sub = AluOpWidth'(8),
Sra = AluOpWidth'(9)
} AluOps;
module alu#(parameter int XLEN = 32) (
input AluOps op,
input logic [XLEN-1: 0]lhs,
input logic [XLEN-1: 0]rhs,
output logic [XLEN-1: 0] out
);
// signed variant of lhs
logic signed [XLEN-1: 0]lhsi;
// signed variant of rhs
logic signed [XLEN-1: 0]rhsi;
// get Signed variant of all inputs
assign rhsi = rhs;
assign lhsi = lhs;
always_comb begin
unique case(op)
Add: out = lhs + rhs;
Slt: out = {{XLEN-1{1'b0}}, (lhsi < rhsi)};
SltU: out = {{XLEN-1{1'b0}}, (lhs < rhs)};
BitAnd: out = lhs & rhs;
BitOr: out = lhs | rhs;
BitXor: out = lhs ^ rhs;
Sll: out = lhs << rhs;
Slr: out = lhs >> rhs;
Sub: out = lhs - rhs;
Sra: out = lhsi >>> rhs;
default: out = {XLEN{1'b0}};
endcase
end
endmodule
The test benchmark tb/alu.tb.sv
module alu_tb;
parameter XLEN = 8;
logic [XLEN-1: 0]lhs, rhs, expected, out;
AluOps op;
alu#(.XLEN(XLEN)) dut(op, lhs, rhs, out);
integer file, ret;
initial begin
$dumpfile("alu.vcd");
$dumpvars(0, alu_tb);
file = $fopen("test_vectors.txt", "r");
if (file == 0) begin
$display("Error: Could not open test_vectors.txt");
$fatal;
end
while (!$feof(file)) begin
ret = $fscanf(file, "%h %h %h %h\n", op, lhs, rhs, expected);
if (ret != 4) begin
$display("Invalid test vector format");
$fatal;
end
#10;
if (expected !== out) begin
$display("Test Failed: op = %h, lhs = %h, rhs = %h, expected = %h, out = %h",
op, lhs, rhs, expected, out);
$fatal;
end else begin
$display("Test passed: op = %h, lhs = %h, rhs = %h, expected = %h",
op, lhs, rhs, out);
end
end
$finish;
end
endmodule
The alu.core file:
CAPI=2:
name: learning::alu
description: General Purpose Parametrizable ALU
filesets:
rtl:
files:
- rtl/alu.sv
file_type: systemVerilogSource
tb:
files:
- tb/alu.tb.sv
file_type: systemVerilogSource
gen_tests:
files: [tb/gen_tests.py : {file_type : user, copyto : gen_tests.py}]
targets:
default: &default
filesets: [rtl]
lint:
<<: *default
default_tool : verilator
filesets_append: [tb]
tools:
verilator :
mode : lint-only
toplevel : alu
sim:
<<: *default
description: Simulate the design
default_tool: icarus
filesets_append: [tb, gen_tests]
toplevel: alu_tb
hooks:
pre_run: [gen_tests]
tools:
icarus:
iverilog_options:
- -g2012
scripts:
gen_tests:
cmd : ["./gen_tests.py"]
And:
./gen_tests.py which basically exhaust the whole search space for 8 bits ALU. This file
does not really matter. It will generate file that looks like this:
0 0 0 0
0 0 1 1
0 0 2 2
...
...
8 d2 4d 85
...
...
9 ff fe ff
9 ff ff ff