Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,9 @@ jobs:

- name: Run RV32I Privilege compliance tests
run: riscof run --config=$SERV/verif/config.ini --suite=riscv-arch-test/riscv-test-suite/rv32i_m/privilege --env=riscv-arch-test/riscv-test-suite/env --no-browser

- name: Run RV32E compliance tests
run: riscof run --config=$SERV/verif/config_rv32e.ini --suite=riscv-arch-test/riscv-test-suite/rv32e_m/E --env=riscv-arch-test/riscv-test-suite/env --no-browser

- name: Run RV32EC compliance tests
run: riscof run --config=$SERV/verif/config_rv32e.ini --suite=riscv-arch-test/riscv-test-suite/rv32e_m/C --env=riscv-arch-test/riscv-test-suite/env --no-browser
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ jobs:
- run: pip3 install fusesoc
- run: fusesoc library add $REPO $GITHUB_WORKSPACE/$REPO
- run: fusesoc run --target=lint $VLNV --W=${{ matrix.width }}
- run: fusesoc run --target=lint $VLNV --W=${{ matrix.width }} --WITH_RV32E=1
- run: fusesoc run --target=lint servant --width=${{ matrix.width }}
- run: fusesoc run --target=lint serving
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
sw/*.elf
sw/*.bin
build/
riscof_work/
servant_test_rv32e/
fusesoc.conf
fusesoc_libraries/
riscv-arch-test/
verif/fusesoc.conf
verif/fusesoc_libraries/
verif/riscof_work/
verif/bin/sail-riscv/
**/__pycache__/
*.new
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,35 @@ If the [toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain) is insta
SERV is verified using RISC-V compliance tests for the base ISA (RV32I) and the implemented extensions (M, C, Zicsr). The instructions on running Compliance tests using RISCOF framework are given in [verif](/verif/) directory.


## RV32E support

SERV supports the RV32E base ISA variant via the `WITH_RV32E` parameter (default: 0). RV32E uses 16 general-purpose registers instead of 32, halving the register-file RAM depth.

**Area impact by target:**
- **ASIC / DFF-based targets**: ~36% fewer cells in generic synthesis — a meaningful saving.
- **FPGA targets**: No measurable LUT or BRAM reduction. Both RV32I and RV32E register files fit inside a single block RAM (e.g., one `SB_RAM40_4K` on iCE40 or one BSRAM on Gowin), so the BRAM count is unchanged. If fitting a small FPGA is the goal, removing optional logic (MDU, CSR) or reducing firmware size will have more impact than RV32E alone.

To simulate with RV32E enabled:

fusesoc run --target=verilator_tb servant --firmware=$SERV/sw/hello_uart_rv32e.hex --with_rv32e=1

RV32E can be combined with the Compressed (C) extension (`--compressed=1`) for ~25% smaller code size:

fusesoc run --target=verilator_tb servant --firmware=$SERV/sw/hello_uart_rv32ec.hex --with_rv32e=1 --compressed=1

To lint with RV32E enabled:

fusesoc run --target=lint serv --WITH_RV32E=1

In RV32E mode, any access to registers x16–x31 is trapped as an illegal instruction (mcause=2, mtval=faulting instruction). This applies to both 32-bit and compressed instructions.

RV32E simulation can also be run with Icarus Verilog:

fusesoc run --target=sim servant --firmware=$SERV/sw/trap_test_rv32e.hex --with_rv32e=1
fusesoc run --target=sim servant --firmware=$SERV/sw/trap_test_rv32ec.hex --with_rv32e=1 --compressed=1

RV32E compliance is verified using the RISCOF framework against the SAIL reference model. See the [verif/](/verif/) directory for details.

## Other targets

The above targets are run on the servant SoC, but there are some targets defined for the CPU itself. Verilator can be run in lint mode to check for design problems by running
Expand Down
18 changes: 10 additions & 8 deletions bench/servant_sim.v
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module servant_sim
parameter memsize = 8192;
parameter width = 1;
parameter with_csr = 1;
parameter with_rv32e = 0;
parameter compressed = 0;
parameter align = compressed;

Expand All @@ -21,14 +22,15 @@ module servant_sim
end

servant
#(.memfile (memfile),
.memsize (memsize),
.width (width),
.debug (1'b1),
.sim (1),
.with_csr (with_csr),
.compress (compressed[0:0]),
.align (align[0:0]))
#(.memfile (memfile),
.memsize (memsize),
.width (width),
.debug (1'b1),
.sim (1),
.with_csr (with_csr),
.with_rv32e (with_rv32e),
.compress (compressed[0:0]),
.align (align[0:0]))
dut(wb_clk, wb_rst, q);

assign pc_adr = dut.wb_mem_adr;
Expand Down
12 changes: 8 additions & 4 deletions bench/servant_tb.v
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module servant_tb;
parameter memsize = 8192;
parameter width = 1;
parameter with_csr = 1;
parameter with_rv32e = 0;
parameter compressed = 0;

localparam baud_rate =
(width == 4) ? 57600*3 :
Expand All @@ -23,10 +25,12 @@ module servant_tb;
uart_decoder #(baud_rate) uart_decoder (q);

servant_sim
#(.memfile (memfile),
.memsize (memsize),
.width (width),
.with_csr (with_csr))
#(.memfile (memfile),
.memsize (memsize),
.width (width),
.with_csr (with_csr),
.with_rv32e (with_rv32e),
.compressed (compressed))
dut
(.wb_clk (wb_clk),
.wb_rst (wb_rst),
Expand Down
9 changes: 7 additions & 2 deletions data/verilator_waiver.vlt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
`verilator_config
// Bits [1:0] in i_wb_rdt are not used at all
lint_off -rule UNUSED -file "*/serv_top.v" -lines 185
lint_off -rule UNUSEDSIGNAL -file "*/serv_top.v" -lines 186

//Some bits in the instruction word are not used in serv_decode but it's easier
//to just send in the whole word than picking out bits
lint_off -rule UNUSED -file "*/serv_decode.v" -lines 14

//Some variables are only used when we connect an Extension with serv_decode
lint_off -rule UNUSED -file "*/serv_top.v" -lines 76
lint_off -rule UNUSEDSIGNAL -file "*/serv_top.v" -lines 77

//Some bufreg signals are not used in 1-bit mode
lint_off -rule UNUSED -file "*/serv_bufreg.v" -lines 16
Expand All @@ -17,3 +17,8 @@ lint_off -rule UNUSED -file "*/serv_bufreg.v" -lines 25-27
lint_off -rule UNUSED -file "*/serv_immdec.v" -lines 17
lint_off -rule UNUSED -file "*/serv_bufreg.v" -lines 15
lint_off -rule UNUSED -file "*/serv_ctrl.v" -lines 23

//In RV32E mode, bit [4] of i_rs1_raddr (line 52) and i_rs2_raddr (line 55) is
//unused because only 16 GPRs exist; the 5-bit port is kept for interface compatibility
lint_off -rule UNUSEDSIGNAL -file "*/serv_rf_if.v" -lines 52
lint_off -rule UNUSEDSIGNAL -file "*/serv_rf_if.v" -lines 55
10 changes: 3 additions & 7 deletions rtl/serv_compdec.v
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ module serv_compdec
input wire [31:0] i_instr,
input wire i_ack,
output wire [31:0] o_instr,
output reg o_iscomp);
output reg o_iscomp,
output wire o_illegal);

localparam OPCODE_LOAD = 7'h03;
localparam OPCODE_OP_IMM = 7'h13;
Expand All @@ -28,6 +29,7 @@ module serv_compdec
reg illegal_instr;

assign o_instr = illegal_instr ? i_instr : comp_instr;
assign o_illegal = illegal_instr & (i_instr[1:0] != 2'b11);

always @(posedge i_clk) begin
if(i_ack)
Expand Down Expand Up @@ -72,9 +74,6 @@ module serv_compdec

// C1

// Register address checks for RV32E are performed in the regular instruction decoder.
// If this check fails, an illegal instruction exception is triggered and the controller
// writes the actual faulting instruction to mtval.
2'b01: begin
case (i_instr[15:13])
3'b000: begin
Expand Down Expand Up @@ -171,9 +170,6 @@ module serv_compdec

// C2

// Register address checks for RV32E are performed in the regular instruction decoder.
// If this check fails, an illegal instruction exception is triggered and the controller
// writes the actual faulting instruction to mtval.
2'b10: begin
case (i_instr[15:14])
2'b00: begin
Expand Down
14 changes: 10 additions & 4 deletions rtl/serv_csr.v
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module serv_csr
input wire [1:0] i_csr_source,
input wire i_mret,
input wire i_csr_d_sel,
input wire i_illegal,
//Data
input wire [B:0] i_rf_csr_out,
output wire [B:0] o_csr_in,
Expand Down Expand Up @@ -147,10 +148,15 @@ module serv_csr
ctrl => 0000 (jump=0)
*/
if (i_mcause_en & i_en & i_cnt0to3 | (i_trap & i_cnt_done)) begin
mcause3_0[3] <= (i_e_op & !i_ebreak) | (!i_trap & csr_in[B]);
mcause3_0[2] <= o_new_irq | i_mem_op | (!i_trap & ((W == 1) ? mcause3_0[3] : csr_in[(W == 1) ? 0 : 2]));
mcause3_0[1] <= o_new_irq | i_e_op | (i_mem_op & i_mem_cmd) | (!i_trap & ((W == 1) ? mcause3_0[2] : csr_in[(W == 1) ? 0 : 1]));
mcause3_0[0] <= o_new_irq | i_e_op | (!i_trap & ((W == 1) ? mcause3_0[1] : csr_in[0]));
if (i_illegal & !o_new_irq) begin
// Illegal instruction exception = 2
mcause3_0 <= 4'b0010;
end else begin
mcause3_0[3] <= (i_e_op & !i_ebreak) | (!i_trap & csr_in[B]);
mcause3_0[2] <= o_new_irq | i_mem_op | (!i_trap & ((W == 1) ? mcause3_0[3] : csr_in[(W == 1) ? 0 : 2]));
mcause3_0[1] <= o_new_irq | i_e_op | (i_mem_op & i_mem_cmd) | (!i_trap & ((W == 1) ? mcause3_0[2] : csr_in[(W == 1) ? 0 : 1]));
mcause3_0[0] <= o_new_irq | i_e_op | (!i_trap & ((W == 1) ? mcause3_0[1] : csr_in[0]));
end
end
if (i_mcause_en & i_cnt_done | i_trap)
mcause31 <= i_trap ? o_new_irq : csr_in[B];
Expand Down
70 changes: 39 additions & 31 deletions rtl/serv_rf_if.v
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
`default_nettype none
module serv_rf_if
#(parameter WITH_CSR = 1,
parameter WITH_RV32E = 0,
parameter W = 1,
parameter B = W-1
)
(//RF Interface
input wire i_cnt_en,
output wire [4+WITH_CSR:0] o_wreg0,
output wire [4+WITH_CSR:0] o_wreg1,
output wire [4+WITH_CSR-WITH_RV32E:0] o_wreg0,
output wire [4+WITH_CSR-WITH_RV32E:0] o_wreg1,
output wire o_wen0,
output wire o_wen1,
output wire [B:0] o_wdata0,
output wire [B:0] o_wdata1,
output wire [4+WITH_CSR:0] o_rreg0,
output wire [4+WITH_CSR:0] o_rreg1,
output wire [4+WITH_CSR-WITH_RV32E:0] o_rreg0,
output wire [4+WITH_CSR-WITH_RV32E:0] o_rreg1,
input wire [B:0] i_rdata0,
input wire [B:0] i_rdata1,

Expand All @@ -28,6 +29,8 @@ module serv_rf_if
input wire i_mret,
input wire [B:0] i_mepc,
input wire i_mtval_pc,
input wire i_illegal,
input wire [B:0] i_mtval_instr,
input wire [B:0] i_bufreg_q,
input wire [B:0] i_bad_pc,
output wire [B:0] o_csr_pc,
Expand Down Expand Up @@ -69,25 +72,22 @@ module serv_rf_if
{W{i_rd_mem_en}} & i_mem_rd |
i_ctrl_rd;

wire [B:0] mtval = i_mtval_pc ? i_bad_pc : i_bufreg_q;
wire [B:0] mtval = i_illegal ? i_mtval_instr :
i_mtval_pc ? i_bad_pc : i_bufreg_q;

assign o_wdata0 = i_trap ? mtval : rd;
assign o_wdata1 = i_trap ? i_mepc : i_csr;

/* Port 0 handles writes to mtval during traps and rd otherwise
* Port 1 handles writes to mepc during traps and csr accesses otherwise
*
* GPR registers are mapped to address 0-31 (bits 0xxxxx).
* Following that are four CSR registers
* mscratch 100000
* mtvec 100001
* mepc 100010
* mtval 100011
* RV32I: GPRs at 0-31, CSRs follow at 32-35
* mscratch 100000 mtvec 100001 mepc 100010 mtval 100011
*
* RV32E: GPRs at 0-15, CSRs follow at 16-19
* mscratch 10000 mtvec 10001 mepc 10010 mtval 10011
*/

assign o_wreg0 = i_trap ? {6'b100011} : {1'b0,i_rd_waddr};
assign o_wreg1 = i_trap ? {6'b100010} : {4'b1000,i_csr_addr};

assign o_wen0 = i_cnt_en & (i_trap | rd_wen);
assign o_wen1 = i_cnt_en & (i_trap | i_csr_en);

Expand All @@ -98,8 +98,6 @@ module serv_rf_if
//0 : RS1
//1 : RS2 / CSR

assign o_rreg0 = {1'b0, i_rs1_raddr};

/*
The address of the second read port (o_rreg1) can get assigned from four
different sources
Expand All @@ -109,20 +107,30 @@ module serv_rf_if
trap : MTVEC
mret : MEPC

Address 0-31 in the RF are assigned to the GPRs. After that follows the four
CSRs on addresses 32-35

32 MSCRATCH
33 MTVEC
34 MEPC
35 MTVAL

The expression below is an optimized version of this logic
*/
wire sel_rs2 = !(i_trap | i_mret | i_csr_en);
assign o_rreg1 = {~sel_rs2,
i_rs2_raddr[4:2] & {3{sel_rs2}},
{1'b0,i_trap} | {i_mret,1'b0} | ({2{i_csr_en}} & i_csr_addr) | ({2{sel_rs2}} & i_rs2_raddr[1:0])};

// lower 2 bits of rreg1 are common to RV32I and RV32E
wire [1:0] rreg1_lo = {1'b0,i_trap} | {i_mret,1'b0} | ({2{i_csr_en}} & i_csr_addr) | ({2{sel_rs2}} & i_rs2_raddr[1:0]);

if (!(|WITH_RV32E)) begin : gen_rv32i_addr
// RV32I: 6-bit register addresses, CSR base = 32
assign o_wreg0 = i_trap ? {6'b100011} : {1'b0, i_rd_waddr};
assign o_wreg1 = i_trap ? {6'b100010} : {4'b1000, i_csr_addr};
assign o_rreg0 = {1'b0, i_rs1_raddr};
assign o_rreg1 = {~sel_rs2, i_rs2_raddr[4:2] & {3{sel_rs2}}, rreg1_lo};
end else begin : gen_rv32e_addr
// RV32E: 5-bit register addresses, CSR base = 16
// Bit[4] of rs1/rs2 raddr is not used in the RF (only x0-x15 exist);
// x16-x31 illegal-register detection happens upstream in serv_top.
// The _unused_ prefix suppresses lint warnings across all Verilator versions.
wire _unused_rv32e_rs_msb = i_rs1_raddr[4] | i_rs2_raddr[4];
assign o_wreg0 = i_trap ? {5'b10011} : {1'b0, i_rd_waddr[3:0]};
assign o_wreg1 = i_trap ? {5'b10010} : {3'b100, i_csr_addr};
assign o_rreg0 = {1'b0, i_rs1_raddr[3:0]};
assign o_rreg1 = {~sel_rs2, i_rs2_raddr[3:2] & {2{sel_rs2}}, rreg1_lo};
end

assign o_rs1 = i_rdata0;
assign o_rs2 = i_rdata1;
Expand All @@ -137,8 +145,8 @@ module serv_rf_if
assign o_wdata0 = rd;
assign o_wdata1 = {W{1'b0}};

assign o_wreg0 = i_rd_waddr;
assign o_wreg1 = 5'd0;
assign o_wreg0 = i_rd_waddr[4-WITH_RV32E:0];
assign o_wreg1 = {(5-WITH_RV32E){1'b0}};

assign o_wen0 = i_cnt_en & rd_wen;
assign o_wen1 = 1'b0;
Expand All @@ -147,8 +155,8 @@ module serv_rf_if
********** Read side ***********
*/

assign o_rreg0 = i_rs1_raddr;
assign o_rreg1 = i_rs2_raddr;
assign o_rreg0 = i_rs1_raddr[4-WITH_RV32E:0];
assign o_rreg1 = i_rs2_raddr[4-WITH_RV32E:0];

assign o_rs1 = i_rdata0;
assign o_rs2 = i_rdata1;
Expand Down
3 changes: 2 additions & 1 deletion rtl/serv_rf_ram.v
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
module serv_rf_ram
#(parameter width=0,
parameter csr_regs=4,
parameter depth=32*(32+csr_regs)/width)
parameter gpr_regs=32,
parameter depth=32*(gpr_regs+csr_regs)/width)
(input wire i_clk,
input wire [$clog2(depth)-1:0] i_waddr,
input wire [width-1:0] i_wdata,
Expand Down
5 changes: 4 additions & 1 deletion rtl/serv_rf_ram_if.v
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ module serv_rf_ram_if
// GPR registers in the RAM.
parameter csr_regs=4,

//Number of GPR registers (32 for RV32I, 16 for RV32E)
parameter gpr_regs=32,

//Internal parameters calculated from above values. Do not change
parameter B=W-1,
parameter raw=$clog2(32+csr_regs), //Register address width
parameter raw=$clog2(gpr_regs+csr_regs), //Register address width
parameter l2w=$clog2(width), //log2 of width
parameter aw=5+raw-l2w) //Address width
(
Expand Down
Loading