Add clocking blocks to the ibex_icache_core_agent

Since this agent doesn't currently do any monitoring (will be
addressed in a later patch), the monitor_cb clocking block doesn't do
very much at the moment.

The driver_cb clocking block *is* used, though. The input lines are just
those needed to drive things correctly (ready is needed to do
ready/valid signalling properly; err is needed to abort instruction
fetches and do a branch after an error).

I've marked the output signals as negedge: this doesn't really make
any difference to simulation results, since the design samples
everything on posedge, but makes it rather easier to read dumped
waves.
This commit is contained in:
Rupert Swarbrick 2020-04-16 11:21:20 +01:00 committed by Rupert Swarbrick
parent bce473b1dc
commit 5a188342e7
2 changed files with 75 additions and 31 deletions

View file

@ -6,9 +6,14 @@ class ibex_icache_core_driver extends dv_base_driver #(ibex_icache_core_item, ib
`uvm_component_utils(ibex_icache_core_driver)
`uvm_component_new
// The current state of the enable pin: this is toggled by sequence items, but the current state
// is stored on the driver.
bit enable;
// reset signals
virtual task automatic reset_signals();
cfg.vif.reset();
enable = 1'b0;
endtask
// drive trans received from sequencer
@ -34,11 +39,15 @@ class ibex_icache_core_driver extends dv_base_driver #(ibex_icache_core_item, ib
// (enable/disable, invalidate, read instructions).
virtual task automatic drive_branch_trans(ibex_icache_core_item req);
// Make sure that req is enabled (has no effect unless this is the first transaction)
cfg.vif.req <= 1'b1;
cfg.vif.driver_cb.req <= 1'b1;
// Toggle the enable state if necessary and drive its pin
if (req.toggle_enable)
enable = ~enable;
cfg.vif.driver_cb.enable <= enable;
fork
cfg.vif.branch_to(req.branch_addr);
if (req.toggle_enable) toggle_enable();
if (req.invalidate) invalidate();
read_insns(req.num_insns);
join
@ -62,25 +71,24 @@ class ibex_icache_core_driver extends dv_base_driver #(ibex_icache_core_item, ib
[100:200] :/ 2,
[1000:1200] :/ 1 };)
// Toggle the enable state if necessary and drive its pin
if (req.toggle_enable)
enable = ~enable;
cfg.vif.driver_cb.enable <= enable;
fork
if (req_low_cycles > 0) lower_req(req_low_cycles);
if (req.toggle_enable) toggle_enable();
if (req.invalidate) invalidate();
join
read_insns(req.num_insns);
endtask
// Toggle whether the cache is enabled
virtual task automatic toggle_enable();
cfg.vif.enable <= ~cfg.vif.enable;
endtask
// Read up to num_insns instructions from the cache, stopping early on an error
virtual task automatic read_insns(int num_insns);
for (int i = 0; i < num_insns; i++) begin
read_insn();
// Spot any error and exit early
if (cfg.vif.err)
if (cfg.vif.driver_cb.err)
break;
end
endtask
@ -91,7 +99,7 @@ class ibex_icache_core_driver extends dv_base_driver #(ibex_icache_core_item, ib
// Maybe (1 time in 10) wait for a valid signal before even considering asserting ready.
if ($urandom_range(9) == 0)
wait (cfg.vif.valid);
wait (cfg.vif.driver_cb.valid);
// Then pick how long we wait before asserting that we are ready.
//
@ -99,21 +107,21 @@ class ibex_icache_core_driver extends dv_base_driver #(ibex_icache_core_item, ib
cfg.vif.wait_clks($urandom_range(3));
// Assert ready and then wait until valid
cfg.vif.ready <= 1'b1;
cfg.vif.driver_cb.ready <= 1'b1;
while (1'b1) begin
@(posedge cfg.vif.clk);
if (cfg.vif.valid)
@(cfg.vif.driver_cb);
if (cfg.vif.driver_cb.valid)
break;
end
cfg.vif.ready <= 1'b0;
cfg.vif.driver_cb.ready <= 1'b0;
endtask
// Lower the req line for the given number of cycles
virtual task automatic lower_req(int unsigned num_cycles);
cfg.vif.req <= 1'b0;
repeat (num_cycles) @(posedge cfg.vif.clk);
cfg.vif.req <= 1'b1;
cfg.vif.driver_cb.req <= 1'b0;
repeat (num_cycles) @(cfg.vif.driver_cb);
cfg.vif.driver_cb.req <= 1'b1;
endtask
// Raise the invalidate line for a randomly chosen number of cycles > 0.

View file

@ -27,16 +27,52 @@ interface ibex_icache_core_if (input clk);
// itself)
logic busy;
// A clocking block for test code that drives the interface
clocking driver_cb @(posedge clk);
// Drive signals on the following negedge: this isn't needed by the design, but makes it
// slightly easier to read dumped waves.
default output negedge;
output req;
output branch;
output branch_addr;
output ready;
input valid;
input err;
output enable;
output invalidate;
endclocking
// A clocking block for test code that needs to monitor the interface
clocking monitor_cb @(posedge clk);
input req;
input branch;
input branch_addr;
input ready;
input valid;
input rdata;
input addr;
input err;
input err_plus2;
input enable;
input invalidate;
input busy;
endclocking
// Drive the branch pin for a single cycle, redirecting the cache to the given instruction
// address.
task automatic branch_to(logic [31:0] addr);
branch <= 1'b1;
branch_addr <= addr;
driver_cb.branch <= 1'b1;
driver_cb.branch_addr <= addr;
@(posedge clk);
@(driver_cb);
branch <= 1'b0;
branch_addr <= 'X;
driver_cb.branch <= 1'b0;
driver_cb.branch_addr <= 'X;
endtask
// Raise a pulse on the invalidate line for the given number of cycles.
@ -44,24 +80,24 @@ interface ibex_icache_core_if (input clk);
// A one-cycle pulse will start an invalidation, but testing might want a longer pulse (which the
// cache should support)
task automatic invalidate_pulse(int num_cycles);
invalidate <= 1'b1;
repeat (num_cycles) @(posedge clk);
invalidate <= 1'b0;
driver_cb.invalidate <= 1'b1;
wait_clks(num_cycles);
driver_cb.invalidate <= 1'b0;
endtask
// A task that waits for num_clks posedges on the clk signal
task automatic wait_clks(int num_clks);
repeat (num_clks) @(posedge clk);
repeat (num_clks) @(driver_cb);
endtask
// Reset all the signals from the core to the cache (the other direction is controlled by the
// DUT)
task automatic reset();
req <= 1'b0;
branch <= 1'b0;
ready <= 1'b0;
enable <= 1'b0;
invalidate <= 1'b0;
driver_cb.req <= 1'b0;
driver_cb.branch <= 1'b0;
driver_cb.ready <= 1'b0;
driver_cb.enable <= 1'b0;
driver_cb.invalidate <= 1'b0;
endtask
endinterface