mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-22 12:57:13 -04:00
Track mem_err_shift better in the ICache scoreboard
This fixes a test failure that I was seeing when following a "many errors" test by something different. To reproduce, make -C dv/uvm/icache/dv \ SEED=1465832714 \ TESTS=ibex_icache_stress_all_with_reset There are actually two different ways this can come unstuck: (1) Memory request goes out and gets put into the response queue. req_i goes low. Sequence changes. req_i goes high and we get the response from the previous request (but mem_err_shift has changed in the meantime). To fix this, we pair up the memory seed and its associated mem_err_shift in the scoreboard queue, rather than retrieving mem_err_shift from the config object when the response comes in. (2) Memory request goes out. Sequence changes. Memory request is handled (with new mem_err_shift). Scoreboard sees the result. New sequence generates its first item. In this case, the scoreboard will expect the old mem_err_shift and see the new one. To fix this, we add an extra entry to the list of valid states in the scoreboard if needed so that we also check the mem_err_shift currently in the config object. You might worry about what happens if we have two back-to-back sequence changes that change mem_err_shift without ever changing seed: what happens if we have a situation like (1), but for the "middle" sequence. To avoid this problem, we actually add the extra entry in the fix for (2), so it will look like a new seed arrived as part of the middle sequence, so long as we have read at least one result (always true in the core sequence).
This commit is contained in:
parent
84bcf4973a
commit
94d5057168
1 changed files with 80 additions and 50 deletions
130
dv/uvm/icache/dv/env/ibex_icache_scoreboard.sv
vendored
130
dv/uvm/icache/dv/env/ibex_icache_scoreboard.sv
vendored
|
@ -22,22 +22,24 @@ class ibex_icache_scoreboard
|
|||
// A memory model. Note that the model is stateful (because of the seed). Since we need to try out
|
||||
// different seeds to check whether a fetch was correct, we need our own instance, rather than a
|
||||
// handle to the model used by the agent. The actual seed inside mem_model is ephemeral: we keep
|
||||
// track of state in mem_seeds, below.
|
||||
// track of state in mem_states, below.
|
||||
ibex_icache_mem_model #(BusWidth) mem_model;
|
||||
|
||||
// A queue of memory seeds.
|
||||
bit [31:0] mem_seeds[$] = {32'd0};
|
||||
// A queue of memory seeds, together with their associated mem_err_shift values. This gets a new
|
||||
// item every time we read a value from seed_fifo, and we store the associated mem_err_shift at
|
||||
// that point.
|
||||
bit [63:0] mem_states[$] = {};
|
||||
|
||||
// Tracks the next address we expect to see on a fetch. This gets reset to 'X, then is set to an
|
||||
// address after each branch transaction.
|
||||
logic [31:0] next_addr;
|
||||
|
||||
// This counter is used for tracking invalidations. When we see an invalidation happen, we set
|
||||
// this to the index of the last seed in mem_seeds. When the next branch happens, we clear out
|
||||
// this to the index of the last seed in mem_states. When the next branch happens, we clear out
|
||||
// memory seeds up to that index.
|
||||
int unsigned invalidate_seed = 0;
|
||||
|
||||
// This counter points to the index (in mem_seeds) of the seed that was in use when the last
|
||||
// This counter points to the index (in mem_states) of the seed that was in use when the last
|
||||
// branch happened. If the cache is supposed to be disabled, a fetch is only allowed to return
|
||||
// data corresponding to that seed or later.
|
||||
int unsigned last_branch_seed = 0;
|
||||
|
@ -124,6 +126,8 @@ class ibex_icache_scoreboard
|
|||
|
||||
task run_phase(uvm_phase phase);
|
||||
super.run_phase(phase);
|
||||
|
||||
mem_states.push_back({32'd0, cfg.mem_agent_cfg.mem_err_shift});
|
||||
tracking_reset();
|
||||
fork
|
||||
process_core_fifo();
|
||||
|
@ -154,13 +158,13 @@ class ibex_icache_scoreboard
|
|||
|
||||
if (invalidate_seed > 0) begin
|
||||
// We've seen an invalidate signal recently. Clear out any expired seeds.
|
||||
assert(invalidate_seed < mem_seeds.size);
|
||||
mem_seeds = mem_seeds[invalidate_seed:$];
|
||||
assert(invalidate_seed < mem_states.size);
|
||||
mem_states = mem_states[invalidate_seed:$];
|
||||
invalidate_seed = 0;
|
||||
end
|
||||
|
||||
assert(mem_seeds.size > 0);
|
||||
last_branch_seed = mem_seeds.size - 1;
|
||||
assert(mem_states.size > 0);
|
||||
last_branch_seed = mem_states.size - 1;
|
||||
|
||||
if (!enabled) no_cache = 1'b1;
|
||||
endtask
|
||||
|
@ -180,8 +184,8 @@ class ibex_icache_scoreboard
|
|||
endtask
|
||||
|
||||
task process_invalidate(ibex_icache_core_bus_item item);
|
||||
assert(mem_seeds.size > 0);
|
||||
invalidate_seed = mem_seeds.size - 1;
|
||||
assert(mem_states.size > 0);
|
||||
invalidate_seed = mem_states.size - 1;
|
||||
|
||||
not_invalidating = 1'b0;
|
||||
endtask
|
||||
|
@ -223,8 +227,11 @@ class ibex_icache_scoreboard
|
|||
int unsigned seed;
|
||||
forever begin
|
||||
seed_fifo.get(seed);
|
||||
`uvm_info(`gfn, $sformatf("received new seed: %08h", seed), UVM_HIGH)
|
||||
mem_seeds.push_back(seed);
|
||||
`uvm_info(`gfn,
|
||||
$sformatf("received new seed: %08h; mem_err_shift: %0d",
|
||||
seed, cfg.mem_agent_cfg.mem_err_shift),
|
||||
UVM_HIGH)
|
||||
mem_states.push_back({seed, cfg.mem_agent_cfg.mem_err_shift});
|
||||
end
|
||||
endtask
|
||||
|
||||
|
@ -240,8 +247,8 @@ class ibex_icache_scoreboard
|
|||
next_addr = 'X;
|
||||
|
||||
// Throw away any old seeds
|
||||
invalidate_seed = mem_seeds.size - 1;
|
||||
mem_seeds = mem_seeds[invalidate_seed:$];
|
||||
invalidate_seed = mem_states.size - 1;
|
||||
mem_states = mem_states[invalidate_seed:$];
|
||||
invalidate_seed = 0;
|
||||
last_branch_seed = 0;
|
||||
|
||||
|
@ -404,11 +411,11 @@ class ibex_icache_scoreboard
|
|||
// Do a single read from the memory model, rounding down the address and re-aligning the returned
|
||||
// data if necessary. Use is_fetch_compatible_1 to decide whether the result seen is compatible
|
||||
// with the seed.
|
||||
function automatic logic is_seed_compatible_1(logic [31:0] address,
|
||||
logic [31:0] seen_insn_data,
|
||||
logic seen_err,
|
||||
bit [31:0] seed,
|
||||
bit chatty);
|
||||
function automatic logic is_state_compatible_1(logic [31:0] address,
|
||||
logic [31:0] seen_insn_data,
|
||||
logic seen_err,
|
||||
bit [63:0] mem_state,
|
||||
bit chatty);
|
||||
int bus_shift;
|
||||
logic [31:0] addr_lo;
|
||||
int unsigned lo_bits_to_drop;
|
||||
|
@ -417,6 +424,9 @@ class ibex_icache_scoreboard
|
|||
|
||||
bit [BusWidth-1:0] rdata;
|
||||
|
||||
bit [31:0] seed;
|
||||
int unsigned mem_err_shift;
|
||||
|
||||
bus_shift = $clog2(BusWidth / 8);
|
||||
addr_lo = (address >> bus_shift) << bus_shift;
|
||||
|
||||
|
@ -426,12 +436,12 @@ class ibex_icache_scoreboard
|
|||
// compressed instruction, in which case we don't care about the top bits anyway.
|
||||
lo_bits_to_drop = 8 * (address - addr_lo);
|
||||
|
||||
{seed, mem_err_shift} = mem_state;
|
||||
rdata = mem_model.read_data(seed, addr_lo) >> lo_bits_to_drop;
|
||||
|
||||
return is_fetch_compatible_1(seen_insn_data,
|
||||
seen_err,
|
||||
mem_model.is_either_error(seed, addr_lo,
|
||||
cfg.mem_agent_cfg.mem_err_shift),
|
||||
mem_model.is_either_error(seed, addr_lo, mem_err_shift),
|
||||
rdata[31:0],
|
||||
seed,
|
||||
chatty);
|
||||
|
@ -440,13 +450,13 @@ class ibex_icache_scoreboard
|
|||
// Do a pair of reads from the memory model with the given pair of seeds to model a misaligned
|
||||
// access. Glue together the results and pass them to is_fetch_compatible_2 to decide whether the
|
||||
// result seen is compatible with the given pair of seeds.
|
||||
function automatic logic is_seed_compatible_2(logic [31:0] address,
|
||||
logic [31:0] seen_insn_data,
|
||||
logic seen_err_plus2,
|
||||
function automatic logic is_state_compatible_2(logic [31:0] address,
|
||||
logic [31:0] seen_insn_data,
|
||||
logic seen_err_plus2,
|
||||
|
||||
bit [31:0] seed_lo,
|
||||
bit [31:0] seed_hi,
|
||||
bit chatty);
|
||||
bit [63:0] mem_state_lo,
|
||||
bit [63:0] mem_state_hi,
|
||||
bit chatty);
|
||||
int bus_shift;
|
||||
logic [31:0] addr_lo, addr_hi;
|
||||
|
||||
|
@ -456,6 +466,9 @@ class ibex_icache_scoreboard
|
|||
|
||||
int unsigned lo_bits_to_take, lo_bits_to_drop;
|
||||
|
||||
bit [31:0] seed_lo, seed_hi;
|
||||
int unsigned mem_err_shift_lo, mem_err_shift_hi;
|
||||
|
||||
bus_shift = $clog2(BusWidth / 8);
|
||||
addr_lo = (address >> bus_shift) << bus_shift;
|
||||
addr_hi = addr_lo + (32'd1 << bus_shift);
|
||||
|
@ -469,14 +482,17 @@ class ibex_icache_scoreboard
|
|||
lo_bits_to_take = 8 * (address - addr_lo);
|
||||
lo_bits_to_drop = BusWidth - lo_bits_to_take;
|
||||
|
||||
{seed_lo, mem_err_shift_lo} = mem_state_lo;
|
||||
{seed_hi, mem_err_shift_hi} = mem_state_hi;
|
||||
|
||||
// Do the first read (from the low address) and shift right to drop the bits that we don't need.
|
||||
exp_err_lo = mem_model.is_either_error(seed_lo, addr_lo, cfg.mem_agent_cfg.mem_err_shift);
|
||||
exp_err_lo = mem_model.is_either_error(seed_lo, addr_lo, mem_err_shift_lo);
|
||||
rdata = mem_model.read_data(seed_lo, addr_lo) >> lo_bits_to_drop;
|
||||
exp_data = rdata[31:0];
|
||||
|
||||
// Now do the second read (from the upper address). Shift the result up by lo_bits_to_take,
|
||||
// which will discard some top bits. Then extract 32 bits and OR with what we have so far.
|
||||
exp_err_hi = mem_model.is_either_error(seed_hi, addr_hi, cfg.mem_agent_cfg.mem_err_shift);
|
||||
exp_err_hi = mem_model.is_either_error(seed_hi, addr_hi, mem_err_shift_hi);
|
||||
rdata = mem_model.read_data(seed_hi, addr_hi) << lo_bits_to_take;
|
||||
exp_data = exp_data | rdata[31:0];
|
||||
|
||||
|
@ -485,17 +501,17 @@ class ibex_icache_scoreboard
|
|||
seed_lo, seed_hi, chatty);
|
||||
endfunction
|
||||
|
||||
// The logic to check whether a fetch that's been seen is compatible with some seed that's visible
|
||||
// at the moment.
|
||||
// The logic to check whether a fetch that's been seen is compatible with some memory state that's
|
||||
// visible at the moment.
|
||||
function automatic bit check_compatible_1(logic [31:0] address,
|
||||
logic [31:0] seen_insn_data,
|
||||
logic seen_err,
|
||||
int unsigned min_idx,
|
||||
bit chatty);
|
||||
|
||||
for (int unsigned i = min_idx; i < mem_seeds.size; i++) begin
|
||||
if (is_seed_compatible_1(address, seen_insn_data, seen_err, mem_seeds[i], chatty)) begin
|
||||
last_fetch_age = mem_seeds.size - 1 - i;
|
||||
for (int unsigned i = min_idx; i < mem_states.size; i++) begin
|
||||
if (is_state_compatible_1(address, seen_insn_data, seen_err, mem_states[i], chatty)) begin
|
||||
last_fetch_age = mem_states.size - 1 - i;
|
||||
return 1'b1;
|
||||
end
|
||||
end
|
||||
|
@ -503,29 +519,29 @@ class ibex_icache_scoreboard
|
|||
return 1'b0;
|
||||
endfunction
|
||||
|
||||
// The logic to check whether a fetch that's been seen is compatible with some pair of seeds that
|
||||
// are visible at the moment.
|
||||
// The logic to check whether a fetch that's been seen is compatible with some pair of memory
|
||||
// states that are visible at the moment.
|
||||
function automatic bit check_compatible_2(logic [31:0] address,
|
||||
logic [31:0] seen_insn_data,
|
||||
logic seen_err_plus2,
|
||||
int unsigned min_idx,
|
||||
bit chatty);
|
||||
|
||||
// We want to iterate over all pairs of seeds. We can do this with a nested pair of foreach
|
||||
// We want to iterate over all pairs of states. We can do this with a nested pair of foreach
|
||||
// loops, but we expect that usually we'll get a hit on the "diagonal", so we check that first.
|
||||
for (int unsigned i = min_idx; i < mem_seeds.size; i++) begin
|
||||
if (is_seed_compatible_2(address, seen_insn_data, seen_err_plus2,
|
||||
mem_seeds[i], mem_seeds[i], chatty)) begin
|
||||
last_fetch_age = mem_seeds.size - 1 - i;
|
||||
for (int unsigned i = min_idx; i < mem_states.size; i++) begin
|
||||
if (is_state_compatible_2(address, seen_insn_data, seen_err_plus2,
|
||||
mem_states[i], mem_states[i], chatty)) begin
|
||||
last_fetch_age = mem_states.size - 1 - i;
|
||||
return 1'b1;
|
||||
end
|
||||
end
|
||||
for (int unsigned i = min_idx; i < mem_seeds.size; i++) begin
|
||||
for (int unsigned j = min_idx; j < mem_seeds.size; j++) begin
|
||||
for (int unsigned i = min_idx; i < mem_states.size; i++) begin
|
||||
for (int unsigned j = min_idx; j < mem_states.size; j++) begin
|
||||
if (i != j) begin
|
||||
if (is_seed_compatible_2(address, seen_insn_data, seen_err_plus2,
|
||||
mem_seeds[i], mem_seeds[j], chatty)) begin
|
||||
last_fetch_age = mem_seeds.size - 1 - (i < j ? i : j);
|
||||
if (is_state_compatible_2(address, seen_insn_data, seen_err_plus2,
|
||||
mem_states[i], mem_states[j], chatty)) begin
|
||||
last_fetch_age = mem_states.size - 1 - (i < j ? i : j);
|
||||
return 1'b1;
|
||||
end
|
||||
end
|
||||
|
@ -540,12 +556,26 @@ class ibex_icache_scoreboard
|
|||
logic uncompressed;
|
||||
int unsigned min_idx;
|
||||
|
||||
bit [31:0] last_seed;
|
||||
int unsigned last_mem_err_shift;
|
||||
|
||||
misaligned = (item.address & 3) != 0;
|
||||
good_bottom_word = (~item.err) | item.err_plus2;
|
||||
uncompressed = item.insn_data[1:0] == 2'b11;
|
||||
|
||||
min_idx = no_cache ? last_branch_seed : 0;
|
||||
`DV_CHECK_LT_FATAL(min_idx, mem_seeds.size);
|
||||
`DV_CHECK_LT_FATAL(min_idx, mem_states.size);
|
||||
|
||||
// If the current value of mem_err_shift in the configuration object doesn't match the back of
|
||||
// mem_states, append a fake entry with the same seed, but the current mem_err_shift.
|
||||
{last_seed, last_mem_err_shift} = mem_states[mem_states.size() - 1];
|
||||
if (last_mem_err_shift != cfg.mem_agent_cfg.mem_err_shift) begin
|
||||
`uvm_info(`gfn,
|
||||
$sformatf("Change of mem_err_shift (%0d -> %0d) with no new seed.",
|
||||
last_mem_err_shift, cfg.mem_agent_cfg.mem_err_shift),
|
||||
UVM_HIGH)
|
||||
mem_states.push_back({last_seed, cfg.mem_agent_cfg.mem_err_shift});
|
||||
end
|
||||
|
||||
if (misaligned && good_bottom_word && uncompressed) begin
|
||||
// It looks like this was a misaligned fetch (so came from two fetches from memory) and the
|
||||
|
@ -571,9 +601,9 @@ class ibex_icache_scoreboard
|
|||
end
|
||||
|
||||
// All is well. The call to check_compatible_* will have set last_fetch_age. The maximum
|
||||
// possible value is mem_seeds.size - 1 - min_idx. If this is positive, count whether we got a
|
||||
// possible value is mem_states.size - 1 - min_idx. If this is positive, count whether we got a
|
||||
// value corresponding to an old seed or not.
|
||||
if (mem_seeds.size > min_idx + 1) begin
|
||||
if (mem_states.size > min_idx + 1) begin
|
||||
possible_old_count += 1;
|
||||
if (last_fetch_age > 0) actual_old_count += 1;
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue