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