package MemArbiter; import Connectable::*; import Vector::*; export MemArbiterOp(..); export MemArbiterServer(..); export MemArbiterClient(..); export MemArbiter(..), mkPriorityMemArbiter, mkRoundRobinMemArbiter; // A MemArbiterOp is an operation that a client is seeking permission // to perform. typedef struct { Bool write; addr addr; } MemArbiterOp#(type addr) deriving (Bits, Eq, FShow); function Bool mem_ops_conflict(Maybe#(MemArbiterOp#(addr)) a, Maybe#(MemArbiterOp#(addr)) b) provisos(Eq#(addr)); if (a matches tagged Valid .ar &&& b matches tagged Valid .br &&& ar.addr == br.addr) return ar.write || br.write; else return False; endfunction // A MemArbiterServer receives requests and emits grants. (* always_ready *) interface MemArbiterServer#(type addr); method Action request(MemArbiterOp#(addr) req); method Bool grant(); endinterface // A MemArbiterClient emits requests and receives grants. interface MemArbiterClient#(type addr); method Maybe#(MemArbiterOp#(addr)) request(); method Action grant(); endinterface // Arbiter clients and servers can be connected in the obvious way. instance Connectable#(MemArbiterClient#(addr), MemArbiterServer#(addr)); module mkConnection(MemArbiterClient#(addr) client, MemArbiterServer#(addr) server, Empty ifc); rule send_request (client.request matches tagged Valid .req); server.request(req); endrule rule send_grant (server.grant()); client.grant(); endrule endmodule endinstance // A MemArbiter manages concurrent access to a memory port. interface MemArbiter#(numeric type num_clients, type addr); // ports allow clients to request memory access. interface Vector#(num_clients, MemArbiterServer#(addr)) ports; // The following methods are to support arbiter chaining. // // Suppose you're arbitrating access to a dual-port // memory. Typically, such a memory specifies that if one port is // writing to an address, the other must not concurrently read or // write that same address. This means the arbiters attached to // each memory port must cooperate to avoid simultaneously granting // conflicting requests from their clients. // // Calling conflict prevents the arbiter from granting a concurrent // request that would result in a write-write, read-write or // write-read conflict. granted_op emits the operation that the // arbiter is granting, if any. // // MemArbiter intances are Connectable: mkConnection(a, b) gives // conflict priority to a. That is, b only grants requests that // don't conflict with a's grant. (* always_ready *) method Action conflict(MemArbiterOp#(addr) conflict); method MemArbiterOp#(addr) granted_op(); endinterface instance Connectable#(MemArbiter#(m, addr), MemArbiter#(n, addr)); module mkConnection(MemArbiter#(m, addr) a, MemArbiter#(n, addr) b, Empty ifc); (* fire_when_enabled *) rule forward_conflict; b.conflict(a.granted_op); endrule endmodule endinstance // mkPriorityMemArbiter returns a MemArbiter that gives priority to // lower numbered ports. module mkPriorityMemArbiter(MemArbiter#(num_clients, addr)) provisos (Bits#(addr, _), Eq#(addr), Min#(num_clients, 1, 1)); Vector#(num_clients, RWire#(MemArbiterOp#(addr))) reqs <- replicateM(mkRWire()); Wire#(Vector#(num_clients, Bool)) grants <- mkBypassWire(); RWire#(MemArbiterOp#(addr)) conflict_in <- mkRWire(); RWire#(MemArbiterOp#(addr)) granted_op_out <- mkRWire(); (* no_implicit_conditions, fire_when_enabled *) rule grant_requests; Vector#(num_clients, Bool) grant = replicate(False); Bool done = False; for (Integer i=0; i