From 0a643a1fb05c714cba83d10cad157469b6579400 Mon Sep 17 00:00:00 2001 From: Michael Schaffner Date: Thu, 18 Jan 2024 16:12:27 -0800 Subject: [PATCH] Update lowrisc_ibex to lowRISC/ibex@123d46b4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update code from upstream repository https://github.com/lowRISC/ibex.git to revision 123d46b4d60068502f80c117772a279db12f5af7 * [dv] Fix paths in `merge_cov.py` (Sᴜᴘᴇʀ Lᴇᴇ) * Tweak questa timescale argument (Harry Callahan) * Fixup the questa build/sim command templates in rtl_simulation.yaml (Harry Callahan) * [rtl] Fix FI vulnerability in RF (Pascal Nasahl) * [doc] Update cosim version (Pascal Nasahl) * [util] Update check_tool_requirements.py (Gary Guo) * [rtl] Avoid name collision in ibex_pmp.sv (Rupert Swarbrick) * [dv] Fix performance counter printing in simple system (Rupert Swarbrick) * Fix spelling of separator (Rupert Swarbrick) * [dv] Add an extra key to common_project_cfg.hjson (Rupert Swarbrick) * [verilator] Slight refactor in ibex_tracer to avoid BLKSEQ warning (Rupert Swarbrick) * [verilator] Waive MULTIDRIVEN warning in ibex_tracer.sv (Rupert Swarbrick) Signed-off-by: Michael Schaffner --- hw/vendor/lowrisc_ibex.lock.hjson | 2 +- .../doc/03_reference/security.rst | 9 ++ .../doc/03_reference/verification.rst | 3 +- .../dv/uvm/common_project_cfg.hjson | 1 + .../dv/uvm/core_ibex/common/prim/prim_and2.sv | 27 ++++ .../lowrisc_ibex/dv/uvm/core_ibex/ibex_dv.f | 4 + .../dv/uvm/core_ibex/scripts/merge_cov.py | 8 +- .../uvm/core_ibex/scripts/report_lib/util.py | 2 +- .../dv/uvm/core_ibex/tb/core_ibex_tb_top.sv | 8 ++ .../dv/uvm/core_ibex/yaml/rtl_simulation.yaml | 6 +- .../dv/verilator/pcount/cpp/ibex_pcounts.cc | 35 +++++- hw/vendor/lowrisc_ibex/ibex_top.core | 2 + .../lowrisc_ibex/lint/verilator_waiver.vlt | 7 ++ hw/vendor/lowrisc_ibex/rtl/ibex_pmp.sv | 19 +-- .../lowrisc_ibex/rtl/ibex_register_file_ff.sv | 111 +++++++++++++++- .../rtl/ibex_register_file_fpga.sv | 118 ++++++++++++++++-- .../rtl/ibex_register_file_latch.sv | 111 +++++++++++++++- hw/vendor/lowrisc_ibex/rtl/ibex_top.sv | 20 +-- hw/vendor/lowrisc_ibex/rtl/ibex_tracer.sv | 56 +++++---- .../util/check_tool_requirements.py | 19 +-- 20 files changed, 485 insertions(+), 83 deletions(-) create mode 100644 hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/common/prim/prim_and2.sv diff --git a/hw/vendor/lowrisc_ibex.lock.hjson b/hw/vendor/lowrisc_ibex.lock.hjson index 976f90d0858d3f..5f599a9044ee37 100644 --- a/hw/vendor/lowrisc_ibex.lock.hjson +++ b/hw/vendor/lowrisc_ibex.lock.hjson @@ -9,6 +9,6 @@ upstream: { url: https://github.com/lowRISC/ibex.git - rev: bac72d96ec713a68ed07f3e1cb341ffbf40b8228 + rev: 123d46b4d60068502f80c117772a279db12f5af7 } } diff --git a/hw/vendor/lowrisc_ibex/doc/03_reference/security.rst b/hw/vendor/lowrisc_ibex/doc/03_reference/security.rst index a66dd709568ae4..52a62af50b2dfe 100644 --- a/hw/vendor/lowrisc_ibex/doc/03_reference/security.rst +++ b/hw/vendor/lowrisc_ibex/doc/03_reference/security.rst @@ -90,6 +90,15 @@ When Ibex is configured with the SecureIbex parameter, the write enable signal i This can be useful to detect fault injection attacks. No attempt is made to correct detected errors, but an internal major alert is signaled for the system to take action. +Register file read addresses glitch detection +------------------------------------------- + +When Ibex is configured with the SecureIbex parameter, the read addresses provided to the register file are converted to one-hot encoded signals, and a one-hot encoded MUX is used to select the register to read from. +By using one-hot encoding checkers, glitches in the one-hot encoded signals are detected. +Bit-flips inside the plain read addresses before the one-hot conversion happens are detected by the dual core lockstep. +This can be useful to detect fault injection attacks. +No attempt is made to correct detected errors, but an internal major alert is signaled for the system to take action. + ICache ECC ---------- diff --git a/hw/vendor/lowrisc_ibex/doc/03_reference/verification.rst b/hw/vendor/lowrisc_ibex/doc/03_reference/verification.rst index a4511fb3658619..361597160cd9c4 100644 --- a/hw/vendor/lowrisc_ibex/doc/03_reference/verification.rst +++ b/hw/vendor/lowrisc_ibex/doc/03_reference/verification.rst @@ -111,8 +111,7 @@ In order to run the co-simulation flow, you'll need: + Some custom CSRs + Custom NMI behavior - Ibex verification should work with the Spike version that is tagged as ``ibex-cosim-v0.3``. - Other, later, versions called ``ibex-cosim-v*`` may also work but there's no guarantee of backwards compatibility. + Ibex verification should work with the Spike version that is tagged as ``ibex-cosim-v0.5``. Spike must be built with the ``--enable-commitlog`` and ``--enable-misaligned`` options. ``--enable-commitlog`` is needed to produce log output to track the instructions that were executed. diff --git a/hw/vendor/lowrisc_ibex/dv/uvm/common_project_cfg.hjson b/hw/vendor/lowrisc_ibex/dv/uvm/common_project_cfg.hjson index f91771b3d801d9..ef0499fe055bf1 100644 --- a/hw/vendor/lowrisc_ibex/dv/uvm/common_project_cfg.hjson +++ b/hw/vendor/lowrisc_ibex/dv/uvm/common_project_cfg.hjson @@ -5,6 +5,7 @@ project: ibex // These keys are expected by dvsim.py, so we have to set them to something. + book: bogus.book.domain doc_server: bogus.doc.server results_server: bogus.results.server results_html_name: report.html diff --git a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/common/prim/prim_and2.sv b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/common/prim/prim_and2.sv new file mode 100644 index 00000000000000..157d4dba67ee10 --- /dev/null +++ b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/common/prim/prim_and2.sv @@ -0,0 +1,27 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// Abstract primitives wrapper. +// +// This file is a stop-gap until the DV file list is generated by FuseSoC. +// Its contents are taken from the file which would be generated by FuseSoC. +// https://github.com/lowRISC/ibex/issues/893 + +module prim_and2 #( + parameter int Width = 1 +) ( + input [Width-1:0] in0_i, + input [Width-1:0] in1_i, + output logic [Width-1:0] out_o +); + +if (1) begin : gen_generic + prim_generic_and2 #( + .Width(Width) + ) u_impl_generic ( + .* + ); +end + +endmodule diff --git a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/ibex_dv.f b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/ibex_dv.f index fa79191b266035..f6c54794bd852b 100644 --- a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/ibex_dv.f +++ b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/ibex_dv.f @@ -45,6 +45,8 @@ ${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_clock_mux2.sv ${LOWRISC_IP_DIR}/ip/prim_generic/rtl/prim_generic_flop.sv ${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_flop.sv +${LOWRISC_IP_DIR}/ip/prim_generic/rtl/prim_generic_and2.sv +${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_and2.sv // Shared lowRISC code ${LOWRISC_IP_DIR}/ip/prim/rtl/prim_cipher_pkg.sv @@ -64,6 +66,8 @@ ${LOWRISC_IP_DIR}/ip/prim/rtl/prim_secded_72_64_enc.sv ${LOWRISC_IP_DIR}/ip/prim/rtl/prim_secded_72_64_dec.sv ${LOWRISC_IP_DIR}/ip/prim/rtl/prim_onehot_check.sv +${LOWRISC_IP_DIR}/ip/prim/rtl/prim_onehot_enc.sv +${LOWRISC_IP_DIR}/ip/prim/rtl/prim_onehot_mux.sv ${LOWRISC_IP_DIR}/ip/prim/rtl/prim_mubi_pkg.sv // ibex CORE RTL files diff --git a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/scripts/merge_cov.py b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/scripts/merge_cov.py index 35e26fed671b68..6c58c2d2bd487e 100755 --- a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/scripts/merge_cov.py +++ b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/scripts/merge_cov.py @@ -42,11 +42,11 @@ def merge_cov_vcs(md: RegressionMetadata, cov_dirs: Set[pathlib.Path]) -> int: logging.info("Generating merged coverage directory") cmd = (['urg', '-full64', '-format', 'both', - '-dbname', str(md.cov_dir/'merged.vdb'), - '-report', str(md.cov_dir/'report'), - '-log', str(md.cov_dir/'merge.log'), + '-dbname', str(md.dir_cov/'merged.vdb'), + '-report', str(md.dir_cov/'report'), + '-log', str(md.dir_cov/'merge.log'), '-dir'] + - list(cov_dirs)) + [str(cov_dir) for cov_dir in list(cov_dirs)]) return run_one(md.verbose, cmd, redirect_stdstreams='/dev/null') diff --git a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/scripts/report_lib/util.py b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/scripts/report_lib/util.py index c94698d49f9838..f6a00756c2d096 100644 --- a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/scripts/report_lib/util.py +++ b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/scripts/report_lib/util.py @@ -96,7 +96,7 @@ def parse_xcelium_cov_report(cov_report: str) -> Dict[str, Dict[str, Dict[str, i metric_info.append((metric_info_match.group(1), metric_info_match.group(2))) - # Skip header seperator line + # Skip header separator line metrics_start_line = line_no + 2 if metrics_start_line == -1: diff --git a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv index cf1f13b176e325..4e758489720673 100644 --- a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv +++ b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/tb/core_ibex_tb_top.sv @@ -336,4 +336,12 @@ module core_ibex_tb_top; assign dut.u_ibex_top.gen_regfile_ff.register_file_i.gen_wren_check.u_prim_onehot_check. unused_assert_connected = 1; end + + // Disable the assertion for onhot check in case RdataMuxCheck (set by SecureIbex) is enabled. + if (SecureIbex) begin : gen_disable_rdata_mux_check + assign dut.u_ibex_top.gen_regfile_ff.register_file_i.gen_rdata_mux_check. + u_prim_onehot_check_raddr_a.unused_assert_connected = 1; + assign dut.u_ibex_top.gen_regfile_ff.register_file_i.gen_rdata_mux_check. + u_prim_onehot_check_raddr_b.unused_assert_connected = 1; + end endmodule diff --git a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/yaml/rtl_simulation.yaml b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/yaml/rtl_simulation.yaml index a973e7adc448fd..a193889f0b8d09 100644 --- a/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/yaml/rtl_simulation.yaml +++ b/hw/vendor/lowrisc_ibex/dv/uvm/core_ibex/yaml/rtl_simulation.yaml @@ -99,9 +99,9 @@ -mfcu -cuname design_cuname +define+UVM_REGEX_NO_DPI +define+UVM - -timescale \"1 ns / 1 ps \" + -timescale 1ns/1ps -writetoplevels /top.list - -l / + -l sim: cmd: @@ -118,7 +118,7 @@ +UVM_VERBOSITY=UVM_LOW +bin= +ibex_tracer_file_base= - -l /sim.log + -l cov_opts: >- -do "coverage save -onexit /cov.ucdb;" diff --git a/hw/vendor/lowrisc_ibex/dv/verilator/pcount/cpp/ibex_pcounts.cc b/hw/vendor/lowrisc_ibex/dv/verilator/pcount/cpp/ibex_pcounts.cc index 733dcf09bf100d..6924ee5261fabe 100644 --- a/hw/vendor/lowrisc_ibex/dv/verilator/pcount/cpp/ibex_pcounts.cc +++ b/hw/vendor/lowrisc_ibex/dv/verilator/pcount/cpp/ibex_pcounts.cc @@ -10,6 +10,7 @@ #include extern "C" { +extern unsigned int mhpmcounter_num(); extern unsigned long long mhpmcounter_get(int index); } @@ -32,24 +33,48 @@ const std::vector ibex_counter_names = { "Multiply Wait", "Divide Wait"}; +static bool has_hpm_counter(int index) { + // The "cycles" and "instructions retired" counters are special and always + // exist. + if (index == 0 || index == 2) + return true; + + // The "NONE" counter is a placeholder. The space reserves an index that was + // once the "MTIME" CSR, but now is unused. Return false: there's no real HPM + // counter at index 1. + if (index == 1) + return false; + + // Otherwise, a counter exists if the index is strictly less than + // the MHPMCounterNum parameter that got passed to the + // ibex_cs_registers module. + return index < mhpmcounter_num(); +} + std::string ibex_pcount_string(bool csv) { - char seperator = csv ? ',' : ':'; + char separator = csv ? ',' : ':'; std::string::size_type longest_name_length; if (!csv) { longest_name_length = 0; - for (const std::string &counter_name : ibex_counter_names) { - longest_name_length = std::max(longest_name_length, counter_name.length()); + for (int i = 0; i < ibex_counter_names.size(); ++i) { + if (has_hpm_counter(i)) { + longest_name_length = + std::max(longest_name_length, ibex_counter_names[i].length()); + } } - // Add 1 to always get at least once space after the seperator + // Add 1 to always get at least once space after the separator longest_name_length++; } std::stringstream pcount_ss; for (int i = 0; i < ibex_counter_names.size(); ++i) { - pcount_ss << ibex_counter_names[i] << seperator; + if (!has_hpm_counter(i)) + continue; + + pcount_ss << ibex_counter_names[i] << separator; if (!csv) { int padding = longest_name_length - ibex_counter_names[i].length(); diff --git a/hw/vendor/lowrisc_ibex/ibex_top.core b/hw/vendor/lowrisc_ibex/ibex_top.core index db33a72f840eaa..e8e5d011e9f843 100644 --- a/hw/vendor/lowrisc_ibex/ibex_top.core +++ b/hw/vendor/lowrisc_ibex/ibex_top.core @@ -10,11 +10,13 @@ filesets: depend: - lowrisc:ibex:ibex_pkg - lowrisc:ibex:ibex_core + - lowrisc:prim:and2 - lowrisc:prim:buf - lowrisc:prim:clock_mux2 - lowrisc:prim:flop - lowrisc:prim:ram_1p_scr - lowrisc:prim:onehot_check + - lowrisc:prim:onehot files: - rtl/ibex_register_file_ff.sv # generic FF-based - rtl/ibex_register_file_fpga.sv # FPGA diff --git a/hw/vendor/lowrisc_ibex/lint/verilator_waiver.vlt b/hw/vendor/lowrisc_ibex/lint/verilator_waiver.vlt index b7c952ca746614..d03f9bcb486fe7 100644 --- a/hw/vendor/lowrisc_ibex/lint/verilator_waiver.vlt +++ b/hw/vendor/lowrisc_ibex/lint/verilator_waiver.vlt @@ -64,6 +64,13 @@ lint_off -rule UNUSED -file "*/rtl/ibex_compressed_decoder.sv" -match "*rst_ni*" lint_off -rule UNUSED -file "*/rtl/ibex_decoder.sv" -match "*rst_ni*" lint_off -rule UNUSED -file "*/rtl/ibex_branch_predict.sv" -match "*rst_ni*" +// Don't worry about the fact that decoded_str and data_accessed appear to be +// written by multiple processes that might race with each other. They can't +// race with each other (everything is a descendent of the always_comb block), +// but Verilator doesn't notice this. +lint_off -rule MULTIDRIVEN -file "*/rtl/ibex_tracer.sv" -match "*decoded_str*" +lint_off -rule MULTIDRIVEN -file "*/rtl/ibex_tracer.sv" -match "*data_accessed*" + // Temporary waivers until OpenTitan primitives are lint-clean // https://github.com/lowRISC/opentitan/issues/2313 lint_off -file "*/lowrisc_prim_*/rtl/*.sv" diff --git a/hw/vendor/lowrisc_ibex/rtl/ibex_pmp.sv b/hw/vendor/lowrisc_ibex/rtl/ibex_pmp.sv index 6bda6e03d3dd70..48c3a7ed674c03 100644 --- a/hw/vendor/lowrisc_ibex/rtl/ibex_pmp.sv +++ b/hw/vendor/lowrisc_ibex/rtl/ibex_pmp.sv @@ -51,16 +51,16 @@ module ibex_pmp #( // \--> pmp_req_err_o // Compute permissions checks that apply when MSECCFG.MML is set. Added for Smepmp support. - function automatic logic mml_perm_check(ibex_pkg::pmp_cfg_t csr_pmp_cfg, + function automatic logic mml_perm_check(ibex_pkg::pmp_cfg_t region_csr_pmp_cfg, ibex_pkg::pmp_req_e pmp_req_type, ibex_pkg::priv_lvl_e priv_mode, logic permission_check); logic result = 1'b0; - logic unused_cfg = |csr_pmp_cfg.mode; + logic unused_cfg = |region_csr_pmp_cfg.mode; - if (!csr_pmp_cfg.read && csr_pmp_cfg.write) begin + if (!region_csr_pmp_cfg.read && region_csr_pmp_cfg.write) begin // Special-case shared regions where R = 0, W = 1 - unique case ({csr_pmp_cfg.lock, csr_pmp_cfg.exec}) + unique case ({region_csr_pmp_cfg.lock, region_csr_pmp_cfg.exec}) // Read/write in M, read only in S/U 2'b00: result = (pmp_req_type == PMP_ACC_READ) | @@ -77,14 +77,15 @@ module ibex_pmp #( default: ; endcase end else begin - if (csr_pmp_cfg.read & csr_pmp_cfg.write & csr_pmp_cfg.exec & csr_pmp_cfg.lock) begin + if (region_csr_pmp_cfg.read & region_csr_pmp_cfg.write & + region_csr_pmp_cfg.exec & region_csr_pmp_cfg.lock) begin // Special-case shared read only region when R = 1, W = 1, X = 1, L = 1 result = pmp_req_type == PMP_ACC_READ; end else begin // Otherwise use basic permission check. Permission is always denied if in S/U mode and // L is set or if in M mode and L is unset. result = permission_check & - (priv_mode == PRIV_LVL_M ? csr_pmp_cfg.lock : ~csr_pmp_cfg.lock); + (priv_mode == PRIV_LVL_M ? region_csr_pmp_cfg.lock : ~region_csr_pmp_cfg.lock); end end return result; @@ -105,15 +106,15 @@ module ibex_pmp #( // A wrapper function in which it is decided which form of permission check function gets called function automatic logic perm_check_wrapper(logic csr_pmp_mseccfg_mml, - ibex_pkg::pmp_cfg_t csr_pmp_cfg, + ibex_pkg::pmp_cfg_t region_csr_pmp_cfg, ibex_pkg::pmp_req_e pmp_req_type, ibex_pkg::priv_lvl_e priv_mode, logic permission_check); - return csr_pmp_mseccfg_mml ? mml_perm_check(csr_pmp_cfg, + return csr_pmp_mseccfg_mml ? mml_perm_check(region_csr_pmp_cfg, pmp_req_type, priv_mode, permission_check) : - orig_perm_check(csr_pmp_cfg.lock, + orig_perm_check(region_csr_pmp_cfg.lock, priv_mode, permission_check); endfunction diff --git a/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_ff.sv b/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_ff.sv index 45890db88664d6..53c89e2d34190a 100644 --- a/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_ff.sv +++ b/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_ff.sv @@ -15,6 +15,7 @@ module ibex_register_file_ff #( parameter int unsigned DataWidth = 32, parameter bit DummyInstructions = 0, parameter bit WrenCheck = 0, + parameter bit RdataMuxCheck = 0, parameter logic [DataWidth-1:0] WordZeroVal = '0 ) ( // Clock and Reset @@ -39,7 +40,7 @@ module ibex_register_file_ff #( input logic [DataWidth-1:0] wdata_a_i, input logic we_a_i, - // This indicates whether spurious WE are detected. + // This indicates whether spurious WE or non-one-hot encoded raddr are detected. output logic err_o ); @@ -49,6 +50,8 @@ module ibex_register_file_ff #( logic [DataWidth-1:0] rf_reg [NUM_WORDS]; logic [NUM_WORDS-1:0] we_a_dec; + logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; + always_comb begin : we_a_decoder for (int unsigned i = 0; i < NUM_WORDS; i++) begin we_a_dec[i] = (waddr_a_i == 5'(i)) ? we_a_i : 1'b0; @@ -78,12 +81,12 @@ module ibex_register_file_ff #( .oh_i(we_a_dec_buf), .addr_i(waddr_a_i), .en_i(we_a_i), - .err_o + .err_o(oh_we_err) ); end else begin : gen_no_wren_check logic unused_strobe; assign unused_strobe = we_a_dec[0]; // this is never read from in this case - assign err_o = 1'b0; + assign oh_we_err = 1'b0; end // No flops for R0 as it's hard-wired to 0 @@ -130,8 +133,106 @@ module ibex_register_file_ff #( assign rf_reg[0] = WordZeroVal; end - assign rdata_a_o = rf_reg[raddr_a_i]; - assign rdata_b_o = rf_reg[raddr_b_i]; + if (RdataMuxCheck) begin : gen_rdata_mux_check + // Encode raddr_a/b into one-hot encoded signals. + logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b; + logic [NUM_WORDS-1:0] raddr_onehot_a_buf, raddr_onehot_b_buf; + prim_onehot_enc #( + .OneHotWidth(NUM_WORDS) + ) u_prim_onehot_enc_raddr_a ( + .in_i (raddr_a_i), + .en_i (1'b1), + .out_o (raddr_onehot_a) + ); + + prim_onehot_enc #( + .OneHotWidth(NUM_WORDS) + ) u_prim_onehot_enc_raddr_b ( + .in_i (raddr_b_i), + .en_i (1'b1), + .out_o (raddr_onehot_b) + ); + + // Buffer the one-hot encoded signals so that the checkers + // are not optimized. + prim_buf #( + .Width(NUM_WORDS) + ) u_prim_buf_raddr_a ( + .in_i (raddr_onehot_a), + .out_o(raddr_onehot_a_buf) + ); + + prim_buf #( + .Width(NUM_WORDS) + ) u_prim_buf_raddr_b ( + .in_i (raddr_onehot_b), + .out_o(raddr_onehot_b_buf) + ); + + // SEC_CM: DATA_REG_SW.GLITCH_DETECT + // Check the one-hot encoded signals for glitches. + prim_onehot_check #( + .AddrWidth(ADDR_WIDTH), + .OneHotWidth(NUM_WORDS), + .AddrCheck(1), + // When AddrCheck=1 also EnableCheck needs to be 1. + .EnableCheck(1) + ) u_prim_onehot_check_raddr_a ( + .clk_i, + .rst_ni, + .oh_i (raddr_onehot_a_buf), + .addr_i (raddr_a_i), + // Set enable=1 as address is always valid. + .en_i (1'b1), + .err_o (oh_raddr_a_err) + ); + + prim_onehot_check #( + .AddrWidth(ADDR_WIDTH), + .OneHotWidth(NUM_WORDS), + .AddrCheck(1), + // When AddrCheck=1 also EnableCheck needs to be 1. + .EnableCheck(1) + ) u_prim_onehot_check_raddr_b ( + .clk_i, + .rst_ni, + .oh_i (raddr_onehot_b_buf), + .addr_i (raddr_b_i), + // Set enable=1 as address is always valid. + .en_i (1'b1), + .err_o (oh_raddr_b_err) + ); + + // MUX register to rdata_a/b_o according to raddr_a/b_onehot. + prim_onehot_mux #( + .Width(DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_a_mux ( + .clk_i, + .rst_ni, + .in_i (rf_reg), + .sel_i (raddr_onehot_a), + .out_o (rdata_a_o) + ); + + prim_onehot_mux #( + .Width(DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_b_mux ( + .clk_i, + .rst_ni, + .in_i (rf_reg), + .sel_i (raddr_onehot_b), + .out_o (rdata_b_o) + ); + end else begin : gen_no_rdata_mux_check + assign rdata_a_o = rf_reg[raddr_a_i]; + assign rdata_b_o = rf_reg[raddr_b_i]; + assign oh_raddr_a_err = 1'b0; + assign oh_raddr_b_err = 1'b0; + end + + assign err_o = oh_raddr_a_err || oh_raddr_b_err || oh_we_err; // Signal not used in FF register file logic unused_test_en; diff --git a/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_fpga.sv b/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_fpga.sv index 420b8fe8bc13a1..7a0ae34b151fad 100644 --- a/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_fpga.sv +++ b/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_fpga.sv @@ -16,6 +16,7 @@ module ibex_register_file_fpga #( parameter int unsigned DataWidth = 32, parameter bit DummyInstructions = 0, parameter bit WrenCheck = 0, + parameter bit RdataMuxCheck = 0, parameter logic [DataWidth-1:0] WordZeroVal = '0 ) ( // Clock and Reset @@ -37,7 +38,7 @@ module ibex_register_file_fpga #( input logic [DataWidth-1:0] wdata_a_i, input logic we_a_i, - // This indicates whether spurious WE are detected. + // This indicates whether spurious WE or non-one-hot encoded raddr are detected. output logic err_o ); @@ -47,11 +48,114 @@ module ibex_register_file_fpga #( logic [DataWidth-1:0] mem[NUM_WORDS]; logic we; // write enable if writing to any register other than R0 - // async_read a - assign rdata_a_o = (raddr_a_i == '0) ? '0 : mem[raddr_a_i]; + logic [DataWidth-1:0] mem_o_a, mem_o_b; - // async_read b - assign rdata_b_o = (raddr_b_i == '0) ? '0 : mem[raddr_b_i]; + // WE strobe and one-hot encoded raddr alert. + logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; + assign err_o = oh_raddr_a_err || oh_raddr_b_err || oh_we_err; + + if (RdataMuxCheck) begin : gen_rdata_mux_check + // Encode raddr_a/b into one-hot encoded signals. + logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b; + logic [NUM_WORDS-1:0] raddr_onehot_a_buf, raddr_onehot_b_buf; + prim_onehot_enc #( + .OneHotWidth(NUM_WORDS) + ) u_prim_onehot_enc_raddr_a ( + .in_i (raddr_a_i), + .en_i (1'b1), + .out_o (raddr_onehot_a) + ); + + prim_onehot_enc #( + .OneHotWidth(NUM_WORDS) + ) u_prim_onehot_enc_raddr_b ( + .in_i (raddr_b_i), + .en_i (1'b1), + .out_o (raddr_onehot_b) + ); + + // Buffer the one-hot encoded signals so that the checkers + // are not optimized. + prim_buf #( + .Width(NUM_WORDS) + ) u_prim_buf_raddr_a ( + .in_i (raddr_onehot_a), + .out_o(raddr_onehot_a_buf) + ); + + prim_buf #( + .Width(NUM_WORDS) + ) u_prim_buf_raddr_b ( + .in_i (raddr_onehot_b), + .out_o(raddr_onehot_b_buf) + ); + + // SEC_CM: DATA_REG_SW.GLITCH_DETECT + // Check the one-hot encoded signals for glitches. + prim_onehot_check #( + .AddrWidth(ADDR_WIDTH), + .OneHotWidth(NUM_WORDS), + .AddrCheck(1), + // When AddrCheck=1 also EnableCheck needs to be 1. + .EnableCheck(1) + ) u_prim_onehot_check_raddr_a ( + .clk_i, + .rst_ni, + .oh_i (raddr_onehot_a_buf), + .addr_i (raddr_a_i), + // Set enable=1 as address is always valid. + .en_i (1'b1), + .err_o (oh_raddr_a_err) + ); + + prim_onehot_check #( + .AddrWidth(ADDR_WIDTH), + .OneHotWidth(NUM_WORDS), + .AddrCheck(1), + // When AddrCheck=1 also EnableCheck needs to be 1. + .EnableCheck(1) + ) u_prim_onehot_check_raddr_b ( + .clk_i, + .rst_ni, + .oh_i (raddr_onehot_b_buf), + .addr_i (raddr_b_i), + // Set enable=1 as address is always valid. + .en_i (1'b1), + .err_o (oh_raddr_b_err) + ); + + // MUX register to rdata_a/b_o according to raddr_a/b_onehot. + prim_onehot_mux #( + .Width(DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_a_mux ( + .clk_i, + .rst_ni, + .in_i (mem), + .sel_i (raddr_onehot_a), + .out_o (mem_o_a) + ); + + prim_onehot_mux #( + .Width(DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_b_mux ( + .clk_i, + .rst_ni, + .in_i (mem), + .sel_i (raddr_onehot_b), + .out_o (mem_o_b) + ); + + assign rdata_a_o = (raddr_a_i == '0) ? '0 : mem_o_a; + assign rdata_b_o = (raddr_b_i == '0) ? '0 : mem_o_b; + end else begin : gen_no_rdata_mux_check + // async_read a + assign rdata_a_o = (raddr_a_i == '0) ? '0 : mem[raddr_a_i]; + + // async_read b + assign rdata_b_o = (raddr_b_i == '0) ? '0 : mem[raddr_b_i]; + end // we select assign we = (waddr_a_i == '0) ? 1'b0 : we_a_i; @@ -60,9 +164,9 @@ module ibex_register_file_fpga #( // This checks for spurious WE strobes on the regfile. if (WrenCheck) begin : gen_wren_check // Since the FPGA uses a memory macro, there is only one write-enable strobe to check. - assign err_o = we && !we_a_i; + assign oh_we_err = we && !we_a_i; end else begin : gen_no_wren_check - assign err_o = 1'b0; + assign oh_we_err = 1'b0; end // Note that the SystemVerilog LRM requires variables on the LHS of assignments within diff --git a/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_latch.sv b/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_latch.sv index 8b8a8bcc1acfb9..4c295e2ed136e3 100644 --- a/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_latch.sv +++ b/hw/vendor/lowrisc_ibex/rtl/ibex_register_file_latch.sv @@ -16,6 +16,7 @@ module ibex_register_file_latch #( parameter int unsigned DataWidth = 32, parameter bit DummyInstructions = 0, parameter bit WrenCheck = 0, + parameter bit RdataMuxCheck = 0, parameter logic [DataWidth-1:0] WordZeroVal = '0 ) ( // Clock and Reset @@ -39,7 +40,7 @@ module ibex_register_file_latch #( input logic [DataWidth-1:0] wdata_a_i, input logic we_a_i, - // This indicates whether spurious WE are detected. + // This indicates whether spurious WE or non-one-hot encoded raddr are detected. output logic err_o ); @@ -50,6 +51,8 @@ module ibex_register_file_latch #( logic [NUM_WORDS-1:0] waddr_onehot_a; + logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; + logic [NUM_WORDS-1:1] mem_clocks; logic [DataWidth-1:0] wdata_a_q; @@ -62,11 +65,109 @@ module ibex_register_file_latch #( logic clk_int; + assign err_o = oh_raddr_a_err || oh_raddr_b_err || oh_we_err; + ////////// // READ // ////////// - assign rdata_a_o = mem[raddr_a_int]; - assign rdata_b_o = mem[raddr_b_int]; + if (RdataMuxCheck) begin : gen_rdata_mux_check + // Encode raddr_a/b into one-hot encoded signals. + logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b; + logic [NUM_WORDS-1:0] raddr_onehot_a_buf, raddr_onehot_b_buf; + prim_onehot_enc #( + .OneHotWidth(NUM_WORDS) + ) u_prim_onehot_enc_raddr_a ( + .in_i (raddr_a_int), + .en_i (1'b1), + .out_o (raddr_onehot_a) + ); + + prim_onehot_enc #( + .OneHotWidth(NUM_WORDS) + ) u_prim_onehot_enc_raddr_b ( + .in_i (raddr_b_int), + .en_i (1'b1), + .out_o (raddr_onehot_b) + ); + + // Buffer the one-hot encoded signals so that the checkers + // are not optimized. + prim_buf #( + .Width(NUM_WORDS) + ) u_prim_buf_raddr_a ( + .in_i(raddr_onehot_a), + .out_o(raddr_onehot_a_buf) + ); + + prim_buf #( + .Width(NUM_WORDS) + ) u_prim_buf_raddr_b ( + .in_i(raddr_onehot_b), + .out_o(raddr_onehot_b_buf) + ); + + // SEC_CM: DATA_REG_SW.GLITCH_DETECT + // Check the one-hot encoded signals for glitches. + prim_onehot_check #( + .AddrWidth(ADDR_WIDTH), + .OneHotWidth(NUM_WORDS), + .AddrCheck(1), + // When AddrCheck=1 also EnableCheck needs to be 1. + .EnableCheck(1) + ) u_prim_onehot_check_raddr_a ( + .clk_i, + .rst_ni, + .oh_i (raddr_onehot_a_buf), + .addr_i (raddr_b_int), + // Set enable=1 as address is always valid. + .en_i (1'b1), + .err_o (oh_raddr_a_err) + ); + + prim_onehot_check #( + .AddrWidth(ADDR_WIDTH), + .OneHotWidth(NUM_WORDS), + .AddrCheck(1), + // When AddrCheck=1 also EnableCheck needs to be 1. + .EnableCheck(1) + ) u_prim_onehot_check_raddr_b ( + .clk_i, + .rst_ni, + .oh_i (raddr_onehot_b_buf), + .addr_i (raddr_b_int), + // Set enable=1 as address is always valid. + .en_i (1'b1), + .err_o (oh_raddr_b_err) + ); + + // MUX register to rdata_a/b_o according to raddr_a/b_onehot. + prim_onehot_mux #( + .Width(DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_a_mux ( + .clk_i, + .rst_ni, + .in_i (mem), + .sel_i (raddr_onehot_a), + .out_o (rdata_a_o) + ); + + prim_onehot_mux #( + .Width(DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_b_mux ( + .clk_i, + .rst_ni, + .in_i (mem), + .sel_i (raddr_onehot_b), + .out_o (rdata_b_o) + ); + end else begin : gen_no_rdata_mux_check + assign rdata_a_o = mem[raddr_a_int]; + assign rdata_b_o = mem[raddr_b_int]; + assign oh_raddr_a_err = 1'b0; + assign oh_raddr_b_err = 1'b0; + end /////////// // WRITE // @@ -125,12 +226,12 @@ module ibex_register_file_latch #( .oh_i(waddr_onehot_a_buf), .addr_i(waddr_a_i), .en_i(we_a_i), - .err_o + .err_o(oh_we_err) ); end else begin : gen_no_wren_check logic unused_strobe; assign unused_strobe = waddr_onehot_a[0]; // this is never read from in this case - assign err_o = 1'b0; + assign oh_we_err = 1'b0; end // Individual clock gating (if integrated clock-gating cells are available) diff --git a/hw/vendor/lowrisc_ibex/rtl/ibex_top.sv b/hw/vendor/lowrisc_ibex/rtl/ibex_top.sv index f4e97e8b6adfb2..dff77b0cde31f1 100644 --- a/hw/vendor/lowrisc_ibex/rtl/ibex_top.sv +++ b/hw/vendor/lowrisc_ibex/rtl/ibex_top.sv @@ -140,14 +140,15 @@ module ibex_top import ibex_pkg::*; #( input logic scan_rst_ni ); - localparam bit Lockstep = SecureIbex; - localparam bit ResetAll = Lockstep; - localparam bit DummyInstructions = SecureIbex; - localparam bit RegFileECC = SecureIbex; - localparam bit RegFileWrenCheck = SecureIbex; - localparam int unsigned RegFileDataWidth = RegFileECC ? 32 + 7 : 32; - localparam bit MemECC = SecureIbex; - localparam int unsigned MemDataWidth = MemECC ? 32 + 7 : 32; + localparam bit Lockstep = SecureIbex; + localparam bit ResetAll = Lockstep; + localparam bit DummyInstructions = SecureIbex; + localparam bit RegFileECC = SecureIbex; + localparam bit RegFileWrenCheck = SecureIbex; + localparam bit RegFileRdataMuxCheck = SecureIbex; + localparam int unsigned RegFileDataWidth = RegFileECC ? 32 + 7 : 32; + localparam bit MemECC = SecureIbex; + localparam int unsigned MemDataWidth = MemECC ? 32 + 7 : 32; // Icache parameters localparam int unsigned BusSizeECC = ICacheECC ? (BUS_SIZE + 7) : BUS_SIZE; localparam int unsigned LineSizeECC = BusSizeECC * IC_LINE_BEATS; @@ -421,6 +422,7 @@ module ibex_top import ibex_pkg::*; #( .DummyInstructions(DummyInstructions), // SEC_CM: DATA_REG_SW.GLITCH_DETECT .WrenCheck (RegFileWrenCheck), + .RdataMuxCheck (RegFileRdataMuxCheck), .WordZeroVal (RegFileDataWidth'(prim_secded_pkg::SecdedInv3932ZeroWord)) ) register_file_i ( .clk_i (clk), @@ -446,6 +448,7 @@ module ibex_top import ibex_pkg::*; #( .DummyInstructions(DummyInstructions), // SEC_CM: DATA_REG_SW.GLITCH_DETECT .WrenCheck (RegFileWrenCheck), + .RdataMuxCheck (RegFileRdataMuxCheck), .WordZeroVal (RegFileDataWidth'(prim_secded_pkg::SecdedInv3932ZeroWord)) ) register_file_i ( .clk_i (clk), @@ -471,6 +474,7 @@ module ibex_top import ibex_pkg::*; #( .DummyInstructions(DummyInstructions), // SEC_CM: DATA_REG_SW.GLITCH_DETECT .WrenCheck (RegFileWrenCheck), + .RdataMuxCheck (RegFileRdataMuxCheck), .WordZeroVal (RegFileDataWidth'(prim_secded_pkg::SecdedInv3932ZeroWord)) ) register_file_i ( .clk_i (clk), diff --git a/hw/vendor/lowrisc_ibex/rtl/ibex_tracer.sv b/hw/vendor/lowrisc_ibex/rtl/ibex_tracer.sv index b07b742c506623..f361ddb35ce8b8 100644 --- a/hw/vendor/lowrisc_ibex/rtl/ibex_tracer.sv +++ b/hw/vendor/lowrisc_ibex/rtl/ibex_tracer.sv @@ -107,20 +107,9 @@ module ibex_tracer ( end end - function automatic void printbuffer_dumpline(); + function automatic void printbuffer_dumpline(int fh); string rvfi_insn_str; - if (file_handle == 32'h0) begin - string file_name_base = "trace_core"; - void'($value$plusargs("ibex_tracer_file_base=%s", file_name_base)); - $sformat(file_name, "%s_%h.log", file_name_base, hart_id_i); - - $display("%m: Writing execution trace to %s", file_name); - file_handle = $fopen(file_name, "w"); - $fwrite(file_handle, - "Time\tCycle\tPC\tInsn\tDecoded instruction\tRegister and memory contents\n"); - end - // Write compressed instructions as four hex digits (16 bit word), and // uncompressed ones as 8 hex digits (32 bit words). if (insn_is_compressed) begin @@ -129,33 +118,33 @@ module ibex_tracer ( rvfi_insn_str = $sformatf("%h", rvfi_insn); end - $fwrite(file_handle, "%15t\t%d\t%h\t%s\t%s\t", + $fwrite(fh, "%15t\t%d\t%h\t%s\t%s\t", $time, cycle, rvfi_pc_rdata, rvfi_insn_str, decoded_str); if ((data_accessed & RS1) != 0) begin - $fwrite(file_handle, " %s:0x%08x", reg_addr_to_str(rvfi_rs1_addr), rvfi_rs1_rdata); + $fwrite(fh, " %s:0x%08x", reg_addr_to_str(rvfi_rs1_addr), rvfi_rs1_rdata); end if ((data_accessed & RS2) != 0) begin - $fwrite(file_handle, " %s:0x%08x", reg_addr_to_str(rvfi_rs2_addr), rvfi_rs2_rdata); + $fwrite(fh, " %s:0x%08x", reg_addr_to_str(rvfi_rs2_addr), rvfi_rs2_rdata); end if ((data_accessed & RS3) != 0) begin - $fwrite(file_handle, " %s:0x%08x", reg_addr_to_str(rvfi_rs3_addr), rvfi_rs3_rdata); + $fwrite(fh, " %s:0x%08x", reg_addr_to_str(rvfi_rs3_addr), rvfi_rs3_rdata); end if ((data_accessed & RD) != 0) begin - $fwrite(file_handle, " %s=0x%08x", reg_addr_to_str(rvfi_rd_addr), rvfi_rd_wdata); + $fwrite(fh, " %s=0x%08x", reg_addr_to_str(rvfi_rd_addr), rvfi_rd_wdata); end if ((data_accessed & MEM) != 0) begin - $fwrite(file_handle, " PA:0x%08x", rvfi_mem_addr); + $fwrite(fh, " PA:0x%08x", rvfi_mem_addr); if (rvfi_mem_rmask != 4'b0000) begin - $fwrite(file_handle, " store:0x%08x", rvfi_mem_wdata); + $fwrite(fh, " store:0x%08x", rvfi_mem_wdata); end if (rvfi_mem_wmask != 4'b0000) begin - $fwrite(file_handle, " load:0x%08x", rvfi_mem_rdata); + $fwrite(fh, " load:0x%08x", rvfi_mem_rdata); end end - $fwrite(file_handle, "\n"); + $fwrite(fh, "\n"); endfunction @@ -747,14 +736,33 @@ module ibex_tracer ( // close output file for writing final begin if (file_handle != 32'h0) begin - $fclose(file_handle); + // This dance with "fh" is a bit silly. Some versions of Verilator treat a call of $fclose(xx) + // as a blocking assignment to xx. They then complain about the mixture with that an the + // non-blocking assignment we use when opening the file. The bug is fixed with recent versions + // of Verilator, but this hack is probably worth it for now. + int fh = file_handle; + $fclose(fh); end end // log execution - always_ff @(posedge clk_i) begin + always @(posedge clk_i) begin if (rvfi_valid && trace_log_enable) begin - printbuffer_dumpline(); + + int fh = file_handle; + + if (fh == 32'h0) begin + string file_name_base = "trace_core"; + void'($value$plusargs("ibex_tracer_file_base=%s", file_name_base)); + $sformat(file_name, "%s_%h.log", file_name_base, hart_id_i); + + $display("%m: Writing execution trace to %s", file_name); + fh = $fopen(file_name, "w"); + file_handle <= fh; + $fwrite(fh, "Time\tCycle\tPC\tInsn\tDecoded instruction\tRegister and memory contents\n"); + end + + printbuffer_dumpline(fh); end end diff --git a/hw/vendor/lowrisc_ibex/util/check_tool_requirements.py b/hw/vendor/lowrisc_ibex/util/check_tool_requirements.py index 08c5167210334b..bc9e32f6e123d5 100755 --- a/hw/vendor/lowrisc_ibex/util/check_tool_requirements.py +++ b/hw/vendor/lowrisc_ibex/util/check_tool_requirements.py @@ -4,9 +4,10 @@ # SPDX-License-Identifier: Apache-2.0 import argparse -from distutils.version import StrictVersion +from importlib.metadata import version import logging as log import os +from packaging.version import Version import re import shlex import subprocess @@ -156,7 +157,7 @@ def check(self): 'Failed to convert requirement to semantic version: {}' .format(err)) try: - min_sv = StrictVersion(min_semver) + min_sv = Version(min_semver) except ValueError as err: return (False, 'Bad semver inferred from required version ({}): {}' @@ -174,7 +175,7 @@ def check(self): 'Failed to convert installed to semantic version: {}' .format(err)) try: - actual_sv = StrictVersion(actual_semver) + actual_sv = Version(actual_semver) except ValueError as err: return (False, 'Bad semver inferred from installed version ({}): {}' @@ -212,7 +213,7 @@ class VeribleToolReq(ToolReq): def to_semver(self, version, from_req): # Drop the hash suffix and convert into version string that - # is compatible with StrictVersion in check_version below. + # is compatible with Version in check_version below. # Example: v0.0-808-g1e17daa -> 0.0.808 m = re.fullmatch(r'v([0-9]+)\.([0-9]+)-([0-9]+)-g[0-9a-f]+$', version) if m is None: @@ -237,7 +238,7 @@ def to_semver(self, version, from_req): # already. A version always has the "2020.03" (year and month) part, # and may also have an -SP and/or - suffix. # - # Since StrictVersion expects a 3 digit versioning scheme, we multiply + # Since Version expects a 3 digit versioning scheme, we multiply # any SP number by 100, which should work as long as the patch version # isn't greater than 99. # @@ -261,11 +262,11 @@ def to_semver(self, version, from_req): class PyModuleToolReq(ToolReq): '''A tool in a Python module (its version can be found by running pip)''' - version_regex = re.compile(r'Version: (.*)') - - def _get_tool_cmd(self): - return ['pip3', 'show', self.tool] + # For Python modules, use metadata directly instead of call into pip3, which + # may not always be available for some systems. + def get_version(self): + return version(self.tool) def dict_to_tool_req(path, tool, raw): '''Parse a dict (as read from Python) as a ToolReq