Every dispatched instruction takes the next ROB entry in order. The entry tracks completion and holds the result until it becomes the oldest uncommitted instruction.
Only the head can commit. A completed instruction deeper in the buffer waits, which is exactly what keeps commit in program order.
ROB entry field | Purpose | Set when |
|---|---|---|
Valid | Entry holds a live instruction | Dispatch allocates it |
Ready | Result is computed | Execution completes, possibly out of order |
Result data | Value to commit to state | Execution completes |
Destination | Where the result goes | Decode and rename |
The window must hide average producer latency at the target IPC. Little's law gives the bound: in-flight instructions equal IPC times latency in cycles.
The example below uses stated parameters, not a measured workload.
target IPC 3, average producer latency 15 cycles (stated example)
in-flight needed = IPC x latency = 3 x 15 = 45 instructions
ROB must hold at least 45 entries -> choose 64 (next convenient size)A 4-entry reorder buffer plus a testbench that completes entries out of order and proves commit stays in order.
module rob4 (
input logic clk,
input logic rst_n,
input logic alloc,
input logic [7:0] alloc_data,
output logic alloc_ok,
input logic mark_en,
input logic [1:0] mark_idx,
output logic commit_valid,
output logic [7:0] commit_data,
input logic retire
);
logic [3:0] entry_valid;
logic [3:0] entry_ready;
logic [7:0] entry_data [4];
logic [1:0] head;
logic [1:0] tail;
logic [2:0] count;
logic do_alloc;
logic do_retire;
assign do_alloc = alloc & alloc_ok;
assign do_retire = retire & commit_valid;
assign alloc_ok = (count < 4);
assign commit_valid = entry_valid[head] & entry_ready[head];
assign commit_data = entry_data[head];
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
entry_valid <= 4'b0;
entry_ready <= 4'b0;
head <= 2'd0;
tail <= 2'd0;
count <= 3'd0;
end else begin
if (do_alloc) begin
entry_valid[tail] <= 1'b1;
entry_ready[tail] <= 1'b0;
entry_data[tail] <= alloc_data;
tail <= tail + 2'd1;
end
if (mark_en) begin
entry_ready[mark_idx] <= 1'b1;
end
if (do_retire) begin
entry_valid[head] <= 1'b0;
entry_ready[head] <= 1'b0;
head <= head + 2'd1;
end
if (do_alloc & ~do_retire) count <= count + 3'd1;
else if (do_retire & ~do_alloc) count <= count - 3'd1;
end
end
endmodule
None yet: only the head commits, so entries 2 and 3 wait for entries 0 and 1 to retire first.
They are flushed without committing, and their physical registers return to the free list.