diff --git a/vram/MemArbiter.bsv b/vram/MemArbiter.bsv index 4138c14..cf6f9e2 100644 --- a/vram/MemArbiter.bsv +++ b/vram/MemArbiter.bsv @@ -3,28 +3,32 @@ package MemArbiter; import Connectable::*; import Vector::*; -export MemArbiterWrite(..); +export MemArbiterOp(..); export MemArbiterServer(..); export MemArbiterClient(..); -export MemArbiter(..); -export mkMemArbiter; +export MemArbiter(..), mkPriorityMemArbiter, mkRoundRobinMemArbiter; + +typedef struct { + Bool write; + addr addr; +} MemArbiterOp#(type addr) deriving (Bits, Eq, FShow); // A MemArbiterServer receives requests for memory access and emits // grants. -interface MemArbiterServer#(type request); - method Action request(request req); +interface MemArbiterServer#(type addr); + method Action request(MemArbiterOp#(addr) req); method Bool grant(); endinterface // A MemArbiterClient emits requests for memory access and emits // grants. -interface MemArbiterClient#(type request); - method Maybe#(request) request(); +interface MemArbiterClient#(type addr); + method Maybe#(MemArbiterOp#(addr)) request(); method Action grant(); endinterface -instance Connectable#(MemArbiterClient#(req), MemArbiterServer#(req)); - module mkConnection(MemArbiterClient#(req) client, MemArbiterServer#(req) server, Empty ifc); +instance Connectable#(MemArbiterClient#(addr), MemArbiterServer#(addr)); + module mkConnection(MemArbiterClient#(addr) client, MemArbiterServer#(addr) server, Empty ifc); rule send_request (client.request matches tagged Valid .req); server.request(req); endrule @@ -35,157 +39,149 @@ instance Connectable#(MemArbiterClient#(req), MemArbiterServer#(req)); endmodule endinstance -typedef struct { - Bool write; - addr addr; -} MemArbiterWrite#(type addr) deriving (Bits, Eq); - -// A MemArbiter manages concurrent access to memory ports. -interface MemArbiter#(type addr); - interface MemArbiterServer#(MemArbiterWrite#(addr)) cpu; - interface MemArbiterServer#(MemArbiterWrite#(addr)) debugger; - interface MemArbiterServer#(addr) palette; - - interface MemArbiterServer#(addr) tile1; - interface MemArbiterServer#(addr) tile2; - interface MemArbiterServer#(addr) sprite; +interface MemArbiter#(numeric type num_clients, type addr); + interface Vector#(num_clients, MemArbiterServer#(addr)) ports; + method Action forbid_addr(addr addr); + method addr forbidden_addr(); endinterface -// mkMemArbiter builds a GARY memory arbiter. -// -// Port A arbitrates with strict priority: CPU requests go first, then -// the debugger, then the palette DAC. -// -// Port B does round-robin arbitration, giving each client a fair -// share of memory access. -module mkMemArbiter(MemArbiter#(addr)) - provisos(Bits#(addr, _), - Eq#(addr), - Alias#(write_req, MemArbiterWrite#(addr))); +module mkPriorityMemArbiter(MemArbiter#(num_clients, addr)) + provisos (Bits#(addr, _), + Eq#(addr), + Min#(num_clients, 1, 1)); - ////// - // Port A users + Vector#(num_clients, RWire#(MemArbiterOp#(addr))) reqs <- replicateM(mkRWire()); + Wire#(Vector#(num_clients, Bool)) grants <- mkBypassWire(); - RWire#(write_req) cpu_req <- mkRWire(); - RWire#(write_req) debugger_req <- mkRWire(); - PulseWire palette_req <- mkPulseWire(); + RWire#(addr) blocked_in <- mkRWire(); + RWire#(addr) blocked_out <- mkRWire(); - PulseWire cpu_ok <- mkPulseWire(); - PulseWire debugger_ok <- mkPulseWire(); - PulseWire palette_ok <- mkPulseWire(); + function Bool is_blocked(addr addr); + return blocked_in.wget() == tagged Valid addr; + endfunction - // Address written to by port A, if any. Used to block port B - // clients that are trying to read the same address. - RWire#(addr) written_addr <- mkRWire(); + (* no_implicit_conditions, fire_when_enabled *) + rule grant_requests; + Vector#(num_clients, Bool) grant = replicate(False); + Bool done = False; - // We could be fancy with rule conditions to express the priorities - // between clients, but Bluespec has the preempts annotation to - // express the ranking directly. - (* 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; + for (Integer i=0; i= 0) - ret[granted_a] = True; - if (granted_b >= 0) - ret[granted_b+3] = True; - return ret; +function Maybe#(Addr) noForbid(); + return tagged Invalid; endfunction -function TestCase testCase(String name, - Maybe#(MemArbiterWrite#(Addr)) cpu, - Maybe#(MemArbiterWrite#(Addr)) debugger, - Maybe#(Addr) palette, - Maybe#(Addr) tile1, - Maybe#(Addr) tile2, - Maybe#(Addr) sprite, - Integer portA, - Integer portB); +function Maybe#(Addr) forbid(Addr a); + return tagged Valid a; +endfunction + +function Vector#(n, Bool) grant(Integer granted); + function gen(idx); + return idx == granted; + endfunction + + return genWith(gen); +endfunction + +function Vector#(n, Bool) noGrant(); + return replicate(False); +endfunction + +function TestCase#(n) testCase(String name, + Vector#(n, Maybe#(MemArbiterOp#(Addr))) reqs, + Maybe#(Addr) forbid_addr, + Vector#(n, Bool) want_grants, + Maybe#(Addr) want_forbid_addr); return TestCase{ name: name, - cpu: cpu, - debugger: debugger, - palette: palette, - tile1: tile1, - tile2: tile2, - sprite: sprite, - want: grant(portA, portB) + reqs: reqs, + forbid_addr: forbid_addr, + want_grants: want_grants, + want_forbid_addr: want_forbid_addr }; endfunction -module mkTB(); - Vector#(29, TestCase) tests = vec( - testCase("All idle", - idle, idle, idle, - idle, idle, idle, - -1, -1), +interface TB; + method Action start(); + (* always_ready *) + method Bool done(); +endinterface - // Single client accesses at a time - testCase("CPU read", rwRead(1), idle, idle, - 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), +module mkArbiterTB(MemArbiter#(n, Addr) dut, Vector#(m, TestCase#(n)) tests, TB ifc); + let cycles <- mkCycleCounter(); - // Strict priority on port A - testCase("CPU + Debugger + Palette", - 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), + Reg#(Bit#(TLog#(m))) idx <- mkReg(0); + Reg#(Bool) running <- mkReg(False); - // Round-robin on port B - testCase("Sprite read", // to reset round robin - idle, idle, idle, - 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 + for (Integer i=0; i"); + tagged Valid .a: return $format("%0d", a); endcase endfunction (* no_implicit_conditions, fire_when_enabled *) - rule check_grants; - 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(); - + rule check (running); let test = tests[idx]; - let got = pack(reverse(gotVec)); - let want = pack(reverse(test.want)); + let reqs = test.reqs; + let want_grants = test.want_grants; + let want_forbid_addr = test.want_forbid_addr; + Vector#(n, Bool) got_grants = newVector; + for (Integer i=0; i