190 lines
6.0 KiB
Plaintext
190 lines
6.0 KiB
Plaintext
package MemArbiter;
|
|
|
|
import Vector::*;
|
|
|
|
export MemArbiterWriter(..);
|
|
export MemArbiterReader(..);
|
|
export MemArbiter(..);
|
|
export mkMemArbiter;
|
|
|
|
// A MemArbiterWriter can request use of a memory port to read or
|
|
// write to an address. When a request is feasible, grant() returns
|
|
// True in the same cycle.
|
|
(* always_ready *)
|
|
interface MemArbiterWriter#(type addr);
|
|
method Action request(Bool write, addr address);
|
|
method Bool grant();
|
|
endinterface
|
|
|
|
// MemArbiterReader 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 MemArbiterReader#(type addr);
|
|
method Action request(addr address);
|
|
method Bool grant();
|
|
endinterface
|
|
|
|
// A MemArbiter manages concurrent access to memory ports. It
|
|
// mediates access between 2 writers and 4 readers.
|
|
interface MemArbiter#(type addr);
|
|
// Assigned to port A.
|
|
interface MemArbiterWriter#(addr) cpu;
|
|
interface MemArbiterWriter#(addr) debugger;
|
|
interface MemArbiterReader#(addr) palette;
|
|
|
|
// Assigned to port B.
|
|
interface MemArbiterReader#(addr) tile1;
|
|
interface MemArbiterReader#(addr) tile2;
|
|
interface MemArbiterReader#(addr) sprite;
|
|
endinterface
|
|
|
|
// mkMemArbiter 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 mkMemArbiter(MemArbiter#(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 MemArbiterWriter cpu;
|
|
method Action request(write, addr);
|
|
cpu_req.wset(tuple2(write, addr));
|
|
endmethod
|
|
method grant = cpu_ok;
|
|
endinterface
|
|
|
|
interface MemArbiterWriter debugger;
|
|
method Action request(write, addr);
|
|
debugger_req.wset(tuple2(write, addr));
|
|
endmethod
|
|
method grant = debugger_ok;
|
|
endinterface
|
|
|
|
interface MemArbiterReader palette;
|
|
method Action request(addr);
|
|
palette_req.send();
|
|
endmethod
|
|
method grant = palette_ok;
|
|
endinterface
|
|
|
|
interface MemArbiterReader tile1;
|
|
method Action request(addr);
|
|
portB_req[0].wset(addr);
|
|
endmethod
|
|
method grant = portB_grant[0];
|
|
endinterface
|
|
|
|
interface MemArbiterReader tile2;
|
|
method Action request(addr);
|
|
portB_req[1].wset(addr);
|
|
endmethod
|
|
method grant = portB_grant[1];
|
|
endinterface
|
|
|
|
interface MemArbiterReader sprite;
|
|
method Action request(addr);
|
|
portB_req[2].wset(addr);
|
|
endmethod
|
|
method grant = portB_grant[2];
|
|
endinterface
|
|
endmodule
|
|
|
|
endpackage
|