Compare commits

..

No commits in common. "1929bbe3cc0d66e2585d7c71fe357acb2d5eec7e" and "913c407224c59e60e05907b92b30eefcf9f18275" have entirely different histories.

4 changed files with 117 additions and 225 deletions

View File

@ -5,6 +5,7 @@ import Vector::*;
export MemArbiterOp(..);
export MemArbiterServer(..);
export MemArbiterClient(..);
export MemArbiter(..), mkPriorityMemArbiter, mkRoundRobinMemArbiter;
// A MemArbiterOp is an operation that a client is seeking permission
@ -14,8 +15,6 @@ typedef struct {
addr addr;
} 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)
provisos(Eq#(addr));
@ -32,39 +31,58 @@ interface MemArbiterServer#(type addr);
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;
// 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.
//
// 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
// 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.
//
// conflict_in supplies an already granted operation that this
// arbiter must avoid conflicting with. conflict_out emits the
// operation that the arbiter is granting, if any.
// 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.
//
// mkConnection(firstArbiter, secondArbiter) gives conflict
// priority to firstArbiter. That is, secondArbiter only grants
// requests that don't conflict with grants made by firstArbiter.
// 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_in(MemArbiterOp#(addr) conflict);
method MemArbiterOp#(addr) conflict_out();
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);
mkConnection(a.conflict_out, b.conflict_in);
(* fire_when_enabled *)
rule forward_conflict;
b.conflict(a.granted_op);
endrule
endmodule
endinstance
@ -73,14 +91,13 @@ endinstance
module mkPriorityMemArbiter(MemArbiter#(num_clients, addr))
provisos (Bits#(addr, _),
Eq#(addr),
Min#(num_clients, 1, 1),
Alias#(client_idx, UInt#(TLog#(num_clients))));
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_op <- mkRWire();
RWire#(client_idx) granted_idx <- mkRWire();
RWire#(MemArbiterOp#(addr)) conflict_in <- mkRWire();
RWire#(MemArbiterOp#(addr)) granted_op_out <- mkRWire();
(* no_implicit_conditions, fire_when_enabled *)
rule grant_requests;
@ -89,11 +106,11 @@ module mkPriorityMemArbiter(MemArbiter#(num_clients, addr))
for (Integer i=0; i<valueOf(num_clients); i=i+1) begin
if (reqs[i].wget() matches tagged Valid .req &&&
!mem_ops_conflict(conflict_op.wget(), reqs[i].wget()) &&&
!mem_ops_conflict(conflict_in.wget(), reqs[i].wget()) &&&
!done) begin
done = True;
grant[i] = True;
granted_idx.wset(fromInteger(i));
granted_op_out.wset(req);
end
end
@ -108,29 +125,24 @@ module mkPriorityMemArbiter(MemArbiter#(num_clients, addr))
endinterface);
interface ports = _ifcs;
method client_idx granted_port() if (granted_idx.wget() matches tagged Valid .idx);
return idx;
endmethod
method MemArbiterOp#(addr) conflict_out() if (granted_idx.wget() matches tagged Valid .idx &&&
reqs[idx].wget() matches tagged Valid .op);
method conflict = conflict_in.wset;
method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op);
return op;
endmethod
method conflict_in = conflict_op.wset;
endmodule
typedef struct {
Vector#(n, Bool) grant_vec;
Maybe#(UInt#(TLog#(n))) granted_idx;
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,
client_idx hipri,
UInt#(TLog#(n)) hipri,
Maybe#(MemArbiterOp#(addr)) conflict)
provisos (Eq#(addr),
Alias#(client_idx, UInt#(TLog#(n))));
provisos (Eq#(addr));
function onehot(idx);
let ret = replicate(False);
@ -139,15 +151,13 @@ function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr
endfunction
function GrantResult#(n, addr) do_fold(GrantResult#(n, addr) acc,
Tuple2#(client_idx,
Tuple2#(UInt#(TLog#(n)),
Maybe#(MemArbiterOp#(addr))) next);
match {.idx, .mreq} = next;
if (mreq matches tagged Valid .req &&&
acc.granted_idx matches tagged Invalid &&&
!mem_ops_conflict(conflict, mreq))
if (mreq matches tagged Valid .req &&& acc.granted_op matches tagged Invalid &&& !mem_ops_conflict(conflict, mreq))
return GrantResult{
grant_vec: onehot(idx),
granted_idx: tagged Valid idx
granted_op: tagged Valid req
};
else
// Previous grant won, not requesting, or request not satisfiable.
@ -158,7 +168,7 @@ function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr
let rot = rotateBy(in, fromInteger(valueOf(n)-1)-hipri+1);
let seed = GrantResult{
grant_vec: replicate(False),
granted_idx: tagged Invalid
granted_op: tagged Invalid
};
return foldl(do_fold, seed, rot);
endfunction
@ -166,20 +176,19 @@ endfunction
module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr))
provisos (Bits#(addr, _),
Eq#(addr),
Min#(num_clients, 1, 1),
Alias#(client_idx, UInt#(TLog#(num_clients))));
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_op <- mkRWire();
RWire#(client_idx) granted_idx_out <- mkRWire();
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#(client_idx) high_prio <- mkReg(0);
Reg#(UInt#(TLog#(num_clients))) high_prio <- mkReg(0);
function Maybe#(_t) get_mreq(RWire#(_t) w);
return w.wget();
@ -187,11 +196,11 @@ module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr))
rule grant;
let in = map(get_mreq, reqs);
let res = select_grant(in, high_prio, conflict_op.wget());
let res = select_grant(in, high_prio, conflict_in.wget());
grants <= res.grant_vec;
if (res.granted_idx matches tagged Valid .idx) begin
granted_idx_out.wset(idx);
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
@ -204,14 +213,10 @@ module mkRoundRobinMemArbiter(MemArbiter#(num_clients, addr))
endinterface);
interface ports = _ifcs;
method client_idx granted_port() if (granted_idx_out.wget() matches tagged Valid .idx);
return idx;
endmethod
method MemArbiterOp#(addr) conflict_out() if (granted_idx_out.wget() matches tagged Valid .idx &&&
reqs[idx].wget() matches tagged Valid .op);
method conflict = conflict_in.wset;
method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op);
return op;
endmethod
method conflict_in = conflict_op.wset;
endmodule
endpackage

View File

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

View File

@ -1,99 +0,0 @@
package VRAM;
import Connectable::*;
import GetPut::*;
import ClientServer::*;
import Vector::*;
import FIFOF::*;
import SpecialFIFOs::*;
import MemArbiter::*;
import VRAMCore::*;
// Re-exports from VRAMCore
export VRAMAddr, VRAMData, VRAMRequest(..), VRAMResponse(..);
export VRAMServer(..);
export VRAM(..), mkVRAM;
typedef Server#(VRAMRequest, VRAMResponse) VRAMServer;
// mkArbitratedVRAMServers expands a VRAMServer port into multiple
// ports through the use of a MemArbiter.
module mkArbitratedVRAMServers(VRAMServer ram, MemArbiter#(n, VRAMAddr) arb, Vector#(n, VRAMServer) ifc)
provisos (Min#(n, 1, 1),
Alias#(port_idx, UInt#(TLog#(n))));
Vector#(n, FIFOF#(VRAMRequest)) requests <- replicateM(mkBypassFIFOF());
Vector#(n, FIFOF#(VRAMResponse)) responses <- replicateM(mkBypassFIFOF());
Reg#(Maybe#(port_idx)) awaiting_response[2] <- mkCReg(2, tagged Invalid);
(* fire_when_enabled *)
rule request_ports;
for (Integer i=0; i<valueOf(n); i=i+1)
if (requests[i].notEmpty) begin
let req = requests[i].first;
let arb_req = MemArbiterOp{
write: isValid(req.data),
addr: req.addr
};
arb.ports[i].request(arb_req);
end
endrule
(* fire_when_enabled *)
rule submit (awaiting_response[1] matches tagged Invalid);
let port = arb.granted_port();
ram.request.put(requests[port].first);
requests[port].deq();
awaiting_response[1] <= tagged Valid port;
endrule
(* fire_when_enabled *)
rule response (awaiting_response[0] matches tagged Valid .port &&& responses[port].notFull);
let resp <- ram.response.get();
responses[port].enq(resp);
awaiting_response[0] <= tagged Invalid;
endrule
return map(uncurry(toGPServer), zip(requests, responses));
endmodule
// VRAM is a GARY video RAM and its memory ports.
interface VRAM;
interface VRAMServer cpu;
interface VRAMServer debugger;
interface VRAMServer palette;
interface VRAMServer tile1;
interface VRAMServer tile2;
interface VRAMServer sprite;
endinterface
// mkVRAM constructs a VRAM of the requested size. Memory access is
// spread across two internal ports as follows:
//
// Port A: strict most-important-wins priority: CPU, then debugger,
// then palette DAC.
// Port B: equal round-robin prioritization between two tile engines
// and the sprite engine.
module mkVRAM(Integer num_kilobytes, VRAM ifc);
VRAMCore ram <- mkVRAMCore(num_kilobytes);
MemArbiter#(3, VRAMAddr) arbA <- mkPriorityMemArbiter();
Vector#(3, VRAMServer) portA <- mkArbitratedVRAMServers(ram.portA, arbA);
MemArbiter#(3, VRAMAddr) arbB <- mkRoundRobinMemArbiter();
Vector#(3, VRAMServer) portB <- mkArbitratedVRAMServers(ram.portB, arbB);
// Connect up the arbiters so they correctly prevent write-write
// and write-read conflicts.
mkConnection(arbA, arbB);
interface cpu = portA[0];
interface debugger = portA[1];
interface palette = portA[2];
interface tile1 = portB[0];
interface tile2 = portB[1];
interface sprite = portB[2];
endmodule
endpackage

View File

@ -1,82 +1,48 @@
package VRAMCore;
import Connectable::*;
import GetPut::*;
import ClientServer::*;
import BRAMCore::*;
import DReg::*;
import BRAM::*;
import Vector::*;
import FIFOF::*;
import SpecialFIFOs::*;
import Real::*;
import Printf::*;
import DelayLine::*;
import ECP5_RAM::*;
export VRAMAddr;
export VRAMData;
export VRAMRequest(..);
export VRAMResponse(..);
export VRAMCore(..);
export VRAMRequest;
export VRAMResponse;
export VRAMClient;
export VRAMServer;
export VRAMCore;
export mkVRAMCore;
typedef Bit#(8) VRAMData;
typedef UInt#(17) VRAMAddr;
typedef UInt#(2) ArrayAddr;
typedef UInt#(3) ChipAddr;
// Each byte RAM we build below can address 4096 bytes, which is 12
// address bits.
typedef UInt#(12) ByteAddr;
typedef UInt#(3) ChipAddr;
// ByteRAM is two EBRs glued together to make a whole-byte memory.
typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM;
typedef struct {
VRAMAddr addr;
Maybe#(VRAMData) data;
} VRAMRequest deriving (Bits, Eq);
typedef struct {
VRAMData data;
} VRAMResponse deriving (Bits, Eq);
module mkNibbleRAM_ECP5(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) ifc);
EBRPortConfig cfg = defaultValue;
cfg.chip_select_addr = chip_addr;
let _ret <- mkEBRCore(cfg, cfg);
return _ret;
endmodule
module mkNibbleRAM_Sim(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) ifc);
BRAM_DUAL_PORT#(ByteAddr, Bit#(4)) ram <- mkBRAMCore2(4096, False);
interface EBRPort portA;
method Action put(UInt#(3) chip_select, Bool write, ByteAddr address, Bit#(4) datain);
if (chip_select == chip_addr)
ram.a.put(write, address, datain);
endmethod
method read = ram.a.read;
endinterface
interface EBRPort portB;
method Action put(UInt#(3) chip_select, Bool write, ByteAddr address, Bit#(4) datain);
if (chip_select == chip_addr)
ram.b.put(write, address, datain);
endmethod
method read = ram.b.read;
endinterface
endmodule
module mkNibbleRAM(ChipAddr chip_addr, EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) ifc);
let _ret;
if (genC())
_ret <- mkNibbleRAM_Sim(chip_addr);
else
_ret <- mkNibbleRAM_ECP5(chip_addr);
return _ret;
endmodule
// mkByteRAM glues two ECP5 EBRs together to make a 4096x8b memory
// block. Like the underlying ECP5 EBRs, callers must bring their own
// flow control to read out responses one cycle after putting a read
// request.
module mkByteRAM(ChipAddr chip_addr, ByteRAM ifc);
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) upper <- mkNibbleRAM(chip_addr);
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) lower <- mkNibbleRAM(chip_addr);
EBRPortConfig cfg = defaultValue;
cfg.chip_select_addr = chip_addr;
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) upper <- mkEBRCore(cfg, cfg);
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) lower <- mkEBRCore(cfg, cfg);
interface EBRPort portA;
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
@ -153,9 +119,25 @@ module mkByteRAMArray(Integer num_chips, ByteRAM ifc);
endinterface
endmodule
typedef UInt#(2) ArrayAddr;
typedef UInt#(17) VRAMAddr;
typedef struct {
VRAMAddr addr;
Maybe#(VRAMData) data;
} VRAMRequest deriving (Bits, Eq);
typedef struct {
VRAMData data;
} VRAMResponse deriving (Bits, Eq);
typedef Server#(VRAMRequest, VRAMResponse) VRAMServer;
typedef Client#(VRAMRequest, VRAMResponse) VRAMClient;
interface VRAMCore;
interface Server#(VRAMRequest, VRAMResponse) portA;
interface Server#(VRAMRequest, VRAMResponse) portB;
interface VRAMServer portA;
interface VRAMServer portB;
endinterface
// mkVRAMCore creates a dual port VRAM of the specified size, using
@ -181,7 +163,11 @@ module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
let num_arrays = ceil(fromInteger(num_byterams) / 8);
function Tuple3#(ArrayAddr, ChipAddr, ByteAddr) split_addr(VRAMAddr a);
return unpack(pack(a));
if (num_bytes < 128*1024)
a = a % fromInteger(num_bytes);
match {.top, .byteaddr} = split(pack(a));
Tuple2#(Bit#(SizeOf#(ArrayAddr)), Bit#(SizeOf#(ChipAddr))) route = split(top);
return tuple3(unpack(tpl_1(route)), unpack(tpl_2(route)), unpack(byteaddr));
endfunction
ByteRAM arrays[num_arrays];
@ -193,7 +179,7 @@ module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
Reg#(Maybe#(ArrayAddr)) inflight_A[2] <- mkCReg(2, tagged Invalid);
Reg#(Maybe#(ArrayAddr)) inflight_B[2] <- mkCReg(2, tagged Invalid);
interface Server portA;
interface VRAMServer portA;
interface Put request;
method Action put(VRAMRequest req) if (inflight_A[1] matches tagged Invalid);
match {.array, .chip, .byteaddr} = split_addr(req.addr);
@ -210,7 +196,7 @@ module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
endinterface
endinterface
interface Server portB;
interface VRAMServer portB;
interface Put request;
method Action put(VRAMRequest req) if (inflight_B[1] matches tagged Invalid);
match {.array, .chip, .byteaddr} = split_addr(req.addr);