gary/vram/MemoryArbiter.bsv

191 lines
6.2 KiB
Plaintext

package MemoryArbiter;
import Vector::*;
export MemoryArbiterWriter(..);
export MemoryArbiterReader(..);
export MemoryArbiter(..);
export mkMemoryArbiter;
// A MemoryArbiterWriter can request use of a memory port to write to
// an address. When a request is feasible, grant() returns True in the
// same cycle.
(* always_ready *)
interface MemoryArbiterWriter#(type addr);
method Action request(Bool write, addr address);
method Bool grant();
endinterface
// MemoryArbiterReader can request use of a memory port to read from
// an address. When a request is feasible, grant() returns True in the
// same cycle.
(* always_ready *)
interface MemoryArbiterReader#(type addr);
method Action request(addr address);
method Bool grant();
endinterface
// A MemoryArbiter manages concurrent access to memory ports. It
// mediates access between 2 writers and 4 readers, which are
// statically allocated to one of 2 internal memory ports.
interface MemoryArbiter#(type addr);
// Assigned to port A.
interface MemoryArbiterWriter#(addr) cpu;
interface MemoryArbiterWriter#(addr) debugger;
interface MemoryArbiterReader#(addr) palette;
// Assigned to port B.
interface MemoryArbiterReader#(addr) tile1;
interface MemoryArbiterReader#(addr) tile2;
interface MemoryArbiterReader#(addr) sprite;
endinterface
// mkMemoryArbiter builds a GARY memory arbiter.
//
// Port A arbitrates with strict priority: CPU requests always proceed
// first, followed by debugger requests, then the palette DAC.
//
// Port B does round-robin arbitration, giving each client a fair
// chance of having its requests processed.
module mkMemoryArbiter(MemoryArbiter#(addr) ifc)
provisos(Bits#(addr, _),
Eq#(addr),
Alias#(write_req, Tuple2#(Bool, 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 {.write, .addr});
cpu_ok.send();
if (write)
written_addr.wset(addr);
endrule
rule grant_debugger (debugger_req.wget matches tagged Valid {.write, .addr});
debugger_ok.send();
if (write)
written_addr.wset(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 MemoryArbiterWriter cpu;
method Action request(write, addr);
cpu_req.wset(tuple2(write, addr));
endmethod
method grant = cpu_ok;
endinterface
interface MemoryArbiterWriter debugger;
method Action request(write, addr);
debugger_req.wset(tuple2(write, addr));
endmethod
method grant = debugger_ok;
endinterface
interface MemoryArbiterReader palette;
method Action request(addr);
palette_req.send();
endmethod
method grant = palette_ok;
endinterface
interface MemoryArbiterReader tile1;
method Action request(addr);
portB_req[0].wset(addr);
endmethod
method grant = portB_grant[0];
endinterface
interface MemoryArbiterReader tile2;
method Action request(addr);
portB_req[1].wset(addr);
endmethod
method grant = portB_grant[1];
endinterface
interface MemoryArbiterReader sprite;
method Action request(addr);
portB_req[2].wset(addr);
endmethod
method grant = portB_grant[2];
endinterface
endmodule
endpackage