diff --git a/Bender.yml b/Bender.yml index 94c17537..9ada50fe 100644 --- a/Bender.yml +++ b/Bender.yml @@ -25,14 +25,26 @@ sources: - rtl/hsiao_ecc/hsiao_ecc_enc.sv - rtl/hsiao_ecc/hsiao_ecc_dec.sv - rtl/hsiao_ecc/hsiao_ecc_cor.sv + - rtl/time_redundancy/DTR_interface.sv + - rtl/time_redundancy/retry_interface.sv + - rtl/time_redundancy/rr_arb_tree_lock.sv - rtl/TMR_voter.sv - rtl/TMR_voter_fail.sv - rtl/TMR_word_voter.sv # Level 1 - rtl/ODRG_unit/odrg_manager_reg_top.sv - rtl/ecc_wrap/ecc_manager_reg_top.sv - - rtl/pulpissimo_tcls/tcls_manager_reg_top.sv - rtl/ecc_wrap/ecc_scrubber.sv + - rtl/pulpissimo_tcls/tcls_manager_reg_top.sv + - rtl/time_redundancy/DTR_end.sv + - rtl/time_redundancy/DTR_start.sv + - rtl/time_redundancy/redundancy_controller.sv + - rtl/time_redundancy/retry_end.sv + - rtl/time_redundancy/retry_inorder_start.sv + - rtl/time_redundancy/retry_inorder_end.sv + - rtl/time_redundancy/retry_start.sv + - rtl/time_redundancy/TTR_end.sv + - rtl/time_redundancy/TTR_start.sv - target: any(deprecated, axi_ecc, hci_ecc, pulp_ecc, test) files: @@ -89,6 +101,11 @@ sources: - rtl/pulpissimo_tcls/TCLS_unit.sv - target: test files: + - test/tb_bitwise_tmr_voter.sv + - test/tb_bitwise_tmr_voter_fail.sv + - test/tb_dtr.sv + - test/tb_dtr_retry.sv + - test/tb_dtr_retry_lock.sv - test/tb_ecc_scrubber.sv - test/tb_ecc_secded.sv - test/tb_ecc_sram.sv @@ -96,8 +113,12 @@ sources: - test/tb_tmr_voter_fail.sv - test/tb_tmr_voter_detect.sv - test/tb_tmr_word_voter.sv - - test/tb_bitwise_tmr_voter.sv - - test/tb_bitwise_tmr_voter_fail.sv + - test/tb_ttr.sv + - test/tb_ttr_lock.sv + - test/tb_redundancy_controller.sv + - test/tb_retry.sv + - test/tb_retry_inorder.sv + - test/tb_rr_arb_tree_lock.sv - test/tb_voter_macros.sv vendor_package: diff --git a/rtl/time_redundancy/DTR_end.sv b/rtl/time_redundancy/DTR_end.sv new file mode 100644 index 00000000..d50d7023 --- /dev/null +++ b/rtl/time_redundancy/DTR_end.sv @@ -0,0 +1,372 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: DTR is a pair of modules that can be used to +// detect faults in any (pipelined) combinatorial process. This is +// done by repeating each input twice and comparing the outputs. +// +// Faults that can be handled: +// - Any number of fault in the datapath during one cycle e.g. wrong result. +// - A single fault in the handshake (valid or ready) e.g. dropped or injected results. +// - A single fault in the accompanying ID. +// +// In case a fault occurs and the result needs to be recalculated, the needs_retry_o signal +// is asserted for one cycle and the ID that needs to be tried again is given. +// Not all faults nessesarily need a retry, in case you just want to know if +// faults are happening you can use fault_detected_o for it. +// +// This needs_retry_o signal should be used to trigger some kind of mitigating action: +// For example, one could use the "retry" modules or "retry_inorder" modules +// to recalculate in hardware, or invoke some recalculation or error on the software side. +// +// In order to propperly function: +// - DTR_interface of DTR_start needs to be connected to DTR_interface of DTR_end. +// - id_o of DTR_start needs to be passed paralelly to the combinatorial logic, using the same handshake +// and arrive at id_i of DTR_end. +// - All operation pairs in contact with each other have a unique ID. +// - The module can only be enabled / disabled when the combinatorially process holds no valid data. +// +// This module can deal with out-of-order combinatorial processes under the conditions that +// the two operations belonging together are not separated. +// To facilitate this on the output side the module has the lock_o signal which is asserted if +// the module wants another element of the same kind in the next cycle. +// This can for example be used with the rr_arb_tree_lock module, but other implementations are +// permissible. + +`include "redundancy_cells/voters.svh" +`include "common_cells/registers.svh" + +module DTR_end # ( + // The data type you want to send through / replicate + parameter type DataType = logic, + // How long until the time_TMR module should abort listening for further copies + // of some data when they don't show up e.g. handshake failed + // For an in-order process you should set this to 4 + // For an out-of-order process (with RR Arbitrator) you should set it to 5 + // In case your combinatorial logic takes more than once cycle to compute an element + // multiply by the respective number of cycles + parameter int unsigned LockTimeout = 4, + // The size of the ID to use as an auxilliary signal + // For an in-order process, this can be set to 2. + // For an out of order process, it needs to be big enough so that the out-of-orderness can never + // rearange the elements with the same id next to each other + // As an estimate you can use log2(longest_pipeline) + 1 + // Needs to match with DTR_start! + parameter int unsigned IDSize = 1, + // Determines if the internal state machines should + // be parallely redundant, meaning errors inside this module + // can also not cause errors in the output + // The external output is never protected! + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1 +) ( + input logic clk_i, + input logic rst_ni, + input logic enable_i, + + // Direct connection + DTR_interface.reciever dtr_interface, + + // Upstream connection + input DataType data_i, + input logic [IDSize-1:0] id_i, + input logic valid_i, + output logic ready_o, + + // Signal for working with upstream Lockable RR Arbiter + output logic [REP-1:0] lock_o, + + // Downstream connection + output DataType data_o, + output logic [IDSize-2:0] id_o, + output logic needs_retry_o, + output logic valid_o, + input logic ready_i, + + // Output Flags for Error Counting + output logic fault_detected_o +); + + // Redundant Output signals + logic [REP-1:0] ready_ov; + logic [REP-1:0] valid_ov; + logic [REP-1:0] needs_retry_ov; + logic [REP-1:0] fault_detected_ov; + + ///////////////////////////////////////////////////////////////////////////////// + // Storage of incomming results and generating good output data + + DataType data_q; + logic [IDSize-1:0] id_q; + + // Next State Combinatorial Logic + logic load_enable; + assign load_enable = valid_i & ready_o & enable_i; + + // Storage Element + `FFL(data_q, data_i, load_enable, 'h1); + `FFL(id_q, id_i, load_enable, 'h1); + + ///////////////////////////////////////////////////////////////////////////////// + // Comparisons genereration for Handshake / State Machine + + logic [1:0] data_same, id_same, full_same, partial_same; + logic data_same_in, id_same_in; + logic data_same_q, id_same_q; + + always_comb begin: gen_data_same + data_same_in = '0; + id_same_in = '0; + + // If disabled just send out input + if (!enable_i) begin + data_o = data_i; + id_o = id_i[IDSize-2:0]; + end else begin + data_o = data_q; + id_o = id_q[IDSize-2:0]; + end + + if (data_i == data_q) data_same_in = '1; + if (id_i == id_q) id_same_in = '1; + end + + + ///////////////////////////////////////////////////////////////////////////////// + // Storage of same / not same for one extra cycle + + `FFL(data_same_q, data_same_in, load_enable, 1'b1); + `FFL(id_same_q, id_same_in, load_enable, 1'b1); + + // Output (merged) signal assigment + assign data_same = {data_same_q, data_same_in}; + assign id_same = {id_same_q, id_same_in}; + + assign full_same = data_same & id_same; + assign partial_same = data_same | id_same; + + ///////////////////////////////////////////////////////////////////////////////// + // Logic to find out what we should do with our data based on same / not same + + logic [REP-1:0] new_element_arrived_v; + logic [REP-1:0] data_usable_v; + + // Flag Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_data_flags + always_comb begin: gen_data_flags_comb + // Some new element just showed up and we need to send data outwards again. + new_element_arrived_v[r] = (id_same == 2'b01) || (id_same == 2'b00); + + // Data has at least two new things that are the same + data_usable_v[r] = data_same[0]; + end + end + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // State machine to figure out handshake + + typedef enum logic [1:0] {BASE, WAIT_FOR_READY} state_t; + state_t [REP-1:0] state_b, state_v, state_d, state_q; + logic [REP-1:0] valid_internal_v, lock_internal_v; + + // Special State Description: + // Wait for Ready: We got some data that is usable, but downstream can't use it yet + // -> We keep shifting as far as our pipeline goes to collect all data samples if we haven't yet and then stop + // Wait for Valid: We got some data that is usable, and sent it downstream right away + // -> We try to collect one more piece of our data and then move on + + + // Next State Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_next_state + always_comb begin: gen_next_state_comb + // Default to staying in the same state + state_v[r] = state_q[r]; + + case (state_q[r]) + BASE: + if (valid_i) begin + if (new_element_arrived_v[r]) begin + if (!ready_i) begin + state_v[r] = WAIT_FOR_READY; + end + end + end + WAIT_FOR_READY: + if (ready_i) begin + state_v[r] = BASE; // Downstream takes the data that we are holding and we can go back to the base state + end + endcase + end + end + + // Generate default cases + for (genvar r = 0; r < REP; r++) begin: gen_default_state + assign state_b[r] = BASE; + end + + // State Voting Logic + `VOTEXX(REP, state_v, state_d); + + // State Storage + `FF(state_q, state_d, state_b); + + // Output Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_output + always_comb begin: gen_output_comb + if (enable_i) begin + case (state_q[r]) + BASE: valid_internal_v[r] = valid_i & new_element_arrived_v[r]; + WAIT_FOR_READY: valid_internal_v[r] = valid_i; + endcase + + case (state_q[r]) + BASE: lock_internal_v[r] = !valid_i | !full_same[0]; + WAIT_FOR_READY: lock_internal_v[r] = !ready_i; + endcase + + case (state_q[r]) + BASE: ready_ov[r] = ready_i | !new_element_arrived_v[r]; + WAIT_FOR_READY: ready_ov[r] = ready_i; + endcase + end else begin + valid_internal_v[r] = valid_i; + lock_internal_v[r] = 0; + ready_ov[r] = ready_i; + end + end + end + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // State machine to lock / unlock Arbitrator with Watchdog timer + + logic [REP-1:0] lock_b, lock_v, lock_d, lock_q; + logic [REP-1:0][$clog2(LockTimeout)-1:0] counter_b, counter_v, counter_d, counter_q; + + // Next State Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_lock_next_state + always_comb begin: gen_lock_next_state_comb + if (counter_q[r] > LockTimeout) begin + lock_v[r] = 0; + counter_v[r] = 0; + + end else begin + if (lock_q[r] & !valid_i) begin + counter_v[r] = counter_q[r] + 1; + end else begin + counter_v[r] = 0; + end + + // To set Lock -> 1 require previous imput to be valid, to set Lock -> 0 lock don't require anything + if (valid_i | lock_q[r]) begin + lock_v[r] = lock_internal_v[r]; + end else begin + lock_v[r] = lock_q[r]; + end + end + end + end + + // State Voting Logic + `VOTEXX(REP, lock_v, lock_d); + `VOTEXX(REP, counter_v, counter_d); + + assign lock_o = lock_d; + + // Default state + for (genvar r = 0; r < REP; r++) begin: gen_lock_default_state + assign lock_b[r] = '0; + assign counter_b[r] = '0; + end + + // State Storage + `FF(lock_q, lock_d, lock_b); + `FF(counter_q, counter_d, counter_b); + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // Output deduplication based on ID and ID Faults + + // Split ID signal into parts + logic id_q_fault; + logic [IDSize-2:0] id_q_noparity; + + assign id_q_fault = ^id_q; + assign id_q_noparity = id_q[IDSize-2:0]; + + logic [REP-1:0][2 ** (IDSize-1)-1:0] recently_seen_b, recently_seen_v, recently_seen_d, recently_seen_q; + + for (genvar r = 0; r < REP; r++) begin: gen_deduplication_next_state + always_comb begin: gen_deduplication_next_state_comb + recently_seen_v[r] = recently_seen_q[r]; + + if (dtr_interface.sent[r]) begin + recently_seen_v[r][dtr_interface.id[r][IDSize-2:0]] = 0; + end + + if (valid_internal_v[r] & ready_i & !id_q_fault) begin + recently_seen_v[r][id_q_noparity] = 1; + end + end + end + + // State Voting Logic + `VOTEXX(REP, recently_seen_v, recently_seen_d); + + // Default state + for (genvar r = 0; r < REP; r++) begin: gen_deduplication_default_state + assign recently_seen_b[r] = ~'0; // All 1s! + end + + // State Storage + `FF(recently_seen_q, recently_seen_d, recently_seen_b); + + for (genvar r = 0; r < REP; r++) begin: gen_deduplication_output + always_comb begin: gen_deduplication_output_comb + if (enable_i) begin + if (id_q_fault | recently_seen_q[r][id_q_noparity] & valid_internal_v[r]) begin + valid_ov[r] = 0; + end else begin + valid_ov[r] = valid_internal_v[r]; + end + needs_retry_ov[r] = id_same[0] & !data_usable_v[r]; + end else begin + valid_ov[r] = valid_internal_v[r]; + needs_retry_ov[r] = 0; + end + end + end + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // Build error flag + + // Since we can sometimes output data that only showed up for one cycle e.g. when another id was faulty + // or handshake failed and are sure it does not need retry, but a fault occured anyway + // We can not us the need_retry_o signal to signify all faults. Instead we have a special signal + // In a non-error case, we should have a full same signal every other cycle + // So if that is not the case we had a fault. + + logic [REP-1:0] fault_detected_b, fault_detected_d, fault_detected_q; + + for (genvar r = 0; r < REP; r++) begin: gen_flag_next_state + assign fault_detected_d[r] = ~|full_same[1:0] & valid_i;; + end + + // Default state + for (genvar r = 0; r < REP; r++) begin: gen_flag_default_state + assign fault_detected_b[r] = '0; + end + + `FF(fault_detected_q, fault_detected_d, fault_detected_b); + + for (genvar r = 0; r < REP; r++) begin: gen_flag_output + assign fault_detected_ov[r] = fault_detected_d[r] & !fault_detected_q[r]; + end + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // Output Voting + + `VOTEX1(REP, ready_ov, ready_o); + `VOTEX1(REP, valid_ov, valid_o); + `VOTEX1(REP, needs_retry_ov, needs_retry_o); + `VOTEX1(REP, fault_detected_ov, fault_detected_o); + +endmodule diff --git a/rtl/time_redundancy/DTR_interface.sv b/rtl/time_redundancy/DTR_interface.sv new file mode 100644 index 00000000..6f7ea614 --- /dev/null +++ b/rtl/time_redundancy/DTR_interface.sv @@ -0,0 +1,29 @@ +// Author: Maurus Item , ETH Zurich +// Date: 09.09.2024 +// Description: Interface in between DTR modules to transmit which elements are valid + +interface DTR_interface #( + parameter IDSize = 0, + // Determines if the internal state machines should + // be parallely redundant, meaning errors inside this module + // can also not cause errors in the output + // The external output is never protected! + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1 +) ( + /* No ports on this interface */ +); + logic [REP-1:0][IDSize-1:0] id; + logic [REP-1:0] sent; + + modport sender ( + input id, + input sent + ); + + modport reciever ( + output id, + output sent + ); +endinterface \ No newline at end of file diff --git a/rtl/time_redundancy/DTR_start.sv b/rtl/time_redundancy/DTR_start.sv new file mode 100644 index 00000000..0d70998b --- /dev/null +++ b/rtl/time_redundancy/DTR_start.sv @@ -0,0 +1,293 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: DTR is a pair of modules that can be used to +// detect faults in any (pipelined) combinatorial process. This is +// done by repeating each input twice and comparing the outputs. +// +// Faults that can be handled: +// - Any number of fault in the datapath during one cycle e.g. wrong result. +// - A single fault in the handshake (valid or ready) e.g. dropped or injected results. +// - A single fault in the accompanying ID. +// +// In case a fault occurs and the result needs to be recalculated, the needs_retry_o signal +// is asserted for one cycle and the ID that needs to be tried again is given. +// Not all faults nessesarily need a retry, in case you just want to know if +// faults are happening you can use fault_detected_o for it. +// +// This needs_retry_o signal should be used to trigger some kind of mitigating action: +// For example, one could use the "retry" modules or "retry_inorder" modules +// to recalculate in hardware, or invoke some recalculation or error on the software side. +// +// In order to propperly function: +// - DTR_interface of DTR_start needs to be connected to DTR_interface of DTR_end. +// - id_o of DTR_start needs to be passed paralelly to the combinatorial logic, using the same handshake +// and arrive at id_i of DTR_end. +// - All operation pairs in contact with each other have a unique ID. +// - The module can only be enabled / disabled when the combinatorially process holds no valid data. +// +// This module can deal with out-of-order combinatorial processes under the conditions that +// the two operations belonging together are not separated. +// To facilitate this on the output side the module has the lock_o signal which is asserted if +// the module wants another element of the same kind in the next cycle. +// This can for example be used with the rr_arb_tree_lock module, but other implementations are +// permissible. + +`include "redundancy_cells/voters.svh" +`include "common_cells/registers.svh" + +module DTR_start # ( + // The data type you want to send through / replicate + parameter type DataType = logic, + // The size of the ID to use as an auxilliary signal + // For an in-order process, this can be set to 2. + // For an out of order process, it needs to be big enough so that the + // out-of-orderness can never rearange the elements with the same id + // next to each other and needs an extra bit for error detection. + // As an estimate you can use log2(longest_pipeline) + 2. + // Needs to match with DTR_end! + parameter int unsigned IDSize = 1, + // If you want Ready to be set as early as possible, and store elements + // internally in redundant mode. Increases area but potentially stops + // upstream stalls. + parameter bit EarlyReadyEnable = 1, + // Set to 1 if the id_i port should be used. Since the internal ID representation + // has a parity bit but id_i does not, the size will be IDSize-1!. + parameter bit UseExternalId = 0, + // Determines if the internal state machines should + // be parallely redundant, meaning errors inside this module + // can also not cause errors in the output + // The external output is never protected! + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1 +) ( + input logic clk_i, + input logic rst_ni, + input logic enable_i, + + // Direct connection + DTR_interface.sender dtr_interface, + + // Upstream connection + input DataType data_i, + input logic [IDSize-2:0] id_i, + input logic valid_i, + output logic ready_o, + + // Downstream connection + output DataType data_o, + output logic [IDSize-1:0] id_o, + output logic valid_o, + input logic ready_i +); + + // ID Generation Logic + logic [REP-1:0][IDSize-1:0] next_id_ov; + logic [REP-1:0][IDSize-1:0] id_b, id_v, id_d, id_q; + + for (genvar r = 0; r < REP; r++) begin: gen_id + if (UseExternalId == 1) begin + // Add a parity bit + assign next_id_ov[r][IDSize-2:0] = id_i; + assign next_id_ov[r][IDSize-1] = ^id_i; + end else begin + // Increment and add parity bit + assign next_id_ov[r][IDSize-2:0] = id_q[r][IDSize-2:0] + 1; + assign next_id_ov[r][IDSize-1] = ^next_id_ov[r][IDSize-2:0]; + end + + assign dtr_interface.id[r] = next_id_ov[r]; + end + + if (EarlyReadyEnable) begin : gen_store_fsm + // Redundant Output signals + logic [REP-1:0] ready_ov; + logic [REP-1:0] valid_ov; + + // State machine TLDR + // - counting the state from 0 to 2 if the handshake is good + // - counting the ID up whenever the state goes back to 0 + + // Next State Combinatorial Logic + typedef enum logic [1:0] {STORE_AND_SEND, SEND, REPLICATE} state_t; + state_t [REP-1:0] state_b, state_v, state_d, state_q; + DataType [REP-1:0] data_b, data_v, data_d, data_q; + + for (genvar r = 0; r < REP; r++) begin: gen_next_state + + always_comb begin: gen_next_state_comb + // Default to staying in same state + state_v[r] = state_q[r]; + data_v[r] = data_q[r]; + id_v[r] = id_q[r]; + + case (state_q[r]) + STORE_AND_SEND: + if (valid_i) begin + data_v[r] = data_i; + id_v[r] = next_id_ov[r]; + + if (ready_i) begin + if (enable_i) begin + state_v[r] = REPLICATE; + end else begin + state_v[r] = STORE_AND_SEND; + end + end else begin + state_v[r] = SEND; + end + end + SEND: + if (ready_i) begin + if (enable_i) begin + state_v[r] = REPLICATE; // Reset seqeuence + end else begin + state_v[r] = STORE_AND_SEND; + end + end + REPLICATE: + if (ready_i) begin + state_v[r] = STORE_AND_SEND; + end + endcase + end + end + + // State Voting Logic + `VOTEXX(REP, state_v, state_d); + `VOTEXX(REP, id_v, id_d); + `VOTEXX(REP, data_v, data_d); + + // Generate default cases + for (genvar r = 0; r < REP; r++) begin: gen_default_state + assign state_b[r] = STORE_AND_SEND; + assign data_b[r] = 0; + assign id_b[r] = 0; + end + + // State Storage + `FF(state_q, state_d, state_b); + `FF(data_q, data_d, data_b); + `FF(id_q, id_d, id_b); + + // Output Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_output + always_comb begin : gen_output_comb + case (state_q[r]) + STORE_AND_SEND: begin + valid_ov[r] = valid_i; + ready_ov[r] = 1; + dtr_interface.sent[r] = 1; + end + SEND: begin + valid_ov[r] = '1; + ready_ov[r] = '0; + dtr_interface.sent[r] = 0; + end + REPLICATE: begin + valid_ov[r] = '1; + ready_ov[r] = '0; + dtr_interface.sent[r] = 1; + end + endcase + end + end + + // Output Voting Logic + assign data_o = data_d[0]; + assign id_o = id_d[0]; + + `VOTEX1(REP, ready_ov, ready_o); + `VOTEX1(REP, valid_ov, valid_o); + + end else begin: gen_no_store_fsm + // Redundant Output signals + logic [REP-1:0] ready_ov; + + // State machine TLDR + // - Wait for valid and count number of ready cycles after valid is sent + + // Next State Combinatorial Logic + typedef enum logic [1:0] {SEND, SEND_AND_CONSUME, SEND_NO_INCREMENT} state_t; + state_t [REP-1:0] state_b, state_v, state_d, state_q; + + for (genvar r = 0; r < REP; r++) begin: gen_next_state + always_comb begin: gen_next_state_comb + // Default to staying in same state + state_v[r] = state_q[r]; + id_v[r] = id_q[r]; + + case (state_q[r]) + SEND: + if (valid_i) begin + id_v[r] = next_id_ov[r]; + + if (ready_i) begin + if (enable_i) begin + state_v[r] = SEND_AND_CONSUME; + end else begin + state_v[r] = SEND; + end + end else begin + state_v[r] = SEND_NO_INCREMENT; + end + end + SEND_NO_INCREMENT: + if (ready_i) begin + if (enable_i) begin + state_v[r] = SEND_AND_CONSUME; + end else begin + state_v[r] = SEND; + end + end + SEND_AND_CONSUME: + if (ready_i) begin + state_v[r] = SEND; + end + endcase + end + end + + // State Voting Logic + `VOTEXX(REP, state_v, state_d); + `VOTEXX(REP, id_v, id_d); + + // Generate default cases + for (genvar r = 0; r < REP; r++) begin: gen_default_state + assign state_b[r] = SEND; + assign id_b[r] = 0; + end + + // State Storage + `FF(state_q, state_d, state_b); + `FF(id_q, id_d, id_b); + + // Output Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_output + always_comb begin : gen_output_comb + case (state_q[r]) + SEND: begin + ready_ov[r] = ~enable_i & ready_i; + dtr_interface.sent[r] = valid_i & enable_i; + end + SEND_NO_INCREMENT: begin + ready_ov[r] = ~enable_i & ready_i; + dtr_interface.sent[r] = 0; + end + SEND_AND_CONSUME: begin + ready_ov[r] = enable_i & ready_i; + dtr_interface.sent[r] = 0; + end + endcase + end + end + + // Output Voting Logic + assign data_o = data_i; + assign valid_o = valid_i; + assign id_o = id_d[0]; + + `VOTEX1(REP, ready_ov, ready_o); + + end +endmodule diff --git a/rtl/time_redundancy/TTR_end.sv b/rtl/time_redundancy/TTR_end.sv new file mode 100644 index 00000000..6b90e4f3 --- /dev/null +++ b/rtl/time_redundancy/TTR_end.sv @@ -0,0 +1,507 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: TTR is a pair of modules that can be used to error correct +// any transient fault in a (pipelined) combinatorial process. This is done by +// repeating the element three times and comparing the results +// +// Faults that can be handled: +// - Any number of fault in the datapath during one cycle e.g. wrong result. +// - A single fault in the handshake (valid or ready) e.g. dropped or injected results. +// - A single fault in the accompanying ID. +// +// In order to propperly function: +// - id_o of TTR_start needs to be passed paralelly to the combinatorial logic, +// using the same handshake and arrive at id_i of TTR_end. +// - All operation tripplets in contact which each other have a unique ID. +// - The module can only be enabled / disabled when the combinatorially process holds no valid data. +// +// This module can deal with out-of-order combinatorial processes under the conditions that +// the three operations belonging together are not separated. +// To facilitate that the tripples stay together, the module has the lock_o signal +// Which defines if the same pipeline should output again in the next cycle. +// This can for example be used with the rr_arb_tree_lock module, but other implementations are +// permissible. + +`include "redundancy_cells/voters.svh" +`include "common_cells/registers.svh" + +module TTR_end # ( + // The data type you want to send through / replicate + parameter type DataType = logic, + // How long until the TTR module should abort listening for further copies + // of some data when they don't show up e.g. handshake failed + // For an in-order process you should set this to 4 + // For an out-of-order process (with RR Arbitrator) you should set it to 5 + // In case your combinatorial logic takes more than once cycle to compute an element + // multiply by the respective number of cycles + parameter int unsigned LockTimeout = 4, + // The size of the ID to use as an auxilliary signal + // For an in-order process, this can be set to 1 + // For an out of order process, it needs to be big enough so that the out-of-orderness can never + // rearange the elements with the same id next to each other + // As an estimate you can use log2(longest_pipeline) + 1 + // Needs to match with TTR_start! + parameter int unsigned IDSize = 1, + // This parameter chooses the implementation of the internal state machine. + // EarlyValidEnable = 1: + // The state machine will return a valid output after it recieved two out of three + // data elements belonging together if possible e.g. no faults occur (otherwise valid in third cycle) + // This option slightly increases area and critical path. + // EarlyValidEnable = 0: + // The sate machine will always return a valid data element after collecting all 3 parts. + // Internal area and critical path are reduced. + parameter bit EarlyValidEnable = 0, + // Determines if the internal state machines should + // be parallely redundant, meaning errors inside this module + // can also not cause errors in the output + // The external output is never protected! + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1 +) ( + input logic clk_i, + input logic rst_ni, + input logic enable_i, + + // Upstream connection + input DataType data_i, + input logic [IDSize-1:0] id_i, + input logic valid_i, + output logic ready_o, + + // Downstream connection + output DataType data_o, + output logic valid_o, + input logic ready_i, + + // Signal for working with Lockable RR Arbiter + output logic [REP-1:0] lock_o, + + // Flag for External Error Counter + output logic fault_detected_o +); + + // Redundant Output signals + logic [REP-1:0] ready_ov; + logic [REP-1:0] valid_ov; + logic [REP-1:0] fault_detected_ov; + + ///////////////////////////////////////////////////////////////////////////////// + // Storage of incomming results and generating good output data + + DataType data_q1, data_q2; + logic [IDSize-1:0] id_q1, id_q2; + logic load_enable; + assign load_enable = valid_i & ready_o & enable_i; + + // Storage Element + `FFL(data_q1, data_i, load_enable, 'h1); + `FFL(data_q2, data_q1, load_enable, 'h2); + `FFL(id_q1, id_i, load_enable, 'h1); + `FFL(id_q2, id_q1, load_enable, 'h2); + + ///////////////////////////////////////////////////////////////////////////////// + // Comparisons genereration for Handshake / State Machine + + logic [4:0] data_same, id_same, full_same, partial_same; + logic [2:0] data_same_in, id_same_in; + logic [1:0] data_same_d, data_same_q, id_same_d, id_same_q; + + always_comb begin: gen_data_same + data_same_in = '0; + id_same_in = '0; + data_o = '0; + + // Slot 0 and 1 are filled with the same data (slot 2 might be different) + if (data_i == data_q1) begin + data_o = data_i; + data_same_in[0] = '1; + end + + // Slot 0 and 2 are filled with the same data (slot 1 might be different) + // This deals with cases where the second id got corrupted + if (data_i == data_q2) begin + data_o = data_i; + data_same_in[1] = '1; + end + + // Slot 1 and 2 are filled with the same data (slot 0 might be different) + if (data_q1 == data_q2) begin + data_o = data_q1; + data_same_in[2] = '1; + end + + // If disabled just send out input + if (!enable_i) begin + data_o = data_i; + end + + // Same kind of logic for id signal + if (id_i == id_q1) id_same_in[0] = '1; + if (id_i == id_q2) id_same_in[1] = '1; + if (id_q1 == id_q2) id_same_in[2] = '1; + end + + ///////////////////////////////////////////////////////////////////////////////// + // Storage of same / not same for one extra cycle + + // Next State Combinatorial Logic + always_comb begin : data_same_storage_comb + data_same_d = data_same_in[2:1]; + id_same_d = id_same_in[2:1]; + end + + // Storage Element + `FFL(data_same_q, data_same_d, load_enable, 2'b11); + `FFL(id_same_q, id_same_d, load_enable, 2'b11); + + // Output (merged) signal assigment + assign data_same = {data_same_q, data_same_in}; + assign id_same = {id_same_q, id_same_in}; + + assign full_same = data_same & id_same; + assign partial_same = data_same | id_same; + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // State machine to figure out handshake + logic [REP-1:0] lock_internal_v; + + if (EarlyValidEnable) begin: gen_early_valid_statemachine + + // Input signal reassignment to make state machine more readable + logic [REP-1:0] element_needs_shift_v; + logic [REP-1:0] new_element_arrived_v; + logic [REP-1:0] element_in_input_v; + logic [REP-1:0] element_relies_on_input_v; + logic [REP-1:0] data_usable_v; + + for (genvar r = 0; r < REP; r++) begin: gen_data_flags + always_comb begin: gen_data_flags_comb + // Some new element just showed up and we need to send data outwards again. + new_element_arrived_v[r] = (id_same != 5'b11111) && ( // ID All same -> No element change counts. ID always needs to change! + (full_same & 5'b01111) == 5'b00001 || // 1st and 2nd element the same, other two each different from pair + (full_same & 5'b10111) == 5'b00010 // 1st and 3rd element the same, other two each different from pair + ); + + // Data or id is in the input -> We should consume the input for this element + // Same data or id count as matches since we can remove an inexact pair on error and be fine + // (Pairs where one thing matches and the other doesn't which are from a different elements can only happen with two errors) + element_in_input_v[r] = |partial_same[1:0]; + + // Second register does not contain something that is completely the same elsewhere -> We should keep shifting until it is + element_needs_shift_v[r] = ~|full_same[2:1]; + + // Data is in input and only one of the registers -> We need to take valid_i into account for valid_o + element_relies_on_input_v[r] = |full_same[1:0] & ~full_same[2]; + + // Data has at least two new things that are the same + data_usable_v[r] = |data_same[2:0]; + end + end + + // State Definition + // Special State Description: + // WAIT_FOR_READY: We got some data that is usable, but downstream can't use it yet + // -> We keep shifting as far as our pipeline goes to collect all data samples if we haven't yet and then stop + // WAIT_FOR_VALID: We got some data that is usable, and sent it downstream right away + // -> We try to collect one more piece of our data and then move on + // WAIT_FOR_DATA: We got some pieces of data that should belong together but they are not the same + // -> We try to collect one more piece of the same data and then send it downstream + typedef enum logic [1:0] {BASE, WAIT_FOR_READY, WAIT_FOR_VALID, WAIT_FOR_DATA} state_t; + state_t [REP-1:0] state_b, state_v, state_d, state_q; + + + // Next State Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_next_state + always_comb begin: gen_next_state_comb + // Default to staying in the same state + state_v[r] = state_q[r]; + + case (state_q[r]) + BASE: + if (valid_i) begin + if (new_element_arrived_v[r]) begin + if (!data_usable_v[r]) begin + state_v[r] = WAIT_FOR_DATA; + end else begin + if (ready_i) begin + if (element_needs_shift_v[r]) begin + // We can already send our data element, but it needs another shift to collect -> Go into special stat for this + state_v[r] = WAIT_FOR_VALID; + end + end else begin + state_v[r] = WAIT_FOR_READY; // We keep the data until downstream is ready, only shifting as far as our registers go + end + end + end + end + WAIT_FOR_READY: + if (ready_i) begin + state_v[r] = BASE; // Downstream takes the data that we are holding and we can go back to the base state + end + WAIT_FOR_VALID: begin + if (valid_i) begin + state_v[r] = BASE; // We needed another shift to get back into base state + end + end + WAIT_FOR_DATA: begin + if (valid_i) begin + // We got another shift to get our redundant data completed + if (ready_i) begin + state_v[r] = BASE; // And we send it on to the next stage immediately + end else begin + state_v[r] = WAIT_FOR_READY; // We keep the data until downstream is ready, only shifting as far as our registers go + end + end + end + endcase + end + end + + // State Voting Logic + `VOTEXX(REP, state_v, state_d); + + // Generate default cases + for (genvar r = 0; r < REP; r++) begin: gen_default_state + assign state_b[r] = WAIT_FOR_VALID; + end + + // State Storage + `FF(state_q, state_d, state_b); + + // Output Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_output + always_comb begin: gen_output_comb + if (enable_i) begin + case (state_q[r]) + BASE: valid_ov[r] = (!element_relies_on_input_v[r] | valid_i) & data_usable_v[r] & new_element_arrived_v[r]; + WAIT_FOR_DATA: valid_ov[r] = (!element_relies_on_input_v[r] | valid_i) & data_usable_v[r]; + WAIT_FOR_READY: valid_ov[r] = (!element_relies_on_input_v[r] | valid_i); + WAIT_FOR_VALID: valid_ov[r] = 0; + endcase + + case (state_q[r]) + BASE: lock_internal_v[r] = !ready_i | element_needs_shift_v[r] | !new_element_arrived_v[r]; + WAIT_FOR_DATA: lock_internal_v[r] = !ready_i | element_needs_shift_v[r]; + WAIT_FOR_READY: lock_internal_v[r] = !ready_i; + WAIT_FOR_VALID: lock_internal_v[r] = !valid_i; + endcase + + case (state_q[r]) + BASE: ready_ov[r] = ready_i & element_in_input_v[r] | element_needs_shift_v[r] | !new_element_arrived_v[r]; + WAIT_FOR_DATA: ready_ov[r] = ready_i & element_in_input_v[r] | element_needs_shift_v[r]; + WAIT_FOR_READY: ready_ov[r] = ready_i & element_in_input_v[r] | element_needs_shift_v[r]; + WAIT_FOR_VALID: ready_ov[r] = element_in_input_v[r]; + endcase + end else begin + valid_ov[r] = valid_i; + lock_internal_v[r] = 0; + ready_ov[r] = ready_i; + end + end + end + end else begin : gen_late_valid_statemachine + + // Input signal reassignment to make state machine more readable + logic [REP-1:0] new_id_arrived_v; + logic [REP-1:0] id_in_input_v; + logic [REP-1:0] id_all_same_v; + logic [REP-1:0] id_all_cover_v; + logic [REP-1:0] data_usable_v; + + for (genvar r = 0; r < REP; r++) begin: gen_data_flags + always_comb begin: gen_data_flags_comb + new_id_arrived_v[r] = ( + (id_same == 5'b00111) || + (id_same == 5'b00100) || + (id_same == 5'b00010) || + (id_same == 5'b01010) + ); + + id_in_input_v[r] = |id_same[1:0]; + + id_all_same_v[r] = &id_same[2:0]; + id_all_cover_v[r] = id_same[1]; + + data_usable_v[r] = |data_same[2:0]; + end + end + + // State Definition + // Special State Description: + // WAIT_FOR_READY: We got some data that is usable, but downstream can't use it yet + // -> We keep shifting as far as our pipeline goes to collect all data samples if we haven't yet and then stop + // WAIT_FOR_VALID: We got two usable pieces of data in the last and another register + // -> We need to wait for at least one new data element before data can be valid again + // WAIT_FOR_VALID_X2: We have recieved three fully usable pieces of data + // -> We need to wait for at least two new data elements before data can be valid again + typedef enum logic [1:0] {BASE, WAIT_FOR_READY, WAIT_FOR_VALID, WAIT_FOR_VALID_X2} state_t; + state_t [REP-1:0] state_b, state_v, state_d, state_q; + + // Next State Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_next_state + always_comb begin: gen_next_state_comb + // Default to staying in the same state + state_v[r] = state_q[r]; + + case (state_q[r]) + BASE: + if (valid_i) begin + if (new_id_arrived_v[r]) begin + if (ready_i) begin + if (id_all_cover_v[r]) begin + state_v[r] = WAIT_FOR_VALID_X2; + end else begin + state_v[r] = WAIT_FOR_VALID; + end + end else begin + state_v[r] = WAIT_FOR_READY; // We keep the data until downstream is ready, only shifting as far as our registers go + end + end + end + WAIT_FOR_READY: + if (ready_i) begin + if (id_all_cover_v[r]) begin + state_v[r] = WAIT_FOR_VALID_X2; + end else begin + state_v[r] = WAIT_FOR_VALID; + end + end + WAIT_FOR_VALID: begin + if (valid_i) begin + state_v[r] = BASE; + end + end + WAIT_FOR_VALID_X2: begin + if (valid_i) begin + state_v[r] = WAIT_FOR_VALID; + end + end + endcase + end + end + + // State Voting Logic + `VOTEXX(REP, state_v, state_d); + + // Generate default cases + for (genvar r = 0; r < REP; r++) begin: gen_default_state + assign state_b[r] = WAIT_FOR_VALID; + end + + // State Storage + `FF(state_q, state_d, state_b); + + // Output Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_output + always_comb begin: gen_output_comb + if (enable_i) begin + case (state_q[r]) + BASE: valid_ov[r] = valid_i & new_id_arrived_v[r] & data_usable_v[r]; + WAIT_FOR_READY: valid_ov[r] = new_id_arrived_v[r] & data_usable_v[r]; + WAIT_FOR_VALID: valid_ov[r] = 0; + WAIT_FOR_VALID_X2: valid_ov[r] = 0; + endcase + + case (state_q[r]) + BASE: lock_internal_v[r] = !(ready_i & valid_i) | !new_id_arrived_v[r] ; + WAIT_FOR_READY: lock_internal_v[r] = !ready_i; + WAIT_FOR_VALID: lock_internal_v[r] = 1; + WAIT_FOR_VALID_X2: lock_internal_v[r] = 1; + endcase + + case (state_q[r]) + BASE: ready_ov[r] = ready_i & id_in_input_v[r] | !new_id_arrived_v[r]; + WAIT_FOR_READY: ready_ov[r] = ready_i & id_in_input_v[r]; + WAIT_FOR_VALID: ready_ov[r] = 1; + WAIT_FOR_VALID_X2: ready_ov[r] = 1; + endcase + end else begin + valid_ov[r] = valid_i; + lock_internal_v[r] = 0; + ready_ov[r] = ready_i; + end + end + end + end + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // State machine to lock / unlock Arbitrator with Watchdog timer + + logic [REP-1:0] lock_b, lock_v, lock_d, lock_q; + logic [REP-1:0][$clog2(LockTimeout)-1:0] counter_b, counter_v, counter_d, counter_q; + + // Next State Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_lock_next_state + always_comb begin : gen_lock_next_state_comb + if (counter_q[r] > LockTimeout) begin + lock_v[r] = 0; + counter_v[r] = 0; + + end else begin + if (lock_q[r] & !valid_i) begin + counter_v[r] = counter_q[r] + 1; + end else begin + counter_v[r] = 0; + end + + // To set Lock -> 1 require previous imput to be valid, to set Lock -> 0 lock don't require anything + if (valid_i | lock_q[r]) begin + lock_v[r] = lock_internal_v[r]; + end else begin + lock_v[r] = lock_q[r]; + end + end + end + end + + // State Voting Logic + `VOTEXX(REP, lock_v, lock_d); + `VOTEXX(REP, counter_v, counter_d); + + assign lock_o = lock_d; + + // Default state + for (genvar r = 0; r < REP; r++) begin: gen_lock_default_state + assign lock_b[r] = '0; + assign counter_b[r] = '0; + end + + // State Storage + `FF(lock_q, lock_d, lock_b); + `FF(counter_q, counter_d, counter_b); + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // Build error flag + + // Since we can already output at two same elements we could have not seen an error yet, + // so we can't rely on valid / ready to be in sync with 3 same elements! + // Instead we use the following observation: If the data comes nicely in pairs of 3 we + // always have at least two data elements that are the same in the first 3 elements. + // Make output only 1 for a signly cycle even if internal pipeline is stopped + + logic [REP-1:0] fault_detected_b, fault_detected_d, fault_detected_q; + + for (genvar r = 0; r < REP; r++) begin: gen_flag_next_state + assign fault_detected_d[r] = ~|full_same[2:0] & valid_i; + end + + // Default state + for (genvar r = 0; r < REP; r++) begin: gen_flag_default_state + assign fault_detected_b[r] = '0; + end + + `FF(fault_detected_q, fault_detected_d, fault_detected_b); + + for (genvar r = 0; r < REP; r++) begin: gen_flag_output + assign fault_detected_ov[r] = fault_detected_d[r] & !fault_detected_q[r]; + end + + /////////////////////////////////////////////////////////////////////////////////////////////////// + // Output Voting + + `VOTEX1(REP, ready_ov, ready_o); + `VOTEX1(REP, valid_ov, valid_o); + `VOTEX1(REP, fault_detected_ov, fault_detected_o); + +endmodule diff --git a/rtl/time_redundancy/TTR_start.sv b/rtl/time_redundancy/TTR_start.sv new file mode 100644 index 00000000..3da1adc5 --- /dev/null +++ b/rtl/time_redundancy/TTR_start.sv @@ -0,0 +1,257 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: TTR is a pair of modules that can be used to error correct +// any transient fault in a (pipelined) combinatorial process. This is done by +// repeating the element three times and comparing the results +// +// Faults that can be handled: +// - Any number of fault in the datapath during one cycle e.g. wrong result. +// - A single fault in the handshake (valid or ready) e.g. dropped or injected results. +// - A single fault in the accompanying ID. +// +// In order to propperly function: +// - id_o of TTR_start needs to be passed paralelly to the combinatorial logic, +// using the same handshake and arrive at id_i of TTR_end. +// - All operation tripplets in contact which each other have a unique ID. +// - The module can only be enabled / disabled when the combinatorially process holds no valid data. +// +// This module can deal with out-of-order combinatorial processes under the conditions that +// the three operations belonging together are not separated. +// To facilitate that the tripples stay together, the module has the lock_o signal +// Which defines if the same pipeline should output again in the next cycle. +// This can for example be used with the rr_arb_tree_lock module, but other implementations are +// permissible. + +`include "redundancy_cells/voters.svh" +`include "common_cells/registers.svh" + +module TTR_start # ( + // The data type you want to send through / replicate + parameter type DataType = logic, + // The size of the ID to use as an auxilliary signal + // For an in-order process, this can be set to 1 + // For an out of order process, it needs to be big enough so that the + // out-of-orderness can never rearange the elements with the same id + // next to each other. + // As an estimate you can use log2(longest_pipeline) + 1. + // Needs to match with TTR_end! + parameter int unsigned IDSize = 1, + // If you want Ready to be set as early as possible, and store elements + // internally in redundant mode. Increases area but potentially stops + // upstream stalls. + parameter bit EarlyReadyEnable = 1, + // Determines if the internal state machines should + // be parallely redundant, meaning errors inside this module + // can also not cause errors in the output + // The external output is never protected! + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1 +) ( + input logic clk_i, + input logic rst_ni, + input logic enable_i, + + // Upstream connection + input DataType data_i, + input logic valid_i, + output logic ready_o, + + // Downstream connection + output DataType data_o, + output logic [IDSize-1:0] id_o, + output logic valid_o, + input logic ready_i +); + + if (EarlyReadyEnable) begin: gen_store_fsm + + // Redundant Output signals + logic [REP-1:0] ready_ov; + logic [REP-1:0] valid_ov; + + // State machine TLDR + // - counting the state from 0 to 2 if the handshake is good + // - counting the ID up whenever the state goes back to 0 + // - stay in state 0 whenever there is no redundancy + + typedef enum logic [1:0] {STORE_AND_SEND, SEND, REPLICATE_ONE, REPLICATE_TWO} state_t; + state_t [REP-1:0] state_b, state_v, state_d, state_q; + DataType [REP-1:0] data_b, data_v, data_d, data_q; + logic [REP-1:0][IDSize-1:0] id_b, id_v, id_d, id_q; + + // Next State Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_next_state + always_comb begin : gen_next_state_comb + // Default to staying in same state + state_v[r] = state_q[r]; + data_v[r] = data_q[r]; + id_v[r] = id_q[r]; + + case (state_q[r]) + STORE_AND_SEND: + if (valid_i) begin + data_v[r] = data_i; + + if (ready_i) begin + if (enable_i) begin + state_v[r] = REPLICATE_ONE; + end else begin + state_v[r] = STORE_AND_SEND; + end + end else begin + state_v[r] = SEND; + end + end + SEND: + if (ready_i) begin + if (enable_i) begin + state_v[r] = REPLICATE_ONE; // Reset seqeuence + end else begin + state_v[r] = STORE_AND_SEND; + end + end + REPLICATE_ONE: + if (ready_i) begin + state_v[r] = REPLICATE_TWO; // Reset seqeuence + end + REPLICATE_TWO: begin + if (ready_i) begin + state_v[r] = STORE_AND_SEND; + id_v[r] = id_q[r] + 1; + end + end + endcase + end + end + + // State Voting Logic + `VOTEXX(REP, state_v, state_d); + `VOTEXX(REP, id_v, id_d); + `VOTEXX(REP, data_v, data_d); + + // Generate default cases + for (genvar r = 0; r < REP; r++) begin: gen_default_state + assign state_b[r] = STORE_AND_SEND; + assign data_b[r] = 0; + assign id_b[r] = 0; + end + + // State Storage + `FF(state_q, state_d, state_b); + `FF(data_q, data_d, data_b); + `FF(id_q, id_d, id_b); + + // Output Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_output + always_comb begin: gen_output_comb + case (state_q[r]) + STORE_AND_SEND: begin + valid_ov[r] = valid_i; + ready_ov[r] = 1; + end + SEND: begin + valid_ov[r] = '1; + ready_ov[r] = '0; + end + REPLICATE_ONE: begin + valid_ov[r] = '1; + ready_ov[r] = '0; + end + REPLICATE_TWO: begin + valid_ov[r] = '1; + ready_ov[r] = '0; + end + endcase + end + end + + // Output Voting Logic + assign data_o = data_d[0]; + assign id_o = id_q[0]; + + `VOTEX1(REP, ready_ov, ready_o); + `VOTEX1(REP, valid_ov, valid_o); + + end else begin: gen_no_store_fsm + + // Redundant Output signals + logic [REP-1:0] ready_ov; + + // State machine TLDR + // - counting the state from 0 to 2 if the handshake is good + // - counting the ID up whenever the state goes back to 0 + // - stay in state 2 whenever there is no redundancy + + typedef enum logic [1:0] {SEND_ONE, SEND_TWO, SEND_AND_CONSUME} state_t; + state_t [REP-1:0] state_b, state_v, state_d, state_q; + logic [REP-1:0][IDSize-1:0] id_b, id_v, id_d, id_q; + + // Next State Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_next_state + always_comb begin : gen_next_state_comb + // Default to staying in same state + state_v[r] = state_q[r]; + id_v[r] = id_q[r]; + + case (state_q[r]) + SEND_ONE: + if (valid_i & ready_i) begin + if (enable_i) begin + state_v[r] = SEND_TWO; + end else begin + state_v[r] = SEND_ONE; + end + end + SEND_TWO: + if (ready_i) begin + state_v[r] = SEND_AND_CONSUME; + end + SEND_AND_CONSUME: + if (ready_i) begin + id_v[r] = id_q[r] + 1; + state_v[r] = SEND_ONE; + end + endcase + end + end + + // State Voting Logic + `VOTEXX(REP, state_v, state_d); + `VOTEXX(REP, id_v, id_d); + + // Generate default cases + for (genvar r = 0; r < REP; r++) begin: gen_default_state + assign state_b[r] = SEND_ONE; + assign id_b[r] = 0; + end + + // State Storage + `FF(state_q, state_d, state_b); + `FF(id_q, id_d, id_b); + + // Output Combinatorial Logic + for (genvar r = 0; r < REP; r++) begin: gen_output + always_comb begin: gen_output_comb + case (state_q[r]) + SEND_ONE: begin + ready_ov[r] = ~enable_i & ready_i; + end + SEND_TWO: begin + ready_ov[r] = '0; + end + SEND_AND_CONSUME: begin + ready_ov[r] = ready_i & enable_i; + end + endcase + end + end + + // Output Voting Logic + assign data_o = data_i; + assign valid_o = valid_i; + assign id_o = id_q[0]; + + `VOTEX1(REP, ready_ov, ready_o); + end +endmodule diff --git a/rtl/time_redundancy/redundancy_controller.sv b/rtl/time_redundancy/redundancy_controller.sv new file mode 100644 index 00000000..b85ec792 --- /dev/null +++ b/rtl/time_redundancy/redundancy_controller.sv @@ -0,0 +1,118 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Module that can correctly enable / disable time_TMR or time_DMR modules +// when they do not hold valid data. It does so by stalling the handshake +// upstream of the redundancy modules until they are empty, +// then enables / disables them. +// +// Caveat: In case a fault occurs while this module is trying to switch to a non-redundant mode +// the module might currently stall since not all data in the combinatorial process can propperly +// be consumed and the condition for switching can not be reached. +// Switching back to the redundant mode will unstall the setup. + +`include "redundancy_cells/voters.svh" +`include "common_cells/registers.svh" + +module redundancy_controller # ( + // How long the redundancy controller should keep trying to + // Resolve unfinished transactions before switching modes + // Needs to be equal or bigger than timeout in underlying TMR / DMR modules + parameter int unsigned LockTimeout = 4, + // Determines if the internal state machines should + // be parallely redundant, meaning errors inside this module + // can also not cause errors in the output + // The external output is never protected! + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1 +) ( + input logic clk_i, + input logic rst_ni, + + // Connection to outside + input logic enable_i, + output logic busy_o, + + // Connection to redundancy modules + input logic busy_i, + output logic enable_o, + + // Upstream Handshake + input logic valid_i, + output logic ready_o, + + // Downstream Handshake + output logic valid_o, + input logic ready_i +); + + // Redundant versions of output signals + logic [REP-1:0] valid_ov; + logic [REP-1:0] ready_ov; + logic [REP-1:0] busy_ov; + logic [REP-1:0] enable_ov; + logic [REP-1:0] flush_ov; + logic [REP-1:0] timeout_v; + + logic [REP-1:0] enable_b, enable_v, enable_d, enable_q; + logic [REP-1:0][$clog2(LockTimeout)-1:0] counter_b, counter_v, counter_d, counter_q; + + for (genvar r = 0; r < REP; r++) begin: gen_next_state + always_comb begin + // As long as the unit is busy, do not change the enable state + if (busy_i) begin + enable_v[r] = enable_q[r]; + end else begin + enable_v[r] = enable_i; + end + + // If the unit is stalled e.g. nothing gets out during for the timeout, then trickle new operations in to unstall it + if (counter_q[r] > LockTimeout) begin + counter_v[r] = 0; + timeout_v[r] = 1; + end else if (valid_i && enable_q[r] && !enable_i) begin + counter_v[r] = counter_q[r] + 1; + timeout_v[r] = 0; + end else begin + counter_v[r] = 0; + timeout_v[r] = 0; + end + end + end + + `VOTEXX(REP, enable_v, enable_d); + `VOTEXX(REP, counter_v, counter_d); + + // Generate default case + for (genvar r = 0; r < REP; r++) begin: gen_default_state + assign enable_b[r] = '0; + assign counter_b[r] = '0; + end + + `FFARN(enable_q, enable_d, enable_b, clk_i, rst_ni); + `FFARN(counter_q, counter_d, counter_b, clk_i, rst_ni); + + // Output combinatorial logic + for (genvar r = 0; r < REP; r++) begin: gen_output + always_comb begin + enable_ov[r] = enable_q[r]; + + if ((enable_q[r] == enable_i) || timeout_v[r]) begin + valid_ov[r] = valid_i; + ready_ov[r] = ready_i; + busy_ov[r] = busy_i; + end else begin + valid_ov[r] = 1'b0; + ready_ov[r] = 1'b0; + busy_ov[r] = 1'b1; + end + end + end + + // Output voting logic + `VOTEX1(REP, enable_ov, enable_o); + `VOTEX1(REP, valid_ov, valid_o); + `VOTEX1(REP, ready_ov, ready_o); + `VOTEX1(REP, busy_ov, busy_o); + +endmodule diff --git a/rtl/time_redundancy/retry_end.sv b/rtl/time_redundancy/retry_end.sv new file mode 100644 index 00000000..67a76cc7 --- /dev/null +++ b/rtl/time_redundancy/retry_end.sv @@ -0,0 +1,67 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: retry is a pair of modules that can be used to run an operation +// passing through a (pipelined) combinatorial process. +// +// In order to propperly function: +// - id_o of retry_start needs to be passed paralelly along the combinatorial logic, +// using the same handshake and arrive at id_i of retry_end +// - interface retry of retry_start needs to be directly connected to retry of retry_end + +// - All elements in processing have a unique ID +// +// This modules might cause operations to reach the output of retry_end in a different +// order than they have entered retry_start e.g. can be out-of-order in case of a retry +// so make sure you can deal with out-of-order results. +// For the special case that the process is purely combinatorial, the same operaton is tried again +// in the very next cycle and thus the order is preserved. +// If you need in-order for pipelined processes have a look at retry_inorder instead. + +module retry_end # ( + parameter type DataType = logic, + // The size of the ID to use as an auxilliary signal + // For an in-order process, this can be set to 1. + // For an out of order process, it needs to be big enough so that the out-of-orderness can never + // rearange the elements with the same id next to each other + // As an estimate you can use log2(longest_pipeline) + 1 + // Needs to match with retry_start! + parameter IDSize = 1 +) ( + input logic clk_i, + input logic rst_ni, + + // Upstream connection + input DataType data_i, + input logic [IDSize-1:0] id_i, + input logic needs_retry_i, + input logic valid_i, + output logic ready_o, + + // Downstream connection + output DataType data_o, + output logic valid_o, + input logic ready_i, + + // Retry Connection + retry_interface.ende retry +); + + // Assign signals + assign retry.id = id_i; + assign data_o = data_i; + + always_comb begin: gen_output + if (needs_retry_i) begin + retry.valid = valid_i; + ready_o = retry.ready; + valid_o = 0; + end else begin + valid_o = valid_i; + ready_o = ready_i; + retry.valid = 0; + end + end + +endmodule + + diff --git a/rtl/time_redundancy/retry_inorder_end.sv b/rtl/time_redundancy/retry_inorder_end.sv new file mode 100644 index 00000000..ccc4d8c4 --- /dev/null +++ b/rtl/time_redundancy/retry_inorder_end.sv @@ -0,0 +1,85 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: retry is a pair of modules that can be used to run an operation +// passing through a (pipelined) combinatorial process. +// +// In order to propperly function: +// - id_o of retry_start needs to be passed paralelly along the combinatorial logic, +// using the same handshake and arrive at id_i of retry_end +// - interface retry of retry_start needs to be directly connected to retry of retry_end +// - All elements in processing have a unique ID +// +// This module always keeps results in order by also retrying results that have been correct +// but at the wronge place or time. + +`include "common_cells/registers.svh" + +module retry_inorder_end # ( + parameter type DataType = logic, + // The size of the ID to use as an auxilliary signal + // For an in-order process, this can be set to 1. + // For an out of order process, it needs to be big enough so that the out-of-orderness can never + // rearange the elements with the same id next to each other + // As an estimate you can use log2(longest_pipeline) + 1 + // Needs to match with retry_inorder_start! + parameter int IDSize = 1 +) ( + input logic clk_i, + input logic rst_ni, + + // Upstream connection + input DataType data_i, + input logic [IDSize-1:0] id_i, + input logic needs_retry_i, + input logic valid_i, + output logic ready_o, + + // Downstream connection + output DataType data_o, + output logic valid_o, + input logic ready_i, + + // Retry Connection + retry_interface.ende retry +); + + // Signals do not change, only validity changes + assign retry.id = id_i; + assign data_o = data_i; + + logic [IDSize-1:0] failed_id_d, failed_id_q; + logic retry_d, retry_q; + + always_comb begin: gen_next_state + if (valid_i & retry_q) begin + failed_id_d = failed_id_q; + retry_d = ~(failed_id_q == id_i); + end else if (valid_i & needs_retry_i) begin + failed_id_d = retry.id_feedback; + retry_d = 1; + end else begin + failed_id_d = failed_id_q; + retry_d = retry_q; + end + end + + assign retry.lock = retry_d; + + `FF(retry_q, retry_d, '0); + `FF(failed_id_q, failed_id_d, '0); + + always_comb begin: gen_output + if (retry_d) begin + retry.valid = valid_i; + ready_o = retry.ready; + valid_o = 0; + end else begin + valid_o = valid_i; + ready_o = ready_i; + retry.valid = 0; + end + end + +endmodule + + diff --git a/rtl/time_redundancy/retry_inorder_start.sv b/rtl/time_redundancy/retry_inorder_start.sv new file mode 100644 index 00000000..fee88669 --- /dev/null +++ b/rtl/time_redundancy/retry_inorder_start.sv @@ -0,0 +1,152 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: retry is a pair of modules that can be used to run an operation +// passing through a (pipelined) combinatorial process. +// +// In order to propperly function: +// - id_o of retry_start needs to be passed paralelly along the combinatorial logic, +// using the same handshake and arrive at id_i of retry_end +// - interface retry of retry_start needs to be directly connected to retry of retry_end +// - All elements in processing have a unique ID +// +// This module always keeps results in order by also retrying results that have been correct +// but at the wronge place or time. + +`include "common_cells/registers.svh" + + +module retry_inorder_start # ( + parameter type DataType = logic, + // The size of the ID to use as an auxilliary signal + // For an in-order process, this can be set to 1. + // For an out of order process, it needs to be big enough so that the out-of-orderness can never + // rearange the elements with the same id next to each other + // As an estimate you can use log2(longest_pipeline) + 1 + // Needs to match with retry_inorder_end! + parameter int IDSize = 1, + // Amount of bits from the ID which are defined externally and should not be incremented. + // This allows for seperating the ID spaces into multiple sections, which will behave and overwrite + // each other indefinitely. For example, if you set IDSize=3 and ExternalIDBits=1, then you will get + // two sets of IDs which each cycle through the ID. + // Set 1: 000, 001, 010, 011 + // Set 2: 100, 101, 110, 111 + // You can use this to reduce storage space required if some operations take significantly longer in + // the pipelines than others. E.g. use Set 1 with operations done in 1 cycle, and Set 2 with operations + // that take 10 cycles. Each subset must satisfy the required ID Size of log2(longest_pipeline) + 1, + // excluding the bits used to distinguish the sets. + parameter ExternalIDBits = 0, + // Physical width of the ID bits input so that the case of 0 is well defined + // Must be equal or greater than the ExternalIDBits that are actually used + parameter ExternalIDWidth = (ExternalIDBits == 0) ? 1 : ExternalIDBits, + localparam NormalIDSize = IDSize - ExternalIDBits +) ( + input logic clk_i, + input logic rst_ni, + + // Upstream connection + input DataType data_i, + input logic valid_i, + output logic ready_o, + input logic [ExternalIDWidth-1:0] ext_id_bits_i, + + // Downstream connection + output DataType data_o, + output logic [IDSize-1:0] id_o, + output logic valid_o, + input logic ready_i, + + // Retry Connection + retry_interface.start retry +); + + initial begin + assert (ExternalIDWidth >= ExternalIDBits) + else $fatal("ExternalIDWidth must be at least as large as ExternalIDBits."); + end + + ////////////////////////////////////////////////////////////////////// + // Register to store failed id for one cycle + logic [IDSize-1:0] failed_id_d, failed_id_q; + logic failed_valid_d, failed_valid_q; + logic retry_lock_d, retry_lock_q; + + always_comb begin: gen_next_cycle_decision + if (ready_i | retry.valid) begin + failed_valid_d = retry.valid; + end else begin + failed_valid_d = failed_valid_q; + end + + if (retry.valid & retry.ready) begin + failed_id_d = retry.id; + end else begin + failed_id_d = failed_id_q; + end + end + + assign retry_lock_d = retry.lock; + `FF(failed_id_q, failed_id_d, '0); + `FF(failed_valid_q, failed_valid_d, '0); + `FF(retry_lock_q, retry_lock_d, '0); + + assign retry.ready = ready_i | ~failed_valid_q; + + ////////////////////////////////////////////////////////////////////// + // ID Counter with parity bit + + logic [IDSize-1:0] counter_id_d, counter_id_q; + + always_comb begin: gen_id_counter + if ((failed_valid_q | valid_i) & ready_i) begin + + // The topmost ID bits are not incremented but are controlled externally if + // required to split the storage area into sections. In this case get if fron external + // or take it from the element to retry. + counter_id_d[NormalIDSize-1:0] = counter_id_q[NormalIDSize-1:0] + 1; + if (ExternalIDBits > 0) begin + if (failed_valid_q) begin + counter_id_d[IDSize-1: NormalIDSize] = failed_id_q[IDSize-1: NormalIDSize]; + end else begin + counter_id_d[IDSize-1: NormalIDSize] = ext_id_bits_i[ExternalIDBits-1 :0]; + end + end + + end else begin + counter_id_d = counter_id_q; + end + end + + `FF(counter_id_q, counter_id_d, 0); + + ////////////////////////////////////////////////////////////////////// + // General Element storage + + logic [2 ** IDSize -1:0][$bits(DataType)-1:0] data_storage_d, data_storage_q; + + always_comb begin: gen_failed_state + // Keep data as is as abase + data_storage_d = data_storage_q; + + if ((failed_valid_q | valid_i) & ready_i) begin + data_storage_d[counter_id_q] = data_o; + end + end + + `FF(data_storage_q, data_storage_d, 0); + + always_comb begin: gen_output + if (failed_valid_q & ready_i) begin + data_o = data_storage_q[failed_id_q]; + end else begin + data_o = data_i; + end + id_o = counter_id_q; + retry.id_feedback = counter_id_d; + end + + ////////////////////////////////////////////////////////////////////// + // Handshake assignment + assign ready_o = ready_i & !failed_valid_q & !retry_lock_q; + assign valid_o = valid_i & !retry_lock_q | failed_valid_q; + +endmodule diff --git a/rtl/time_redundancy/retry_interface.sv b/rtl/time_redundancy/retry_interface.sv new file mode 100644 index 00000000..43543e0f --- /dev/null +++ b/rtl/time_redundancy/retry_interface.sv @@ -0,0 +1,27 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: Interface in between retry modules to transmit which elements need to be tried again + +interface retry_interface #(parameter IDSize = 0) ( /* No ports on this interface */ ); + logic [IDSize-1:0] id; + logic valid; + logic ready; + logic [IDSize-1:0] id_feedback; + logic lock; + + modport start ( + input id, + input valid, + output ready, + output id_feedback, + input lock + ); + + modport ende ( + output id, + output valid, + input ready, + input id_feedback, + output lock + ); +endinterface \ No newline at end of file diff --git a/rtl/time_redundancy/retry_start.sv b/rtl/time_redundancy/retry_start.sv new file mode 100644 index 00000000..f5920f0e --- /dev/null +++ b/rtl/time_redundancy/retry_start.sv @@ -0,0 +1,148 @@ +// Author: Maurus Item , ETH Zurich +// Date: 25.04.2024 +// Description: retry is a pair of modules that can be used to run an operation +// passing through a (pipelined) combinatorial process. +// +// In order to propperly function: +// - id_o of retry_start needs to be passed paralelly along the combinatorial logic, +// using the same handshake and arrive at id_i of retry_end +// - interface retry of retry_start needs to be directly connected to retry of retry_end +// - All elements in processing have a unique ID +// +// This modules might cause operations to reach the output of retry_end in a different +// order than they have entered retry_start e.g. can be out-of-order in case of a retry +// so make sure you can deal with out-of-order results. +// For the special case that the process is purely combinatorial, the same operaton is tried again +// in the very next cycle and thus the order is preserved. +// If you need in-order for pipelined processes have a look at retry_inorder instead. + +`include "common_cells/registers.svh" + + +module retry_start # ( + parameter type DataType = logic, + // The size of the ID to use as an auxilliary signal + // For an in-order process, this can be set to 1. + // For an out of order process, it needs to be big enough so that the out-of-orderness can never + // rearange the elements with the same id next to each other + // As an estimate you can use log2(longest_pipeline) + 1 + // Needs to match with retry_end! + parameter IDSize = 1, + // Amount of bits from the ID which are defined externally and should not be incremented. + // This allows for seperating the ID spaces into multiple sections, which will behave and overwrite + // each other indefinitely. For example, if you set IDSize=3 and ExternalIDBits=1, then you will get + // two sets of IDs which each cycle through the ID. + // Set 1: 000, 001, 010, 011 + // Set 2: 100, 101, 110, 111 + // You can use this to reduce storage space required if some operations take significantly longer in + // the pipelines than others. E.g. use Set 1 with operations done in 1 cycle, and Set 2 with operations + // that take 10 cycles. Each subset must satisfy the required ID Size of log2(longest_pipeline) + 1, + // excluding the bits used to distinguish the sets. + parameter ExternalIDBits = 0, + // Physical width of the ID bits input so that the case of 0 is well defined + // Must be equal or greater than the ExternalIDBits that are actually used + parameter ExternalIDWidth = (ExternalIDBits == 0) ? 1 : ExternalIDBits, + localparam NormalIDSize = IDSize - ExternalIDBits +) ( + input logic clk_i, + input logic rst_ni, + + // Upstream connection + input DataType data_i, + input logic valid_i, + output logic ready_o, + input logic [ExternalIDWidth-1:0] ext_id_bits_i, + + // Downstream connection + output DataType data_o, + output logic [IDSize-1:0] id_o, + output logic valid_o, + input logic ready_i, + + // Retry Connection + retry_interface.start retry +); + + + ////////////////////////////////////////////////////////////////////// + // Register to store failed id for one cycle + logic [IDSize-1:0] failed_id_d, failed_id_q; + logic failed_valid_d, failed_valid_q; + + always_comb begin: gen_next_cycle_decision + if (ready_i | retry.valid) begin + failed_valid_d = retry.valid; + end else begin + failed_valid_d = failed_valid_q; + end + + if (retry.valid & retry.ready) begin + failed_id_d = retry.id; + end else begin + failed_id_d = failed_id_q; + end + end + + `FF(failed_id_q, failed_id_d, '0); + `FF(failed_valid_q, failed_valid_d, '0); + + assign retry.ready = ready_i | ~failed_valid_q; + + ////////////////////////////////////////////////////////////////////// + // ID Counter + + logic [IDSize-1:0] counter_id_d, counter_id_q; + + always_comb begin: gen_id_counter + if ((failed_valid_q | valid_i) & ready_i) begin + + // The topmost ID bits are not incremented but are controlled externally if + // required to split the storage area into sections. In this case get if fron external + // or take it from the element to retry. + counter_id_d[NormalIDSize-1:0] = counter_id_q[NormalIDSize-1:0] + 1; + if (ExternalIDBits > 0) begin + if (failed_valid_q) begin + counter_id_d[IDSize-1: NormalIDSize] = failed_id_q[IDSize-1: NormalIDSize]; + end else begin + counter_id_d[IDSize-1: NormalIDSize] = ext_id_bits_i[ExternalIDBits-1 :0]; + end + end + + end else begin + counter_id_d = counter_id_q; + end + end + + `FF(counter_id_q, counter_id_d, 0); + + ////////////////////////////////////////////////////////////////////// + // General Element storage + + logic [2 ** IDSize -1:0][$bits(DataType)-1:0] data_storage_d, data_storage_q; + + always_comb begin: gen_failed_state + // Keep data as is as abase + data_storage_d = data_storage_q; + + if ((failed_valid_q | valid_i) & ready_i) begin + data_storage_d[counter_id_q] = data_o; + end + end + + `FF(data_storage_q, data_storage_d, 0); + + always_comb begin: gen_output + if (failed_valid_q & ready_i) begin + data_o = data_storage_q[failed_id_q]; + end else begin + data_o = data_i; + end + id_o = counter_id_q; + end + + ////////////////////////////////////////////////////////////////////// + // Handshake assignment + assign ready_o = ready_i & !failed_valid_q; + assign valid_o = valid_i | failed_valid_q; + +endmodule diff --git a/rtl/time_redundancy/rr_arb_tree_lock.sv b/rtl/time_redundancy/rr_arb_tree_lock.sv new file mode 100644 index 00000000..778c08ef --- /dev/null +++ b/rtl/time_redundancy/rr_arb_tree_lock.sv @@ -0,0 +1,356 @@ +// Copyright 2019 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. +// +// Author: Michael Schaffner , ETH Zurich +// Wolfgang Roenninger , ETH Zurich +// Maurus Item , ETH Zurich +// Date: 10.04.2023 +// Description: logarithmic arbitration tree with round robin arbitration scheme +// that can be externally locked to not arbitrate and just send the same output. + +/// The rr_arb_tree employs non-starving round robin-arbitration - i.e., the priorities +/// rotate each cycle. +/// +/// ## Fair vs. unfair Arbitration +/// +/// This refers to fair throughput distribution when not all inputs have active requests. +/// This module has an internal state `rr_q` which defines the highest priority input. (When +/// `ExtPrio` is `1'b1` this state is provided from the outside.) The arbitration tree will +/// choose the input with the same index as currently defined by the state if it has an active +/// request. Otherwise a *random* other active input is selected. The parameter `FairArb` is used +/// to distinguish between two methods of calculating the next state. +/// * `1'b0`: The next state is calculated by advancing the current state by one. This leads to the +/// state being calculated without the context of the active request. Leading to an +/// unfair throughput distribution if not all inputs have active requests. +/// * `1'b1`: The next state jumps to the next unserved request with higher index. +/// This is achieved by using two trailing-zero-counters (`lzc`). The upper has the masked +/// `req_i` signal with all indices which will have a higher priority in the next state. +/// The trailing zero count defines the input index with the next highest priority after +/// the current one is served. When the upper is empty the lower `lzc` provides the +/// wrapped index if there are outstanding requests with lower or same priority. +/// The implication of throughput fairness on the module timing are: +/// * The trailing zero counter (`lzc`) has a loglog relation of input to output timing. This means +/// that in this module the input to register path scales with Log(Log(`NumIn`)). +/// * The `rr_arb_tree` data multiplexing scales with Log(`NumIn`). This means that the input to output +/// timing path of this module also scales scales with Log(`NumIn`). +/// This implies that in this module the input to output path is always longer than the input to +/// register path. As the output data usually also terminates in a register the parameter `FairArb` +/// only has implications on the area. When it is `1'b0` a static plus one adder is instantiated. +/// If it is `1'b1` two `lzc`, a masking logic stage and a two input multiplexer are instantiated. +/// However these are small in respect of the data multiplexers needed, as the width of the `req_i` +/// signal is usually less as than `DataWidth`. + +`include "redundancy_cells/voters.svh" + +module rr_arb_tree_lock #( + /// Number of inputs to be arbitrated. + parameter int unsigned NumIn = 64, + /// Data width of the payload in bits. Not needed if `DataType` is overwritten. + parameter int unsigned DataWidth = 32, + /// Data type of the payload, can be overwritten with custom type. Only use of `DataWidth`. + parameter type DataType = logic [DataWidth-1:0], + /// The `ExtPrio` option allows to override the internal round robin counter via the + /// `rr_i` signal. This can be useful in case multiple arbiters need to have + /// rotating priorities that are operating in lock-step. If static priority arbitration + /// is needed, just connect `rr_i` to '0. + /// + /// Set to 1'b1 to enable. + parameter bit ExtPrio = 1'b0, + /// If `AxiVldRdy` is set, the req/gnt signals are compliant with the AXI style vld/rdy + /// handshake. Namely, upstream vld (req) must not depend on rdy (gnt), as it can be deasserted + /// again even though vld is asserted. Enabling `AxiVldRdy` leads to a reduction of arbiter + /// delay and area. + /// + /// Set to `1'b1` to treat req/gnt as vld/rdy. + parameter bit AxiVldRdy = 1'b0, + /// When set, ensures that throughput gets distributed evenly between all inputs. + /// + /// Set to `1'b0` to disable. + parameter bit FairArb = 1'b1, + /// Dependent parameter, do **not** overwrite. + /// Width of the arbitration priority signal and the arbitrated index. + parameter int unsigned IdxWidth = (NumIn > 32'd1) ? unsigned'($clog2(NumIn)) : 32'd1, + /// Dependent parameter, do **not** overwrite. + /// Type for defining the arbitration priority and arbitrated index signal. + parameter type idx_t = logic [IdxWidth-1:0], + // Determines if the internal state machines should + // be parallely redundant, meaning errors inside this module + // can also not cause errors in the output + // The external output is never protected! + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1 +) ( + /// Clock, positive edge triggered. + input logic clk_i, + /// Asynchronous reset, active low. + input logic rst_ni, + /// Clears the arbiter state. Only used if `ExtPrio` is `1'b0` or `LockIn` is `1'b1`. + input logic [REP-1:0] flush_i, + /// External round-robin priority. + input idx_t rr_i, + /// lock idx signal, only used if `ExtPrio` is `1'b0.` + input logic [REP-1:0] lock_rr_i, + /// Input requests arbitration. + input logic [NumIn-1:0] req_i, + /* verilator lint_off UNOPTFLAT */ + /// Input request is granted. + output logic [NumIn-1:0] gnt_o, + /* verilator lint_on UNOPTFLAT */ + /// Input data for arbitration. + input DataType [NumIn-1:0] data_i, + /// Output request is valid. + output logic req_o, + /// Output request is granted. + input logic gnt_i, + /// Output data. + output DataType data_o, + /// Index from which input the data came from. + output idx_t idx_o +); + + // pragma translate_off + `ifndef VERILATOR + `ifndef XSIM + // Default SVA reset + default disable iff (!rst_ni || flush_i[0]); + `endif + `endif + // pragma translate_on + + // just pass through in this corner case + if (NumIn == unsigned'(1)) begin : gen_pass_through + assign req_o = req_i[0]; + assign gnt_o[0] = gnt_i; + assign data_o = data_i[0]; + assign idx_o = '0; + // non-degenerate cases + end else begin : gen_arbiter + localparam int unsigned NumLevels = unsigned'($clog2(NumIn)); + + /* verilator lint_off UNOPTFLAT */ + idx_t [2**NumLevels-2:0][REP-1:0] index_nodes; // used to propagate the indices + DataType [2**NumLevels-2:0] data_nodes; // used to propagate the data + logic [2**NumLevels-2:0] gnt_nodes; // used to propagate the grant to masters + logic [2**NumLevels-2:0] req_nodes; // used to propagate the requests to slave + /* lint_off */ + idx_t [REP-1:0] rr_q; + logic [NumIn-1:0] req_d; + + // the final arbitration decision can be taken from the root of the tree + assign req_o = req_nodes[0]; + assign data_o = data_nodes[0]; + assign idx_o = index_nodes[0][0]; + + + if (ExtPrio) begin : gen_ext_rr + assign rr_q[0] = rr_i; + assign req_d = req_i; + end else begin : gen_int_rr + idx_t [REP-1:0] rr_d; + + assign req_d = req_i; + + idx_t next_idx; + + if (FairArb) begin : gen_fair_arb + + logic [NumIn-1:0] upper_mask, lower_mask; + idx_t upper_idx, lower_idx; + logic upper_empty, lower_empty; + + for (genvar i = 0; i < NumIn; i++) begin : gen_mask + assign upper_mask[i] = (i > rr_q[0]) ? req_d[i] : 1'b0; + assign lower_mask[i] = (i <= rr_q[0]) ? req_d[i] : 1'b0; + end + + lzc #( + .WIDTH ( NumIn ), + .MODE ( 1'b0 ) + ) i_lzc_upper ( + .in_i ( upper_mask ), + .cnt_o ( upper_idx ), + .empty_o ( upper_empty ) + ); + + lzc #( + .WIDTH ( NumIn ), + .MODE ( 1'b0 ) + ) i_lzc_lower ( + .in_i ( lower_mask ), + .cnt_o ( lower_idx ), + .empty_o ( /*unused*/ ) + ); + + assign next_idx = upper_empty ? lower_idx : upper_idx; + end else begin : gen_unfair_arb + assign next_idx = ((rr_q[0] == idx_t'(NumIn-1)) ? '0 : rr_q[0] + 1'b1); + end + + idx_t [REP-1:0] voted_idx, voted_rr_q; + `VOTEXX(REP, index_nodes[0], voted_idx); + `VOTEXX(REP, rr_q, voted_rr_q); + + for (genvar r = 0; r < REP; r++) begin: gen_rr + always_comb begin + if (lock_rr_i[r]) begin + rr_d[r] = voted_idx[r]; + end else begin + if (gnt_i && req_o) begin + rr_d[r] = next_idx; + end else begin + rr_d[r] = voted_rr_q[r]; + end + end + end + + // this holds the highest priority + always_ff @(posedge clk_i or negedge rst_ni) begin : p_rr_regs + if (!rst_ni) begin + rr_q[r] <= '0; + end else begin + if (flush_i[r]) begin + rr_q[r] <= '0; + end else begin + rr_q[r] <= rr_d[r]; + end + end + end + end + end + + logic [REP-1:0] lock_rr_q; + for (genvar r = 0; r < REP; r++) begin: gen_lock_q + always_ff @(posedge clk_i or negedge rst_ni) begin : p_lock_reg + if (!rst_ni) begin + lock_rr_q[r] <= '1; + end else begin + if (flush_i[r]) begin + lock_rr_q[r] <= '0; + end else begin + lock_rr_q[r] <= lock_rr_i[r]; + end + end + end + end + + assign gnt_nodes[0] = gnt_i; + + // arbiter tree + for (genvar level = 0; unsigned'(level) < NumLevels; level++) begin : gen_levels + for (genvar l = 0; l < 2**level; l++) begin : gen_level + // local select signal + logic [REP-1:0] sel; + // index calcs + localparam int unsigned Idx0 = 2**level-1+l;// current node + localparam int unsigned Idx1 = 2**(level+1)-1+l*2; + ////////////////////////////////////////////////////////////// + // uppermost level where data is fed in from the inputs + if (unsigned'(level) == NumLevels-1) begin : gen_first_level + // if two successive indices are still in the vector... + if (unsigned'(l) * 2 < NumIn-1) begin : gen_reduce + + for (genvar r = 0; r < REP; r++) begin: gen_select + // arbitration: round robin + always_comb begin + if (lock_rr_q[r]) begin + sel[r] = rr_q[r][NumLevels-1-level]; + end else begin + sel[r] = ~req_d[l*2] | req_d[l*2+1] & rr_q[0][NumLevels-1-level]; + end + end + assign index_nodes[Idx0][r] = idx_t'(sel[r]); + end + + assign req_nodes[Idx0] = (sel[0]) ? req_d[l*2+1] : req_d[l*2] ; + assign data_nodes[Idx0] = (sel[0]) ? data_i[l*2+1] : data_i[l*2]; + assign gnt_o[l*2] = gnt_nodes[Idx0] & (AxiVldRdy | req_d[l*2]) & ~sel[0]; + assign gnt_o[l*2+1] = gnt_nodes[Idx0] & (AxiVldRdy | req_d[l*2+1]) & sel[0]; + end + // if only the first index is still in the vector... + if (unsigned'(l) * 2 == NumIn-1) begin : gen_first + assign req_nodes[Idx0] = req_d[l*2]; + assign index_nodes[Idx0] = '0; + assign data_nodes[Idx0] = data_i[l*2]; + assign gnt_o[l*2] = gnt_nodes[Idx0] & (AxiVldRdy | req_d[l*2]); + end + // if index is out of range, fill up with zeros (will get pruned) + if (unsigned'(l) * 2 > NumIn-1) begin : gen_out_of_range + assign req_nodes[Idx0] = 1'b0; + assign index_nodes[Idx0] = '0; + assign data_nodes[Idx0] = DataType'('0); + end + ////////////////////////////////////////////////////////////// + // general case for other levels within the tree + end else begin : gen_other_levels + + // arbitration: round robin + for (genvar r = 0; r < REP; r++) begin: gen_select + always_comb begin + if (lock_rr_q[r]) begin + sel[r] = rr_q[r][NumLevels-1-level]; + end else begin + sel[r] = ~req_nodes[Idx1] | req_nodes[Idx1+1] & rr_q[0][NumLevels-1-level]; + end + end + + assign index_nodes[Idx0][r] = (sel[r]) ? + idx_t'({1'b1, index_nodes[Idx1+1][r][NumLevels-unsigned'(level)-2:0]}) : + idx_t'({1'b0, index_nodes[Idx1][r][NumLevels-unsigned'(level)-2:0]}); + end + + assign req_nodes[Idx0] = (sel[0]) ? req_nodes[Idx1+1] : req_nodes[Idx1]; + assign data_nodes[Idx0] = (sel[0]) ? data_nodes[Idx1+1] : data_nodes[Idx1]; + assign gnt_nodes[Idx1] = gnt_nodes[Idx0] & ~sel[0]; + assign gnt_nodes[Idx1+1] = gnt_nodes[Idx0] & sel[0]; + end + ////////////////////////////////////////////////////////////// + end + end + + // pragma translate_off + `ifndef VERILATOR + `ifndef XSIM + initial begin : p_assert + assert(NumIn) + else $error(1, "Input must be at least one element wide."); + assert(!(ExtPrio && (REP > 1))) + else $error(1, "There is no reason to use REP > 1 with ExtPrio!"); + end + + hot_one : assert property( + @(posedge clk_i) $onehot0(gnt_o)) + else $error (1, "Grant signal must be hot1 or zero."); + + gnt0 : assert property( + @(posedge clk_i) |gnt_o |-> gnt_i) + else $error (1, "Grant out implies grant in."); + + gnt1 : assert property( + @(posedge clk_i) req_o |-> gnt_i |-> |gnt_o) + else $error (1, "Req out and grant in implies grant out."); + + gnt_idx : assert property( + @(posedge clk_i) req_o |-> gnt_i |-> gnt_o[idx_o]) + else $error (1, "Idx_o / gnt_o do not match."); + + req1 : assert property( + @(posedge clk_i) req_o |-> |req_i) + else $error (1, "Req out implies req in."); + + lock2 : assert property( + @(posedge clk_i) disable iff (!rst_ni) lock_rr_q[0] |-> idx_o == $past(idx_o)) + else $error (1, "Lock means idx_o does not change."); + `endif + `endif + // pragma translate_on + end + +endmodule : rr_arb_tree_lock diff --git a/run_tests.sh b/run_tests.sh index 6a04ad78..3205c0f5 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -50,3 +50,26 @@ call_vsim -GDataWidth=32 tb_ecc_secded call_vsim -GDataWidth=64 tb_ecc_secded call_vsim tb_ecc_scrubber call_vsim tb_voter_macros + +call_vsim tb_retry +call_vsim tb_retry_inorder + +for redundancy in 0 1; do + call_vsim tb_redundancy_controller -GInternalRedundancy=$redundancy + + for early_ready in 0 1; do + for early_valid in 0 1; do + call_vsim tb_ttr -GEarlyValidEnable=$early_valid -GEarlyReadyEnable=$early_ready -GInternalRedundancy=$redundancy + call_vsim tb_ttr_lock -GEarlyValidEnable=$early_valid -GEarlyReadyEnable=$early_ready -GInternalRedundancy=$redundancy + done + + call_vsim tb_dtr -GEarlyReadyEnable=$early_ready -GInternalRedundancy=$redundancy + done + + call_vsim tb_dtr_retry -GEarlyReadyEnable=$early_ready -GInternalRedundancy=$redundancy + call_vsim tb_dtr_retry_lock -voptargs="+acc" -GEarlyReadyEnable=$early_ready -GInternalRedundancy=$redundancy +done + +for num in 1 4 7; do + call_vsim tb_rr_arb_tree_lock -GNumInp=$num -coverage -voptargs="+acc +cover=bcesfx" -suppress vsim-3009 +done diff --git a/src_files.yml b/src_files.yml index 3e44aeea..719134ce 100644 --- a/src_files.yml +++ b/src_files.yml @@ -9,6 +9,9 @@ redundancy_cells: lowrisc_ecc/prim_secded_39_32_enc.sv, lowrisc_ecc/prim_secded_72_64_dec.sv, lowrisc_ecc/prim_secded_72_64_enc.sv, + rtl/time_redundancy/DTR_interface.sv + rtl/time_redundancy/retry_interface.sv + rtl/time_redundancy/rr_arb_tree_lock.sv rtl/TMR_voter.sv, rtl/TMR_voter_fail.sv, rtl/TMR_word_voter, @@ -28,6 +31,15 @@ redundancy_cells: rtl/BUS_enc_dec/TCDM_XBAR_bus_ecc_enc.sv, rlt/BUS_enc_dec/XBAR_DEMUX_BUS_ecc_dec.sv, rtl/BUS_enc_dec/XBAR_DEMUX_BUS_ecc_enc.sv, + rtl/time_redundancy/DTR_end.sv + rtl/time_redundancy/DTR_start.sv + rtl/time_redundancy/redundancy_controller.sv + rtl/time_redundancy/retry_end.sv + rtl/time_redundancy/retry_inorder_start.sv + rtl/time_redundancy/retry_inorder_end.sv + rtl/time_redundancy/retry_start.sv + rtl/time_redundancy/TTR_end.sv + rtl/time_redundancy/TTR_start.sv rtl/TMR_voter_detect.sv, # Level 2 rtl/bitwise_TMR_voter.sv, diff --git a/test/tb_dtr.sv b/test/tb_dtr.sv new file mode 100644 index 00000000..acbda5b3 --- /dev/null +++ b/test/tb_dtr.sv @@ -0,0 +1,286 @@ +module tb_dtr #( + // DUT Parameters + parameter IDSize = 4, + parameter int LockTimeout = 4 * 12, + parameter bit InternalRedundancy = 0, + + // TB Parameters + parameter int unsigned TESTS = 10000, + parameter time CLK_PERIOD = 10ns, + parameter time APPLICATION_DELAY = 2ns, + parameter time AQUISITION_DELAY = 8ns + +) ( /* no ports on TB */ ); + + `include "tb_time.svh" + + //////////////////////////////////////////////////////////////////////////////////7 + // DUT (s) + //////////////////////////////////////////////////////////////////////////////////7 + + typedef logic [7:0] data_t; + + data_t data_in, data_redundant, data_fault, data_redundant_faulty, data_out; + logic valid_redundant, valid_fault, valid_redundant_faulty; + logic ready_redundant, ready_fault, ready_redundant_faulty; + logic [IDSize-1:0] id_redundant, id_fault, id_redundant_faulty, id_next; + + // Forward connection + DTR_interface #( + .IDSize(IDSize), + .InternalRedundancy(InternalRedundancy) + ) dtr_interface (); + + // DUT Instances + DTR_start #( + .DataType(data_t), + .IDSize(IDSize), + .UseExternalId(0), + .InternalRedundancy(InternalRedundancy) + ) dut_start ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + .dtr_interface(dtr_interface), + + // Upstream connection + .data_i(data_in), + .id_i('0), + .valid_i(valid_in), + .ready_o(ready_in), + + // Downstream connection + .data_o(data_redundant), + .id_o(id_redundant), + .valid_o(valid_redundant), + .ready_i(ready_redundant_faulty) + ); + + DTR_end #( + .DataType(data_t), + .LockTimeout(LockTimeout), + .IDSize(IDSize), + .InternalRedundancy(InternalRedundancy) + ) dut_end ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + .dtr_interface(dtr_interface), + + // Upstream connection + .data_i(data_redundant_faulty), + .id_i(id_redundant_faulty), + .valid_i(valid_redundant_faulty), + .ready_o(ready_redundant), + .lock_o(/*Unused*/), + + // Downstream connection + .data_o(data_out), + .id_o(/*Unused*/), + .needs_retry_o(needs_retry_out), + .valid_o(valid_out), + .ready_i(ready_out), + + .fault_detected_o(/*Unused*/) + ); + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Input + //////////////////////////////////////////////////////////////////////////////////7 + data_t golden_queue [$]; + + initial begin + forever begin + input_handshake_begin(); + data_in = $random; + golden_queue.push_back(data_in); + input_handshake_end(); + end + end + + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Output + //////////////////////////////////////////////////////////////////////////////////7 + data_t data_golden, data_actual; + logic needs_retry_actual; + logic error; // Helper signal so one can quickly scroll to errors in questa + longint unsigned error_cnt = 0; + int fault_budget = 0; + + // Progress reporting + task reset_metrics(); + reset(); + error_cnt = 0; + in_hs_count = 0; + out_hs_count = 0; + golden_queue.delete(); + endtask + + initial begin + $timeformat(-9, 0, " ns", 20); + forever begin + output_handshake_start(); + data_actual = data_out; + needs_retry_actual = needs_retry_out; + if (golden_queue.size() > 0) begin + data_golden = golden_queue.pop_front(); + + if (needs_retry_actual) begin + fault_budget -= 1; + if (fault_budget < 0) begin + $error("[T=%t] More faults detected than injected!", $time); + error = 1; + error_cnt += 1; + end + end else begin + if (data_actual != data_golden) begin + $error("[T=%t] Mismatch: Golden: %h, Actual: %h", $time, data_golden, data_actual); + error = 1; + error_cnt += 1; + end else begin + fault_budget = 1; + error = 0; + end + end + + end else begin + $display("[T=%t] Data %h Output when nothing was in golden queue", $time, data_actual); + error = 1; + error_cnt += 1; + end + output_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Fault Injection + //////////////////////////////////////////////////////////////////////////////////7 + + longint unsigned min_fault_delay = 12 * 4; + longint unsigned max_fault_delay = 12 * 4 + 20; + + // Signals to show what faults are going on + enum {NONE, DATA_FAULT, VALID_FAULT, READY_FAULT, ID_FAULT} fault_type, fault_current; + + assign data_redundant_faulty = data_redundant ^ data_fault; + assign valid_redundant_faulty = (valid_redundant & stall) ^ valid_fault; + assign ready_redundant_faulty = (ready_redundant & stall) ^ ready_fault; + assign id_redundant_faulty = id_redundant ^ id_fault; + + initial data_fault = '0; + initial valid_fault = '0; + initial ready_fault = '0; + initial id_fault = '0; + + task inject_fault(); + // Send correct data for some cycles to space errors + repeat ($urandom_range(min_fault_delay, max_fault_delay)) begin + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + end + + // Send wrong data + fault_current = fault_type; + case (fault_type) + DATA_FAULT: data_fault <= $random; + VALID_FAULT: valid_fault <= 1; + READY_FAULT: ready_fault <= 1; + ID_FAULT: id_fault <= (1 << $urandom_range(0,IDSize-1)); + endcase + + fault_budget += 1; + + // Send correct data again + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + endtask + + //////////////////////////////////////////////////////////////////////////////////7 + // Main Loop + //////////////////////////////////////////////////////////////////////////////////7 + longint unsigned total_error_cnt = 0; + + initial begin + reset_metrics(); + + // Check normal operation + fault_type = NONE; + enable = 0; + repeat (10 * TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc disabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + // Check fault tolerance + fault_type = DATA_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and data faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = VALID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and valid fault, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = READY_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and ready faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = ID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and ID faults, got %d errors.", error_cnt); + reset_metrics(); + + // Measure throughput + fault_type = NONE; + enable = 0; + in_hs_max_starvation = 0; + out_hs_max_starvation = 0; + internal_hs_max_starvation = 0; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc disabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS, error_cnt); + if (TESTS - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc disabled!"); + end + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS /2, error_cnt); + if (TESTS /2 - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc enabled!"); + end + reset_metrics(); + $display("Checked %0d tests of each type, found %0d mismatches.", TESTS, total_error_cnt); + $finish(error_cnt); + end + +endmodule diff --git a/test/tb_dtr_retry.sv b/test/tb_dtr_retry.sv new file mode 100644 index 00000000..d08023ca --- /dev/null +++ b/test/tb_dtr_retry.sv @@ -0,0 +1,364 @@ +module tb_dtr_retry #( + // DUT Parameters + parameter int IDSize = 4, + parameter int LockTimeout = 4 * 12, + parameter bit InternalRedundancy = 0, + parameter bit EarlyReadyEnable = 0, + + // TB Parameters + parameter int unsigned TESTS = 10000, + parameter time CLK_PERIOD = 10ns, + parameter time APPLICATION_DELAY = 2ns, + parameter time AQUISITION_DELAY = 8ns + +) ( /* no ports on TB */ ); + + `include "tb_time.svh" + + //////////////////////////////////////////////////////////////////////////////////7 + // DUT (s) + //////////////////////////////////////////////////////////////////////////////////7 + + // Signal Types + typedef logic [7:0] data_t; + typedef logic [7:0] tag_t; + + typedef struct packed { + data_t data; + tag_t tag; + } tagged_data_t; + + // Downstream Connection + tagged_data_t data_in, data_detectable, data_redundant, data_fault, data_redundant_faulty, data_detected, data_out; + logic valid_detectable, valid_redundant, valid_fault, valid_redundant_faulty, valid_detected; + logic ready_detectable, ready_redundant, ready_fault, ready_redundant_faulty, ready_detected; + logic [IDSize-1:0] id_redundant, id_fault, id_redundant_faulty; + logic [IDSize-2:0] id_detectable, id_detected; + logic needs_retry_detected; + + // Forward connection + DTR_interface #( + .IDSize(IDSize), + .InternalRedundancy(InternalRedundancy) + ) dtr_connection (); + + // Feedback connection + retry_interface #( + .IDSize(IDSize-1) + ) retry_connection (); + + // DUT Instances + retry_start #( + .DataType(tagged_data_t), + .IDSize(IDSize-1) + ) dut_retry_start ( + .clk_i(clk), + .rst_ni(rst_n), + + // Upstream connection + .data_i(data_in), + .valid_i(valid_in), + .ready_o(ready_in), + + // Downstream connection + .data_o(data_detectable), + .id_o(id_detectable), + .valid_o(valid_detectable), + .ready_i(ready_detectable), + + // Retry Connection + .retry(retry_connection) + ); + + + // DUT Instances + DTR_start #( + .DataType(tagged_data_t), + .IDSize(IDSize), + .EarlyReadyEnable(EarlyReadyEnable), + .UseExternalId(1), + .InternalRedundancy(InternalRedundancy) + ) dut_DMR_start ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + .dtr_interface(dtr_connection), + + // Upstream connection + .data_i(data_detectable), + .id_i(id_detectable), + .valid_i(valid_detectable), + .ready_o(ready_detectable), + + // Downstream connection + .data_o(data_redundant), + .id_o(id_redundant), + .valid_o(valid_redundant), + .ready_i(ready_redundant_faulty) + ); + + // DUT Instances + DTR_end #( + .DataType(tagged_data_t), + .LockTimeout(LockTimeout), + .IDSize(IDSize), + .InternalRedundancy(InternalRedundancy) + ) dut_DMR_end ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + .dtr_interface(dtr_connection), + + // Upstream connection + .data_i(data_redundant_faulty), + .id_i(id_redundant_faulty), + .valid_i(valid_redundant_faulty), + .ready_o(ready_redundant), + + // Downstream connection + .data_o(data_detected), + .id_o(id_detected), + .needs_retry_o(needs_retry_detected), + .valid_o(valid_detected), + .ready_i(ready_detected), + .lock_o(/*Unused*/), + + .fault_detected_o(/*Unused*/) + ); + + // DUT Instances + retry_end #( + .DataType(tagged_data_t), + .IDSize(IDSize-1) + ) dut_retry_end ( + .clk_i(clk), + .rst_ni(rst_n), + + // Upstream connection + .data_i(data_detected), + .id_i(id_detected), + .needs_retry_i(needs_retry_detected), + .valid_i(valid_detected), + .ready_o(ready_detected), + + // Downstream connection + .data_o(data_out), + .valid_o(valid_out), + .ready_i(ready_out), + + // Retry Connection + .retry(retry_connection) + ); + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Input + //////////////////////////////////////////////////////////////////////////////////7 + tagged_data_t golden_queue[$]; + + initial begin + tag_t tag_new; + tagged_data_t data_new; + + tag_new = 0; + + forever begin + input_handshake_begin(); + tag_new += 1; + data_new.data = $random; + data_new.tag = tag_new; + data_in = data_new; + golden_queue.push_back(data_new); + input_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Output + //////////////////////////////////////////////////////////////////////////////////7 + tagged_data_t data_golden, data_actual; + logic error, found; // Helper signal so one can quickly scroll to errors in questa + longint unsigned error_cnt = 0; + + // Progress reporting + task reset_metrics(); + reset(); + error_cnt = 0; + in_hs_count = 0; + out_hs_count = 0; + golden_queue.delete(); + endtask + + initial begin + $timeformat(-9, 0, " ns", 20); + forever begin + output_handshake_start(); + data_actual = data_out; + + if (golden_queue.size() > 0) begin + found = 0; + + repeat (golden_queue.size()) begin + data_golden = golden_queue.pop_front(); + if (data_golden.tag == data_actual.tag) begin + // Check output + if (data_actual.data != data_golden.data) begin + $error("[T=%t] Mismatch: Golden: %h, Actual: %h", $time, data_golden, data_actual); + error = 1; + error_cnt += 1; + break; + end else begin + error = 0; + found = 1; + break; + end + end else begin + golden_queue.push_back(data_golden); + end + end + if (found == 0) begin + $display("[T=%t] Tag %h Data %h Output but was not in golden queue ", $time, data_actual.tag, data_actual.data); + error = 1; + error_cnt += 1; + end + end else if (golden_queue.size() > 2 ** IDSize) begin + $display("[T=%t] Data does not get output in a timely manner!", $time); + error = 1; + error_cnt += 1; + end else begin + $display("[T=%t] Tag %h Data %h Output when nothing was in golden queue", $time, data_actual.tag, data_actual.data); + error = 1; + error_cnt += 1; + end + output_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Fault Injection + //////////////////////////////////////////////////////////////////////////////////7 + + longint unsigned min_fault_delay = 12 * 4; + longint unsigned max_fault_delay = 12 * 4 + 20; + + // Signals to show what faults are going on + enum {NONE, DATA_FAULT, VALID_FAULT, READY_FAULT, ID_FAULT} fault_type, fault_current; + + assign data_redundant_faulty = data_redundant ^ data_fault; + assign valid_redundant_faulty = (valid_redundant & stall) ^ valid_fault; + assign ready_redundant_faulty = (ready_redundant & stall) ^ ready_fault; + assign id_redundant_faulty = id_redundant ^ id_fault; + + initial data_fault = '0; + initial valid_fault = '0; + initial ready_fault = '0; + initial id_fault = '0; + + task inject_fault(); + // Send correct data for some cycles to space errors + repeat ($urandom_range(min_fault_delay, max_fault_delay)) begin + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + end + + // Send wrong data + fault_current = fault_type; + case (fault_type) + DATA_FAULT: data_fault <= $random; + VALID_FAULT: valid_fault <= 1; + READY_FAULT: ready_fault <= 1; + ID_FAULT: id_fault <= (1 << $urandom_range(0,IDSize-1)); + endcase + + // Send correct data again + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + endtask + + //////////////////////////////////////////////////////////////////////////////////7 + // Main Loop + //////////////////////////////////////////////////////////////////////////////////7 + longint unsigned total_fault_cnt = 0; + + initial begin + reset_metrics(); + + // Check normal operation + fault_type = NONE; + enable = 0; + repeat (10 * TESTS) @(posedge clk); + total_fault_cnt += error_cnt; + $display("Ending Test with ecc disabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_fault_cnt += error_cnt; + $display("Ending Test with ecc enabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + // Check fault tolerance + fault_type = DATA_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_fault_cnt += error_cnt; + $display("Ending Test with ecc enabled and data faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = VALID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_fault_cnt += error_cnt; + $display("Ending Test with ecc enabled and valid fault, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = READY_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_fault_cnt += error_cnt; + $display("Ending Test with ecc enabled and ready faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = ID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_fault_cnt += error_cnt; + $display("Ending Test with ecc enabled and ID faults, got %d errors.", error_cnt); + reset_metrics(); + + // Measure throughput + fault_type = NONE; + enable = 0; + in_hs_max_starvation = 0; + out_hs_max_starvation = 0; + internal_hs_max_starvation = 0; + repeat (TESTS) @(posedge clk); + total_fault_cnt += error_cnt; + $display("Ending Test with ecc disabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS, error_cnt); + if (TESTS - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc disabled!"); + end + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_fault_cnt += error_cnt; + $display("Ending Test with ecc enabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS/2, error_cnt); + if (TESTS/2 - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc enabled!"); + end + reset_metrics(); + $display("Checked %0d tests of each type, found %0d mismatches.", TESTS, total_fault_cnt); + $finish(error_cnt); + end + +endmodule diff --git a/test/tb_dtr_retry_lock.sv b/test/tb_dtr_retry_lock.sv new file mode 100644 index 00000000..e20451c8 --- /dev/null +++ b/test/tb_dtr_retry_lock.sv @@ -0,0 +1,509 @@ +`include "common_cells/registers.svh" + +module tb_dtr_retry_lock #( + // DUT Parameters + parameter int LockTimeout = 5 * 12, + parameter int NumOpgroups = 3, + parameter int OpgroupWidth = $clog2(NumOpgroups), + parameter int IDSize = 9, + parameter [NumOpgroups-1:0][7:0] OpgroupNumRegs = {8'd4, 8'd3, 8'd3}, + parameter bit EarlyReadyEnable = 0, + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1, + + // TB Parameters + parameter int unsigned TESTS = 10000, + parameter time CLK_PERIOD = 10ns, + parameter time APPLICATION_DELAY = 2ns, + parameter time AQUISITION_DELAY = 8ns +) ( /* no ports on TB */ ); + + `include "tb_time.svh" + + //////////////////////////////////////////////////////////////////////////////////7 + // DUT (s) + //////////////////////////////////////////////////////////////////////////////////7 + + typedef logic [7:0] data_t; + typedef logic [OpgroupWidth-1:0] operation_t; + typedef logic [IDSize-1:0] id_parity_t; + typedef logic [IDSize-2:0] id_t; + typedef logic [7:0] tag_t; + + typedef struct packed { + data_t data; + tag_t tag; + } tagged_data_t; + + // Typedef for stacked signal in TMR + typedef struct packed { + tagged_data_t data; + operation_t operation; + } tmr_stacked_t; + + // Typedef for stacked signal in TMR + typedef struct packed { + id_parity_t id; + tagged_data_t data; + } rr_stacked_t; + + // Input & Output + tagged_data_t data_in, data_out; + operation_t operation_in, operation_out; + + // Fault Connections for Injection + tagged_data_t data_fault; + logic valid_fault; + logic ready_fault; + id_parity_t id_fault; + + // Signals for after TMR + tmr_stacked_t in_tmr_stack_redundant; + logic in_valid_redundant, in_ready_redundant; + id_parity_t in_id_redundant; + + // Forward connection + DTR_interface #( + .IDSize(IDSize), + .InternalRedundancy(InternalRedundancy) + ) dtr_connection (); + + // Feedback connection + retry_interface #( + .IDSize(IDSize-1) + ) retry_connection (); + + // Connection between retry and DMR + tmr_stacked_t data_retry2dmr; + id_t id_retry2dmr; + logic valid_retry2dmr; + logic ready_retry2dmr; + + // Input connection + tmr_stacked_t in_tmr_stack; + assign in_tmr_stack.data = data_in; + assign in_tmr_stack.operation = operation_in; + + // DUT Instances + retry_start #( + .DataType(tmr_stacked_t), + .IDSize(IDSize-1) + ) i_retry_start ( + .clk_i(clk), + .rst_ni(rst_n), + + // Upstream connection + .data_i(in_tmr_stack), + .valid_i(valid_in), + .ready_o(ready_in), + + // Downstream connection + .data_o(data_retry2dmr), + .id_o(id_retry2dmr), + .valid_o(valid_retry2dmr), + .ready_i(ready_retry2dmr), + + // Retry Connection + .retry(retry_connection) + ); + + + DTR_start #( + .DataType(tmr_stacked_t), + .IDSize (IDSize), + .EarlyReadyEnable(EarlyReadyEnable), + .UseExternalId(1), + .InternalRedundancy(InternalRedundancy) + ) i_DTR_start ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + .dtr_interface(dtr_connection), + + // Upstream connection + .data_i(data_retry2dmr), + .id_i(id_retry2dmr), + .valid_i(valid_retry2dmr), + .ready_o(ready_retry2dmr), + + // Downstream connection + .data_o(in_tmr_stack_redundant), + .id_o (in_id_redundant), + .valid_o(in_valid_redundant), + .ready_i(in_ready_redundant) + ); + + // Handshake signal array for opgroup block + logic [NumOpgroups-1:0] in_opgrp_ready, out_opgrp_valid, out_opgrp_ready; + rr_stacked_t [NumOpgroups-1:0] out_opgrp_rr_stack; + rr_stacked_t out_rr_stack; + + // Pass ready up based on the current operation_i + assign in_ready_redundant = in_valid_redundant & in_opgrp_ready[in_tmr_stack_redundant.operation]; + + for (genvar opgrp = 0; opgrp < int'(NumOpgroups); opgrp++) begin : gen_operation_groups + localparam NUM_REGS = OpgroupNumRegs[opgrp]; + + // Input pipeline signals, index i holds signal after i register stages + tagged_data_t [0:NUM_REGS] pipe_data; + logic [0:NUM_REGS] pipe_valid; + logic [0:NUM_REGS] pipe_ready; + id_parity_t [0:NUM_REGS] pipe_id; + + // Upstream Connection + // Error Injection + assign pipe_valid[0] = ((in_valid_redundant & stall) ^ valid_fault) && (opgrp == in_tmr_stack_redundant.operation); + assign pipe_data[0] = in_tmr_stack_redundant.data ^ data_fault; + assign pipe_id[0] = in_id_redundant ^ id_fault; + assign in_opgrp_ready[opgrp] = (pipe_ready[0] & stall) ^ ready_fault; + + // Generate the register stages + for (genvar i = 0; i < NUM_REGS; i++) begin : gen_pipeline + // Internal register enable for this stage + logic reg_ena; + + // Determine the ready signal of the current stage - advance the pipeline: + // 1. if the next stage is ready for our data + // 2. if the next stage only holds a bubble (not valid) -> we can pop it + assign pipe_ready[i] = pipe_ready[i+1] | ~pipe_valid[i+1]; + + // Valid: enabled by ready signal, synchronous clear with the flush signal + `FFLARNC(pipe_valid[i+1], pipe_valid[i], pipe_ready[i], 1'b0, 1'b0, clk, rst_n) + // Enable register if pipleine ready and a valid data item is present + assign reg_ena = (pipe_ready[i] & pipe_valid[i]); // | reg_ena_i[i]; + // Generate the pipeline registers within the stages, use enable-registers + `FFLARN(pipe_data[i+1], pipe_data[i], reg_ena, tagged_data_t'('0), clk, rst_n) + `FFLARN( pipe_id[i+1], pipe_id[i], reg_ena, id_parity_t'('0), clk, rst_n) + end + + // Downstream connection + assign out_opgrp_valid[opgrp] = pipe_valid[NUM_REGS]; + assign out_opgrp_rr_stack[opgrp].data = pipe_data[NUM_REGS]; + assign out_opgrp_rr_stack[opgrp].id = pipe_id[NUM_REGS]; + assign pipe_ready[NUM_REGS] = out_opgrp_ready[opgrp]; + end + + // Signals for after RR + logic out_tmr_valid, out_tmr_ready; + tmr_stacked_t out_tmr_stack; + + // Backpropagating lock signal + logic [REP-1:0] lock; + + // Round-Robin arbiter to decide which result to use + rr_arb_tree_lock #( + .NumIn ( NumOpgroups ), + .DataType ( rr_stacked_t ), + .AxiVldRdy ( 1'b1 ), + .InternalRedundancy ( InternalRedundancy ) + ) i_arbiter ( + .clk_i(clk), + .rst_ni(rst_n), + .flush_i('0), + .rr_i ('0), + .lock_rr_i (lock), + + // Upstream connection + .req_i(out_opgrp_valid), + .gnt_o(out_opgrp_ready), + .data_i(out_opgrp_rr_stack), + + // Downstream connection + .gnt_i(out_tmr_ready), + .req_o(out_tmr_valid), + .data_o(out_rr_stack), + .idx_o(out_tmr_stack.operation) + ); + + + // Signals for after TMR + tmr_stacked_t out_stacked; + logic [IDSize-1:0] out_tmr_id; + + assign out_tmr_id = out_rr_stack.id; + assign out_tmr_stack.data = out_rr_stack.data; + + // Connection between retry and DMR + tmr_stacked_t data_dmr2retry; + id_t id_dmr2retry; + logic needs_retry_dmr2retry; + logic valid_dmr2retry; + logic ready_dmr2retry; + + DTR_end #( + .DataType(tmr_stacked_t), + .LockTimeout(LockTimeout), + .IDSize (IDSize), + .InternalRedundancy(InternalRedundancy) + ) i_DTR_end ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + .dtr_interface(dtr_connection), + + // Upstream connection + .data_i(out_tmr_stack), + .id_i (out_tmr_id), + .valid_i(out_tmr_valid), + .ready_o(out_tmr_ready), + + // Lock connection to upstream + .lock_o(lock), + + // Downstream connection + .data_o(data_dmr2retry), + .id_o(id_dmr2retry), + .needs_retry_o(needs_retry_dmr2retry), + .valid_o(valid_dmr2retry), + .ready_i(ready_dmr2retry), + + // Flag output + .fault_detected_o(/*Unused*/) + ); + + retry_end #( + .DataType(tmr_stacked_t), + .IDSize(IDSize-1) + ) i_retry_end ( + .clk_i(clk), + .rst_ni(rst_n), + + // Upstream connection + .data_i(data_dmr2retry), + .id_i(id_dmr2retry), + .needs_retry_i(needs_retry_dmr2retry), + .valid_i(valid_dmr2retry), + .ready_o(ready_dmr2retry), + + // Downstream connection + .data_o(out_stacked), + .valid_o(valid_out), + .ready_i(ready_out), + + // Retry Connection + .retry(retry_connection) + ); + + assign data_out = out_stacked.data; + assign operation_out = out_stacked.operation; + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Input + //////////////////////////////////////////////////////////////////////////////////7 + tagged_data_t golden_queue [NumOpgroups-1:0][$]; + tag_t tag_new; + + initial begin + tag_new = 0; + forever begin + input_handshake_begin(); + tag_new += 1; + operation_in = $urandom_range(0, NumOpgroups-1);; + data_in.data = $random; + data_in.tag = tag_new; + golden_queue[operation_in].push_back(data_in); + input_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Output + //////////////////////////////////////////////////////////////////////////////////7 + tagged_data_t data_golden, data_actual; + operation_t operation_actual; + logic error; // Helper signal so one can quickly scroll to errors in questa + logic found; + longint unsigned error_cnt = 0; + + // Progress reporting + task reset_metrics(); + reset(); + error_cnt = 0; + in_hs_count = 0; + out_hs_count = 0; + for (int i = 0; i < NumOpgroups; i++) begin + golden_queue[i].delete(); + end + endtask + + initial begin + $timeformat(-9, 0, " ns", 20); + forever begin + output_handshake_start(); + data_actual = data_out; + operation_actual = operation_out; + if (golden_queue[operation_actual].size() > 0) begin + + // Try to find an element with matching tag and consume it + found = 0; + repeat (golden_queue[operation_actual].size()) begin + data_golden = golden_queue[operation_actual].pop_front(); + if (data_golden.tag == data_actual.tag) begin + found = 1; + + // Check output + if (data_actual.data != data_golden.data) begin + $error("[T=%t] Mismatch: Golden: %h, Actual: %h", $time, data_golden, data_actual); + error = 1; + error_cnt += 1; + end else begin + error = 0; + break; + end + + end else begin + golden_queue[operation_actual].push_back(data_golden); + end + end + + // In case not tag matched error + if (found == 0) begin + $display("[T=%t] Tag %h Data %h Output but was not in golden queue ", $time, data_actual.tag, data_actual.data); + error = 1; + error_cnt += 1; + end + end else begin + $display("[T=%t] Operation %h -> Data %h Output when nothing was in golden queue", $time, operation_actual, data_actual); + error = 1; + error_cnt += 1; + end + + + // Check that no queue runs out of bounds + for (int operation = 0; operation < NumOpgroups; operation++) begin + if (golden_queue[operation].size() > 2 ** IDSize) begin + $display("[T=%t] Data does not get output in a timely manner for operation %d! (Q %d)", $time, operation, golden_queue[operation].size()); + error = 1; + error_cnt += 1; + end + end + output_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Fault Injection + //////////////////////////////////////////////////////////////////////////////////7 + + longint unsigned min_fault_delay = (12 + 5) * 5; + longint unsigned max_fault_delay = (12 + 5) * 5 + 20; + + // Signals to show what faults are going on + enum {NONE, DATA_FAULT, VALID_FAULT, READY_FAULT, ID_FAULT} fault_type, fault_current; + + initial data_fault = '0; + initial valid_fault = '0; + initial ready_fault = '0; + initial id_fault = '0; + + task inject_fault(); + // Send correct data for some cycles to space errors + repeat ($urandom_range(min_fault_delay, max_fault_delay)) begin + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + end + + // Send wrong data + fault_current = fault_type; + case (fault_type) + DATA_FAULT: data_fault = $random; + VALID_FAULT: valid_fault = 1; + READY_FAULT: ready_fault = 1; + ID_FAULT: id_fault = (1 << $urandom_range(0,IDSize-1)); + endcase + + // Send correct data again + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + endtask + + //////////////////////////////////////////////////////////////////////////////////7 + // Main Loop + //////////////////////////////////////////////////////////////////////////////////7 + longint unsigned total_error_cnt = 0; + + initial begin + reset_metrics(); + + // Check normal operation + fault_type = NONE; + enable = 0; + repeat (10 * TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc disabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + // Check fault tolerance + fault_type = DATA_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and data faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = VALID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and valid fault, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = READY_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and ready faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = ID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and ID faults, got %d errors.", error_cnt); + reset_metrics(); + + // Measure throughput + fault_type = NONE; + enable = 0; + in_hs_max_starvation = 0; + out_hs_max_starvation = 0; + internal_hs_max_starvation = 0; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc disabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS, error_cnt); + if (TESTS - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc disabled!"); + end + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS/2, error_cnt); + if (TESTS/2 - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc enabled!"); + end + reset_metrics(); + $display("Checked %0d tests of each type, found %0d mismatches.", TESTS, total_error_cnt); + $finish(error_cnt); + end + + +endmodule diff --git a/test/tb_redundancy_controller.sv b/test/tb_redundancy_controller.sv new file mode 100644 index 00000000..7da21448 --- /dev/null +++ b/test/tb_redundancy_controller.sv @@ -0,0 +1,95 @@ +module tb_redundancy_controller #( + // DUT Parameters + parameter bit InternalRedundancy = 0, + + // TB Parameters + parameter int unsigned TESTS = 10, + parameter time CLK_PERIOD = 10ns, + parameter time APPLICATION_DELAY = 2ns, + parameter time AQUISITION_DELAY = 8ns + +) ( /* no ports on TB */ ); + + // Signals for dut + logic clk_i; + logic rst_ni; + + // Connection to outside + logic enable_i; + logic busy_o; + + // Connection to redundancy modules + logic busy_i; + logic enable_o; + + // Upstream Handshake + logic valid_i; + logic ready_o; + + // Downstream Handshake + logic valid_o; + logic ready_i; + + // Dut + redundancy_controller # ( + .InternalRedundancy(InternalRedundancy) + ) i_dut ( + .* + ); + + initial clk_i = 0; + initial rst_ni = 1; + always #((CLK_PERIOD/2)) clk_i = ~clk_i; + + task check_handshake_passthrough(); + valid_i = $random; + ready_i = $random; + # (AQUISITION_DELAY - APPLICATION_DELAY); + assert(valid_o == valid_i); + assert(ready_o == ready_o); + assert(enable_o == enable_i); + endtask + + task check_handshake_blocked(); + valid_i = $random; + ready_i = $random; + # (AQUISITION_DELAY - APPLICATION_DELAY); + assert(valid_o == 0); + assert(ready_o == 0); + assert(enable_o != enable_i); + endtask + + + initial begin + // Start state + enable_i = 0; + busy_i = 0; + + repeat (TESTS) begin + @(posedge clk_i); + # APPLICATION_DELAY; + busy_i = 1; + enable_i = ~enable_i; + + repeat ($urandom_range(0, 5)) begin + check_handshake_blocked(); + @(posedge clk_i); + #APPLICATION_DELAY; + end + + @(posedge clk_i); + # APPLICATION_DELAY; + busy_i = 0; + check_handshake_blocked(); + + repeat (5) begin + @(posedge clk_i); + #APPLICATION_DELAY; + check_handshake_passthrough(); + end + + end + $finish; + end + +endmodule \ No newline at end of file diff --git a/test/tb_retry.sv b/test/tb_retry.sv new file mode 100644 index 00000000..7a642c7d --- /dev/null +++ b/test/tb_retry.sv @@ -0,0 +1,201 @@ +module tb_retry; + + // Clock Parameters + localparam time CLK_PERIOD = 10ns; + localparam time APPLICATION_DELAY = 2ns; + localparam time AQUISITION_DELAY = 8ns; + localparam unsigned RST_CLK_CYCLES = 10; + localparam unsigned TESTS = 10000; + + // Parameters + typedef logic [7:0] data_t; + parameter IDSize = 2; + + // Testbench signals + data_t golden_queue [$]; + data_t data_golden, data_actual; + logic error; + int error_cnt; + + // Signals for DUTS + logic clk; + logic rst_n; + + data_t data_in, data_middle, data_out; + logic valid_in, valid_middle, valid_out; + logic ready_in, ready_middle, ready_out; + logic [IDSize-1:0] id_middle; + logic needs_retry_middle; + + retry_interface #(.IDSize(IDSize)) retry_connection (); + + // Clock Generation + initial begin + clk = '1; + rst_n = '0; + repeat (10) @(posedge clk); + rst_n = 1; + end + + always #((CLK_PERIOD/2)) clk = ~clk; + + // DUT Instances + retry_start #( + .DataType(data_t), + .IDSize(IDSize) + ) dut_start ( + .clk_i(clk), + .rst_ni(rst_n), + + // Upstream connection + .data_i(data_in), + .valid_i(valid_in), + .ready_o(ready_in), + + // Downstream connection + .data_o(data_middle), + .id_o(id_middle), + .valid_o(valid_middle), + .ready_i(ready_middle), + + // Retry Connection + .retry (retry_connection) + ); + + + retry_end #( + .DataType(data_t), + .IDSize(IDSize) + ) dut_end ( + .clk_i(clk), + .rst_ni(rst_n), + + // Upstream connection + .data_i(data_middle), + .id_i(id_middle), + .needs_retry_i(needs_retry_middle), + .valid_i(valid_middle), + .ready_o(ready_middle), + + // Downstream connection + .data_o(data_out), + .valid_o(valid_out), + .ready_i(ready_out), + + // Retry Connection + .retry (retry_connection) + ); + + // Data Application + initial begin + // Initialize Handshake and Data + data_in = 8'h00; + valid_in = 1'b0; + + // Wait for reset to be lifted + @(posedge rst_n); + + forever begin + // Wait random time (with no valid data) + repeat ($urandom_range(1, 5)) begin + @(posedge clk); + # APPLICATION_DELAY; + valid_in <= '0; + end + + valid_in <= '1; + + data_in = $random; + golden_queue.push_back(data_in); + + // Wait for handshake and as soon as it happens invalidate data + # (AQUISITION_DELAY - APPLICATION_DELAY); + while (!ready_in) begin + @(posedge clk); + # AQUISITION_DELAY; + end; + + end + end + + // Drive Fault signal + initial begin + repeat (TESTS) begin + + // Send correct data for some cycles to space errors + repeat ($urandom_range(15, 20)) begin + @(posedge clk); + # (APPLICATION_DELAY); + needs_retry_middle = '0; + end + + @(posedge clk); + # (APPLICATION_DELAY); + needs_retry_middle = '1; + + // Wait for handshake + # (AQUISITION_DELAY - APPLICATION_DELAY); + while (!(ready_middle & valid_middle)) begin + @(posedge clk); + # AQUISITION_DELAY; + end + end + + $display("Checked %0d tests of each type, found %0d mismatches.", TESTS, error_cnt); + $finish(0); + end + + + // Aquisition & Validation + initial begin + $timeformat(-9, 0, " ns", 20); + + // Initialize error metrics + error = 0; // Signal so errors can easily be scrolled to in wave + error_cnt = 0; + + // Initialize Handshake + ready_out = '0; + + // Wait for reset to be lifted + @(posedge rst_n); + + forever begin + // Wait random time (while not ready) + repeat ($urandom_range(1, 5)) begin + @(posedge clk); + # APPLICATION_DELAY; + ready_out <= '0; + end + + // Set ready + ready_out <= '1; + + // Wait for handshake + # (AQUISITION_DELAY - APPLICATION_DELAY); + while (!valid_out) begin + @(posedge clk); + # AQUISITION_DELAY; + end; + + // Once it happened check if output was good and reset ready again + data_actual = data_out; + + if (golden_queue.size() > 0) begin + data_golden = golden_queue.pop_front(); + if (data_actual != data_golden) begin + $error("[T=%t] Mismatch: Golden: %h, Actual: %h", $time, data_golden, data_actual); + error = 1; + error_cnt += 1; + end else begin + error = 0; + end + end else begin + $display("[T=%t] Data %h Output when nothing was in golden queue", $time, data_actual); + error = 1; + error_cnt += 1; + end + end + end + +endmodule diff --git a/test/tb_retry_inorder.sv b/test/tb_retry_inorder.sv new file mode 100644 index 00000000..ca93ffce --- /dev/null +++ b/test/tb_retry_inorder.sv @@ -0,0 +1,226 @@ +`include "common_cells/registers.svh" + +module tb_retry_inorder; + + // Clock Parameters + localparam time CLK_PERIOD = 10ns; + localparam time APPLICATION_DELAY = 2ns; + localparam time AQUISITION_DELAY = 8ns; + localparam unsigned RST_CLK_CYCLES = 10; + localparam unsigned TESTS = 10000; + + // Parameters + typedef logic [7:0] data_t; + parameter IDSize = 4; + localparam int unsigned NUM_REGS = 4; + + // Testbench signals + data_t golden_queue [$]; + data_t data_golden, data_actual; + logic error; + int error_cnt; + + // Signals for DUTS + logic clk; + logic rst_n; + + data_t data_in, data_out; + logic valid_in, valid_out; + logic ready_in, ready_out; + logic needs_retry_middle; + + retry_interface #(.IDSize(IDSize)) retry_connection (); + + data_t [0:NUM_REGS] data_middle; + logic [0:NUM_REGS] valid_middle; + logic [0:NUM_REGS] ready_middle; + logic [0:NUM_REGS][IDSize-1:0] id_middle; + + // Clock Generation + initial begin + clk = '1; + rst_n = '0; + repeat (10) @(posedge clk); + rst_n = 1; + end + + always #((CLK_PERIOD/2)) clk = ~clk; + + // DUT Instances + retry_inorder_start #( + .DataType(data_t), + .IDSize(IDSize) + ) dut_start ( + .clk_i(clk), + .rst_ni(rst_n), + + // Upstream connection + .data_i(data_in), + .valid_i(valid_in), + .ready_o(ready_in), + + // Downstream connection + .data_o(data_middle[0]), + .id_o(id_middle[0]), + .valid_o(valid_middle[0]), + .ready_i(ready_middle[0]), + + // Retry Connection + .retry(retry_connection) + ); + + // Generate the register stages + for (genvar i = 0; i < NUM_REGS; i++) begin : gen_pipeline + // Internal register enable for this stage + logic reg_ena; + + // Determine the ready signal of the current stage - advance the pipeline: + // 1. if the next stage is ready for our data + // 2. if the next stage only holds a bubble (not valid) -> we can pop it + assign ready_middle[i] = ready_middle[i+1] | ~valid_middle[i+1]; + + // Valid: enabled by ready signal, synchronous clear with the flush signal + `FFLARNC(valid_middle[i+1], valid_middle[i], ready_middle[i], 1'b0, 1'b0, clk, rst_n) + // Enable register if pipleine ready and a valid data item is present + assign reg_ena = (ready_middle[i] & valid_middle[i]); // | reg_ena_i[i]; + // Generate the pipeline registers within the stages, use enable-registers + `FFLARN(data_middle[i+1], data_middle[i], reg_ena, '0, clk, rst_n) + `FFLARN( id_middle[i+1], id_middle[i], reg_ena, '0, clk, rst_n) + end + + retry_inorder_end #( + .DataType(data_t), + .IDSize(IDSize) + ) dut_end ( + .clk_i(clk), + .rst_ni(rst_n), + + // Upstream connection + .data_i(data_middle[NUM_REGS]), + .id_i(id_middle[NUM_REGS]), + .needs_retry_i(needs_retry_middle), + .valid_i(valid_middle[NUM_REGS]), + .ready_o(ready_middle[NUM_REGS]), + + // Downstream connection + .data_o(data_out), + .valid_o(valid_out), + .ready_i(ready_out), + + // Retry Connection + .retry(retry_connection) + ); + + // Data Application + initial begin + // Initialize Handshake and Data + data_in = 8'h00; + valid_in = 1'b0; + + // Wait for reset to be lifted + @(posedge rst_n); + + forever begin + // Wait random time (with no valid data) + repeat ($urandom_range(1, 5)) begin + @(posedge clk); + # APPLICATION_DELAY; + valid_in <= '0; + end + + valid_in <= '1; + + data_in = $random; + golden_queue.push_back(data_in); + + // Wait for handshake and as soon as it happens invalidate data + # (AQUISITION_DELAY - APPLICATION_DELAY); + while (!ready_in) begin + @(posedge clk); + # AQUISITION_DELAY; + end; + + end + end + + // Drive Fault signal + initial begin + repeat (TESTS) begin + + // Send correct data for some cycles to space errors + repeat ($urandom_range(15, 20)) begin + @(posedge clk); + # (APPLICATION_DELAY); + needs_retry_middle = '0; + end + + @(posedge clk); + # (APPLICATION_DELAY); + needs_retry_middle = '1; + + // Wait for handshake + # (AQUISITION_DELAY - APPLICATION_DELAY); + while (!(ready_middle[NUM_REGS] & valid_middle[NUM_REGS])) begin + @(posedge clk); + # AQUISITION_DELAY; + end + end + + $display("Checked %0d tests of each type, found %0d mismatches.", TESTS, error_cnt); + $finish(0); + end + + + // Aquisition & Validation + initial begin + $timeformat(-9, 0, " ns", 20); + + // Initialize error metrics + error = 0; // Signal so errors can easily be scrolled to in wave + error_cnt = 0; + + // Initialize Handshake + ready_out = '0; + + // Wait for reset to be lifted + @(posedge rst_n); + + forever begin + // Wait random time (while not ready) + repeat ($urandom_range(1, 5)) begin + @(posedge clk); + # APPLICATION_DELAY; + ready_out <= '0; + end + + // Set ready + ready_out <= '1; + + // Wait for handshake + # (AQUISITION_DELAY - APPLICATION_DELAY); + while (!valid_out) begin + @(posedge clk); + # AQUISITION_DELAY; + end; + + // Once it happened check if output was good and reset ready again + data_actual = data_out; + + if (golden_queue.size() > 0) begin + data_golden = golden_queue.pop_front(); + if (data_actual != data_golden) begin + $error("[T=%t] Mismatch: Golden: %h, Actual: %h", $time, data_golden, data_actual); + error = 1; + error_cnt += 1; + end else begin + error = 0; + end + end else begin + $display("[T=%t] Data %h Output when nothing was in golden queue", $time, data_actual); + error = 1; + error_cnt += 1; + end + end + end + +endmodule diff --git a/test/tb_rr_arb_tree_lock.sv b/test/tb_rr_arb_tree_lock.sv new file mode 100644 index 00000000..7efa92cf --- /dev/null +++ b/test/tb_rr_arb_tree_lock.sv @@ -0,0 +1,281 @@ +// Copyright 2020 ETH Zurich and University of Bologna. +// Copyright and related rights are licensed under the Solderpad Hardware +// License, Version 0.51 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law +// or agreed to in writing, software, hardware and materials distributed under +// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Author: Wolfgang Roennigner + +/// Testbench for the module `rr_arb_tree` +module tb_rr_arb_tree_lock #( + /// Number of input streams to the DUT + parameter int unsigned NumInp = 32'd7, + /// Number of requests per input + parameter int unsigned NumReqs = 32'd20000, + /// Handshaking as in AXI + parameter bit AxiVldRdy = 1'b1, + /// Do not deassert the input request + parameter bit LockIn = 1'b1, + /// Enable fair arbitration, when disabled, no assertion for fairness + parameter bit FairArb = 1'b1 +); + + localparam time CyclTime = 10ns; + localparam time ApplTime = 2ns; + localparam time TestTime = 8ns; + + // throw an error if the measured throughput and expected throughput differ + // more than this value + localparam real error_threshold = 0.1; + + localparam int unsigned IdxWidth = (NumInp > 32'd1) ? unsigned'($clog2(NumInp)) : 32'd1; + localparam int unsigned DataWidth = 32'd45; + typedef logic [IdxWidth-1:0] idx_t; + typedef logic [DataWidth-1:0] data_t; + + // clock signal + logic clk; + logic rst_n; + logic flush; + idx_t rr, idx; + logic [NumInp-1:0] req_inp, gnt_inp, end_of_sim; + data_t [NumInp-1:0] data_inp; + logic req_oup, gnt_oup; + logic lock; + data_t data_oup; + + // clock and rst gen + clk_rst_gen #( + .ClkPeriod ( CyclTime ), + .RstClkCycles ( 5 ) + ) i_clk_rst_gen ( + .clk_o ( clk ), + .rst_no ( rst_n ) + ); + + // drive input streams + // Can not use `stream_driver`, as this also tests for requests taken away. + for (genvar i = 0; i < NumInp; i++) begin : gen_stream_gen + initial begin : proc_stream_gen + automatic data_t stimuli; + automatic int unsigned rand_wait; + + end_of_sim[i] = 1'b0; + @(posedge rst_n); + data_inp[i] = '0; + req_inp[i] = 1'b0; + + // First have them continuously active for a number of time + for (int unsigned j = 0; j < (i+1) * NumReqs; j++) begin + data_inp[i] <= #ApplTime data_t'(i); + req_inp[i] <= #ApplTime 1'b1; + @(posedge clk); + end + data_inp[i] <= #ApplTime '0; + req_inp[i] <= #ApplTime 1'b0; + + // wait until all other processes have their fixed request finished. + repeat ((NumInp-i)*NumReqs) @(posedge clk); + repeat (1000) @(posedge clk); + + // First have them continuously active for a number of time + for (int unsigned j = 0; j < (NumInp-i) * NumReqs; j++) begin + data_inp[i] <= #ApplTime data_t'(i); + req_inp[i] <= #ApplTime 1'b1; + @(posedge clk); + end + data_inp[i] <= #ApplTime '0; + req_inp[i] <= #ApplTime 1'b0; + + repeat ((1+i)*NumReqs) @(posedge clk); + repeat (1000) @(posedge clk); + + // First have them continuously active for a number of time + for (int unsigned j = 0; j < (NumInp-i) * NumReqs; j++) begin + if ((i % 2) == 0) begin + data_inp[i] <= #ApplTime data_t'(i); + req_inp[i] <= #ApplTime 1'b1; + end + @(posedge clk); + end + data_inp[i] <= #ApplTime '0; + req_inp[i] <= #ApplTime 1'b0; + + repeat ((1+i)*NumReqs) @(posedge clk); + repeat (1000) @(posedge clk); + + + // have random stimuli + for (int unsigned j = 0; j < NumReqs; j++) begin + data_inp[i] <= #ApplTime new_stimuli(); + req_inp[i] <= #ApplTime 1'b1; + rand_wait = $urandom_range(1, 2*NumInp); + if (LockIn) begin + #TestTime; + while (!gnt_inp[i]) begin + @(posedge clk); + #TestTime; + end + end else begin + repeat (rand_wait) @(posedge clk); + end + @(posedge clk); + data_inp[i] <= #ApplTime '0; + req_inp[i] <= #ApplTime 1'b0; + + rand_wait = $urandom_range(0, NumInp); + repeat (rand_wait) @(posedge clk); + end + + end_of_sim[i] = 1'b1; + end + end + + function data_t new_stimuli(); + for (int unsigned i = 0; i < DataWidth; i++) begin + new_stimuli[i] = $urandom(); + end + endfunction : new_stimuli + + // consume streams + initial begin : proc_stream_consume + @(posedge rst_n); + gnt_oup = 1'b0; + forever begin + gnt_oup <= #ApplTime 1'b1; + @(posedge clk); + end + end + + // set external lock every few cycles + initial begin : proc_generate_lock + automatic int unsigned rand_wait; + @(posedge rst_n); + lock = 1'b0; + forever begin + rand_wait = $urandom_range(4, 10); + repeat (rand_wait) @(posedge clk); + lock <= #ApplTime 1'b1; + @(posedge clk); + lock <= #ApplTime 1'b0; + end + end + + // flush sometimes + initial begin : proc_flush + automatic int unsigned rand_wait; + flush = 1'b0; + @(posedge rst_n); + forever begin + rand_wait = $urandom_range(2000, 20000); + repeat (rand_wait) @(posedge clk); + flush <= #ApplTime 1'b1; + @(posedge clk); + flush <= #ApplTime 1'b0; + end + end + + // end simulation + initial begin : proc_sim_end + @(posedge rst_n); + wait (&end_of_sim); + repeat (10) @(posedge clk); + $stop(); + end + + // Check throughput + for (genvar i = 0; i < NumInp; i++) begin : gen_throughput_checker + initial begin : proc_throughput_checker + automatic longint unsigned tot_active [NumInp:1]; + automatic longint unsigned tot_served [NumInp:1]; + automatic int unsigned num_active; + automatic real throughput, exp_through, error; + for (int unsigned j = 0; j < NumInp; j++) begin + tot_active[j] = 0; + tot_served[j] = 0; + end + + @(posedge rst_n); + while (!(&end_of_sim)) begin + #TestTime; + + if (req_inp[i] && gnt_oup) begin + num_active = 0; + for (int unsigned j = 0; j < NumInp; j++) begin + if (req_inp[j]) begin + num_active++; + end + end + + tot_active[num_active] = tot_active[num_active] + 1; + if (gnt_inp[i]) begin + tot_served[num_active] = tot_served[num_active] + 1; + end + end + @(posedge clk); + end + + for (int unsigned j = 1; j <= NumInp; j++) begin + if (tot_active[j] > 0) begin + throughput = real'(tot_served[j])/real'(tot_active[j]); + exp_through = real'(1)/real'(j); + error = throughput - exp_through; + if (FairArb && LockIn) begin + assert(error < error_threshold && error > -error_threshold) else + $warning("Line: %0d is unfair!", i); + end + $display("Line: %0d, TotActice: %0d Throughput: %0f Ideal: %0f Diff: %0f", + i, j, throughput, exp_through, error); end + end + end + end + + // check data + initial begin : proc_check_data + automatic data_t data_queues[NumInp-1:0][$]; + automatic data_t exp_data; + forever begin + @(posedge clk); + #TestTime; + // Put exp data into the queues + for (int unsigned i = 0; i < NumInp; i++) begin + if (req_inp[i] && gnt_inp[i]) begin + data_queues[i].push_back(data_inp[i]); + end + end + + // check that the right data is observed + if (req_oup && gnt_oup) begin + exp_data = data_queues[idx].pop_front(); + assert(exp_data === data_oup); + end + end + end + + // DUT + rr_arb_tree_lock #( + .NumIn ( NumInp ), + .DataWidth ( DataWidth ), + .ExtPrio ( 1'b0 ), + .AxiVldRdy ( AxiVldRdy ), + .LockIn ( LockIn ), + .FairArb ( FairArb ) + ) i_rr_arb_tree_lock_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .flush_i ( flush ), + .rr_i ( '0 ), + .lock_rr_i ( lock ), + .req_i ( req_inp ), + .gnt_o ( gnt_inp ), + .data_i ( data_inp ), + .gnt_i ( gnt_oup ), + .req_o ( req_oup ), + .data_o ( data_oup ), + .idx_o ( idx ) + ); +endmodule diff --git a/test/tb_time.svh b/test/tb_time.svh new file mode 100644 index 00000000..d6d2e035 --- /dev/null +++ b/test/tb_time.svh @@ -0,0 +1,122 @@ +//////////////////////////////////////////////////////////////////////////////////7 +// Signal Definitions +//////////////////////////////////////////////////////////////////////////////////7 + +logic clk, rst_n; +logic valid_in, ready_in; +logic valid_out, ready_out; +logic enable; +logic stall; + +//////////////////////////////////////////////////////////////////////////////////7 +// Initial State +//////////////////////////////////////////////////////////////////////////////////7 +initial clk = 1'b0; +initial rst_n = 1'b1; +initial valid_in = 1'b0; +initial ready_out = 1'b0; +initial enable = 1'b0; + +//////////////////////////////////////////////////////////////////////////////////7 +// Clock Setup +//////////////////////////////////////////////////////////////////////////////////7 +longint unsigned reset_length = 20; + +always #((CLK_PERIOD/2)) clk = ~clk; + +task reset(); + rst_n = 1'b0; + repeat (reset_length) @(negedge clk); + rst_n = 1'b1; +endtask + +//////////////////////////////////////////////////////////////////////////////////7 +// Handshake setup +//////////////////////////////////////////////////////////////////////////////////7 +longint unsigned in_hs_count = 0; +longint unsigned in_hs_max_starvation = 5; + +longint unsigned out_hs_count = 0; +longint unsigned out_hs_max_starvation = 5; + +task input_handshake_begin(); + // Wait random time (with no valid data) + repeat ($urandom_range(0, in_hs_max_starvation)) begin + @(posedge clk); + #APPLICATION_DELAY; + end + + // Wait for reset to pass + while (!rst_n) begin + @(posedge clk) ; + #APPLICATION_DELAY; + end +endtask + +task input_handshake_end(); + // Perform Handshake + valid_in = 1'b1; + + # (AQUISITION_DELAY - APPLICATION_DELAY); + while (!ready_in & rst_n) begin + @(posedge clk); + # AQUISITION_DELAY; + end + @(posedge clk); + in_hs_count = in_hs_count + 1; + + # APPLICATION_DELAY; + valid_in = 1'b0; // This might get overwritten by next handshake +endtask + +task output_handshake_start(); + // Wait random time (with no valid data) + repeat ($urandom_range(0, out_hs_max_starvation)) begin + @(posedge clk); + # APPLICATION_DELAY; + end + + // Wait for reset to pass + while (!rst_n) begin + @(posedge clk); + # APPLICATION_DELAY; + end + + ready_out = 1'b1; + + // Wait for correct amount of time in cycle + # (AQUISITION_DELAY - APPLICATION_DELAY); + while (!valid_out | !rst_n) begin + @(posedge clk); + #AQUISITION_DELAY; + end +endtask + +task output_handshake_end(); + @(posedge clk); + out_hs_count = out_hs_count + 1; + + # APPLICATION_DELAY; + ready_out = 1'b0; // This might get overwritten by next handshake +endtask + +//////////////////////////////////////////////////////////////////////////////////7 +// Internal Stall +//////////////////////////////////////////////////////////////////////////////////7 + +longint unsigned internal_hs_max_starvation = 12; + +initial begin + forever begin + // Wait random time (with pipeline stalled) + repeat ($urandom_range(0, internal_hs_max_starvation)) begin + @(posedge clk); + #APPLICATION_DELAY; + stall = 0; + end + + @(posedge clk) ; + #APPLICATION_DELAY; + stall = 1; + end +end diff --git a/test/tb_ttr.sv b/test/tb_ttr.sv new file mode 100644 index 00000000..fc2bc610 --- /dev/null +++ b/test/tb_ttr.sv @@ -0,0 +1,263 @@ +module tb_ttr #( + // DUT Parameters + parameter IDSize = 4, + parameter int LockTimeout = 4 * 12, + parameter bit EarlyValidEnable = 0, + parameter bit EarlyReadyEnable = 0, + parameter bit InternalRedundancy = 0, + + // TB Parameters + parameter int unsigned TESTS = 10000, + parameter time CLK_PERIOD = 10ns, + parameter time APPLICATION_DELAY = 2ns, + parameter time AQUISITION_DELAY = 8ns + +) ( /* no ports on TB */ ); + + `include "tb_time.svh" + + //////////////////////////////////////////////////////////////////////////////////7 + // DUT (s) + //////////////////////////////////////////////////////////////////////////////////7 + + typedef logic [7:0] data_t; + + // Input & Output + data_t data_in, data_out; + + // Internal Connections + data_t data_redundant, data_fault, data_redundant_faulty; + logic valid_redundant, valid_fault, valid_redundant_faulty; + logic ready_redundant, ready_fault, ready_redundant_faulty; + logic [IDSize-1:0] id_redundant, id_fault, id_redundant_faulty; + + TTR_start #( + .DataType(data_t), + .IDSize(IDSize), + .EarlyReadyEnable(EarlyReadyEnable), + .InternalRedundancy(InternalRedundancy) + ) dut_start ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + // Upstream connection + .data_i(data_in), + .valid_i(valid_in), + .ready_o(ready_in), + + // Downstream connection + .data_o(data_redundant), + .id_o(id_redundant), + .valid_o(valid_redundant), + .ready_i(ready_redundant_faulty) + ); + + TTR_end #( + .DataType(data_t), + .LockTimeout(LockTimeout), + .IDSize(IDSize), + .EarlyValidEnable(EarlyValidEnable), + .InternalRedundancy(InternalRedundancy) + ) dut_end ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + // Upstream connection + .data_i(data_redundant_faulty), + .id_i(id_redundant_faulty), + .valid_i(valid_redundant_faulty), + .ready_o(ready_redundant), + + // Downstream connection + .data_o(data_out), + .valid_o(valid_out), + .ready_i(ready_out), + .lock_o(/*Unused*/), + + // Flags + .fault_detected_o(/* Unused */) + ); + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Input + //////////////////////////////////////////////////////////////////////////////////7 + data_t golden_queue [$]; + + initial begin + forever begin + input_handshake_begin(); + data_in = $random; + golden_queue.push_back(data_in); + input_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Output + //////////////////////////////////////////////////////////////////////////////////7 + data_t data_golden, data_actual; + logic error; // Helper signal so one can quickly scroll to errors in questa + longint unsigned error_cnt = 0; + + // Progress reporting + task reset_metrics(); + reset(); + error_cnt = 0; + in_hs_count = 0; + out_hs_count = 0; + golden_queue.delete(); + endtask + + initial begin + $timeformat(-9, 0, " ns", 20); + forever begin + output_handshake_start(); + data_actual = data_out; + if (golden_queue.size() > 0) begin + data_golden = golden_queue.pop_front(); + if (data_actual != data_golden) begin + $error("[T=%t] Mismatch: Golden: %h, Actual: %h", $time, data_golden, data_actual); + error = 1; + error_cnt += 1; + end else begin + error = 0; + end + end else begin + $display("[T=%t] Data %h Output when nothing was in golden queue", $time, data_actual); + error = 1; + error_cnt += 1; + end + output_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Fault Injection + //////////////////////////////////////////////////////////////////////////////////7 + + longint unsigned min_fault_delay = 12 * 3; + longint unsigned max_fault_delay = 12 * 3 + 20; + + // Signals to show what faults are going on + enum {NONE, DATA_FAULT, VALID_FAULT, READY_FAULT, ID_FAULT} fault_type, fault_current; + + assign data_redundant_faulty = data_redundant ^ data_fault; + assign valid_redundant_faulty = (valid_redundant & stall) ^ valid_fault; + assign ready_redundant_faulty = (ready_redundant & stall) ^ ready_fault; + assign id_redundant_faulty = id_redundant ^ id_fault; + + initial data_fault = '0; + initial valid_fault = '0; + initial ready_fault = '0; + initial id_fault = '0; + + task inject_fault(); + // Send correct data for some cycles to space errors + repeat ($urandom_range(min_fault_delay, max_fault_delay)) begin + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + end + + // Send wrong data + fault_current = fault_type; + case (fault_type) + DATA_FAULT: data_fault = $random; + VALID_FAULT: valid_fault = 1; + READY_FAULT: ready_fault = 1; + ID_FAULT: id_fault = $random; + endcase + + // Send correct data again + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + endtask + + //////////////////////////////////////////////////////////////////////////////////7 + // Main Loop + //////////////////////////////////////////////////////////////////////////////////7 + longint unsigned total_error_cnt = 0; + + initial begin + reset_metrics(); + + // Check normal operation + fault_type = NONE; + enable = 0; + repeat (10 * TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc disabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + // Check fault tolerance + fault_type = DATA_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and data faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = VALID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and valid fault, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = READY_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and ready faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = ID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and ID faults, got %d errors.", error_cnt); + reset_metrics(); + + // Measure throughput + fault_type = NONE; + enable = 0; + in_hs_max_starvation = 0; + out_hs_max_starvation = 0; + internal_hs_max_starvation = 0; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc disabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS, error_cnt); + if (TESTS - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc disabled!"); + end + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS/3, error_cnt); + if (TESTS / 3 - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc enabled!"); + end + reset_metrics(); + $display("Checked %0d tests of each type, found %0d mismatches.", TESTS, total_error_cnt); + $finish(error_cnt); + end + + +endmodule diff --git a/test/tb_ttr_lock.sv b/test/tb_ttr_lock.sv new file mode 100644 index 00000000..1362c940 --- /dev/null +++ b/test/tb_ttr_lock.sv @@ -0,0 +1,387 @@ +`include "common_cells/registers.svh" + +module tb_ttr_lock #( + // DUT Parameters + parameter int LockTimeout = 5 * 12, + parameter int NumOpgroups = 3, + parameter int OpgroupWidth = $clog2(NumOpgroups), + parameter int IDSize = 5 + $clog2(12), + parameter [NumOpgroups-1:0][7:0] OpgroupNumRegs = {8'd4, 8'd3, 8'd3}, + parameter bit EarlyValidEnable = 0, + parameter bit EarlyReadyEnable = 0, + parameter bit InternalRedundancy = 0, + // Do not modify + localparam int REP = InternalRedundancy ? 3 : 1, + + // TB Parameters + parameter int unsigned TESTS = 10000, + parameter time CLK_PERIOD = 10ns, + parameter time APPLICATION_DELAY = 2ns, + parameter time AQUISITION_DELAY = 8ns +) ( /* no ports on TB */ ); + + `include "tb_time.svh" + + //////////////////////////////////////////////////////////////////////////////////7 + // DUT (s) + //////////////////////////////////////////////////////////////////////////////////7 + + typedef logic [7:0] data_t; + typedef logic [OpgroupWidth-1:0] operation_t; + typedef logic [IDSize-1:0] id_t; + + // Typedef for stacked signal in TMR + typedef struct packed { + data_t data; + operation_t operation; + } tmr_stacked_t; + + // Typedef for stacked signal in TMR + typedef struct packed { + id_t id; + data_t data; + } rr_stacked_t; + + // Input & Output + data_t data_in, data_out; + operation_t operation_in, operation_out; + + // Fault Connections for Injection + data_t data_fault; + logic valid_fault; + logic ready_fault; + id_t id_fault; + + tmr_stacked_t in_tmr_stack; + assign in_tmr_stack.data = data_in; + assign in_tmr_stack.operation = operation_in; + + // Signals for after TMR + tmr_stacked_t in_tmr_stack_redundant; + logic in_valid_redundant, in_ready_redundant; + id_t in_id_redundant; + + TTR_start #( + .DataType(tmr_stacked_t), + .IDSize (IDSize), + .EarlyReadyEnable(EarlyReadyEnable), + .InternalRedundancy(InternalRedundancy) + ) i_TTR_start ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + // Upstream connection + .data_i(in_tmr_stack), + .valid_i(valid_in), + .ready_o(ready_in), + + // Downstream connection + .data_o(in_tmr_stack_redundant), + .id_o (in_id_redundant), + .valid_o(in_valid_redundant), + .ready_i(in_ready_redundant) + ); + + // Handshake signal array for opgroup block + logic [NumOpgroups-1:0] in_opgrp_ready, out_opgrp_valid, out_opgrp_ready; + rr_stacked_t [NumOpgroups-1:0] out_opgrp_rr_stack; + rr_stacked_t out_rr_stack; + + // Pass ready up based on the current operation_i + assign in_ready_redundant = in_valid_redundant & in_opgrp_ready[in_tmr_stack_redundant.operation]; + + for (genvar opgrp = 0; opgrp < int'(NumOpgroups); opgrp++) begin : gen_operation_groups + localparam NUM_REGS = OpgroupNumRegs[opgrp]; + + // Input pipeline signals, index i holds signal after i register stages + data_t [0:NUM_REGS] pipe_data; + logic [0:NUM_REGS] pipe_valid; + logic [0:NUM_REGS] pipe_ready; + id_t [0:NUM_REGS] pipe_id; + + // Upstream Connection + // Error Injection + assign pipe_valid[0] = ((in_valid_redundant & stall) ^ valid_fault) && (opgrp == in_tmr_stack_redundant.operation); + assign pipe_data[0] = in_tmr_stack_redundant.data ^ data_fault; + assign pipe_id[0] = in_id_redundant ^ id_fault; + assign in_opgrp_ready[opgrp] = (pipe_ready[0] & stall) ^ ready_fault; + + // Generate the register stages + for (genvar i = 0; i < NUM_REGS; i++) begin : gen_pipeline + // Internal register enable for this stage + logic reg_ena; + + // Determine the ready signal of the current stage - advance the pipeline: + // 1. if the next stage is ready for our data + // 2. if the next stage only holds a bubble (not valid) -> we can pop it + assign pipe_ready[i] = pipe_ready[i+1] | ~pipe_valid[i+1]; + + // Valid: enabled by ready signal, synchronous clear with the flush signal + `FFLARNC(pipe_valid[i+1], pipe_valid[i], pipe_ready[i], 1'b0, 1'b0, clk, rst_n) + // Enable register if pipleine ready and a valid data item is present + assign reg_ena = (pipe_ready[i] & pipe_valid[i]); // | reg_ena_i[i]; + // Generate the pipeline registers within the stages, use enable-registers + `FFLARN(pipe_data[i+1], pipe_data[i], reg_ena, data_t'('0), clk, rst_n) + `FFLARN( pipe_id[i+1], pipe_id[i], reg_ena, id_t'('0), clk, rst_n) + end + + // Downstream connection + assign out_opgrp_valid[opgrp] = pipe_valid[NUM_REGS]; + assign out_opgrp_rr_stack[opgrp].data = pipe_data[NUM_REGS]; + assign out_opgrp_rr_stack[opgrp].id = pipe_id[NUM_REGS]; + assign pipe_ready[NUM_REGS] = out_opgrp_ready[opgrp]; + end + + // Signals for after RR + logic out_tmr_valid, out_tmr_ready; + tmr_stacked_t out_tmr_stack; + + // Backpropagating lock signal + logic [REP-1:0] lock; + + // Round-Robin arbiter to decide which result to use + rr_arb_tree_lock #( + .NumIn ( NumOpgroups ), + .DataType ( rr_stacked_t ), + .AxiVldRdy ( 1'b1 ), + .InternalRedundancy ( InternalRedundancy ) + ) i_arbiter ( + .clk_i(clk), + .rst_ni(rst_n), + .flush_i('0), + .rr_i ('0), + .lock_rr_i (lock), + + // Upstream connection + .req_i(out_opgrp_valid), + .gnt_o(out_opgrp_ready), + .data_i(out_opgrp_rr_stack), + + // Downstream connection + .gnt_i(out_tmr_ready), + .req_o(out_tmr_valid), + .data_o(out_rr_stack), + .idx_o(out_tmr_stack.operation) + ); + + + // Signals for after TMR + tmr_stacked_t out_stacked; + id_t out_tmr_id; + + assign out_tmr_id = out_rr_stack.id; + assign out_tmr_stack.data = out_rr_stack.data; + + TTR_end #( + .DataType(tmr_stacked_t), + .LockTimeout(LockTimeout), + .IDSize (IDSize), + .EarlyValidEnable(EarlyValidEnable), + .InternalRedundancy(InternalRedundancy) + ) i_TTR_end ( + .clk_i(clk), + .rst_ni(rst_n), + .enable_i(enable), + + // Upstream connection + .data_i(out_tmr_stack), + .id_i (out_tmr_id), + .valid_i(out_tmr_valid), + .ready_o(out_tmr_ready), + + // Downstream connection + .data_o(out_stacked), + .valid_o(valid_out), + .ready_i(ready_out), + .lock_o(lock), + + // Flags + .fault_detected_o(/* Unused */) + ); + + assign data_out = out_stacked.data; + assign operation_out = out_stacked.operation; + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Input + //////////////////////////////////////////////////////////////////////////////////7 + data_t golden_queue [NumOpgroups-1:0][$]; + + initial begin + forever begin + input_handshake_begin(); + operation_in = $urandom_range(0, NumOpgroups-1);; + data_in = $random; + golden_queue[operation_in].push_back(data_in); + input_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Data Output + //////////////////////////////////////////////////////////////////////////////////7 + data_t data_golden, data_actual; + logic [OpgroupWidth-1:0] operation_actual; + logic error; // Helper signal so one can quickly scroll to errors in questa + longint unsigned error_cnt = 0; + + // Progress reporting + task reset_metrics(); + reset(); + error_cnt = 0; + in_hs_count = 0; + out_hs_count = 0; + for (int i = 0; i < NumOpgroups; i++) begin + golden_queue[i].delete(); + end + endtask + + initial begin + $timeformat(-9, 0, " ns", 20); + forever begin + output_handshake_start(); + // Once it happened check if output was good and reset ready again + data_actual = data_out; + operation_actual = operation_out; + if (golden_queue[operation_actual].size() > 0) begin + data_golden = golden_queue[operation_actual].pop_front(); + if (data_actual != data_golden) begin + $display("[T=%t] Operation %h -> Data %h Mismatch, should be %h", $time, operation_actual, data_actual, data_golden); + error = 1; + error_cnt += 1; + end else begin + error = 0; + end + end else begin + $display("[T=%t] Operation %h -> Data %h Output when nothing was in golden queue", $time, operation_actual, data_actual); + error = 1; + error_cnt += 1; + end + output_handshake_end(); + end + end + + //////////////////////////////////////////////////////////////////////////////////7 + // Fault Injection + //////////////////////////////////////////////////////////////////////////////////7 + + longint unsigned min_fault_delay = (12 + 5) * 5; + longint unsigned max_fault_delay = (12 + 5) * 5 + 20; + + // Signals to show what faults are going on + enum {NONE, DATA_FAULT, VALID_FAULT, READY_FAULT, ID_FAULT} fault_type, fault_current; + + initial data_fault = '0; + initial valid_fault = '0; + initial ready_fault = '0; + initial id_fault = '0; + + task inject_fault(); + // Send correct data for some cycles to space errors + repeat ($urandom_range(min_fault_delay, max_fault_delay)) begin + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + end + + // Send wrong data + fault_current = fault_type; + case (fault_type) + DATA_FAULT: data_fault = $random; + VALID_FAULT: valid_fault = 1; + READY_FAULT: ready_fault = 1; + ID_FAULT: id_fault = $random; + endcase + + // Send correct data again + @(posedge clk); + fault_current = NONE; + data_fault = '0; + valid_fault = '0; + ready_fault = '0; + id_fault = '0; + endtask + + //////////////////////////////////////////////////////////////////////////////////7 + // Main Loop + //////////////////////////////////////////////////////////////////////////////////7 + longint unsigned total_error_cnt = 0; + + initial begin + reset_metrics(); + + // Check normal operation + fault_type = NONE; + enable = 0; + repeat (10 * TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc disabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and no faults, got %d errors.", error_cnt); + reset_metrics(); + + // Check fault tolerance + fault_type = DATA_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and data faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = VALID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and valid fault, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = READY_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and ready faults, got %d errors.", error_cnt); + reset_metrics(); + + fault_type = ID_FAULT; + enable = 1; + repeat (TESTS) inject_fault(); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled and ID faults, got %d errors.", error_cnt); + reset_metrics(); + + // Measure throughput + fault_type = NONE; + enable = 0; + in_hs_max_starvation = 0; + out_hs_max_starvation = 0; + internal_hs_max_starvation = 0; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc disabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS, error_cnt); + if (TESTS - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc disabled!"); + end + reset_metrics(); + + enable = 1; + repeat (TESTS) @(posedge clk); + total_error_cnt += error_cnt; + $display("Ending Test with ecc enabled got a max throughtput of %d/%d and %d errors.", out_hs_count, TESTS/3, error_cnt); + if (TESTS / 3 - out_hs_count > TESTS / 20) begin + $error("Stall detected with ecc enabled!"); + end + reset_metrics(); + $display("Checked %0d tests of each type, found %0d mismatches.", TESTS, total_error_cnt); + $finish(error_cnt); + end + + +endmodule