diff --git a/config/config.mk b/config/config.mk index 0691071c8..f30040cb5 100644 --- a/config/config.mk +++ b/config/config.mk @@ -68,3 +68,16 @@ xqueue_size ?= 0 # Enable the XpulpIMG extension xpulpimg ?= 1 + +# Enable the Reliability mode for RO caches and L1 and L0 icache +rel_rocache ?= 1 + +rel_l1icache ?= 1 + +rel_l0icache ?= 0 + +# Enable fault injection in the instruction caches and define fault rate (clok_periods/fault) + +fault_injection ?= 0 + +fault_rate ?= 100 \ No newline at end of file diff --git a/hardware/Makefile b/hardware/Makefile index 2faf4d255..9da9c0fba 100644 --- a/hardware/Makefile +++ b/hardware/Makefile @@ -47,6 +47,8 @@ verilator_top ?= mempool_tb_verilator python ?= python3 # Enable tracing snitch_trace ?= 0 +# Enable faults +icache_faults ?= 0 # Check if the specified QuestaSim version exists ifeq (, $(shell which $(questa_cmd))) @@ -97,6 +99,7 @@ vlog_defs += -DRO_LINE_WIDTH=$(ro_line_width) vlog_defs += -DDMAS_PER_GROUP=$(dmas_per_group) vlog_defs += -DAXI_HIER_RADIX=$(axi_hier_radix) -DAXI_MASTERS_PER_GROUP=$(axi_masters_per_group) vlog_defs += -DSEQ_MEM_SIZE=$(seq_mem_size) -DXQUEUE_SIZE=$(xqueue_size) +vlog_defs += -DREL_ROCACHE=$(rel_rocache) -DREL_L1ICACHE=$(rel_l1icache) -DREL_L0ICACHE=$(rel_l0icache) # Traffic generation enabled ifdef tg diff --git a/hardware/deps/snitch/Bender.yml b/hardware/deps/snitch/Bender.yml index 24427e910..9200fa1ee 100644 --- a/hardware/deps/snitch/Bender.yml +++ b/hardware/deps/snitch/Bender.yml @@ -38,3 +38,9 @@ sources: - src/snitch_icache/snitch_icache_refill.sv - src/snitch_read_only_cache/snitch_axi_to_cache.sv - src/snitch_read_only_cache/snitch_read_only_cache.sv + + - target: rocache_test + files: + #- ../../tb/RO_cache_tb/sourcecode/tb/snitch_read_only_cache_tb.sv + - tb/src/snitch_read_only_cache_tb.sv + diff --git a/hardware/deps/snitch/src/snitch_icache/snitch_icache.sv b/hardware/deps/snitch/src/snitch_icache/snitch_icache.sv index 56615429b..6a026b53e 100644 --- a/hardware/deps/snitch/src/snitch_icache/snitch_icache.sv +++ b/hardware/deps/snitch/src/snitch_icache/snitch_icache.sv @@ -26,6 +26,10 @@ module snitch_icache #( parameter int FILL_AW = -1, /// Fill interface data width. Power of two; >= 8. parameter int FILL_DW = -1, + /// Add parity checks for the L1 caches + parameter bit RELIABILITY_L1 = 0, + /// Add parity checks for the L0 caches + parameter bit RELIABILITY_L0 = 0, /// Replace the L1 tag banks with latch-based SCM. parameter bit L1_TAG_SCM = 0, /// This reduces area impact at the cost of @@ -42,6 +46,7 @@ module snitch_icache #( parameter int L0_EARLY_TAG_WIDTH = -1, /// Operate L0 cache in slower clock-domain parameter bit ISO_CROSSING = 1, + parameter type axi_req_t = logic, parameter type axi_rsp_t = logic ) ( @@ -79,6 +84,8 @@ module snitch_icache #( FETCH_DW: FETCH_DW, FILL_AW: FILL_AW, FILL_DW: FILL_DW, + RELIABILITY_L1: RELIABILITY_L1, + RELIABILITY_L0: RELIABILITY_L0, L1_TAG_SCM: L1_TAG_SCM, EARLY_LATCH: EARLY_LATCH, BUFFER_LOOKUP: 0, diff --git a/hardware/deps/snitch/src/snitch_icache/snitch_icache_handler.sv b/hardware/deps/snitch/src/snitch_icache/snitch_icache_handler.sv index 04d9dabce..f149a70ea 100644 --- a/hardware/deps/snitch/src/snitch_icache/snitch_icache_handler.sv +++ b/hardware/deps/snitch/src/snitch_icache/snitch_icache_handler.sv @@ -152,7 +152,7 @@ module snitch_icache_handler #( .empty_o ( ) ); - // Gurarntee ordering + // Guarantee ordering // Check if there is a miss in flight from this ID. In that case, stall all // further requests to guarantee correct ordering of requests. logic [CFG.ID_WIDTH_RESP-1:0] miss_in_flight_d, miss_in_flight_q; @@ -214,8 +214,8 @@ module snitch_icache_handler #( in_req_ready_o = hit_ready; // The cache lookup was a miss, but there is already a pending - // refill that covers the line. - end else if (pending) begin + // refill that covers the line and the lookup accepted the request. + end else if (pending && !(write_valid_o && !write_ready_i)) begin push_index = pending_id; push_enable = 1; diff --git a/hardware/deps/snitch/src/snitch_icache/snitch_icache_l0.sv b/hardware/deps/snitch/src/snitch_icache/snitch_icache_l0.sv index 8906490fc..35f7f4546 100644 --- a/hardware/deps/snitch/src/snitch_icache/snitch_icache_l0.sv +++ b/hardware/deps/snitch/src/snitch_icache/snitch_icache_l0.sv @@ -37,17 +37,20 @@ module snitch_icache_l0 import snitch_icache_pkg::*; #( input logic out_rsp_valid_i, output logic out_rsp_ready_o ); + localparam bit RELIABILITY_MODE = CFG.RELIABILITY_L0; + localparam int DATA_PARITY_WIDTH = RELIABILITY_MODE ? 'd8 : '0; + localparam LINE_SPLIT = CFG.LINE_WIDTH/DATA_PARITY_WIDTH; typedef logic [CFG.FETCH_AW-1:0] addr_t; typedef struct packed { - logic [CFG.L0_TAG_WIDTH-1:0] tag; + logic [CFG.L0_TAG_WIDTH+RELIABILITY_MODE-1:0] tag; logic vld; } tag_t; logic [CFG.L0_TAG_WIDTH-1:0] addr_tag, addr_tag_prefetch; tag_t [CFG.L0_LINE_COUNT-1:0] tag; - logic [CFG.L0_LINE_COUNT-1:0][CFG.LINE_WIDTH-1:0] data; + logic [CFG.L0_LINE_COUNT-1:0][CFG.LINE_WIDTH+DATA_PARITY_WIDTH-1:0] data; logic [CFG.L0_LINE_COUNT-1:0] hit, hit_early, hit_prefetch; logic hit_early_is_onehot; @@ -83,6 +86,10 @@ module snitch_icache_l0 import snitch_icache_pkg::*; #( `FF(last_cycle_was_prefetch_q, latch_prefetch, '0) logic evict_because_miss, evict_because_prefetch; + + logic data_parity_error; + logic [CFG.L0_LINE_COUNT-1:0] tag_parity_error_vect; + logic [CFG.L0_LINE_COUNT-1:0] exp_tag_parity; typedef struct packed { logic is_prefetch; @@ -104,22 +111,53 @@ module snitch_icache_l0 import snitch_icache_pkg::*; #( // ------------ // Tag Compare // ------------ - for (genvar i = 0; i < CFG.L0_LINE_COUNT; i++) begin : gen_cmp_fetch - assign hit_early[i] = tag[i].vld & - (tag[i].tag[CFG.L0_EARLY_TAG_WIDTH-1:0] == addr_tag[CFG.L0_EARLY_TAG_WIDTH-1:0]); - // The two signals calculate the same. - if (CFG.L0_TAG_WIDTH == CFG.L0_EARLY_TAG_WIDTH) begin : gen_hit_assign - assign hit[i] = hit_early[i]; - // Compare the rest of the tag. - end else begin : gen_hit - assign hit[i] = hit_early[i] & - (tag[i].tag[CFG.L0_TAG_WIDTH-1:CFG.L0_EARLY_TAG_WIDTH] - == addr_tag[CFG.L0_TAG_WIDTH-1:CFG.L0_EARLY_TAG_WIDTH]); + + // For every line, compute the current parity bit + if (RELIABILITY_MODE) begin + for (genvar i = 0; i < CFG.L0_LINE_COUNT; i++) begin : gen_exp_parity + assign exp_tag_parity[i] = ~^tag[i].tag[CFG.L0_TAG_WIDTH-1:0]; end - assign hit_prefetch[i] = tag[i].vld & (tag[i].tag == addr_tag_prefetch); end - assign hit_any = |hit; + if (!RELIABILITY_MODE) begin + for (genvar i = 0; i < CFG.L0_LINE_COUNT; i++) begin : gen_cmp_fetch + assign hit_early[i] = tag[i].vld & + (tag[i].tag[CFG.L0_EARLY_TAG_WIDTH-1:0] == addr_tag[CFG.L0_EARLY_TAG_WIDTH-1:0]); + // The two signals calculate the same. + if (CFG.L0_TAG_WIDTH == CFG.L0_EARLY_TAG_WIDTH) begin : gen_hit_assign + assign hit[i] = hit_early[i]; + // Compare the rest of the tag. + end else begin : gen_hit + assign hit[i] = hit_early[i] & + (tag[i].tag[CFG.L0_TAG_WIDTH-1:CFG.L0_EARLY_TAG_WIDTH] + == addr_tag[CFG.L0_TAG_WIDTH-1:CFG.L0_EARLY_TAG_WIDTH]); + end + assign hit_prefetch[i] = tag[i].vld & (tag[i].tag[CFG.L0_TAG_WIDTH-1:0] == addr_tag_prefetch); + end + end else begin + for (genvar i = 0; i < CFG.L0_LINE_COUNT; i++) begin : gen_cmp_fetch + // Compute a parity error vector comparing the expected parity bit and the current one. + assign tag_parity_error_vect[i] = (exp_tag_parity[i] != tag[i].tag[CFG.L0_TAG_WIDTH]) && tag[i].vld; + assign hit_early[i] = tag[i].vld & + (tag[i].tag[CFG.L0_EARLY_TAG_WIDTH-1:0] == addr_tag[CFG.L0_EARLY_TAG_WIDTH-1:0]); + // The two signals calculate the same. + if (CFG.L0_TAG_WIDTH == CFG.L0_EARLY_TAG_WIDTH) begin : gen_hit_assign + assign hit[i] = hit_early[i] & !tag_parity_error_vect[i]; + // Compare the rest of the tag and corresponding parity error vector. + end else begin : gen_hit + assign hit[i] = hit_early[i] & + (tag[i].tag[CFG.L0_TAG_WIDTH-1:CFG.L0_EARLY_TAG_WIDTH] + == addr_tag[CFG.L0_TAG_WIDTH-1:CFG.L0_EARLY_TAG_WIDTH]) & !tag_parity_error_vect[i]; + end + assign hit_prefetch[i] = tag[i].vld & (tag[i].tag[CFG.L0_TAG_WIDTH-1:0] == addr_tag_prefetch); + end + end + if(RELIABILITY_MODE) begin + // A parity error in the data overwrites the hit into a miss + assign hit_any = |hit && !data_parity_error; + end else begin + assign hit_any = |hit; + end assign hit_prefetch_any = |hit_prefetch; assign miss = ~hit_any & in_valid_i & ~pending_refill_q; @@ -129,42 +167,89 @@ module snitch_icache_l0 import snitch_icache_pkg::*; #( .clk_o (clk_inv) ); + logic [DATA_PARITY_WIDTH-1:0] data_parity; + if (RELIABILITY_MODE) begin + // For every block of the configured block size, compute the parity bit + for (genvar j = 0; j < DATA_PARITY_WIDTH; j++) begin + assign data_parity[j] = ~^out_rsp_data_i[CFG.LINE_WIDTH - LINE_SPLIT*j -1 -: LINE_SPLIT]; + end + end for (genvar i = 0; i < CFG.L0_LINE_COUNT; i++) begin : gen_array // Tag Array - always_ff @(posedge clk_i or negedge rst_ni) begin - if (!rst_ni) begin - tag[i].vld <= 0; - tag[i].tag <= 0; - end else begin - if (evict_strb[i]) begin - tag[i].vld <= 1'b0; - tag[i].tag <= evict_because_prefetch ? addr_tag_prefetch : addr_tag; - end else if (validate_strb[i]) begin - tag[i].vld <= 1'b1; + if(!RELIABILITY_MODE) begin + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + tag[i].vld <= 0; + tag[i].tag <= 0; + end else begin + if (evict_strb[i]) begin + tag[i].vld <= 1'b0; + tag[i].tag <= evict_because_prefetch ? addr_tag_prefetch : addr_tag; + end else if (validate_strb[i]) begin + tag[i].vld <= 1'b1; + end + if (flush_strb[i]) begin + tag[i].vld <= 1'b0; + end end - if (flush_strb[i]) begin - tag[i].vld <= 1'b0; + end + if (CFG.EARLY_LATCH) begin : gen_latch + logic clk_vld; + tc_clk_gating i_clk_gate ( + .clk_i (clk_inv ), + .en_i (validate_strb[i]), + .test_en_i (1'b0 ), + .clk_o (clk_vld ) + ); + // Data Array + /* verilator lint_off NOLATCH */ + always_latch begin + if (clk_vld) begin + data[i] <= out_rsp_data_i; + end end + /* verilator lint_on NOLATCH */ + end else begin : gen_ff + `FFLNR(data[i], out_rsp_data_i, validate_strb[i], clk_i) end - end - if (CFG.EARLY_LATCH) begin : gen_latch - logic clk_vld; - tc_clk_gating i_clk_gate ( - .clk_i (clk_inv ), - .en_i (validate_strb[i]), - .test_en_i (1'b0 ), - .clk_o (clk_vld ) - ); - // Data Array - /* verilator lint_off NOLATCH */ - always_latch begin - if (clk_vld) begin - data[i] <= out_rsp_data_i; + end else begin + // Compute parity bit + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + tag[i].vld <= 0; + tag[i].tag <= 0; + end else begin + if (evict_strb[i]) begin + tag[i].vld <= 1'b0; + tag[i].tag <= evict_because_prefetch ? {~^addr_tag_prefetch, addr_tag_prefetch} : {~^addr_tag, addr_tag}; + end else if (validate_strb[i]) begin + tag[i].vld <= 1'b1; + end + if (flush_strb[i]) begin + tag[i].vld <= 1'b0; + end + end + end + if (CFG.EARLY_LATCH) begin : gen_latch + logic clk_vld; + tc_clk_gating i_clk_gate ( + .clk_i (clk_inv ), + .en_i (validate_strb[i]), + .test_en_i (1'b0 ), + .clk_o (clk_vld ) + ); + // Data Array + // Store both the parity and the data + /* verilator lint_off NOLATCH */ + always_latch begin + if (clk_vld) begin + data[i] <= {data_parity, out_rsp_data_i}; + end end + /* verilator lint_on NOLATCH */ + end else begin : gen_ff + `FFLNR(data[i], {data_parity, out_rsp_data_i}, validate_strb[i], clk_i) end - /* verilator lint_on NOLATCH */ - end else begin : gen_ff - `FFLNR(data[i], out_rsp_data_i, validate_strb[i], clk_i) end end @@ -172,6 +257,20 @@ module snitch_icache_l0 import snitch_icache_pkg::*; #( // HIT // ---- // we hit in the cache and there was a unique hit. + logic [CFG.L0_LINE_COUNT-1:0] data_parity_error_vect; + + if (RELIABILITY_MODE) begin + logic [CFG.L0_LINE_COUNT-1:0][DATA_PARITY_WIDTH-1:0] exp_data_parity; + // For every line, we compute the expected parity for every block, then we determine which lines are faulty + for (genvar i = 0; i < CFG.L0_LINE_COUNT; i++) begin + for (genvar j = 0; j < DATA_PARITY_WIDTH; j++) begin + assign exp_data_parity[i][j] = ~^data[i][CFG.LINE_WIDTH - LINE_SPLIT*j -1 -: LINE_SPLIT]; + end + assign data_parity_error_vect[i] = (exp_data_parity[i] != data[i][CFG.LINE_WIDTH+:DATA_PARITY_WIDTH]) && tag[i].vld; + end + // Check whether the currently selected data has an error + assign data_parity_error = data_parity_error_vect >> in_addr_i[CFG.LINE_ALIGN-1:CFG.FETCH_ALIGN]; + end assign in_ready_o = hit_any & hit_early_is_onehot; logic [CFG.LINE_WIDTH-1:0] ins_data; @@ -225,9 +324,24 @@ module snitch_icache_l0 import snitch_icache_pkg::*; #( // but didn't hit in the final comparison. flush_strb = ~hit & hit_early; end + if (RELIABILITY_MODE && tag_parity_error_vect!='0) begin + // Evict all tags that have a fault + flush_strb = flush_strb | tag_parity_error_vect; + end + if (RELIABILITY_MODE && data_parity_error_vect!='0) begin + // Evict entry that hit but has faults on the data + flush_strb = flush_strb | data_parity_error_vect; + end if (flush_valid_i) flush_strb = '1; end + // Send a warning that an error was detected + always @ (posedge clk_i) begin + if (RELIABILITY_MODE && tag_parity_error_vect != '0 && data_parity_error_vect != '0) $display("%t [l0cache]: tag and data fault: flushing tags: %b",$time, flush_strb); + else if (RELIABILITY_MODE && tag_parity_error_vect != '0) $display("%t [l0cache]: tag fault: flushing tags: %b",$time, flush_strb); + else if (RELIABILITY_MODE && data_parity_error_vect != '0) $display("%t [l0cache]: data fault: flushing tags: %b",$time, flush_strb); + end + `FF(cnt_q, cnt_d, '0) // ------------- diff --git a/hardware/deps/snitch/src/snitch_icache/snitch_icache_lookup_serial.sv b/hardware/deps/snitch/src/snitch_icache/snitch_icache_lookup_serial.sv index f9c1268a7..fd1179438 100644 --- a/hardware/deps/snitch/src/snitch_icache/snitch_icache_lookup_serial.sv +++ b/hardware/deps/snitch/src/snitch_icache/snitch_icache_lookup_serial.sv @@ -38,8 +38,9 @@ module snitch_icache_lookup_serial #( input logic write_valid_i, output logic write_ready_o ); - + localparam int RELIABILITY_MODE = CFG.RELIABILITY_L1; localparam int unsigned DATA_ADDR_WIDTH = $clog2(CFG.SET_COUNT) + CFG.COUNT_ALIGN; + localparam int unsigned DATA_PARITY_WIDTH = RELIABILITY_MODE ? 'd8 : '0; // TODO: propagate it up as a parameter `ifndef SYNTHESIS initial assert(CFG != '0); @@ -75,13 +76,19 @@ module snitch_icache_lookup_serial #( logic error; } tag_rsp_t; + typedef struct packed { + logic [CFG.FETCH_AW-1:0] addr; + logic [CFG.SET_ALIGN-1:0] cset; + logic parity_error; + } tag_inv_req_t; + logic req_valid, req_ready; logic req_handshake; - logic [CFG.COUNT_ALIGN-1:0] tag_addr; - logic [CFG.SET_COUNT-1:0] tag_enable; - logic [CFG.TAG_WIDTH+1:0] tag_wdata, tag_rdata [CFG.SET_COUNT]; - logic tag_write; + logic [CFG.COUNT_ALIGN-1:0] tag_addr; + logic [CFG.SET_COUNT-1:0] tag_enable; + logic [CFG.TAG_WIDTH+1+RELIABILITY_MODE:0] tag_wdata, tag_rdata [CFG.SET_COUNT]; + logic tag_write; tag_req_t tag_req_d, tag_req_q; tag_rsp_t tag_rsp_s, tag_rsp_d, tag_rsp_q, tag_rsp; @@ -90,36 +97,77 @@ module snitch_icache_lookup_serial #( logic [CFG.TAG_WIDTH-1:0] required_tag; logic [CFG.SET_COUNT-1:0] line_hit; + logic [CFG.SET_COUNT-1:0] tag_parity_error_d, tag_parity_error_q; + logic faulty_hit_valid, faulty_hit_ready, faulty_hit_d, faulty_hit_q; logic [DATA_ADDR_WIDTH-1:0] lookup_addr; logic [DATA_ADDR_WIDTH-1:0] write_addr; + tag_inv_req_t data_parity_inv_d, data_parity_inv_q; + logic data_fault_valid, data_fault_ready; + // Connect input requests to tag stage assign tag_req_d.addr = in_addr_i; assign tag_req_d.id = in_id_i; // Multiplex read and write access to the tag banks onto one port, prioritizing write accesses + + logic tag_parity_bit; + if (RELIABILITY_MODE) begin + always_comb begin + tag_parity_bit = ^write_tag_i; + if (init_phase) begin + tag_parity_bit = 1'b0; + end else if (data_fault_valid) begin + tag_parity_bit = 1'b1; + end else if (write_valid_i) begin + tag_parity_bit = ^write_tag_i; + end else if (faulty_hit_valid) begin + tag_parity_bit = 1'b1; + end else if (in_valid_i) begin + // read phase: write tag not used + end + tag_wdata [CFG.TAG_WIDTH+2] = tag_parity_bit; + end + end + + assign data_fault_valid = RELIABILITY_MODE ? data_parity_inv_q.parity_error : '0; always_comb begin - tag_addr = in_addr_i >> CFG.LINE_ALIGN; + tag_addr = in_addr_i >> CFG.LINE_ALIGN; //right shift to get {tag, index} bits tag_enable = '0; - tag_wdata = {1'b1, write_error_i, write_tag_i}; + tag_wdata[CFG.TAG_WIDTH+1:0] = {1'b1, write_error_i, write_tag_i}; tag_write = 1'b0; - write_ready_o = 1'b0; - in_ready_o = 1'b0; - req_valid = 1'b0; + write_ready_o = 1'b0; + in_ready_o = 1'b0; + req_valid = 1'b0; + data_fault_ready = 1'b0; + faulty_hit_ready = 1'b0; if (init_phase) begin tag_addr = init_count_q; tag_enable = '1; - tag_wdata = '0; + tag_wdata[CFG.TAG_WIDTH+1:0] = '0; tag_write = 1'b1; + end else if (data_fault_valid && RELIABILITY_MODE) begin + tag_addr = data_parity_inv_q.addr >> CFG.LINE_ALIGN; + tag_enable = $unsigned(1 << data_parity_inv_q.cset); + tag_wdata[CFG.TAG_WIDTH+1:0] = '0; + tag_write = 1'b1; + data_fault_ready = 1'b1; end else if (write_valid_i) begin // Write a refill request tag_addr = write_addr_i; tag_enable = $unsigned(1 << write_set_i); tag_write = 1'b1; write_ready_o = 1'b1; + end else if (faulty_hit_valid && RELIABILITY_MODE) begin // we need to set second bit (valid) of write data of the previous adress to 0 + // we do not accept read requests and we do not store data in the pipeline. + tag_addr = tag_req_q.addr >> CFG.LINE_ALIGN; // buffered version of in_addr_i + tag_enable = tag_parity_error_q; // which set must be written to (the one(s) with faults + tag_wdata[CFG.TAG_WIDTH+1:0] = '0; + tag_write = 1'b1; + faulty_hit_ready = 1'b1; end else if (in_valid_i) begin // Check cache tag_enable = '1; @@ -128,13 +176,24 @@ module snitch_icache_lookup_serial #( req_valid = 1'b1; end end + int unsigned tag_faults=0, data_faults=0; + always @ (posedge clk_i) begin + if(data_fault_valid && RELIABILITY_MODE) begin + $warning("%t [snitch_icache_lookup]: DataFault -> Invalidating address %h, index: %h, set %h", $time, data_parity_inv_q.addr, data_parity_inv_q.addr[CFG.LINE_ALIGN+:CFG.COUNT_ALIGN], data_parity_inv_q.cset); + data_faults = data_faults+1; + end + else if(faulty_hit_valid && RELIABILITY_MODE) begin + $warning("%t [snitch_icache_lookup]: TagFault -> Invalidating address %h, index: %h", $time, tag_req_q.addr, tag_req_q.addr[CFG.LINE_ALIGN+:CFG.COUNT_ALIGN]); + tag_faults = tag_faults+1; + end + end // Instantiate the tag sets. if (CFG.L1_TAG_SCM) begin : gen_scm for (genvar i = 0; i < CFG.SET_COUNT; i++) begin : g_sets latch_scm #( .ADDR_WIDTH ($clog2(CFG.LINE_COUNT)), - .DATA_WIDTH (CFG.TAG_WIDTH+2 ) + .DATA_WIDTH (CFG.TAG_WIDTH+2+RELIABILITY_MODE) ) i_tag ( .clk ( clk_i ), .ReadEnable ( tag_enable[i] && !tag_write ), @@ -146,13 +205,13 @@ module snitch_icache_lookup_serial #( ); end end else begin : gen_sram - logic [CFG.SET_COUNT*(CFG.TAG_WIDTH+2)-1:0] tag_rdata_flat; + logic [CFG.SET_COUNT*(CFG.TAG_WIDTH+2+RELIABILITY_MODE)-1:0] tag_rdata_flat; for (genvar i = 0; i < CFG.SET_COUNT; i++) begin : g_sets_rdata - assign tag_rdata[i] = tag_rdata_flat[i*(CFG.TAG_WIDTH+2)+:CFG.TAG_WIDTH+2]; + assign tag_rdata[i] = tag_rdata_flat[i*(CFG.TAG_WIDTH+2+RELIABILITY_MODE)+:CFG.TAG_WIDTH+2+RELIABILITY_MODE]; end tc_sram #( - .DataWidth ( (CFG.TAG_WIDTH+2) * CFG.SET_COUNT ), - .ByteWidth ( CFG.TAG_WIDTH+2 ), + .DataWidth ( (CFG.TAG_WIDTH+2+RELIABILITY_MODE) * CFG.SET_COUNT ), //we store set_count number of tags (and valid/error bits) in every line + .ByteWidth ( CFG.TAG_WIDTH+2+RELIABILITY_MODE ), .NumWords ( CFG.LINE_COUNT ), .NumPorts ( 1 ) ) i_tag ( @@ -167,29 +226,55 @@ module snitch_icache_lookup_serial #( ); end + // compute tag parity bit the cycle before reading the tag and buffer it + logic exp_tag_parity_bit_d, exp_tag_parity_bit_q; + + if (RELIABILITY_MODE) begin + assign exp_tag_parity_bit_d = ^(tag_req_d.addr >> (CFG.LINE_ALIGN + CFG.COUNT_ALIGN)); + `FFL(exp_tag_parity_bit_q, exp_tag_parity_bit_d, req_valid && req_ready, '0, clk_i, rst_ni); + end + // Determine which set hit always_comb begin automatic logic [CFG.SET_COUNT-1:0] errors; required_tag = tag_req_q.addr >> (CFG.LINE_ALIGN + CFG.COUNT_ALIGN); for (int i = 0; i < CFG.SET_COUNT; i++) begin - line_hit[i] = tag_rdata[i][CFG.TAG_WIDTH+1] && tag_rdata[i][CFG.TAG_WIDTH-1:0] == required_tag; - errors[i] = tag_rdata[i][CFG.TAG_WIDTH] && line_hit[i]; + line_hit[i] = tag_rdata[i][CFG.TAG_WIDTH+1] && tag_rdata[i][CFG.TAG_WIDTH-1:0] == required_tag; //check valid bit and tag + errors[i] = tag_rdata[i][CFG.TAG_WIDTH] && line_hit[i]; //check error bit end - tag_rsp_s.hit = |line_hit; tag_rsp_s.error = |errors; end + if (RELIABILITY_MODE) begin + for (genvar i = 0; i < CFG.SET_COUNT; i++) begin : gen_check + assign tag_parity_error_d[i] = ((tag_rdata[i][CFG.TAG_WIDTH+2] == exp_tag_parity_bit_q)) ? '0:'1; //check both ways' parity bit with expected one + end + assign tag_rsp_s.hit = |(line_hit & ~tag_parity_error_d); + assign faulty_hit_d = |(line_hit & tag_parity_error_d); //if correspondent bits are both one, there was a false hit + end else begin + assign tag_rsp_s.hit = |line_hit; + end + lzc #(.WIDTH(CFG.SET_COUNT)) i_lzc ( .in_i ( line_hit ), .cnt_o ( tag_rsp_s.cset ), .empty_o ( ) ); - + //assert property (@(negedge clk_i) |tag_enable && in_ready_o |-> ##1 $countones(line_hit) <=1 ) else $error("two or more sets hit!, index=%h, address=%h", tag_req_q.addr >> CFG.LINE_ALIGN, tag_req_q.addr); + //assert property (@(negedge clk_i) |tag_enable && in_ready_o |-> ##1 tag_rdata[0] != tag_rdata[1] || ((tag_rdata[0] == tag_rdata[1]) && !tag_rdata[0][CFG.TAG_WIDTH+1])) else $error("two sets with same tag!, tag1=%h, tag2=%h", tag_rdata[0], tag_rdata[1]); // Buffer the metadata on a valid handshake. Stall on write (implicit in req_valid/ready) `FFL(tag_req_q, tag_req_d, req_valid && req_ready, '0, clk_i, rst_ni) + if(RELIABILITY_MODE) begin + // save faulty sets and clear when upstream invalidated them + `FFL(tag_parity_error_q, tag_parity_error_d, req_valid && req_ready, '0, clk_i, rst_ni) + `FFL(faulty_hit_q, (faulty_hit_ready && !(req_valid && req_ready))? 1'b0 : faulty_hit_d, req_valid && req_ready || faulty_hit_ready, '0, clk_i, rst_ni) + end + assign faulty_hit_valid = RELIABILITY_MODE ? faulty_hit_q : '0; //if there is a write request, select the buffered version to be invalidated + `FF(tag_valid, req_valid ? 1'b1 : tag_ready ? 1'b0 : tag_valid, '0, clk_i, rst_ni) - // Ready if buffer is empy or downstream is reading. Stall on write - assign req_ready = (!tag_valid || tag_ready) && !tag_write; + + // Ready if buffer is empy or downstream is reading. Stall on write + assign req_ready = (!tag_valid || tag_ready) && !tag_write; // Register the handshake of the reg stage to buffer the tag output data in the next cycle `FF(req_handshake, req_valid && req_ready, 1'b0, clk_i, rst_ni) @@ -197,7 +282,7 @@ module snitch_icache_lookup_serial #( // Fall-through buffer the tag data: Store the tag data if the SRAM bank accepted a request in // the previous cycle and if we actually have to buffer them because the receiver is not ready `FF(tag_rsp_q, tag_rsp_d, '0, clk_i, rst_ni) - assign tag_rsp = req_handshake ? tag_rsp_s : tag_rsp_q; + assign tag_rsp = req_handshake ? tag_rsp_s : tag_rsp_q; // At handshake take data directly from tag stage, select buffer always_comb begin tag_rsp_d = tag_rsp_q; // Load the FF if new data is incoming and downstream is not ready @@ -205,7 +290,7 @@ module snitch_icache_lookup_serial #( tag_rsp_d = tag_rsp_s; end // Override the hit if the write that stalled us invalidated the data - if (lookup_addr == write_addr && write_valid_i) begin + if ((lookup_addr == write_addr) && write_valid_i && write_ready_o) begin tag_rsp_d.hit = 1'b0; end end @@ -222,7 +307,8 @@ module snitch_icache_lookup_serial #( logic error; } data_req_t; - typedef logic [CFG.LINE_WIDTH-1:0] data_rsp_t; + typedef logic [CFG.LINE_WIDTH + DATA_PARITY_WIDTH - 1:0] data_rsp_t; + logic [DATA_ADDR_WIDTH-1:0] data_addr; logic data_enable; @@ -232,6 +318,7 @@ module snitch_icache_lookup_serial #( data_req_t data_req_d, data_req_q; data_rsp_t data_rsp_q; logic data_valid, data_ready; + logic hit_invalid, hit_invalid_q; // Connect tag stage response to data stage request assign data_req_d.addr = tag_req_q.addr; @@ -243,25 +330,37 @@ module snitch_icache_lookup_serial #( assign lookup_addr = {tag_rsp.cset, tag_req_q.addr[CFG.LINE_ALIGN +: CFG.COUNT_ALIGN]}; assign write_addr = {write_set_i, write_addr_i}; + localparam LINE_SPLIT = CFG.LINE_WIDTH/DATA_PARITY_WIDTH; // Data bank port mux + + // Compute parity bit + if (RELIABILITY_MODE) begin + for (genvar i = 0; i < DATA_PARITY_WIDTH; i++) begin + assign data_wdata[CFG.LINE_WIDTH + DATA_PARITY_WIDTH -1 - i] = ~^write_data_i[CFG.LINE_WIDTH - LINE_SPLIT*i -1 -: LINE_SPLIT]; + end + end always_comb begin // Default read request data_addr = lookup_addr; data_enable = tag_valid && tag_rsp.hit; // Only read data on hit - data_wdata = write_data_i; + data_wdata[CFG.LINE_WIDTH -1:0] = write_data_i; data_write = 1'b0; - // Write takes priority - if (!init_phase && write_valid_i) begin + // Write takes priority, with FT-> write does not have priority over invalidation to not invalidate new data + if (!init_phase && write_valid_i && !data_fault_valid) begin data_addr = write_addr; data_enable = 1'b1; data_write = 1'b1; end + + // Assertions to check whether new data is written in both data and tag + //assert property ( @(posedge clk_i) tag_write && write_valid_i |-> data_write ) else $error("Writing tag but not data"); + //assert property ( @(posedge clk_i) data_write && write_valid_i |-> tag_write ) else $error("Writing data but not tag"); end tc_sram #( - .DataWidth ( CFG.LINE_WIDTH ), - .NumWords ( CFG.LINE_COUNT * CFG.SET_COUNT ), - .NumPorts ( 1 ) + .DataWidth ( CFG.LINE_WIDTH + DATA_PARITY_WIDTH ), + .NumWords ( CFG.LINE_COUNT * CFG.SET_COUNT ), + .NumPorts ( 1 ) ) i_data ( .clk_i ( clk_i ), .rst_ni ( rst_ni ), @@ -273,6 +372,17 @@ module snitch_icache_lookup_serial #( .rdata_o ( data_rdata ) ); + // Parity check + logic [DATA_PARITY_WIDTH-1:0] data_parity_error; + if (RELIABILITY_MODE) begin + always_comb begin : p_parity_check + data_parity_error = '0; + for (int i = 0; i < DATA_PARITY_WIDTH; i++) begin + data_parity_error[DATA_PARITY_WIDTH-1-i] = (data_rdata[CFG.LINE_WIDTH + DATA_PARITY_WIDTH -1 - i] == ~^data_rdata[CFG.LINE_WIDTH - LINE_SPLIT*i -1 -: LINE_SPLIT])? '0 : '1; + end + data_parity_inv_d.parity_error = |data_parity_error; + end + end // Buffer the metadata on a valid handshake. Stall on write (implicit in tag_ready) `FFL(data_req_q, data_req_d, tag_valid && tag_ready, '0, clk_i, rst_ni) `FF(data_valid, (tag_valid && !data_write) ? 1'b1 : data_ready ? 1'b0 : data_valid, '0, clk_i, rst_ni) @@ -287,12 +397,22 @@ module snitch_icache_lookup_serial #( // the previous cycle and if we actually have to buffer them because the receiver is not ready `FFL(data_rsp_q, data_rdata, tag_handshake && !data_ready, '0, clk_i, rst_ni) assign out_data_o = tag_handshake ? data_rdata : data_rsp_q; + assign hit_invalid = RELIABILITY_MODE ? (tag_handshake ? data_parity_inv_d.parity_error : hit_invalid_q) : 1'b0; + + // Buffer the metadata when there is faulty data for the invalidation procedure + if (RELIABILITY_MODE) begin + assign data_parity_inv_d.addr = data_req_q.addr; + assign data_parity_inv_d.cset = data_req_q.id; + `FFL(data_parity_inv_q, (data_fault_ready && !tag_handshake) ? '0 : data_parity_inv_d, tag_handshake || data_fault_ready, '0, clk_i, rst_ni) //reset value when it has been invalidated and there is no new value + `FFL(hit_invalid_q, data_parity_inv_d.parity_error, tag_handshake, '0, clk_i, rst_ni) + end + // Generate the remaining output signals. assign out_addr_o = data_req_q.addr; assign out_id_o = data_req_q.id; assign out_set_o = data_req_q.cset; - assign out_hit_o = data_req_q.hit; + assign out_hit_o = data_req_q.hit && !hit_invalid; assign out_error_o = data_req_q.error; assign out_valid_o = data_valid; assign data_ready = out_ready_i; diff --git a/hardware/deps/snitch/src/snitch_icache/snitch_icache_pkg.sv b/hardware/deps/snitch/src/snitch_icache/snitch_icache_pkg.sv index 2dfd2dedb..0be4f525c 100644 --- a/hardware/deps/snitch/src/snitch_icache/snitch_icache_pkg.sv +++ b/hardware/deps/snitch/src/snitch_icache/snitch_icache_pkg.sv @@ -25,6 +25,8 @@ package snitch_icache_pkg; int FETCH_DW; int FILL_AW; int FILL_DW; + bit RELIABILITY_L1; + bit RELIABILITY_L0; bit L1_TAG_SCM; bit EARLY_LATCH; bit BUFFER_LOOKUP; diff --git a/hardware/deps/snitch/src/snitch_read_only_cache/snitch_read_only_cache.sv b/hardware/deps/snitch/src/snitch_read_only_cache/snitch_read_only_cache.sv index cdf76cbc3..3ae450445 100644 --- a/hardware/deps/snitch/src/snitch_read_only_cache/snitch_read_only_cache.sv +++ b/hardware/deps/snitch/src/snitch_read_only_cache/snitch_read_only_cache.sv @@ -15,6 +15,8 @@ module snitch_read_only_cache #( parameter int unsigned LineCount = -1, /// The set associativity of the cache. Power of two; >= 1. parameter int unsigned SetCount = 1, + /// Reliability mode adds parity checks in the lookup module + parameter bit Reliability = 0, /// AXI address width parameter int unsigned AxiAddrWidth = 0, /// AXI data width @@ -195,6 +197,8 @@ module snitch_read_only_cache #( FETCH_DW: AxiDataWidth, FILL_AW: AxiAddrWidth, FILL_DW: AxiDataWidth, + RELIABILITY_L1: Reliability, + RELIABILITY_L0: 0, // Unused here L1_TAG_SCM: 0, // Unused here EARLY_LATCH: 0, // Unused here BUFFER_LOOKUP: 1, // Mandatory here diff --git a/hardware/deps/snitch/tb/README.md b/hardware/deps/snitch/tb/README.md new file mode 100644 index 000000000..abfbe0720 --- /dev/null +++ b/hardware/deps/snitch/tb/README.md @@ -0,0 +1,16 @@ +## Read-only cache test bench + +This folder contains the test bench for the read-only cache and the fault injection scripts to test the fault tolerance mechanism. +Faults are injected on the tag and data lines inside the lookup module. + +In order to test fault detection on the tag lines, a fault can be injected only on the parity bits, otherwise it will be treated as a miss. +To test fault detection on the data lines, each line is added separately to the list of signals where to inject faults. + +In order to test for the faults, the dimensions inside the `mempool/hardware/deps/snitch/tb/fault_injection/ROC_inject_fault.tcl` file have to be updated. + +Usage: + +```bash +cd questa +questa-2022.3 vsim -do ../FI_sim_setup.tcl +``` \ No newline at end of file diff --git a/hardware/deps/snitch/tb/fault_injection/ROC_inject_fault.tcl b/hardware/deps/snitch/tb/fault_injection/ROC_inject_fault.tcl new file mode 100644 index 000000000..540a39062 --- /dev/null +++ b/hardware/deps/snitch/tb/fault_injection/ROC_inject_fault.tcl @@ -0,0 +1,67 @@ +transcript quietly + +set verbosity 1 +set log_injections 1 +set seed 12345 +set script_base_path "/scratch/sem23h18/project/mempool/hardware/deps/snitch/tb/fault_injection/" +set inject_start_time 500000ns +set inject_stop_time 0 +#use a clock in the tb +set injection_clock "/snitch_read_only_cache_tb/fault_clk" +set injection_clock_trigger 0 +set fault_period 10 +set rand_initial_injection_phase 0 +set max_num_fault_inject 1000 +set signal_fault_duration 20ns +set register_fault_duration 0ns + +set allow_multi_bit_upset 1 +set use_bitwidth_as_weight 0 +set check_core_output_modification 0 +set check_core_next_state_modification 0 +set reg_to_sig_ratio 1 + +proc base_path {} {return "/snitch_read_only_cache_tb/dut/i_lookup/gen_sram/i_tag"} + + + +#set inject_register_netlist [find nets [base_path]/*] +set inject_register_netlist { + +} + +# Set the path to the SRAM +set itag_sram_path "/snitch_read_only_cache_tb/dut/i_lookup/gen_sram/i_tag/sram" + +# Determine the maximum possible index for the first index +set max_index_value 127 ; # Replace with the actual maximum value +#debug : 32, normal 127 + +# Determine the largest possible second index +set max_tag_line_index 47 ; # Replace with the actual maximum value +#debug 57, normal 47 + +# Add the signal paths with maximum indices to the inject_register_netlist +# Do not add the tag signal without indexes, as it flips entire lines to 0 +# To check tag functionality, we invert only the parity bit, which is the last one +for {set index $max_index_value} {$index >= 0} {incr index -1} { + set signal_path "${itag_sram_path}[${index}][${max_tag_line_index}]" + lappend inject_register_netlist $signal_path +} + +#set max_data_index 63; +set max_data_index [expr { + $max_index_value * 2 + 1 +}] +set idata_sram_path "/snitch_read_only_cache_tb/dut/i_lookup/i_data/sram"; +for {set index $max_data_index} {$index >= 0} {incr index -1} { + set signal_path "${idata_sram_path}[${index}]" + lappend inject_register_netlist $signal_path +} + +set inject_signals_netlist [] +set output_netlist [] +set next_state_netlist [] +set assertion_disable_list [] + +source ${::script_base_path}inject_fault.tcl \ No newline at end of file diff --git a/hardware/deps/snitch/tb/fault_injection/extract_nets.tcl b/hardware/deps/snitch/tb/fault_injection/extract_nets.tcl new file mode 100644 index 000000000..24503aac4 --- /dev/null +++ b/hardware/deps/snitch/tb/fault_injection/extract_nets.tcl @@ -0,0 +1,216 @@ +# Copyright 2023 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 +# +# Author: Luca Rufer (lrufer@student.ethz.ch) + +# Description: This file provides some generic procs to extract next from a +# circuit. + +# ================================= Overview ================================== +# ----------------------- General Netlist utility procs ----------------------- +# 'get_net_type' : Get the type of a Net using the describe command. +# Example return type are "Register", "Net", "Enum", +# "Array", "Record" (struct), and others +# 'get_net_array_length' : Get the length of an Array using the describe +# command. +# 'get_net_reg_width' : Get the width (number of bits) of a Register or +# Net using the describe command +# 'get_record_field_names' : Get the names of all fiels of a Record (struct). +# ------------------------- Netlist Extraction procs -------------------------- +# 'get_state_netlist' : Example function for how to extract state nets. +# Non-recursive implementation. +# 'get_state_netlist_revursive' : Example function for how to extract state nets +# Recursive implementation. +# 'get_next_state_netlist' : Example function for how to extract next state +# nets. Non-recursive implementation. +# 'get_next_state_netlist_recursive' : Example function for how to extract +# next state nets. Recursive implementation. +# 'get_output_netlist' : Example function for how to extract output nets +# of a circuit. +# 'extract_netlists' : Given a list of nets obtained e.g. from the 'find' +# command, recursively extract signals until only +# signals of type "Register", "Net" and "Enum" +# remain. +# 'extract_all_nets_recursive_filtered' : Extract all nets from a circuit, +# filter them using the given patterns, and +# recursively extract them using 'extract_netlists'. + +################################## +# Net extraction utility procs # +################################## + +proc get_net_type {signal_name} { + set sig_description [examine -describe $signal_name] + set type_string [string trim [string range $sig_description 1 [string wordend $sig_description 1]] " \n\r()\[\]{}"] + if { $type_string == "Verilog" } { set type_string "Enum"} + return $type_string +} + +proc get_net_array_length {signal_name} { + set sig_description [examine -describe $signal_name] + regexp "\\\[length (\\d+)\\\]" $sig_description -> match + return $match +} + +proc get_net_reg_width {signal_name} { + set sig_description [examine -describe $signal_name] + set length 1 + if {[regexp "\\\[(\\d+):(\\d+)\\\]" $sig_description -> up_lim low_lim]} { + set length [expr $up_lim - $low_lim + 1] + } + return $length +} + +proc get_record_number_of_fields {signal_name} { + set sig_description [examine -describe $signal_name] + if {[regexp "^\{Record \\\[(\\d+) elements?\\\]" $sig_description -> num]} { + return $num + } else { + echo "\[Netlist Extraction Error\] Failed to extract the number of fields in record $signal_name." + return 0 + } +} + +proc get_record_field_names {signal_name} { + set sig_description [examine -describe $signal_name] + set matches [regexp -all -inline "\\n Element #\\d* \"\[a-zA-Z_\]\[a-zA-Z0-9_\]*\"" $sig_description] + set field_names [list] + foreach match $matches { lappend field_names [lindex [split $match \"] 1] } + set num_fields_expected [get_record_number_of_fields $signal_name] + if {[llength $field_names] != $num_fields_expected} { + echo "\[Netlist Extraction Error\] Could not determine the field names of signal $signal_name." + echo "Expected $num_fields_expected Fields, extracted [llength $field_names]: $field_names. Skipping this net..." + return [list] + } + return $field_names +} + +########################################### +# Recursevely extract all nets and enums # +########################################### + +# description: recursively extract all nets from the given 'item_list' by +# breaking them down into the most fundamental bit vectors. +# +# Set 'injection_save' to 1 to make sure only nets that can be +# injected are extracted. Questa SIM has a bug in the 'force' +# command: When trying to force a signal in an array inside a +# struct (Record), the force command will force the field at array +# index 0, and not the selected index. E.g. forcing a.b[7] will +# actually force a.b[0]. When 'injection_save' is set to 1, +# arrays inside structs are skipped from extraction, and a +# info message is generated if the verbosity is high enough +proc extract_netlists {item_list {injection_save 0}} { + set extract_list [list] + foreach item $item_list { + set item_type [get_net_type $item] + if {$item_type == "Register" || $item_type == "Net" || $item_type == "Enum"} { + lappend extract_list $item + } elseif { $item_type == "Array"} { + set array_length [get_net_array_length $item] + if {$injection_save && [string first "." $item] != -1} { + if { $::verbosity >= 3 } { + echo "\[Netlist Extraction\] Net $item is an array inside a struct and will be skipped." + } + } else { + for {set i 0} {$i < $array_length} {incr i} { + set new_net "$item\[$i\]" + set extract_list [concat $extract_list [extract_netlists $new_net $injection_save]] + } + } + } elseif { $item_type == "Record"} { + set fields [get_record_field_names $item] + foreach field $fields { + set new_net $item.$field + set extract_list [concat $extract_list [extract_netlists $new_net $injection_save]] + } + } elseif { $item_type == "int"} { + # Ignore + } else { + if { $::verbosity >= 2 } { + echo "\[Netlist Extraction\] Unknown Type $item_type of net $item. Skipping..." + } + } + } + return $extract_list +} + +#################### +# State Netlists # +#################### + +# Example proc on how to extract state nets (not recursive) +# This is not guaranteed to work for every circuit, as net may not be named +# according to conventions! +proc get_state_netlist {base_path} { + return [extract_netlists [find signal $base_path/*_q] 1] +} + +# Example proc on how to extract state nets. +# This is not guaranteed to work for every circuit, as net may not be named +# according to conventions! +proc get_state_netlist_revursive {base_path} { + return [extract_netlists [find signal -r $base_path/*_q] 1] +} + +##################### +# Next State Nets # +##################### + +# Example proc on how to extract next state nets (not recursive). +# This is not guaranteed to work for every circuit, as net may not be named +# according to conventions! +proc get_next_state_netlist {base_path} { + return [find signal $base_path/*_d] +} + +# Example proc on how to extract next state nets. +# This is not guaranteed to work for every circuit, as net may not be named +# according to conventions! +proc get_next_state_netlist_recursive {base_path} { + return [find signal -r $base_path/*_d] +} + +######################### +# Circuit Output Nets # +######################### + +proc get_output_netlist {base_path} { + return [find signal -out $base_path/*] +} + +################## +# Get all nets # +################## + +proc extract_all_nets_recursive_filtered {base_path filter_list} { + + # recursively extract all signals from the circuit + set netlist [find signal -r $base_path/*]; + + # filter and sort the signals + set netlist_filtered [list]; + foreach net $netlist { + set ignore_net 0 + # ignore any net that matches any ignore pattern + foreach ignore_pattern $filter_list { + if {[string match $ignore_pattern $net]} { + set ignore_net 1 + break + } + } + # add all nets that are not ignored + if {$ignore_net == 0} { + lappend netlist_filtered $net + } + } + + # sort the filtered nets alphabetically + set netlist_filtered [lsort -dictionary $netlist_filtered] + + # recursively extract all nets and enums from arrays and structs + set netlist_extracted [extract_netlists $netlist_filtered 1] + + return $netlist_extracted +} diff --git a/hardware/deps/snitch/tb/fault_injection/inject_fault.tcl b/hardware/deps/snitch/tb/fault_injection/inject_fault.tcl new file mode 100644 index 000000000..9b206b438 --- /dev/null +++ b/hardware/deps/snitch/tb/fault_injection/inject_fault.tcl @@ -0,0 +1,802 @@ +# Copyright 2021 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 +# +# Author: Luca Rufer (lrufer@student.ethz.ch) + +# ============ List of variables that may be passed to this script ============ +# Any of these variables may not be changed while the fault injection script +# is running, unless noted otherwise. Changing any of the settings during +# runtime may result in undefined behaviour. +# ----------------------------------- General --------------------------------- +# 'verbosity' : Controls the amount of information printed during script +# execution. Possible values are: +# 0 : No statements at all +# 1 : Only important initializaion information +# 2 : Important information and occurences of bitflips +# (Recommended). Default +# 3 : All information that is possible +# 'log_injections' : Create a logfile of all injected faults, including +# timestamps, the absolute path of the flipped net, the +# value before the flip, the value after the flip and +# more. +# The logfile is named "fault_injection_.log". +# 0 : Disable logging (Default) +# 1 : Enable logging +# 'seed' : Set the seed for the number generator. Default: 12345 +# To reset the seed and start samping numbers from the +# start of the seed, it is in the responsibility of the +# user to call 'srand'. This is only done one in this +# script when it's sourced. +# 'print_statistics' : Print statistics about the fault injections at the end +# of the fault injection. Which statistics are printed +# also depends on other settings below. +# 0 : Don't print statistics +# 1 : Print statistics (Default) +# 'script_base_path' : Base path of all the scripts that are sourced here: +# Default is set to './' +# ------------------------------- Timing settings ----------------------------- +# 'inject_start_time' : Earliest time of the first fault injection. +# 'inject_stop_time' : Latest possible time for a fault injection. +# Set to 0 for no stop. +# 'injection_clock' : Absolute path to the net that is used as an injected +# trigger and clock. Can be a special trigger clock in +# the testbench, or the normal system clock. +# If 'injection_clock' is set to an empty string (""), +# this script will not perform periodical fault injection. +# 'injection_clock_trigger' : Signal value of 'injection_clock' that triggers +# the fault injection. If a normal clock of a rising edge +# triggered circuit is used as injection clock, it is +# recommended to set the trigger to '0', so injected +# flips can clearly be distinguished in the waveforms. +# 'fault_period' : Period of the fault injection in clock cycles of the +# injection clock. Set to 0 for only a single flip. +# 'rand_initial_injection_phase' : Set the phase relative to the 'fault_period' +# to a random initial value between 0 (inclusive) and +# 'fault_period' (exclusive). If multiple simulation +# with different seeds are performed, this option allows +# the injected faults to be evenly distributed accross +# the 'injection_clock' cycles. +# 0 : Disable random phase. The first fault injection +# is performed at the first injeciton clock trigger +# after the 'inject_start_time'. Default. +# 1 : Enable random phase. +# 'max_num_fault_inject' : Maximum number of faults to be injected. The number +# of faults injected may be lower than this if the +# simualtion finishes before, or if the 'inject_stop_time' +# is reached. If 'max_num_fault_inject' is set to 0, this +# setting is ignored (default). +# 'forced_injection_times' : Provide an explicit list of times when faults are +# to be injected into the simulation. If an empty +# 'forced_injection_signals' list is provided, the signals +# are selected at random according to the Flip settings. +# By default, this list is empty. +# Note that flips forced by this list are not bound +# by the 'inject_start_time' and 'inject_stop_time'. +# Flips forced by this list only count towards the +# 'max_num_fault_inject' limit if +# 'include_forced_inj_in_stats' is set to 1. +# 'forced_injection_signals' : Provide an explicit list of signals where faults +# are to be injected into the simulation at the +# 'forced_injection_times'. This list must have the same +# length as 'forced_injection_times'. Entries in the list +# must have the format {'signal_name' 'is_register}. +# For example: {{"tb/data_q" 1} {"tb/enable" 0}} +# If this list is empty, the signals to be injected at the +# 'forced_injection_times' are selected randomly according +# to the settings below (default). +# Note that listing enums, or signals with a width wider +# than one bit will case a random bit to be selected, +# which will alter the outcome of the periodic random +# fault injection. +# 'include_forced_inj_in_stats' : Select wether the forced injections should be +# included in the statistics or not. Including them in the +# statistics will also cause them to be logged (if logging +# is enabled) and variables like 'last_flipped_net' and +# 'last_injection_time' to be changed. +# 0: Don't include forced injections in statistics and +# logs (default). +# 1: Include forced injections in statistics and logs. +# 'signal_fault_duration' : Duration of faults injected into combinatorial +# signals, before the original value is restored. +# 'register_fault_duration' : Minumum duration of faults injected into +# registers. Faults injected into registers are not +# restored after the 'register_fault_duration' and will +# persist until overwritten by the circuit under test. +# -------------------------------- Flip settings ------------------------------ +# 'allow_multi_bit_upset' : Allow injecting another error in a Register that was +# already flipped and not driven to another value yet. +# 0 : Disable multi bit upsets (default) +# 1 : Enable multi bit upsets +# 'use_bitwidth_as_weight' : Use the bit width of a net as a weight for the +# random fault injection net selection. If this option +# is enabled, a N-bit net has an N times higher chance +# than a 1-bit net of being selected for fault injection. +# 0 : Disable using the bitwidth as weight and give every +# net the same chance of being picked (Default). +# 1 : Enable using the bit width of nets as weights. +# 'check_core_output_modification' : Check if an injected fault changes the +# output of the circuit under test. All nets in +# 'output_netlist' are checked. The result of the check +# is printed after every flip (if verbosity high enough), +# and logged to the logfile. +# 0 : Disable output modification checks. The check will +# be logged as 'x'. +# 1 : Enable output modification checks. +# 'check_core_next_state_modification' : Check if an injected fault changes the +# next state of the circuit under test. All nets in +# 'next_state_netlist' are checked. The result of the +# check is printed after every flip (if verbosity high +# enough), and logged to the logfile. +# 0 : Disable next state modification checks. The check +# will be logged as 'x'. +# 1 : Enable next state modification checks. +# 'reg_to_sig_ratio' : Ratio of Registers to combinatorial signals to be +# selected for a fault injection. Example: A value of 4 +# selects a ratio of 4:1, giving an 80% for a Register to +# be selected, and a 20% change of a combinatorial signal +# to be selected. If the provided +# 'inject_register_netlist' is empty, or the +# 'inject_signals_netlist' is empty, this parameter is +# ignored and nets are only selected from the non-empty +# netlist. +# Default value is 1, so the default ratio is 1:1. +# ---------------------------------- Netlists --------------------------------- +# 'inject_register_netlist' : List of absolute paths to Registers to be flipped +# in the simulation. This is used to simulate Single +# Event Upsets (SEUs). Flips injected in registers are not +# removed by the injection script. If the inject netlist +# is changed after this script was first called, the proc +# 'updated_inject_netlist' must be called. +# 'inject_signals_netlist' : List of absolute paths to combinatorial signals to +# be flipped in the simulation. This is used to simulate +# Single Event Transients (SETs). A fault injection +# drives the target signal for a 'fault_duration', and +# afterwards returns the signal to its original state. +# If the inject netlist is changed after this script was +# first called, the proc 'updated_inject_netlist' must be +# called. +# 'output_netlist' : List of absolute net or register paths to be used for +# the output modification check. +# 'next_state_netlist' : List of absolute net or register paths to be used for +# the next state modification check. +# 'assertion_disable_list' : List of absolute paths to named assertions that +# need to be disabled for during fault injecton. +# Assertions are enabled again after the simulation stop +# time. + +################################## +# Set default parameter values # +################################## + +# General +if {![info exists verbosity]} { set verbosity 2 } +if {![info exists log_injections]} { set log_injections 0 } +if {![info exists seed]} { set seed 12345 } +if {![info exists print_statistics]} { set print_statistics 1 } +if {![info exists script_base_path]} { set script_base_path "./" } +# Timing settings +if {![info exists inject_start_time]} { set inject_start_time 100ns } +if {![info exists inject_stop_time]} { set inject_stop_time 0 } +if {![info exists injection_clock]} { set injection_clock "clk" } +if {![info exists injection_clock_trigger]} { set injection_clock_trigger 0 } +if {![info exists fault_period]} { set fault_period 0 } +if {![info exists rand_initial_injection_phase]} { set rand_initial_injection_phase 0 } +if {![info exists max_num_fault_inject]} { set max_num_fault_inject 0 } +if {![info exists forced_injection_times]} { set forced_injection_times [list] } +if {![info exists forced_injection_signals]} { set forced_injection_signals [list] } +if {![info exists include_forced_inj_in_stats]} { set include_forced_inj_in_stats 0 } +if {![info exists signal_fault_duration]} { set signal_fault_duration 1ns } +if {![info exists register_fault_duration]} { set register_fault_duration 0ns } +# Flip settings +if {![info exists allow_multi_bit_upset]} { set allow_multi_bit_upset 0 } +if {![info exists check_core_output_modification]} { set check_core_output_modification 0 } +if {![info exists check_core_next_state_modification]} { set check_core_next_state_modification 0 } +if {![info exists reg_to_sig_ratio]} { set reg_to_sig_ratio 1 } +if {![info exists use_bitwidth_as_weight]} { set use_bitwidth_as_weight 0 } +# Netlists +if {![info exists inject_register_netlist]} { set inject_register_netlist [list] } +if {![info exists inject_signals_netlist]} { set inject_signals_netlist [list] } +if {![info exists output_netlist]} { set output_netlist [list] } +if {![info exists next_state_netlist]} { set next_state_netlist [list] } +if {![info exists assertion_disable_list]} { set assertion_disable_list [list] } + +# Additional checks +if {[llength $forced_injection_times] != [llength $forced_injection_signals] && \ + [llength $forced_injection_times] != 0 && \ + [llength $forced_injection_signals] != 0} { + if {$::verbosity >= 1} { + echo "\[Fault Injection\] Error: 'forced_injection_times' and \ + 'forced_injection_signals' don't have the same non-zero length!" + } + exit +} + +# Source generic netlist extraction procs +source [subst ${::script_base_path}extract_nets.tcl] + +######################################## +# Finish setup depending on settings # +######################################## + +# Common path sections of all nets where errors can be injected +set ::netlist_common_path_sections [list] + +proc restart_fault_injection {} { + # Start the Error injection script + if {$::verbosity >= 1} { + echo "\[Fault Injection\] Info: Injection script running." + } + + # cleanup from previous runs + if {[info exists ::forced_injection_when_labels]} { + foreach l $::forced_injection_when_labels { + catch {nowhen $l} + } + } + + # Last net that was flipped + set ::last_flipped_net "" + set ::last_injection_time -1 + + # Open the log file + if {$::log_injections} { + set time_stamp [exec date +%Y%m%d_%H%M%S] + set ::injection_log [open "fault_injection_$time_stamp.log" w+] + puts $::injection_log "timestamp,netname,pre_flip_value,post_flip_value,output_changed,new_state_changed" + } else { + set ::injection_log "" + } + + # Dictionary to keep track of injections + set ::inject_dict [dict create] + + # determine the phase for the initial fault injection + if {$::rand_initial_injection_phase} { + set ::prescaler [expr int(rand() * $::fault_period)] + } else { + set ::prescaler [expr $::fault_period - 1] + } + + # List of when-statement labels of forced injection times + set ::forced_injection_when_labels [list] + + # determine the first injection time + set start_time [earliest_time [concat $::forced_injection_times $::inject_start_time]] + + # determine the injection stop time + if {$::inject_stop_time == 0 && ![string equal $::injection_clock ""]} { + set stop_time 0 + } else { + set stop_time [latest_time [concat $::forced_injection_times $::inject_stop_time]] + } + + # Create all When statements + + # start fault injection + when -label inject_start "\$now == @$start_time" { + ::start_fault_injection + nowhen inject_start + } + + # periodically inject faults + if {![string equal $::injection_clock ""]} { + when -label inject_fault "\$now >= @$::inject_start_time and $::injection_clock == $::injection_clock_trigger" { + ::inject_trigger + } + } + + # forced injection times + for {set i 0} { $i < [llength $::forced_injection_times] } { incr i } { + set label "forced_injection_$i" + set t [lindex $::forced_injection_times $i] + if {[llength $::forced_injection_signals] == 0} { + set cmd "::inject_fault $::include_forced_inj_in_stats" + } else { + # Extract the signal infos + set signal_info [lindex $::forced_injection_signals $i] + foreach {signal_name is_register} $signal_info {} + if {$::include_forced_inj_in_stats} { + set cmd "fault_injection_pre_flip_statistics; \ + set flip_return \[::flipbit $signal_name $is_register\]; \ + fault_injection_post_flip_statistics $signal_name \$flip_return" + } else { + set cmd "::flipbit $signal_name $is_register" + } + } + # Create the when statement to flip the bit + when -label $label "\$now == @$t" "$cmd" + # Store the label + lappend ::forced_injection_when_labels $label + } + + # stop the simulation and output statistics + if {$stop_time != 0} { + when -label inject_stop "\$now > @$stop_time" { + ::stop_fault_injection + nowhen inject_stop + } + } +} + +proc start_fault_injection {} { + if {$::verbosity >= 1} { + echo "[time_ns $::now]: \[Fault Injection\] Starting fault injection." + } + # Disable Assertions + foreach assertion $::assertion_disable_list { + assertion enable -off $assertion + } + # Reset statistics + set ::stat_num_bitflips 0 + set ::stat_num_outputs_changed 0 + set ::stat_num_state_changed 0 + set ::stat_num_flip_propagated 0 +} + +################ +# User Procs # +################ + +proc stop_fault_injection {} { + # Stop fault injection + catch {nowhen inject_fault} + # Enable Assertions again + foreach assertion $::assertion_disable_list { + assertion enable -on $assertion + } + # Output simulation Statistics + if {$::verbosity >= 1 && $::print_statistics} { + echo " ========== Fault Injection Statistics ========== " + echo " Number of Bitflips : $::stat_num_bitflips" + if {$::check_core_output_modification} { + echo " Number of Bitflips propagated to outputs : $::stat_num_outputs_changed" + } + if {$::check_core_next_state_modification} { + echo " Number of Bitflips propagated to new state : $::stat_num_state_changed" + } + if {$::check_core_output_modification && $::check_core_next_state_modification} { + echo " Number of Bitflips propagated : $::stat_num_flip_propagated" + } + echo "" + } + # Close the logfile + if {$::log_injections} { + close $::injection_log + } + return $::stat_num_bitflips +} + +####################### +# Helper Procedures # +####################### + +proc earliest_time {time_list} { + if {[llength $time_list] == 0} { + return 0 + } + set earliest [lindex $time_list 0] + foreach t $time_list { + if {[ltTime $t $earliest]} { + set earliest $t + } + } + return $earliest +} + +proc latest_time {time_list} { + if {[llength $time_list] == 0} { + return -1 + } + set latest [lindex $time_list 0] + foreach t $time_list { + if {[gtTime $t $latest]} { + set latest $t + } + } + return $latest +} + +proc time_ns {time_ps} { + set time_str "" + append time_str "[expr $time_ps / 1000]" + set remainder [expr $time_ps % 1000] + if {$remainder != 0} { + append time_str "." + if {$remainder < 100} {append time_str "0"} + if {$remainder < 10} {append time_str "0"} + append time_str "$remainder" + } + append time_str " ns" + return $time_str +} + +proc find_common_path_sections {netlist} { + # Safety check if the list has any elements + if {[llength $netlist] == 0} { + return [list] + } + # Extract the first net as reference + set first_net [lindex $netlist 0] + set first_net_sections [split $first_net "/"] + # Determine the minimal number of sections in the netlist + set min_num_sections 9999 + foreach net $netlist { + set cur_path_sections [split $net "/"] + set num_sections [llength $cur_path_sections] + if {$num_sections < $min_num_sections} {set min_num_sections $num_sections} + } + # Create a match list + set match_list [list] + for {set i 0} {$i < $min_num_sections} {incr i} {lappend match_list 1} + # Test for every net which sections in its path matches the first net path + foreach net $netlist { + set cur_path_sections [split $net "/"] + # Test every section + for {set i 0} {$i < $min_num_sections} {incr i} { + # prevent redundant checking for speedup + if {[lindex $match_list $i] != 0} { + # check if the sections matches the first net section + if {[lindex $first_net_sections $i] != [lindex $cur_path_sections $i]} { + lset match_list $i 0 + } + } + } + } + return $match_list +} + +proc net_print_str {net_name} { + # Check if the list exists + if {[llength $::netlist_common_path_sections] == 0} { + return $net_name + } + # Split the netname path + set cur_path_sections [split $net_name "/"] + set print_str "" + set printed_dots 0 + # check sections individually + for {set i 0} {$i < [llength $cur_path_sections]} {incr i} { + # check if the section at the current index is a common to all paths + if {$i < [llength $::netlist_common_path_sections] && [lindex $::netlist_common_path_sections $i] == 1} { + # Do not print the dots if multiple sections match in sequence + if {!$printed_dots} { + # Print dots to indicate the path was shortened + append print_str "\[...\]" + if {$i != [llength $cur_path_sections] - 1} {append print_str "/"} + set printed_dots 1 + } + } else { + # Sections don't match, print the path section + append print_str "[lindex $cur_path_sections $i]" + if {$i != [llength $cur_path_sections] - 1} {append print_str "/"} + set printed_dots 0 + } + } + return $print_str +} + +proc calculate_weight_by_width {netlist} { + set total_weight 0 + set group_weight_dict [dict create] + set group_net_dict [dict create] + foreach net $netlist { + # determine the width of a net (used as weight) + set width [get_net_reg_width $net] + if {![dict exists $group_weight_dict $width]} { + # New width discovered, add new entry + dict set group_weight_dict $width $width + dict set group_net_dict $width [list $net] + } else { + dict incr group_weight_dict $width $width + dict lappend group_net_dict $width $net + } + } + # Sum weights of all groups + foreach group_weight [dict values $group_weight_dict] { + set total_weight [expr $total_weight + $group_weight] + } + return [list $total_weight $group_weight_dict $group_net_dict] +} + +proc updated_inject_netlist {} { + # print how many nets were found + set num_reg_nets [llength $::inject_register_netlist] + set num_comb_nets [llength $::inject_signals_netlist] + if {$::verbosity >= 1} { + echo "\[Fault Injection\] Selected $num_reg_nets Registers for fault injection." + echo "\[Fault Injection\] Selected $num_comb_nets combinatorial Signals for fault injection." + } + # print all nets that were found + if {$::verbosity >= 3} { + echo "Registers: " + foreach net $::inject_register_netlist { + echo " - [get_net_reg_width $net]-bit [get_net_type $net] : $net" + } + echo "Combinatorial Signals: " + foreach net $::inject_signals_netlist { + echo " - [get_net_reg_width $net]-bit [get_net_type $net] : $net" + } + echo "" + } + # determine the common sections + set combined_inject_netlist [concat $::inject_register_netlist $::inject_signals_netlist] + set ::netlist_common_path_sections [find_common_path_sections $combined_inject_netlist] + # determine the distribution of the nets + if {$::use_bitwidth_as_weight} { + set ::inject_register_distibrution_info [calculate_weight_by_width $::inject_register_netlist] + set ::inject_signals_distibrution_info [calculate_weight_by_width $::inject_signals_netlist] + } +} + +########################## +# Random Net Selection # +########################## + +proc select_random_net {} { + # Choose between Register and Signal + if {[llength $::inject_register_netlist] != 0 && \ + ([llength $::inject_signals_netlist] == 0 || \ + rand() * ($::reg_to_sig_ratio + 1) >= 1)} { + set is_register 1 + set selected_list $::inject_register_netlist + } else { + set is_register 0 + set selected_list $::inject_signals_netlist + } + # Select the distribution + if {$::use_bitwidth_as_weight} { + # select the distribution + if {$is_register} { + set distibrution_info $::inject_register_distibrution_info + } else { + set distibrution_info $::inject_signals_distibrution_info + } + # unpack the distribution + set distribution_total_weight [lindex $distibrution_info 0] + set distribution_weight_dict [lindex $distibrution_info 1] + set distribution_net_dict [lindex $distibrution_info 2] + # determine the group + set selec [expr rand() * $distribution_total_weight] + dict for {group group_weight} $distribution_weight_dict { + if {$selec <= $group_weight} { + break + } else { + set selec [expr $selec - $group_weight] + } + } + set selected_list [dict get $distribution_net_dict $group] + } + set idx [expr int(rand()*[llength $selected_list])] + set selected_net [lindex $selected_list $idx] + return [list $selected_net $is_register] +} + +################ +# Flip a Bit # +################ + +# flip a spefific bit of the given net name. returns a 1 if the bit could be flipped +proc flipbit {signal_name is_register} { + set success 0 + set old_value [examine -radixenumsymbolic $signal_name] + # check if net is an enum + if {[examine -radixenumnumeric $signal_name] != [examine -radixenumsymbolic $signal_name]} { + set old_value_numeric [examine -radix binary,enumnumeric $signal_name] + set new_value_numeric [expr int(rand()*([expr 2 ** [string length $old_value_numeric]]))] + while {$old_value_numeric == $new_value_numeric && [string length $old_value_numeric] != 1} { + set new_value_numeric [expr int(rand()*([expr 2 ** [string length $old_value_numeric]]))] + } + if {$is_register} { + force -freeze $signal_name $new_value_numeric -cancel $::register_fault_duration + } else { + force -freeze $signal_name $new_value_numeric, $old_value_numeric $::signal_fault_duration -cancel $::signal_fault_duration + } + set success 1 + } else { + set flip_signal_name $signal_name + set bin_val [examine -radix binary $signal_name] + set len [string length $bin_val] + set flip_index 0 + if {$len != 1} { + set flip_index [expr int(rand()*$len)] + set flip_signal_name $signal_name\($flip_index\) + } + set old_bit_value "0" + set new_bit_value "1" + if {[string index $bin_val [expr $len - 1 - $flip_index]] == "1"} { + set new_bit_value "0" + set old_bit_value "1" + } + if {$is_register} { + force -freeze $flip_signal_name $new_bit_value -cancel $::register_fault_duration + } else { + force -freeze $flip_signal_name $new_bit_value, $old_bit_value $::signal_fault_duration -cancel $::signal_fault_duration + } + if {[examine -radix binary $signal_name] != $bin_val} {set success 1} + } + set new_value [examine -radixenumsymbolic $signal_name] + set result [list $success $old_value $new_value] + return $result +} + +################################ +# Fault Injection Statistics # +################################ + +proc fault_injection_pre_flip_statistics {} { + # record the output before the flip + set ::pre_flip_out_val [list] + if {$::check_core_output_modification} { + foreach net $::output_netlist { + lappend ::pre_flip_out_val [examine $net] + } + } + # record the new state before the flip + set ::pre_flip_next_state_val [list] + if {$::check_core_next_state_modification} { + foreach net $::next_state_netlist { + lappend ::pre_flip_next_state_val [examine $net] + } + } +} + +proc fault_injection_post_flip_statistics {flipped_net flip_return} { + incr ::stat_num_bitflips + set ::last_flipped_net $flipped_net + set ::last_injection_time $::now + + set flip_propagated 0 + # record the output after the flip + set post_flip_out_val [list] + if {$::check_core_output_modification} { + foreach net $::output_netlist { + lappend post_flip_out_val [examine $net] + } + # check if the output changed + set output_state "not modified" + set output_changed [expr ![string equal $::pre_flip_out_val $post_flip_out_val]] + if {$output_changed} { + set output_state "changed" + incr ::stat_num_outputs_changed + set flip_propagated 1 + } + } else { + set output_changed "x" + } + # record the new state before the flip + set post_flip_next_state_val [list] + if {$::check_core_next_state_modification} { + foreach net $::next_state_netlist { + lappend post_flip_next_state_val [examine $net] + } + # check if the new state changed + set new_state_state "not modified" + set new_state_changed [expr ![string equal $::pre_flip_next_state_val $post_flip_next_state_val]] + if {$new_state_changed} { + set new_state_state "changed" + incr ::stat_num_state_changed + set flip_propagated 1 + } + } else { + set new_state_changed "x" + } + + if {$flip_propagated} { + incr ::stat_num_flip_propagated + } + # display the result + if {$::verbosity >= 2} { + set print_str "[time_ns $::now]: \[Fault Injection\] " + append print_str "Flipped net [net_print_str $flipped_net] from [lindex $flip_return 1] to [lindex $flip_return 2]. " + if {$::check_core_output_modification} { + append print_str "Output signals $output_state. " + } + if {$::check_core_next_state_modification} { + append print_str "New state $new_state_state. " + } + echo $print_str + } + # Log the result + if {$::log_injections} { + puts $::injection_log "$::now,$flipped_net,[lindex $flip_return 1],[lindex $flip_return 2],$output_changed,$new_state_changed" + flush $::injection_log + } +} + +############################## +# Fault injection routine # +############################## + +proc inject_fault {include_in_statistics} { + # If enabled, prepare the statistics for the flip + if {$include_in_statistics} { fault_injection_pre_flip_statistics } + + # Questa currently has a bug that it won't force certain nets. So we retry + # until we successfully flip a net. + # The bug primarily affects arrays of structs: + # If you try to force a member/field of a struct in an array, QuestaSim will + # flip force that member/field in the struct/record with index 0 in the + # array, not at the array index that was specified. + set success 0 + set attempts 0 + while {!$success && [incr attempts] < 50} { + # get a random net + set net_selc_info [::select_random_net] + set net_to_flip [lindex $net_selc_info 0] + set is_register [lindex $net_selc_info 1] + # Check if the selected net is allowed to be flipped + set allow_flip 1 + if {$is_register && !$::allow_multi_bit_upset} { + set net_value [examine -radixenumsymbolic $net_to_flip] + if {[dict exists $::inject_dict $net_to_flip] && [dict get $::inject_dict $net_to_flip] == $net_value} { + set allow_flip 0 + if {$::verbosity >= 3} { + echo "[time_ns $::now]: \[Fault Injection\] Tried to flip [net_print_str $net_to_flip], but was already flipped." + } + } + } + # flip the random net + if {$allow_flip} { + if {[catch {set flip_return [::flipbit $net_to_flip $is_register]}]} { + set flip_return {0 "x" "x"} + } + if {[lindex $flip_return 0]} { + set success 1 + if {$is_register && !$::allow_multi_bit_upset} { + # save the new value to the dict + dict set ::inject_dict $net_to_flip [examine -radixenumsymbolic $net_to_flip] + } + } else { + if {$::verbosity >= 3} { + echo "[time_ns $::now]: \[Fault Injection\] Failed to flip [net_print_str $net_to_flip]. Choosing another one." + } + } + } + } + if {$success && !$include_in_statistics} { + if {$::verbosity >= 2} { + echo "[time_ns $::now]: \[Fault Injection\] \ + Flipped net [net_print_str $net_to_flip] \ + from [lindex $flip_return 1] \ + to [lindex $flip_return 2]. " + } + } + if {$success && $include_in_statistics} { + fault_injection_post_flip_statistics $net_to_flip $flip_return + } +} + +proc ::inject_trigger {} { + # check if any nets are selected for injection + if {[llength $::inject_register_netlist] == 0 && \ + [llength $::inject_signals_netlist] == 0} { + return + } + # check if we reached the injection limit + if {($::max_num_fault_inject != 0) && ($::stat_num_bitflips >= $::max_num_fault_inject)} { + # Stop the simulation + if {$::verbosity >= 2} { + echo "\[Fault Injection\] Injection limit ($::max_num_fault_inject) reached. Stopping error injection..." + } + # Disable the trigger (if not already done so) + catch {nowhen inject_fault} + return + } + # increase prescaler + incr ::prescaler + if {$::prescaler == $::fault_period} { + set ::prescaler 0 + # inject a fault + ::inject_fault 1 + } +} + +# Set the seed for the first time +expr srand($::seed) + +# Update the inject netlist +updated_inject_netlist + +# Reset the fault injection +restart_fault_injection diff --git a/hardware/deps/snitch/tb/scripts/FI_sim_setup.tcl b/hardware/deps/snitch/tb/scripts/FI_sim_setup.tcl new file mode 100644 index 000000000..c9af0bdac --- /dev/null +++ b/hardware/deps/snitch/tb/scripts/FI_sim_setup.tcl @@ -0,0 +1,18 @@ +do compile.tcl + +vopt snitch_read_only_cache_tb -o snitch_read_only_cache_tb_opt +acc + +vsim snitch_read_only_cache_tb_opt -voptargs=+acc + +# waves +add wave -noupdate -expand -group lookup /snitch_read_only_cache_tb/dut/i_lookup/* +add wave -noupdate -group handler /snitch_read_only_cache_tb/dut/i_handler/* +add wave -noupdate -group refill /snitch_read_only_cache_tb/dut/i_refill/* +add wave -noupdate -group top /snitch_read_only_cache_tb/dut/* +add wave -noupdate /snitch_read_only_cache_tb/dut/i_lookup/gen_sram/i_tag/sram +add wave -noupdate /snitch_read_only_cache_tb/dut/i_lookup/i_data/sram + + +do ../fault_injection/ROC_inject_fault.tcl + +#run -all \ No newline at end of file diff --git a/hardware/deps/snitch/tb/scripts/compile.tcl b/hardware/deps/snitch/tb/scripts/compile.tcl new file mode 100644 index 000000000..cd4570c4e --- /dev/null +++ b/hardware/deps/snitch/tb/scripts/compile.tcl @@ -0,0 +1,247 @@ +# This script was generated automatically by bender. +set ROOT "/scratch/sem23h18/project/mempool" + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "$ROOT/hardware/deps/common_verification/src/clk_rst_gen.sv" \ + "$ROOT/hardware/deps/common_verification/src/rand_id_queue.sv" \ + "$ROOT/hardware/deps/common_verification/src/rand_stream_mst.sv" \ + "$ROOT/hardware/deps/common_verification/src/rand_synch_holdable_driver.sv" \ + "$ROOT/hardware/deps/common_verification/src/rand_verif_pkg.sv" \ + "$ROOT/hardware/deps/common_verification/src/sim_timeout.sv" \ + "$ROOT/hardware/deps/common_verification/src/rand_synch_driver.sv" \ + "$ROOT/hardware/deps/common_verification/src/rand_stream_slv.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/cluster_clk_cells.sv" \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/pulp_clk_cells.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "$ROOT/hardware/deps/tech_cells_generic/src/rtl/tc_clk.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/cluster_pwr_cells.sv" \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/generic_memory.sv" \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/generic_rom.sv" \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/pad_functional.sv" \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/pulp_buffer.sv" \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/pulp_pwr_cells.sv" \ + "$ROOT/hardware/deps/tech_cells_generic/src/tc_pwr.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "$ROOT/hardware/deps/tech_cells_generic/src/deprecated/pulp_clock_gating_async.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "+incdir+$ROOT/hardware/deps/common_cells/include" \ + "$ROOT/hardware/deps/common_cells/src/binary_to_gray.sv" \ + "$ROOT/hardware/deps/common_cells/src/cb_filter_pkg.sv" \ + "$ROOT/hardware/deps/common_cells/src/cc_onehot.sv" \ + "$ROOT/hardware/deps/common_cells/src/cdc_2phase.sv" \ + "$ROOT/hardware/deps/common_cells/src/cf_math_pkg.sv" \ + "$ROOT/hardware/deps/common_cells/src/clk_div.sv" \ + "$ROOT/hardware/deps/common_cells/src/delta_counter.sv" \ + "$ROOT/hardware/deps/common_cells/src/ecc_pkg.sv" \ + "$ROOT/hardware/deps/common_cells/src/edge_propagator_tx.sv" \ + "$ROOT/hardware/deps/common_cells/src/exp_backoff.sv" \ + "$ROOT/hardware/deps/common_cells/src/fifo_v3.sv" \ + "$ROOT/hardware/deps/common_cells/src/gray_to_binary.sv" \ + "$ROOT/hardware/deps/common_cells/src/isochronous_4phase_handshake.sv" \ + "$ROOT/hardware/deps/common_cells/src/isochronous_spill_register.sv" \ + "$ROOT/hardware/deps/common_cells/src/lfsr.sv" \ + "$ROOT/hardware/deps/common_cells/src/lfsr_16bit.sv" \ + "$ROOT/hardware/deps/common_cells/src/lfsr_8bit.sv" \ + "$ROOT/hardware/deps/common_cells/src/mv_filter.sv" \ + "$ROOT/hardware/deps/common_cells/src/onehot_to_bin.sv" \ + "$ROOT/hardware/deps/common_cells/src/plru_tree.sv" \ + "$ROOT/hardware/deps/common_cells/src/popcount.sv" \ + "$ROOT/hardware/deps/common_cells/src/rr_arb_tree.sv" \ + "$ROOT/hardware/deps/common_cells/src/rstgen_bypass.sv" \ + "$ROOT/hardware/deps/common_cells/src/serial_deglitch.sv" \ + "$ROOT/hardware/deps/common_cells/src/shift_reg.sv" \ + "$ROOT/hardware/deps/common_cells/src/spill_register_flushable.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_demux.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_filter.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_fork.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_intf.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_join.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_mux.sv" \ + "$ROOT/hardware/deps/common_cells/src/sub_per_hash.sv" \ + "$ROOT/hardware/deps/common_cells/src/sync.sv" \ + "$ROOT/hardware/deps/common_cells/src/sync_wedge.sv" \ + "$ROOT/hardware/deps/common_cells/src/unread.sv" \ + "$ROOT/hardware/deps/common_cells/src/addr_decode.sv" \ + "$ROOT/hardware/deps/common_cells/src/cb_filter.sv" \ + "$ROOT/hardware/deps/common_cells/src/cdc_fifo_2phase.sv" \ + "$ROOT/hardware/deps/common_cells/src/counter.sv" \ + "$ROOT/hardware/deps/common_cells/src/ecc_decode.sv" \ + "$ROOT/hardware/deps/common_cells/src/ecc_encode.sv" \ + "$ROOT/hardware/deps/common_cells/src/edge_detect.sv" \ + "$ROOT/hardware/deps/common_cells/src/lzc.sv" \ + "$ROOT/hardware/deps/common_cells/src/max_counter.sv" \ + "$ROOT/hardware/deps/common_cells/src/rstgen.sv" \ + "$ROOT/hardware/deps/common_cells/src/spill_register.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_delay.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_fifo.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_fork_dynamic.sv" \ + "$ROOT/hardware/deps/common_cells/src/cdc_fifo_gray.sv" \ + "$ROOT/hardware/deps/common_cells/src/fall_through_register.sv" \ + "$ROOT/hardware/deps/common_cells/src/id_queue.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_to_mem.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_arbiter_flushable.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_register.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_xbar.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_arbiter.sv" \ + "$ROOT/hardware/deps/common_cells/src/stream_omega_net.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "+incdir+$ROOT/hardware/deps/common_cells/include" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/sram.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "+incdir+$ROOT/hardware/deps/common_cells/include" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/clock_divider_counter.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/find_first_one.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/generic_LFSR_8bit.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/generic_fifo.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/prioarbiter.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/pulp_sync.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/pulp_sync_wedge.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/rrarbiter.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/clock_divider.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/fifo_v2.sv" \ + "$ROOT/hardware/deps/common_cells/src/deprecated/fifo_v1.sv" \ + "$ROOT/hardware/deps/common_cells/src/edge_propagator.sv" \ + "$ROOT/hardware/deps/common_cells/src/edge_propagator_rx.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "+incdir+$ROOT/hardware/deps/axi/include" \ + "+incdir+$ROOT/hardware/deps/common_cells/include" \ + "$ROOT/hardware/deps/axi/src/axi_pkg.sv" \ + "$ROOT/hardware/deps/axi/src/axi_intf.sv" \ + "$ROOT/hardware/deps/axi/src/axi_atop_filter.sv" \ + "$ROOT/hardware/deps/axi/src/axi_burst_splitter.sv" \ + "$ROOT/hardware/deps/axi/src/axi_cdc_dst.sv" \ + "$ROOT/hardware/deps/axi/src/axi_cdc_src.sv" \ + "$ROOT/hardware/deps/axi/src/axi_cut.sv" \ + "$ROOT/hardware/deps/axi/src/axi_delayer.sv" \ + "$ROOT/hardware/deps/axi/src/axi_demux.sv" \ + "$ROOT/hardware/deps/axi/src/axi_dw_downsizer.sv" \ + "$ROOT/hardware/deps/axi/src/axi_dw_upsizer.sv" \ + "$ROOT/hardware/deps/axi/src/axi_id_remap.sv" \ + "$ROOT/hardware/deps/axi/src/axi_id_prepend.sv" \ + "$ROOT/hardware/deps/axi/src/axi_isolate.sv" \ + "$ROOT/hardware/deps/axi/src/axi_join.sv" \ + "$ROOT/hardware/deps/axi/src/axi_lite_demux.sv" \ + "$ROOT/hardware/deps/axi/src/axi_lite_join.sv" \ + "$ROOT/hardware/deps/axi/src/axi_lite_mailbox.sv" \ + "$ROOT/hardware/deps/axi/src/axi_lite_mux.sv" \ + "$ROOT/hardware/deps/axi/src/axi_lite_regs.sv" \ + "$ROOT/hardware/deps/axi/src/axi_lite_to_apb.sv" \ + "$ROOT/hardware/deps/axi/src/axi_lite_to_axi.sv" \ + "$ROOT/hardware/deps/axi/src/axi_modify_address.sv" \ + "$ROOT/hardware/deps/axi/src/axi_mux.sv" \ + "$ROOT/hardware/deps/axi/src/axi_serializer.sv" \ + "$ROOT/hardware/deps/axi/src/axi_cdc.sv" \ + "$ROOT/hardware/deps/axi/src/axi_err_slv.sv" \ + "$ROOT/hardware/deps/axi/src/axi_dw_converter.sv" \ + "$ROOT/hardware/deps/axi/src/axi_id_serialize.sv" \ + "$ROOT/hardware/deps/axi/src/axi_multicut.sv" \ + "$ROOT/hardware/deps/axi/src/axi_to_axi_lite.sv" \ + "$ROOT/hardware/deps/axi/src/axi_iw_converter.sv" \ + "$ROOT/hardware/deps/axi/src/axi_lite_xbar.sv" \ + "$ROOT/hardware/deps/axi/src/axi_xbar.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "+incdir+$ROOT/hardware/deps/axi/include" \ + "+incdir+$ROOT/hardware/deps/common_cells/include" \ + "$ROOT/hardware/deps/axi/src/axi_sim_mem.sv" \ + "$ROOT/hardware/deps/axi/src/axi_test.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+SNITCH_ENABLE_PERF=1 \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "+incdir+$ROOT/hardware/deps/axi/include" \ + "+incdir+$ROOT/hardware/deps/common_cells/include" \ + "$ROOT/hardware/deps/snitch/src/riscv_instr.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_pkg.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_axi_pkg.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_icache/snitch_icache_pkg.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_regfile_ff.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_lsu.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_ipu.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_shared_muldiv.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_demux.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_axi_adapter.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_icache/snitch_icache.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_icache/snitch_icache_l0.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_icache/snitch_icache_handler.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_icache/snitch_icache_lfsr.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_icache/snitch_icache_lookup_parallel.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_icache/snitch_icache_lookup_serial.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_icache/snitch_icache_refill.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_read_only_cache/snitch_axi_to_cache.sv" \ + "$ROOT/hardware/deps/snitch/src/snitch_read_only_cache/snitch_read_only_cache.sv" +}]} {return 1} + +if {[catch {vlog -incr -sv \ + -svinputport=compat \ + +define+TARGET_ROCACHE_TEST \ + +define+TARGET_SIMULATION \ + +define+TARGET_VSIM \ + "+incdir+$ROOT/hardware/deps/axi/include" \ + "+incdir+$ROOT/hardware/deps/common_cells/include" \ + "$ROOT/hardware/deps/snitch/tb/src/snitch_read_only_cache_tb.sv" +}]} {return 1} diff --git a/hardware/deps/snitch/tb/scripts/gen_script.sh b/hardware/deps/snitch/tb/scripts/gen_script.sh new file mode 100755 index 000000000..4a9b75dd1 --- /dev/null +++ b/hardware/deps/snitch/tb/scripts/gen_script.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +#bender -d /scratch/sem23h18/project/mempool/hardware/deps/snitch script vsim -t rocache_test -p snitch --vlog-arg="-svinputport=compat" > compile.tcl + +bender -d /scratch/sem23h18/project/mempool script vsim -t rocache_test -p snitch --vlog-arg="-svinputport=compat" > compile.tcl diff --git a/hardware/deps/snitch/tb/scripts/sim_setup.tcl b/hardware/deps/snitch/tb/scripts/sim_setup.tcl new file mode 100644 index 000000000..dcc595cbd --- /dev/null +++ b/hardware/deps/snitch/tb/scripts/sim_setup.tcl @@ -0,0 +1,14 @@ +do compile.tcl + +vopt snitch_read_only_cache_tb -o snitch_read_only_cache_tb_opt +acc + +vsim snitch_read_only_cache_tb_opt -voptargs=+acc + +# waves +add wave -noupdate -expand -group lookup /snitch_read_only_cache_tb/dut/i_lookup/* +add wave -noupdate -group handler /snitch_read_only_cache_tb/dut/i_handler/* +add wave -noupdate -group refill /snitch_read_only_cache_tb/dut/i_refill/* +add wave -noupdate /snitch_read_only_cache_tb/dut/i_lookup/gen_sram/i_tag/sram +add wave -noupdate /snitch_read_only_cache_tb/dut/i_lookup/i_data/sram + +run -all \ No newline at end of file diff --git a/hardware/deps/snitch/tb/src/snitch_read_only_cache_tb.sv b/hardware/deps/snitch/tb/src/snitch_read_only_cache_tb.sv new file mode 100644 index 000000000..b5d2f3438 --- /dev/null +++ b/hardware/deps/snitch/tb/src/snitch_read_only_cache_tb.sv @@ -0,0 +1,714 @@ +// Copyright 2021 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +// Author: Florian Zaruba +// Samuel Riedel + +class icache_request #( + parameter int unsigned AddrWidth = 48 +); + rand logic [AddrWidth-1:0] addr; + rand bit flush; + + constraint flush_c { + flush dist { 1 := 2, 0 := 200}; + } + + constraint addr_c { + addr[1:0] == 0; + } +endclass + +class riscv_inst; + rand logic [31:0] inst; + rand bit ctrl_flow; + constraint inst_c { + ctrl_flow dist { 1 := 3, 0 := 10}; + inst[1:0] == 2'b11; + if (ctrl_flow) { + inst[6:0] inside { + riscv_instr::BEQ[6:0], + riscv_instr::JAL[6:0] + }; + // we don't support compressed instructions, make sure + // that we only emit aligned jump targets. + if (inst[6:0] == riscv_instr::BEQ[6:0]) { + inst[8] == 0; + } + if (inst[6:0] == riscv_instr::JAL[6:0]) { + inst[21] == 0; + } + // make sure that we don't emit control flow instructions + } else { + !(inst[6:0] inside { + riscv_instr::BEQ[6:0], + riscv_instr::JAL[6:0] + }); + } + } +endclass + +// Inherit from the random AXI master, but modify the request to emulate a core requesting intstructions. +class semirand_axi_master #( + // AXI interface parameters + parameter int AW = 32, + parameter int DW = 32, + parameter int IW = 8, + parameter int UW = 1, + // Stimuli application and test time + parameter time TA = 0ps, + parameter time TT = 0ps, + // Maximum number of read and write transactions in flight + parameter int MAX_READ_TXNS = 1, + parameter int MAX_WRITE_TXNS = 1, + // Upper and lower bounds on wait cycles on Ax, W, and resp (R and B) channels + parameter int AX_MIN_WAIT_CYCLES = 0, + parameter int AX_MAX_WAIT_CYCLES = 100, + parameter int W_MIN_WAIT_CYCLES = 0, + parameter int W_MAX_WAIT_CYCLES = 5, + parameter int RESP_MIN_WAIT_CYCLES = 0, + parameter int RESP_MAX_WAIT_CYCLES = 20, + // AXI feature usage + parameter int AXI_MAX_BURST_LEN = 0, // maximum number of beats in burst; 0 = AXI max (256) + parameter int TRAFFIC_SHAPING = 0, + parameter int AXI_ADDR_INCR = 1, + parameter bit AXI_EXCLS = 1'b0, + parameter bit AXI_ATOPS = 1'b0, + parameter bit AXI_BURST_FIXED = 1'b1, + parameter bit AXI_BURST_INCR = 1'b1, + parameter bit AXI_BURST_WRAP = 1'b0, + // Dependent parameters, do not override. + parameter int AXI_STRB_WIDTH = DW/8, + parameter int N_AXI_IDS = 2**IW +) extends axi_test::axi_rand_master #( + .AW ( AW ), + .DW ( DW ), + .IW ( IW ), + .UW ( UW ), + .TA ( TA ), + .TT ( TT ), + .MAX_READ_TXNS ( MAX_READ_TXNS ), + .MAX_WRITE_TXNS ( MAX_WRITE_TXNS ), + .AX_MIN_WAIT_CYCLES ( AX_MIN_WAIT_CYCLES ), + .AX_MAX_WAIT_CYCLES ( AX_MAX_WAIT_CYCLES ), + .W_MIN_WAIT_CYCLES ( W_MIN_WAIT_CYCLES ), + .W_MAX_WAIT_CYCLES ( W_MAX_WAIT_CYCLES ), + .RESP_MIN_WAIT_CYCLES ( RESP_MIN_WAIT_CYCLES ), + .RESP_MAX_WAIT_CYCLES ( RESP_MAX_WAIT_CYCLES ), + .AXI_MAX_BURST_LEN ( AXI_MAX_BURST_LEN ), + .TRAFFIC_SHAPING ( TRAFFIC_SHAPING ), + .AXI_EXCLS ( AXI_EXCLS ), + .AXI_ATOPS ( AXI_ATOPS ), + .AXI_BURST_FIXED ( AXI_BURST_FIXED ), + .AXI_BURST_INCR ( AXI_BURST_INCR ), + .AXI_BURST_WRAP ( AXI_BURST_WRAP ) +); + + function new( + virtual AXI_BUS_DV #( + .AXI_ADDR_WIDTH(AW), + .AXI_DATA_WIDTH(DW), + .AXI_ID_WIDTH(IW), + .AXI_USER_WIDTH(UW) + ) axi + ); + super.new(axi); + endfunction + + task send_ars(input int n_reads); + automatic logic rand_success; + automatic ax_beat_t ar_beat = new_rand_burst(1'b1); + repeat (n_reads) begin + automatic id_t id; + automatic logic jump; + // Increment address per default, randomize sporadically + rand_success = std::randomize(jump) with {jump dist {1'b0 := 90, 1'b1 := 10};}; + assert(rand_success); + if (jump) begin + ar_beat = new_rand_burst(1'b1); + end else begin + ar_beat.ax_addr = ar_beat.ax_addr + AXI_ADDR_INCR; + end + while (tot_r_flight_cnt >= MAX_READ_TXNS) begin + rand_wait(1, 1); + end + if (AXI_EXCLS) begin + rand_excl_ar(ar_beat); + end + if (AXI_ATOPS) begin + // The ID must not be the same as that of any in-flight ATOP. + forever begin + cnt_sem.get(); + rand_success = std::randomize(id); assert(rand_success); + if (!atop_resp_b[id] && !atop_resp_r[id]) begin + break; + end else begin + // The random ID does not meet the requirements, so try another ID in the next cycle. + cnt_sem.put(); + rand_wait(1, 1); + end + end + ar_beat.ax_id = id; + end else begin + cnt_sem.get(); + end + r_flight_cnt[ar_beat.ax_id]++; + tot_r_flight_cnt++; + cnt_sem.put(); + rand_wait(AX_MIN_WAIT_CYCLES, AX_MAX_WAIT_CYCLES); + drv.send_ar(ar_beat); + if (ar_beat.ax_lock) excl_queue.push_back(ar_beat); + end + endtask + + // Issue n_reads random read and n_writes random write transactions to an address range. + task run(input int n_reads, input int n_writes); + automatic logic ar_done = 1'b0, + aw_done = 1'b0; + fork + begin + send_ars(n_reads); + ar_done = 1'b1; + end + recv_rs(ar_done, aw_done); + begin + create_aws(n_writes); + aw_done = 1'b1; + end + send_aws(aw_done); + send_ws(aw_done); + recv_bs(aw_done); + join + endtask + +endclass + +// Inherit from the random AXI slave, but return the same data for reads from the same address. +// --> Emulate a random ROM +class const_axi_slave #( + // AXI interface parameters + parameter int AW = 32, + parameter int DW = 32, + parameter int IW = 8, + parameter int UW = 1, + // Stimuli application and test time + parameter time TA = 0ps, + parameter time TT = 0ps, + parameter bit RAND_RESP = 0, + // Upper and lower bounds on wait cycles on Ax, W, and resp (R and B) channels + parameter int AX_MIN_WAIT_CYCLES = 0, + parameter int AX_MAX_WAIT_CYCLES = 100, + parameter int R_MIN_WAIT_CYCLES = 0, + parameter int R_MAX_WAIT_CYCLES = 5, + parameter int RESP_MIN_WAIT_CYCLES = 0, + parameter int RESP_MAX_WAIT_CYCLES = 20 +) extends axi_test::axi_rand_slave #( + .AW ( AW ), + .DW ( DW ), + .IW ( IW ), + .UW ( UW ), + .TA ( TA ), + .TT ( TT ), + .RAND_RESP ( RAND_RESP ), + .AX_MIN_WAIT_CYCLES ( AX_MIN_WAIT_CYCLES ), + .AX_MAX_WAIT_CYCLES ( AX_MAX_WAIT_CYCLES ), + .R_MIN_WAIT_CYCLES ( R_MIN_WAIT_CYCLES ), + .R_MAX_WAIT_CYCLES ( R_MAX_WAIT_CYCLES ), + .RESP_MIN_WAIT_CYCLES ( RESP_MIN_WAIT_CYCLES ), + .RESP_MAX_WAIT_CYCLES ( RESP_MAX_WAIT_CYCLES ) +); + function new( + virtual AXI_BUS_DV #( + .AXI_ADDR_WIDTH(AW), + .AXI_DATA_WIDTH(DW), + .AXI_ID_WIDTH(IW), + .AXI_USER_WIDTH(UW) + ) axi + ); + super.new(axi); + endfunction + + task send_rs(); + forever begin + automatic logic rand_success; + automatic ax_beat_t ar_beat; + automatic r_beat_t r_beat = new; + wait (!ar_queue.empty()); + ar_beat = ar_queue.peek(); + rand_success = std::randomize(r_beat); assert(rand_success); + for (int i = 0; i < (DW/32); i++) begin + r_beat.r_data[i*32 +: 32] = (ar_beat.ax_addr >> $clog2(DW/8) << $clog2(DW/8)) + (4*i); + end + r_beat.r_id = ar_beat.ax_id; + if (RAND_RESP && !ar_beat.ax_atop[5]) + r_beat.r_resp[1] = $urandom(); + if (ar_beat.ax_lock) + r_beat.r_resp[0]= $urandom(); + rand_wait(R_MIN_WAIT_CYCLES, R_MAX_WAIT_CYCLES); + if (ar_beat.ax_len == '0) begin + r_beat.r_last = 1'b1; + void'(ar_queue.pop_id(ar_beat.ax_id)); + end else begin + ar_beat.ax_len--; + ar_beat.ax_addr += 1 << ar_beat.ax_size; + ar_queue.set(ar_beat.ax_id, ar_beat); + end + drv.send_r(r_beat); + end + endtask + + task run(); + fork + recv_ars(); + send_rs(); + recv_aws(); + recv_ws(); + send_bs(); + join + endtask + +endclass + +`include "common_cells/assertions.svh" +localparam debug = 1; +module snitch_read_only_cache_tb import snitch_pkg::*; #( + parameter int unsigned AxiAddrWidth = 32, + parameter int unsigned AxiDataWidth = debug? 32:128, + parameter int unsigned AxiIdWidth = 5, + parameter int unsigned LineWidth = debug? 32:256, + parameter int unsigned LineCount = debug? 32:128, + parameter int unsigned SetCount = debug? 2:2, + parameter bit Reliability = 1'b1, + parameter int unsigned FaultFreq = 200 +); + + localparam time ClkPeriod = 10ns; + localparam time TA = 2ns; + localparam time TT = 8ns; + localparam bit DEBUG = 1'b0; + + // AXI parameters + `include "axi/typedef.svh" + `include "axi/assign.svh" + + localparam int unsigned AxiStrbWidth = AxiDataWidth/8; + localparam int unsigned AxiInIdWidth = AxiIdWidth; + localparam int unsigned AxiOutIdWidth = AxiInIdWidth+1; + localparam int unsigned AxiUserWidth = 1; + + typedef logic [AxiAddrWidth-1:0] axi_addr_t; + typedef logic [AxiDataWidth-1:0] axi_data_t; + typedef logic [AxiStrbWidth-1:0] axi_strb_t; + typedef logic [AxiInIdWidth-1:0] axi_in_id_t; + typedef logic [AxiOutIdWidth-1:0] axi_out_id_t; + typedef logic [AxiUserWidth-1:0] axi_user_t; + + `AXI_TYPEDEF_ALL(axi_mst, axi_addr_t, axi_in_id_t, axi_data_t, axi_strb_t, axi_user_t) + `AXI_TYPEDEF_ALL(axi_slv, axi_addr_t, axi_out_id_t, axi_data_t, axi_strb_t, axi_user_t) + + // Address regions + localparam axi_addr_t CachedRegionStart = axi_addr_t'(32'h8000_0000); + localparam axi_addr_t CachedRegionEnd = axi_addr_t'(32'h8000_1000); + + // backing memory + logic [LineWidth-1:0] memory [logic [AxiAddrWidth-1:0]]; + + logic clk, rst, fault_clk; + + typedef semirand_axi_master #( + .AW ( AxiAddrWidth ), + .DW ( AxiDataWidth ), + .IW ( AxiInIdWidth ), + .UW ( AxiUserWidth ), + .TA ( TA ), + .TT ( TT ), + .MAX_READ_TXNS ( 16 ), + .MAX_WRITE_TXNS ( 4 ), + .AX_MIN_WAIT_CYCLES ( 0 ), + .AX_MAX_WAIT_CYCLES ( 8 ), + .W_MIN_WAIT_CYCLES ( 0 ), + .W_MAX_WAIT_CYCLES ( 8 ), + .RESP_MIN_WAIT_CYCLES ( 0 ), + .RESP_MAX_WAIT_CYCLES ( 8 ), + .AXI_MAX_BURST_LEN ( 16 ), + .TRAFFIC_SHAPING ( 0 ), + .AXI_ADDR_INCR ( LineWidth/8 ), + .AXI_EXCLS ( 1'b1 ), + .AXI_ATOPS ( 1'b1 ), + .AXI_BURST_FIXED ( 1'b0 ), + .AXI_BURST_INCR ( 1'b1 ), + .AXI_BURST_WRAP ( 1'b0 ) + ) axi_rand_master_t; + + typedef const_axi_slave #( + .AW ( AxiAddrWidth ), + .DW ( AxiDataWidth ), + .IW ( AxiOutIdWidth ), + .UW ( AxiUserWidth ), + .TA ( TA ), + .TT ( TT ), + .RAND_RESP ( 0 ), + .AX_MIN_WAIT_CYCLES ( 0 ), + .AX_MAX_WAIT_CYCLES ( 8 ), + .R_MIN_WAIT_CYCLES ( 0 ), + .R_MAX_WAIT_CYCLES ( 8 ), + .RESP_MIN_WAIT_CYCLES ( 0 ), + .RESP_MAX_WAIT_CYCLES ( 8 ) + ) axi_rand_slave_t; + + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiInIdWidth ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) axi_mst_dv ( + .clk_i ( clk ) + ); + + AXI_BUS_DV #( + .AXI_ADDR_WIDTH ( AxiAddrWidth ), + .AXI_DATA_WIDTH ( AxiDataWidth ), + .AXI_ID_WIDTH ( AxiOutIdWidth ), + .AXI_USER_WIDTH ( AxiUserWidth ) + ) axi_slv_dv ( + .clk_i ( clk ) + ); + + axi_rand_master_t mst_intf = new(axi_mst_dv); + axi_rand_slave_t slv_intf = new(axi_slv_dv); + + axi_mst_req_t axi_mst_req; + axi_mst_resp_t axi_mst_resp; + + axi_slv_req_t axi_slv_req; + axi_slv_resp_t axi_slv_resp; + + `AXI_ASSIGN_TO_REQ(axi_mst_req, axi_mst_dv) + `AXI_ASSIGN_FROM_RESP(axi_mst_dv, axi_mst_resp) + + `AXI_ASSIGN_FROM_REQ(axi_slv_dv, axi_slv_req) + `AXI_ASSIGN_TO_RESP(axi_slv_resp, axi_slv_dv) + + snitch_read_only_cache #( + .LineWidth ( LineWidth ), + .LineCount ( LineCount ), + .SetCount ( SetCount ), + .Reliability ( Reliability ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiDataWidth ( AxiDataWidth ), + .AxiIdWidth ( AxiInIdWidth ), + .AxiUserWidth ( 1 ), + .MaxTrans ( 32'd8 ), + .NrAddrRules ( 1 ), + .slv_req_t ( axi_mst_req_t ), + .slv_rsp_t ( axi_mst_resp_t ), + .mst_req_t ( axi_slv_req_t ), + .mst_rsp_t ( axi_slv_resp_t ) + ) dut ( + .clk_i ( clk ), + .rst_ni ( ~rst ), + .enable_i ( 1'b1 ), + .flush_valid_i ( 1'b0 ), + .flush_ready_o ( /*unused*/ ), + .start_addr_i ( {CachedRegionStart} ), + .end_addr_i ( {CachedRegionEnd} ), + .axi_slv_req_i ( axi_mst_req ), + .axi_slv_rsp_o ( axi_mst_resp ), + .axi_mst_req_o ( axi_slv_req ), + .axi_mst_rsp_i ( axi_slv_resp ) + ); + + task static cycle_start; + #TT; + endtask + + task static cycle_end; + @(posedge clk); + endtask + + typedef axi_test::axi_driver #( + .AW(AxiAddrWidth), .DW(AxiDataWidth), .IW(AxiInIdWidth), .UW(AxiUserWidth), .TA(TA), .TT(TT) + ) axi_driver_t; + + typedef axi_driver_t::ax_beat_t ar_beat_t; + typedef axi_driver_t::r_beat_t r_beat_t; + + initial begin + automatic logic rand_success; + automatic ar_beat_t ar_beat = new; + automatic r_beat_t r_beat = new; + + // Initialize memory region of random axi master to only fetch from two lines + mst_intf.add_memory_region(CachedRegionStart, CachedRegionEnd, axi_pkg::WTHRU_RALLOCATE); + + // Reset + mst_intf.reset(); + @(negedge rst); + #1000ns; + + // Issue single miss and hit burst for debugging + ar_beat.ax_id = '1; + ar_beat.ax_addr = CachedRegionStart; + ar_beat.ax_len = 7; + ar_beat.ax_size = $clog2(AxiStrbWidth); + ar_beat.ax_burst = axi_pkg::BURST_INCR; + ar_beat.ax_lock = 1'b0; + ar_beat.ax_cache = axi_pkg::WTHRU_RALLOCATE; + ar_beat.ax_prot = '0; + ar_beat.ax_qos = '0; + ar_beat.ax_region = '0; + ar_beat.ax_atop = '0; + ar_beat.ax_user = '1; + + mst_intf.drv.send_ar(ar_beat); + for (int i = 0; i <= ar_beat.ax_len; i++) begin + mst_intf.drv.recv_r(r_beat); + end + #10000ns; + mst_intf.drv.send_ar(ar_beat); + for (int i = 0; i <= ar_beat.ax_len; i++) begin + mst_intf.drv.recv_r(r_beat); + end + #10000ns; + + // Issue two requests to the same location simultaneously + fork + begin + ar_beat.ax_addr = CachedRegionStart+'h100; + ar_beat.ax_len = 0; + mst_intf.drv.send_ar(ar_beat); + ar_beat.ax_id = '0; + mst_intf.drv.send_ar(ar_beat); + end + begin + for (int i = 0; i <= ar_beat.ax_len; i++) begin + mst_intf.drv.recv_r(r_beat); + end + for (int i = 0; i <= ar_beat.ax_len; i++) begin + mst_intf.drv.recv_r(r_beat); + end + end + join + #10000ns; + + // Issue a cache and bypass request simultaneously + // to test reordering or transactions + fork + begin + ar_beat.ax_addr = CachedRegionStart+'h200; + ar_beat.ax_len = 1; + mst_intf.drv.send_ar(ar_beat); + ar_beat.ax_len = 0; + ar_beat.ax_addr = CachedRegionEnd+'h200; + mst_intf.drv.send_ar(ar_beat); + end + begin + for (int i = 0; i <= ar_beat.ax_len; i++) begin + mst_intf.drv.recv_r(r_beat); + end + for (int i = 0; i <= ar_beat.ax_len; i++) begin + mst_intf.drv.recv_r(r_beat); + end + end + join + #10000ns; + + // Issue random request to the cached region + mst_intf.run(10000, 0); + mst_intf.add_memory_region(CachedRegionStart, + CachedRegionStart+2*(CachedRegionEnd-CachedRegionStart), + axi_pkg::WTHRU_RALLOCATE); + + #1000ns; + // Issue random requests, 50% to the cached region + mst_intf.run(10000, 0); + + $finish(); + end + + initial begin : proc_sim_mem + slv_intf.reset(); + @(negedge rst); + slv_intf.run(); + end + + // Time-out + initial begin + automatic int unsigned timeout; + @(negedge rst); + timeout = 0; + while (1) begin + @(posedge clk); + timeout += 1; + if (axi_mst_req.ar_valid && axi_mst_resp.ar_ready) begin + timeout = 0; + end + if (timeout > 5000) begin + $error("Simulation timed out at %0t", $time); + $finish(); + end + end + end + + //////////////////////// + // Checker tasks // + //////////////////////// + + // Queues + localparam int unsigned NoIds = 2**AxiInIdWidth; + axi_mst_ar_chan_t ar_queues[NoIds][$]; + axi_mst_r_chan_t r_queues[NoIds][$]; + + // channel sampling into queues + always @(posedge clk) #TT begin : proc_channel_sample + automatic axi_mst_ar_chan_t ar_beat; + // only execute when reset is high + if (!rst) begin + // AR channel + if (axi_mst_req.ar_valid && axi_mst_resp.ar_ready) begin + ar_queues[axi_mst_req.ar.id].push_back(axi_mst_req.ar); + end + // R channel + if (axi_mst_resp.r_valid && axi_mst_req.r_ready) begin + r_queues[axi_mst_resp.r.id].push_back(axi_mst_resp.r); + end + end + end + + initial begin + automatic axi_mst_ar_chan_t ar_beat; + automatic axi_mst_r_chan_t r_beat; + automatic axi_addr_t addr; + automatic axi_addr_t aligned_addr; + automatic axi_data_t exp_data; + automatic int unsigned no_r_beat[NoIds]; + $timeformat(-9, 2, " ns", 20); + @(negedge rst); + forever begin + @(posedge clk); + #TT; + // Check all read queues + for (int unsigned i = 0; i < NoIds; i++) begin + while (ar_queues[i].size() != 0 && r_queues[i].size() != 0) begin + ar_beat = ar_queues[i][0]; + addr = ar_beat.addr; + for (int unsigned j = 0; j <= ar_beat.len; j++) begin + wait (r_queues[i].size() > 0); + r_beat = r_queues[i].pop_front(); + // Check data + aligned_addr = addr >> $clog2(AxiDataWidth/8) << $clog2(AxiDataWidth/8); + for (int i = 0; i < (AxiDataWidth/32); i++) begin + exp_data[i*32 +: 32] = aligned_addr + (4*i); + end + if (r_beat.data != exp_data) begin + $display("Error (%0t): Wrong data. Addr=0x%x (idx=%h), Beat=%d, Size=%d Aqc=0x%x Exp=0x%x", + $time, ar_beat.addr, ar_beat.addr[2+:5], no_r_beat[i], ar_beat.size, r_beat.data, exp_data); + //$finish(); + //end else begin + // $display("(%0t): Correct data. Addr=0x%x, Beat=%d, Size=%d Aqc=0x%x Exp=0x%x", + // $time, ar_beat.addr, no_r_beat[i], ar_beat.size, r_beat.data, exp_data); + end + + if (r_beat.last && !(ar_beat.len == no_r_beat[i])) begin + $display("ERROR> Last flag was not expected!!!!!!!!!!!!!"); + end + no_r_beat[i]++; + // pop the queue if it is the last flag + if (r_beat.last) begin + ar_beat = ar_queues[i].pop_front(); + no_r_beat[i] = 0; + end else begin + addr = addr + (1 << ar_beat.size); + end + end + end + end + end + end + + //////////////////////// + // Debug // + //////////////////////// + if (DEBUG) begin: gen_chan_logger + axi_chan_logger #( + .TestTime ( 8ns ), + .LoggerName ( "mst_logger" ), + .aw_chan_t ( axi_mst_aw_chan_t ), + .w_chan_t ( axi_mst_w_chan_t ), + .b_chan_t ( axi_mst_b_chan_t ), + .ar_chan_t ( axi_mst_ar_chan_t ), + .r_chan_t ( axi_mst_r_chan_t ) + ) i_axi_chan_logger_mst ( + .clk_i ( clk ), + .rst_ni ( ~rst ), + .end_sim_i ( 1'b0 ), + .aw_chan_i ( axi_mst_req.aw ), + .aw_valid_i ( axi_mst_req.aw_valid ), + .aw_ready_i ( axi_mst_resp.aw_ready ), + .w_chan_i ( axi_mst_req.w ), + .w_valid_i ( axi_mst_req.w_valid ), + .w_ready_i ( axi_mst_resp.w_ready ), + .b_chan_i ( axi_mst_resp.b ), + .b_valid_i ( axi_mst_resp.b_valid ), + .b_ready_i ( axi_mst_req.b_ready ), + .ar_chan_i ( axi_mst_req.ar ), + .ar_valid_i ( axi_mst_req.ar_valid ), + .ar_ready_i ( axi_mst_resp.ar_ready ), + .r_chan_i ( axi_mst_resp.r ), + .r_valid_i ( axi_mst_resp.r_valid ), + .r_ready_i ( axi_mst_req.r_ready ) + ); + + axi_chan_logger #( + .TestTime ( 8ns ), + .LoggerName ( "slv_logger" ), + .aw_chan_t ( axi_slv_aw_chan_t ), + .w_chan_t ( axi_slv_w_chan_t ), + .b_chan_t ( axi_slv_b_chan_t ), + .ar_chan_t ( axi_slv_ar_chan_t ), + .r_chan_t ( axi_slv_r_chan_t ) + ) i_axi_chan_logger_slv ( + .clk_i ( clk ), + .rst_ni ( ~rst ), + .end_sim_i ( 1'b0 ), + .aw_chan_i ( axi_slv_req.aw ), + .aw_valid_i ( axi_slv_req.aw_valid ), + .aw_ready_i ( axi_slv_resp.aw_ready ), + .w_chan_i ( axi_slv_req.w ), + .w_valid_i ( axi_slv_req.w_valid ), + .w_ready_i ( axi_slv_resp.w_ready ), + .b_chan_i ( axi_slv_resp.b ), + .b_valid_i ( axi_slv_resp.b_valid ), + .b_ready_i ( axi_slv_req.b_ready ), + .ar_chan_i ( axi_slv_req.ar ), + .ar_valid_i ( axi_slv_req.ar_valid ), + .ar_ready_i ( axi_slv_resp.ar_ready ), + .r_chan_i ( axi_slv_resp.r ), + .r_valid_i ( axi_slv_resp.r_valid ), + .r_ready_i ( axi_slv_req.r_ready ) + ); + end + + // Clock generation. + initial begin + rst = 1; + repeat (3) begin + #(ClkPeriod/2) clk = 0; + #(ClkPeriod/2) clk = 1; + end + rst = 0; + forever begin + #(ClkPeriod/2) clk = 0; + #(ClkPeriod/2) clk = 1; + end + end + + // Fault injection clock generation + initial begin + forever begin + #(ClkPeriod/2*FaultFreq) fault_clk = 0; + #(ClkPeriod/2*FaultFreq) fault_clk = 1; + end + end +endmodule diff --git a/hardware/scripts/questa/README.md b/hardware/scripts/questa/README.md new file mode 100644 index 000000000..90897fe53 --- /dev/null +++ b/hardware/scripts/questa/README.md @@ -0,0 +1,27 @@ +## Testing MemPool with Fault Injection + +The fault injection scripts (`inject_fault.tcl`, `extract_nets.tcl`) are called by the script `mempool_inject_fault.tcl`. +In this script, the signals under fault injection must be listed and the parameters modified. + +This can be done with the `find signal` command in Questa, but for packed arrays this approach does not work. + +Therefore, the full signal path is appended to the instance path of the modules containing the cache. This is done in the script `get_banks.tcl`. + +To run the simulation, in the script `mempool_inject_fault.tcl` adapt the list of nets where to inject faults. Then, in the hardware folder run the following command: + +```bash +app=hello_world icache_faults=1 make sim +``` + + +### Get the number of faults that the Snitch Lookup had to deal with + +After the simulation finishes, it is possible to get the information on how many faults were detected. + +In the Questa command line, run the following script: + +```bash +do ../script/questa/get_number_faults.tcl +``` + +This script generates a file `mempool/hardware/build/data_tag_faults_stats.txt` listing, for each lookup modules, how many tag and data faults occurred. It also computes the average value across all modules. \ No newline at end of file diff --git a/hardware/scripts/questa/extract_nets.tcl b/hardware/scripts/questa/extract_nets.tcl new file mode 100644 index 000000000..24503aac4 --- /dev/null +++ b/hardware/scripts/questa/extract_nets.tcl @@ -0,0 +1,216 @@ +# Copyright 2023 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 +# +# Author: Luca Rufer (lrufer@student.ethz.ch) + +# Description: This file provides some generic procs to extract next from a +# circuit. + +# ================================= Overview ================================== +# ----------------------- General Netlist utility procs ----------------------- +# 'get_net_type' : Get the type of a Net using the describe command. +# Example return type are "Register", "Net", "Enum", +# "Array", "Record" (struct), and others +# 'get_net_array_length' : Get the length of an Array using the describe +# command. +# 'get_net_reg_width' : Get the width (number of bits) of a Register or +# Net using the describe command +# 'get_record_field_names' : Get the names of all fiels of a Record (struct). +# ------------------------- Netlist Extraction procs -------------------------- +# 'get_state_netlist' : Example function for how to extract state nets. +# Non-recursive implementation. +# 'get_state_netlist_revursive' : Example function for how to extract state nets +# Recursive implementation. +# 'get_next_state_netlist' : Example function for how to extract next state +# nets. Non-recursive implementation. +# 'get_next_state_netlist_recursive' : Example function for how to extract +# next state nets. Recursive implementation. +# 'get_output_netlist' : Example function for how to extract output nets +# of a circuit. +# 'extract_netlists' : Given a list of nets obtained e.g. from the 'find' +# command, recursively extract signals until only +# signals of type "Register", "Net" and "Enum" +# remain. +# 'extract_all_nets_recursive_filtered' : Extract all nets from a circuit, +# filter them using the given patterns, and +# recursively extract them using 'extract_netlists'. + +################################## +# Net extraction utility procs # +################################## + +proc get_net_type {signal_name} { + set sig_description [examine -describe $signal_name] + set type_string [string trim [string range $sig_description 1 [string wordend $sig_description 1]] " \n\r()\[\]{}"] + if { $type_string == "Verilog" } { set type_string "Enum"} + return $type_string +} + +proc get_net_array_length {signal_name} { + set sig_description [examine -describe $signal_name] + regexp "\\\[length (\\d+)\\\]" $sig_description -> match + return $match +} + +proc get_net_reg_width {signal_name} { + set sig_description [examine -describe $signal_name] + set length 1 + if {[regexp "\\\[(\\d+):(\\d+)\\\]" $sig_description -> up_lim low_lim]} { + set length [expr $up_lim - $low_lim + 1] + } + return $length +} + +proc get_record_number_of_fields {signal_name} { + set sig_description [examine -describe $signal_name] + if {[regexp "^\{Record \\\[(\\d+) elements?\\\]" $sig_description -> num]} { + return $num + } else { + echo "\[Netlist Extraction Error\] Failed to extract the number of fields in record $signal_name." + return 0 + } +} + +proc get_record_field_names {signal_name} { + set sig_description [examine -describe $signal_name] + set matches [regexp -all -inline "\\n Element #\\d* \"\[a-zA-Z_\]\[a-zA-Z0-9_\]*\"" $sig_description] + set field_names [list] + foreach match $matches { lappend field_names [lindex [split $match \"] 1] } + set num_fields_expected [get_record_number_of_fields $signal_name] + if {[llength $field_names] != $num_fields_expected} { + echo "\[Netlist Extraction Error\] Could not determine the field names of signal $signal_name." + echo "Expected $num_fields_expected Fields, extracted [llength $field_names]: $field_names. Skipping this net..." + return [list] + } + return $field_names +} + +########################################### +# Recursevely extract all nets and enums # +########################################### + +# description: recursively extract all nets from the given 'item_list' by +# breaking them down into the most fundamental bit vectors. +# +# Set 'injection_save' to 1 to make sure only nets that can be +# injected are extracted. Questa SIM has a bug in the 'force' +# command: When trying to force a signal in an array inside a +# struct (Record), the force command will force the field at array +# index 0, and not the selected index. E.g. forcing a.b[7] will +# actually force a.b[0]. When 'injection_save' is set to 1, +# arrays inside structs are skipped from extraction, and a +# info message is generated if the verbosity is high enough +proc extract_netlists {item_list {injection_save 0}} { + set extract_list [list] + foreach item $item_list { + set item_type [get_net_type $item] + if {$item_type == "Register" || $item_type == "Net" || $item_type == "Enum"} { + lappend extract_list $item + } elseif { $item_type == "Array"} { + set array_length [get_net_array_length $item] + if {$injection_save && [string first "." $item] != -1} { + if { $::verbosity >= 3 } { + echo "\[Netlist Extraction\] Net $item is an array inside a struct and will be skipped." + } + } else { + for {set i 0} {$i < $array_length} {incr i} { + set new_net "$item\[$i\]" + set extract_list [concat $extract_list [extract_netlists $new_net $injection_save]] + } + } + } elseif { $item_type == "Record"} { + set fields [get_record_field_names $item] + foreach field $fields { + set new_net $item.$field + set extract_list [concat $extract_list [extract_netlists $new_net $injection_save]] + } + } elseif { $item_type == "int"} { + # Ignore + } else { + if { $::verbosity >= 2 } { + echo "\[Netlist Extraction\] Unknown Type $item_type of net $item. Skipping..." + } + } + } + return $extract_list +} + +#################### +# State Netlists # +#################### + +# Example proc on how to extract state nets (not recursive) +# This is not guaranteed to work for every circuit, as net may not be named +# according to conventions! +proc get_state_netlist {base_path} { + return [extract_netlists [find signal $base_path/*_q] 1] +} + +# Example proc on how to extract state nets. +# This is not guaranteed to work for every circuit, as net may not be named +# according to conventions! +proc get_state_netlist_revursive {base_path} { + return [extract_netlists [find signal -r $base_path/*_q] 1] +} + +##################### +# Next State Nets # +##################### + +# Example proc on how to extract next state nets (not recursive). +# This is not guaranteed to work for every circuit, as net may not be named +# according to conventions! +proc get_next_state_netlist {base_path} { + return [find signal $base_path/*_d] +} + +# Example proc on how to extract next state nets. +# This is not guaranteed to work for every circuit, as net may not be named +# according to conventions! +proc get_next_state_netlist_recursive {base_path} { + return [find signal -r $base_path/*_d] +} + +######################### +# Circuit Output Nets # +######################### + +proc get_output_netlist {base_path} { + return [find signal -out $base_path/*] +} + +################## +# Get all nets # +################## + +proc extract_all_nets_recursive_filtered {base_path filter_list} { + + # recursively extract all signals from the circuit + set netlist [find signal -r $base_path/*]; + + # filter and sort the signals + set netlist_filtered [list]; + foreach net $netlist { + set ignore_net 0 + # ignore any net that matches any ignore pattern + foreach ignore_pattern $filter_list { + if {[string match $ignore_pattern $net]} { + set ignore_net 1 + break + } + } + # add all nets that are not ignored + if {$ignore_net == 0} { + lappend netlist_filtered $net + } + } + + # sort the filtered nets alphabetically + set netlist_filtered [lsort -dictionary $netlist_filtered] + + # recursively extract all nets and enums from arrays and structs + set netlist_extracted [extract_netlists $netlist_filtered 1] + + return $netlist_extracted +} diff --git a/hardware/scripts/questa/get_banks.tcl b/hardware/scripts/questa/get_banks.tcl new file mode 100644 index 000000000..98a16ede2 --- /dev/null +++ b/hardware/scripts/questa/get_banks.tcl @@ -0,0 +1,116 @@ +# This script produces a list of the tag and data nets in the L0, L1 and RO cache. +# Optionally, these can be saved in a file. + +# Search for all instances of tag banks for ROC +set pattern_match "*/i_snitch_read_only_cache/i_lookup/gen_sram/i_tag*" ; +# Get the list of instance paths +set inst_list [find instances -r $pattern_match] ; +# Initialize an empty list to strip off the architecture names +set ROC_tag_list [list] ; +foreach inst $inst_list { + set ipath [lindex $inst 0] + if {[string match $pattern_match $ipath]} { + append ipath "/sram" + lappend ROC_tag_list $ipath + } +} + +# At this point, ROC_tag_list contains the list of instances only-- +# no architecture names +# +# Begin sorting list +#set ROC_tag_list [lsort -dictionary $ROC_tag_list] +## Open a file to write out the list +# +#set fhandle [open "ROC_tag.txt" w] +#foreach inst $ROC_tag_list { +# # Print instance path, one per line +# puts $fhandle $inst +#} +## Close the file, done. +#close $fhandle ; + + + +# Search for all instances of tag banks for L1 IC +set pattern_match "*]/i_tag*" +set inst_list [find instances -r $pattern_match] ; +set L1_tag_list [list] ; +foreach inst $inst_list { + set ipath [lindex $inst 0] + if {[string match $pattern_match $ipath]} { + append ipath "/MemContentxDP" + lappend L1_tag_list $ipath + } +} +#set L1_tag_list [lsort -dictionary $L1_tag_list] +#set fhandle [open "L1I_tag.txt" w] +#foreach inst $L1_tag_list { +# puts $fhandle $inst +#} +#close $fhandle ; + + + +# Search for all instances of data banks for ROC +set pattern_match "*i_snitch_read_only_cache/i_lookup/i_data*" +set inst_list [find instances -r $pattern_match] ; +set ROC_data_list [list] ; +foreach inst $inst_list { + set ipath [lindex $inst 0] + if {[string match $pattern_match $ipath]} { + append ipath "/sram" + lappend ROC_data_list $ipath + } +} +#set ROC_data_list [lsort -dictionary $ROC_data_list] +#set fhandle [open "ROC_data.txt" w] +#foreach inst $ROC_data_list { +# puts $fhandle $inst +#} +#close $fhandle ; + + + +# Search for all instances of data banks for L1 IC +set pattern_match "*/i_snitch_icache/i_lookup/i_data*" +set inst_list [find instances -r $pattern_match] ; +set L1_data_list [list] ; +foreach inst $inst_list { + set ipath [lindex $inst 0] + if {[string match $pattern_match $ipath]} { + append ipath "/sram" + lappend L1_data_list $ipath + } +} +#set L1_data_list [lsort -dictionary $L1_data_list] +#set fhandle [open "L1I_data.txt" w] +#foreach inst $L1_data_list { +# puts $fhandle $inst +#} +#close $fhandle ; + + +# Search for all instances of tag banks for L0 IC +set pattern_match "*i_snitch_icache_l0" +set inst_list [find instances -r $pattern_match] ; +set L0_tag_list [list] ; +foreach inst $inst_list { + set ipath [lindex $inst 0] + if {[string match $pattern_match $ipath]} { + append ipath "/tag" + lappend L0_tag_list $ipath + } +} + +# Search for all instances of data banks for L0 IC +set pattern_match "*i_snitch_icache_l0" +set inst_list [find instances -r $pattern_match] ; +set L0_data_list [list] ; +foreach inst $inst_list { + set ipath [lindex $inst 0] + if {[string match $pattern_match $ipath]} { + append ipath "/data" + lappend L0_data_list $ipath + } +} \ No newline at end of file diff --git a/hardware/scripts/questa/get_number_faults.tcl b/hardware/scripts/questa/get_number_faults.tcl new file mode 100644 index 000000000..2c62372ec --- /dev/null +++ b/hardware/scripts/questa/get_number_faults.tcl @@ -0,0 +1,47 @@ +# Find all signals in the design +set signal_list [find signals -r */data_faults] + +# Open a file for writing +set file_id [open "data_tag_faults_stats.txt" w] + +# Initialize variables for sum and count +set sum 0.0 +set count 0 + +# Loop through each signal in the list +foreach signal $signal_list { + # Get the value of the signal and store it in a variable + set signal_value [examine -radix decimal $signal] + + # Print the signal name and value to the file + puts $file_id "Signal: $signal, Value: $signal_value" + + # Add the value to the sum + set sum [expr {$sum + $signal_value}] + + # Increment the count + incr count +} + +# Calculate the average +set average [expr {$sum / $count}] + +# Print the average to the file +puts $file_id "\nAverage of all values: $average\n\n" + +set signal_list [find signals -r */tag_faults] +set sum 0.0 +set count 0 + +foreach signal $signal_list { + set signal_value [examine -radix decimal $signal] + puts $file_id "Signal: $signal, Value: $signal_value" + set sum [expr {$sum + $signal_value}] + incr count +} +set average [expr {$sum / $count}] + +puts $file_id "\nAverage of all values: $average" + +# Close the file +close $file_id \ No newline at end of file diff --git a/hardware/scripts/questa/inject_fault.tcl b/hardware/scripts/questa/inject_fault.tcl new file mode 100644 index 000000000..9b206b438 --- /dev/null +++ b/hardware/scripts/questa/inject_fault.tcl @@ -0,0 +1,802 @@ +# Copyright 2021 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 +# +# Author: Luca Rufer (lrufer@student.ethz.ch) + +# ============ List of variables that may be passed to this script ============ +# Any of these variables may not be changed while the fault injection script +# is running, unless noted otherwise. Changing any of the settings during +# runtime may result in undefined behaviour. +# ----------------------------------- General --------------------------------- +# 'verbosity' : Controls the amount of information printed during script +# execution. Possible values are: +# 0 : No statements at all +# 1 : Only important initializaion information +# 2 : Important information and occurences of bitflips +# (Recommended). Default +# 3 : All information that is possible +# 'log_injections' : Create a logfile of all injected faults, including +# timestamps, the absolute path of the flipped net, the +# value before the flip, the value after the flip and +# more. +# The logfile is named "fault_injection_.log". +# 0 : Disable logging (Default) +# 1 : Enable logging +# 'seed' : Set the seed for the number generator. Default: 12345 +# To reset the seed and start samping numbers from the +# start of the seed, it is in the responsibility of the +# user to call 'srand'. This is only done one in this +# script when it's sourced. +# 'print_statistics' : Print statistics about the fault injections at the end +# of the fault injection. Which statistics are printed +# also depends on other settings below. +# 0 : Don't print statistics +# 1 : Print statistics (Default) +# 'script_base_path' : Base path of all the scripts that are sourced here: +# Default is set to './' +# ------------------------------- Timing settings ----------------------------- +# 'inject_start_time' : Earliest time of the first fault injection. +# 'inject_stop_time' : Latest possible time for a fault injection. +# Set to 0 for no stop. +# 'injection_clock' : Absolute path to the net that is used as an injected +# trigger and clock. Can be a special trigger clock in +# the testbench, or the normal system clock. +# If 'injection_clock' is set to an empty string (""), +# this script will not perform periodical fault injection. +# 'injection_clock_trigger' : Signal value of 'injection_clock' that triggers +# the fault injection. If a normal clock of a rising edge +# triggered circuit is used as injection clock, it is +# recommended to set the trigger to '0', so injected +# flips can clearly be distinguished in the waveforms. +# 'fault_period' : Period of the fault injection in clock cycles of the +# injection clock. Set to 0 for only a single flip. +# 'rand_initial_injection_phase' : Set the phase relative to the 'fault_period' +# to a random initial value between 0 (inclusive) and +# 'fault_period' (exclusive). If multiple simulation +# with different seeds are performed, this option allows +# the injected faults to be evenly distributed accross +# the 'injection_clock' cycles. +# 0 : Disable random phase. The first fault injection +# is performed at the first injeciton clock trigger +# after the 'inject_start_time'. Default. +# 1 : Enable random phase. +# 'max_num_fault_inject' : Maximum number of faults to be injected. The number +# of faults injected may be lower than this if the +# simualtion finishes before, or if the 'inject_stop_time' +# is reached. If 'max_num_fault_inject' is set to 0, this +# setting is ignored (default). +# 'forced_injection_times' : Provide an explicit list of times when faults are +# to be injected into the simulation. If an empty +# 'forced_injection_signals' list is provided, the signals +# are selected at random according to the Flip settings. +# By default, this list is empty. +# Note that flips forced by this list are not bound +# by the 'inject_start_time' and 'inject_stop_time'. +# Flips forced by this list only count towards the +# 'max_num_fault_inject' limit if +# 'include_forced_inj_in_stats' is set to 1. +# 'forced_injection_signals' : Provide an explicit list of signals where faults +# are to be injected into the simulation at the +# 'forced_injection_times'. This list must have the same +# length as 'forced_injection_times'. Entries in the list +# must have the format {'signal_name' 'is_register}. +# For example: {{"tb/data_q" 1} {"tb/enable" 0}} +# If this list is empty, the signals to be injected at the +# 'forced_injection_times' are selected randomly according +# to the settings below (default). +# Note that listing enums, or signals with a width wider +# than one bit will case a random bit to be selected, +# which will alter the outcome of the periodic random +# fault injection. +# 'include_forced_inj_in_stats' : Select wether the forced injections should be +# included in the statistics or not. Including them in the +# statistics will also cause them to be logged (if logging +# is enabled) and variables like 'last_flipped_net' and +# 'last_injection_time' to be changed. +# 0: Don't include forced injections in statistics and +# logs (default). +# 1: Include forced injections in statistics and logs. +# 'signal_fault_duration' : Duration of faults injected into combinatorial +# signals, before the original value is restored. +# 'register_fault_duration' : Minumum duration of faults injected into +# registers. Faults injected into registers are not +# restored after the 'register_fault_duration' and will +# persist until overwritten by the circuit under test. +# -------------------------------- Flip settings ------------------------------ +# 'allow_multi_bit_upset' : Allow injecting another error in a Register that was +# already flipped and not driven to another value yet. +# 0 : Disable multi bit upsets (default) +# 1 : Enable multi bit upsets +# 'use_bitwidth_as_weight' : Use the bit width of a net as a weight for the +# random fault injection net selection. If this option +# is enabled, a N-bit net has an N times higher chance +# than a 1-bit net of being selected for fault injection. +# 0 : Disable using the bitwidth as weight and give every +# net the same chance of being picked (Default). +# 1 : Enable using the bit width of nets as weights. +# 'check_core_output_modification' : Check if an injected fault changes the +# output of the circuit under test. All nets in +# 'output_netlist' are checked. The result of the check +# is printed after every flip (if verbosity high enough), +# and logged to the logfile. +# 0 : Disable output modification checks. The check will +# be logged as 'x'. +# 1 : Enable output modification checks. +# 'check_core_next_state_modification' : Check if an injected fault changes the +# next state of the circuit under test. All nets in +# 'next_state_netlist' are checked. The result of the +# check is printed after every flip (if verbosity high +# enough), and logged to the logfile. +# 0 : Disable next state modification checks. The check +# will be logged as 'x'. +# 1 : Enable next state modification checks. +# 'reg_to_sig_ratio' : Ratio of Registers to combinatorial signals to be +# selected for a fault injection. Example: A value of 4 +# selects a ratio of 4:1, giving an 80% for a Register to +# be selected, and a 20% change of a combinatorial signal +# to be selected. If the provided +# 'inject_register_netlist' is empty, or the +# 'inject_signals_netlist' is empty, this parameter is +# ignored and nets are only selected from the non-empty +# netlist. +# Default value is 1, so the default ratio is 1:1. +# ---------------------------------- Netlists --------------------------------- +# 'inject_register_netlist' : List of absolute paths to Registers to be flipped +# in the simulation. This is used to simulate Single +# Event Upsets (SEUs). Flips injected in registers are not +# removed by the injection script. If the inject netlist +# is changed after this script was first called, the proc +# 'updated_inject_netlist' must be called. +# 'inject_signals_netlist' : List of absolute paths to combinatorial signals to +# be flipped in the simulation. This is used to simulate +# Single Event Transients (SETs). A fault injection +# drives the target signal for a 'fault_duration', and +# afterwards returns the signal to its original state. +# If the inject netlist is changed after this script was +# first called, the proc 'updated_inject_netlist' must be +# called. +# 'output_netlist' : List of absolute net or register paths to be used for +# the output modification check. +# 'next_state_netlist' : List of absolute net or register paths to be used for +# the next state modification check. +# 'assertion_disable_list' : List of absolute paths to named assertions that +# need to be disabled for during fault injecton. +# Assertions are enabled again after the simulation stop +# time. + +################################## +# Set default parameter values # +################################## + +# General +if {![info exists verbosity]} { set verbosity 2 } +if {![info exists log_injections]} { set log_injections 0 } +if {![info exists seed]} { set seed 12345 } +if {![info exists print_statistics]} { set print_statistics 1 } +if {![info exists script_base_path]} { set script_base_path "./" } +# Timing settings +if {![info exists inject_start_time]} { set inject_start_time 100ns } +if {![info exists inject_stop_time]} { set inject_stop_time 0 } +if {![info exists injection_clock]} { set injection_clock "clk" } +if {![info exists injection_clock_trigger]} { set injection_clock_trigger 0 } +if {![info exists fault_period]} { set fault_period 0 } +if {![info exists rand_initial_injection_phase]} { set rand_initial_injection_phase 0 } +if {![info exists max_num_fault_inject]} { set max_num_fault_inject 0 } +if {![info exists forced_injection_times]} { set forced_injection_times [list] } +if {![info exists forced_injection_signals]} { set forced_injection_signals [list] } +if {![info exists include_forced_inj_in_stats]} { set include_forced_inj_in_stats 0 } +if {![info exists signal_fault_duration]} { set signal_fault_duration 1ns } +if {![info exists register_fault_duration]} { set register_fault_duration 0ns } +# Flip settings +if {![info exists allow_multi_bit_upset]} { set allow_multi_bit_upset 0 } +if {![info exists check_core_output_modification]} { set check_core_output_modification 0 } +if {![info exists check_core_next_state_modification]} { set check_core_next_state_modification 0 } +if {![info exists reg_to_sig_ratio]} { set reg_to_sig_ratio 1 } +if {![info exists use_bitwidth_as_weight]} { set use_bitwidth_as_weight 0 } +# Netlists +if {![info exists inject_register_netlist]} { set inject_register_netlist [list] } +if {![info exists inject_signals_netlist]} { set inject_signals_netlist [list] } +if {![info exists output_netlist]} { set output_netlist [list] } +if {![info exists next_state_netlist]} { set next_state_netlist [list] } +if {![info exists assertion_disable_list]} { set assertion_disable_list [list] } + +# Additional checks +if {[llength $forced_injection_times] != [llength $forced_injection_signals] && \ + [llength $forced_injection_times] != 0 && \ + [llength $forced_injection_signals] != 0} { + if {$::verbosity >= 1} { + echo "\[Fault Injection\] Error: 'forced_injection_times' and \ + 'forced_injection_signals' don't have the same non-zero length!" + } + exit +} + +# Source generic netlist extraction procs +source [subst ${::script_base_path}extract_nets.tcl] + +######################################## +# Finish setup depending on settings # +######################################## + +# Common path sections of all nets where errors can be injected +set ::netlist_common_path_sections [list] + +proc restart_fault_injection {} { + # Start the Error injection script + if {$::verbosity >= 1} { + echo "\[Fault Injection\] Info: Injection script running." + } + + # cleanup from previous runs + if {[info exists ::forced_injection_when_labels]} { + foreach l $::forced_injection_when_labels { + catch {nowhen $l} + } + } + + # Last net that was flipped + set ::last_flipped_net "" + set ::last_injection_time -1 + + # Open the log file + if {$::log_injections} { + set time_stamp [exec date +%Y%m%d_%H%M%S] + set ::injection_log [open "fault_injection_$time_stamp.log" w+] + puts $::injection_log "timestamp,netname,pre_flip_value,post_flip_value,output_changed,new_state_changed" + } else { + set ::injection_log "" + } + + # Dictionary to keep track of injections + set ::inject_dict [dict create] + + # determine the phase for the initial fault injection + if {$::rand_initial_injection_phase} { + set ::prescaler [expr int(rand() * $::fault_period)] + } else { + set ::prescaler [expr $::fault_period - 1] + } + + # List of when-statement labels of forced injection times + set ::forced_injection_when_labels [list] + + # determine the first injection time + set start_time [earliest_time [concat $::forced_injection_times $::inject_start_time]] + + # determine the injection stop time + if {$::inject_stop_time == 0 && ![string equal $::injection_clock ""]} { + set stop_time 0 + } else { + set stop_time [latest_time [concat $::forced_injection_times $::inject_stop_time]] + } + + # Create all When statements + + # start fault injection + when -label inject_start "\$now == @$start_time" { + ::start_fault_injection + nowhen inject_start + } + + # periodically inject faults + if {![string equal $::injection_clock ""]} { + when -label inject_fault "\$now >= @$::inject_start_time and $::injection_clock == $::injection_clock_trigger" { + ::inject_trigger + } + } + + # forced injection times + for {set i 0} { $i < [llength $::forced_injection_times] } { incr i } { + set label "forced_injection_$i" + set t [lindex $::forced_injection_times $i] + if {[llength $::forced_injection_signals] == 0} { + set cmd "::inject_fault $::include_forced_inj_in_stats" + } else { + # Extract the signal infos + set signal_info [lindex $::forced_injection_signals $i] + foreach {signal_name is_register} $signal_info {} + if {$::include_forced_inj_in_stats} { + set cmd "fault_injection_pre_flip_statistics; \ + set flip_return \[::flipbit $signal_name $is_register\]; \ + fault_injection_post_flip_statistics $signal_name \$flip_return" + } else { + set cmd "::flipbit $signal_name $is_register" + } + } + # Create the when statement to flip the bit + when -label $label "\$now == @$t" "$cmd" + # Store the label + lappend ::forced_injection_when_labels $label + } + + # stop the simulation and output statistics + if {$stop_time != 0} { + when -label inject_stop "\$now > @$stop_time" { + ::stop_fault_injection + nowhen inject_stop + } + } +} + +proc start_fault_injection {} { + if {$::verbosity >= 1} { + echo "[time_ns $::now]: \[Fault Injection\] Starting fault injection." + } + # Disable Assertions + foreach assertion $::assertion_disable_list { + assertion enable -off $assertion + } + # Reset statistics + set ::stat_num_bitflips 0 + set ::stat_num_outputs_changed 0 + set ::stat_num_state_changed 0 + set ::stat_num_flip_propagated 0 +} + +################ +# User Procs # +################ + +proc stop_fault_injection {} { + # Stop fault injection + catch {nowhen inject_fault} + # Enable Assertions again + foreach assertion $::assertion_disable_list { + assertion enable -on $assertion + } + # Output simulation Statistics + if {$::verbosity >= 1 && $::print_statistics} { + echo " ========== Fault Injection Statistics ========== " + echo " Number of Bitflips : $::stat_num_bitflips" + if {$::check_core_output_modification} { + echo " Number of Bitflips propagated to outputs : $::stat_num_outputs_changed" + } + if {$::check_core_next_state_modification} { + echo " Number of Bitflips propagated to new state : $::stat_num_state_changed" + } + if {$::check_core_output_modification && $::check_core_next_state_modification} { + echo " Number of Bitflips propagated : $::stat_num_flip_propagated" + } + echo "" + } + # Close the logfile + if {$::log_injections} { + close $::injection_log + } + return $::stat_num_bitflips +} + +####################### +# Helper Procedures # +####################### + +proc earliest_time {time_list} { + if {[llength $time_list] == 0} { + return 0 + } + set earliest [lindex $time_list 0] + foreach t $time_list { + if {[ltTime $t $earliest]} { + set earliest $t + } + } + return $earliest +} + +proc latest_time {time_list} { + if {[llength $time_list] == 0} { + return -1 + } + set latest [lindex $time_list 0] + foreach t $time_list { + if {[gtTime $t $latest]} { + set latest $t + } + } + return $latest +} + +proc time_ns {time_ps} { + set time_str "" + append time_str "[expr $time_ps / 1000]" + set remainder [expr $time_ps % 1000] + if {$remainder != 0} { + append time_str "." + if {$remainder < 100} {append time_str "0"} + if {$remainder < 10} {append time_str "0"} + append time_str "$remainder" + } + append time_str " ns" + return $time_str +} + +proc find_common_path_sections {netlist} { + # Safety check if the list has any elements + if {[llength $netlist] == 0} { + return [list] + } + # Extract the first net as reference + set first_net [lindex $netlist 0] + set first_net_sections [split $first_net "/"] + # Determine the minimal number of sections in the netlist + set min_num_sections 9999 + foreach net $netlist { + set cur_path_sections [split $net "/"] + set num_sections [llength $cur_path_sections] + if {$num_sections < $min_num_sections} {set min_num_sections $num_sections} + } + # Create a match list + set match_list [list] + for {set i 0} {$i < $min_num_sections} {incr i} {lappend match_list 1} + # Test for every net which sections in its path matches the first net path + foreach net $netlist { + set cur_path_sections [split $net "/"] + # Test every section + for {set i 0} {$i < $min_num_sections} {incr i} { + # prevent redundant checking for speedup + if {[lindex $match_list $i] != 0} { + # check if the sections matches the first net section + if {[lindex $first_net_sections $i] != [lindex $cur_path_sections $i]} { + lset match_list $i 0 + } + } + } + } + return $match_list +} + +proc net_print_str {net_name} { + # Check if the list exists + if {[llength $::netlist_common_path_sections] == 0} { + return $net_name + } + # Split the netname path + set cur_path_sections [split $net_name "/"] + set print_str "" + set printed_dots 0 + # check sections individually + for {set i 0} {$i < [llength $cur_path_sections]} {incr i} { + # check if the section at the current index is a common to all paths + if {$i < [llength $::netlist_common_path_sections] && [lindex $::netlist_common_path_sections $i] == 1} { + # Do not print the dots if multiple sections match in sequence + if {!$printed_dots} { + # Print dots to indicate the path was shortened + append print_str "\[...\]" + if {$i != [llength $cur_path_sections] - 1} {append print_str "/"} + set printed_dots 1 + } + } else { + # Sections don't match, print the path section + append print_str "[lindex $cur_path_sections $i]" + if {$i != [llength $cur_path_sections] - 1} {append print_str "/"} + set printed_dots 0 + } + } + return $print_str +} + +proc calculate_weight_by_width {netlist} { + set total_weight 0 + set group_weight_dict [dict create] + set group_net_dict [dict create] + foreach net $netlist { + # determine the width of a net (used as weight) + set width [get_net_reg_width $net] + if {![dict exists $group_weight_dict $width]} { + # New width discovered, add new entry + dict set group_weight_dict $width $width + dict set group_net_dict $width [list $net] + } else { + dict incr group_weight_dict $width $width + dict lappend group_net_dict $width $net + } + } + # Sum weights of all groups + foreach group_weight [dict values $group_weight_dict] { + set total_weight [expr $total_weight + $group_weight] + } + return [list $total_weight $group_weight_dict $group_net_dict] +} + +proc updated_inject_netlist {} { + # print how many nets were found + set num_reg_nets [llength $::inject_register_netlist] + set num_comb_nets [llength $::inject_signals_netlist] + if {$::verbosity >= 1} { + echo "\[Fault Injection\] Selected $num_reg_nets Registers for fault injection." + echo "\[Fault Injection\] Selected $num_comb_nets combinatorial Signals for fault injection." + } + # print all nets that were found + if {$::verbosity >= 3} { + echo "Registers: " + foreach net $::inject_register_netlist { + echo " - [get_net_reg_width $net]-bit [get_net_type $net] : $net" + } + echo "Combinatorial Signals: " + foreach net $::inject_signals_netlist { + echo " - [get_net_reg_width $net]-bit [get_net_type $net] : $net" + } + echo "" + } + # determine the common sections + set combined_inject_netlist [concat $::inject_register_netlist $::inject_signals_netlist] + set ::netlist_common_path_sections [find_common_path_sections $combined_inject_netlist] + # determine the distribution of the nets + if {$::use_bitwidth_as_weight} { + set ::inject_register_distibrution_info [calculate_weight_by_width $::inject_register_netlist] + set ::inject_signals_distibrution_info [calculate_weight_by_width $::inject_signals_netlist] + } +} + +########################## +# Random Net Selection # +########################## + +proc select_random_net {} { + # Choose between Register and Signal + if {[llength $::inject_register_netlist] != 0 && \ + ([llength $::inject_signals_netlist] == 0 || \ + rand() * ($::reg_to_sig_ratio + 1) >= 1)} { + set is_register 1 + set selected_list $::inject_register_netlist + } else { + set is_register 0 + set selected_list $::inject_signals_netlist + } + # Select the distribution + if {$::use_bitwidth_as_weight} { + # select the distribution + if {$is_register} { + set distibrution_info $::inject_register_distibrution_info + } else { + set distibrution_info $::inject_signals_distibrution_info + } + # unpack the distribution + set distribution_total_weight [lindex $distibrution_info 0] + set distribution_weight_dict [lindex $distibrution_info 1] + set distribution_net_dict [lindex $distibrution_info 2] + # determine the group + set selec [expr rand() * $distribution_total_weight] + dict for {group group_weight} $distribution_weight_dict { + if {$selec <= $group_weight} { + break + } else { + set selec [expr $selec - $group_weight] + } + } + set selected_list [dict get $distribution_net_dict $group] + } + set idx [expr int(rand()*[llength $selected_list])] + set selected_net [lindex $selected_list $idx] + return [list $selected_net $is_register] +} + +################ +# Flip a Bit # +################ + +# flip a spefific bit of the given net name. returns a 1 if the bit could be flipped +proc flipbit {signal_name is_register} { + set success 0 + set old_value [examine -radixenumsymbolic $signal_name] + # check if net is an enum + if {[examine -radixenumnumeric $signal_name] != [examine -radixenumsymbolic $signal_name]} { + set old_value_numeric [examine -radix binary,enumnumeric $signal_name] + set new_value_numeric [expr int(rand()*([expr 2 ** [string length $old_value_numeric]]))] + while {$old_value_numeric == $new_value_numeric && [string length $old_value_numeric] != 1} { + set new_value_numeric [expr int(rand()*([expr 2 ** [string length $old_value_numeric]]))] + } + if {$is_register} { + force -freeze $signal_name $new_value_numeric -cancel $::register_fault_duration + } else { + force -freeze $signal_name $new_value_numeric, $old_value_numeric $::signal_fault_duration -cancel $::signal_fault_duration + } + set success 1 + } else { + set flip_signal_name $signal_name + set bin_val [examine -radix binary $signal_name] + set len [string length $bin_val] + set flip_index 0 + if {$len != 1} { + set flip_index [expr int(rand()*$len)] + set flip_signal_name $signal_name\($flip_index\) + } + set old_bit_value "0" + set new_bit_value "1" + if {[string index $bin_val [expr $len - 1 - $flip_index]] == "1"} { + set new_bit_value "0" + set old_bit_value "1" + } + if {$is_register} { + force -freeze $flip_signal_name $new_bit_value -cancel $::register_fault_duration + } else { + force -freeze $flip_signal_name $new_bit_value, $old_bit_value $::signal_fault_duration -cancel $::signal_fault_duration + } + if {[examine -radix binary $signal_name] != $bin_val} {set success 1} + } + set new_value [examine -radixenumsymbolic $signal_name] + set result [list $success $old_value $new_value] + return $result +} + +################################ +# Fault Injection Statistics # +################################ + +proc fault_injection_pre_flip_statistics {} { + # record the output before the flip + set ::pre_flip_out_val [list] + if {$::check_core_output_modification} { + foreach net $::output_netlist { + lappend ::pre_flip_out_val [examine $net] + } + } + # record the new state before the flip + set ::pre_flip_next_state_val [list] + if {$::check_core_next_state_modification} { + foreach net $::next_state_netlist { + lappend ::pre_flip_next_state_val [examine $net] + } + } +} + +proc fault_injection_post_flip_statistics {flipped_net flip_return} { + incr ::stat_num_bitflips + set ::last_flipped_net $flipped_net + set ::last_injection_time $::now + + set flip_propagated 0 + # record the output after the flip + set post_flip_out_val [list] + if {$::check_core_output_modification} { + foreach net $::output_netlist { + lappend post_flip_out_val [examine $net] + } + # check if the output changed + set output_state "not modified" + set output_changed [expr ![string equal $::pre_flip_out_val $post_flip_out_val]] + if {$output_changed} { + set output_state "changed" + incr ::stat_num_outputs_changed + set flip_propagated 1 + } + } else { + set output_changed "x" + } + # record the new state before the flip + set post_flip_next_state_val [list] + if {$::check_core_next_state_modification} { + foreach net $::next_state_netlist { + lappend post_flip_next_state_val [examine $net] + } + # check if the new state changed + set new_state_state "not modified" + set new_state_changed [expr ![string equal $::pre_flip_next_state_val $post_flip_next_state_val]] + if {$new_state_changed} { + set new_state_state "changed" + incr ::stat_num_state_changed + set flip_propagated 1 + } + } else { + set new_state_changed "x" + } + + if {$flip_propagated} { + incr ::stat_num_flip_propagated + } + # display the result + if {$::verbosity >= 2} { + set print_str "[time_ns $::now]: \[Fault Injection\] " + append print_str "Flipped net [net_print_str $flipped_net] from [lindex $flip_return 1] to [lindex $flip_return 2]. " + if {$::check_core_output_modification} { + append print_str "Output signals $output_state. " + } + if {$::check_core_next_state_modification} { + append print_str "New state $new_state_state. " + } + echo $print_str + } + # Log the result + if {$::log_injections} { + puts $::injection_log "$::now,$flipped_net,[lindex $flip_return 1],[lindex $flip_return 2],$output_changed,$new_state_changed" + flush $::injection_log + } +} + +############################## +# Fault injection routine # +############################## + +proc inject_fault {include_in_statistics} { + # If enabled, prepare the statistics for the flip + if {$include_in_statistics} { fault_injection_pre_flip_statistics } + + # Questa currently has a bug that it won't force certain nets. So we retry + # until we successfully flip a net. + # The bug primarily affects arrays of structs: + # If you try to force a member/field of a struct in an array, QuestaSim will + # flip force that member/field in the struct/record with index 0 in the + # array, not at the array index that was specified. + set success 0 + set attempts 0 + while {!$success && [incr attempts] < 50} { + # get a random net + set net_selc_info [::select_random_net] + set net_to_flip [lindex $net_selc_info 0] + set is_register [lindex $net_selc_info 1] + # Check if the selected net is allowed to be flipped + set allow_flip 1 + if {$is_register && !$::allow_multi_bit_upset} { + set net_value [examine -radixenumsymbolic $net_to_flip] + if {[dict exists $::inject_dict $net_to_flip] && [dict get $::inject_dict $net_to_flip] == $net_value} { + set allow_flip 0 + if {$::verbosity >= 3} { + echo "[time_ns $::now]: \[Fault Injection\] Tried to flip [net_print_str $net_to_flip], but was already flipped." + } + } + } + # flip the random net + if {$allow_flip} { + if {[catch {set flip_return [::flipbit $net_to_flip $is_register]}]} { + set flip_return {0 "x" "x"} + } + if {[lindex $flip_return 0]} { + set success 1 + if {$is_register && !$::allow_multi_bit_upset} { + # save the new value to the dict + dict set ::inject_dict $net_to_flip [examine -radixenumsymbolic $net_to_flip] + } + } else { + if {$::verbosity >= 3} { + echo "[time_ns $::now]: \[Fault Injection\] Failed to flip [net_print_str $net_to_flip]. Choosing another one." + } + } + } + } + if {$success && !$include_in_statistics} { + if {$::verbosity >= 2} { + echo "[time_ns $::now]: \[Fault Injection\] \ + Flipped net [net_print_str $net_to_flip] \ + from [lindex $flip_return 1] \ + to [lindex $flip_return 2]. " + } + } + if {$success && $include_in_statistics} { + fault_injection_post_flip_statistics $net_to_flip $flip_return + } +} + +proc ::inject_trigger {} { + # check if any nets are selected for injection + if {[llength $::inject_register_netlist] == 0 && \ + [llength $::inject_signals_netlist] == 0} { + return + } + # check if we reached the injection limit + if {($::max_num_fault_inject != 0) && ($::stat_num_bitflips >= $::max_num_fault_inject)} { + # Stop the simulation + if {$::verbosity >= 2} { + echo "\[Fault Injection\] Injection limit ($::max_num_fault_inject) reached. Stopping error injection..." + } + # Disable the trigger (if not already done so) + catch {nowhen inject_fault} + return + } + # increase prescaler + incr ::prescaler + if {$::prescaler == $::fault_period} { + set ::prescaler 0 + # inject a fault + ::inject_fault 1 + } +} + +# Set the seed for the first time +expr srand($::seed) + +# Update the inject netlist +updated_inject_netlist + +# Reset the fault injection +restart_fault_injection diff --git a/hardware/scripts/questa/mempool_inject_fault.tcl b/hardware/scripts/questa/mempool_inject_fault.tcl new file mode 100644 index 000000000..b7930fa8f --- /dev/null +++ b/hardware/scripts/questa/mempool_inject_fault.tcl @@ -0,0 +1,103 @@ +transcript quietly + +set verbosity 1 +set log_injections 1 +set seed 12345 +set script_base_path "/scratch/sem23h18/project/mempool/hardware/scripts/questa/" +set inject_start_time 5500ns +set inject_stop_time 10000ns +#use a clock in the tb, fault_clk is 10 times faster than clk +set injection_clock "/mempool_tb/fault_clk" +set injection_clock_trigger 0 +set fault_period 1 +set rand_initial_injection_phase 0 +set max_num_fault_inject 12000 +set signal_fault_duration 20ns +set register_fault_duration 0ns + +set allow_multi_bit_upset 0 +set use_bitwidth_as_weight 1 +set check_core_output_modification 0 +set check_core_next_state_modification 0 +set reg_to_sig_ratio 1 + +source ${::script_base_path}get_banks.tcl +set inject_register_netlist {} + +# Tag fault injection on Snitch Instruction Cache +# how many lines in the tag banks +set max_idx_icache 31 +# length of tag banks line +set max_tag_width_icache 25 +# lines in the data banks +set no_banks_icache 63 + +# To check error detection on the tag, inject faults only on the parity bits. To check performance, inject on the entire line. + foreach inst $L1_tag_list { + for {set index $max_idx_icache} {$index >= 0} {incr index -1} { + set signal_path "${inst}[${index}]" +#set signal_path "${inst}[${index}][${max_tag_width_icache}]" + lappend inject_register_netlist $signal_path + } + } + foreach inst $L1_data_list { + for {set index $no_banks_icache} {$index >= 0} {incr index -1} { + set signal_path "${inst}[${index}]" + lappend inject_register_netlist $signal_path + } + } + +# Tag fault injection on Snitch RO Cache +set max_idx_rocache 63 +set max_tag_width_rocache 47 +set no_banks_rocache 127 + + foreach inst $ROC_tag_list { + for {set index $max_idx_rocache} {$index >= 0} {incr index -1} { +#set signal_path "${inst}[${index}][${max_tag_width_rocache}]" + set signal_path "${inst}[${index}]" + #lappend inject_register_netlist $signal_path + } + } + foreach inst $ROC_data_list { + for {set index $no_banks_rocache} {$index >= 0} {incr index -1} { + set signal_path "${inst}[${index}]" + #lappend inject_register_netlist $signal_path + } + } + +# Tag fault injection in the L0 Cache, unfortunately not working correctly due to a bug. See fault injection script for more details. +set max_idx_l0cache 3 +set max_tag_width_l0cache 27 +set no_banks_l0cache 3 + + foreach inst $L0_tag_list { + for {set index $max_idx_l0cache} {$index >= 0} {incr index -1} { + #set signal_path "${inst}[${index}].tag[${max_tag_width_l0cache}]" + set signal_path "${inst}[${index}].tag" + #set signal_path "${inst}[${index}]" + #lappend inject_register_netlist $signal_path + } + } + foreach inst $L0_data_list { + for {set index $no_banks_l0cache} {$index >= 0} {incr index -1} { + set signal_path "${inst}[${index}]" + lappend inject_register_netlist $signal_path + } + } + + +# Random shuffle of the list, the log showed that fault injection script is not so random in picking the signals +for {set i 0} {$i < [llength $inject_register_netlist]} {incr i} { + set j [expr {int(rand() * [llength $inject_register_netlist])}] + set temp [lindex $inject_register_netlist $j] + set inject_register_netlist [lreplace $inject_register_netlist $j $j [lindex $inject_register_netlist $i]] + set inject_register_netlist [lreplace $inject_register_netlist $i $i $temp] +} + +set inject_signals_netlist [] +set output_netlist [] +set next_state_netlist [] +set assertion_disable_list [] + +source ${::script_base_path}inject_fault.tcl diff --git a/hardware/scripts/questa/run.tcl b/hardware/scripts/questa/run.tcl index 9310e79e5..cb74cbe5f 100644 --- a/hardware/scripts/questa/run.tcl +++ b/hardware/scripts/questa/run.tcl @@ -1,7 +1,14 @@ # Copyright 2021 ETH Zurich and University of Bologna. # Solderpad Hardware License, Version 0.51, see LICENSE for details. # SPDX-License-Identifier: SHL-0.51 +set allow_faults 1 +#[lindex $argv 0] +# Run the fault injection script +if {$::env(icache_faults) == 1} { + source ../scripts/questa/mempool_inject_fault.tcl +} do ../scripts/questa/wave.tcl + log -r /* run -a diff --git a/hardware/scripts/questa/wave.tcl b/hardware/scripts/questa/wave.tcl index 970e59cfb..dc388218f 100644 --- a/hardware/scripts/questa/wave.tcl +++ b/hardware/scripts/questa/wave.tcl @@ -84,4 +84,6 @@ add wave -Group DMA_midend_cluster /mempool_tb/dut/i_mempool_cluster/i_idma_dist add wave -Group DMA_split /mempool_tb/dut/i_mempool_cluster/i_idma_split_midend/* +add wave -Group L0CACHE /mempool_tb/dut/i_mempool_cluster/gen_groups[0]/i_group/gen_tiles[0]/i_tile/gen_caches[0]/i_snitch_icache/gen_prefetcher[0]/i_snitch_icache_l0/* + do ../scripts/questa/wave_cache.tcl 0 0 0 diff --git a/hardware/src/axi_hier_interco.sv b/hardware/src/axi_hier_interco.sv index 49249c940..e52877593 100644 --- a/hardware/src/axi_hier_interco.sv +++ b/hardware/src/axi_hier_interco.sv @@ -25,22 +25,23 @@ module axi_hier_interco import mempool_pkg::ro_cache_ctrl_t; #( - parameter int unsigned NumSlvPorts = 0, - parameter int unsigned NumMstPorts = 0, - parameter int unsigned Radix = 2, - parameter int unsigned EnableCache = 0, - parameter int unsigned CacheLineWidth = 0, - parameter int unsigned CacheSizeByte = 0, - parameter int unsigned CacheSets = 0, - parameter int unsigned AddrWidth = 0, - parameter int unsigned DataWidth = 0, - parameter int unsigned SlvIdWidth = 0, - parameter int unsigned MstIdWidth = 0, - parameter int unsigned UserWidth = 0, - parameter type slv_req_t = logic, - parameter type slv_resp_t = logic, - parameter type mst_req_t = logic, - parameter type mst_resp_t = logic + parameter int unsigned NumSlvPorts = 0, + parameter int unsigned NumMstPorts = 0, + parameter int unsigned Radix = 2, + parameter int unsigned EnableCache = 0, + parameter int unsigned CacheLineWidth = 0, + parameter int unsigned CacheSizeByte = 0, + parameter int unsigned CacheSets = 0, + parameter bit CacheReliability = 0, + parameter int unsigned AddrWidth = 0, + parameter int unsigned DataWidth = 0, + parameter int unsigned SlvIdWidth = 0, + parameter int unsigned MstIdWidth = 0, + parameter int unsigned UserWidth = 0, + parameter type slv_req_t = logic, + parameter type slv_resp_t = logic, + parameter type mst_req_t = logic, + parameter type mst_resp_t = logic ) ( input logic clk_i, input logic rst_ni, @@ -120,60 +121,62 @@ module axi_hier_interco for (genvar i = 0; i < NumMuxes; i++) begin : gen_lower_level axi_hier_interco #( - .NumSlvPorts (Radix ), - .NumMstPorts (1 ), - .Radix (Radix ), - .EnableCache (EnableCache ), - .CacheLineWidth (CacheLineWidth), - .CacheSizeByte (CacheSizeByte ), - .CacheSets (CacheSets ), - .AddrWidth (AddrWidth ), - .DataWidth (DataWidth ), - .SlvIdWidth (SlvIdWidth ), - .MstIdWidth (SlvIdWidth ), - .UserWidth (UserWidth ), - .slv_req_t (slv_req_t ), - .slv_resp_t (slv_resp_t ), - .mst_req_t (slv_req_t ), - .mst_resp_t (slv_resp_t ) + .NumSlvPorts (Radix ), + .NumMstPorts (1 ), + .Radix (Radix ), + .EnableCache (EnableCache ), + .CacheLineWidth (CacheLineWidth ), + .CacheSizeByte (CacheSizeByte ), + .CacheSets (CacheSets ), + .CacheReliability (CacheReliability), + .AddrWidth (AddrWidth ), + .DataWidth (DataWidth ), + .SlvIdWidth (SlvIdWidth ), + .MstIdWidth (SlvIdWidth ), + .UserWidth (UserWidth ), + .slv_req_t (slv_req_t ), + .slv_resp_t (slv_resp_t ), + .mst_req_t (slv_req_t ), + .mst_resp_t (slv_resp_t ) ) i_axi_interco ( - .clk_i (clk_i ), - .rst_ni (rst_ni ), - .test_i (test_i ), - .ro_cache_ctrl_i (ro_cache_ctrl_i ), - .slv_req_i (slv_req_i[i*Radix +: Radix] ), - .slv_resp_o (slv_resp_o[i*Radix +: Radix]), - .mst_req_o (int_req[i] ), - .mst_resp_i (int_resp[i] ) + .clk_i (clk_i ), + .rst_ni (rst_ni ), + .test_i (test_i ), + .ro_cache_ctrl_i (ro_cache_ctrl_i ), + .slv_req_i (slv_req_i[i*Radix +: Radix] ), + .slv_resp_o (slv_resp_o[i*Radix +: Radix]), + .mst_req_o (int_req[i] ), + .mst_resp_i (int_resp[i] ) ); end axi_hier_interco #( - .NumSlvPorts (NumMuxes ), - .NumMstPorts (NumMstPorts ), - .Radix (Radix ), - .EnableCache (EnableCache>>1), - .CacheLineWidth (CacheLineWidth), - .CacheSizeByte (CacheSizeByte ), - .CacheSets (CacheSets ), - .AddrWidth (AddrWidth ), - .DataWidth (DataWidth ), - .SlvIdWidth (SlvIdWidth ), - .MstIdWidth (MstIdWidth ), - .UserWidth (UserWidth ), - .slv_req_t (slv_req_t ), - .slv_resp_t (slv_resp_t ), - .mst_req_t (mst_req_t ), - .mst_resp_t (mst_resp_t ) + .NumSlvPorts (NumMuxes ), + .NumMstPorts (NumMstPorts ), + .Radix (Radix ), + .EnableCache (EnableCache>>1 ), + .CacheLineWidth (CacheLineWidth ), + .CacheSizeByte (CacheSizeByte ), + .CacheSets (CacheSets ), + .CacheReliability (CacheReliability), + .AddrWidth (AddrWidth ), + .DataWidth (DataWidth ), + .SlvIdWidth (SlvIdWidth ), + .MstIdWidth (MstIdWidth ), + .UserWidth (UserWidth ), + .slv_req_t (slv_req_t ), + .slv_resp_t (slv_resp_t ), + .mst_req_t (mst_req_t ), + .mst_resp_t (mst_resp_t ) ) i_axi_interco ( - .clk_i (clk_i ), - .rst_ni (rst_ni ), - .test_i (test_i ), - .ro_cache_ctrl_i (ro_cache_ctrl_i), - .slv_req_i (int_req ), - .slv_resp_o (int_resp ), - .mst_req_o (mst_req_o ), - .mst_resp_i (mst_resp_i ) + .clk_i (clk_i ), + .rst_ni (rst_ni ), + .test_i (test_i ), + .ro_cache_ctrl_i (ro_cache_ctrl_i), + .slv_req_i (int_req ), + .slv_resp_o (int_resp ), + .mst_req_o (mst_req_o ), + .mst_resp_i (mst_resp_i ) ); end end else if (NumSlvPorts <= Radix && NumMstPorts == 1) begin : gen_bottom_level @@ -225,19 +228,20 @@ module axi_hier_interco if (EnableCache[0]) begin: gen_ro_cache localparam int unsigned LineCount = CacheSizeByte/(CacheSets*CacheLineWidth/8); snitch_read_only_cache #( - .LineWidth (CacheLineWidth), - .LineCount (LineCount ), - .SetCount (CacheSets ), - .AxiAddrWidth (AddrWidth ), - .AxiDataWidth (DataWidth ), - .AxiIdWidth (IntIdWidth ), - .AxiUserWidth (UserWidth ), - .MaxTrans (32'd16 ), - .NrAddrRules (NrAddrRules ), - .slv_req_t (int_req_t ), - .slv_rsp_t (int_resp_t ), - .mst_req_t (cache_req_t ), - .mst_rsp_t (cache_resp_t ) + .LineWidth (CacheLineWidth ), + .LineCount (LineCount ), + .SetCount (CacheSets ), + .Reliability (CacheReliability), + .AxiAddrWidth (AddrWidth ), + .AxiDataWidth (DataWidth ), + .AxiIdWidth (IntIdWidth ), + .AxiUserWidth (UserWidth ), + .MaxTrans (32'd16 ), + .NrAddrRules (NrAddrRules ), + .slv_req_t (int_req_t ), + .slv_rsp_t (int_resp_t ), + .mst_req_t (cache_req_t ), + .mst_rsp_t (cache_resp_t ) ) i_snitch_read_only_cache ( .clk_i (clk_i ), .rst_ni (rst_ni ), diff --git a/hardware/src/mempool_group.sv b/hardware/src/mempool_group.sv index 458c061d6..a39c9b050 100644 --- a/hardware/src/mempool_group.sv +++ b/hardware/src/mempool_group.sv @@ -386,22 +386,23 @@ module mempool_group end : gen_axi_slv_vec axi_hier_interco #( - .NumSlvPorts (NumTilesPerGroup+NumDmasPerGroup), - .NumMstPorts (NumAXIMastersPerGroup ), - .Radix (AxiHierRadix ), - .EnableCache (32'hFFFFFFFF ), - .CacheLineWidth (ROCacheLineWidth ), - .CacheSizeByte (ROCacheSizeByte ), - .CacheSets (ROCacheSets ), - .AddrWidth (AddrWidth ), - .DataWidth (AxiDataWidth ), - .SlvIdWidth (AxiTileIdWidth ), - .MstIdWidth (AxiTileIdWidth ), - .UserWidth (1 ), - .slv_req_t (axi_tile_req_t ), - .slv_resp_t (axi_tile_resp_t ), - .mst_req_t (axi_tile_req_t ), - .mst_resp_t (axi_tile_resp_t ) + .NumSlvPorts (NumTilesPerGroup+NumDmasPerGroup), + .NumMstPorts (NumAXIMastersPerGroup ), + .Radix (AxiHierRadix ), + .EnableCache (32'hFFFFFFFF ), + .CacheLineWidth (ROCacheLineWidth ), + .CacheSizeByte (ROCacheSizeByte ), + .CacheSets (ROCacheSets ), + .CacheReliability (ROCacheReliability ), + .AddrWidth (AddrWidth ), + .DataWidth (AxiDataWidth ), + .SlvIdWidth (AxiTileIdWidth ), + .MstIdWidth (AxiTileIdWidth ), + .UserWidth (1 ), + .slv_req_t (axi_tile_req_t ), + .slv_resp_t (axi_tile_resp_t ), + .mst_req_t (axi_tile_req_t ), + .mst_resp_t (axi_tile_resp_t ) ) i_axi_interco ( .clk_i (clk_i ), .rst_ni (rst_ni ), diff --git a/hardware/src/mempool_pkg.sv b/hardware/src/mempool_pkg.sv index de8449b99..d81d5e6a2 100644 --- a/hardware/src/mempool_pkg.sv +++ b/hardware/src/mempool_pkg.sv @@ -27,6 +27,7 @@ package mempool_pkg; localparam integer unsigned AxiDataWidth = `ifdef AXI_DATA_WIDTH `AXI_DATA_WIDTH `else 0 `endif; localparam integer unsigned AxiLiteDataWidth = 32; + /*********************** * MEMORY PARAMETERS * ***********************/ @@ -134,6 +135,8 @@ package mempool_pkg; localparam int unsigned ICacheSizeByte = 512 * NumCoresPerCache; // Total Size of instruction cache in bytes localparam int unsigned ICacheSets = NumCoresPerCache / 2; // Number of sets localparam int unsigned ICacheLineWidth = 32 * 2 * NumCoresPerCache; // Size of each cache line in bits + localparam bit ICacheReliabilityL1 = `ifdef REL_L1ICACHE `REL_L1ICACHE `else 1'bX `endif; // Reliability for L1 icaches enabled? + localparam bit ICacheReliabilityL0 = `ifdef REL_L0ICACHE `REL_L0ICACHE `else 1'bX `endif; // Reliability for L0 icaches enabled? /********************* * READ-ONLY CACHE * @@ -143,6 +146,7 @@ package mempool_pkg; localparam int unsigned ROCacheLineWidth = `ifdef RO_LINE_WIDTH `RO_LINE_WIDTH `else 0 `endif; localparam int unsigned ROCacheSizeByte = 8192; localparam int unsigned ROCacheSets = 2; + localparam bit ROCacheReliability = `ifdef REL_ROCACHE `REL_ROCACHE `else 1'bX `endif; // Reliability for ROcaches enabled? localparam int unsigned ROCacheNumAddrRules = 4; typedef struct packed { diff --git a/hardware/src/mempool_tile.sv b/hardware/src/mempool_tile.sv index a3a6aa50b..314105ee2 100644 --- a/hardware/src/mempool_tile.sv +++ b/hardware/src/mempool_tile.sv @@ -177,6 +177,8 @@ module mempool_tile .FETCH_DW (DataWidth ), .FILL_AW (AddrWidth ), .FILL_DW (AxiDataWidth ), + .RELIABILITY_L1 (ICacheReliabilityL1 ), + .RELIABILITY_L0 (ICacheReliabilityL0 ), .L1_TAG_SCM (1 ), /// Make the early cache latch-based. This reduces latency at the cost of /// increased combinatorial path lengths and the hassle of having latches in diff --git a/hardware/tb/mempool_tb.sv b/hardware/tb/mempool_tb.sv index dcefc5365..7250ef43b 100644 --- a/hardware/tb/mempool_tb.sv +++ b/hardware/tb/mempool_tb.sv @@ -40,15 +40,17 @@ module mempool_tb; * Clock and Reset Generation * ********************************/ - logic clk; + logic clk, fault_clk; logic rst_n; // Toggling the clock always #(ClockPeriod/2) clk = !clk; + always #(ClockPeriod/20) fault_clk = !fault_clk; // Controlling the reset initial begin clk = 1'b1; + fault_clk = 1'b1; rst_n = 1'b0; repeat (5)