gary/vram/MemArbiter.bsv

192 lines
5.9 KiB
Plaintext

package MemArbiter;
import Connectable::*;
import Vector::*;
export MemArbiterWrite(..);
export MemArbiterServer(..);
export MemArbiterClient(..);
export MemArbiter(..);
export mkMemArbiter;
// A MemArbiterServer receives requests for memory access and emits
// grants.
interface MemArbiterServer#(type request);
method Action request(request req);
method Bool grant();
endinterface
// A MemArbiterClient emits requests for memory access and emits
// grants.
interface MemArbiterClient#(type request);
method request request();
method Action grant();
endinterface
instance Connectable#(MemArbiterClient#(req), MemArbiterServer#(req));
module mkConnection(MemArbiterClient#(req) client, MemArbiterServer#(req) server, Empty ifc);
rule send_request;
server.request(client.request());
endrule
rule send_grant (server.grant());
client.grant();
endrule
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;
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)));
//////
// Port A users
RWire#(write_req) cpu_req <- mkRWire();
RWire#(write_req) debugger_req <- mkRWire();
PulseWire palette_req <- mkPulseWire();
PulseWire cpu_ok <- mkPulseWire();
PulseWire debugger_ok <- mkPulseWire();
PulseWire palette_ok <- mkPulseWire();
// 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();
// 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;
end
end
portB_grant <= grants;
// If we granted a request, the grantee becomes the lowest
// 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
//////
// External interface
interface MemArbiterServer cpu;
method request = cpu_req.wset;
method grant = cpu_ok;
endinterface
interface MemArbiterServer debugger;
method request = debugger_req.wset;
method grant = debugger_ok;
endinterface
interface MemArbiterServer palette;
method Action request(addr);
palette_req.send();
endmethod
method grant = palette_ok;
endinterface
interface MemArbiterServer tile1;
method request = portB_req[0].wset;
method grant = portB_grant[0];
endinterface
interface MemArbiterServer tile2;
method request = portB_req[1].wset;
method grant = portB_grant[1];
endinterface
interface MemArbiterServer sprite;
method request = portB_req[2].wset;
method grant = portB_grant[2];
endinterface
endmodule
endpackage