gary/vram/MemArbiter.bsv

203 lines
7.0 KiB
Plaintext
Raw Normal View History

package MemArbiter;
import Connectable::*;
import Vector::*;
export MemArbiterOp(..);
export MemArbiterServer(..);
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 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<valueOf(num_clients); i=i+1) begin
if (reqs[i].wget() matches tagged Valid .req &&&
!mem_ops_conflict(conflict_in.wget(), reqs[i].wget()) &&&
!done) begin
done = True;
grant[i] = True;
granted_op_out.wset(req);
end
end
grants <= grant;
endrule
Vector#(num_clients, MemArbiterServer#(addr)) _ifcs = newVector();
for (Integer i=0; i<valueOf(num_clients); i=i+1)
_ifcs[i] = (interface MemArbiterServer#(addr);
method request = reqs[i].wset;
method grant = grants[i];
endinterface);
interface ports = _ifcs;
method conflict = conflict_in.wset;
method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op);
return op;
endmethod
endmodule
typedef struct {
Vector#(n, Bool) grant_vec;
Maybe#(MemArbiterOp#(addr)) granted_op;
} GrantResult#(numeric type n, type addr) deriving (Bits, Eq, FShow);
// select_grant computes which one entry of requests should be
// granted. Priority order is descending starting from
// requests[hipri].
function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr))) requests,
UInt#(TLog#(n)) hipri,
Maybe#(MemArbiterOp#(addr)) conflict)
provisos (Eq#(addr));
function onehot(idx);
let ret = replicate(False);
ret[idx] = True;
return ret;
endfunction
function GrantResult#(n, addr) do_fold(GrantResult#(n, addr) acc,
Tuple2#(UInt#(TLog#(n)),
Maybe#(MemArbiterOp#(addr))) next);
match {.idx, .mreq} = next;
if (mreq matches tagged Valid .req &&& acc.granted_op matches tagged Invalid &&& !mem_ops_conflict(conflict, mreq))
return GrantResult{
grant_vec: onehot(idx),
granted_op: tagged Valid req
};
else
// Previous grant won, not requesting, or request not satisfiable.
return acc;
endfunction
let in = zip(map(fromInteger, genVector()), requests);
let rot = rotateBy(in, fromInteger(valueOf(n)-1)-hipri+1);
let seed = GrantResult{
grant_vec: replicate(False),
granted_op: tagged Invalid
};
return foldl(do_fold, seed, rot);
endfunction
module mkRoundRobinMemArbiter(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();
// high_prio is the index of the client that should be first in
// line to receive access. Every time we grant access to a client,
// the one after that in sequence becomes high_prio in the next
// round.
Reg#(UInt#(TLog#(num_clients))) high_prio <- mkReg(0);
function Maybe#(_t) get_mreq(RWire#(_t) w);
return w.wget();
endfunction
rule grant;
let in = map(get_mreq, reqs);
let res = select_grant(in, high_prio, conflict_in.wget());
grants <= res.grant_vec;
if (res.granted_op matches tagged Valid .op) begin
granted_op_out.wset(op);
high_prio <= validValue(findElem(True, rotateR(res.grant_vec)));
end
endrule
Vector#(num_clients, MemArbiterServer#(addr)) _ifcs = newVector();
for (Integer i=0; i<valueOf(num_clients); i=i+1)
_ifcs[i] = (interface MemArbiterServer#(addr);
method request = reqs[i].wset;
method grant = grants[i];
endinterface);
interface ports = _ifcs;
method conflict = conflict_in.wset;
method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op);
return op;
endmethod
endmodule
endpackage