vram/MemArbiter: add a granted_port method to make downstream wiring easier

To implement the mux tree that feeds into RAM ports, we need to know the
port index of the grantee to be able to wire it up. In theory we could
dispense with the per-port grant signal, but keeping it around allows
each client to deal with local concerns separate from the port routing.
This commit is contained in:
David Anderson 2024-09-08 09:26:59 -07:00
parent 2ebf399d62
commit 79b54ca86f
2 changed files with 67 additions and 52 deletions

View File

@ -14,6 +14,8 @@ typedef struct {
addr addr; addr addr;
} MemArbiterOp#(type addr) deriving (Bits, Eq, FShow); } MemArbiterOp#(type addr) deriving (Bits, Eq, FShow);
// mem_ops_conflict reports whether memory accesses a and b would
// cause undefined behavior if they proceed simultaneously.
function Bool mem_ops_conflict(Maybe#(MemArbiterOp#(addr)) a, Maybe#(MemArbiterOp#(addr)) b) function Bool mem_ops_conflict(Maybe#(MemArbiterOp#(addr)) a, Maybe#(MemArbiterOp#(addr)) b)
provisos(Eq#(addr)); provisos(Eq#(addr));
@ -35,34 +37,34 @@ interface MemArbiter#(numeric type num_clients, type addr);
// ports allow clients to request memory access. // ports allow clients to request memory access.
interface Vector#(num_clients, MemArbiterServer#(addr)) ports; interface Vector#(num_clients, MemArbiterServer#(addr)) ports;
// granted_port returns the index in ports of the client that is
// being granted its request.
method UInt#(TLog#(num_clients)) granted_port();
// The following methods are to support arbiter chaining. // The following methods are to support arbiter chaining.
// //
// Suppose you're arbitrating access to a dual-port // Suppose you're arbitrating access to a dual-port memory.
// memory. Typically, such a memory specifies that if one port is // Typically, such a memory specifies that if one port is writing
// writing to an address, the other must not concurrently read or // to an address, the other must not concurrently read or write
// write that same address. This means the arbiters attached to // that same address. This means the arbiters attached to each
// each memory port must cooperate to avoid simultaneously granting // memory port must cooperate to avoid simultaneously granting
// conflicting requests from their clients. // conflicting requests from their clients.
// //
// Calling conflict prevents the arbiter from granting a concurrent // conflict_in supplies an already granted operation that this
// request that would result in a write-write, read-write or // arbiter must avoid conflicting with. conflict_out emits the
// write-read conflict. granted_op emits the operation that the // operation that the arbiter is granting, if any.
// arbiter is granting, if any.
// //
// MemArbiter intances are Connectable: mkConnection(a, b) gives // mkConnection(firstArbiter, secondArbiter) gives conflict
// conflict priority to a. That is, b only grants requests that // priority to firstArbiter. That is, secondArbiter only grants
// don't conflict with a's grant. // requests that don't conflict with grants made by firstArbiter.
(* always_ready *) (* always_ready *)
method Action conflict(MemArbiterOp#(addr) conflict); method Action conflict_in(MemArbiterOp#(addr) conflict);
method MemArbiterOp#(addr) granted_op(); method MemArbiterOp#(addr) conflict_out();
endinterface endinterface
instance Connectable#(MemArbiter#(m, addr), MemArbiter#(n, addr)); instance Connectable#(MemArbiter#(m, addr), MemArbiter#(n, addr));
module mkConnection(MemArbiter#(m, addr) a, MemArbiter#(n, addr) b, Empty ifc); module mkConnection(MemArbiter#(m, addr) a, MemArbiter#(n, addr) b, Empty ifc);
(* fire_when_enabled *) mkConnection(a.conflict_out, b.conflict_in);
rule forward_conflict;
b.conflict(a.granted_op);
endrule
endmodule endmodule
endinstance endinstance
@ -71,13 +73,14 @@ endinstance
module mkPriorityMemArbiter(MemArbiter#(num_clients, addr)) module mkPriorityMemArbiter(MemArbiter#(num_clients, addr))
provisos (Bits#(addr, _), provisos (Bits#(addr, _),
Eq#(addr), Eq#(addr),
Min#(num_clients, 1, 1)); Min#(num_clients, 1, 1),
Alias#(client_idx, UInt#(TLog#(num_clients))));
Vector#(num_clients, RWire#(MemArbiterOp#(addr))) reqs <- replicateM(mkRWire()); Vector#(num_clients, RWire#(MemArbiterOp#(addr))) reqs <- replicateM(mkRWire());
Wire#(Vector#(num_clients, Bool)) grants <- mkBypassWire(); Wire#(Vector#(num_clients, Bool)) grants <- mkBypassWire();
RWire#(MemArbiterOp#(addr)) conflict_in <- mkRWire(); RWire#(MemArbiterOp#(addr)) conflict_op <- mkRWire();
RWire#(MemArbiterOp#(addr)) granted_op_out <- mkRWire(); RWire#(client_idx) granted_idx <- mkRWire();
(* no_implicit_conditions, fire_when_enabled *) (* no_implicit_conditions, fire_when_enabled *)
rule grant_requests; rule grant_requests;
@ -86,11 +89,11 @@ module mkPriorityMemArbiter(MemArbiter#(num_clients, addr))
for (Integer i=0; i<valueOf(num_clients); i=i+1) begin for (Integer i=0; i<valueOf(num_clients); i=i+1) begin
if (reqs[i].wget() matches tagged Valid .req &&& if (reqs[i].wget() matches tagged Valid .req &&&
!mem_ops_conflict(conflict_in.wget(), reqs[i].wget()) &&& !mem_ops_conflict(conflict_op.wget(), reqs[i].wget()) &&&
!done) begin !done) begin
done = True; done = True;
grant[i] = True; grant[i] = True;
granted_op_out.wset(req); granted_idx.wset(fromInteger(i));
end end
end end
@ -105,24 +108,29 @@ module mkPriorityMemArbiter(MemArbiter#(num_clients, addr))
endinterface); endinterface);
interface ports = _ifcs; interface ports = _ifcs;
method conflict = conflict_in.wset; method client_idx granted_port() if (granted_idx.wget() matches tagged Valid .idx);
method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op); return idx;
endmethod
method MemArbiterOp#(addr) conflict_out() if (granted_idx.wget() matches tagged Valid .idx &&&
reqs[idx].wget() matches tagged Valid .op);
return op; return op;
endmethod endmethod
method conflict_in = conflict_op.wset;
endmodule endmodule
typedef struct { typedef struct {
Vector#(n, Bool) grant_vec; Vector#(n, Bool) grant_vec;
Maybe#(MemArbiterOp#(addr)) granted_op; Maybe#(UInt#(TLog#(n))) granted_idx;
} GrantResult#(numeric type n, type addr) deriving (Bits, Eq, FShow); } GrantResult#(numeric type n, type addr) deriving (Bits, Eq, FShow);
// select_grant computes which one entry of requests should be // select_grant computes which one entry of requests should be
// granted. Priority order is descending starting from // granted. Priority order is descending starting from
// requests[hipri]. // requests[hipri].
function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr))) requests, function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr))) requests,
UInt#(TLog#(n)) hipri, client_idx hipri,
Maybe#(MemArbiterOp#(addr)) conflict) Maybe#(MemArbiterOp#(addr)) conflict)
provisos (Eq#(addr)); provisos (Eq#(addr),
Alias#(client_idx, UInt#(TLog#(n))));
function onehot(idx); function onehot(idx);
let ret = replicate(False); let ret = replicate(False);
@ -131,13 +139,15 @@ function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr
endfunction endfunction
function GrantResult#(n, addr) do_fold(GrantResult#(n, addr) acc, function GrantResult#(n, addr) do_fold(GrantResult#(n, addr) acc,
Tuple2#(UInt#(TLog#(n)), Tuple2#(client_idx,
Maybe#(MemArbiterOp#(addr))) next); Maybe#(MemArbiterOp#(addr))) next);
match {.idx, .mreq} = next; match {.idx, .mreq} = next;
if (mreq matches tagged Valid .req &&& acc.granted_op matches tagged Invalid &&& !mem_ops_conflict(conflict, mreq)) if (mreq matches tagged Valid .req &&&
acc.granted_idx matches tagged Invalid &&&
!mem_ops_conflict(conflict, mreq))
return GrantResult{ return GrantResult{
grant_vec: onehot(idx), grant_vec: onehot(idx),
granted_op: tagged Valid req granted_idx: tagged Valid idx
}; };
else else
// Previous grant won, not requesting, or request not satisfiable. // Previous grant won, not requesting, or request not satisfiable.
@ -148,7 +158,7 @@ function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr
let rot = rotateBy(in, fromInteger(valueOf(n)-1)-hipri+1); let rot = rotateBy(in, fromInteger(valueOf(n)-1)-hipri+1);
let seed = GrantResult{ let seed = GrantResult{
grant_vec: replicate(False), grant_vec: replicate(False),
granted_op: tagged Invalid granted_idx: tagged Invalid
}; };
return foldl(do_fold, seed, rot); return foldl(do_fold, seed, rot);
endfunction endfunction
@ -156,19 +166,20 @@ endfunction
module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr)) module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr))
provisos (Bits#(addr, _), provisos (Bits#(addr, _),
Eq#(addr), Eq#(addr),
Min#(num_clients, 1, 1)); Min#(num_clients, 1, 1),
Alias#(client_idx, UInt#(TLog#(num_clients))));
Vector#(num_clients, RWire#(MemArbiterOp#(addr))) reqs <- replicateM(mkRWire); Vector#(num_clients, RWire#(MemArbiterOp#(addr))) reqs <- replicateM(mkRWire);
Wire#(Vector#(num_clients, Bool)) grants <- mkBypassWire(); Wire#(Vector#(num_clients, Bool)) grants <- mkBypassWire();
RWire#(MemArbiterOp#(addr)) conflict_in <- mkRWire(); RWire#(MemArbiterOp#(addr)) conflict_op <- mkRWire();
RWire#(MemArbiterOp#(addr)) granted_op_out <- mkRWire(); RWire#(client_idx) granted_idx_out <- mkRWire();
// high_prio is the index of the client that should be first in // 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, // line to receive access. Every time we grant access to a client,
// the one after that in sequence becomes high_prio in the next // the one after that in sequence becomes high_prio in the next
// round. // round.
Reg#(UInt#(TLog#(num_clients))) high_prio <- mkReg(0); Reg#(client_idx) high_prio <- mkReg(0);
function Maybe#(_t) get_mreq(RWire#(_t) w); function Maybe#(_t) get_mreq(RWire#(_t) w);
return w.wget(); return w.wget();
@ -176,11 +187,11 @@ module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr))
rule grant; rule grant;
let in = map(get_mreq, reqs); let in = map(get_mreq, reqs);
let res = select_grant(in, high_prio, conflict_in.wget()); let res = select_grant(in, high_prio, conflict_op.wget());
grants <= res.grant_vec; grants <= res.grant_vec;
if (res.granted_op matches tagged Valid .op) begin if (res.granted_idx matches tagged Valid .idx) begin
granted_op_out.wset(op); granted_idx_out.wset(idx);
high_prio <= validValue(findElem(True, rotateR(res.grant_vec))); high_prio <= validValue(findElem(True, rotateR(res.grant_vec)));
end end
endrule endrule
@ -193,10 +204,14 @@ module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr))
endinterface); endinterface);
interface ports = _ifcs; interface ports = _ifcs;
method conflict = conflict_in.wset; method client_idx granted_port() if (granted_idx_out.wget() matches tagged Valid .idx);
method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op); return idx;
endmethod
method MemArbiterOp#(addr) conflict_out() if (granted_idx_out.wget() matches tagged Valid .idx &&&
reqs[idx].wget() matches tagged Valid .op);
return op; return op;
endmethod endmethod
method conflict_in = conflict_op.wset;
endmodule endmodule
endpackage endpackage

View File

@ -19,7 +19,7 @@ typedef struct {
Maybe#(MemArbiterOp#(Addr)) conflict; Maybe#(MemArbiterOp#(Addr)) conflict;
Vector#(n, Bool) want_grants; Vector#(n, Bool) want_grants;
Maybe#(MemArbiterOp#(Addr)) want_granted_op; Maybe#(MemArbiterOp#(Addr)) want_conflict_out;
} TestCase#(numeric type n) deriving (Bits, Eq); } TestCase#(numeric type n) deriving (Bits, Eq);
function Maybe#(MemArbiterOp#(Addr)) read(Addr addr); function Maybe#(MemArbiterOp#(Addr)) read(Addr addr);
@ -54,13 +54,13 @@ function TestCase#(n) testCase(String name,
Vector#(n, Maybe#(MemArbiterOp#(Addr))) reqs, Vector#(n, Maybe#(MemArbiterOp#(Addr))) reqs,
Maybe#(MemArbiterOp#(Addr)) conflict, Maybe#(MemArbiterOp#(Addr)) conflict,
Vector#(n, Bool) want_grants, Vector#(n, Bool) want_grants,
Maybe#(MemArbiterOp#(Addr)) want_granted_op); Maybe#(MemArbiterOp#(Addr)) want_conflict_out);
return TestCase{ return TestCase{
name: name, name: name,
reqs: reqs, reqs: reqs,
conflict: conflict, conflict: conflict,
want_grants: want_grants, want_grants: want_grants,
want_granted_op: want_granted_op want_conflict_out: want_conflict_out
}; };
endfunction endfunction
@ -84,14 +84,14 @@ module mkArbiterTB(MemArbiter#(n, Addr) dut, Vector#(m, TestCase#(n)) tests, TB
(* no_implicit_conditions, fire_when_enabled *) (* no_implicit_conditions, fire_when_enabled *)
rule forbid (running && isValid(tests[idx].conflict)); rule forbid (running && isValid(tests[idx].conflict));
dut.conflict(validValue(tests[idx].conflict)); dut.conflict_in(validValue(tests[idx].conflict));
endrule endrule
Wire#(Maybe#(MemArbiterOp#(Addr))) got_granted_op <- mkDWire(tagged Invalid); Wire#(Maybe#(MemArbiterOp#(Addr))) got_conflict_out <- mkDWire(tagged Invalid);
(* fire_when_enabled *) (* fire_when_enabled *)
rule collect_granted_op (running); rule collect_conflict_out (running);
got_granted_op <= tagged Valid dut.granted_op(); got_conflict_out <= tagged Valid dut.conflict_out();
endrule endrule
function Fmt req_s(Maybe#(MemArbiterOp#(Addr)) v); function Fmt req_s(Maybe#(MemArbiterOp#(Addr)) v);
@ -107,13 +107,13 @@ module mkArbiterTB(MemArbiter#(n, Addr) dut, Vector#(m, TestCase#(n)) tests, TB
let test = tests[idx]; let test = tests[idx];
let reqs = test.reqs; let reqs = test.reqs;
let want_grants = test.want_grants; let want_grants = test.want_grants;
let want_granted_op = test.want_granted_op; let want_conflict_out = test.want_conflict_out;
Vector#(n, Bool) got_grants = newVector; Vector#(n, Bool) got_grants = newVector;
for (Integer i=0; i<valueOf(n); i=i+1) for (Integer i=0; i<valueOf(n); i=i+1)
got_grants[i] = dut.ports[i].grant(); got_grants[i] = dut.ports[i].grant();
$display("RUN %s (%0d)", tests[idx].name, idx); $display("RUN %s (%0d)", tests[idx].name, idx);
if (got_grants != want_grants || got_granted_op != want_granted_op) begin if (got_grants != want_grants || got_conflict_out != want_conflict_out) begin
$display("input:"); $display("input:");
for (Integer i=0; i<valueOf(n); i=i+1) for (Integer i=0; i<valueOf(n); i=i+1)
$display(" ", $format("%0d", i), ": ", req_s(reqs[i])); $display(" ", $format("%0d", i), ": ", req_s(reqs[i]));
@ -121,10 +121,10 @@ module mkArbiterTB(MemArbiter#(n, Addr) dut, Vector#(m, TestCase#(n)) tests, TB
$display(" output:"); $display(" output:");
$display(" grants: ", fshow(got_grants)); $display(" grants: ", fshow(got_grants));
$display(" granted: ", fshow(got_granted_op)); $display(" granted: ", fshow(got_conflict_out));
$display(" want grants: ", fshow(tests[idx].want_grants)); $display(" want grants: ", fshow(tests[idx].want_grants));
$display(" want granted: ", fshow(want_granted_op)); $display(" want granted: ", fshow(want_conflict_out));
dynamicAssert(False, "wrong arbiter output"); dynamicAssert(False, "wrong arbiter output");
end end