diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3e530ce5..c278517b 100755 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -136,4 +136,21 @@ RUN mkdir -p /tmp/sail-riscv && \ USER dev RUN pip3 install riscof +# install riscv-dv +USER dev +RUN pip3 install PyYAML bitstring Sphinx Pallets-Sphinx-Themes sphinxcontrib-log-cabinet \ + sphinx-issues sphinx_rtd_theme rst2pdf flake8 pyvsc tabulate pandas +ARG RISCV_DV_VERSION=b7a0b4b0b51346a3c64f159f81ea262d867c14a9 +ARG RISCV_DV_TAR_NAME=${RISCV_DV_VERSION}.tar.gz +ARG RISCV_DV_TAR_URL=https://github.com/chipsalliance/riscv-dv/archive/${RISCV_DV_TAR_NAME} +ARG RISCV_DV_TAR_HASH="e95d179b52f3c8dd1566b5b7bf6a5b42c570bfb17cca7f1f2c73460a0c5d0669 ${RISCV_DV_TAR_NAME}" +RUN mkdir -p /tmp/riscv-dv && \ + cd /tmp/riscv-dv && \ + wget ${RISCV_DV_TAR_URL} && \ + echo ${RISCV_DV_TAR_HASH} | sha256sum -c && \ + tar -xzf ${RISCV_DV_TAR_NAME} && \ + sudo mv riscv-dv-* /opt/riscv-dv && \ + cd .. && rm -rf /tmp/riscv-dv +ENV RISCV_DV_HOME=/opt/riscv-dv + ENTRYPOINT ["/usr/bin/dumb-init", "--"] diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 97c5c427..92ec5dae 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -162,6 +162,35 @@ jobs: riscof/riscof_work/style.css retention-days: 7 + riscv_dv: + timeout-minutes: 30 + needs: select_ci_image + # dont skip if build_ci_image is skipped + if: always() + runs-on: ubuntu-latest + container: + image: ${{ needs.select_ci_image.outputs.image-tag }} + + strategy: + matrix: *utoss-risc-v-matrix + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run riscv-dv + env: + RISCV_DV_DIR: /opt/riscv-dv + run: make riscv_dv UTOSS_RISCV_CONFIG=${{ matrix.config }} + + - name: Upload riscv-dv logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: riscv-dv-logs-${{ matrix.config }} + path: utoss_riscv_dv/logs/** + retention-days: 7 + de1-soc: timeout-minutes: 30 needs: diff --git a/.gitignore b/.gitignore index f5cc6bed..e6cd3bde 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ riscof/utoss_riscv/utoss_riscv_isa.yaml riscof/sail_cSim/__pycache__ riscof/config.ini riscof/dut_sim +utoss_riscv_dv/logs/ envs/de1-soc/poc/poc*.mem envs/de1-soc/poc/poc*.mif diff --git a/Dockerfile.ci b/Dockerfile.ci index 449b64e6..7eaabd79 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -85,6 +85,22 @@ USER root RUN apt update && apt install -y python3 python3-pip && \ pip3 install riscof +# install riscv-dv +RUN pip3 install PyYAML bitstring Sphinx Pallets-Sphinx-Themes sphinxcontrib-log-cabinet \ + sphinx-issues sphinx_rtd_theme rst2pdf flake8 pyvsc tabulate pandas +ARG RISCV_DV_VERSION=b7a0b4b0b51346a3c64f159f81ea262d867c14a9 +ARG RISCV_DV_TAR_NAME=${RISCV_DV_VERSION}.tar.gz +ARG RISCV_DV_TAR_URL=https://github.com/chipsalliance/riscv-dv/archive/${RISCV_DV_TAR_NAME} +ARG RISCV_DV_TAR_HASH="e95d179b52f3c8dd1566b5b7bf6a5b42c570bfb17cca7f1f2c73460a0c5d0669 ${RISCV_DV_TAR_NAME}" +RUN mkdir -p /tmp/riscv-dv && \ + cd /tmp/riscv-dv && \ + wget ${RISCV_DV_TAR_URL} && \ + echo ${RISCV_DV_TAR_HASH} | sha256sum -c && \ + tar -xzf ${RISCV_DV_TAR_NAME} && \ + mv riscv-dv-* /opt/riscv-dv && \ + cd .. && rm -rf /tmp/riscv-dv +ENV RISCV_DV_HOME=/opt/riscv-dv + # install svlint USER dev ARG SVLINT_VERSION=0.9.3 diff --git a/Makefile b/Makefile index 3c528bf1..3e3aee5d 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,8 @@ RISCOF_UTOSS_RISCV_ISA_CONFIG := $(RISCOF_DIR)/utoss_riscv/utoss_riscv_ UTOSS_RISCV_CONFIG ?= RV32I +UTOSS_BOOT_ADDR ?= 32\'h0000_0000 + # Convert B extension to Zbb for RISC-V ISA spec RISCOF_ISA_STRING = $(subst B,Zbb,$(UTOSS_RISCV_CONFIG)) @@ -50,7 +52,8 @@ UTOSS_RISCV_RISCOF_VERILATOR_DEFINES := -DUTOSS_PIPELINE_LOGGER # =========================== VERILATOR_FLAGS := -Wall --binary --trace --timing -sv -cc -O3 $(UTOSS_RISCV_VERILATOR_DEFINES) -RISCOF_VERILATOR_FLAGS := $(VERILATOR_FLAGS) $(UTOSS_RISCV_RISCOF_VERILATOR_DEFINES) +TOP_VERILATOR_FLAGS := $(VERILATOR_FLAGS) -GBOOT_ADDR=$(UTOSS_BOOT_ADDR) +RISCOF_VERILATOR_FLAGS := $(TOP_VERILATOR_FLAGS) $(UTOSS_RISCV_RISCOF_VERILATOR_DEFINES) # Testbench-only defines TB_DEFINES := -DTESTBENCH @@ -91,7 +94,7 @@ run_top: $(OUT_DIR)/top_sim $(OUT_DIR)/top_sim: $(SRCS) @mkdir -p $(BUILD_DIR)/top - $(VERILATOR) $(VERILATOR_FLAGS) \ + $(VERILATOR) $(TOP_VERILATOR_FLAGS) \ --top-module top \ --Mdir $(BUILD_DIR)/top \ -o top_sim \ @@ -181,6 +184,8 @@ riscof_run: $(RISCOF_UTOSS_RISCV_ISA_CONFIG) $(RISCOF_CONFIG) riscof_build_dut --suite=riscv-arch-test/riscv-test-suite/ \ --env=riscv-arch-test/riscv-test-suite/env +include utoss_riscv_dv/Makefile + # sidekick image builds GITHUB_CONTAINER_REGISTRY=ghcr.io GITHUB_ORG_NAME=utoss @@ -207,4 +212,5 @@ svlint_tb: .PHONY: all build_top run_top build_tb run_tb new_tb \ svlint svlint_tb \ riscof_build_dut riscof_validateyaml riscof_clone_archtest \ - riscof_generate_testlist riscof_run FORCE + riscof_generate_testlist riscof_run \ + riscv_dv FORCE diff --git a/envs/simulation/memory.sv b/envs/simulation/memory.sv index 3b1bd2df..d64d3cd2 100644 --- a/envs/simulation/memory.sv +++ b/envs/simulation/memory.sv @@ -3,7 +3,9 @@ // a dual-read single-write memory block that we use with our core in simulation environment module memory - #( parameter SIZE = 1024 ) + #( parameter SIZE = 1024 + , parameter addr_t BOOT_ADDR = addr_t'(0) + ) ( input wire clk , mem_bus.memory i_bus @@ -32,16 +34,25 @@ module memory end end - wire unused = &{d_bus.address[`PROCESSOR_BITNESS -1:SIZE_W], d_bus.address[1:0], i_bus.address[`PROCESSOR_BITNESS -1:SIZE_W], i_bus.address[1:0]}; + addr_t d_base_address; + addr_t i_base_address; + + assign d_base_address = d_bus.address - BOOT_ADDR; + assign i_base_address = i_bus.address - BOOT_ADDR; + + wire unused = &{d_base_address[`PROCESSOR_BITNESS -1:SIZE_W] + , d_base_address[1:0] + , i_base_address[`PROCESSOR_BITNESS -1:SIZE_W] + , i_base_address[1:0]}; always @(posedge clk) begin - d_bus.read_data <= M[d_bus.address[SIZE_W + 1:2]]; - i_bus.read_data <= M[i_bus.address[SIZE_W + 1:2]]; + d_bus.read_data <= M[d_base_address[SIZE_W + 1:2]]; + i_bus.read_data <= M[i_base_address[SIZE_W + 1:2]]; - if (d_bus.write_enable[0]) M[d_bus.address[SIZE_W +1:2]][7:0] <= d_bus.write_data[7:0]; - if (d_bus.write_enable[1]) M[d_bus.address[SIZE_W +1:2]][15:8] <= d_bus.write_data[15:8]; - if (d_bus.write_enable[2]) M[d_bus.address[SIZE_W +1:2]][23:16] <= d_bus.write_data[23:16]; - if (d_bus.write_enable[3]) M[d_bus.address[SIZE_W +1:2]][31:24] <= d_bus.write_data[31:24]; + if (d_bus.write_enable[0]) M[d_base_address[SIZE_W +1:2]][7:0] <= d_bus.write_data[7:0]; + if (d_bus.write_enable[1]) M[d_base_address[SIZE_W +1:2]][15:8] <= d_bus.write_data[15:8]; + if (d_bus.write_enable[2]) M[d_base_address[SIZE_W +1:2]][23:16] <= d_bus.write_data[23:16]; + if (d_bus.write_enable[3]) M[d_base_address[SIZE_W +1:2]][31:24] <= d_bus.write_data[31:24]; end endmodule diff --git a/envs/simulation/top.sv b/envs/simulation/top.sv index c9d291ac..7e5735d5 100644 --- a/envs/simulation/top.sv +++ b/envs/simulation/top.sv @@ -2,7 +2,9 @@ `include "src/interfaces/mem_bus.svh" module top - #( parameter MEM_SIZE = 1024 ) //maybe change to 2048 if using dual port? + #( parameter MEM_SIZE = 1024 //maybe change to 2048 if using dual port? + , parameter addr_t BOOT_ADDR = addr_t'(0) + ) ( input wire clk , input wire reset ); @@ -11,7 +13,10 @@ module top mem_bus d_bus(); wire unused = &{i_bus.write_data, i_bus.write_enable}; - memory #( .SIZE ( MEM_SIZE ) ) + memory + #( .SIZE ( MEM_SIZE ) + , .BOOT_ADDR ( BOOT_ADDR ) + ) u_memory ( .clk ( clk ) @@ -19,7 +24,9 @@ module top , .i_bus ( i_bus.memory ) ); - utoss_riscv core + utoss_riscv + #( .BOOT_ADDR ( BOOT_ADDR ) ) + core ( .clk ( clk ) , .reset ( reset ) diff --git a/riscof/dut.sv b/riscof/dut.sv index 9a8dab0a..7829663f 100644 --- a/riscof/dut.sv +++ b/riscof/dut.sv @@ -2,12 +2,16 @@ `include "test/utils.svh" -module dut; +module dut + #( parameter logic [31:0] BOOT_ADDR = 32'h0000_0000 ); reg clk; reg reset; - top #( .MEM_SIZE ( 500000 /* 2MB */ ) ) + top + #( .MEM_SIZE ( 500000 /* 2MB */ ) + , .BOOT_ADDR ( BOOT_ADDR ) + ) top ( .clk ( clk ) , .reset ( reset ) @@ -33,7 +37,7 @@ module dut; task watch_tohost(); /* verilator lint_off UNUSEDSIGNAL */ - integer tohost; + integer tohost, tohost_local; reg [31:0] tohost_data; /* verilator lint_on UNUSEDSIGNAL */ @@ -43,7 +47,8 @@ module dut; while (tohost_data === 0 || tohost_data === 32'bx) begin @(posedge clk); - tohost_data = top.u_memory.M[19'(tohost[31:2])]; + tohost_local = tohost - BOOT_ADDR; + tohost_data = top.u_memory.M[19'(tohost_local[31:2])]; end $display("%m: memory[tohost] written <%0d> at time %t", tohost_data, $time); @@ -68,7 +73,7 @@ module dut; /* verilator lint_off UNUSEDSIGNAL */ integer begin_signature, end_signature; string sig_filename; - integer sig_file, i; + integer sig_file, i, i_local; //integer i_fixed; /* verilator lint_on UNUSEDSIGNAL */ @@ -90,7 +95,8 @@ module dut; sig_file = $fopen(sig_filename, "w"); if (sig_file != 0) begin for (i = begin_signature; i < end_signature; i = i + 4) begin - $fwrite(sig_file, "%08x\n", top.u_memory.M[19'(i[31:2])]); + i_local = i - BOOT_ADDR; + $fwrite(sig_file, "%08x\n", top.u_memory.M[19'(i_local[31:2])]); end $fclose(sig_file); $display("%m: signature written to %s", sig_filename); diff --git a/src/modules/Logger.sv b/src/modules/Logger.sv index b68ddcbd..ddb47e26 100644 --- a/src/modules/Logger.sv +++ b/src/modules/Logger.sv @@ -225,7 +225,8 @@ module Logger ); $display - ( "MEM: addr=%08h we=%b wdata=%08h rdata=%08h alu_result=%08h rd=%s result_src=%0d regwrite=%0b" + ( "MEM: pc=%08h addr=%08h we=%b wdata=%08h rdata=%08h alu_result=%08h rd=%s result_src=%0d regwrite=%0b" + , mem_stage.pc_cur , dmem_address , dmem_write_enable , dmem_write_data @@ -237,7 +238,8 @@ module Logger ); $display - ( "WB : rd=%s(%0d) regwrite=%0b result_src=%0d wb_result=%08h alu_result=%08h" + ( "WB : pc=%08h rd=%s(%0d) regwrite=%0b result_src=%0d wb_result=%08h alu_result=%08h" + , wb_stage.pc_cur , reg_name(wb_rd) , wb_rd , wb_stage.reg_write diff --git a/src/stages/fetch_stage.sv b/src/stages/fetch_stage.sv index 7aaf3d11..a93fbf4f 100644 --- a/src/stages/fetch_stage.sv +++ b/src/stages/fetch_stage.sv @@ -5,6 +5,7 @@ `include "src/interfaces/ex_to_if_if.svh" module fetch_stage + #( parameter addr_t BOOT_ADDR = addr_t'(0) ) ( output if_to_id_t if_to_id , input ex_to_if_t ex_to_if @@ -51,10 +52,10 @@ module fetch_stage endcase always_ff @ (posedge clk) - if (!stall_f) pc_cur <= reset ? 0 : pc_next; + if (!stall_f) pc_cur <= reset ? BOOT_ADDR : pc_next; always_ff @ (posedge clk) - if (!stall_f) pc_prev <= reset ? 0 : pc_cur; + if (!stall_f) pc_prev <= reset ? BOOT_ADDR : pc_cur; // With synchronous instruction memory, one in-flight instruction can arrive after stall_f rises. // Keep a one-entry skid copy so decode can consume it once the stall is released; diff --git a/src/utoss_riscv.sv b/src/utoss_riscv.sv index 340c9bda..b14c5b25 100644 --- a/src/utoss_riscv.sv +++ b/src/utoss_riscv.sv @@ -9,6 +9,7 @@ // pipelined implementation of our core module utoss_riscv + #( parameter addr_t BOOT_ADDR = addr_t'(0) ) ( input wire clk , input wire reset , mem_bus.consumer i_bus @@ -37,7 +38,9 @@ module utoss_riscv // fetch stage start (@thatlittlegit) - fetch_stage u_fetch_stage + fetch_stage + #( .BOOT_ADDR ( BOOT_ADDR ) ) + u_fetch_stage ( .if_to_id ( if_to_id_out ) , .ex_to_if ( ex_to_if_out ) diff --git a/utoss_riscv_dv/Makefile b/utoss_riscv_dv/Makefile new file mode 100644 index 00000000..e9dd9fdb --- /dev/null +++ b/utoss_riscv_dv/Makefile @@ -0,0 +1,31 @@ +RISCV_DV_DIR ?= $(if $(RISCV_DV_HOME),$(RISCV_DV_HOME),$(HOME)/tools/riscv-dv) +RISCV_DV_OUT ?= utoss_riscv_dv/logs/out_utoss_smoke +RISCV_DV_TEST ?= utoss_rv32i_basic_smoke +RISCV_DV_TESTLIST ?= utoss_riscv_dv/utoss_riscv_dv_smoke.yaml +RISCV_DV_SIMULATOR ?= pyflow +RISCV_DV_MABI ?= ilp32 +RISCV_DV_ISS ?= sail +RISCV_DV_SEED ?= +RISCV_DV_GEN_TIMEOUT ?= 3600 +RISCV_DV_ARGS ?= + +RISCV_DV_TARGET ?= $(shell printf '%s' '$(UTOSS_RISCV_CONFIG)' | tr '[:upper:]' '[:lower:]') + +# Note: The provided IMCB target uses outdated instructions so will not work +RISCV_DV_ISA ?= $(shell printf '%s' '$(subst B,_Zba_Zbb_Zbc_Zbs,$(UTOSS_RISCV_CONFIG))_zicsr_zifencei' | tr '[:upper:]' '[:lower:]') + +riscv_dv: + python3 utoss_riscv_dv/utoss_riscv_dv.py \ + --repo-root . \ + --riscv-dv-dir $(RISCV_DV_DIR) \ + --output $(RISCV_DV_OUT) \ + --test $(RISCV_DV_TEST) \ + --testlist $(RISCV_DV_TESTLIST) \ + --simulator $(RISCV_DV_SIMULATOR) \ + --target $(RISCV_DV_TARGET) \ + --isa $(RISCV_DV_ISA) \ + --mabi $(RISCV_DV_MABI) \ + --iss $(RISCV_DV_ISS) \ + $(if $(RISCV_DV_SEED),--seed $(RISCV_DV_SEED)) \ + $(if $(RISCV_DV_GEN_TIMEOUT),--gen-timeout $(RISCV_DV_GEN_TIMEOUT)) \ + $(RISCV_DV_ARGS) diff --git a/utoss_riscv_dv/README.md b/utoss_riscv_dv/README.md new file mode 100644 index 00000000..38ca2770 --- /dev/null +++ b/utoss_riscv_dv/README.md @@ -0,0 +1,24 @@ +To install riscv-dv: + +``` +sudo apt update +sudo apt install -y git python3 python3-pip make + +mkdir -p ~/tools +git clone https://github.com/chipsalliance/riscv-dv.git ~/tools/riscv-dv +cd ~/tools/riscv-dv + +pip3 install -r requirements.txt +pip3 install --user -e . + +echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc +echo 'export RISCV_DV_HOME=$HOME/tools/riscv-dv' >> ~/.bashrc +source ~/.bashrc +``` + +Usage: +``` +make riscv_dv +``` + +TODO: Delete this README and add to wiki? \ No newline at end of file diff --git a/utoss_riscv_dv/env/link.ld b/utoss_riscv_dv/env/link.ld new file mode 100644 index 00000000..8ad95e04 --- /dev/null +++ b/utoss_riscv_dv/env/link.ld @@ -0,0 +1,18 @@ +OUTPUT_ARCH( "riscv" ) +ENTRY(rvtest_entry_point) + +SECTIONS +{ + . = 0x80000000; + .text.init : { *(.text.init) } + . = ALIGN(0x1000); + .tohost : { *(.tohost) } + . = ALIGN(0x1000); + .text : { *(.text) } + . = ALIGN(0x1000); + .data : { *(.data) } + .data.string : { *(.data.string)} + .bss : { *(.bss) } + _end = .; +} + diff --git a/utoss_riscv_dv/sail_iss.yaml b/utoss_riscv_dv/sail_iss.yaml new file mode 100644 index 00000000..e9bec6bf --- /dev/null +++ b/utoss_riscv_dv/sail_iss.yaml @@ -0,0 +1,3 @@ +- iss: sail + path_var: SAIL_RISCV + cmd: /riscv_sim_RV diff --git a/utoss_riscv_dv/utoss_log_to_trace_csv.py b/utoss_riscv_dv/utoss_log_to_trace_csv.py new file mode 100644 index 00000000..5bab6b6a --- /dev/null +++ b/utoss_riscv_dv/utoss_log_to_trace_csv.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Convert UTOSS pipeline logger output to riscv-dv trace CSV.""" + +import argparse +import os +import re +import sys + + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +# Reuse riscv-dv's CSV writer so UTOSS emits the same column layout as Sail. +RISCV_DV_DIR = os.environ.get("RISCV_DV_DIR") or os.environ.get("RISCV_DV_HOME") +if not RISCV_DV_DIR: + RISCV_DV_DIR = os.path.join(REPO_ROOT, "riscv-dv") +RISCV_DV_DIR = os.path.expandvars(os.path.expanduser(RISCV_DV_DIR)) +sys.path.insert(0, os.path.join(RISCV_DV_DIR, "scripts")) + +from riscv_trace_csv import RiscvInstructionTraceCsv, RiscvInstructionTraceEntry + + +WB_RE = re.compile( + # The writeback line is the architectural register update from the pipeline. + r"\bWB\s*:\s*(?:pc=(?P[0-9a-fA-FxX]+)\s+)?" + r"rd=(?P[a-z0-9?]+)\((?P\d+)\)" + r"\s+regwrite=1\b.*?\bwb_result=(?P[0-9a-fA-F]+)" +) +FETCH_RE = re.compile( + # IF/ID lines provide instruction context for the later writeback line. + r"\b(?:IF|ID)\s*:\s*pc=(?P[0-9a-fA-FxX]+)\s+" + r"instr=(?P[0-9a-fA-FxX]+)\s+op=(?P[a-z0-9_?]+)" +) +ID_RE = re.compile( + r"\bID\s*:\s*pc=(?P[0-9a-fA-FxX]+)\s+" + r"instr=(?P[0-9a-fA-FxX]+)\s+op=(?P[a-z0-9_?]+)\s+" + r"rs1=(?P[a-z0-9?]+)\s+rs2=(?P[a-z0-9?]+)\s+" + r"rd=(?P[a-z0-9?]+)\s+imm=(?P[0-9a-fA-FxX]+)" +) +MEM_RE = re.compile( + # Store parsing is kept separate so memory lines cannot be misread as writes. + r"\bMEM:\s*(?:pc=(?P[0-9a-fA-FxX]+)\s+)?" + r"addr=(?P[0-9a-fA-FxX]+)\s+we=(?P[01xX]+)\s+" + r"wdata=(?P[0-9a-fA-FxX]+)" +) + + +def clean_hex(value): + # Unknown Verilog values contain x; skip them rather than producing bad CSV. + if not value or "x" in value.lower(): + return "" + return value.lower() + + +def set_if_present(entry, name, value): + if value and hasattr(entry, name): + setattr(entry, name, value) + + +def add_instruction_context(entry, pc, instruction_by_pc): + # The comparator is more helpful when each register write has the PC and + # instruction that produced it, not just the destination register value. + pc = clean_hex(pc) + set_if_present(entry, "pc", pc) + set_if_present(entry, "instr_addr", pc) + + context = instruction_by_pc.get(pc, {}) + binary = clean_hex(context.get("binary", "")) + op = context.get("op", "") + set_if_present(entry, "binary", binary) + set_if_present(entry, "instr_bin", binary) + set_if_present(entry, "instr_str", op) + set_if_present(entry, "instr", op) + + operand = context.get("operand", "") + set_if_present(entry, "operand", operand) + + +def add_instruction_context_from_line(instruction_by_pc, match): + pc = clean_hex(match.group("pc")) + if pc: + context = instruction_by_pc.setdefault(pc, {}) + context["binary"] = match.group("binary") + context["op"] = match.group("op") + + +def add_id_context(instruction_by_pc, match): + add_instruction_context_from_line(instruction_by_pc, match) + pc = clean_hex(match.group("pc")) + if pc: + context = instruction_by_pc.setdefault(pc, {}) + context["rs1"] = match.group("rs1") + context["rs2"] = match.group("rs2") + context["rd"] = match.group("rd") + context["imm"] = clean_hex(match.group("imm")) + + if context.get("op", "").startswith("s"): + context["operand"] = "{},{},{}".format( + context["rs2"], + context["rs1"], + context["imm"], + ) + + +def write_store_entry(trace_csv, mem_match, instruction_by_pc): + entry = RiscvInstructionTraceEntry() + add_instruction_context(entry, mem_match.group("pc") or "", instruction_by_pc) + trace_csv.write_trace_entry(entry) + + +def process_utoss_sim_log(log, csv): + with open(log, "r", errors="replace") as log_fd, open(csv, "w") as csv_fd: + trace_csv = RiscvInstructionTraceCsv(csv_fd) + trace_csv.start_new_trace() + instruction_by_pc = {} + + for line in log_fd: + # The tohost write is the generated program's "test complete" signal. + if "memory[tohost] written" in line: + break + + # Cache instruction context by PC as it flows through the front end. + id_match = ID_RE.search(line) + if id_match: + add_id_context(instruction_by_pc, id_match) + continue + + fetch_match = FETCH_RE.search(line) + if fetch_match: + add_instruction_context_from_line(instruction_by_pc, fetch_match) + continue + + mem_match = MEM_RE.search(line) + if mem_match and "1" in mem_match.group("we"): + # Store rows do not affect riscv-dv's default GPR comparison, but + # keeping them in the CSV makes the memory side of the trace visible. + write_store_entry(trace_csv, mem_match, instruction_by_pc) + continue + + match = WB_RE.search(line) + if not match: + continue + + # Keep zero-valued writes so row order stays aligned with Sail. + rd = match.group("rd") + rd_num = int(match.group("rd_num")) + value = match.group("value").lower() + if rd_num == 0 or "x" in value: + continue + + entry = RiscvInstructionTraceEntry() + add_instruction_context(entry, match.group("pc") or "", instruction_by_pc) + entry.gpr.append(f"{rd}:{value}") + trace_csv.write_trace_entry(entry) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--log", required=True) + parser.add_argument("--csv", required=True) + args = parser.parse_args() + process_utoss_sim_log(args.log, args.csv) + + +if __name__ == "__main__": + main() diff --git a/utoss_riscv_dv/utoss_riscv_dv.py b/utoss_riscv_dv/utoss_riscv_dv.py new file mode 100644 index 00000000..43b97c41 --- /dev/null +++ b/utoss_riscv_dv/utoss_riscv_dv.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +"""Generate a riscv-dv test, run it on UTOSS, and compare against Sail.""" + +import argparse +import os +import subprocess +import sys +from pathlib import Path + + +def expand_path(path): + return Path(os.path.expandvars(os.path.expanduser(str(path)))) + + +def riscv_target_parts(name): + name = name.lower().replace("_", "") + extensions = set(name[4:]) + if "g" in extensions: + extensions.remove("g") + extensions.update("imafd") + return name[:4], extensions + + +def choose_riscv_dv_target(config, riscv_dv, simulator="pyflow"): + config_base, config_extensions = riscv_target_parts(config) + candidates = [] + if simulator == "pyflow": + target_dir = (riscv_dv / "pygen" / "pygen_src" / "target") + else: + target_dir = (riscv_dv / "target") + for target in (riscv_dv / "pygen" / "pygen_src" / "target").iterdir(): + if not target.is_dir() or not target.name.lower().startswith(config_base): + continue + target_base, target_extensions = riscv_target_parts(target.name) + if target_base == config_base and target_extensions <= config_extensions: + if target.name == "rv32imcb" and args.simulator == "pyflow": # This target is currently broken + continue + candidates.append((len(target_extensions), target.name)) + return max(candidates)[1] + + +def main(): + script_dir = Path(__file__).resolve().parent + parser = argparse.ArgumentParser() + parser.add_argument("--repo-root", type=Path, default=script_dir.parent) + parser.add_argument("--riscv-dv-dir", type=Path, default=Path(os.environ.get("RISCV_DV_HOME", "~/tools/riscv-dv"))) + parser.add_argument("--output", "-o", default="utoss_riscv_dv/logs/out_utoss_smoke") + parser.add_argument("--test", default="utoss_rv32i_arithmetic_smoke") + parser.add_argument("--testlist", default="utoss_riscv_dv/utoss_riscv_dv_smoke.yaml") + parser.add_argument("--target", default="rv32i") + parser.add_argument("--simulator", default="pyflow") + parser.add_argument("--isa", default="") + parser.add_argument("--mabi", default="ilp32") + parser.add_argument("--iss", default="sail") + parser.add_argument("--seed") + parser.add_argument("--gen-timeout", type=int) + parser.add_argument("--skip-dut", action="store_true") + args, extra_riscv_dv_args = parser.parse_known_args() + + root = expand_path(args.repo_root).resolve() + riscv_dv_path = expand_path(args.riscv_dv_dir) + riscv_dv = (riscv_dv_path if riscv_dv_path.is_absolute() else root / riscv_dv_path).resolve() + if not (riscv_dv / "run.py").exists(): + print(f"Could not find riscv-dv run.py under: {riscv_dv}") + print("Set RISCV_DV_HOME or pass RISCV_DV_DIR=/path/to/riscv-dv.") + return 1 + + target = choose_riscv_dv_target(args.target, riscv_dv, args.simulator) + if target != args.target: + print(f"Target {args.target} is not available. Defaulting to riscv-dv target {target}.\n") + + testlist = expand_path(args.testlist) + if not testlist.is_absolute() and (root / testlist).exists(): + testlist = (root / testlist).resolve() + output_path = expand_path(args.output) + out = (output_path if output_path.is_absolute() else root / output_path).resolve() + test = args.test + riscv_dv_test = f"{test}_0" + asm = out / "asm_test" / f"{riscv_dv_test}.S" + elf = out / "asm_test" / f"{test}.dut.elf" + mem = out / "asm_test" / f"{test}.mem" + sail_log = out / f"{args.iss}_sim" / f"{riscv_dv_test}.log" + utoss_log = out / "dut.log" + sail_csv = out / f"{test}.sail.csv" + compare_csv = out / f"{test}.utoss.csv" + compare_log = out / "trace_compare.log" + riscv_dv_log = out / "riscv_dv_run.log" + + env = os.environ.copy() + # riscv-dv reads these tool paths from the environment. Set defaults so the + # Makefile target works without requiring every variable to be exported. + env["RISCV_DV_DIR"] = str(riscv_dv) + env["RISCV_GCC"] = env.get("RISCV_GCC") or "/opt/riscv/bin/riscv32-unknown-elf-gcc" + env["RISCV_OBJCOPY"] = env.get("RISCV_OBJCOPY") or "/opt/riscv/bin/riscv32-unknown-elf-objcopy" + env["RISCV_NM"] = env.get("RISCV_NM") or "/opt/riscv/bin/riscv32-unknown-elf-nm" + if args.iss == "sail": + env["SAIL_RISCV"] = env.get("SAIL_RISCV") or "/usr/local/bin" + gcc = env["RISCV_GCC"] + objcopy = env["RISCV_OBJCOPY"] + nm = env["RISCV_NM"] + + run_py = [ + sys.executable, + "run.py", + "--testlist", str(testlist), + "--test", args.test, + "--target", target, + "--simulator", args.simulator, + "--iss", args.iss, + "--steps", "gen,gcc_compile,iss_sim", + "--isa", args.isa, + "--mabi", args.mabi, + "-o", str(out), + ] + if args.seed: + run_py += ["--seed", args.seed] + if args.gen_timeout: + run_py += ["--gen_timeout", str(args.gen_timeout)] + if args.iss == "sail": + run_py += ["--iss_yaml", str(script_dir / "sail_iss.yaml")] + out.mkdir(parents=True, exist_ok=True) + # Let riscv-dv do the generation, GCC compile, and Sail reference run. + with riscv_dv_log.open("w") as log: + command = run_py + extra_riscv_dv_args + print("+ " + " ".join(str(arg) for arg in command)) + result = subprocess.run(command, cwd=riscv_dv, stdout=log, stderr=subprocess.STDOUT, env=env) + if result.returncode: + print(f"\nCommand failed with exit code {result.returncode}.") + print(f"Log: {riscv_dv_log}") + return result.returncode + + # Recompile the same assembly with the UTOSS/RISCOF linker script and turn it + # into the Verilog memory image consumed by the processor simulator. + command = [ + gcc, + f"-march={args.isa}", + f"-mabi={args.mabi}", + "-mno-relax", + "-static", + "-mcmodel=medany", + "-fvisibility=hidden", + "-nostdlib", + "-nostartfiles", + "-I", str(riscv_dv / "user_extension"), + "-T", "utoss_riscv_dv/env/link.ld", + "-Wl,-e,_start", + str(asm), + "-o", str(elf), + ] + print("+ " + " ".join(str(arg) for arg in command)) + subprocess.run(command, cwd=root, check=True, env=env) + + command = [objcopy, "-O", "verilog", "--verilog-data-width=4", "--change-addresses=-0x80000000", str(elf), str(mem)] + print("+ " + " ".join(str(arg) for arg in command)) + subprocess.run(command, cwd=root, check=True, env=env) + + if args.skip_dut: + print(f"Sail log written to {sail_log}") + return 0 + + # The simulator watches the generated program's tohost symbol to know when to stop. + result = subprocess.run([nm, str(elf)], cwd=root, text=True, stdout=subprocess.PIPE, check=True) + tohost = "" + for line in result.stdout.splitlines(): + parts = line.split() + if len(parts) >= 3 and parts[2] == "tohost": + tohost = parts[0] + break + if not tohost: + print(f"No tohost symbol found in {elf}; the simulator needs it to stop.") + return 1 + + # Run the UTOSS Verilator simulator on the generated memory image. + command = ["make", "-B", "riscof_build_dut"] + env["UTOSS_BOOT_ADDR"] = "32\\'h8000_0000" + + print("+ " + " ".join(str(arg) for arg in command)) + subprocess.run(command, cwd=root, check=True, env=env) + with utoss_log.open("w") as log: + command = [ + "./riscof/dut_sim", + f"+MEM={mem.as_posix()}", + f"+tohost={tohost}", + f"+VCD_PATH={out.as_posix()}", + ] + print("+ " + " ".join(str(arg) for arg in command)) + result = subprocess.run(command, cwd=root, stdout=log, stderr=subprocess.STDOUT, env=env) + if result.returncode: + print(f"\nCommand failed with exit code {result.returncode}.") + print(f"Log: {utoss_log}") + return result.returncode + + # Convert both logs to riscv-dv trace CSVs and use riscv-dv's comparator for + # the final pass/fail decision. + compare_log.unlink(missing_ok=True) + command = [sys.executable, str(riscv_dv / "scripts" / "sail_log_to_trace_csv.py"), "--log", str(sail_log), "--csv", str(sail_csv)] + print("+ " + " ".join(str(arg) for arg in command)) + subprocess.run(command, cwd=root, check=True, env=env) + + command = [sys.executable, str(root / "utoss_riscv_dv" / "utoss_log_to_trace_csv.py"), "--log", str(utoss_log), "--csv", str(compare_csv)] + print("+ " + " ".join(str(arg) for arg in command)) + subprocess.run(command, cwd=root, check=True, env=env) + command = [ + sys.executable, + str(riscv_dv / "scripts" / "instr_trace_compare.py"), + "--csv_file_1", str(sail_csv), + "--csv_file_2", str(compare_csv), + "--csv_name_1", "sail", + "--csv_name_2", "utoss", + "--log", str(compare_log), + ] + print("+ " + " ".join(str(arg) for arg in command)) + subprocess.run(command, cwd=root, check=True, env=env) + + sail_text = sail_log.read_text(errors="replace") if sail_log.exists() else "" + utoss_text = utoss_log.read_text(errors="replace") if utoss_log.exists() else "" + compare_text = compare_log.read_text(errors="replace") if compare_log.exists() else "" + sail_ok = "SUCCESS" in sail_text + utoss_ok = "memory[tohost] written <1>" in utoss_text + trace_ok = "[PASSED]" in compare_text + + print("\nriscv-dv result") + print(f" Sail : {'PASS' if sail_ok else 'FAIL'} ({sail_log})") + print(f" UTOSS: {'PASS' if utoss_ok else 'FAIL'} ({utoss_log})") + print(f" Trace: {'PASS' if trace_ok else 'FAIL'} ({compare_log})") + print(f" Sail CSV : {sail_csv}") + print(f" UTOSS CSV: {compare_csv}") + + passed = sail_ok and utoss_ok and trace_ok + print(f"VERDICT: {'PASS' if passed else 'FAIL'}") + return 0 if passed else 1 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except subprocess.CalledProcessError as error: + print(f"\nCommand failed with exit code {error.returncode}.") + raise SystemExit(error.returncode) diff --git a/utoss_riscv_dv/utoss_riscv_dv_smoke.yaml b/utoss_riscv_dv/utoss_riscv_dv_smoke.yaml new file mode 100644 index 00000000..316f7141 --- /dev/null +++ b/utoss_riscv_dv/utoss_riscv_dv_smoke.yaml @@ -0,0 +1,34 @@ +# Minimal pyflow smoke tests for the UTOSS RV32I core. +# +# This avoids the heavy default riscv_rand_instr_test from yaml/base_testlist.yaml, +# which uses 10k instructions, multiple subprograms, and directed loop streams. + +- test: utoss_rv32i_arithmetic_smoke + description: > + Small RV32I arithmetic-only generator smoke test. + iterations: 1 + gen_test: riscv_instr_base_test + gen_opts: > + +instr_cnt=1000 + +num_of_sub_program=0 + +no_directed_instr=1 + +no_fence=1 + +no_data_page=1 + +no_branch_jump=1 + +no_load_store=1 + +no_csr_instr=1 + +bare_program_mode=1 + +boot_mode=m + rtl_test: core_base_test + +- test: utoss_rv32i_basic_smoke + description: > + Small RV32I random generator smoke test without directed streams. + iterations: 1 + gen_test: riscv_instr_base_test + gen_opts: > + +instr_cnt=1000 + +num_of_sub_program=0 + +bare_program_mode=1 + +boot_mode=m + rtl_test: core_base_test