mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-04-23 21:57:11 -04:00
New scoreboard implementation (simplified)
This commit is contained in:
parent
a4ccd3c85d
commit
a4f2d965f0
1 changed files with 131 additions and 298 deletions
|
@ -22,8 +22,7 @@ import ariane_pkg::*;
|
|||
|
||||
module scoreboard #(
|
||||
parameter int NR_ENTRIES = 8,
|
||||
parameter int NR_WB_PORTS = 1,
|
||||
parameter type dtype = scoreboard_entry
|
||||
parameter int NR_WB_PORTS = 1
|
||||
)
|
||||
(
|
||||
input logic clk_i, // Clock
|
||||
|
@ -44,17 +43,17 @@ module scoreboard #(
|
|||
output logic rs2_valid_o,
|
||||
|
||||
// advertise instruction to commit stage, if commit_ack_i is asserted advance the commit pointer
|
||||
output dtype commit_instr_o,
|
||||
output scoreboard_entry commit_instr_o,
|
||||
input logic commit_ack_i,
|
||||
|
||||
// instruction to put on top of scoreboard e.g. : top pointer
|
||||
// we can always put this instruction to the to p unless we signal with asserted full_o
|
||||
input dtype decoded_instr_i,
|
||||
input scoreboard_entry decoded_instr_i,
|
||||
input logic decoded_instr_valid_i,
|
||||
output logic decoded_instr_ack_o,
|
||||
|
||||
// instruction to issue logic, if issue_instr_valid and issue_ready is asserted, advance the issue pointer
|
||||
output dtype issue_instr_o,
|
||||
output scoreboard_entry issue_instr_o,
|
||||
output logic issue_instr_valid_o,
|
||||
input logic issue_ack_i,
|
||||
|
||||
|
@ -64,312 +63,146 @@ module scoreboard #(
|
|||
input exception [NR_WB_PORTS-1:0] ex_i, // exception from a functional unit (e.g.: ld/st exception, divide by zero)
|
||||
input logic [NR_WB_PORTS-1:0] wb_valid_i // data in is valid
|
||||
);
|
||||
localparam BITS_ENTRIES = $clog2(NR_ENTRIES);
|
||||
localparam BITS_ENTRIES = $clog2(NR_ENTRIES);
|
||||
|
||||
dtype [NR_ENTRIES-1:0] mem_q, mem_n;
|
||||
logic [BITS_ENTRIES-1:0] issue_pointer_n, issue_pointer_q, // points to the instruction currently in issue
|
||||
commit_pointer_n, commit_pointer_q, // points to the instruction currently in commit
|
||||
top_pointer_n, top_pointer_q, top_pointer_qq; // points to the top of the scoreboard, an empty slot, top pointer two cycles ago
|
||||
// this is the FIFO struct of the issue queue
|
||||
struct packed {
|
||||
logic issued; // this bit indicates whether we issued this instruction e.g.: if it is valid
|
||||
scoreboard_entry sbe; // this is the score board entry we will send to ex
|
||||
} mem_q [NR_ENTRIES-1:0], mem_n [NR_ENTRIES-1:0];
|
||||
|
||||
logic pointer_overflow;
|
||||
logic empty;
|
||||
logic reset_condition;
|
||||
// full and empty signaling: signal that we are not able to take new instructions
|
||||
// track pointer overflow
|
||||
// && top_pointer_q >= top_pointer_qq
|
||||
assign reset_condition = top_pointer_q == top_pointer_qq;
|
||||
assign pointer_overflow = (top_pointer_q <= commit_pointer_q && ~reset_condition) ? 1'b1 : 1'b0;
|
||||
assign full_o = (reset_condition) ? 1'b0 : (commit_pointer_q == top_pointer_q);
|
||||
assign empty = (pointer_overflow) ? 1'b0 : (commit_pointer_q == top_pointer_q);
|
||||
logic [$clog2(NR_ENTRIES)-1:0] issue_cnt_n, issue_cnt_q;
|
||||
logic [$clog2(NR_ENTRIES)-1:0] issue_pointer_n, issue_pointer_q;
|
||||
logic [$clog2(NR_ENTRIES)-1:0] commit_pointer_n, commit_pointer_q;
|
||||
logic issue_full;
|
||||
|
||||
// rd_clobber output: output currently clobbered destination registers
|
||||
// but only between commit and issue pointer
|
||||
// normal case: overflow case:
|
||||
// ._________________________. ._________________________.
|
||||
// |_________________________| |_________________________|
|
||||
// |_________________________|<- commit pointer |_________________________|<- issue pointer
|
||||
// |_________________________| |_________________________|<- top pointer
|
||||
// |_________________________|<- issue pointer |_________________________|<- commit pointer
|
||||
// |_________________________|<- top pointer |_________________________|
|
||||
//
|
||||
always_comb begin : clobber_output
|
||||
rd_clobber_o = '{default: NONE};
|
||||
// excluding issue, the issue pointer points to the instruction which is currently not issued
|
||||
// but might be issued as soon as the issue unit acknowledges
|
||||
if (commit_pointer_q < issue_pointer_q) begin
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
// non overflowed case, depicted on the left
|
||||
if (i[BITS_ENTRIES-1:0] >= commit_pointer_q && i[BITS_ENTRIES-1:0] < issue_pointer_q)
|
||||
rd_clobber_o[mem_q[i].rd] = mem_q[i].fu;
|
||||
end
|
||||
end else if (commit_pointer_q == issue_pointer_q) begin // everything committed
|
||||
rd_clobber_o = '{default: NONE};
|
||||
end else begin // the issue pointer has overflowed, invert logic, depicted on the right
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
if (i[BITS_ENTRIES-1:0] >= commit_pointer_q || i[BITS_ENTRIES-1:0] < issue_pointer_q)
|
||||
rd_clobber_o[mem_q[i].rd] = mem_q[i].fu;
|
||||
end
|
||||
// the issue queue is full don't issue any new instructions
|
||||
assign issue_full = (issue_cnt_q == NR_ENTRIES);
|
||||
// output commit instruction directly
|
||||
assign commit_instr_o = mem_q[commit_pointer_q].sbe;
|
||||
|
||||
// an instruction is ready for issue if we have place in the issue FIFO and it the decoder says it is valid
|
||||
always_comb begin
|
||||
issue_instr_o = decoded_instr_i;
|
||||
// make sure we assign the correct trans ID
|
||||
issue_instr_o.trans_id = issue_pointer_q;
|
||||
issue_instr_valid_o = ~issue_full && decoded_instr_valid_i && !flush_unissued_instr_i;
|
||||
decoded_instr_ack_o = issue_ack_i;
|
||||
end
|
||||
// the zero register is always free
|
||||
rd_clobber_o[0] = NONE;
|
||||
end
|
||||
// read operand interface: same logic as register file, including a valid file
|
||||
always_comb begin : read_operands
|
||||
// maintain a FIFO with issued instructions
|
||||
// keep track of all issued instructions
|
||||
always_comb begin : issue_fifo
|
||||
automatic logic [$clog2(NR_ENTRIES)-1:0] issue_cnt = issue_cnt_q;
|
||||
// default assignment
|
||||
mem_n = mem_q;
|
||||
commit_pointer_n = commit_pointer_q;
|
||||
issue_pointer_n = issue_pointer_q;
|
||||
|
||||
rs1_o = 64'b0;
|
||||
rs2_o = 64'b0;
|
||||
rs1_valid_o = 1'b0;
|
||||
rs2_valid_o = 1'b0;
|
||||
// if we got a acknowledge from the FIFO, put this scoreboard entry in the queue
|
||||
if (issue_ack_i) begin
|
||||
// increase the issue counter
|
||||
issue_cnt++;
|
||||
// the decoded instruction we put in there is valid (1st bit)
|
||||
mem_n[issue_pointer_q] = {1'b1, decoded_instr_i};
|
||||
// advance issue pointer
|
||||
issue_pointer_n = issue_pointer_q + 1'b1;
|
||||
end
|
||||
|
||||
if (commit_pointer_q < issue_pointer_q) begin
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
if (i[BITS_ENTRIES-1:0] >= commit_pointer_q && i[BITS_ENTRIES-1:0] < issue_pointer_q) begin
|
||||
// we've got an acknowledge from commit
|
||||
if (commit_ack_i) begin
|
||||
// decrease the issue counter
|
||||
issue_cnt--;
|
||||
// this instruction is no longer in issue e.g.: it is considered finished
|
||||
mem_n[commit_pointer_q].issued = 1'b0;
|
||||
mem_n[commit_pointer_q].sbe.valid = 1'b0;
|
||||
// advance commit pointer
|
||||
commit_pointer_n = commit_pointer_n + 1'b1;
|
||||
end
|
||||
// ------------
|
||||
// Write Back
|
||||
// ------------
|
||||
for (int i = 0; i < NR_WB_PORTS; i++) begin
|
||||
if (wb_valid_i[i]) begin
|
||||
mem_n[trans_id_i[i]].sbe.valid = 1'b1;
|
||||
mem_n[trans_id_i[i]].sbe.result = wdata_i[i];
|
||||
mem_n[trans_id_i[i]].sbe.ex = ex_i[i];
|
||||
end
|
||||
end
|
||||
|
||||
// ------
|
||||
// Flush
|
||||
// ------
|
||||
if (flush_i) begin
|
||||
for (int i = 0; i < NR_ENTRIES; i++) begin
|
||||
// set all valid flags for all entries to zero
|
||||
mem_n[i].issued = 1'b0;
|
||||
mem_n[i].sbe.valid = 1'b0;
|
||||
mem_n[i].sbe.ex.valid = 1'b0;
|
||||
// set the pointer and counter back to zero
|
||||
issue_cnt = '0;
|
||||
issue_pointer_n = '0;
|
||||
commit_pointer_n = '0;
|
||||
end
|
||||
end
|
||||
// update issue counter
|
||||
issue_cnt_n = issue_cnt;
|
||||
end
|
||||
|
||||
// -------------------
|
||||
// RD clobber process
|
||||
// -------------------
|
||||
// rd_clobber output: output currently clobbered destination registers
|
||||
always_comb begin : clobber_output
|
||||
rd_clobber_o = '{default: NONE};
|
||||
// check for all valid entries and set the clobber register accordingly
|
||||
for (int i = 0; i < NR_ENTRIES; i++) begin
|
||||
if (mem_q[i].issued) begin
|
||||
// output the functional unit which is going to clobber this register
|
||||
rd_clobber_o[mem_q[i].sbe.rd] = mem_q[i].sbe.fu;
|
||||
end
|
||||
end
|
||||
// the zero register is always free
|
||||
rd_clobber_o[0] = NONE;
|
||||
end
|
||||
|
||||
// ----------------------------------
|
||||
// Read Operands (a.k.a forwarding)
|
||||
// ----------------------------------
|
||||
// read operand interface: same logic as register file
|
||||
always_comb begin : read_operands
|
||||
rs1_o = 64'b0;
|
||||
rs2_o = 64'b0;
|
||||
rs1_valid_o = 1'b0;
|
||||
rs2_valid_o = 1'b0;
|
||||
|
||||
for (int i = 0; i < NR_ENTRIES; i++) begin
|
||||
// only consider this entry if it is valid
|
||||
if (mem_q[i].issued) begin
|
||||
// look at the appropriate fields and look whether there was an
|
||||
// instruction that wrote the rd field before, first for RS1 and then for RS2
|
||||
if (mem_q[i[BITS_ENTRIES-1:0]].rd == rs1_i) begin
|
||||
rs1_o = mem_q[i].result;
|
||||
rs1_valid_o = mem_q[i].valid;
|
||||
// do the same for rs2
|
||||
end else if (mem_q[i].rd == rs2_i) begin
|
||||
rs2_o = mem_q[i].result;
|
||||
rs2_valid_o = mem_q[i].valid;
|
||||
end
|
||||
end
|
||||
end
|
||||
end else begin // the issue pointer has overflowed, invert logic
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
if (i[BITS_ENTRIES-1:0] >= commit_pointer_q || i[BITS_ENTRIES-1:0] < issue_pointer_q) begin
|
||||
// same as above but for the overflowed pointer case
|
||||
if (mem_q[i[BITS_ENTRIES-1:0]].rd == rs1_i) begin
|
||||
rs1_o = mem_q[i].result;
|
||||
rs1_valid_o = mem_q[i].valid;
|
||||
// do the same for rs2
|
||||
end else if (mem_q[i[BITS_ENTRIES-1:0]].rd == rs2_i) begin
|
||||
rs2_o = mem_q[i].result;
|
||||
rs2_valid_o = mem_q[i].valid;
|
||||
if (mem_q[i].sbe.rd == rs1_i) begin
|
||||
rs1_o = mem_q[i].sbe.result;
|
||||
rs1_valid_o = mem_q[i].sbe.valid;
|
||||
end else if (mem_q[i].sbe.rd == rs2_i) begin
|
||||
rs2_o = mem_q[i].sbe.result;
|
||||
rs2_valid_o = mem_q[i].sbe.valid;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// provide a direct combinational path from WB a.k.a forwarding
|
||||
// make sure that we are not forwarding a result that got an exception
|
||||
for (int j = 0; j < NR_WB_PORTS; j++) begin
|
||||
if (mem_q[trans_id_i[j]].rd == rs1_i && wb_valid_i[j] && ~ex_i[j].valid) begin
|
||||
rs1_o = wdata_i[j];
|
||||
rs1_valid_o = wb_valid_i[j];
|
||||
break;
|
||||
end
|
||||
if (mem_q[trans_id_i[j]].rd == rs2_i && wb_valid_i[j] && ~ex_i[j].valid) begin
|
||||
rs2_o = wdata_i[j];
|
||||
rs2_valid_o = wb_valid_i[j];
|
||||
break;
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if(~rst_ni) begin
|
||||
mem_q <= '{default: 0};
|
||||
issue_cnt_q <= '0;
|
||||
commit_pointer_q <= '0;
|
||||
issue_pointer_q <= '0;
|
||||
end else begin
|
||||
mem_q <= mem_n;
|
||||
issue_cnt_q <= issue_cnt_n;
|
||||
commit_pointer_q <= commit_pointer_n;
|
||||
issue_pointer_q <= issue_pointer_n;
|
||||
end
|
||||
end
|
||||
|
||||
// make sure we didn't read the zero register
|
||||
if (rs1_i == '0)
|
||||
rs1_valid_o = 1'b0;
|
||||
if (rs2_i == '0)
|
||||
rs2_valid_o = 1'b0;
|
||||
end
|
||||
// push new decoded instruction: if still empty space push the instruction to the scoreboard
|
||||
// write-back instruction: update value of RD register in scoreboard
|
||||
always_comb begin : push_instruction_and_wb
|
||||
// default assignment
|
||||
top_pointer_n = top_pointer_q;
|
||||
mem_n = mem_q;
|
||||
// acknowledge decoded instruction
|
||||
decoded_instr_ack_o = 1'b0;
|
||||
// if we are not full we can push a new instruction
|
||||
if (~full_o && decoded_instr_valid_i) begin
|
||||
mem_n[$unsigned(top_pointer_q)] = decoded_instr_i;
|
||||
// label the transaction ID with the current top pointer
|
||||
mem_n[$unsigned(top_pointer_q)].trans_id = top_pointer_q;
|
||||
top_pointer_n = top_pointer_q + 1;
|
||||
decoded_instr_ack_o = 1'b1;
|
||||
end
|
||||
|
||||
// write back:
|
||||
// look for the instruction with the given transaction ID and write the result data back
|
||||
// also set the valid bit
|
||||
for (int j = 0; j < NR_WB_PORTS; j++) begin
|
||||
if (wb_valid_i[j]) begin
|
||||
mem_n[trans_id_i[j]].valid = 1'b1;
|
||||
mem_n[trans_id_i[j]].result = wdata_i[j];
|
||||
mem_n[trans_id_i[j]].ex = ex_i[j];
|
||||
end
|
||||
end
|
||||
// ------------------------------
|
||||
// Flush un-issued instructions
|
||||
// ------------------------------
|
||||
// flush all instructions which are not issued, e.g. set the top pointer back to the issue pointer
|
||||
// -> everything we decoded so far was garbage
|
||||
if (flush_unissued_instr_i) begin
|
||||
top_pointer_n = issue_pointer_q;
|
||||
// also clear the valid flag for those instructions as it can be that we already got the valid
|
||||
// flag set because of an exception
|
||||
for (int i = 0; i < NR_ENTRIES; i++) begin
|
||||
// pointer has overflowed
|
||||
if (top_pointer_q < issue_pointer_q) begin
|
||||
if (i <= top_pointer_q || i >+ issue_pointer_q)
|
||||
mem_n[i].valid = 1'b0;
|
||||
// also clear exception signal
|
||||
mem_n[i].ex.valid = 1'b0;
|
||||
end else begin
|
||||
if (i >= issue_pointer_q && i <= top_pointer_q)
|
||||
mem_n[i].valid = 1'b0;
|
||||
mem_n[i].ex.valid = 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// issue instruction: advance the issue pointer
|
||||
always_comb begin : issue_instruction
|
||||
|
||||
|
||||
// provide a combinatorial path in case the scoreboard is empty
|
||||
if (top_pointer_q == issue_pointer_q && ~full_o) begin
|
||||
issue_instr_o = decoded_instr_i;
|
||||
issue_instr_o.trans_id = issue_pointer_q;
|
||||
issue_instr_valid_o = decoded_instr_valid_i;
|
||||
// if not empty go to scoreboard and get the instruction at the issue pointer
|
||||
end else begin
|
||||
issue_instr_o = mem_q[$unsigned(issue_pointer_q)];
|
||||
// we have not reached the top of the buffer
|
||||
// issue pointer has overflowed
|
||||
if (issue_pointer_q < commit_pointer_q) begin
|
||||
if (issue_pointer_q < top_pointer_q)
|
||||
issue_instr_valid_o = 1'b1;
|
||||
else
|
||||
issue_instr_valid_o = 1'b0;
|
||||
end else if (issue_pointer_q == commit_pointer_q) begin
|
||||
// commit and issue pointer are the same, so we are waiting
|
||||
// for instructions to be written back
|
||||
issue_instr_valid_o = 1'b0;
|
||||
end else begin // issue pointer has not overflowed
|
||||
if (pointer_overflow)
|
||||
issue_instr_valid_o = 1'b1;
|
||||
else if (issue_pointer_q <= top_pointer_q)
|
||||
issue_instr_valid_o = 1'b1;
|
||||
else
|
||||
issue_instr_valid_o = 1'b0;
|
||||
end
|
||||
|
||||
end
|
||||
// default assignment: issue didn't read
|
||||
issue_pointer_n = issue_pointer_q;
|
||||
// advance pointer if the issue logic was ready
|
||||
if (issue_ack_i) begin
|
||||
issue_pointer_n = issue_pointer_q + 1;
|
||||
end
|
||||
|
||||
// if we are flushing we should not issue the current instruction
|
||||
if (flush_unissued_instr_i)
|
||||
issue_instr_valid_o = 1'b0;
|
||||
|
||||
end
|
||||
|
||||
// commit instruction: remove from scoreboard, advance pointer
|
||||
always_comb begin: commit_instruction
|
||||
commit_pointer_n = commit_pointer_q;
|
||||
// we can always safely output the instruction at which the commit pointer points
|
||||
// since the scoreboard entry has a valid bit which the commit stage needs to check anyway
|
||||
commit_instr_o = mem_q[commit_pointer_q];
|
||||
if (commit_ack_i) begin
|
||||
commit_pointer_n = commit_pointer_q + 1;
|
||||
end
|
||||
end
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin : sequential
|
||||
if(~rst_ni) begin
|
||||
issue_pointer_q <= '{default: 0};
|
||||
commit_pointer_q <= '{default: 0};
|
||||
top_pointer_q <= '{default: 0};
|
||||
top_pointer_qq <= '{default: 0};
|
||||
for (int i = 0; i < NR_ENTRIES; i++) begin
|
||||
mem_q[i] <= {$bits(scoreboard_entry){1'b0}};
|
||||
end
|
||||
end else if (flush_i) begin // reset pointers on flush
|
||||
// flush signal, e.g.: flush everything we need to backtrack after an exception or a CSR instruction
|
||||
issue_pointer_q <= '{default: 0};
|
||||
commit_pointer_q <= '{default: 0};
|
||||
top_pointer_q <= '{default: 0};
|
||||
top_pointer_qq <= '{default: 0};
|
||||
for (int i = 0; i < NR_ENTRIES; i++) begin
|
||||
mem_q[i] <= {$bits(scoreboard_entry){1'b0}};
|
||||
end
|
||||
end else begin
|
||||
issue_pointer_q <= issue_pointer_n;
|
||||
commit_pointer_q <= commit_pointer_n;
|
||||
top_pointer_q <= top_pointer_n;
|
||||
mem_q <= mem_n;
|
||||
if (decoded_instr_valid_i && ~full_o) // only advance if we decoded instruction and we are not full
|
||||
if (flush_unissued_instr_i)
|
||||
top_pointer_qq <= top_pointer_n;
|
||||
else
|
||||
top_pointer_qq <= top_pointer_q;
|
||||
end
|
||||
end
|
||||
|
||||
`ifndef SYNTHESIS
|
||||
`ifndef verilator
|
||||
initial begin
|
||||
assert (NR_ENTRIES == 2**$clog2(NR_ENTRIES)) else $fatal("Scoreboard size needs to be a power of two.");
|
||||
end
|
||||
|
||||
// assert that zero is never set
|
||||
assert property (
|
||||
@(posedge clk_i) rst_ni |-> (rd_clobber_o[0] == NONE))
|
||||
else $error ("RD 0 should not bet set");
|
||||
// assert that we never acknowledge a commit if the instruction is not valid
|
||||
assert property (
|
||||
@(posedge clk_i) (rst_ni && commit_ack_i |-> commit_instr_o.valid))
|
||||
else $error ("Commit acknowledged but instruction is not valid");
|
||||
// assert that we never give an issue ack signal if the instruction is not valid
|
||||
assert property (
|
||||
@(posedge clk_i) (rst_ni && issue_ack_i |-> issue_instr_valid_o))
|
||||
else $error ("Issue acknowledged but instruction is not valid");
|
||||
|
||||
// there should never be more than one instruction writing the same destination register (except x0)
|
||||
// assert strict pointer ordering
|
||||
|
||||
// print scoreboard
|
||||
// initial begin
|
||||
// automatic string pointer = "";
|
||||
// static integer f = $fopen("scoreboard.txt", "w");
|
||||
|
||||
// forever begin
|
||||
// wait(rst_ni == 1'b1);
|
||||
// @(posedge clk_i)
|
||||
// $fwrite(f, $time);
|
||||
// $fwrite(f, "\n");
|
||||
// $fwrite(f, "._________________________.\n");
|
||||
// for (int i = 0; i < NR_ENTRIES; i++) begin
|
||||
// if (i == commit_pointer_q && i == issue_pointer_q && i == top_pointer_q)
|
||||
// pointer = " <- top, issue, commit pointer";
|
||||
// else if (i == commit_pointer_q && i == issue_pointer_q)
|
||||
// pointer = " <- issue, commit pointer";
|
||||
// else if (i == top_pointer_q && i == issue_pointer_q)
|
||||
// pointer = " <- top, issue pointer";
|
||||
// else if (i == top_pointer_q && i == commit_pointer_q)
|
||||
// pointer = " <- top, commit pointer";
|
||||
// else if (i == top_pointer_q)
|
||||
// pointer = " <- top pointer";
|
||||
// else if (i == commit_pointer_q)
|
||||
// pointer = " <- commit pointer";
|
||||
// else if (i == issue_pointer_q)
|
||||
// pointer = " <- issue pointer";
|
||||
// else
|
||||
// pointer = "";
|
||||
// $fwrite(f, "|_________________________| %s\n", pointer);
|
||||
// end
|
||||
// $fwrite(f, "\n");
|
||||
// end
|
||||
// $fclose(f);
|
||||
// end
|
||||
`endif
|
||||
`endif
|
||||
endmodule
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue