From 191cd1bfa2afc16d7e3f7a68832c42a2fcd3553c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sun, 8 Sep 2024 13:31:53 -0700 Subject: [PATCH] vram: document MemArbiter, fix round-robin ordering bug --- vram/MemArbiter.bsv | 58 ++++++++++++++++++++++++------ vram/MemArbiter_Test.bsv | 78 +++++++++++++++++++++++----------------- 2 files changed, 92 insertions(+), 44 deletions(-) diff --git a/vram/MemArbiter.bsv b/vram/MemArbiter.bsv index cf6f9e2..70b1c4d 100644 --- a/vram/MemArbiter.bsv +++ b/vram/MemArbiter.bsv @@ -8,25 +8,26 @@ 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); -// A MemArbiterServer receives requests for memory access and emits -// grants. +// A MemArbiterServer receives requests and emits grants. interface MemArbiterServer#(type addr); method Action request(MemArbiterOp#(addr) req); method Bool grant(); endinterface -// A MemArbiterClient emits requests for memory access and emits -// grants. +// 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); @@ -39,12 +40,42 @@ instance Connectable#(MemArbiterClient#(addr), MemArbiterServer#(addr)); 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 forbid_addr prevents the arbiter from granting a + // concurrent request to access the given address. forbidden_addr + // emits the address for which a write access is being granted. + // + // MemArbiter intances are Connectable: mkConnection(a, b) gives + // conflict priority to a. That is, b will not grant requests that + // conflict with the grant that a has emitted. method Action forbid_addr(addr addr); method addr forbidden_addr(); 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_forbid; + b.forbid_addr(a.forbidden_addr); + 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), @@ -98,8 +129,9 @@ typedef struct { Maybe#(addr) blocked_addr; } GrantResult#(numeric type n, type addr) deriving (Bits, Eq, FShow); +// select_grant computes which one entry of requests should be granted. function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr))) requests, - UInt#(TLog#(n)) lopri, + UInt#(TLog#(n)) hipri, Maybe#(addr) block_addr) provisos (Eq#(addr)); @@ -130,7 +162,7 @@ function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr endfunction let in = zip(map(fromInteger, genVector()), requests); - let rot = reverse(rotateBy(reverse(in), lopri)); + let rot = rotateBy(in, fromInteger(valueOf(n)-1)-hipri+1); let seed = GrantResult{ granted: False, grant_vec: replicate(False), @@ -151,10 +183,11 @@ module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr)) RWire#(addr) blocked_in <- mkRWire(); Wire#(Maybe#(addr)) blocked_out <- mkBypassWire(); - // low_priority is the index of the client that should be last 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, - // that client becomes low_priority for the next round. - Reg#(UInt#(TLog#(num_clients))) low_priority <- mkReg(0); + // 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(); @@ -162,11 +195,14 @@ module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr)) rule grant; let in = map(get_mreq, reqs); - let res = select_grant(in, low_priority, blocked_in.wget()); + let res = select_grant(in, high_prio, blocked_in.wget()); grants <= res.grant_vec; if (res.granted) - low_priority <= res.selected+1; + if (res.selected == fromInteger(valueOf(num_clients)-1)) + high_prio <= 0; + else + high_prio <= res.selected+1; blocked_out <= res.blocked_addr; endrule diff --git a/vram/MemArbiter_Test.bsv b/vram/MemArbiter_Test.bsv index b8d4940..f87fc79 100644 --- a/vram/MemArbiter_Test.bsv +++ b/vram/MemArbiter_Test.bsv @@ -125,7 +125,7 @@ module mkArbiterTB(MemArbiter#(n, Addr) dut, Vector#(m, TestCase#(n)) tests, TB $display("RUN %s (%0d)", tests[idx].name, idx); if (got_grants != want_grants || got_forbid_addr != want_forbid_addr) begin - $display(" input:"); + $display("input:"); for (Integer i=0; i