diff --git a/.github/workflows/riscv-dv.yml b/.github/workflows/riscv-dv.yml new file mode 100644 index 00000000000..5c623e5ffac --- /dev/null +++ b/.github/workflows/riscv-dv.yml @@ -0,0 +1,254 @@ +name: RISC-V DV tests + +on: + push: + pull_request: + +jobs: + + verilator: + name: Build Verilator + runs-on: ubuntu-latest + env: + CCACHE_DIR: "/opt/veer-el2/.cache/" + DEBIAN_FRONTEND: "noninteractive" + + steps: + - name: Install prerequisities + run: | + sudo apt -qqy update && sudo apt -qqy --no-install-recommends install \ + git autoconf automake autotools-dev curl python3 python3-pip \ + libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex \ + texinfo gperf libtool patchutils bc zlib1g zlib1g-dev libexpat-dev \ + ninja-build ccache libfl2 libfl-dev + + - name: Create Cache Timestamp + id: cache_timestamp + uses: nanzm/get-time-action@v1.1 + with: + format: 'YYYY-MM-DD-HH-mm-ss' + + - name: Setup cache + uses: actions/cache@v2 + timeout-minutes: 3 + continue-on-error: true + with: + path: "/opt/veer-el2/.cache/" + key: cache_verilator_${{ steps.cache_timestamp.outputs.time }} + restore-keys: cache_verilator_ + + - name: Build Verilator + run: | + git clone https://github.com/verilator/verilator + pushd verilator + git checkout v5.002 + autoconf + ./configure --prefix=/opt/verilator + make -j `nproc` + make install + popd + cd /opt && tar -czvf verilator.tar.gz verilator/ + + - name: Store Verilator binaries + uses: actions/upload-artifact@v3 + with: + name: verilator + path: /opt/*.tar.gz + retention-days: 1 + + spike: + name: Build Spike ISS + runs-on: ubuntu-latest + env: + CCACHE_DIR: "/opt/veer-el2/.cache/" + DEBIAN_FRONTEND: "noninteractive" + + steps: + - name: Install prerequisities + run: | + sudo apt -qqy update && sudo apt -qqy --no-install-recommends install \ + git build-essential cmake ccache device-tree-compiler + + - name: Create Cache Timestamp + id: cache_timestamp + uses: nanzm/get-time-action@v1.1 + with: + format: 'YYYY-MM-DD-HH-mm-ss' + + - name: Setup cache + uses: actions/cache@v2 + timeout-minutes: 3 + continue-on-error: true + with: + path: "/opt/veer-el2/.cache/" + key: cache_spike_${{ steps.cache_timestamp.outputs.time }} + restore-keys: cache_spike_ + + - name: Build Spike + run: | + git clone https://github.com/riscv-software-src/riscv-isa-sim spike + export CC="ccache gcc" + export CXX="ccache g++" + pushd spike + git checkout d70ea67d + mkdir build + cd build + ../configure --prefix=/opt/spike + make -j`nproc` + make install + popd + rm -rf /opt/spike/include # Remove include and lib to save space + rm -rf /opt/spike/lib + cd /opt && tar -czvf spike.tar.gz spike/ + + - name: Store Spike binaries + uses: actions/upload-artifact@v3 + with: + name: spike + path: /opt/*.tar.gz + retention-days: 1 + + veer-iss: + name: Build VeeR-ISS + runs-on: ubuntu-latest + env: + CCACHE_DIR: "/opt/veer-el2/.cache/" + DEBIAN_FRONTEND: "noninteractive" + + steps: + - name: Install prerequisities + run: | + sudo apt -qqy update && sudo apt -qqy --no-install-recommends install \ + git build-essential ccache libboost-all-dev + + - name: Create Cache Timestamp + id: cache_timestamp + uses: nanzm/get-time-action@v1.1 + with: + format: 'YYYY-MM-DD-HH-mm-ss' + + - name: Setup cache + uses: actions/cache@v2 + timeout-minutes: 3 + continue-on-error: true + with: + path: "/opt/veer-el2/.cache/" + key: cache_veer-iss_${{ steps.cache_timestamp.outputs.time }} + restore-keys: cache_veer-iss_ + + - name: Build VeeR-ISS + run: | + git clone https://github.com/chipsalliance/VeeR-ISS veer-iss + export CC="ccache gcc" + export CXX="ccache g++" + pushd veer-iss + git checkout 666c94e + make -j`nproc` + mkdir -p /opt/veer-iss + cp build-Linux/whisper /opt/veer-iss/ + popd + cd /opt && tar -czvf veer-iss.tar.gz veer-iss/ + + - name: Store VeeR-ISS binaries + uses: actions/upload-artifact@v3 + with: + name: veer-iss + path: /opt/*.tar.gz + retention-days: 1 + + tests: + name: Run RISC-V DV tests + runs-on: ubuntu-latest + needs: [verilator, spike, veer-iss] + strategy: + fail-fast: false + matrix: + test: + - riscv_arithmetic_basic_test + iss: + - spike + - whisper + env: + DEBIAN_FRONTEND: "noninteractive" + + steps: + - name: Install utils + run: | + sudo apt -qqy update && sudo apt -qqy --no-install-recommends install \ + git cpanminus ccache device-tree-compiler python3-minimal python3-pip \ + libboost-all-dev gcc-riscv64-unknown-elf + sudo cpanm Bit::Vector + + - name: Download Verilator binaries + uses: actions/download-artifact@v3 + with: + name: verilator + path: /opt + + - name: Download Spike binaries + uses: actions/download-artifact@v3 + with: + name: spike + path: /opt + + - name: Download VeeR-ISS binaries + uses: actions/download-artifact@v3 + with: + name: veer-iss + path: /opt + + - name: Unpack binaries + run: | + pushd /opt + tar -zxvf verilator.tar.gz + tar -zxvf spike.tar.gz + tar -zxvf veer-iss.tar.gz + popd + + - name: Setup repository + uses: actions/checkout@v2 + with: + submodules: recursive + path: veer + + - name: Install Python deps + run: | + pip install -r veer/third_party/riscv-dv/requirements.txt + + - name: Create Cache Timestamp + id: cache_timestamp + uses: nanzm/get-time-action@v1.1 + with: + format: 'YYYY-MM-DD-HH-mm-ss' + + - name: Setup cache + uses: actions/cache@v2 + timeout-minutes: 3 + continue-on-error: true + with: + path: "/opt/veer-el2/.cache/" + key: cache_tests_${{ steps.cache_timestamp.outputs.time }} + restore-keys: cache_tests_ + + - name: Run test + run: | + export PATH=/opt/verilator/bin:$PATH + export PATH=/opt/veer-iss:$PATH + export RV_ROOT=`realpath veer` + export RISCV_GCC=riscv64-unknown-elf-gcc + export RISCV_OBJCOPY=riscv64-unknown-elf-objcopy + export SPIKE_PATH=/opt/spike/bin + export WHISPER_ISS=/opt/veer-iss/whisper + + ${RISCV_GCC} --version + + pushd ${RV_ROOT} + cd tools/riscv-dv && make -j`nproc` RISCV_DV_TEST=${{ matrix.test }} RISCV_DV_ISS=${{ matrix.iss }} RISCV_DV_ITER=3 run + popd + + - name: Pack artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: artifacts-${{ matrix.test }} + path: veer/tools/riscv-dv/work/test_* diff --git a/.gitmodules b/.gitmodules index bbb77d9c518..bc3f4fcced0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "third-party/picolibc"] path = third_party/picolibc url = https://github.com/picolibc/picolibc +[submodule "third_party/riscv-dv"] + path = third_party/riscv-dv + url = https://github.com/antmicro/riscv-dv diff --git a/testbench/tb_top.sv b/testbench/tb_top.sv index b8e31d40a30..a9665282064 100644 --- a/testbench/tb_top.sv +++ b/testbench/tb_top.sv @@ -105,6 +105,10 @@ module tb_top ( input bit core_clk ); logic [4:0] wb_dest; logic [31:0] wb_data; + logic wb_csr_valid; + logic [11:0] wb_csr_dest; + logic [31:0] wb_csr_data; + `ifdef RV_BUILD_AXI4 //-------------------------- LSU AXI signals-------------------------- // AXI Write Channels @@ -350,9 +354,12 @@ module tb_top ( input bit core_clk ); // trace monitor always @(posedge core_clk) begin - wb_valid <= `DEC.dec_i0_wen_r; - wb_dest <= `DEC.dec_i0_waddr_r; - wb_data <= `DEC.dec_i0_wdata_r; + wb_valid <= `DEC.dec_i0_wen_r; + wb_dest <= `DEC.dec_i0_waddr_r; + wb_data <= `DEC.dec_i0_wdata_r; + wb_csr_valid <= `DEC.dec_csr_wen_r; + wb_csr_dest <= `DEC.dec_csr_wraddr_r; + wb_csr_data <= `DEC.dec_csr_wrdata_r; if (trace_rv_i_valid_ip) begin $fwrite(tp,"%b,%h,%h,%0h,%0h,3,%b,%h,%h,%b\n", trace_rv_i_valid_ip, 0, trace_rv_i_address_ip, 0, trace_rv_i_insn_ip,trace_rv_i_exception_ip,trace_rv_i_ecause_ip, @@ -360,18 +367,19 @@ module tb_top ( input bit core_clk ); // Basic trace - no exception register updates // #1 0 ee000000 b0201073 c 0b02 00000000 commit_count++; - $fwrite (el, "%10d : %8s 0 %h %h%13s ; %s\n", cycleCnt, $sformatf("#%0d",commit_count), + $fwrite (el, "%10d : %8s 0 %h %h%13s %14s ; %s\n", cycleCnt, $sformatf("#%0d",commit_count), trace_rv_i_address_ip, trace_rv_i_insn_ip, - (wb_dest !=0 && wb_valid)? $sformatf("%s=%h", abi_reg[wb_dest], wb_data) : " ", + (wb_dest !=0 && wb_valid)? $sformatf("%s=%h", abi_reg[wb_dest], wb_data) : " ", + (wb_csr_valid)? $sformatf("c%h=%h", wb_csr_dest, wb_csr_data) : " ", dasm(trace_rv_i_insn_ip, trace_rv_i_address_ip, wb_dest & {5{wb_valid}}, wb_data) ); end if(`DEC.dec_nonblock_load_wen) begin - $fwrite (el, "%10d : %32s=%h ; nbL\n", cycleCnt, abi_reg[`DEC.dec_nonblock_load_waddr], `DEC.lsu_nonblock_load_data); + $fwrite (el, "%10d : %32s=%h ; nbL\n", cycleCnt, abi_reg[`DEC.dec_nonblock_load_waddr], `DEC.lsu_nonblock_load_data); tb_top.gpr[0][`DEC.dec_nonblock_load_waddr] = `DEC.lsu_nonblock_load_data; end if(`DEC.exu_div_wren) begin - $fwrite (el, "%10d : %32s=%h ; nbD\n", cycleCnt, abi_reg[`DEC.div_waddr_wb], `DEC.exu_div_result); + $fwrite (el, "%10d : %32s=%h ; nbD\n", cycleCnt, abi_reg[`DEC.div_waddr_wb], `DEC.exu_div_result); tb_top.gpr[0][`DEC.div_waddr_wb] = `DEC.exu_div_result; end end @@ -422,7 +430,7 @@ module tb_top ( input bit core_clk ); $readmemh("program.hex", imem.mem); tp = $fopen("trace_port.csv","w"); el = $fopen("exec.log","w"); - $fwrite (el, "// Cycle : #inst 0 pc opcode reg=value ; mnemonic\n"); + $fwrite (el, "// Cycle : #inst 0 pc opcode reg=value csr=value ; mnemonic\n"); fd = $fopen("console.log","w"); commit_count = 0; preload_dccm(); diff --git a/third_party/riscv-dv b/third_party/riscv-dv new file mode 160000 index 00000000000..d30ac5eb8c5 --- /dev/null +++ b/third_party/riscv-dv @@ -0,0 +1 @@ +Subproject commit d30ac5eb8c5b523c54314716b7f578d6e859a130 diff --git a/tools/riscv-dv/Makefile b/tools/riscv-dv/Makefile new file mode 100644 index 00000000000..5e31f2606e8 --- /dev/null +++ b/tools/riscv-dv/Makefile @@ -0,0 +1,117 @@ +RISCV_DV_PATH = $(RV_ROOT)/third_party/riscv-dv +RISCV_DV_ISS ?= spike +RISCV_DV_TEST ?= riscv_arithmetic_basic_test +RISCV_DV_ITER ?= 1 +RISCV_DV_BATCH ?= 1 + +WORK_DIR ?= work +TEST_DIR = $(WORK_DIR)/test_$(RISCV_DV_TEST) +SIM_DIR = $(TEST_DIR)/hdl_sim + +VEER_TARGET = default +VEER_CONF = -set build_axi4 \ + -set reset_vec=0x80000000# \ + -set inst_access_enable0=1 \ + -set inst_access_addr0=0x00000000 \ + -set inst_access_mask0=0x001fffff \ + -set data_access_enable0=1 \ + -set data_access_addr0=0x00000000 \ + -set data_access_mask0=0x001fffff + +VERILATOR = verilator +VERILATOR_CFLAGS= "-std=c++11" +VERILATOR_INC = -I$(WORK_DIR) -I$(RV_ROOT)/testbench +VERILATOR_EXE = $(RV_ROOT)/testbench/test_tb_top.cpp + +HDL_FILES = $(WORK_DIR)/common_defines.vh \ + $(WORK_DIR)/el2_pdef.vh \ + $(RV_ROOT)/testbench/tb_top.sv \ + $(RV_ROOT)/testbench/ahb_sif.sv \ + $(RV_ROOT)/design/include/el2_def.sv + +all: + @echo "Use 'make run'" + +# Directory rules +$(WORK_DIR): + mkdir -p $@ + +$(TEST_DIR): + mkdir -p $@ + +# VeeR config +$(WORK_DIR)/defines.h: | $(WORK_DIR) + BUILD_PATH=$(WORK_DIR) $(RV_ROOT)/configs/veer.config -target=$(VEER_TARGET) $(VEER_CONF) + echo '`undef RV_ASSERT_ON' >> $(WORK_DIR)/common_defines.vh + +# Verilated testbench rules +$(WORK_DIR)/verilator/Vtb_top.mk: $(WORK_DIR)/defines.h + $(VERILATOR) --cc -CFLAGS $(VERILATOR_CFLAGS) $(VERILATOR_INC) \ + $(HDL_FILES) -f $(RV_ROOT)/testbench/flist --top-module tb_top \ + -exe $(VERILATOR_EXE) -Wno-WIDTH -Wno-UNOPTFLAT --autoflush \ + -Mdir $(WORK_DIR)/verilator + +$(WORK_DIR)/verilator/Vtb_top: $(WORK_DIR)/verilator/Vtb_top.mk + $(MAKE) -C $(WORK_DIR)/verilator -f Vtb_top.mk OPT_FAST="-O3" + +# Code generation, compilation and ISS simulation via RISC-V DV flow +$(TEST_DIR)/generate.log: | $(TEST_DIR) + # Generate + PYTHONPATH=$(RISCV_DV_PATH)/pygen python3 $(RISCV_DV_PATH)/run.py --simulator pyflow \ + --test $(RISCV_DV_TEST) --iss $(RISCV_DV_ISS) \ + --iterations $(RISCV_DV_ITER) --batch_size $(RISCV_DV_BATCH) \ + --isa rv32imc --mabi ilp32 --steps gen -v -o $(TEST_DIR) 2>&1 | tee $(TEST_DIR)/generate.log + + # Patch the code + find $(TEST_DIR)/asm_test -name "*.S" -exec python3 code_fixup.py -i {} -o {} \; + + # Compile, simulate + PYTHONPATH=$(RISCV_DV_PATH)/pygen python3 $(RISCV_DV_PATH)/run.py --simulator pyflow \ + --test $(RISCV_DV_TEST) --iss $(RISCV_DV_ISS) \ + --iterations $(RISCV_DV_ITER) --batch_size $(RISCV_DV_BATCH) \ + --isa rv32imc --mabi ilp32 --steps gcc_compile,iss_sim -v -o $(TEST_DIR) 2>&1 | tee -a $(TEST_DIR)/generate.log + +$(TEST_DIR)/asm_test/%.hex: $(TEST_DIR)/asm_test/%.o + $(RISCV_OBJCOPY) -O verilog $< $@ + +# HDL simulation +$(SIM_DIR)/%.log: $(TEST_DIR)/asm_test/%.hex $(WORK_DIR)/verilator/Vtb_top + mkdir -p $(basename $@) + cp $< $(basename $@)/program.hex + cd $(basename $@) && $(abspath $(WORK_DIR)/verilator/Vtb_top) + mv $(basename $@)/exec.log $@ + +# Log conversion rules +$(TEST_DIR)/spike_sim/%.csv: $(TEST_DIR)/spike_sim/%.log + python3 $(RISCV_DV_PATH)/scripts/spike_log_to_trace_csv.py --log $< --csv $@ + +$(TEST_DIR)/whisper_sim/%.csv: $(TEST_DIR)/whisper_sim/%.log + python3 $(RISCV_DV_PATH)/scripts/whisper_log_trace_csv.py --log $< --csv $@ + +$(SIM_DIR)/%.csv: $(SIM_DIR)/%.log veer_log_to_trace_csv.py + PYTHONPATH=$(RISCV_DV_PATH)/scripts python3 veer_log_to_trace_csv.py --log $< --csv $@ + +# Trace comparison +$(TEST_DIR)/comp_%.log: $(TEST_DIR)/$(RISCV_DV_ISS)_sim/%.csv $(SIM_DIR)/%.csv + rm -rf $@ + python3 $(RISCV_DV_PATH)/scripts/instr_trace_compare.py \ + --csv_file_1 $(word 1, $^) --csv_name_1 ISS --csv_file_2 $(word 2, $^) --csv_name_2 HDL \ + --in_order_mode 1 --log $@ --verbose 10 --mismatch_print_limit 20 + tail -n 2 $@ + +run: + # Run RISC-V DV + $(MAKE) $(TEST_DIR)/generate.log + # Run HDL simulation(s) and trace comparison + find $(TEST_DIR)/ -name "sim_*.log" | sed 's/sim_/comp_/g' | xargs $(MAKE) + # Check for errors + for F in $(TEST_DIR)/comp_*.log; do grep "\[PASSED\]" $$F; if [ $$? -ne 0 ]; then exit 255; fi; done + +clean: + rm -rf $(TEST_DIR) + +fullclean: + rm -rf $(WORK_DIR) + +.PHONY: all run generate clean fullclean compare +.SECONDARY: diff --git a/tools/riscv-dv/code_fixup.py b/tools/riscv-dv/code_fixup.py new file mode 100644 index 00000000000..cd982544dd9 --- /dev/null +++ b/tools/riscv-dv/code_fixup.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +import argparse +import re + +# ============================================================================= + +class AssemblyLine: + """ + Simple assembly line representation + """ + + RE_INSTR = re.compile(r"(?P\S+)\s+(?P.*)") + + def __init__(self, text): + self.text = text + self.mnemonic = None + self.operands = None + + # Strip label if any + if ":" in text: + text = text.split(":", maxsplit=1)[1] + + # Strip comment if any + if "#" in text: + text = text.split("#", maxsplit=1)[0] + + # Get instruction and operands + m = self.RE_INSTR.match(text.strip()) + if m is not None: + + if m.group("mnemonic")[0] == ".": + return + + self.mnemonic = m.group("mnemonic").lower() + self.operands = [op.strip() for op in m.group("operands").split()] + + def __str__(self): + return self.text + +# ============================================================================= + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-i", + type=str, + required=True, + help="Input assembly file" + ) + parser.add_argument( + "-o", + type=str, + required=True, + help="Output assembly file" + ) + + args = parser.parse_args() + + max_nops = 10 + + # Read and parse + with open(args.i, "r") as fp: + inp_lines = [AssemblyLine(l) for l in fp.readlines()] + + # Identify a delayed write instruction followed by another one which writes + # to the same register + out_lines = [] + for i in range(len(inp_lines)): + line = inp_lines[i] + out_lines.append(line) + + # Bypass + if not line.mnemonic: + continue + + # Check if it is a delayed write. If not then bypass + is_delayed = line.mnemonic in ["div", "divu", "rem", "remu", "lw"] + if not is_delayed: + continue + + # Get next 2 instructions + following = [] + for j in range(i+1, len(inp_lines)): + if inp_lines[j].mnemonic is not None: + following.append(inp_lines[j]) + if len(following) >= 2: + break + + # If any of the instructions targets the same register insert NOPs + dst = line.operands[0] + for j, l in enumerate(following): + if l.operands and l.operands[0] == dst: + nops = max(0, max_nops - j) + for _ in range(nops): + out_lines.append(" " * 18 + "nop # FIXME: A fixup not to make VeeR cancel a delayed write\n") + break + + # Write + with open(args.o, "w") as fp: + for l in out_lines: + fp.write(str(l)) + + +if __name__ == "__main__": + main() diff --git a/tools/riscv-dv/veer_log_to_trace_csv.py b/tools/riscv-dv/veer_log_to_trace_csv.py new file mode 100644 index 00000000000..8c905dcb04d --- /dev/null +++ b/tools/riscv-dv/veer_log_to_trace_csv.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +import argparse +import re + +from riscv_trace_csv import RiscvInstructionTraceEntry, RiscvInstructionTraceCsv + +# ============================================================================= + +INSTR_RE = re.compile(r"^\s*(?P[0-9]+)\s+:\s+#(?P[0-9]+)\s+0\s+" + r"(?P[0-9a-f]+)\s+(?P[0-9a-f]+)\s+" + r"((?P[^=;]+)=(?P[0-9a-f]+))?\s+" + r"((?P[^=;]+)=(?P[0-9a-f]+))?" + r"\s+;\s+(?P.*)") + +NB_RE = re.compile(r"^\s*(?P[0-9]+)\s+:\s+" + r"(?P[^=;]+)=(?P[0-9a-f]+)" + r"\s+;\s+(?P(nbL|nbD))") + +# ============================================================================= + +def parse_log(file_name): + """ + Parses VeeR-EL2 execution log generated by HDL simulation. + + The core is in-order however, due to pipelined implementation certain + instructions may have an effect in different clock cycle than they are + executed. The testbench trace handes this by emitting special "nbL" and + "nbD" entries which need to be correlated with the actual instruction. + + Most of the logic of this parser does exactly that. Every trace entry is + put into a temporary queue. Whenever a "nbL"/"nbD" is encountered, the + queue is searched for a matching counterpart. This happens in the opposite + way as well eg. when a "div" is encountered the queue is searched for "nbD" + Once an entry is found, relevant data is filled in. + + Entires are poped of the queue only when they contain all the information + for the complete trace. + """ + + # Read the log + with open(file_name, "r") as fp: + lines = fp.readlines() + + data = [] + queue = [] + + for line in lines: + line = line.strip() + + # Instruction + match = INSTR_RE.match(line) + if match is not None: + groups = match.groupdict() + + gpr = None + csr = None + if groups["reg"] and groups["val"]: + gpr = ("{}:{}".format(groups["reg"], groups["val"])) + if groups["csr"] and groups["csr_val"]: + csr = ("{}:{}".format(groups["csr"], groups["csr_val"])) + + fields = groups["mnemonic"].split() + mnemonic = fields[0] + operands = fields[1].split(",") if len(fields) > 1 else [] + + entry = None + + # Stop on ecall + if mnemonic == "ecall": + break + + # Delayed effect, search the queue + if gpr is None and mnemonic in ["lw", "div", "divu", "rem", "remu"]: + + # Skip if targets x0 (zero) which makes no sense + if operands[0] == "zero": + continue + + for ent in reversed(queue): + + if (ent.operand == "nbL" and mnemonic in ["lw"]) or \ + (ent.operand == "nbD" and mnemonic in ["div", "divu", "rem", "remu"]): + + assert len(operands), line + assert len(ent.gpr), ent.get_trace_string() + + reg, val = ent.gpr[0].split(":") # FIXME: Assuming single GPR + if reg == operands[0]: + entry = ent + break + + # Enqueue or not + enqueue = entry is None and (gpr is not None or mnemonic in \ + ["div", "divu", "rem", "remu", "lw"]) + + # Entry not found in the queue, create it + if not entry: + entry = RiscvInstructionTraceEntry() + + # Fill data + entry.pc = groups["pc"] + entry.binary = groups["opc"] + entry.operand = groups["mnemonic"] + entry.mode = "0" # TODO + + # Append GPR if any + if gpr: + entry.gpr.append(gpr) + if csr: + entry.csr.append(csr) + + # Enqueue + if enqueue: + queue.append(entry) + + # nbL / nbD + match = NB_RE.match(line) + if match is not None: + groups = match.groupdict() + + assert groups["reg"] and groups["val"], line + gpr = ("{}:{}".format(groups["reg"], groups["val"])) + + # Find an existing nbL/nbD entry in the queue. Match destination GPR + for entry in reversed(queue): + + fields = entry.operand.split() + mnemonic = fields[0] + operands = fields[1].split(",") if len(fields) > 1 else [] + + if (groups["mnemonic"] == "nbL" and mnemonic in ["lw"]) or \ + (groups["mnemonic"] == "nbD" and mnemonic in ["div", "divu", "rem", "remu"]): + assert len(operands), entry + if groups["reg"] == operands[0]: + entry.gpr.append(gpr) + break + + # Add a new entry + else: + entry = RiscvInstructionTraceEntry() + entry.operand = groups["mnemonic"] + entry.gpr.append(gpr) + + queue.append(entry) + + # Dequeue entries that have all they need. Stop at the first one which + # is missing something. + while len(queue): + entry = queue[0] + + # Cannot dequeue, break + if not entry.pc or not entry.gpr: + break + + # Pop + data.append(entry) + queue = queue[1:] + + # Safeguard + if len(queue) >= 10: + print("ERROR: Malformed trace, the queue grew too much") + for entry in reversed(queue): + print("", entry.get_trace_string()) + assert False + + return data + + +def write_csv(file_name, data): + """ + Writes the trace to CSV + """ + + with open(file_name, "w") as fp: + + writer = RiscvInstructionTraceCsv(fp) + writer.start_new_trace() + + for entry in data: + writer.write_trace_entry(entry) + +# ============================================================================= + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--log", + type=str, + required=True, + help="HDL simulation trace log" + ) + parser.add_argument( + "--csv", + type=str, + required=True, + help="Output CSV file" + ) + + args = parser.parse_args() + + # Parse log + data = parse_log(args.log) + + # Write CSV + write_csv(args.csv, data) + +if __name__ == "__main__": + main()