diff --git a/src/test/obi_test.sv b/src/test/obi_test.sv index d0911ae..9f6bb3e 100644 --- a/src/test/obi_test.sv +++ b/src/test/obi_test.sv @@ -179,6 +179,9 @@ package obi_test; parameter int unsigned RMinWaitCycles = 0, parameter int unsigned RMaxWaitCycles = 100 ); + + localparam int unsigned IdCount = 1 << ObiCfg.IdWidth; + typedef obi_test::obi_driver #( .ObiCfg ( ObiCfg ), .obi_a_optional_t ( obi_a_optional_t ), @@ -193,6 +196,13 @@ package obi_test; obi_driver_t drv; addr_t a_queue[$]; + // Scoreboard for id usage tracking. Values indicate: + // 0: Id is not used by any outstanding request, + // >0: Id is used by that many non-atomic requests, + // -1: Id is used by an atomic request. + int aid_atop_scoreboard[IdCount]; + std::semaphore atop_sb_sem; + function new( virtual OBI_BUS_DV #( .OBI_CFG ( ObiCfg ), @@ -203,12 +213,34 @@ package obi_test; ); this.drv = new(obi); this.name = name; + this.aid_atop_scoreboard = '{default: '0}; + this.atop_sb_sem = new(1); assert(ObiCfg.AddrWidth != 0) else $fatal(1, "ObiCfg.AddrWidth must be non-zero!"); assert(ObiCfg.DataWidth != 0) else $fatal(1, "ObiCfg.DataWidth must be non-zero!"); endfunction function void reset(); drv.reset_manager(); + this.a_queue.delete(); + this.aid_atop_scoreboard = '{default: '0}; + endfunction + + function bit atop_id_available(); + for (int unsigned i = 0; i < IdCount; i++) begin + if (this.aid_atop_scoreboard[i] == 0) begin + return 1'b1; + end + end + return 1'b0; + endfunction + + function bit non_atop_id_available(); + for (int unsigned i = 0; i < IdCount; i++) begin + if (this.aid_atop_scoreboard[i] >= 0) begin + return 1'b1; + end + end + return 1'b0; endfunction task automatic rand_wait(input int unsigned min, input int unsigned max); @@ -221,6 +253,57 @@ package obi_test; repeat (cycles) @(posedge this.drv.obi.clk_i); endtask + task automatic legalize_id(input obi_a_optional_t a_optional, + output logic [ObiCfg.IdWidth-1:0] aid); + automatic bit is_atop; + if (ObiCfg.OptionalCfg.UseAtop) begin + is_atop = (a_optional.atop != obi_pkg::ATOPNONE); + end else begin + is_atop = 1'b0; + end + + // Requirement R-12: An OBI manager shall prevent initiating a transaction if it would + // lead to multiple outstanding transactions with the same aid of which at least one + // transaction is an atomic transaction. + forever begin + this.atop_sb_sem.get(); + if (is_atop) begin + if (!this.atop_id_available()) begin + // No legal ID available, wait until one becomes available + this.atop_sb_sem.put(); + rand_wait(1, 1); + continue; + end + + // Get a random available id + assert(std::randomize(aid) with { + this.aid_atop_scoreboard[aid] == 0; + }); + + // Mark aid used by atomic + this.aid_atop_scoreboard[aid] = -1; + + end else begin + if (!this.non_atop_id_available()) begin + // No legal ID available, wait until one becomes available + this.atop_sb_sem.put(); + rand_wait(1, 1); + continue; + end + + // Get a random available id + assert(std::randomize(aid) with { + this.aid_atop_scoreboard[aid] != -1; + }); + + // Increment usage of aid for non-atomic + this.aid_atop_scoreboard[aid]++; + end + this.atop_sb_sem.put(); + break; + end + endtask + task automatic send_as(input int unsigned n_reqs); automatic addr_t a_addr; automatic logic a_we; @@ -228,16 +311,108 @@ package obi_test; automatic logic [ ObiCfg.DataWidth-1:0] a_wdata; automatic logic [ ObiCfg.IdWidth-1:0] a_aid; automatic obi_a_optional_t a_optional; + automatic int unsigned be_low, be_width; repeat (n_reqs) begin rand_wait(AMinWaitCycles, AMaxWaitCycles); - a_addr = addr_t'($urandom_range(MinAddr, MaxAddr)); - a_we = $urandom() % 2; - a_be = $urandom() % (1 << (ObiCfg.DataWidth/8)); - a_wdata = $urandom() % (1 << ObiCfg.DataWidth); - a_aid = $urandom() % (1 << ObiCfg.IdWidth); - a_optional = obi_a_optional_t'($urandom()); + assert(std::randomize(a_we)); + assert(std::randomize(a_wdata)); + if (ObiCfg.BeFull) begin + // Requirement R-8: No restrictions on BE + assert(std::randomize(a_be)); + // Back-calculate be_low and be_width + be_low = a_be == '0 ? 0 : $clog2(a_be & -a_be); + be_width = $clog2(a_be + 1) - be_low; + end else begin + // Requirement R-7: + // - At least one of the be bits shall be set to 1. + // - The 1’s in be shall be contiguous. + assert(std::randomize(be_low, be_width) with { + be_low < ObiCfg.DataWidth/8; + be_low + be_width <= ObiCfg.DataWidth/8; + be_width > 0; + }); + a_be = ((1 << (be_width)) - 1) << be_low; + end + // Requirement R-9: If i is the index of the least signification bit in be that is 1, + // then the least significant addr bits shall be <= i. + assert(std::randomize(a_addr) with { + a_addr >= MinAddr; + a_addr <= MaxAddr; + a_addr[$clog2(ObiCfg.DataWidth/8)-1:0] <= be_low; + }); + + a_optional = 'x; + if (ObiCfg.OptionalCfg.UseAtop) begin + automatic obi_pkg::atop_t atop; + // Requirement R-11.3: The transaction associated with a LR.W shall have we = 0; + // the other atomic memory transactions (i.e. SC.W, AMO*) shall use we = 1. + assert(std::randomize(atop) with { + a_we -> atop dist { + ATOPSC, AMOSWAP, AMOADD, AMOXOR, AMOAND, AMOOR, AMOMIN, + AMOMAX, AMOMINU, AMOMAXU, ATOPNONE + }; + !a_we ->atop dist { + ATOPLR, ATOPNONE + }; + }); + a_optional.atop = atop; + if (a_optional.atop != ATOPNONE) begin + // Requirement R-11.5: The byte enables be used in an atomic memory transaction shall + // indicate a word or double-word transfer as implied by the related *.W or *.D + // instruction. + if (ObiCfg.DataWidth == 32) begin + a_be = '1; + end else begin + assert(std::randomize(be_low, be_width) with { + be_width == 4 || be_width == 8; + be_low % be_width == 0; + be_low + be_width <= ObiCfg.DataWidth/8; + }); + a_be = ((1 << (be_width)) - 1) << be_low; + end + // Requirement R-11.4: The address addr used in an atomic memory transaction shall be + // naturally aligned. + a_addr &= ~(be_width - 1); + end + end + if (ObiCfg.OptionalCfg.UseMemtype) begin + automatic obi_pkg::memtype_t memtype; + assert(std::randomize(memtype)); + a_optional.memtype = memtype; + end + if (ObiCfg.OptionalCfg.UseProt) begin + automatic obi_pkg::prot_t prot; + assert(std::randomize(prot)); + a_optional.prot = prot; + end + if (ObiCfg.OptionalCfg.UseDbg) begin + automatic logic dbg; + assert(std::randomize(dbg)); + a_optional.dbg = dbg; + end + if (ObiCfg.OptionalCfg.MidWidth > 0) begin + automatic logic [$bits(a_optional.mid)-1:0] a_mid; + assert(std::randomize(a_mid)); + a_optional.mid = a_mid; + end + if (ObiCfg.OptionalCfg.AUserWidth > 0) begin + automatic logic [$bits(a_optional.auser)-1:0] a_auser; + assert(std::randomize(a_auser)); + a_optional.auser = a_auser; + end + if (ObiCfg.OptionalCfg.WUserWidth > 0) begin + automatic logic [$bits(a_optional.wuser)-1:0] a_wuser; + assert(std::randomize(a_wuser)); + a_optional.wuser = a_wuser; + end + if (ObiCfg.Integrity && ObiCfg.OptionalCfg.AChkWidth > 0) begin + automatic logic [$bits(a_optional.achk)-1:0] achk; + assert(std::randomize(achk)); + a_optional.achk = achk; + end + legalize_id(a_optional, a_aid); this.a_queue.push_back(a_addr); this.drv.send_a(a_addr, a_we, a_be, a_wdata, a_aid, a_optional); @@ -251,13 +426,24 @@ package obi_test; automatic logic r_err; automatic obi_r_optional_t r_optional; repeat (n_rsps) begin - wait (a_queue.size() > 0); + wait (this.a_queue.size() > 0); a_addr = this.a_queue.pop_front(); if (ObiCfg.UseRReady) begin rand_wait(RMinWaitCycles, RMaxWaitCycles); end drv.recv_r(r_rdata, r_rid, r_err, r_optional); + + // Update atomics scoreboard + this.atop_sb_sem.get(); + if (aid_atop_scoreboard[r_rid] > 0) begin + // Non-atomic response + aid_atop_scoreboard[r_rid]--; + end else begin + // Atomic response + aid_atop_scoreboard[r_rid] = 0; + end + this.atop_sb_sem.put(); end endtask