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