diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c6de89e1..b3cd6b563 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ - Don't use tabs, use spaces. - Use 4 spaces to open a new indentation level. - All signal and module names should be lower case with underscores as whitespace replacements (e.g.: `fetch_busy`). -- Instantiation of modules should be postfix with `_i`, e.g.: `prefetcher_i` +- Instantiation of modules should be prefix with `i_`, e.g.: `i_prefetcher` - For port definitions keep a post-fix direction (`_o`, `_i`). - For active low signals put an additional (`_no`, `_ni`). - Denote output of ff with `_q` and the input with `_n`. diff --git a/src/fetch_fifo.sv b/src/fetch_fifo.sv index a05383c7c..f6a2563f1 100644 --- a/src/fetch_fifo.sv +++ b/src/fetch_fifo.sv @@ -25,16 +25,16 @@ module fetch_fifo input logic rst_ni, // control signals input logic flush_i, // clears the contents of the FIFO -> quasi reset - // branch prediction at in_addr_i address, as this is an address and not PC it can be the case + // branch prediction at addr_i address, as this is an address and not PC it can be the case // that we have two compressed instruction (or one compressed instruction and one unaligned instruction) so we // only predict on one entry and discard (or keep) the other depending on its position and prediction. // input port input branchpredict_sbe branch_predict_i, input exception ex_i, // fetch exception in - input logic [63:0] in_addr_i, - input logic [63:0] in_rdata_i, - input logic in_valid_i, - output logic in_ready_o, + input logic [63:0] addr_i, + input logic [63:0] rdata_i, + input logic valid_i, + output logic ready_o, // Dual Port Fetch FIFO // output port 0 output fetch_entry fetch_entry_0_o, @@ -55,7 +55,7 @@ module fetch_fifo logic [$clog2(DEPTH)-1:0] write_pointer_n, write_pointer_q; logic [$clog2(DEPTH)-1:0] status_cnt_n, status_cnt_q; // this integer will be truncated by the synthesis tool - assign in_ready_o = (status_cnt_q < DEPTH-2); + assign ready_o = (status_cnt_q < DEPTH-2); assign full = (status_cnt_q == DEPTH); assign empty = (status_cnt_q == '0); @@ -66,11 +66,11 @@ module fetch_fifo // downsize from 64 bit to 32 bit, simply ignore half of the incoming data always_comb begin : downsize // take the upper half - if (in_addr_i[2]) - in_rdata = in_rdata_i[63:32]; + if (addr_i[2]) + in_rdata = rdata_i[63:32]; // take the lower half of the instruction else - in_rdata = in_rdata_i[31:0]; + in_rdata = rdata_i[31:0]; end always_comb begin : fetch_fifo_logic @@ -84,10 +84,10 @@ module fetch_fifo // ------------- // Input Port // ------------- - if (in_valid_i) begin + if (valid_i) begin status_cnt++; // new input data - mem_n[write_pointer_q] = {in_addr_i, in_rdata, branch_predict_i, ex_i}; + mem_n[write_pointer_q] = {addr_i, in_rdata, branch_predict_i, ex_i}; write_pointer++; end @@ -146,7 +146,7 @@ module fetch_fifo // Make sure we don't overflow the queue assert property (@(posedge clk_i) ((full && !flush_i) |-> ##1 !empty)) else $error("Fetch FIFO Overflowed"); assert property (@(posedge clk_i) (flush_i || (status_cnt_q - status_cnt_n) <= 2 || (status_cnt_q - status_cnt_n) >= -2)) else $error("Fetch FIFO over- or underflowed"); - assert property (@(posedge clk_i) (in_valid_i |-> !full)) else $error("Got a valid signal, although the queue is not ready to accept a new request"); + assert property (@(posedge clk_i) (valid_i |-> !full)) else $error("Got a valid signal, although the queue is not ready to accept a new request"); `endif `endif -endmodule \ No newline at end of file +endmodule diff --git a/src/if_stage.sv b/src/if_stage.sv index 93e0774f4..5c5bfa014 100644 --- a/src/if_stage.sv +++ b/src/if_stage.sv @@ -35,7 +35,7 @@ module if_stage ( input logic instr_gnt_i, input logic instr_rvalid_i, input logic [63:0] instr_rdata_i, - input exception instr_ex_i, // Instruction fetch exception, valid if rvalid is one + input exception instr_ex_i, // Instruction fetch exception, valid if rvalid is one // Output of IF Pipeline stage -> Dual Port Fetch FIFO // output port 0 output fetch_entry fetch_entry_0_o, // fetch entry containing all relevant data for the ID stage @@ -47,193 +47,181 @@ module if_stage ( input logic fetch_ack_1_i // ID acknowledged this instruction ); - enum logic [1:0] {IDLE, WAIT_GNT, WAIT_RVALID, WAIT_ABORTED } CS, NS; + enum logic [2:0] {IDLE, WAIT_GNT, WAIT_RVALID, WAIT_ABORTED, WAIT_ABORTED_REQUEST } CS, NS; + // define a type where we can store address and branch-prediction information + typedef struct packed { + logic [63:0] address; + branchpredict_sbe branchpredict; + } address_fifo_t; - logic [63:0] fetch_address; - logic addr_valid; - logic [63:0] instr_addr_q; - logic fifo_valid; - logic fifo_ready; - branchpredict_sbe branchpredict_q; + logic [63:0] instr_addr_n, instr_addr_q; + branchpredict_sbe branchpredict_n, branchpredict_q; + // Control signals + address_fifo_t push_data, pop_data; + logic fifo_valid, fifo_ready; + logic pop_empty, empty; // pop the address queue in case of a flush, empty signal - //--------------------------------- - // Pre-fetch buffer status - //--------------------------------- - // we are busy if we are either waiting for a grant - // or if the FIFO is full - // or if we are waiting for a rvalid and we didn't receive one yet - assign if_busy_o = (CS == WAIT_GNT) || !fifo_ready || (CS == WAIT_RVALID && !instr_rvalid_i); + // we are busy if we are either waiting for a grant or if the FIFO is full + assign if_busy_o = (CS == WAIT_GNT) || !fifo_ready || (CS == WAIT_ABORTED_REQUEST); assign fetch_address = {fetch_address_i[63:2], 2'b0}; - //--------------------------------- - // Fetch FIFO - // consumes addresses and rdata - //--------------------------------- - fetch_fifo fetch_fifo_i ( - .branch_predict_i ( branchpredict_q ), - .ex_i ( instr_ex_i ), - .in_addr_i ( instr_addr_q ), - .in_rdata_i ( instr_rdata_i ), - .in_valid_i ( fifo_valid ), - .in_ready_o ( fifo_ready ), - .* - ); - - //-------------------------------------------------- + // -------------------------------------------------- // Instruction fetch FSM // deals with instruction memory / instruction cache - //-------------------------------------------------- - always_comb begin + // -------------------------------------------------- + always_comb begin : instr_fetch_fsm + + NS = CS; instr_req_o = 1'b0; instr_addr_o = fetch_address; - fifo_valid = 1'b0; - NS = CS; - addr_valid = 1'b0; + push_data = { fetch_address_i, branch_predict_i }; + fifo_valid = instr_rvalid_i; + pop_empty = instr_rvalid_i; // only pop the address queue - case(CS) - // default state, not waiting for requested data + // get new data by default + if (!if_busy_o) begin + branchpredict_n = branch_predict_i; + instr_addr_n = fetch_address_i; + // if we are not ready, latch the current data + end else begin + branchpredict_n = branchpredict_q; + instr_addr_n = instr_addr_q; + end + + case (CS) + // we are idling, and can accept a new request IDLE: begin - instr_addr_o = fetch_address; - instr_req_o = 1'b0; - - // make a new request + // check if we have space in the FIFO and we want to do a request if (fifo_ready && fetch_valid_i) begin instr_req_o = 1'b1; - addr_valid = 1'b1; - - - if (instr_gnt_i) //~> granted request - // we have one outstanding rvalid: wait for it - if (flush_i) - NS = WAIT_ABORTED; - else - NS = WAIT_RVALID; - else begin //~> got a request but no grant - // if we flush we want to stay in the IDLE state - if (!flush_i) - NS = WAIT_GNT; - end + // did we get a grant? + if (instr_gnt_i) + // Yes, so wait for the rvalid + NS = WAIT_RVALID; + else + // No, so wait for it + NS = WAIT_GNT; end - end // case: IDLE + end // we sent a request but did not yet get a grant WAIT_GNT: begin instr_addr_o = {instr_addr_q[63:2], 2'b0}; instr_req_o = 1'b1; - if(instr_gnt_i) begin + if (instr_gnt_i) begin + // push the old data + push_data = { instr_addr_q, branchpredict_q }; // we have one outstanding rvalid: wait for it if (flush_i) NS = WAIT_ABORTED; else NS = WAIT_RVALID; - end else begin - // if we got a flush request we can safely return to IDLE - if (flush_i) - NS = IDLE; - else - NS = WAIT_GNT; end - end // case: WAIT_GNT + end - // we wait for rvalid, after that we are ready to serve a new request WAIT_RVALID: begin instr_addr_o = fetch_address; - // we are waiting for a rvalid and in case we don't receive one, wait in the aborted state if we flushed - if (flush_i) - NS = WAIT_ABORTED; - // prepare for next request + // prepare for next request, we've got one if the fetch_valid_i is high if (fifo_ready && fetch_valid_i) begin - // wait for the valid signal - if (instr_rvalid_i) begin - instr_req_o = 1'b1; - fifo_valid = 1'b1; - addr_valid = 1'b1; - if (instr_gnt_i) begin - // we have one outstanding rvalid -> wait for it - // if we are receiving a data item during a flush ignore it - if (flush_i) - NS = WAIT_ABORTED; - else - NS = WAIT_RVALID; - end else begin - if (!flush_i) - NS = WAIT_GNT; // lets wait for the grant - else // we didn't get a grant yet so go back to IDLE - NS = IDLE; - end - end - end else begin - // we are requested to abort our current request - // we didn't get an rvalid yet, so wait for it - if (flush_i) begin - NS = WAIT_ABORTED; - end - // just wait for rvalid and go back to IDLE, no new request - if (instr_rvalid_i) begin - // if we are receiving a data item during a flush ignore it - if (flush_i) - fifo_valid = 1'b0; - else - fifo_valid = 1'b1; - // in any case we can go back to IDLE safely - NS = IDLE; - end - end - - end // case: WAIT_RVALID - - // our last request was aborted, but we didn't yet get a rvalid - WAIT_ABORTED: begin - // we can do a new request here, we won't get a grant until the rvalid came back for the - // request we sent to end in here - instr_addr_o = fetch_address; - addr_valid = 1'b1; - // we are aborting this instruction so don't tell the FIFO it is valid - fifo_valid = 1'b0; - // check if the fetch is valid before making a new request - if (fetch_valid_i) begin - - instr_req_o = 1'b1; + instr_req_o = 1'b1; if (instr_gnt_i) begin - // we have one outstanding rvalid - if (flush_i) - NS = WAIT_ABORTED; - else - NS = WAIT_RVALID; + // we have one outstanding rvalid -> wait for it + NS = WAIT_RVALID; end else begin - // only if we are not flushing again leave the state - if (!flush_i) - NS = WAIT_GNT; + NS = WAIT_GNT; // lets wait for the grant end end else begin - if (instr_rvalid_i) begin - // we didn't make a new request but got the rvalid we where waiting for, go back to IDLE - NS = IDLE; - end - // otherwise wait in this state for the rvalid + // go back to IDLE + NS = IDLE; end + + end + // we save a potential new request here + WAIT_ABORTED: begin + // abort the current rvalid, we don't want it anymore + fifo_valid = 1'b0; + // we've got a new fetch here, the fetch fifo is for sure empty as we just flushed it, but we still need to + // wait for the queue to be emptied + if (fetch_valid_i && empty) begin + instr_req_o = 1'b1; + NS = WAIT_GNT; + end else if (fetch_valid_i) // the fetch is valid but the queue is not empty wait for it + NS = WAIT_ABORTED_REQUEST; + else if (empty) // the fetch is not valid and the queue is empty we are back to normal operation + NS = IDLE; + end + + // our last request was aborted, but we didn't yet get a rvalid + WAIT_ABORTED_REQUEST: begin + // abort the current rvalid, we don't want it anymore + fifo_valid = 1'b0; + // Here we wait for the queue to be empty, we do not make any requests + if (empty) + NS = WAIT_GNT; end endcase + // ------------- + // Flush + // ------------- + if (flush_i) begin + // if the address queue is empty this case is simple: just go back to idle + if (empty) + NS = IDLE; + // if it wasn't empty we need to wait for all outstanding rvalids until we can make any further requests + else + NS = WAIT_ABORTED; + end end + // --------------------------------- + // Address and Branch-predict Queue + // --------------------------------- + + fifo #( + .dtype ( address_fifo_t ), + // we can only have two in-flight instructions: + .DEPTH ( 2 ) + ) i_fifo ( + .flush_i ( 1'b0 ), // do not flush, we need to keep track of all outstanding rvalids + .full_o ( ), // This queue should not underflow... + .empty_o ( empty ), // .. or overflow + .single_element_o ( ), // isn't needed here + .data_i ( push_data ), + .push_i ( instr_gnt_i ), // if we got a grant push the address and data + .data_o ( pop_data ), + .pop_i ( fifo_valid || pop_empty ), // pop the data if we say that the fetch is valid + .* + ); + // --------------------------------- + // Fetch FIFO + // consumes addresses and rdata + // --------------------------------- + fetch_fifo i_fetch_fifo ( + .branch_predict_i ( pop_data.branchpredict ), + .ex_i ( instr_ex_i ), + .addr_i ( pop_data.address ), + .rdata_i ( instr_rdata_i ), + .valid_i ( fifo_valid ), + .ready_o ( fifo_ready ), + .* + ); + //------------- // Registers //------------- always_ff @(posedge clk_i, negedge rst_ni) begin if (~rst_ni) begin - CS <= IDLE; - instr_addr_q <= '0; - branchpredict_q <= '{default: 0}; + CS <= IDLE; + instr_addr_q <= '0; + branchpredict_q <= '{default: 0}; end else begin - CS <= NS; - if (addr_valid) begin - instr_addr_q <= fetch_address_i; - branchpredict_q <= branch_predict_i; - end + CS <= NS; + instr_addr_q <= instr_addr_n; + branchpredict_q <= branchpredict_n; end end //------------- diff --git a/tb b/tb index 4d598a69e..36029ea26 160000 --- a/tb +++ b/tb @@ -1 +1 @@ -Subproject commit 4d598a69e20a7dfbd8f9f46f62dcba2bd3e88f16 +Subproject commit 36029ea2635e912e67cd069552c4c1fcd1dea027