mirror of
https://github.com/openhwgroup/cve2.git
synced 2025-04-22 13:07:46 -04:00
[icache] Add RAM Primitives for scrambling
This commit includes switching to a scrambling RAM primitive for ICache data and tag RAMs. Also introduces minor changes to ICache to handle scrambling key valid signal. It also includes a minor bug fix regarding not initializing `fill_way_q` signal without ResetAll parameter. When the parameter is not set and we have our first hit right after ICache enables, the signal hangs. Signed-off-by: Canberk Topal <ctopal@lowrisc.org>
This commit is contained in:
parent
e53b033962
commit
187944c417
25 changed files with 690 additions and 284 deletions
|
@ -26,6 +26,7 @@ Instantiation Template
|
|||
.RegFile ( ibex_pkg::RegFileFF ),
|
||||
.ICache ( 0 ),
|
||||
.ICacheECC ( 0 ),
|
||||
.ICacheScramble ( 0 ),
|
||||
.BranchPrediction ( 0 ),
|
||||
.SecureIbex ( 0 ),
|
||||
.RndCnstLfsrSeed ( ibex_pkg::RndCnstLfsrSeedDefault ),
|
||||
|
@ -131,6 +132,9 @@ Parameters
|
|||
| ``ICacheECC`` | bit | 0 | *EXPERIMENTAL* Enable SECDED ECC protection in ICache (if |
|
||||
| | | | ICache == 1) |
|
||||
+------------------------------+---------------------+------------+-----------------------------------------------------------------------+
|
||||
| ``ICacheScramble`` | bit | 0 | *EXPERIMENTAL* Enabling this parameter replaces tag and data RAMs of |
|
||||
| | | | ICache with scrambling RAM primitives. |
|
||||
+------------------------------+---------------------+------------+-----------------------------------------------------------------------+
|
||||
| ``BranchPrediction`` | bit | 0 | *EXPERIMENTAL* Enable Static branch prediction |
|
||||
+------------------------------+---------------------+------------+-----------------------------------------------------------------------+
|
||||
| ``SecureIbex`` | bit | 0 | *EXPERIMENTAL* Enable various additional features targeting |
|
||||
|
@ -193,6 +197,8 @@ Interfaces
|
|||
| ``data_*`` | Load-store unit interface, see :ref:`load-store-unit` |
|
||||
+-------------------------+------------------------------------------------------------------------+
|
||||
| ``irq_*`` | Interrupt inputs, see :ref:`exceptions-interrupts` |
|
||||
+-------------------------+-------------------------+-----+----------------------------------------+
|
||||
| ``scramble_*`` | Scrambling key interface, see :ref:`icache` |
|
||||
+-------------------------+------------------------------------------------------------------------+
|
||||
| ``debug_*`` | Debug interface, see :ref:`debug-support` |
|
||||
+-------------------------+------------------------------------------------------------------------+
|
||||
|
|
|
@ -93,6 +93,12 @@ Indicative RAM sizes for common configurations are given in the table below:
|
|||
| 4kB, 4 way, 64bit line | 4 x 128 x 22bit | 4 x 128 x 64bit |
|
||||
+------------------------------+-----------------+------------------+
|
||||
|
||||
If ICacheScramble parameter is enabled, all RAM primitives are replaced with scrambling RAM primitive.
|
||||
For more information about how scrambling works internally (see :file:`vendor/lowrisc_ip/ip/prim/doc/prim_ram_1p_scr.md`).
|
||||
Interface for receiving scrambling key follows req / ack protocol.
|
||||
Ibex first requests a new ephemeral key by asserting the request (`scramble_req_o) and when a fresh valid key is indicated by `scramble_key_valid_i, it deasserts the request.
|
||||
Note that in current implementation, it is assumed req/ack protocol is synchronized before arriving to Ibex top level.
|
||||
|
||||
Sub Unit Description
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -5,24 +5,23 @@ CAPI=2:
|
|||
name: "lowrisc:ibex:ibex_riscv_compliance:0.1"
|
||||
description: "Ibex simulation for RISC-V compliance testing (using Verilator)"
|
||||
filesets:
|
||||
files_sim_verilator:
|
||||
files_sim:
|
||||
depend:
|
||||
- lowrisc:dv_verilator:memutil_verilator
|
||||
- lowrisc:dv_verilator:simutil_verilator
|
||||
- lowrisc:ibex:ibex_top_tracing
|
||||
- lowrisc:ibex:sim_shared
|
||||
|
||||
files:
|
||||
- rtl/ibex_riscv_compliance.sv
|
||||
- ibex_riscv_compliance.cc: { file_type: cppSource }
|
||||
- rtl/riscv_testutil.sv
|
||||
file_type: systemVerilogSource
|
||||
|
||||
files_verilator_waiver:
|
||||
files_verilator:
|
||||
depend:
|
||||
- lowrisc:dv_verilator:memutil_verilator
|
||||
- lowrisc:dv_verilator:simutil_verilator
|
||||
files:
|
||||
- ibex_riscv_compliance.cc: { file_type: cppSource }
|
||||
- lint/verilator_waiver.vlt: {file_type: vlt}
|
||||
|
||||
|
||||
parameters:
|
||||
RV32E:
|
||||
datatype: int
|
||||
|
@ -102,12 +101,18 @@ parameters:
|
|||
default: 0
|
||||
description: "Enables security hardening features (EXPERIMENTAL) [0/1]"
|
||||
|
||||
ICacheScramble:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
default: 0
|
||||
description: "Enables ICache scrambling feature (EXPERIMENTAL) [0/1]"
|
||||
|
||||
targets:
|
||||
sim:
|
||||
default_tool: verilator
|
||||
filesets:
|
||||
- tool_verilator ? (files_verilator_waiver)
|
||||
- files_sim_verilator
|
||||
- files_sim
|
||||
- tool_verilator ? (files_verilator)
|
||||
parameters:
|
||||
- RV32E
|
||||
- RV32M
|
||||
|
@ -122,6 +127,7 @@ targets:
|
|||
- PMPGranularity
|
||||
- PMPNumRegions
|
||||
- SecureIbex
|
||||
- ICacheScramble
|
||||
toplevel: ibex_riscv_compliance
|
||||
tools:
|
||||
verilator:
|
||||
|
|
|
@ -28,6 +28,7 @@ module ibex_riscv_compliance (
|
|||
parameter bit ICacheECC = 1'b0;
|
||||
parameter bit BranchPredictor = 1'b0;
|
||||
parameter bit SecureIbex = 1'b0;
|
||||
parameter bit ICacheScramble = 1'b0;
|
||||
|
||||
logic clk_sys, rst_sys_n;
|
||||
|
||||
|
@ -80,7 +81,6 @@ module ibex_riscv_compliance (
|
|||
assign cfg_device_addr_base[TestUtilDevice] = 32'h20000;
|
||||
assign cfg_device_addr_mask[TestUtilDevice] = ~32'h3FF; // 1 kB
|
||||
|
||||
|
||||
bus #(
|
||||
.NrDevices (NrDevices),
|
||||
.NrHosts (NrHosts ),
|
||||
|
@ -127,53 +127,59 @@ module ibex_riscv_compliance (
|
|||
.ICacheECC (ICacheECC ),
|
||||
.BranchPredictor (BranchPredictor ),
|
||||
.SecureIbex (SecureIbex ),
|
||||
.ICacheScramble (ICacheScramble ),
|
||||
.DmHaltAddr (32'h00000000 ),
|
||||
.DmExceptionAddr (32'h00000000 )
|
||||
) u_top (
|
||||
.clk_i (clk_sys ),
|
||||
.rst_ni (rst_sys_n ),
|
||||
.clk_i (clk_sys ),
|
||||
.rst_ni (rst_sys_n ),
|
||||
|
||||
.test_en_i ('b0 ),
|
||||
.scan_rst_ni (1'b1 ),
|
||||
.ram_cfg_i ('b0 ),
|
||||
.test_en_i ('b0 ),
|
||||
.scan_rst_ni (1'b1 ),
|
||||
.ram_cfg_i ('b0 ),
|
||||
|
||||
.hart_id_i (32'b0 ),
|
||||
.hart_id_i (32'b0 ),
|
||||
// First instruction executed is at 0x0 + 0x80
|
||||
.boot_addr_i (32'h00000000 ),
|
||||
.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] ),
|
||||
.instr_rdata_intg_i ('0 ),
|
||||
.instr_err_i (host_err[CoreI] ),
|
||||
.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] ),
|
||||
.instr_rdata_intg_i ('0 ),
|
||||
.instr_err_i (host_err[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_wdata_intg_o ( ),
|
||||
.data_rdata_i (host_rdata[CoreD] ),
|
||||
.data_rdata_intg_i ('0 ),
|
||||
.data_err_i (host_err[CoreD] ),
|
||||
.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_wdata_intg_o ( ),
|
||||
.data_rdata_i (host_rdata[CoreD] ),
|
||||
.data_rdata_intg_i ('0 ),
|
||||
.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 ),
|
||||
.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 ),
|
||||
.crash_dump_o ( ),
|
||||
.scramble_key_valid_i ('0 ),
|
||||
.scramble_key_i ('0 ),
|
||||
.scramble_nonce_i ('0 ),
|
||||
.scramble_req_o ( ),
|
||||
|
||||
.fetch_enable_i ('b1 ),
|
||||
.alert_minor_o ( ),
|
||||
.alert_major_o ( ),
|
||||
.core_sleep_o ( )
|
||||
.debug_req_i ('b0 ),
|
||||
.crash_dump_o ( ),
|
||||
|
||||
.fetch_enable_i ('b1 ),
|
||||
.alert_minor_o ( ),
|
||||
.alert_major_o ( ),
|
||||
.core_sleep_o ( )
|
||||
);
|
||||
|
||||
// SRAM block for instruction and data storage
|
||||
|
|
|
@ -24,13 +24,34 @@ ${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_39_32_enc.sv
|
|||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_39_32_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_72_64_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_inv_72_64_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_prince.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_subst_perm.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_28_22_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_28_22_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_39_32_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_39_32_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_72_64_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_72_64_dec.sv
|
||||
|
||||
// Until this list is generated by FuseSoC, we have to use manually generated
|
||||
// wrappers around the prim_* modules to instantiate the prim_generic_* ones,
|
||||
// see https://github.com/lowRISC/ibex/issues/893.
|
||||
${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_pkg.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_util_pkg.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_pkg.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_22_16_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_22_16_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_64_57_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_64_57_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_22_16_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_22_16_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_39_32_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_39_32_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_72_64_dec.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_hamming_72_64_enc.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_ram_1p_pkg.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_ram_1p_adv.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim/rtl/prim_ram_1p_scr.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_ram_1p.sv
|
||||
${PRJ_DIR}/dv/uvm/core_ibex/common/prim/prim_ram_1p.sv
|
||||
${PRJ_DIR}/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_clock_gating.sv
|
||||
|
|
|
@ -10,6 +10,7 @@ module core_ibex_tb_top;
|
|||
import core_ibex_test_pkg::*;
|
||||
|
||||
wire clk;
|
||||
wire scramble_req;
|
||||
wire rst_n;
|
||||
|
||||
clk_rst_if ibex_clk_if(.clk(clk), .rst_n(rst_n));
|
||||
|
@ -63,6 +64,7 @@ module core_ibex_tb_top;
|
|||
parameter bit ICacheECC = 1'b0;
|
||||
parameter bit BranchPredictor = 1'b0;
|
||||
parameter bit SecureIbex = 1'b0;
|
||||
parameter bit ICacheScramble = 1'b0;
|
||||
|
||||
ibex_top_tracing #(
|
||||
.DmHaltAddr (32'h`BOOT_ADDR + 'h0 ),
|
||||
|
@ -79,6 +81,7 @@ module core_ibex_tb_top;
|
|||
.ICache (ICache ),
|
||||
.ICacheECC (ICacheECC ),
|
||||
.SecureIbex (SecureIbex ),
|
||||
.ICacheScramble (ICacheScramble ),
|
||||
.BranchPredictor (BranchPredictor )
|
||||
) dut (
|
||||
.clk_i (clk ),
|
||||
|
@ -91,39 +94,44 @@ module core_ibex_tb_top;
|
|||
.hart_id_i (32'b0 ),
|
||||
.boot_addr_i (32'h`BOOT_ADDR ), // align with spike boot address
|
||||
|
||||
.instr_req_o (instr_mem_vif.request),
|
||||
.instr_gnt_i (instr_mem_vif.grant ),
|
||||
.instr_rvalid_i (instr_mem_vif.rvalid ),
|
||||
.instr_addr_o (instr_mem_vif.addr ),
|
||||
.instr_rdata_i (instr_mem_vif.rdata ),
|
||||
.instr_rdata_intg_i (instr_mem_vif.rintg ),
|
||||
.instr_err_i (instr_mem_vif.error ),
|
||||
.instr_req_o (instr_mem_vif.request ),
|
||||
.instr_gnt_i (instr_mem_vif.grant ),
|
||||
.instr_rvalid_i (instr_mem_vif.rvalid ),
|
||||
.instr_addr_o (instr_mem_vif.addr ),
|
||||
.instr_rdata_i (instr_mem_vif.rdata ),
|
||||
.instr_rdata_intg_i (instr_mem_vif.rintg ),
|
||||
.instr_err_i (instr_mem_vif.error ),
|
||||
|
||||
.data_req_o (data_mem_vif.request ),
|
||||
.data_gnt_i (data_mem_vif.grant ),
|
||||
.data_rvalid_i (data_mem_vif.rvalid ),
|
||||
.data_addr_o (data_mem_vif.addr ),
|
||||
.data_we_o (data_mem_vif.we ),
|
||||
.data_be_o (data_mem_vif.be ),
|
||||
.data_rdata_i (data_mem_vif.rdata ),
|
||||
.data_rdata_intg_i (data_mem_vif.rintg ),
|
||||
.data_wdata_o (data_mem_vif.wdata ),
|
||||
.data_wdata_intg_o (data_mem_vif.wintg ),
|
||||
.data_err_i (data_mem_vif.error ),
|
||||
.data_req_o (data_mem_vif.request ),
|
||||
.data_gnt_i (data_mem_vif.grant ),
|
||||
.data_rvalid_i (data_mem_vif.rvalid ),
|
||||
.data_addr_o (data_mem_vif.addr ),
|
||||
.data_we_o (data_mem_vif.we ),
|
||||
.data_be_o (data_mem_vif.be ),
|
||||
.data_rdata_i (data_mem_vif.rdata ),
|
||||
.data_rdata_intg_i (data_mem_vif.rintg ),
|
||||
.data_wdata_o (data_mem_vif.wdata ),
|
||||
.data_wdata_intg_o (data_mem_vif.wintg ),
|
||||
.data_err_i (data_mem_vif.error ),
|
||||
|
||||
.irq_software_i (irq_vif.irq_software ),
|
||||
.irq_timer_i (irq_vif.irq_timer ),
|
||||
.irq_external_i (irq_vif.irq_external ),
|
||||
.irq_fast_i (irq_vif.irq_fast ),
|
||||
.irq_nm_i (irq_vif.irq_nm ),
|
||||
.irq_software_i (irq_vif.irq_software ),
|
||||
.irq_timer_i (irq_vif.irq_timer ),
|
||||
.irq_external_i (irq_vif.irq_external ),
|
||||
.irq_fast_i (irq_vif.irq_fast ),
|
||||
.irq_nm_i (irq_vif.irq_nm ),
|
||||
|
||||
.debug_req_i (dut_if.debug_req ),
|
||||
.crash_dump_o ( ),
|
||||
.scramble_key_valid_i ('0 ),
|
||||
.scramble_key_i ('0 ),
|
||||
.scramble_nonce_i ('0 ),
|
||||
.scramble_req_o ( ),
|
||||
|
||||
.fetch_enable_i (dut_if.fetch_enable ),
|
||||
.alert_minor_o (dut_if.alert_minor ),
|
||||
.alert_major_o (dut_if.alert_major ),
|
||||
.core_sleep_o (dut_if.core_sleep )
|
||||
.debug_req_i (dut_if.debug_req ),
|
||||
.crash_dump_o ( ),
|
||||
|
||||
.fetch_enable_i (dut_if.fetch_enable ),
|
||||
.alert_minor_o (dut_if.alert_minor ),
|
||||
.alert_major_o (dut_if.alert_major ),
|
||||
.core_sleep_o (dut_if.core_sleep )
|
||||
);
|
||||
|
||||
// We should never see any alerts triggered in normal testing
|
||||
|
|
|
@ -15,6 +15,7 @@ filesets:
|
|||
files_dv:
|
||||
depend:
|
||||
- lowrisc:dv:ibex_icache_test
|
||||
- lowrisc:prim:ram_1p_scr
|
||||
files:
|
||||
- tb/tb.sv
|
||||
file_type: systemVerilogSource
|
||||
|
|
|
@ -23,17 +23,19 @@
|
|||
// Import additional common sim cfg files.
|
||||
import_cfgs: [
|
||||
// Project wide common sim cfg file
|
||||
"{proj_root}/vendor/lowrisc_ip/dv/tools/dvsim/common_sim_cfg.hjson"
|
||||
"{proj_root}/vendor/lowrisc_ip/dv/tools/dvsim/common_sim_cfg.hjson",
|
||||
"{proj_root}/vendor/lowrisc_ip/dv/verilator/memutil_dpi_scrambled_opts.hjson"
|
||||
]
|
||||
|
||||
build_modes: [
|
||||
{
|
||||
name: default
|
||||
en_build_modes: ["{tool}_icache_ecc"]
|
||||
en_build_modes: ["{tool}_icache_ecc",
|
||||
"{tool}_memutil_dpi_scrambled_build_opts"]
|
||||
}
|
||||
{
|
||||
name: vcs_icache_ecc
|
||||
build_opts: ["-gv tb.ICacheECC=1"]
|
||||
build_opts: ["-gv tb.ICacheECC=1 -gv tb.ICacheScramble=0"]
|
||||
}
|
||||
{
|
||||
name: riviera_icache_ecc
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
module ic_top import ibex_pkg::*; #(parameter bit ICacheECC = 1'b0) (
|
||||
module ic_top import ibex_pkg::*; #(
|
||||
parameter bit ICacheECC = 1'b0,
|
||||
parameter bit ICacheScramble = 1'b0
|
||||
) (
|
||||
input logic clk_i,
|
||||
input logic rst_ni,
|
||||
input logic req_i,
|
||||
|
@ -23,6 +26,12 @@ module ic_top import ibex_pkg::*; #(parameter bit ICacheECC = 1'b0) (
|
|||
input logic instr_err_i,
|
||||
input logic instr_rvalid_i,
|
||||
|
||||
// Scrambling Interface
|
||||
input logic scramble_key_valid_i,
|
||||
input logic [SCRAMBLE_KEY_W-1:0] scramble_key_i,
|
||||
input logic [SCRAMBLE_NONCE_W-1:0] scramble_nonce_i,
|
||||
output logic scramble_req_o,
|
||||
|
||||
input logic icache_enable_i,
|
||||
input logic icache_inval_i,
|
||||
output logic busy_o
|
||||
|
@ -31,7 +40,8 @@ module ic_top import ibex_pkg::*; #(parameter bit ICacheECC = 1'b0) (
|
|||
localparam int unsigned BusSizeECC = ICacheECC ? (BUS_SIZE + 7) : BUS_SIZE;
|
||||
localparam int unsigned LineSizeECC = BusSizeECC * IC_LINE_BEATS;
|
||||
localparam int unsigned TagSizeECC = ICacheECC ? (IC_TAG_SIZE + 6) : IC_TAG_SIZE;
|
||||
|
||||
localparam int unsigned NumAddrScrRounds = ICacheScramble ? 2 : 0;
|
||||
localparam int unsigned NumDiffRounds = NumAddrScrRounds;
|
||||
// RAM IO
|
||||
logic [IC_NUM_WAYS-1:0] ic_tag_req;
|
||||
logic ic_tag_write;
|
||||
|
@ -43,6 +53,12 @@ module ic_top import ibex_pkg::*; #(parameter bit ICacheECC = 1'b0) (
|
|||
logic [IC_INDEX_W-1:0] ic_data_addr;
|
||||
logic [LineSizeECC-1:0] ic_data_wdata;
|
||||
logic [LineSizeECC-1:0] ic_data_rdata [IC_NUM_WAYS];
|
||||
// Scramble signals
|
||||
logic [SCRAMBLE_KEY_W-1:0] scramble_key_q, scramble_key_d;
|
||||
logic [SCRAMBLE_NONCE_W-1:0] scramble_nonce_q, scramble_nonce_d;
|
||||
logic scramble_key_valid_d, scramble_key_valid_q;
|
||||
logic scramble_req_d, scramble_req_q;
|
||||
|
||||
|
||||
// DUT
|
||||
ibex_icache #(
|
||||
|
@ -85,43 +101,135 @@ module ic_top import ibex_pkg::*; #(parameter bit ICacheECC = 1'b0) (
|
|||
.ic_data_addr_o ( ic_data_addr ),
|
||||
.ic_data_wdata_o ( ic_data_wdata ),
|
||||
.ic_data_rdata_i ( ic_data_rdata ),
|
||||
.ic_scr_key_valid_i ( scramble_key_valid_q ),
|
||||
|
||||
.icache_enable_i ( icache_enable_i ),
|
||||
.icache_inval_i ( icache_inval_i ),
|
||||
.busy_o ( busy_o )
|
||||
);
|
||||
|
||||
///////////////////////////////
|
||||
// Scrambling Infrastructure //
|
||||
///////////////////////////////
|
||||
|
||||
if (ICacheScramble) begin : gen_scramble
|
||||
|
||||
// Scramble key valid starts with OTP returning new valid key and stays high
|
||||
// until we request a new valid key.
|
||||
assign scramble_key_valid_d = scramble_req_q ? scramble_key_valid_i :
|
||||
icache_inval_i ? 1'b0 :
|
||||
scramble_key_valid_q;
|
||||
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
scramble_key_q <= 128'hDDDDDDDDEEEEEEEEAAAAAAAADDDDDDDD;
|
||||
scramble_nonce_q <= 64'hBBBBEEEEEEEEFFFF;
|
||||
end else if (scramble_key_valid_i) begin
|
||||
scramble_key_q <= scramble_key_i;
|
||||
scramble_nonce_q <= scramble_nonce_i;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
scramble_key_valid_q <= 1'b1;
|
||||
scramble_req_q <= '0;
|
||||
end else begin
|
||||
scramble_key_valid_q <= scramble_key_valid_d;
|
||||
scramble_req_q <= scramble_req_d;
|
||||
end
|
||||
end
|
||||
|
||||
// Scramble key request starts with invalidate signal from ICache and stays high
|
||||
// until we got a valid key.
|
||||
assign scramble_req_d = scramble_req_q ? ~scramble_key_valid_i : icache_inval_i;
|
||||
assign scramble_req_o = scramble_req_q;
|
||||
|
||||
end else begin : gen_noscramble
|
||||
|
||||
logic unused_scramble_inputs = scramble_key_valid_i & (|scramble_key_i) &
|
||||
(|scramble_nonce_i) & scramble_req_q & icache_inval_i;
|
||||
|
||||
assign scramble_req_d = 1'b0;
|
||||
assign scramble_req_q = 1'b0;
|
||||
assign scramble_req_o = 1'b0;
|
||||
assign scramble_key_d = '0;
|
||||
assign scramble_key_q = '0;
|
||||
assign scramble_nonce_d = '0;
|
||||
assign scramble_nonce_q = '0;
|
||||
assign scramble_key_valid_q = 1'b1;
|
||||
assign scramble_key_valid_d = 1'b1;
|
||||
end
|
||||
|
||||
// RAMs
|
||||
for (genvar way = 0; way < IC_NUM_WAYS; way++) begin : gen_rams
|
||||
// Tag RAM instantiation
|
||||
prim_ram_1p #(
|
||||
.Width (TagSizeECC),
|
||||
.Depth (IC_NUM_LINES),
|
||||
.DataBitsPerMask (TagSizeECC)
|
||||
prim_ram_1p_scr #(
|
||||
.Width (TagSizeECC),
|
||||
.Depth (IC_NUM_LINES),
|
||||
.DataBitsPerMask (TagSizeECC),
|
||||
.EnableParity (0),
|
||||
.DiffWidth (TagSizeECC),
|
||||
.NumAddrScrRounds (NumAddrScrRounds),
|
||||
.NumDiffRounds (NumDiffRounds)
|
||||
) tag_bank (
|
||||
.clk_i (clk_i),
|
||||
.req_i (ic_tag_req[way]),
|
||||
.cfg_i ('0),
|
||||
.write_i (ic_tag_write),
|
||||
.wmask_i ({TagSizeECC{1'b1}}),
|
||||
.addr_i (ic_tag_addr),
|
||||
.wdata_i (ic_tag_wdata),
|
||||
.rdata_o (ic_tag_rdata[way])
|
||||
.clk_i,
|
||||
.rst_ni,
|
||||
|
||||
.key_valid_i (scramble_key_valid_q),
|
||||
.key_i (scramble_key_q),
|
||||
.nonce_i (scramble_nonce_q),
|
||||
|
||||
.req_i (ic_tag_req[way]),
|
||||
|
||||
.gnt_o (),
|
||||
.write_i (ic_tag_write),
|
||||
.addr_i (ic_tag_addr),
|
||||
.wdata_i (ic_tag_wdata),
|
||||
.wmask_i ({TagSizeECC{1'b1}}),
|
||||
.intg_error_i(1'b0),
|
||||
|
||||
.rdata_o (ic_tag_rdata[way]),
|
||||
.rvalid_o (),
|
||||
.raddr_o (),
|
||||
.rerror_o (),
|
||||
.cfg_i ('0)
|
||||
);
|
||||
|
||||
// Data RAM instantiation
|
||||
prim_ram_1p #(
|
||||
.Width (LineSizeECC),
|
||||
.Depth (IC_NUM_LINES),
|
||||
.DataBitsPerMask (LineSizeECC)
|
||||
prim_ram_1p_scr #(
|
||||
.Width (LineSizeECC),
|
||||
.Depth (IC_NUM_LINES),
|
||||
.DataBitsPerMask (LineSizeECC),
|
||||
.EnableParity (0),
|
||||
.ReplicateKeyStream (1),
|
||||
.DiffWidth (LineSizeECC),
|
||||
.NumAddrScrRounds (NumAddrScrRounds),
|
||||
.NumDiffRounds (NumDiffRounds)
|
||||
) data_bank (
|
||||
.clk_i (clk_i),
|
||||
.req_i (ic_data_req[way]),
|
||||
.cfg_i ('0),
|
||||
.write_i (ic_data_write),
|
||||
.wmask_i ({LineSizeECC{1'b1}}),
|
||||
.addr_i (ic_data_addr),
|
||||
.wdata_i (ic_data_wdata),
|
||||
.rdata_o (ic_data_rdata[way])
|
||||
.clk_i,
|
||||
.rst_ni,
|
||||
|
||||
.key_valid_i (scramble_key_valid_q),
|
||||
.key_i (scramble_key_q),
|
||||
.nonce_i (scramble_nonce_q),
|
||||
|
||||
.req_i (ic_data_req[way]),
|
||||
|
||||
.gnt_o (),
|
||||
.write_i (ic_data_write),
|
||||
.addr_i (ic_data_addr),
|
||||
.wdata_i (ic_data_wdata),
|
||||
.wmask_i ({LineSizeECC{1'b1}}),
|
||||
.intg_error_i(1'b0),
|
||||
|
||||
.rdata_o (ic_data_rdata[way]),
|
||||
.rvalid_o (),
|
||||
.raddr_o (),
|
||||
.rerror_o (),
|
||||
.cfg_i ('0)
|
||||
);
|
||||
end
|
||||
|
||||
|
||||
endmodule
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
module tb #(parameter bit ICacheECC = 1'b0);
|
||||
module tb #(
|
||||
parameter bit ICacheECC = 1'b0,
|
||||
parameter bit ICacheScramble = 1'b0
|
||||
);
|
||||
// dep packages
|
||||
import uvm_pkg::*;
|
||||
import dv_utils_pkg::*;
|
||||
|
@ -21,66 +24,93 @@ module tb #(parameter bit ICacheECC = 1'b0);
|
|||
ibex_icache_core_if core_if (.clk(clk), .rst_n(rst_n));
|
||||
ibex_icache_mem_if mem_if (.clk(clk), .rst_n(rst_n));
|
||||
|
||||
logic scramble_key_valid, scramble_req;
|
||||
logic [127:0] scramble_key;
|
||||
logic [63:0] scramble_nonce;
|
||||
|
||||
// dut
|
||||
ic_top #(
|
||||
.ICacheECC (ICacheECC)
|
||||
.ICacheECC (ICacheECC),
|
||||
.ICacheScramble(ICacheScramble)
|
||||
) dut (
|
||||
.clk_i (clk),
|
||||
.rst_ni (rst_n),
|
||||
|
||||
// Connect icache <-> core interface
|
||||
.req_i (core_if.req),
|
||||
.branch_i (core_if.branch),
|
||||
.branch_mispredict_i (1'b0),
|
||||
.mispredict_addr_i (32'b0),
|
||||
.addr_i (core_if.branch_addr),
|
||||
.ready_i (core_if.ready),
|
||||
.valid_o (core_if.valid),
|
||||
.rdata_o (core_if.rdata),
|
||||
.addr_o (core_if.addr),
|
||||
.err_o (core_if.err),
|
||||
.err_plus2_o (core_if.err_plus2),
|
||||
.icache_enable_i (core_if.enable),
|
||||
.icache_inval_i (core_if.invalidate),
|
||||
.busy_o (core_if.busy),
|
||||
.req_i (core_if.req),
|
||||
.branch_i (core_if.branch),
|
||||
.branch_mispredict_i (1'b0),
|
||||
.mispredict_addr_i (32'b0),
|
||||
.addr_i (core_if.branch_addr),
|
||||
.ready_i (core_if.ready),
|
||||
.valid_o (core_if.valid),
|
||||
.rdata_o (core_if.rdata),
|
||||
.addr_o (core_if.addr),
|
||||
.err_o (core_if.err),
|
||||
.err_plus2_o (core_if.err_plus2),
|
||||
.icache_enable_i (core_if.enable),
|
||||
.icache_inval_i (core_if.invalidate),
|
||||
.busy_o (core_if.busy),
|
||||
.scramble_key_valid_i (scramble_key_valid),
|
||||
.scramble_key_i (scramble_key),
|
||||
.scramble_nonce_i (scramble_nonce),
|
||||
.scramble_req_o (scramble_req),
|
||||
|
||||
// Connect icache <-> instruction bus interface
|
||||
.instr_req_o (mem_if.req),
|
||||
.instr_gnt_i (mem_if.gnt),
|
||||
.instr_addr_o (mem_if.addr),
|
||||
.instr_rdata_i (mem_if.rdata),
|
||||
.instr_err_i (mem_if.err),
|
||||
.instr_rvalid_i (mem_if.rvalid)
|
||||
.instr_req_o (mem_if.req),
|
||||
.instr_gnt_i (mem_if.gnt),
|
||||
.instr_addr_o (mem_if.addr),
|
||||
.instr_rdata_i (mem_if.rdata),
|
||||
.instr_err_i (mem_if.err),
|
||||
.instr_rvalid_i (mem_if.rvalid)
|
||||
);
|
||||
|
||||
|
||||
// If the ICacheECC parameter is set in the DUT, generate another interface for each tag ram and
|
||||
// each data ram, binding them into the RAMs themselves. ECC tests can use these to insert errors
|
||||
// into memory lookups.
|
||||
generate if (dut.ICacheECC) begin : gen_ecc
|
||||
for (genvar w = 0; w < ibex_pkg::IC_NUM_WAYS; w++) begin : gen_ecc_ifs
|
||||
bind dut.gen_rams[w].tag_bank.gen_badbit.u_impl_badbit ibex_icache_ecc_if tag_bank_if (.*);
|
||||
bind dut.gen_rams[w].data_bank.gen_badbit.u_impl_badbit ibex_icache_ecc_if data_bank_if (.*);
|
||||
bind dut.gen_rams[w].tag_bank.u_prim_ram_1p_adv.u_mem.gen_badbit.u_impl_badbit
|
||||
ibex_icache_ecc_if tag_bank_if (.*);
|
||||
bind dut.gen_rams[w].data_bank.u_prim_ram_1p_adv.u_mem.gen_badbit.u_impl_badbit
|
||||
ibex_icache_ecc_if data_bank_if (.*);
|
||||
|
||||
initial begin
|
||||
uvm_config_db#(virtual ibex_icache_ecc_if)::
|
||||
set(null,
|
||||
$sformatf("*.env.ecc_tag_agents[%0d]*", w),
|
||||
"vif",
|
||||
dut.gen_rams[w].tag_bank.gen_badbit.u_impl_badbit.tag_bank_if);
|
||||
dut.gen_rams[w].tag_bank.u_prim_ram_1p_adv.
|
||||
u_mem.gen_badbit.u_impl_badbit.tag_bank_if);
|
||||
|
||||
uvm_config_db#(virtual ibex_icache_ecc_if)::
|
||||
set(null,
|
||||
$sformatf("*.env.ecc_data_agents[%0d]*", w),
|
||||
"vif",
|
||||
dut.gen_rams[w].data_bank.gen_badbit.u_impl_badbit.data_bank_if);
|
||||
dut.gen_rams[w].data_bank.u_prim_ram_1p_adv.
|
||||
u_mem.gen_badbit.u_impl_badbit.data_bank_if);
|
||||
end
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
|
||||
always @(posedge scramble_req) begin
|
||||
scramble_key_valid = 1'b0;
|
||||
#(20 * $urandom_range(1, 10))
|
||||
scramble_key = {$urandom(),$urandom(),$urandom(),$urandom()};
|
||||
scramble_nonce = {$urandom(),$urandom()};
|
||||
scramble_key_valid = 1'b1;
|
||||
#20
|
||||
scramble_key_valid = 1'b0;
|
||||
end
|
||||
|
||||
initial begin
|
||||
// drive clk and rst_n from clk_if
|
||||
clk_rst_if.set_active();
|
||||
scramble_key = {$urandom(),$urandom(),$urandom(),$urandom()};
|
||||
scramble_nonce = {$urandom(),$urandom()};
|
||||
scramble_key_valid = 1'b0;
|
||||
|
||||
// Store virtual interfaces into the UVM config database. ECC interfaces are done separately
|
||||
// above because otherwise you have to repeat the (verbose) generate loop.
|
||||
|
|
|
@ -100,6 +100,12 @@ parameters:
|
|||
paramtype: vlogparam
|
||||
description: "Number of PMP regions"
|
||||
|
||||
ICacheScramble:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enables ICache scrambling feature (EXPERIMENTAL) [0/1]"
|
||||
|
||||
targets:
|
||||
default: &default_target
|
||||
filesets:
|
||||
|
@ -119,6 +125,7 @@ targets:
|
|||
- PMPEnable
|
||||
- PMPGranularity
|
||||
- PMPNumRegions
|
||||
- ICacheScramble
|
||||
- SRAMInitFile
|
||||
|
||||
lint:
|
||||
|
|
|
@ -72,6 +72,12 @@ parameters:
|
|||
paramtype: vlogparam
|
||||
description: "Enables security hardening features (EXPERIMENTAL) [0/1]"
|
||||
|
||||
ICacheScramble:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enables ICache scrambling feature (EXPERIMENTAL) [0/1]"
|
||||
|
||||
BranchPredictor:
|
||||
datatype: int
|
||||
paramtype: vlogparam
|
||||
|
@ -107,6 +113,7 @@ targets:
|
|||
- RV32B
|
||||
- RegFile
|
||||
- ICache
|
||||
- ICacheScramble
|
||||
- ICacheECC
|
||||
- BranchTargetALU
|
||||
- WritebackStage
|
||||
|
|
|
@ -30,7 +30,6 @@ filesets:
|
|||
targets:
|
||||
default:
|
||||
filesets:
|
||||
- tool_verilator ? (files_verilator)
|
||||
- tool_veriblelint ? (files_lint_verible)
|
||||
- files_sim
|
||||
- files_verilator
|
||||
- tool_verilator ? (files_verilator)
|
||||
|
|
|
@ -36,6 +36,7 @@ module ibex_simple_system (
|
|||
);
|
||||
|
||||
parameter bit SecureIbex = 1'b0;
|
||||
parameter bit ICacheScramble = 1'b0;
|
||||
parameter bit PMPEnable = 1'b0;
|
||||
parameter int unsigned PMPGranularity = 0;
|
||||
parameter int unsigned PMPNumRegions = 4;
|
||||
|
@ -163,6 +164,7 @@ module ibex_simple_system (
|
|||
|
||||
ibex_top_tracing #(
|
||||
.SecureIbex ( SecureIbex ),
|
||||
.ICacheScramble ( ICacheScramble ),
|
||||
.PMPEnable ( PMPEnable ),
|
||||
.PMPGranularity ( PMPGranularity ),
|
||||
.PMPNumRegions ( PMPNumRegions ),
|
||||
|
@ -216,6 +218,11 @@ module ibex_simple_system (
|
|||
.irq_fast_i (15'b0),
|
||||
.irq_nm_i (1'b0),
|
||||
|
||||
.scramble_key_valid_i ('0),
|
||||
.scramble_key_i ('0),
|
||||
.scramble_nonce_i ('0),
|
||||
.scramble_req_o (),
|
||||
|
||||
.debug_req_i ('b0),
|
||||
.crash_dump_o (),
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ small:
|
|||
PMPGranularity : 0
|
||||
PMPNumRegions : 4
|
||||
SecureIbex : 0
|
||||
ICacheScramble : 0
|
||||
|
||||
# Configuration to match that used in the OpenTitan project
|
||||
opentitan:
|
||||
|
@ -37,6 +38,7 @@ opentitan:
|
|||
PMPGranularity : 0
|
||||
PMPNumRegions : 16
|
||||
SecureIbex : 1
|
||||
ICacheScramble : 0
|
||||
|
||||
# ===============================
|
||||
# * EXPERIMENTAL CONFIGURATIONS *
|
||||
|
@ -59,6 +61,7 @@ experimental-maxperf:
|
|||
PMPGranularity : 0
|
||||
PMPNumRegions : 4
|
||||
SecureIbex : 0
|
||||
ICacheScramble : 0
|
||||
|
||||
# experimental-maxperf config above plus PMP enabled with 16 regions.
|
||||
experimental-maxperf-pmp:
|
||||
|
@ -75,6 +78,7 @@ experimental-maxperf-pmp:
|
|||
PMPGranularity : 0
|
||||
PMPNumRegions : 16
|
||||
SecureIbex : 0
|
||||
ICacheScramble : 0
|
||||
|
||||
# experimental-maxperf-pmp config above with balanced bitmanip extension
|
||||
experimental-maxperf-pmp-bmbalanced:
|
||||
|
@ -91,6 +95,7 @@ experimental-maxperf-pmp-bmbalanced:
|
|||
PMPGranularity : 0
|
||||
PMPNumRegions : 16
|
||||
SecureIbex : 0
|
||||
ICacheScramble : 0
|
||||
|
||||
# experimental-maxperf-pmp config above with full bitmanip extension
|
||||
experimental-maxperf-pmp-bmfull:
|
||||
|
@ -107,6 +112,7 @@ experimental-maxperf-pmp-bmfull:
|
|||
PMPGranularity : 0
|
||||
PMPNumRegions : 16
|
||||
SecureIbex : 0
|
||||
ICacheScramble : 0
|
||||
|
||||
# experimental-maxperf-pmp-bmfull config above with icache enabled
|
||||
experimental-maxperf-pmp-bmfull-icache:
|
||||
|
@ -123,6 +129,7 @@ experimental-maxperf-pmp-bmfull-icache:
|
|||
PMPGranularity : 0
|
||||
PMPNumRegions : 16
|
||||
SecureIbex : 0
|
||||
ICacheScramble : 0
|
||||
|
||||
# experimental-maxperf with branch predictor switched on. This exists to allow
|
||||
# easy use of Ibex with the branch predictor in particular for CI runs. The
|
||||
|
@ -142,4 +149,5 @@ experimental-branch-predictor:
|
|||
PMPGranularity : 0
|
||||
PMPNumRegions : 4
|
||||
SecureIbex : 0
|
||||
ICacheScramble : 0
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ filesets:
|
|||
files_rtl:
|
||||
depend:
|
||||
- lowrisc:prim:secded
|
||||
- lowrisc:prim:ram_1p
|
||||
- lowrisc:prim:assert
|
||||
- lowrisc:ibex:ibex_pkg
|
||||
files:
|
||||
|
|
|
@ -13,6 +13,7 @@ filesets:
|
|||
- lowrisc:prim:buf
|
||||
- lowrisc:prim:clock_mux2
|
||||
- lowrisc:prim:flop
|
||||
- lowrisc:prim:ram_1p_scr
|
||||
files:
|
||||
- rtl/ibex_register_file_ff.sv # generic FF-based
|
||||
- rtl/ibex_register_file_fpga.sv # FPGA
|
||||
|
@ -107,6 +108,12 @@ parameters:
|
|||
paramtype: vlogparam
|
||||
description: "Enables security hardening features (EXPERIMENTAL) [0/1]"
|
||||
|
||||
ICacheScramble:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enables ICache scrambling feature (EXPERIMENTAL) [0/1]"
|
||||
|
||||
PMPEnable:
|
||||
datatype: int
|
||||
default: 0
|
||||
|
|
|
@ -83,6 +83,12 @@ parameters:
|
|||
paramtype: vlogparam
|
||||
description: "Enables security hardening features (EXPERIMENTAL) [0/1]"
|
||||
|
||||
ICacheScramble:
|
||||
datatype: int
|
||||
default: 0
|
||||
paramtype: vlogparam
|
||||
description: "Enables ICache scrambling feature (EXPERIMENTAL) [0/1]"
|
||||
|
||||
PMPEnable:
|
||||
datatype: int
|
||||
default: 0
|
||||
|
@ -124,6 +130,7 @@ targets:
|
|||
- WritebackStage
|
||||
- BranchPredictor
|
||||
- SecureIbex
|
||||
- ICacheScramble
|
||||
- PMPEnable
|
||||
- PMPGranularity
|
||||
- PMPNumRegions
|
||||
|
|
|
@ -88,6 +88,7 @@ module ibex_core import ibex_pkg::*; #(
|
|||
output logic [IC_INDEX_W-1:0] ic_data_addr_o,
|
||||
output logic [LineSizeECC-1:0] ic_data_wdata_o,
|
||||
input logic [LineSizeECC-1:0] ic_data_rdata_i [IC_NUM_WAYS],
|
||||
input logic ic_scr_key_valid_i,
|
||||
|
||||
// Interrupt inputs
|
||||
input logic irq_software_i,
|
||||
|
@ -138,6 +139,7 @@ module ibex_core import ibex_pkg::*; #(
|
|||
input logic fetch_enable_i,
|
||||
output logic alert_minor_o,
|
||||
output logic alert_major_o,
|
||||
output logic icache_inval_o,
|
||||
output logic core_busy_o
|
||||
);
|
||||
|
||||
|
@ -383,16 +385,17 @@ module ibex_core import ibex_pkg::*; #(
|
|||
.instr_rdata_i (instr_rdata_i),
|
||||
.instr_err_i (instr_err_i),
|
||||
|
||||
.ic_tag_req_o (ic_tag_req_o),
|
||||
.ic_tag_write_o (ic_tag_write_o),
|
||||
.ic_tag_addr_o (ic_tag_addr_o),
|
||||
.ic_tag_wdata_o (ic_tag_wdata_o),
|
||||
.ic_tag_rdata_i (ic_tag_rdata_i),
|
||||
.ic_data_req_o (ic_data_req_o),
|
||||
.ic_data_write_o(ic_data_write_o),
|
||||
.ic_data_addr_o (ic_data_addr_o),
|
||||
.ic_data_wdata_o(ic_data_wdata_o),
|
||||
.ic_data_rdata_i(ic_data_rdata_i),
|
||||
.ic_tag_req_o (ic_tag_req_o),
|
||||
.ic_tag_write_o (ic_tag_write_o),
|
||||
.ic_tag_addr_o (ic_tag_addr_o),
|
||||
.ic_tag_wdata_o (ic_tag_wdata_o),
|
||||
.ic_tag_rdata_i (ic_tag_rdata_i),
|
||||
.ic_data_req_o (ic_data_req_o),
|
||||
.ic_data_write_o (ic_data_write_o),
|
||||
.ic_data_addr_o (ic_data_addr_o),
|
||||
.ic_data_wdata_o (ic_data_wdata_o),
|
||||
.ic_data_rdata_i (ic_data_rdata_i),
|
||||
.ic_scr_key_valid_i(ic_scr_key_valid_i),
|
||||
|
||||
// outputs to ID stage
|
||||
.instr_valid_id_o (instr_valid_id),
|
||||
|
@ -608,6 +611,7 @@ module ibex_core import ibex_pkg::*; #(
|
|||
.instr_id_done_o (instr_id_done)
|
||||
);
|
||||
|
||||
assign icache_inval_o = icache_inval;
|
||||
// for RVFI only
|
||||
assign unused_illegal_insn_id = illegal_insn_id;
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ module ibex_icache import ibex_pkg::*; #(
|
|||
output logic [IC_INDEX_W-1:0] ic_data_addr_o,
|
||||
output logic [LineSizeECC-1:0] ic_data_wdata_o,
|
||||
input logic [LineSizeECC-1:0] ic_data_rdata_i [IC_NUM_WAYS],
|
||||
input logic ic_scr_key_valid_i,
|
||||
|
||||
// Cache status
|
||||
input logic icache_enable_i,
|
||||
|
@ -184,6 +185,7 @@ module ibex_icache import ibex_pkg::*; #(
|
|||
logic output_err;
|
||||
// Invalidations
|
||||
logic start_inval, inval_done;
|
||||
logic inval_lock, inval_req_d, inval_req_q;
|
||||
logic reset_inval_q;
|
||||
logic inval_prog_d, inval_prog_q;
|
||||
logic [IC_INDEX_W-1:0] inval_index_d, inval_index_q;
|
||||
|
@ -252,7 +254,8 @@ module ibex_icache import ibex_pkg::*; #(
|
|||
assign fill_grant_ic0 = fill_req_ic0 & ~lookup_req_ic0 & ~inval_prog_q &
|
||||
~ecc_write_req;
|
||||
// Qualified lookup grant to mask ram signals in IC1 if access was not made
|
||||
assign lookup_actual_ic0 = lookup_grant_ic0 & icache_enable_i & ~inval_prog_q & ~start_inval;
|
||||
assign lookup_actual_ic0 = lookup_grant_ic0 & icache_enable_i & ~inval_prog_q &
|
||||
~icache_inval_i & ~inval_lock & ~start_inval;
|
||||
|
||||
// Tagram
|
||||
assign tag_req_ic0 = lookup_req_ic0 | fill_req_ic0 | inval_prog_q | ecc_write_req;
|
||||
|
@ -530,7 +533,7 @@ module ibex_icache import ibex_pkg::*; #(
|
|||
end
|
||||
|
||||
assign fill_cache_new = (branch_i | (|cache_cnt_q)) & icache_enable_i &
|
||||
~icache_inval_i & ~inval_prog_q;
|
||||
~icache_inval_i & ~inval_lock & ~inval_prog_q;
|
||||
|
||||
end else begin : gen_cache_all
|
||||
|
||||
|
@ -598,7 +601,7 @@ module ibex_icache import ibex_pkg::*; #(
|
|||
// Any invalidation or disabling of the cache while the buffer is busy will stop allocation
|
||||
assign fill_cache_d[fb] = (fill_alloc[fb] & fill_cache_new) |
|
||||
(fill_cache_q[fb] & fill_busy_q[fb] &
|
||||
icache_enable_i & ~icache_inval_i);
|
||||
icache_enable_i & ~icache_inval_i & ~inval_lock);
|
||||
// Record whether the request hit in the cache
|
||||
assign fill_hit_ic1[fb] = lookup_valid_ic1 & fill_in_ic1[fb] & tag_hit_ic1 & ~ecc_err_ic1;
|
||||
assign fill_hit_d[fb] = fill_hit_ic1[fb] | (fill_hit_q[fb] & fill_busy_q[fb]);
|
||||
|
@ -784,19 +787,11 @@ module ibex_icache import ibex_pkg::*; #(
|
|||
end
|
||||
end
|
||||
|
||||
if (ResetAll) begin : g_fill_way_ra
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
fill_way_q[fb] <= '0;
|
||||
end else if (fill_way_en[fb]) begin
|
||||
fill_way_q[fb] <= sel_way_ic1;
|
||||
end
|
||||
end
|
||||
end else begin : g_fill_way_nr
|
||||
always_ff @(posedge clk_i) begin
|
||||
if (fill_way_en[fb]) begin
|
||||
fill_way_q[fb] <= sel_way_ic1;
|
||||
end
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
fill_way_q[fb] <= '0;
|
||||
end else if (fill_way_en[fb]) begin
|
||||
fill_way_q[fb] <= sel_way_ic1;
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1065,13 +1060,31 @@ module ibex_icache import ibex_pkg::*; #(
|
|||
// Invalidations //
|
||||
///////////////////
|
||||
|
||||
|
||||
// We need to save the invalidation request inside a register. That way we can wait
|
||||
// until we have a valid scrambling key to do it. Since the key itself is needed for
|
||||
// starting to fill in the RAMs and read from them, ICache also needs to stop operating.
|
||||
assign inval_req_d = (inval_req_q | icache_inval_i) & ~(inval_done & inval_prog_q);
|
||||
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
inval_req_q <= 1'b0;
|
||||
end else begin
|
||||
inval_req_q <= inval_req_d;
|
||||
end
|
||||
end
|
||||
|
||||
// This will act like a lock mechanism.
|
||||
// Main idea is to lock the invalidation request until we got a valid scrambling key.
|
||||
assign inval_lock = inval_req_d & ~ic_scr_key_valid_i;
|
||||
|
||||
// Invalidate on reset, or when instructed. If an invalidation request is received while a
|
||||
// previous invalidation is ongoing, it does not need to be restarted.
|
||||
assign start_inval = (~reset_inval_q | icache_inval_i) & ~inval_prog_q;
|
||||
assign inval_prog_d = start_inval | (inval_prog_q & ~inval_done);
|
||||
// previous invalidation is ongoing, it does not need to be restarted. Do not start
|
||||
// this process until inval lock is removed meaning the scrambling key is valid.
|
||||
assign start_inval = ~inval_lock & (~reset_inval_q | inval_req_q) & ~inval_prog_q ;
|
||||
assign inval_prog_d = ~inval_lock & (start_inval | (inval_prog_q & ~inval_done));
|
||||
assign inval_done = &inval_index_q;
|
||||
assign inval_index_d = start_inval ? '0 :
|
||||
(inval_index_q + {{IC_INDEX_W-1{1'b0}},1'b1});
|
||||
assign inval_index_d = start_inval ? '0 : (inval_index_q + {{IC_INDEX_W-1{1'b0}},1'b1});
|
||||
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
|
@ -1105,7 +1118,7 @@ module ibex_icache import ibex_pkg::*; #(
|
|||
|
||||
// Only busy (for WFI purposes) while an invalidation is in-progress, or external requests are
|
||||
// outstanding.
|
||||
assign busy_o = inval_prog_q | (|(fill_busy_q & ~fill_rvd_done));
|
||||
assign busy_o = inval_req_q | (|(fill_busy_q & ~fill_rvd_done));
|
||||
|
||||
////////////////
|
||||
// Assertions //
|
||||
|
|
|
@ -52,6 +52,7 @@ module ibex_if_stage import ibex_pkg::*; #(
|
|||
output logic [IC_INDEX_W-1:0] ic_data_addr_o,
|
||||
output logic [LineSizeECC-1:0] ic_data_wdata_o,
|
||||
input logic [LineSizeECC-1:0] ic_data_rdata_i [IC_NUM_WAYS],
|
||||
input logic ic_scr_key_valid_i,
|
||||
|
||||
// output of ID stage
|
||||
output logic instr_valid_id_o, // instr in IF-ID is valid
|
||||
|
@ -246,6 +247,7 @@ module ibex_if_stage import ibex_pkg::*; #(
|
|||
.ic_data_addr_o ( ic_data_addr_o ),
|
||||
.ic_data_wdata_o ( ic_data_wdata_o ),
|
||||
.ic_data_rdata_i ( ic_data_rdata_i ),
|
||||
.ic_scr_key_valid_i ( ic_scr_key_valid_i ),
|
||||
|
||||
.icache_enable_i ( icache_enable_i ),
|
||||
.icache_inval_i ( icache_inval_i ),
|
||||
|
@ -283,13 +285,14 @@ module ibex_if_stage import ibex_pkg::*; #(
|
|||
.busy_o ( prefetch_busy )
|
||||
);
|
||||
// ICache tieoffs
|
||||
logic unused_icen, unused_icinv;
|
||||
logic unused_icen, unused_icinv, unused_scr_key_valid;
|
||||
logic [TagSizeECC-1:0] unused_tag_ram_input [IC_NUM_WAYS];
|
||||
logic [LineSizeECC-1:0] unused_data_ram_input [IC_NUM_WAYS];
|
||||
assign unused_icen = icache_enable_i;
|
||||
assign unused_icinv = icache_inval_i;
|
||||
assign unused_tag_ram_input = ic_tag_rdata_i;
|
||||
assign unused_data_ram_input = ic_data_rdata_i;
|
||||
assign unused_scr_key_valid = ic_scr_key_valid_i;
|
||||
assign ic_tag_req_o = 'b0;
|
||||
assign ic_tag_write_o = 'b0;
|
||||
assign ic_tag_addr_o = 'b0;
|
||||
|
|
|
@ -81,6 +81,7 @@ module ibex_lockstep import ibex_pkg::*; #(
|
|||
input logic [IC_INDEX_W-1:0] ic_data_addr_i,
|
||||
input logic [LineSizeECC-1:0] ic_data_wdata_i,
|
||||
input logic [LineSizeECC-1:0] ic_data_rdata_i [IC_NUM_WAYS],
|
||||
input logic ic_scr_key_valid_i,
|
||||
|
||||
input logic irq_software_i,
|
||||
input logic irq_timer_i,
|
||||
|
@ -95,6 +96,7 @@ module ibex_lockstep import ibex_pkg::*; #(
|
|||
input logic fetch_enable_i,
|
||||
output logic alert_minor_o,
|
||||
output logic alert_major_o,
|
||||
input logic icache_inval_i,
|
||||
input logic core_busy_i,
|
||||
input logic test_en_i,
|
||||
input logic scan_rst_ni
|
||||
|
@ -178,6 +180,7 @@ module ibex_lockstep import ibex_pkg::*; #(
|
|||
logic irq_nm;
|
||||
logic debug_req;
|
||||
logic fetch_enable;
|
||||
logic ic_scr_key_valid;
|
||||
} delayed_inputs_t;
|
||||
|
||||
delayed_inputs_t [LockstepOffset-1:0] shadow_inputs_q;
|
||||
|
@ -188,23 +191,24 @@ module ibex_lockstep import ibex_pkg::*; #(
|
|||
logic [LineSizeECC-1:0] shadow_data_rdata_q [IC_NUM_WAYS][LockstepOffset];
|
||||
|
||||
// Assign the inputs to the delay structure
|
||||
assign shadow_inputs_in.instr_gnt = instr_gnt_i;
|
||||
assign shadow_inputs_in.instr_rvalid = instr_rvalid_i;
|
||||
assign shadow_inputs_in.instr_rdata = instr_rdata_i;
|
||||
assign shadow_inputs_in.instr_err = instr_err_i;
|
||||
assign shadow_inputs_in.data_gnt = data_gnt_i;
|
||||
assign shadow_inputs_in.data_rvalid = data_rvalid_i;
|
||||
assign shadow_inputs_in.data_rdata = data_rdata_i;
|
||||
assign shadow_inputs_in.data_err = data_err_i;
|
||||
assign shadow_inputs_in.rf_rdata_a_ecc = rf_rdata_a_ecc_i;
|
||||
assign shadow_inputs_in.rf_rdata_b_ecc = rf_rdata_b_ecc_i;
|
||||
assign shadow_inputs_in.irq_software = irq_software_i;
|
||||
assign shadow_inputs_in.irq_timer = irq_timer_i;
|
||||
assign shadow_inputs_in.irq_external = irq_external_i;
|
||||
assign shadow_inputs_in.irq_fast = irq_fast_i;
|
||||
assign shadow_inputs_in.irq_nm = irq_nm_i;
|
||||
assign shadow_inputs_in.debug_req = debug_req_i;
|
||||
assign shadow_inputs_in.fetch_enable = fetch_enable_i;
|
||||
assign shadow_inputs_in.instr_gnt = instr_gnt_i;
|
||||
assign shadow_inputs_in.instr_rvalid = instr_rvalid_i;
|
||||
assign shadow_inputs_in.instr_rdata = instr_rdata_i;
|
||||
assign shadow_inputs_in.instr_err = instr_err_i;
|
||||
assign shadow_inputs_in.data_gnt = data_gnt_i;
|
||||
assign shadow_inputs_in.data_rvalid = data_rvalid_i;
|
||||
assign shadow_inputs_in.data_rdata = data_rdata_i;
|
||||
assign shadow_inputs_in.data_err = data_err_i;
|
||||
assign shadow_inputs_in.rf_rdata_a_ecc = rf_rdata_a_ecc_i;
|
||||
assign shadow_inputs_in.rf_rdata_b_ecc = rf_rdata_b_ecc_i;
|
||||
assign shadow_inputs_in.irq_software = irq_software_i;
|
||||
assign shadow_inputs_in.irq_timer = irq_timer_i;
|
||||
assign shadow_inputs_in.irq_external = irq_external_i;
|
||||
assign shadow_inputs_in.irq_fast = irq_fast_i;
|
||||
assign shadow_inputs_in.irq_nm = irq_nm_i;
|
||||
assign shadow_inputs_in.debug_req = debug_req_i;
|
||||
assign shadow_inputs_in.fetch_enable = fetch_enable_i;
|
||||
assign shadow_inputs_in.ic_scr_key_valid = ic_scr_key_valid_i;
|
||||
|
||||
// Delay the inputs
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
|
@ -290,6 +294,7 @@ module ibex_lockstep import ibex_pkg::*; #(
|
|||
logic [LineSizeECC-1:0] ic_data_wdata;
|
||||
logic irq_pending;
|
||||
crash_dump_t crash_dump;
|
||||
logic icache_inval;
|
||||
logic core_busy;
|
||||
} delayed_outputs_t;
|
||||
|
||||
|
@ -321,6 +326,7 @@ module ibex_lockstep import ibex_pkg::*; #(
|
|||
assign core_outputs_in.ic_data_wdata = ic_data_wdata_i;
|
||||
assign core_outputs_in.irq_pending = irq_pending_i;
|
||||
assign core_outputs_in.crash_dump = crash_dump_i;
|
||||
assign core_outputs_in.icache_inval = icache_inval_i;
|
||||
assign core_outputs_in.core_busy = core_busy_i;
|
||||
|
||||
// Delay the outputs
|
||||
|
@ -366,58 +372,59 @@ module ibex_lockstep import ibex_pkg::*; #(
|
|||
.DmHaltAddr ( DmHaltAddr ),
|
||||
.DmExceptionAddr ( DmExceptionAddr )
|
||||
) u_shadow_core (
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_shadow_n),
|
||||
.clk_i (clk_i),
|
||||
.rst_ni (rst_shadow_n),
|
||||
|
||||
.hart_id_i (hart_id_i),
|
||||
.boot_addr_i (boot_addr_i),
|
||||
.hart_id_i (hart_id_i),
|
||||
.boot_addr_i (boot_addr_i),
|
||||
|
||||
.instr_req_o (shadow_outputs_d.instr_req),
|
||||
.instr_gnt_i (shadow_inputs_q[0].instr_gnt),
|
||||
.instr_rvalid_i (shadow_inputs_q[0].instr_rvalid),
|
||||
.instr_addr_o (shadow_outputs_d.instr_addr),
|
||||
.instr_rdata_i (shadow_inputs_q[0].instr_rdata),
|
||||
.instr_err_i (shadow_inputs_q[0].instr_err),
|
||||
.instr_req_o (shadow_outputs_d.instr_req),
|
||||
.instr_gnt_i (shadow_inputs_q[0].instr_gnt),
|
||||
.instr_rvalid_i (shadow_inputs_q[0].instr_rvalid),
|
||||
.instr_addr_o (shadow_outputs_d.instr_addr),
|
||||
.instr_rdata_i (shadow_inputs_q[0].instr_rdata),
|
||||
.instr_err_i (shadow_inputs_q[0].instr_err),
|
||||
|
||||
.data_req_o (shadow_outputs_d.data_req),
|
||||
.data_gnt_i (shadow_inputs_q[0].data_gnt),
|
||||
.data_rvalid_i (shadow_inputs_q[0].data_rvalid),
|
||||
.data_we_o (shadow_outputs_d.data_we),
|
||||
.data_be_o (shadow_outputs_d.data_be),
|
||||
.data_addr_o (shadow_outputs_d.data_addr),
|
||||
.data_wdata_o (shadow_outputs_d.data_wdata),
|
||||
.data_rdata_i (shadow_inputs_q[0].data_rdata),
|
||||
.data_err_i (shadow_inputs_q[0].data_err),
|
||||
.data_req_o (shadow_outputs_d.data_req),
|
||||
.data_gnt_i (shadow_inputs_q[0].data_gnt),
|
||||
.data_rvalid_i (shadow_inputs_q[0].data_rvalid),
|
||||
.data_we_o (shadow_outputs_d.data_we),
|
||||
.data_be_o (shadow_outputs_d.data_be),
|
||||
.data_addr_o (shadow_outputs_d.data_addr),
|
||||
.data_wdata_o (shadow_outputs_d.data_wdata),
|
||||
.data_rdata_i (shadow_inputs_q[0].data_rdata),
|
||||
.data_err_i (shadow_inputs_q[0].data_err),
|
||||
|
||||
.dummy_instr_id_o (shadow_outputs_d.dummy_instr_id),
|
||||
.rf_raddr_a_o (shadow_outputs_d.rf_raddr_a),
|
||||
.rf_raddr_b_o (shadow_outputs_d.rf_raddr_b),
|
||||
.rf_waddr_wb_o (shadow_outputs_d.rf_waddr_wb),
|
||||
.rf_we_wb_o (shadow_outputs_d.rf_we_wb),
|
||||
.rf_wdata_wb_ecc_o (shadow_outputs_d.rf_wdata_wb_ecc),
|
||||
.rf_rdata_a_ecc_i (shadow_inputs_q[0].rf_rdata_a_ecc),
|
||||
.rf_rdata_b_ecc_i (shadow_inputs_q[0].rf_rdata_b_ecc),
|
||||
.dummy_instr_id_o (shadow_outputs_d.dummy_instr_id),
|
||||
.rf_raddr_a_o (shadow_outputs_d.rf_raddr_a),
|
||||
.rf_raddr_b_o (shadow_outputs_d.rf_raddr_b),
|
||||
.rf_waddr_wb_o (shadow_outputs_d.rf_waddr_wb),
|
||||
.rf_we_wb_o (shadow_outputs_d.rf_we_wb),
|
||||
.rf_wdata_wb_ecc_o (shadow_outputs_d.rf_wdata_wb_ecc),
|
||||
.rf_rdata_a_ecc_i (shadow_inputs_q[0].rf_rdata_a_ecc),
|
||||
.rf_rdata_b_ecc_i (shadow_inputs_q[0].rf_rdata_b_ecc),
|
||||
|
||||
.ic_tag_req_o (shadow_outputs_d.ic_tag_req),
|
||||
.ic_tag_write_o (shadow_outputs_d.ic_tag_write),
|
||||
.ic_tag_addr_o (shadow_outputs_d.ic_tag_addr),
|
||||
.ic_tag_wdata_o (shadow_outputs_d.ic_tag_wdata),
|
||||
.ic_tag_rdata_i (shadow_tag_rdata_q[0]),
|
||||
.ic_data_req_o (shadow_outputs_d.ic_data_req),
|
||||
.ic_data_write_o (shadow_outputs_d.ic_data_write),
|
||||
.ic_data_addr_o (shadow_outputs_d.ic_data_addr),
|
||||
.ic_data_wdata_o (shadow_outputs_d.ic_data_wdata),
|
||||
.ic_data_rdata_i (shadow_data_rdata_q[0]),
|
||||
.ic_tag_req_o (shadow_outputs_d.ic_tag_req),
|
||||
.ic_tag_write_o (shadow_outputs_d.ic_tag_write),
|
||||
.ic_tag_addr_o (shadow_outputs_d.ic_tag_addr),
|
||||
.ic_tag_wdata_o (shadow_outputs_d.ic_tag_wdata),
|
||||
.ic_tag_rdata_i (shadow_tag_rdata_q[0]),
|
||||
.ic_data_req_o (shadow_outputs_d.ic_data_req),
|
||||
.ic_data_write_o (shadow_outputs_d.ic_data_write),
|
||||
.ic_data_addr_o (shadow_outputs_d.ic_data_addr),
|
||||
.ic_data_wdata_o (shadow_outputs_d.ic_data_wdata),
|
||||
.ic_data_rdata_i (shadow_data_rdata_q[0]),
|
||||
.ic_scr_key_valid_i (shadow_inputs_q[0].ic_scr_key_valid),
|
||||
|
||||
.irq_software_i (shadow_inputs_q[0].irq_software),
|
||||
.irq_timer_i (shadow_inputs_q[0].irq_timer),
|
||||
.irq_external_i (shadow_inputs_q[0].irq_external),
|
||||
.irq_fast_i (shadow_inputs_q[0].irq_fast),
|
||||
.irq_nm_i (shadow_inputs_q[0].irq_nm),
|
||||
.irq_pending_o (shadow_outputs_d.irq_pending),
|
||||
.irq_software_i (shadow_inputs_q[0].irq_software),
|
||||
.irq_timer_i (shadow_inputs_q[0].irq_timer),
|
||||
.irq_external_i (shadow_inputs_q[0].irq_external),
|
||||
.irq_fast_i (shadow_inputs_q[0].irq_fast),
|
||||
.irq_nm_i (shadow_inputs_q[0].irq_nm),
|
||||
.irq_pending_o (shadow_outputs_d.irq_pending),
|
||||
|
||||
.debug_req_i (shadow_inputs_q[0].debug_req),
|
||||
.crash_dump_o (shadow_outputs_d.crash_dump),
|
||||
.debug_req_i (shadow_inputs_q[0].debug_req),
|
||||
.crash_dump_o (shadow_outputs_d.crash_dump),
|
||||
|
||||
`ifdef RVFI
|
||||
.rvfi_valid (),
|
||||
|
@ -452,6 +459,7 @@ module ibex_lockstep import ibex_pkg::*; #(
|
|||
.fetch_enable_i (shadow_inputs_q[0].fetch_enable),
|
||||
.alert_minor_o (shadow_alert_minor),
|
||||
.alert_major_o (shadow_alert_major),
|
||||
.icache_inval_o (shadow_outputs_d.icache_inval),
|
||||
.core_busy_o (shadow_outputs_d.core_busy)
|
||||
);
|
||||
|
||||
|
|
|
@ -330,22 +330,25 @@ package ibex_pkg;
|
|||
} dbg_cause_e;
|
||||
|
||||
// ICache constants
|
||||
parameter int unsigned ADDR_W = 32;
|
||||
parameter int unsigned BUS_SIZE = 32;
|
||||
parameter int unsigned BUS_BYTES = BUS_SIZE/8;
|
||||
parameter int unsigned BUS_W = $clog2(BUS_BYTES);
|
||||
parameter int unsigned IC_SIZE_BYTES = 4096;
|
||||
parameter int unsigned IC_NUM_WAYS = 2;
|
||||
parameter int unsigned IC_LINE_SIZE = 64;
|
||||
parameter int unsigned IC_LINE_BYTES = IC_LINE_SIZE/8;
|
||||
parameter int unsigned IC_LINE_W = $clog2(IC_LINE_BYTES);
|
||||
parameter int unsigned IC_NUM_LINES = IC_SIZE_BYTES / IC_NUM_WAYS / IC_LINE_BYTES;
|
||||
parameter int unsigned IC_LINE_BEATS = IC_LINE_BYTES / BUS_BYTES;
|
||||
parameter int unsigned IC_LINE_BEATS_W = $clog2(IC_LINE_BEATS);
|
||||
parameter int unsigned IC_INDEX_W = $clog2(IC_NUM_LINES);
|
||||
parameter int unsigned IC_INDEX_HI = IC_INDEX_W + IC_LINE_W - 1;
|
||||
parameter int unsigned IC_TAG_SIZE = ADDR_W - IC_INDEX_W - IC_LINE_W + 1; // 1 valid bit
|
||||
parameter int unsigned IC_OUTPUT_BEATS = (BUS_BYTES / 2); // number of halfwords
|
||||
parameter int unsigned ADDR_W = 32;
|
||||
parameter int unsigned BUS_SIZE = 32;
|
||||
parameter int unsigned BUS_BYTES = BUS_SIZE/8;
|
||||
parameter int unsigned BUS_W = $clog2(BUS_BYTES);
|
||||
parameter int unsigned IC_SIZE_BYTES = 4096;
|
||||
parameter int unsigned IC_NUM_WAYS = 2;
|
||||
parameter int unsigned IC_LINE_SIZE = 64;
|
||||
parameter int unsigned IC_LINE_BYTES = IC_LINE_SIZE/8;
|
||||
parameter int unsigned IC_LINE_W = $clog2(IC_LINE_BYTES);
|
||||
parameter int unsigned IC_NUM_LINES = IC_SIZE_BYTES / IC_NUM_WAYS / IC_LINE_BYTES;
|
||||
parameter int unsigned IC_LINE_BEATS = IC_LINE_BYTES / BUS_BYTES;
|
||||
parameter int unsigned IC_LINE_BEATS_W = $clog2(IC_LINE_BEATS);
|
||||
parameter int unsigned IC_INDEX_W = $clog2(IC_NUM_LINES);
|
||||
parameter int unsigned IC_INDEX_HI = IC_INDEX_W + IC_LINE_W - 1;
|
||||
parameter int unsigned IC_TAG_SIZE = ADDR_W - IC_INDEX_W - IC_LINE_W + 1; // 1 valid bit
|
||||
parameter int unsigned IC_OUTPUT_BEATS = (BUS_BYTES / 2); // number of halfwords
|
||||
// ICache Scrambling Parameters
|
||||
parameter int unsigned SCRAMBLE_KEY_W = 128;
|
||||
parameter int unsigned SCRAMBLE_NONCE_W = 64;
|
||||
|
||||
// PMP constants
|
||||
parameter int unsigned PMP_MAX_REGIONS = 16;
|
||||
|
@ -599,5 +602,8 @@ package ibex_pkg;
|
|||
parameter lfsr_perm_t RndCnstLfsrPermDefault = {
|
||||
160'h1e35ecba467fd1b12e958152c04fa43878a8daed
|
||||
};
|
||||
|
||||
parameter logic [SCRAMBLE_KEY_W-1:0] RndCnstIbexKeyDefault =
|
||||
128'h14e8cecae3040d5e12286bb3cc113298;
|
||||
parameter logic [SCRAMBLE_NONCE_W-1:0] RndCnstIbexNonceDefault =
|
||||
64'hf79780bc735f3843;
|
||||
endpackage
|
||||
|
|
202
rtl/ibex_top.sv
202
rtl/ibex_top.sv
|
@ -30,10 +30,14 @@ module ibex_top import ibex_pkg::*; #(
|
|||
parameter bit DbgTriggerEn = 1'b0,
|
||||
parameter int unsigned DbgHwBreakNum = 1,
|
||||
parameter bit SecureIbex = 1'b0,
|
||||
parameter bit ICacheScramble = 1'b0,
|
||||
parameter lfsr_seed_t RndCnstLfsrSeed = RndCnstLfsrSeedDefault,
|
||||
parameter lfsr_perm_t RndCnstLfsrPerm = RndCnstLfsrPermDefault,
|
||||
parameter int unsigned DmHaltAddr = 32'h1A110800,
|
||||
parameter int unsigned DmExceptionAddr = 32'h1A110808
|
||||
parameter int unsigned DmExceptionAddr = 32'h1A110808,
|
||||
// Default seed and nonce for scrambling
|
||||
parameter logic [SCRAMBLE_KEY_W-1:0] RndCnstIbexKey = RndCnstIbexKeyDefault,
|
||||
parameter logic [SCRAMBLE_NONCE_W-1:0] RndCnstIbexNonce = RndCnstIbexNonceDefault
|
||||
) (
|
||||
// Clock and Reset
|
||||
input logic clk_i,
|
||||
|
@ -74,6 +78,12 @@ module ibex_top import ibex_pkg::*; #(
|
|||
input logic [14:0] irq_fast_i,
|
||||
input logic irq_nm_i, // non-maskeable interrupt
|
||||
|
||||
// Scrambling Interface
|
||||
input logic scramble_key_valid_i,
|
||||
input logic [SCRAMBLE_KEY_W-1:0] scramble_key_i,
|
||||
input logic [SCRAMBLE_NONCE_W-1:0] scramble_nonce_i,
|
||||
output logic scramble_req_o,
|
||||
|
||||
// Debug Interface
|
||||
input logic debug_req_i,
|
||||
output crash_dump_t crash_dump_o,
|
||||
|
@ -130,6 +140,9 @@ module ibex_top import ibex_pkg::*; #(
|
|||
localparam int unsigned BusSizeECC = ICacheECC ? (BUS_SIZE + 7) : BUS_SIZE;
|
||||
localparam int unsigned LineSizeECC = BusSizeECC * IC_LINE_BEATS;
|
||||
localparam int unsigned TagSizeECC = ICacheECC ? (IC_TAG_SIZE + 6) : IC_TAG_SIZE;
|
||||
// Scrambling Parameter
|
||||
localparam int unsigned NumAddrScrRounds = ICacheScramble ? 2 : 0;
|
||||
localparam int unsigned NumDiffRounds = NumAddrScrRounds;
|
||||
|
||||
// Clock signals
|
||||
logic clk;
|
||||
|
@ -159,6 +172,12 @@ module ibex_top import ibex_pkg::*; #(
|
|||
// Alert signals
|
||||
logic core_alert_major, core_alert_minor;
|
||||
logic lockstep_alert_major, lockstep_alert_minor;
|
||||
// Scramble signals
|
||||
logic icache_inval;
|
||||
logic [SCRAMBLE_KEY_W-1:0] scramble_key_q;
|
||||
logic [SCRAMBLE_NONCE_W-1:0] scramble_nonce_q;
|
||||
logic scramble_key_valid_d, scramble_key_valid_q;
|
||||
logic scramble_req_d, scramble_req_q;
|
||||
|
||||
/////////////////////
|
||||
// Main clock gate //
|
||||
|
@ -247,16 +266,17 @@ module ibex_top import ibex_pkg::*; #(
|
|||
.rf_rdata_a_ecc_i (rf_rdata_a_ecc),
|
||||
.rf_rdata_b_ecc_i (rf_rdata_b_ecc),
|
||||
|
||||
.ic_tag_req_o (ic_tag_req),
|
||||
.ic_tag_write_o (ic_tag_write),
|
||||
.ic_tag_addr_o (ic_tag_addr),
|
||||
.ic_tag_wdata_o (ic_tag_wdata),
|
||||
.ic_tag_rdata_i (ic_tag_rdata),
|
||||
.ic_data_req_o (ic_data_req),
|
||||
.ic_data_write_o(ic_data_write),
|
||||
.ic_data_addr_o (ic_data_addr),
|
||||
.ic_data_wdata_o(ic_data_wdata),
|
||||
.ic_data_rdata_i(ic_data_rdata),
|
||||
.ic_tag_req_o (ic_tag_req),
|
||||
.ic_tag_write_o (ic_tag_write),
|
||||
.ic_tag_addr_o (ic_tag_addr),
|
||||
.ic_tag_wdata_o (ic_tag_wdata),
|
||||
.ic_tag_rdata_i (ic_tag_rdata),
|
||||
.ic_data_req_o (ic_data_req),
|
||||
.ic_data_write_o (ic_data_write),
|
||||
.ic_data_addr_o (ic_data_addr),
|
||||
.ic_data_wdata_o (ic_data_wdata),
|
||||
.ic_data_rdata_i (ic_data_rdata),
|
||||
.ic_scr_key_valid_i(scramble_key_valid_q),
|
||||
|
||||
.irq_software_i,
|
||||
.irq_timer_i,
|
||||
|
@ -299,9 +319,10 @@ module ibex_top import ibex_pkg::*; #(
|
|||
`endif
|
||||
|
||||
.fetch_enable_i,
|
||||
.alert_minor_o(core_alert_minor),
|
||||
.alert_major_o(core_alert_major),
|
||||
.core_busy_o (core_busy_d)
|
||||
.alert_minor_o (core_alert_minor),
|
||||
.alert_major_o (core_alert_major),
|
||||
.icache_inval_o(icache_inval),
|
||||
.core_busy_o (core_busy_d)
|
||||
);
|
||||
|
||||
/////////////////////////////////
|
||||
|
@ -373,6 +394,58 @@ module ibex_top import ibex_pkg::*; #(
|
|||
);
|
||||
end
|
||||
|
||||
///////////////////////////////
|
||||
// Scrambling Infrastructure //
|
||||
///////////////////////////////
|
||||
|
||||
if (ICacheScramble) begin : gen_scramble
|
||||
|
||||
// Scramble key valid starts with OTP returning new valid key and stays high
|
||||
// until we request a new valid key.
|
||||
assign scramble_key_valid_d = scramble_req_q ? scramble_key_valid_i :
|
||||
icache_inval ? 1'b0 :
|
||||
scramble_key_valid_q;
|
||||
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
scramble_key_q <= RndCnstIbexKey;
|
||||
scramble_nonce_q <= RndCnstIbexNonce;
|
||||
end else if (scramble_key_valid_i) begin
|
||||
scramble_key_q <= scramble_key_i;
|
||||
scramble_nonce_q <= scramble_nonce_i;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if (!rst_ni) begin
|
||||
scramble_key_valid_q <= 1'b1;
|
||||
scramble_req_q <= '0;
|
||||
end else begin
|
||||
scramble_key_valid_q <= scramble_key_valid_d;
|
||||
scramble_req_q <= scramble_req_d;
|
||||
end
|
||||
end
|
||||
|
||||
// Scramble key request starts with invalidate signal from ICache and stays high
|
||||
// until we got a valid key.
|
||||
assign scramble_req_d = scramble_req_q ? ~scramble_key_valid_i : icache_inval;
|
||||
assign scramble_req_o = scramble_req_q;
|
||||
|
||||
end else begin : gen_noscramble
|
||||
|
||||
logic unused_scramble_inputs = scramble_key_valid_i & (|scramble_key_i) & (|RndCnstIbexKey) &
|
||||
(|scramble_nonce_i) & (|RndCnstIbexNonce) & scramble_req_q &
|
||||
icache_inval & scramble_key_valid_d & scramble_req_d;
|
||||
|
||||
assign scramble_req_d = 1'b0;
|
||||
assign scramble_req_q = 1'b0;
|
||||
assign scramble_req_o = 1'b0;
|
||||
assign scramble_key_q = '0;
|
||||
assign scramble_nonce_q = '0;
|
||||
assign scramble_key_valid_q = 1'b1;
|
||||
assign scramble_key_valid_d = 1'b1;
|
||||
end
|
||||
|
||||
////////////////////////
|
||||
// Rams Instantiation //
|
||||
////////////////////////
|
||||
|
@ -380,35 +453,72 @@ module ibex_top import ibex_pkg::*; #(
|
|||
if (ICache) begin : gen_rams
|
||||
|
||||
for (genvar way = 0; way < IC_NUM_WAYS; way++) begin : gen_rams_inner
|
||||
|
||||
// Tag RAM instantiation
|
||||
prim_ram_1p #(
|
||||
.Width (TagSizeECC),
|
||||
.Depth (IC_NUM_LINES),
|
||||
.DataBitsPerMask(TagSizeECC)
|
||||
prim_ram_1p_scr #(
|
||||
.Width (TagSizeECC),
|
||||
.Depth (IC_NUM_LINES),
|
||||
.DataBitsPerMask (TagSizeECC),
|
||||
.EnableParity (0),
|
||||
.DiffWidth (TagSizeECC),
|
||||
.NumAddrScrRounds (NumAddrScrRounds),
|
||||
.NumDiffRounds (NumDiffRounds)
|
||||
) tag_bank (
|
||||
.clk_i (clk_i),
|
||||
.req_i (ic_tag_req[way]),
|
||||
.cfg_i (ram_cfg_i),
|
||||
.write_i(ic_tag_write),
|
||||
.wmask_i({TagSizeECC{1'b1}}),
|
||||
.addr_i (ic_tag_addr),
|
||||
.wdata_i(ic_tag_wdata),
|
||||
.rdata_o(ic_tag_rdata[way])
|
||||
.clk_i,
|
||||
.rst_ni,
|
||||
|
||||
.key_valid_i (scramble_key_valid_q),
|
||||
.key_i (scramble_key_q),
|
||||
.nonce_i (scramble_nonce_q),
|
||||
|
||||
.req_i (ic_tag_req[way]),
|
||||
|
||||
.gnt_o (),
|
||||
.write_i (ic_tag_write),
|
||||
.addr_i (ic_tag_addr),
|
||||
.wdata_i (ic_tag_wdata),
|
||||
.wmask_i ({TagSizeECC{1'b1}}),
|
||||
.intg_error_i(1'b0),
|
||||
|
||||
.rdata_o (ic_tag_rdata[way]),
|
||||
.rvalid_o (),
|
||||
.raddr_o (),
|
||||
.rerror_o (),
|
||||
.cfg_i (ram_cfg_i)
|
||||
);
|
||||
|
||||
// Data RAM instantiation
|
||||
prim_ram_1p #(
|
||||
.Width (LineSizeECC),
|
||||
.Depth (IC_NUM_LINES),
|
||||
.DataBitsPerMask(LineSizeECC)
|
||||
prim_ram_1p_scr #(
|
||||
.Width (LineSizeECC),
|
||||
.Depth (IC_NUM_LINES),
|
||||
.DataBitsPerMask (LineSizeECC),
|
||||
.ReplicateKeyStream (1),
|
||||
.EnableParity (0),
|
||||
.DiffWidth (LineSizeECC),
|
||||
.NumAddrScrRounds (NumAddrScrRounds),
|
||||
.NumDiffRounds (NumDiffRounds)
|
||||
) data_bank (
|
||||
.clk_i (clk_i),
|
||||
.req_i (ic_data_req[way]),
|
||||
.cfg_i (ram_cfg_i),
|
||||
.write_i(ic_data_write),
|
||||
.wmask_i({LineSizeECC{1'b1}}),
|
||||
.addr_i (ic_data_addr),
|
||||
.wdata_i(ic_data_wdata),
|
||||
.rdata_o(ic_data_rdata[way])
|
||||
.clk_i,
|
||||
.rst_ni,
|
||||
|
||||
.key_valid_i (scramble_key_valid_q),
|
||||
.key_i (scramble_key_q),
|
||||
.nonce_i (scramble_nonce_q),
|
||||
|
||||
.req_i (ic_data_req[way]),
|
||||
|
||||
.gnt_o (),
|
||||
.write_i (ic_data_write),
|
||||
.addr_i (ic_data_addr),
|
||||
.wdata_i (ic_data_wdata),
|
||||
.wmask_i ({LineSizeECC{1'b1}}),
|
||||
.intg_error_i(1'b0),
|
||||
|
||||
.rdata_o (ic_data_rdata[way]),
|
||||
.rvalid_o (),
|
||||
.raddr_o (),
|
||||
.rerror_o (),
|
||||
.cfg_i (ram_cfg_i)
|
||||
);
|
||||
end
|
||||
|
||||
|
@ -419,7 +529,11 @@ module ibex_top import ibex_pkg::*; #(
|
|||
|
||||
assign unused_ram_cfg = ram_cfg_i;
|
||||
assign unused_ram_inputs = (|ic_tag_req) & ic_tag_write & (|ic_tag_addr) & (|ic_tag_wdata) &
|
||||
(|ic_data_req) & ic_data_write & (|ic_data_addr) & (|ic_data_wdata);
|
||||
(|ic_data_req) & ic_data_write & (|ic_data_addr) & (|ic_data_wdata) &
|
||||
(|scramble_key_q) & (|scramble_nonce_q) & scramble_key_valid_q &
|
||||
scramble_key_valid_d & (|scramble_nonce_q) &
|
||||
(|NumAddrScrRounds);
|
||||
|
||||
assign ic_tag_rdata = '{default:'b0};
|
||||
assign ic_data_rdata = '{default:'b0};
|
||||
|
||||
|
@ -469,6 +583,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
ic_data_write,
|
||||
ic_data_addr,
|
||||
ic_data_wdata,
|
||||
scramble_key_valid_i,
|
||||
irq_software_i,
|
||||
irq_timer_i,
|
||||
irq_external_i,
|
||||
|
@ -478,6 +593,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
debug_req_i,
|
||||
crash_dump_o,
|
||||
fetch_enable_i,
|
||||
icache_inval,
|
||||
core_busy_d
|
||||
});
|
||||
|
||||
|
@ -523,6 +639,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
logic ic_data_write_local;
|
||||
logic [IC_INDEX_W-1:0] ic_data_addr_local;
|
||||
logic [LineSizeECC-1:0] ic_data_wdata_local;
|
||||
logic scramble_key_valid_local;
|
||||
|
||||
logic irq_software_local;
|
||||
logic irq_timer_local;
|
||||
|
@ -535,6 +652,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
crash_dump_t crash_dump_local;
|
||||
logic fetch_enable_local;
|
||||
|
||||
logic icache_inval_local;
|
||||
logic core_busy_local;
|
||||
|
||||
assign buf_in = {
|
||||
|
@ -573,6 +691,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
ic_data_write,
|
||||
ic_data_addr,
|
||||
ic_data_wdata,
|
||||
scramble_key_valid_q,
|
||||
irq_software_i,
|
||||
irq_timer_i,
|
||||
irq_external_i,
|
||||
|
@ -582,6 +701,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
debug_req_i,
|
||||
crash_dump_o,
|
||||
fetch_enable_i,
|
||||
icache_inval,
|
||||
core_busy_d
|
||||
};
|
||||
|
||||
|
@ -621,6 +741,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
ic_data_write_local,
|
||||
ic_data_addr_local,
|
||||
ic_data_wdata_local,
|
||||
scramble_key_valid_local,
|
||||
irq_software_local,
|
||||
irq_timer_local,
|
||||
irq_external_local,
|
||||
|
@ -630,6 +751,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
debug_req_local,
|
||||
crash_dump_local,
|
||||
fetch_enable_local,
|
||||
icache_inval_local,
|
||||
core_busy_local
|
||||
} = buf_out;
|
||||
|
||||
|
@ -727,6 +849,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
.ic_data_addr_i (ic_data_addr_local),
|
||||
.ic_data_wdata_i (ic_data_wdata_local),
|
||||
.ic_data_rdata_i (ic_data_rdata_local),
|
||||
.ic_scr_key_valid_i (scramble_key_valid_local),
|
||||
|
||||
.irq_software_i (irq_software_local),
|
||||
.irq_timer_i (irq_timer_local),
|
||||
|
@ -741,6 +864,7 @@ module ibex_top import ibex_pkg::*; #(
|
|||
.fetch_enable_i (fetch_enable_local),
|
||||
.alert_minor_o (lockstep_alert_minor_local),
|
||||
.alert_major_o (lockstep_alert_major_local),
|
||||
.icache_inval_i (icache_inval_local),
|
||||
.core_busy_i (core_busy_local),
|
||||
.test_en_i (test_en_i),
|
||||
.scan_rst_ni (scan_rst_ni)
|
||||
|
|
|
@ -24,6 +24,7 @@ module ibex_top_tracing import ibex_pkg::*; #(
|
|||
parameter bit DbgTriggerEn = 1'b0,
|
||||
parameter int unsigned DbgHwBreakNum = 1,
|
||||
parameter bit SecureIbex = 1'b0,
|
||||
parameter bit ICacheScramble = 1'b0,
|
||||
parameter lfsr_seed_t RndCnstLfsrSeed = RndCnstLfsrSeedDefault,
|
||||
parameter lfsr_perm_t RndCnstLfsrPerm = RndCnstLfsrPermDefault,
|
||||
parameter int unsigned DmHaltAddr = 32'h1A110800,
|
||||
|
@ -70,6 +71,12 @@ module ibex_top_tracing import ibex_pkg::*; #(
|
|||
input logic [14:0] irq_fast_i,
|
||||
input logic irq_nm_i, // non-maskeable interrupt
|
||||
|
||||
// Scrambling Interface
|
||||
input logic scramble_key_valid_i,
|
||||
input logic [SCRAMBLE_KEY_W-1:0] scramble_key_i,
|
||||
input logic [SCRAMBLE_NONCE_W-1:0] scramble_nonce_i,
|
||||
output logic scramble_req_o,
|
||||
|
||||
// Debug Interface
|
||||
input logic debug_req_i,
|
||||
output crash_dump_t crash_dump_o,
|
||||
|
@ -145,6 +152,7 @@ module ibex_top_tracing import ibex_pkg::*; #(
|
|||
.DbgHwBreakNum ( DbgHwBreakNum ),
|
||||
.WritebackStage ( WritebackStage ),
|
||||
.SecureIbex ( SecureIbex ),
|
||||
.ICacheScramble ( ICacheScramble ),
|
||||
.RndCnstLfsrSeed ( RndCnstLfsrSeed ),
|
||||
.RndCnstLfsrPerm ( RndCnstLfsrPerm ),
|
||||
.DmHaltAddr ( DmHaltAddr ),
|
||||
|
@ -186,6 +194,11 @@ module ibex_top_tracing import ibex_pkg::*; #(
|
|||
.irq_fast_i,
|
||||
.irq_nm_i,
|
||||
|
||||
.scramble_key_valid_i,
|
||||
.scramble_key_i,
|
||||
.scramble_nonce_i,
|
||||
.scramble_req_o,
|
||||
|
||||
.debug_req_i,
|
||||
.crash_dump_o,
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue