191 lines
6.2 KiB
Plaintext
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
|