Add simulation for RISC-V compliance testing

This adds a Verilator simulation of Ibex for use in RISC-V Compliance
Testing. In addition to ibex itself, the simulation contains a RAM and
a memory-mapped helper module for the test software to interact with the
outside world. The test framework uses this to dump a "test signature",
which is written to a certain part of the memory, and to end the
simulation. (In the future, this could be extended to include printf()
like functionality.)
This commit is contained in:
Philipp Wagner 2019-08-03 00:24:13 +01:00 committed by Philipp Wagner
parent b72f5db6bd
commit 24a9c64bf1
8 changed files with 782 additions and 0 deletions

View file

@ -0,0 +1,67 @@
Ibex simulation for RISC-V Compliance Testing
=============================================
This directory contains a compiled simulation of Ibex to be used as target
in the [RISC-V Compliance Test](https://github.com/riscv/riscv-compliance).
In addition to Ibex itself, it contains a 64 kB RAM and a memory-mapped helper
module to interact with the software, e.g. to dump out the test signature and to
end the simulation.
The simulation is designed for Verilator, but can be adapted to other simulators
if needed.
How to run RISC-V Compliance on Ibex
------------------------------------
0. Check your prerequisites
To compile the simulation and run the compliance test suite you need to
have the following tools installed:
- Verilator
- fusesoc
- srecord (for `srec_cat`)
- A RV32 compiler
On Ubuntu/Debian, install the required tools like this:
```sh
sudo apt-get install srecord python3-pip
pip3 install --user -U fusesoc
```
We recommend installing Verilator from source as versions from Linux distributions are often outdated.
See https://www.veripool.org/projects/verilator/wiki/Installing for installation instructions.
1. Build a simulation of Ibex
```sh
cd $IBEX_REPO_BASE
fusesoc --cores-root=. run --target=sim --setup --build lowrisc:ibex:ibex_riscv_compliance --RV32M=0 --RV32E=0
```
You can use the two compile-time options `--RV32M` and `--RV32E` to
enable/disable the M and E ISA extensions, respectively.
You can now find the compiled simulation at `build/lowrisc_ibex_ibex_riscv_compliance_0.1/sim-verilator/Vibex_riscv_compliance`.
2. Get the RISC-V Compliance test suite
The compliance test suite currently needs workarounds for Ibex.
Get a modified version of it.
```
git clone https://github.com/imphil/riscv-compliance.git
cd riscv-compliance
git checkout ibex
```
3. Run the test suite
```sh
cd $RISCV_COMPLIANCE_REPO_BASE
export RISCV_TARGET=ibex
export RISCV_ISA=rv32i
export RISCV_PREFIX=riscv32-unknown-elf-
export TARGET_SIM=path/to/your/Vibex_riscv_compliance
make clean
make
```

View file

@ -0,0 +1,93 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include <signal.h>
#include <iostream>
#include "Vibex_riscv_compliance.h"
#include "verilated_toplevel.h"
#include "verilator_sim_ctrl.h"
VERILATED_TOPLEVEL(ibex_riscv_compliance)
ibex_riscv_compliance* top;
VerilatorSimCtrl* simctrl;
static void SignalHandler(int sig) {
if (!simctrl) {
return;
}
switch (sig) {
case SIGINT:
simctrl->RequestStop();
break;
case SIGUSR1:
if (simctrl->TracingEnabled()) {
simctrl->TraceOff();
} else {
simctrl->TraceOn();
}
break;
}
}
static void SetupSignalHandler() {
struct sigaction sigIntHandler;
sigIntHandler.sa_handler = SignalHandler;
sigemptyset(&sigIntHandler.sa_mask);
sigIntHandler.sa_flags = 0;
sigaction(SIGINT, &sigIntHandler, NULL);
sigaction(SIGUSR1, &sigIntHandler, NULL);
}
/**
* Get the current simulation time
*
* Called by $time in Verilog, converts to double, to match what SystemC does
*/
double sc_time_stamp() { return simctrl->GetTime(); }
int main(int argc, char** argv) {
int retcode;
top = new ibex_riscv_compliance;
simctrl = new VerilatorSimCtrl(top, top->IO_CLK, top->IO_RST_N,
VerilatorSimCtrlFlags::ResetPolarityNegative);
SetupSignalHandler();
if (!simctrl->ParseCommandArgs(argc, argv, retcode)) {
goto free_return;
}
std::cout << "Simulation of Ibex" << std::endl
<< "==================" << std::endl
<< std::endl;
if (simctrl->TracingPossible()) {
std::cout << "Tracing can be toggled by sending SIGUSR1 to this process:"
<< std::endl
<< "$ kill -USR1 " << getpid() << std::endl;
}
// Initialize RAM
simctrl->InitRam("TOP.ibex_riscv_compliance.u_ram");
simctrl->Run();
simctrl->PrintStatistics();
if (simctrl->TracingEverEnabled()) {
std::cout << std::endl
<< "You can view the simulation traces by calling" << std::endl
<< "$ gtkwave " << simctrl->GetSimulationFileName() << std::endl;
}
free_return:
delete top;
delete simctrl;
return retcode;
}

View file

@ -0,0 +1,65 @@
CAPI=2:
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
name: "lowrisc:ibex:ibex_riscv_compliance:0.1"
description: "Ibex simulation for RISC-V compliance testing (using Verilator)"
filesets:
files_sim_verilator:
depend:
- lowrisc:dv_verilator:simutil_verilator
- lowrisc:ibex:ibex_core
files:
- rtl/ibex_riscv_compliance.sv
- ibex_riscv_compliance.cc: { file_type: cppSource }
- rtl/prim_clock_gating.sv
- rtl/ram_1p.sv
- rtl/bus.sv
- rtl/riscv_testutil.sv
file_type: systemVerilogSource
parameters:
RV32M:
datatype: int
paramtype: vlogparam
default: 1
description: Enable the M ISA extension (hardware multiply/divide) [0/1]
RV32E:
datatype: int
paramtype: vlogparam
default: 0
description: Enable the E ISA extension (reduced register set) [0/1]
targets:
sim:
default_tool: verilator
filesets:
- files_sim_verilator
parameters:
- RV32M
- RV32E
toplevel: ibex_riscv_compliance
tools:
verilator:
mode: cc
verilator_options:
# Disabling tracing reduces compile times by multiple times, but doesn't have a
# huge influence on runtime performance. (Based on early observations.)
- '--trace'
- '--trace-fst' # this requires -DVM_TRACE_FMT_FST in CFLAGS below!
- '--trace-structs'
- '--trace-params'
- '--trace-max-array 1024'
# compiler flags
#
# -O
# Optimization levels have a large impact on the runtime performance of the
# simulation model. -O2 and -O3 are pretty similar, -Os is slower than -O2/-O3
- '-CFLAGS "-std=c++11 -Wall -DVM_TRACE_FMT_FST -g -O0"'
- '-LDFLAGS "-pthread -lutil"'
- "-Wall"
- "-Wno-PINCONNECTEMPTY"
# XXX: Cleanup all warnings and remove this option
# (or make it more fine-grained at least)
- "-Wno-fatal"

View file

@ -0,0 +1,120 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
/**
* Simplistic Ibex bus implementation
*
* This module is designed for demo and simulation purposes, do not use it in
* a real-world system.
*
* This implementation doesn't handle the full bus protocol, but makes the
* following simplifying assumptions.
*
* - All devices (slaves) must respond in the next cycle after the request.
* - Host (master) arbitration is strictly priority based.
*/
module bus #(
parameter NrDevices,
parameter NrHosts,
parameter DataWidth = 32,
parameter AddressWidth = 32
) (
input clk_i,
input rst_ni,
// Hosts (masters)
input host_req_i [NrHosts],
output logic host_gnt_o [NrHosts],
input [AddressWidth-1:0] host_addr_i [NrHosts],
input host_we_i [NrHosts],
input [ DataWidth/8-1:0] host_be_i [NrHosts],
input [ DataWidth-1:0] host_wdata_i [NrHosts],
output logic host_rvalid_o [NrHosts],
output logic [ DataWidth-1:0] host_rdata_o [NrHosts],
output logic host_err_o [NrHosts],
// Devices (slaves)
output logic device_req_o [NrDevices],
output logic [AddressWidth-1:0] device_addr_o [NrDevices],
output logic device_we_o [NrDevices],
output logic [ DataWidth/8-1:0] device_be_o [NrDevices],
output logic [ DataWidth-1:0] device_wdata_o [NrDevices],
input device_rvalid_i [NrDevices],
input [ DataWidth-1:0] device_rdata_i [NrDevices],
input device_err_i [NrDevices],
// Device address map
input [AddressWidth-1:0] cfg_device_addr_base [NrDevices],
input [AddressWidth-1:0] cfg_device_addr_mask [NrDevices]
);
logic [$clog2(NrHosts)-1:0] host_sel_req, host_sel_resp;
logic [$clog2(NrDevices)-1:0] device_sel_req, device_sel_resp;
// Master select prio arbiter
always_comb begin
for (integer host = NrHosts - 1; host >= 0; host = host - 1) begin
if (host_req_i[host]) begin
host_sel_req = $clog2(NrHosts)'(host);
end
end
end
// Device select
always_comb begin
for (integer device = 0; device < NrDevices; device = device + 1) begin
if ((host_addr_i[host_sel_req] & cfg_device_addr_mask[device])
== cfg_device_addr_base[device]) begin
device_sel_req = $clog2(NrDevices)'(device);
end
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
host_sel_resp <= '0;
device_sel_resp <= '0;
end else begin
// Responses are always expected 1 cycle after the request
device_sel_resp <= device_sel_req;
host_sel_resp <= host_sel_req;
end
end
always_comb begin
for (integer device = 0; device < NrDevices; device = device + 1) begin
if ($clog2(NrDevices)'(device) == device_sel_req) begin
device_req_o[device] = host_req_i[host_sel_req];
device_we_o[device] = host_we_i[host_sel_req];
device_addr_o[device] = host_addr_i[host_sel_req];
device_wdata_o[device] = host_wdata_i[host_sel_req];
device_be_o[device] = host_be_i[host_sel_req];
end else begin
device_req_o[device] = 1'b0;
device_we_o[device] = 1'b0;
device_addr_o[device] = 'b0;
device_wdata_o[device] = 'b0;
device_be_o[device] = 'b0;
end
end
end
always_comb begin
for (integer host = 0; host < NrHosts; host = host + 1) begin
if ($clog2(NrHosts)'(host) == host_sel_resp) begin
host_rvalid_o[host] = device_rvalid_i[device_sel_resp];
host_err_o[host] = device_err_i[device_sel_resp];
host_rdata_o[host] = device_rdata_i[device_sel_resp];
end else begin
host_gnt_o[host] = 1'b0;
host_rvalid_o[host] = 1'b0;
host_err_o[host] = 1'b0;
host_rdata_o[host] = 'b0;
end
end
host_gnt_o[host_sel_req] = host_req_i[host_sel_req];
end
endmodule

View file

@ -0,0 +1,185 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
/**
* Ibex simulation to run the RISC-V compliance test on
*
* This is a toplevel wrapper for Ibex with helpers to run the RISC-V compliance
* test. It is designed for Verilator, but should equally work for other
* simulators (if the top-level clk and rst ports are replaced with a generated
* clock).
*/
module ibex_riscv_compliance (
input IO_CLK,
input IO_RST_N
);
parameter bit RV32E = 0;
parameter bit RV32M = 1;
logic clk_sys, rst_sys_n;
assign clk_sys = IO_CLK;
assign rst_sys_n = IO_RST_N;
typedef enum {
TestUtilHost,
CoreI,
CoreD
} bus_host_e;
typedef enum {
Ram,
TestUtilDevice
} bus_device_e;
localparam NrDevices = 2;
localparam NrHosts = 3;
// host and device signals
logic host_req [NrHosts];
logic host_gnt [NrHosts];
logic [31:0] host_addr [NrHosts];
logic host_we [NrHosts];
logic [ 3:0] host_be [NrHosts];
logic [31:0] host_wdata [NrHosts];
logic host_rvalid [NrHosts];
logic [31:0] host_rdata [NrHosts];
logic host_err [NrHosts];
// devices (slaves)
logic device_req [NrDevices];
logic [31:0] device_addr [NrDevices];
logic device_we [NrDevices];
logic [ 3:0] device_be [NrDevices];
logic [31:0] device_wdata [NrDevices];
logic device_rvalid [NrDevices];
logic [31:0] device_rdata [NrDevices];
logic device_err [NrDevices];
// Device address mapping
logic [31:0] cfg_device_addr_base [NrDevices];
logic [31:0] cfg_device_addr_mask [NrDevices];
assign cfg_device_addr_base[Ram] = 32'h0;
assign cfg_device_addr_mask[Ram] = ~32'hFFFF; // 64 kB
assign cfg_device_addr_base[TestUtilDevice] = 32'h20000;
assign cfg_device_addr_mask[TestUtilDevice] = ~32'h3FF; // 1 kB
bus #(
.NrDevices (NrDevices),
.NrHosts (NrHosts ),
.DataWidth (32 ),
.AddressWidth(32 )
) u_bus (
.clk_i (clk_sys),
.rst_ni (rst_sys_n),
.host_req_i (host_req ),
.host_gnt_o (host_gnt ),
.host_addr_i (host_addr ),
.host_we_i (host_we ),
.host_be_i (host_be ),
.host_wdata_i (host_wdata ),
.host_rvalid_o (host_rvalid ),
.host_rdata_o (host_rdata ),
.host_err_o (host_err ),
.device_req_o (device_req ),
.device_addr_o (device_addr ),
.device_we_o (device_we ),
.device_be_o (device_be ),
.device_wdata_o (device_wdata ),
.device_rvalid_i (device_rvalid),
.device_rdata_i (device_rdata ),
.device_err_i (device_err ),
.cfg_device_addr_base,
.cfg_device_addr_mask
);
ibex_core #(
.DmHaltAddr(32'h00000000),
.DmExceptionAddr(32'h00000000),
.RV32E(RV32E),
.RV32M(RV32M)
) u_core (
.clk_i (clk_sys),
.rst_ni (rst_sys_n),
.test_en_i ('b0),
.core_id_i (4'b0),
.cluster_id_i (6'b0),
// First instruction executed is at 0x0 + 0x80
.boot_addr_i (32'h00000000),
.instr_req_o (host_req[CoreI]),
.instr_gnt_i (host_gnt[CoreI]),
.instr_rvalid_i (host_rvalid[CoreI]),
.instr_addr_o (host_addr[CoreI]),
.instr_rdata_i (host_rdata[CoreI]),
.data_req_o (host_req[CoreD]),
.data_gnt_i (host_gnt[CoreD]),
.data_rvalid_i (host_rvalid[CoreD]),
.data_we_o (host_we[CoreD]),
.data_be_o (host_be[CoreD]),
.data_addr_o (host_addr[CoreD]),
.data_wdata_o (host_wdata[CoreD]),
.data_rdata_i (host_rdata[CoreD]),
.data_err_i (host_err[CoreD]),
.irq_software_i (1'b0),
.irq_timer_i (1'b0),
.irq_external_i (1'b0),
.irq_fast_i (15'b0),
.irq_nm_i (1'b0),
.debug_req_i ('b0),
.fetch_enable_i ('b1)
);
// SRAM block for instruction and data storage
ram_1p #(
.Depth(64*1024/4)
) u_ram (
.clk_i (clk_sys),
.rst_ni (rst_sys_n),
.req_i (device_req[Ram]),
.we_i (device_we[Ram]),
.be_i (device_be[Ram]),
.addr_i (device_addr[Ram]),
.wdata_i (device_wdata[Ram]),
.rvalid_o (device_rvalid[Ram]),
.rdata_o (device_rdata[Ram])
);
// RISC-V test utility, used by the RISC-V compliance test to interact with
// the simulator.
riscv_testutil
u_riscv_testutil(
.clk_i (clk_sys),
.rst_ni (rst_sys_n),
// Device port
.dev_req_i (device_req[TestUtilDevice]),
.dev_we_i (device_we[TestUtilDevice]),
.dev_addr_i (device_addr[TestUtilDevice]),
.dev_wdata_i (device_wdata[TestUtilDevice]),
.dev_rvalid_o (device_rvalid[TestUtilDevice]),
.dev_rdata_o (device_rdata[TestUtilDevice]),
.dev_be_i (device_be[TestUtilDevice]),
.dev_err_o (device_err[TestUtilDevice]),
// Host port
.host_req_o (host_req[TestUtilHost]),
.host_gnt_i (host_gnt[TestUtilHost]),
.host_rvalid_i (host_rvalid[TestUtilHost]),
.host_addr_o (host_addr[TestUtilHost]),
.host_rdata_i (host_rdata[TestUtilHost])
);
endmodule

View file

@ -0,0 +1,24 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Dummy clock gating module
module prim_clock_gating (
input clk_i,
input en_i,
input test_en_i,
output logic clk_o
);
logic clk_en;
always_latch begin
if (clk_i == 1'b0) begin
clk_en <= en_i | test_en_i;
end
end
assign clk_o = clk_i & clk_en;
endmodule

View file

@ -0,0 +1,70 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
/**
* Single-port RAM with 1 cycle read/write delay, 32 bit words
*/
module ram_1p #(
parameter int Depth = 128
) (
input clk_i,
input rst_ni,
input req_i,
input we_i,
input [ 3:0] be_i,
input [31:0] addr_i,
input [31:0] wdata_i,
output logic rvalid_o,
output logic [31:0] rdata_o
);
localparam int Aw = $clog2(Depth);
logic [31:0] mem [Depth];
logic [Aw-1:0] addr_idx;
assign addr_idx = addr_i[Aw-1+2:2];
logic [31-Aw:0] unused_addr_parts;
assign unused_addr_parts = {addr_i[31:Aw+2], addr_i[1:0]};
always @(posedge clk_i) begin
if (req_i) begin
if (we_i) begin
for (int i = 0; i < 4; i = i + 1) begin
if (be_i[i] == 1'b1) begin
mem[addr_idx][i*8 +: 8] <= wdata_i[i*8 +: 8];
end
end
end
rdata_o <= mem[addr_idx];
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rvalid_o <= '0;
end else begin
rvalid_o <= req_i;
end
end
`ifdef VERILATOR
export "DPI-C" task simutil_verilator_memload;
task simutil_verilator_memload;
input string file;
$readmemh(file, mem);
endtask
`endif
`ifdef SRAM_INIT_FILE
localparam MEM_FILE = `"`SRAM_INIT_FILE`";
initial begin
$display("Initializing SRAM from %s", MEM_FILE);
$readmemh(MEM_FILE, mem);
end
`endif
endmodule

View file

@ -0,0 +1,158 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
/**
* RISC-V Compliance Test helper module (simulation only)
*
* This module lets the RISC-V compliance test software interact with the
* "outside world".
*
* It consists of a bus device and a bus host.
*
* Through the bus device, the software can interact with the outside world and
* configure this module. The following registers are supported:
* 0x0: read the signature and halt the execution
* 0x4: set the signature start address
* 0x8: set the signature end address
*
* When register 0x0 is written with an arbitrary value, the test signature is
* read through the bus device, and written to STDOUT, prefixed with
* "SIGNATURE: ".
*/
module riscv_testutil (
input clk_i,
input rst_ni,
// bus device (slave) interface
input dev_req_i,
input dev_we_i,
input [31:0] dev_addr_i,
input [31:0] dev_wdata_i,
input [ 3:0] dev_be_i,
output logic dev_rvalid_o,
output logic [31:0] dev_rdata_o,
output logic dev_err_o,
// bus host (master) interface
output logic host_req_o,
input host_gnt_i,
input host_rvalid_i,
output logic [31:0] host_addr_o,
input [31:0] host_rdata_i
);
// ======= Bus device for interaction with software ======= //
localparam ADDR_HALT = 0;
localparam ADDR_SET_BEGIN_SIGNATURE = 4;
localparam ADDR_SET_END_SIGNATURE = 8;
// 1 kB address space for this peripheral
logic [21:0] unused_addr;
assign unused_addr = dev_addr_i[31:10];
logic [31:0] begin_signature_addr_d, begin_signature_addr_q;
logic [31:0] end_signature_addr_d, end_signature_addr_q;
logic read_signature_and_terminate;
always_comb begin
read_signature_and_terminate = 1'b0;
begin_signature_addr_d = begin_signature_addr_q;
end_signature_addr_d = end_signature_addr_q;
if (dev_we_i && dev_req_i) begin
case (dev_addr_i[9:0])
ADDR_HALT: begin
read_signature_and_terminate = 1'b1;
end
ADDR_SET_BEGIN_SIGNATURE: begin
begin_signature_addr_d = dev_wdata_i;
end
ADDR_SET_END_SIGNATURE: begin
end_signature_addr_d = dev_wdata_i;
end
endcase
end
end
always_ff @(posedge clk_i) begin
begin_signature_addr_q <= begin_signature_addr_d;
end_signature_addr_q <= end_signature_addr_d;
end
// all responses are in the next cycle
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
dev_rvalid_o <= 1'b0;
end else begin
dev_rvalid_o <= dev_req_i;
end
end
// only word writes are supported
assign dev_err_o = (~dev_we_i | dev_be_i != 4'hf) & dev_req_i;
assign dev_rdata_o = 32'h0;
// ======= FSM: Read signature from memory and dump it to STDOUT ======= //
typedef enum logic [1:0] {
WAIT, READ, READ_FINISH, TERMINATE
} readsig_state_e;
readsig_state_e state_q, state_d;
logic [31:0] read_addr_d, read_addr_q;
always_comb begin
state_d = state_q;
host_req_o = 1'b0;
unique case (state_q)
WAIT: begin
if (read_signature_and_terminate) begin
$display("Reading signature from 0x%x to 0x%x",
begin_signature_addr_q, end_signature_addr_q);
state_d = READ;
read_addr_d = begin_signature_addr_q;
end
end
READ: begin
host_req_o = 1'b1;
host_addr_o = read_addr_q;
if (host_gnt_i) begin
read_addr_d = read_addr_q + 4;
if (read_addr_d == end_signature_addr_q) begin
state_d = READ_FINISH;
end
end
end
READ_FINISH: begin
if (host_rvalid_i) begin
state_d = TERMINATE;
end
end
TERMINATE: begin
$display("Terminating simulation by software request.");
$finish;
end
endcase
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
state_q <= WAIT;
read_addr_q <= 0;
end else begin
state_q <= state_d;
read_addr_q <= read_addr_d;
if (host_rvalid_i) begin
$display("SIGNATURE: 0x%x", host_rdata_i);
end
end
end
endmodule