mirror of
https://github.com/lowRISC/ibex.git
synced 2025-04-23 21:39:13 -04:00
Fix scheduling for PMP errors in icache testbench
The previous approach didn't work because of possible ordering problems that make it difficult to convert between "combinatorial signals" and transactions. When things are aligned with a clock edge, you solve this problem with a clocking block (which, by default, guarantees to sample "just after" the clock edge, once the signals have settled). Since this signal is not aligned with a clock, we have to be a little more careful. Before and after this patch, the code in the monitor is "eager" and might send glitchy transactions through to the sequence, which ends up passing the data back to the driver. This patch teaches the driver to cope with that, by passing the address that caused a PMP error as part of the memory response. Armed with that address, the driver can figure out whether the error flag applies to what's currently on the bus, or whether it's a few delta cycles behind, in which case it can safely just drop the glitch.
This commit is contained in:
parent
82337cf1a2
commit
8f348d73d1
3 changed files with 38 additions and 33 deletions
|
@ -18,7 +18,7 @@ class ibex_icache_mem_driver
|
|||
int unsigned max_grant_delay = 10;
|
||||
|
||||
mailbox #(ibex_icache_mem_resp_item) rdata_queue;
|
||||
bit pmp_driven;
|
||||
mailbox #(bit [31:0]) pmp_queue;
|
||||
|
||||
`uvm_component_utils(ibex_icache_mem_driver)
|
||||
`uvm_component_new
|
||||
|
@ -26,7 +26,7 @@ class ibex_icache_mem_driver
|
|||
function void build_phase(uvm_phase phase);
|
||||
super.build_phase(phase);
|
||||
rdata_queue = new("rdata_queue");
|
||||
pmp_driven = 1'b0;
|
||||
pmp_queue = new("pmp_queue");
|
||||
endfunction
|
||||
|
||||
virtual task reset_signals();
|
||||
|
@ -38,6 +38,7 @@ class ibex_icache_mem_driver
|
|||
fork
|
||||
drive_grant();
|
||||
take_requests();
|
||||
drive_pmp();
|
||||
drive_responses();
|
||||
join
|
||||
endtask
|
||||
|
@ -72,11 +73,11 @@ class ibex_icache_mem_driver
|
|||
|
||||
// Is this a request or a grant?
|
||||
if (!req.is_grant) begin
|
||||
// If a request, we'll deal with it immediately, spawning a process that drives the PMP line
|
||||
// until the request goes away.
|
||||
fork
|
||||
drive_pmp(req.err);
|
||||
join_none
|
||||
// If a request, it's ignored unless it causes a PMP error. In that case, we push the
|
||||
// address onto pmp_queue.
|
||||
if (req.err) begin
|
||||
pmp_queue.put(req.address);
|
||||
end
|
||||
end else begin
|
||||
// If a grant, we take a copy and add it to the response queue (handled by drive_responses)
|
||||
$cast(rsp, req.clone());
|
||||
|
@ -102,35 +103,33 @@ class ibex_icache_mem_driver
|
|||
|
||||
// Drive the PMP line.
|
||||
//
|
||||
// This task gets spawned by take_requests each time a new address comes in. The driver should
|
||||
// have at most one instance running at once.
|
||||
// This task samples from a "PMP" mailbox, which gets a new item from take_requests each time a
|
||||
// new request comes in whose address would be squashed by the PMP.
|
||||
//
|
||||
// If err is false, there is nothing to do. If err is true, it sets the PMP error flag and then
|
||||
// waits until either req is de-asserted or the address changes, at which point it clears the flag
|
||||
// and exits.
|
||||
task automatic drive_pmp(bit err);
|
||||
// Since this isn't synchronised with a clock edge, it's not really possible to avoid glitches and
|
||||
// to only add items when the "right" address has arrived. Instead, this task gets the address
|
||||
// that caused the error. As soon as req is low OR the address on the bus doesn't match, this
|
||||
// driver clears the address and exits. The idea is that if stuff gets out of sync for a delta
|
||||
// cycle or two, drive_pmp can discard items to catch up.
|
||||
task automatic drive_pmp();
|
||||
bit [31:0] address;
|
||||
|
||||
// This is a simple check to make sure that only one instance of the drive_pmp task is running
|
||||
// at a time. If not, you'll have multiple drivers for the cfg.vif.pmp_err signal, which is
|
||||
// probably going to be very confusing. The logic in the monitor's collect_requests() task is
|
||||
// supposed to be in sync with this code so that this can't happen, but it can't hurt to make
|
||||
// sure.
|
||||
if (pmp_driven) begin
|
||||
`uvm_error(`gfn, "drive_pmp is not re-entrant: bug in monitor?")
|
||||
return;
|
||||
forever begin
|
||||
pmp_queue.get(address);
|
||||
|
||||
// The address we just got means "The req line should be high and the address should be this
|
||||
// one. Squash the request with a PMP error".
|
||||
//
|
||||
// If the req line is low, or the address doesn't match, skip this item.
|
||||
if ((!cfg.vif.req) || (cfg.vif.addr != address)) continue;
|
||||
|
||||
cfg.vif.pmp_err <= 1'b1;
|
||||
|
||||
// Wait for an address change or req to de-assert
|
||||
@(negedge cfg.vif.req or cfg.vif.addr);
|
||||
|
||||
cfg.vif.pmp_err <= 1'b0;
|
||||
end
|
||||
|
||||
if (! err)
|
||||
return;
|
||||
|
||||
pmp_driven = 1'b1;
|
||||
cfg.vif.pmp_err <= 1'b1;
|
||||
|
||||
// Wait for an address change or req to de-assert
|
||||
@(negedge cfg.vif.req or cfg.vif.addr);
|
||||
|
||||
cfg.vif.pmp_err <= 1'b0;
|
||||
pmp_driven = 1'b0;
|
||||
endtask
|
||||
|
||||
endclass
|
||||
|
|
|
@ -18,6 +18,9 @@ class ibex_icache_mem_resp_item
|
|||
// If true, drive either a PMP error (if !is_grant) or respond later with a memory error.
|
||||
bit err;
|
||||
|
||||
// The address of the memory response (only used for requests that trigger a PMP error)
|
||||
bit [31:0] address;
|
||||
|
||||
// Only has an effect if is_grant. The number of cycles to wait between reading this from the
|
||||
// queue and responding with it.
|
||||
rand int unsigned delay;
|
||||
|
@ -42,6 +45,7 @@ class ibex_icache_mem_resp_item
|
|||
`uvm_object_utils_begin(ibex_icache_mem_resp_item)
|
||||
`uvm_field_int (is_grant, UVM_DEFAULT)
|
||||
`uvm_field_int (err, UVM_DEFAULT)
|
||||
`uvm_field_int (address, UVM_DEFAULT | UVM_HEX)
|
||||
`uvm_field_int (delay, UVM_DEFAULT)
|
||||
`uvm_field_int (rdata, UVM_DEFAULT | UVM_HEX)
|
||||
`uvm_object_utils_end
|
||||
|
|
|
@ -29,6 +29,7 @@ class ibex_icache_mem_resp_seq extends ibex_icache_mem_base_seq;
|
|||
// address. The other fields are ignored.
|
||||
resp_item.is_grant = 1'b0;
|
||||
resp_item.err = mem_model.is_pmp_error(req_item.address);
|
||||
resp_item.address = req_item.address;
|
||||
resp_item.rdata = 'X;
|
||||
|
||||
end else begin
|
||||
|
@ -42,6 +43,7 @@ class ibex_icache_mem_resp_seq extends ibex_icache_mem_base_seq;
|
|||
|
||||
resp_item.is_grant = 1'b1;
|
||||
resp_item.err = mem_model.is_mem_error(req_item.address);
|
||||
resp_item.address = req_item.address;
|
||||
resp_item.rdata = resp_item.err ? 'X : mem_model.read_data(req_item.address);
|
||||
end
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue