Compare commits

...

6 Commits

Author SHA1 Message Date
David Anderson e5aabcbf4a vram/MemArbiter: remove unnecessary wires and fields
While iterating many redundant bits were added. This removes them again,
keeping just (I think) the minimum necessary for the arbitrators to work.
2024-09-08 15:13:47 -07:00
David Anderson 519eddc552 vram/MemArbiter: fix bug with write conflict avoidance
Now that all arbiter ports can request to write, arbitration must check
both (write, read) and (read, write) conflicts, not just (write, read).
2024-09-08 15:02:55 -07:00
David Anderson 191cd1bfa2 vram: document MemArbiter, fix round-robin ordering bug 2024-09-08 14:10:52 -07:00
David Anderson f31f64f5a2 vram: refactor MemArbiter into separate arbiters
Rather than hardcode one architecture for GARY, the arbiters
are now split and can be allocated per-port. The arbiter interface
includes plumbing so that one arbiter can propagate a write conflict
to another, so it can still implement multi-port arbitration as long
as every client is statically allocated to one port.
2024-09-08 13:26:25 -07:00
David Anderson 2760bad965 vram: move VRAM to VRAMCore, in prep for arbitrated VRAM 2024-09-08 09:28:28 -07:00
David Anderson 69b7ce7f9e vram: adjust the MemoryArbiterClient interface a little 2024-09-08 09:27:25 -07:00
5 changed files with 484 additions and 444 deletions

View File

@ -4,6 +4,7 @@ import MemArbiter::*;
import Vector::*; import Vector::*;
import DReg::*; import DReg::*;
import DelayLine::*; import DelayLine::*;
import Connectable::*;
typedef UInt#(2) Addr; typedef UInt#(2) Addr;
@ -22,70 +23,51 @@ endinterface
(* synthesize, clock_prefix="clk_25mhz", reset_prefix="rst_btn" *) (* synthesize, clock_prefix="clk_25mhz", reset_prefix="rst_btn" *)
module mkTop(Top); module mkTop(Top);
Vector#(2, Reg#(Maybe#(MemArbiterWrite#(Addr)))) wrin <- replicateM(mkDReg(tagged Invalid)); Vector#(6, Reg#(Maybe#(MemArbiterOp#(Addr)))) wrin <- replicateM(mkDReg(tagged Invalid));
Vector#(4, Reg#(Maybe#(Addr))) rdin <- replicateM(mkDReg(tagged Invalid));
MemArbiter#(Addr) ret <- mkMemArbiter(); MemArbiter#(3, Addr) portA <- mkPriorityMemArbiter();
MemArbiter#(3, Addr) portB <- mkRoundRobinMemArbiter();
mkConnection(portA, portB);
let arbiters = append(portA.ports, portB.ports);
Reg#(Vector#(6, Bool)) ok <- mkReg(replicate(False)); Reg#(Vector#(6, Bool)) ok <- mkReg(replicate(False));
rule req_cpu (wrin[0] matches tagged Valid .req); for (Integer i=0; i<6; i=i+1) begin
ret.cpu.request(req); rule req (wrin[i] matches tagged Valid .req);
endrule arbiters[i].request(req);
rule req_debugger (wrin[1] matches tagged Valid .req);
ret.debugger.request(req);
endrule
rule req_palette (rdin[0] matches tagged Valid .addr);
ret.palette.request(addr);
endrule
rule req_tile1 (rdin[1] matches tagged Valid .addr);
ret.tile1.request(addr);
endrule
rule req_tile2 (rdin[2] matches tagged Valid .addr);
ret.tile2.request(addr);
endrule
rule req_sprite (rdin[3] matches tagged Valid .addr);
ret.sprite.request(addr);
endrule endrule
end
rule resp; rule resp;
Vector#(6, Bool) r = newVector; function Bool get(MemArbiterServer#(Addr) s);
r[0] = ret.cpu.grant(); return s.grant();
r[1] = ret.debugger.grant(); endfunction
r[2] = ret.palette.grant(); ok <= map(get, arbiters);
r[3] = ret.tile1.grant();
r[4] = ret.tile2.grant();
r[5] = ret.sprite.grant();
ok <= r;
endrule endrule
method Action cpu(Bool write, Addr addr); method Action cpu(Bool write, Addr addr);
wrin[0] <= tagged Valid MemArbiterWrite{write: write, addr: addr}; wrin[0] <= tagged Valid MemArbiterOp{write: write, addr: addr};
endmethod endmethod
method Action debugger(Bool write, Addr addr); method Action debugger(Bool write, Addr addr);
wrin[1] <= tagged Valid MemArbiterWrite{write: write, addr: addr}; wrin[1] <= tagged Valid MemArbiterOp{write: write, addr: addr};
endmethod endmethod
method Action palette(Addr addr); method Action palette(Addr addr);
rdin[0] <= tagged Valid addr; wrin[2] <= tagged Valid MemArbiterOp{write: False, addr: addr};
endmethod endmethod
method Action tile1(Addr addr); method Action tile1(Addr addr);
rdin[1] <= tagged Valid addr; wrin[3] <= tagged Valid MemArbiterOp{write: False, addr: addr};
endmethod endmethod
method Action tile2(Addr addr); method Action tile2(Addr addr);
rdin[2] <= tagged Valid addr; wrin[4] <= tagged Valid MemArbiterOp{write: False, addr: addr};
endmethod endmethod
method Action sprite(Addr addr); method Action sprite(Addr addr);
rdin[3] <= tagged Valid addr; wrin[5] <= tagged Valid MemArbiterOp{write: False, addr: addr};
endmethod endmethod
method Bit#(6) grants(); method Bit#(6) grants();

View File

@ -1,12 +1,12 @@
package Top; package Top;
import VRAM::*; import VRAMCore::*;
import ECP5_RAM::*; import ECP5_RAM::*;
import TriState::*; import TriState::*;
(* synthesize *) (* synthesize *)
module mkTop(VRAM); module mkTop(VRAMCore);
let _ret <- mkVRAM(112); let _ret <- mkVRAMCore(112);
return _ret; return _ret;
endmodule endmodule

View File

@ -3,30 +3,45 @@ package MemArbiter;
import Connectable::*; import Connectable::*;
import Vector::*; import Vector::*;
export MemArbiterWrite(..); export MemArbiterOp(..);
export MemArbiterServer(..); export MemArbiterServer(..);
export MemArbiterClient(..); export MemArbiterClient(..);
export MemArbiter(..); export MemArbiter(..), mkPriorityMemArbiter, mkRoundRobinMemArbiter;
export mkMemArbiter;
// A MemArbiterServer receives requests for memory access and emits // A MemArbiterOp is an operation that a client is seeking permission
// grants. // to perform.
interface MemArbiterServer#(type request); typedef struct {
method Action request(request req); Bool write;
addr addr;
} MemArbiterOp#(type addr) deriving (Bits, Eq, FShow);
function Bool mem_ops_conflict(Maybe#(MemArbiterOp#(addr)) a, Maybe#(MemArbiterOp#(addr)) b)
provisos(Eq#(addr));
if (a matches tagged Valid .ar &&& b matches tagged Valid .br &&& ar.addr == br.addr)
return ar.write || br.write;
else
return False;
endfunction
// A MemArbiterServer receives requests and emits grants.
(* always_ready *)
interface MemArbiterServer#(type addr);
method Action request(MemArbiterOp#(addr) req);
method Bool grant(); method Bool grant();
endinterface endinterface
// A MemArbiterClient emits requests for memory access and emits // A MemArbiterClient emits requests and receives grants.
// grants. interface MemArbiterClient#(type addr);
interface MemArbiterClient#(type request); method Maybe#(MemArbiterOp#(addr)) request();
method request request();
method Action grant(); method Action grant();
endinterface endinterface
instance Connectable#(MemArbiterClient#(req), MemArbiterServer#(req)); // Arbiter clients and servers can be connected in the obvious way.
module mkConnection(MemArbiterClient#(req) client, MemArbiterServer#(req) server, Empty ifc); instance Connectable#(MemArbiterClient#(addr), MemArbiterServer#(addr));
rule send_request; module mkConnection(MemArbiterClient#(addr) client, MemArbiterServer#(addr) server, Empty ifc);
server.request(client.request()); rule send_request (client.request matches tagged Valid .req);
server.request(req);
endrule endrule
rule send_grant (server.grant()); rule send_grant (server.grant());
@ -35,157 +50,173 @@ instance Connectable#(MemArbiterClient#(req), MemArbiterServer#(req));
endmodule endmodule
endinstance endinstance
typedef struct { // A MemArbiter manages concurrent access to a memory port.
Bool write; interface MemArbiter#(numeric type num_clients, type addr);
addr addr; // ports allow clients to request memory access.
} MemArbiterWrite#(type addr) deriving (Bits, Eq); interface Vector#(num_clients, MemArbiterServer#(addr)) ports;
// A MemArbiter manages concurrent access to memory ports. // The following methods are to support arbiter chaining.
interface MemArbiter#(type addr); //
interface MemArbiterServer#(MemArbiterWrite#(addr)) cpu; // Suppose you're arbitrating access to a dual-port
interface MemArbiterServer#(MemArbiterWrite#(addr)) debugger; // memory. Typically, such a memory specifies that if one port is
interface MemArbiterServer#(addr) palette; // writing to an address, the other must not concurrently read or
// write that same address. This means the arbiters attached to
interface MemArbiterServer#(addr) tile1; // each memory port must cooperate to avoid simultaneously granting
interface MemArbiterServer#(addr) tile2; // conflicting requests from their clients.
interface MemArbiterServer#(addr) sprite; //
// Calling conflict prevents the arbiter from granting a concurrent
// request that would result in a write-write, read-write or
// write-read conflict. granted_op emits the operation that the
// arbiter is granting, if any.
//
// MemArbiter intances are Connectable: mkConnection(a, b) gives
// conflict priority to a. That is, b only grants requests that
// don't conflict with a's grant.
(* always_ready *)
method Action conflict(MemArbiterOp#(addr) conflict);
method MemArbiterOp#(addr) granted_op();
endinterface endinterface
// mkMemArbiter builds a GARY memory arbiter. instance Connectable#(MemArbiter#(m, addr), MemArbiter#(n, addr));
// module mkConnection(MemArbiter#(m, addr) a, MemArbiter#(n, addr) b, Empty ifc);
// Port A arbitrates with strict priority: CPU requests go first, then (* fire_when_enabled *)
// the debugger, then the palette DAC. rule forward_conflict;
// b.conflict(a.granted_op);
// Port B does round-robin arbitration, giving each client a fair endrule
// share of memory access. endmodule
module mkMemArbiter(MemArbiter#(addr)) endinstance
// mkPriorityMemArbiter returns a MemArbiter that gives priority to
// lower numbered ports.
module mkPriorityMemArbiter(MemArbiter#(num_clients, addr))
provisos (Bits#(addr, _), provisos (Bits#(addr, _),
Eq#(addr), Eq#(addr),
Alias#(write_req, MemArbiterWrite#(addr))); Min#(num_clients, 1, 1));
////// Vector#(num_clients, RWire#(MemArbiterOp#(addr))) reqs <- replicateM(mkRWire());
// Port A users Wire#(Vector#(num_clients, Bool)) grants <- mkBypassWire();
RWire#(write_req) cpu_req <- mkRWire(); RWire#(MemArbiterOp#(addr)) conflict_in <- mkRWire();
RWire#(write_req) debugger_req <- mkRWire(); RWire#(MemArbiterOp#(addr)) granted_op_out <- mkRWire();
PulseWire palette_req <- mkPulseWire();
PulseWire cpu_ok <- mkPulseWire(); (* no_implicit_conditions, fire_when_enabled *)
PulseWire debugger_ok <- mkPulseWire(); rule grant_requests;
PulseWire palette_ok <- mkPulseWire(); Vector#(num_clients, Bool) grant = replicate(False);
Bool done = False;
// Address written to by port A, if any. Used to block port B for (Integer i=0; i<valueOf(num_clients); i=i+1) begin
// clients that are trying to read the same address. if (reqs[i].wget() matches tagged Valid .req &&&
RWire#(addr) written_addr <- mkRWire(); !mem_ops_conflict(conflict_in.wget(), reqs[i].wget()) &&&
!done) begin
// We could be fancy with rule conditions to express the priorities done = True;
// between clients, but Bluespec has the preempts annotation to grant[i] = True;
// express the ranking directly. granted_op_out.wset(req);
(* preempts = "grant_cpu, (grant_debugger, grant_palette)" *)
(* preempts = "grant_debugger, grant_palette" *)
(* fire_when_enabled *)
rule grant_cpu (cpu_req.wget matches tagged Valid .req);
cpu_ok.send();
if (req.write)
written_addr.wset(req.addr);
endrule
rule grant_debugger (debugger_req.wget matches tagged Valid .req);
debugger_ok.send();
if (req.write)
written_addr.wset(req.addr);
endrule
rule grant_palette (palette_req);
palette_ok.send();
endrule
//////
// Port B users
Vector#(3, RWire#(addr)) portB_req <- replicateM(mkRWire);
Wire#(Vector#(3, Bool)) portB_grant <- mkBypassWire();
Vector#(3, Bool) init = replicate(False); init[0] = True;
Reg#(Vector#(3, Bool)) priority_vec <- mkReg(init);
rule grant_portB;
Vector#(3, Bool) grants = replicate(False);
Bool port_available = False;
// This algorithm is a little mystifying at first glance, but it
// works. priority_vec has one bool per client, only one of
// which is True. That True bit identifies the client with the
// highest priority on the next request.
//
// This loop goes through each client twice, using
// port_available to track whether a client can grab the port or
// not. When we start iterating, the port is marked unavailable
// until we reach the top priority client, at which point we
// mark the port available and keep scanning. That effectively
// makes the search for a requesting client start at the top
// priority one.
//
// As we loop back around a second time, the availability bool
// gets reset again, but if you take the example of the True bit
// being in the middle of the vector, and consider cases where
// the first requestor is before/after that starting point,
// you'll see that it all works out, and at the end of the loop
// we have a new bit vector where only one client is True - the
// one whose request is granted.
for (Integer i = 0; i < 6; i=i+1) begin
Integer idx = i % 3;
if (priority_vec[idx])
port_available = True;
let req = portB_req[idx].wget();
if (port_available && isValid(req) && req != written_addr.wget()) begin
port_available = False;
grants[idx] = True;
end end
end end
portB_grant <= grants;
// If we granted a request, the grantee becomes the lowest grants <= grant;
// priority client for the next round of requests. If nobody
// requested anything, keep the same priority as before.
if (any(id, grants))
priority_vec <= rotateR(grants);
endrule endrule
////// Vector#(num_clients, MemArbiterServer#(addr)) _ifcs = newVector();
// External interface for (Integer i=0; i<valueOf(num_clients); i=i+1)
_ifcs[i] = (interface MemArbiterServer#(addr);
method request = reqs[i].wset;
method grant = grants[i];
endinterface);
interface MemArbiterServer cpu; interface ports = _ifcs;
method request = cpu_req.wset; method conflict = conflict_in.wset;
method grant = cpu_ok; method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op);
endinterface return op;
interface MemArbiterServer debugger;
method request = debugger_req.wset;
method grant = debugger_ok;
endinterface
interface MemArbiterServer palette;
method Action request(addr);
palette_req.send();
endmethod endmethod
method grant = palette_ok; endmodule
endinterface
interface MemArbiterServer tile1; typedef struct {
method request = portB_req[0].wset; Vector#(n, Bool) grant_vec;
method grant = portB_grant[0]; Maybe#(MemArbiterOp#(addr)) granted_op;
endinterface } GrantResult#(numeric type n, type addr) deriving (Bits, Eq, FShow);
interface MemArbiterServer tile2; // select_grant computes which one entry of requests should be
method request = portB_req[1].wset; // granted. Priority order is descending starting from
method grant = portB_grant[1]; // requests[hipri].
endinterface function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr))) requests,
UInt#(TLog#(n)) hipri,
Maybe#(MemArbiterOp#(addr)) conflict)
provisos (Eq#(addr));
interface MemArbiterServer sprite; function onehot(idx);
method request = portB_req[2].wset; let ret = replicate(False);
method grant = portB_grant[2]; ret[idx] = True;
endinterface return ret;
endfunction
function GrantResult#(n, addr) do_fold(GrantResult#(n, addr) acc,
Tuple2#(UInt#(TLog#(n)),
Maybe#(MemArbiterOp#(addr))) next);
match {.idx, .mreq} = next;
if (mreq matches tagged Valid .req &&& acc.granted_op matches tagged Invalid &&& !mem_ops_conflict(conflict, mreq))
return GrantResult{
grant_vec: onehot(idx),
granted_op: tagged Valid req
};
else
// Previous grant won, not requesting, or request not satisfiable.
return acc;
endfunction
let in = zip(map(fromInteger, genVector()), requests);
let rot = rotateBy(in, fromInteger(valueOf(n)-1)-hipri+1);
let seed = GrantResult{
grant_vec: replicate(False),
granted_op: tagged Invalid
};
return foldl(do_fold, seed, rot);
endfunction
module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr))
provisos (Bits#(addr, _),
Eq#(addr),
Min#(num_clients, 1, 1));
Vector#(num_clients, RWire#(MemArbiterOp#(addr))) reqs <- replicateM(mkRWire);
Wire#(Vector#(num_clients, Bool)) grants <- mkBypassWire();
RWire#(MemArbiterOp#(addr)) conflict_in <- mkRWire();
RWire#(MemArbiterOp#(addr)) granted_op_out <- mkRWire();
// high_prio is the index of the client that should be first in
// line to receive access. Every time we grant access to a client,
// the one after that in sequence becomes high_prio in the next
// round.
Reg#(UInt#(TLog#(num_clients))) high_prio <- mkReg(0);
function Maybe#(_t) get_mreq(RWire#(_t) w);
return w.wget();
endfunction
rule grant;
let in = map(get_mreq, reqs);
let res = select_grant(in, high_prio, conflict_in.wget());
grants <= res.grant_vec;
if (res.granted_op matches tagged Valid .op) begin
granted_op_out.wset(op);
high_prio <= validValue(findElem(True, rotateR(res.grant_vec)));
end
endrule
Vector#(num_clients, MemArbiterServer#(addr)) _ifcs = newVector();
for (Integer i=0; i<valueOf(num_clients); i=i+1)
_ifcs[i] = (interface MemArbiterServer#(addr);
method request = reqs[i].wset;
method grant = grants[i];
endinterface);
interface ports = _ifcs;
method conflict = conflict_in.wset;
method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op);
return op;
endmethod
endmodule endmodule
endpackage endpackage

View File

@ -14,282 +14,307 @@ typedef UInt#(4) Addr;
typedef struct { typedef struct {
String name; String name;
Maybe#(MemArbiterWrite#(Addr)) cpu;
Maybe#(MemArbiterWrite#(Addr)) debugger;
Maybe#(Addr) palette;
Maybe#(Addr) tile1;
Maybe#(Addr) tile2;
Maybe#(Addr) sprite;
Vector#(6, Bool) want; Vector#(n, Maybe#(MemArbiterOp#(Addr))) reqs;
} TestCase deriving (Bits, Eq); Maybe#(MemArbiterOp#(Addr)) conflict;
function Maybe#(MemArbiterWrite#(Addr)) rwRead(Addr addr); Vector#(n, Bool) want_grants;
return tagged Valid MemArbiterWrite{write: False, addr: addr}; Maybe#(MemArbiterOp#(Addr)) want_granted_op;
} TestCase#(numeric type n) deriving (Bits, Eq);
function Maybe#(MemArbiterOp#(Addr)) read(Addr addr);
return tagged Valid MemArbiterOp{write: False, addr: addr};
endfunction endfunction
function Maybe#(MemArbiterWrite#(Addr)) rwWrite(Addr addr); function Maybe#(MemArbiterOp#(Addr)) write(Addr addr);
return tagged Valid MemArbiterWrite{write: True, addr: addr}; return tagged Valid MemArbiterOp{write: True, addr: addr};
endfunction endfunction
function Maybe#(Addr) read(Addr addr); function Maybe#(MemArbiterOp#(Addr)) idle();
return tagged Valid addr;
endfunction
function Maybe#(t) idle();
return tagged Invalid; return tagged Invalid;
endfunction endfunction
function Vector#(6, Bool) grant(Integer granted_a, Integer granted_b); function Maybe#(MemArbiterOp#(Addr)) noConflict();
let ret = replicate(False); return tagged Invalid;
if (granted_a >= 0)
ret[granted_a] = True;
if (granted_b >= 0)
ret[granted_b+3] = True;
return ret;
endfunction endfunction
function TestCase testCase(String name, function Vector#(n, Bool) grant(Integer granted);
Maybe#(MemArbiterWrite#(Addr)) cpu, function gen(idx);
Maybe#(MemArbiterWrite#(Addr)) debugger, return idx == granted;
Maybe#(Addr) palette, endfunction
Maybe#(Addr) tile1,
Maybe#(Addr) tile2, return genWith(gen);
Maybe#(Addr) sprite, endfunction
Integer portA,
Integer portB); function Vector#(n, Bool) noGrant();
return replicate(False);
endfunction
function TestCase#(n) testCase(String name,
Vector#(n, Maybe#(MemArbiterOp#(Addr))) reqs,
Maybe#(MemArbiterOp#(Addr)) conflict,
Vector#(n, Bool) want_grants,
Maybe#(MemArbiterOp#(Addr)) want_granted_op);
return TestCase{ return TestCase{
name: name, name: name,
cpu: cpu, reqs: reqs,
debugger: debugger, conflict: conflict,
palette: palette, want_grants: want_grants,
tile1: tile1, want_granted_op: want_granted_op
tile2: tile2,
sprite: sprite,
want: grant(portA, portB)
}; };
endfunction endfunction
module mkTB(); interface TB;
Vector#(29, TestCase) tests = vec( method Action start();
testCase("All idle", (* always_ready *)
idle, idle, idle, method Bool done();
idle, idle, idle, endinterface
-1, -1),
// Single client accesses at a time module mkArbiterTB(MemArbiter#(n, Addr) dut, Vector#(m, TestCase#(n)) tests, TB ifc);
testCase("CPU read", rwRead(1), idle, idle, let cycles <- mkCycleCounter();
idle, idle, idle,
0, -1),
testCase("CPU write", rwWrite(1), idle, idle,
idle, idle, idle,
0, -1),
testCase("Debugger read",
idle, rwRead(1), idle,
idle, idle, idle,
1, -1),
testCase("Debugger write",
idle, rwWrite(1), idle,
idle, idle, idle,
1, -1),
testCase("Palette read",
idle, idle, read(1),
idle, idle, idle,
2, -1),
testCase("Tile1 read",
idle, idle, idle,
read(1), idle, idle,
-1, 0),
testCase("Tile2 read",
idle, idle, idle,
idle, read(1), idle,
-1, 1),
testCase("Sprite read",
idle, idle, idle,
idle, idle, read(1),
-1, 2),
// Strict priority on port A Reg#(Bit#(TLog#(m))) idx <- mkReg(0);
testCase("CPU + Debugger + Palette", Reg#(Bool) running <- mkReg(False);
rwRead(1), rwRead(2), read(3),
idle, idle, idle,
0, -1),
testCase("CPU + Palette",
rwRead(1), idle, read(3),
idle, idle, idle,
0, -1),
testCase("Debugger + Palette",
idle, rwRead(2), read(3),
idle, idle, idle,
1, -1),
// Round-robin on port B for (Integer i=0; i<valueOf(n); i=i+1) begin
testCase("Sprite read", // to reset round robin rule request (running && isValid(tests[idx].reqs[i]));
idle, idle, idle, dut.ports[i].request(validValue(tests[idx].reqs[i]));
idle, idle, read(1),
-1, 2),
testCase("Tile1 + Tile2 + Sprite",
idle, idle, idle,
read(1), read(2), read(3),
-1, 0),
testCase("Tile1 + Tile2 + Sprite",
idle, idle, idle,
read(1), read(2), read(3),
-1, 1),
testCase("Tile1 + Tile2 + Sprite",
idle, idle, idle,
read(1), read(2), read(3),
-1, 2),
testCase("Tile1 + Tile2 + Sprite",
idle, idle, idle,
read(1), read(2), read(3),
-1, 0),
testCase("Tile1 + Sprite",
idle, idle, idle,
read(1), idle, read(3),
-1, 2),
testCase("Tile2 + Sprite",
idle, idle, idle,
idle, read(2), read(3),
-1, 1),
testCase("Tile2 + Sprite",
idle, idle, idle,
idle, read(2), read(3),
-1, 2),
// Inter-port conflicts
testCase("Read/read, no conflict",
rwRead(0), idle, idle,
read(0), idle, idle,
0, 0),
testCase("Write/read, no conflict",
rwWrite(1), idle, idle,
read(0), idle, idle,
0, 0),
testCase("Tile1 write conflict",
rwWrite(0), idle, idle,
read(0), idle, idle,
0, -1),
testCase("Tile2 write conflict",
rwWrite(0), idle, idle,
idle, read(0), idle,
0, -1),
testCase("Sprite write conflict",
rwWrite(0), idle, idle,
idle, idle, read(0),
0, -1),
testCase("Tile1 write conflict with debugger",
idle, rwWrite(0), idle,
read(0), idle, idle,
1, -1),
testCase("Sprite read", // to reset round robin
idle, idle, idle,
idle, idle, read(1),
-1, 2),
testCase("CPU write conflict, other port feasible",
rwWrite(0), idle, idle,
read(0), read(1), idle,
0, 1),
testCase("CPU write conflict, conflict resolved",
idle, idle, idle,
read(0), idle, idle,
-1, 0));
MemArbiter#(Addr) dut <- mkMemArbiter();
Reg#(UInt#(32)) idx <- mkReg(0);
rule display_test (idx == 0);
$display("RUN TestMemArbiter");
endrule endrule
(* no_implicit_conditions, fire_when_enabled *)
rule input_cpu (tests[idx].cpu matches tagged Valid .req);
dut.cpu.request(req);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule input_debugger (tests[idx].debugger matches tagged Valid .req);
dut.debugger.request(req);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule input_palette (tests[idx].palette matches tagged Valid .addr);
dut.palette.request(addr);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule input_tile1 (tests[idx].tile1 matches tagged Valid .addr);
dut.tile1.request(addr);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule input_tile2 (tests[idx].tile2 matches tagged Valid .addr);
dut.tile2.request(addr);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule input_sprite (tests[idx].sprite matches tagged Valid .addr);
dut.sprite.request(addr);
endrule
function Fmt rw_str(Maybe#(MemArbiterWrite#(Addr)) v);
case (v) matches
tagged Valid .req: begin
if (req.write)
return $format("Write(%0d)", req.addr);
else
return $format("Read(%0d) ", req.addr);
end end
tagged Invalid: return $format("Idle ");
endcase
endfunction
function Fmt ro_str(Maybe#(Addr) v); (* no_implicit_conditions, fire_when_enabled *)
rule forbid (running && isValid(tests[idx].conflict));
dut.conflict(validValue(tests[idx].conflict));
endrule
Wire#(Maybe#(MemArbiterOp#(Addr))) got_granted_op <- mkDWire(tagged Invalid);
(* fire_when_enabled *)
rule collect_granted_op (running);
got_granted_op <= tagged Valid dut.granted_op();
endrule
function Fmt req_s(Maybe#(MemArbiterOp#(Addr)) v);
case (v) matches case (v) matches
tagged Valid .addr: return $format("Read(%0d) ", addr);
tagged Invalid: return $format("Idle"); tagged Invalid: return $format("Idle");
tagged Valid .req &&& req.write: return $format("Write(%0d)", req.addr);
tagged Valid .req: return $format("Read(%0d)", req.addr);
endcase endcase
endfunction endfunction
(* no_implicit_conditions, fire_when_enabled *) (* no_implicit_conditions, fire_when_enabled *)
rule check_grants; rule check (running);
Vector#(6, Bool) gotVec = newVector;
gotVec[0] = dut.cpu.grant();
gotVec[1] = dut.debugger.grant();
gotVec[2] = dut.palette.grant();
gotVec[3] = dut.tile1.grant();
gotVec[4] = dut.tile2.grant();
gotVec[5] = dut.sprite.grant();
let test = tests[idx]; let test = tests[idx];
let got = pack(reverse(gotVec)); let reqs = test.reqs;
let want = pack(reverse(test.want)); let want_grants = test.want_grants;
let want_granted_op = test.want_granted_op;
Vector#(n, Bool) got_grants = newVector;
for (Integer i=0; i<valueOf(n); i=i+1)
got_grants[i] = dut.ports[i].grant();
$display("RUN %s (%0d)", test.name, idx+1); $display("RUN %s (%0d)", tests[idx].name, idx);
if (got != want) begin if (got_grants != want_grants || got_granted_op != want_granted_op) begin
$display(" input: ", $display("input:");
"0:", rw_str(test.cpu), " 1:", rw_str(test.debugger), " 2:", ro_str(test.palette), for (Integer i=0; i<valueOf(n); i=i+1)
" 3:", ro_str(test.tile1), " 4:", ro_str(test.tile2), " 5:", ro_str(test.sprite)); $display(" ", $format("%0d", i), ": ", req_s(reqs[i]));
$display(" got : %03b %03b", got[5:3], got[2:0]); $display(" conflict: ", fshow(test.conflict));
$display(" want : %03b %03b", want[5:3], want[2:0]);
dynamicAssert(got == want, "wrong arbiter output"); $display(" output:");
$display(" grants: ", fshow(got_grants));
$display(" granted: ", fshow(got_granted_op));
$display(" want grants: ", fshow(tests[idx].want_grants));
$display(" want granted: ", fshow(want_granted_op));
dynamicAssert(False, "wrong arbiter output");
end end
dynamicAssert(cycles == 1, "arbiter took more than 0 cycles");
$display("OK %s", tests[idx].name);
cycles.reset();
if (idx == fromInteger(valueOf(m)-1))
running <= False;
else else
$display("OK %s", test.name); idx <= idx+1;
endrule endrule
(* no_implicit_conditions, fire_when_enabled *) method Action start() if (!running && idx == 0);
rule advance_test; cycles.reset();
let next = idx+1; running <= True;
let max = fromInteger(arrayLength(vectorToArray(tests))); endmethod
if (next == max) begin
$display("OK TestMemArbiter"); method Bool done();
$finish; return !running && idx != 0;
end endmethod
else endmodule
idx <= next;
endrule module mkTB(Empty);
///////////////////////////////
// Strict arbiter
let strictTests = vec(
// Simple grants
testCase("All idle",
vec(idle, idle, idle), noConflict,
noGrant, noConflict),
testCase("Port 0 read",
vec(read(1), idle, idle), noConflict,
grant(0), read(1)),
testCase("Port 0 write",
vec(write(1), idle, idle), noConflict,
grant(0), write(1)),
testCase("Port 1 read",
vec(idle, read(1), idle), noConflict,
grant(1), read(1)),
testCase("Port 1 write",
vec(idle, write(1), idle), noConflict,
grant(1), write(1)),
testCase("Port 2 read",
vec(idle, idle, read(1)), noConflict,
grant(2), read(1)),
testCase("Port 2 write",
vec(idle, idle, write(1)), noConflict,
grant(2), write(1)),
// Priorities
testCase("Port 0+1",
vec(read(1), read(2), idle), noConflict,
grant(0), read(1)),
testCase("Port 0+2",
vec(read(1), idle, read(2)), noConflict,
grant(0), read(1)),
testCase("Port 1+2",
vec(idle, read(1), read(2)), noConflict,
grant(1), read(1)),
testCase("Port 0+1+2",
vec(read(1), read(2), read(3)), noConflict,
grant(0), read(1)),
testCase("Port 0+1+2, overruled writes",
vec(read(1), write(1), write(2)), noConflict,
grant(0), read(1)),
// Forbidden addrs
testCase("Port 0 read-write denied",
vec(read(1), read(2), idle), write(1),
grant(1), read(2)),
testCase("Port 0 write-write denied",
vec(write(1), read(2), idle), write(1),
grant(1), read(2)),
testCase("Port 0 write-read denied",
vec(write(1), read(2), idle), read(1),
grant(1), read(2)),
testCase("Port 0 no addr match",
vec(write(2), idle, idle), write(1),
grant(0), write(2)),
testCase("Port 0 denied, no alternatives",
vec(write(1), idle, idle), write(1),
noGrant, noConflict)
);
MemArbiter#(3, Addr) strict <- mkPriorityMemArbiter();
let strictTB <- mkArbiterTB(strict, strictTests);
///////////////////////////////
// Round-robin arbiter
let rrTests = vec(
// Simple grants
testCase("All idle",
vec(idle, idle, idle, idle), noConflict,
noGrant, noConflict),
testCase("Port 0 read",
vec(read(1), idle, idle, idle), noConflict,
grant(0), read(1)),
testCase("Port 0 write",
vec(write(1), idle, idle, idle), noConflict,
grant(0), write(1)),
testCase("Port 1 read",
vec(idle, read(1), idle, idle), noConflict,
grant(1), read(1)),
testCase("Port 1 write",
vec(idle, write(1), idle, idle), noConflict,
grant(1), write(1)),
testCase("Port 2 read",
vec(idle, idle, read(1), idle), noConflict,
grant(2), read(1)),
testCase("Port 2 write",
vec(idle, idle, write(1), idle), noConflict,
grant(2), write(1)),
testCase("Port 3 read",
vec(idle, idle, idle, read(1)), noConflict,
grant(3), read(1)),
testCase("Port 3 write",
vec(idle, idle, idle, write(1)), noConflict,
grant(3), write(1)),
// Priorities
testCase("Port 3 to reset RR",
vec(idle, idle, idle, read(1)), noConflict,
grant(3), read(1)),
testCase("Port 0+1 #1",
vec(read(1), read(2), idle, idle), noConflict,
grant(0), read(1)),
testCase("Port 0+1 #2",
vec(read(1), read(2), idle, idle), noConflict,
grant(1), read(2)),
testCase("Port 0+1 #3",
vec(read(1), read(2), idle, idle), noConflict,
grant(0), read(1)),
testCase("Port 0+2 #1",
vec(read(1), idle, read(2), idle), noConflict,
grant(2), read(2)),
testCase("Port 0+2 #2",
vec(read(1), idle, read(2), idle), noConflict,
grant(0), read(1)),
testCase("Port 0+1+2+3 #1",
vec(read(1), read(2), read(3), read(4)), noConflict,
grant(1), read(2)),
testCase("Port 0+1+2+3 #2",
vec(read(1), read(2), read(3), read(4)), noConflict,
grant(2), read(3)),
testCase("Port 0+1+2+3 #3",
vec(read(1), read(2), read(3), read(4)), noConflict,
grant(3), read(4)),
testCase("Port 0+1+2+3 #4",
vec(read(1), read(2), read(3), read(4)), noConflict,
grant(0), read(1)),
testCase("Port 0+1+2+3 #5",
vec(read(1), read(2), read(3), read(4)), noConflict,
grant(1), read(2)),
// Forbidden addrs
testCase("Port 3 to reset RR",
vec(idle, idle, idle, read(1)), noConflict,
grant(3), read(1)),
testCase("RR with denied writes #1",
vec(read(1), write(2), read(3), read(4)), write(3),
grant(0), read(1)),
testCase("RR with denied writes #2",
vec(read(1), write(2), read(3), read(4)), write(3),
grant(1), write(2)),
testCase("RR with denied writes #3",
vec(read(1), write(2), read(3), read(4)), write(3),
grant(3), read(4)),
testCase("RR with denied writes #4",
vec(read(1), write(2), read(3), read(4)), write(3),
grant(0), read(1)),
testCase("RR with denied writes #5",
vec(read(1), write(2), read(3), read(4)), write(3),
grant(1), write(2))
);
MemArbiter#(4, Addr) rr <- mkRoundRobinMemArbiter();
let rrTB <- mkArbiterTB(rr, rrTests);
runTest(100,
mkTest("MemArbiter", seq
mkTest("MemArbiter/Strict", seq
strictTB.start();
await(strictTB.done);
endseq);
mkTest("MemArbiter/RoundRobin", seq
rrTB.start();
await(rrTB.done);
endseq);
endseq));
endmodule endmodule
endpackage endpackage

View File

@ -1,5 +1,6 @@
package VRAM; package VRAMCore;
import Connectable::*;
import GetPut::*; import GetPut::*;
import ClientServer::*; import ClientServer::*;
import DReg::*; import DReg::*;
@ -15,11 +16,12 @@ import ECP5_RAM::*;
export VRAMAddr; export VRAMAddr;
export VRAMData; export VRAMData;
export mkVRAM;
export VRAMRequest; export VRAMRequest;
export VRAMResponse; export VRAMResponse;
export VRAMClient;
export VRAMServer; export VRAMServer;
export VRAM; export VRAMCore;
export mkVRAMCore;
typedef Bit#(8) VRAMData; typedef Bit#(8) VRAMData;
@ -133,30 +135,30 @@ typedef struct {
typedef Server#(VRAMRequest, VRAMResponse) VRAMServer; typedef Server#(VRAMRequest, VRAMResponse) VRAMServer;
typedef Client#(VRAMRequest, VRAMResponse) VRAMClient; typedef Client#(VRAMRequest, VRAMResponse) VRAMClient;
interface VRAM; interface VRAMCore;
interface VRAMServer portA; interface VRAMServer portA;
interface VRAMServer portB; interface VRAMServer portB;
endinterface endinterface
// mkVRAM creates a dual port VRAM of the specified size, using ECP5 // mkVRAMCore creates a dual port VRAM of the specified size, using
// EBR memory primitives. The memory size must be a multiple of 4KiB, // ECP5 EBR memory primitives. The memory size must be a multiple of
// with a maximum of 128KiB. // 4KiB, with a maximum of 128KiB.
// //
// The returned VRAM servers implement flow control. As long as // The returned VRAMCore servers implement flow control. As long as
// responses are processed as soon as they're available, each port can // responses are processed as soon as they're available, each port can
// process one memory operation per cycle. // process one memory operation per cycle.
// //
// The VRAM does not prevent write-write or write-read conflicts // The VRAMCore does not prevent write-write or write-read conflicts
// between the ports. The outcome of a simultaneous write to the same // between the ports. The outcome of a simultaneous write to the same
// address is unspecified, as is the read output in a simultaneous // address is unspecified, as is the read output in a simultaneous
// read and write of the same address. The caller must use external // read and write of the same address. The caller must use external
// arbitration to avoid such accesses. // arbitration to avoid such accesses.
module mkVRAM(Integer num_kilobytes, VRAM ifc); module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
if (num_kilobytes > 128) if (num_kilobytes > 128)
error("maximum VRAM size is 128KiB"); error("maximum VRAMCore size is 128KiB");
let num_bytes = num_kilobytes*1024; let num_bytes = num_kilobytes*1024;
if (num_bytes % 4096 != 0) if (num_bytes % 4096 != 0)
error("VRAM must be a multiple of 4096b"); error("VRAMCore must be a multiple of 4096b");
let num_byterams = num_bytes/4096; let num_byterams = num_bytes/4096;
let num_arrays = ceil(fromInteger(num_byterams) / 8); let num_arrays = ceil(fromInteger(num_byterams) / 8);