Compare commits
No commits in common. "e5aabcbf4af208d744e606d366fc93dc391c8cb2" and "81e5264d22d7569602ab20adcb1b63c84b2d0f90" have entirely different histories.
e5aabcbf4a
...
81e5264d22
|
@ -4,7 +4,6 @@ import MemArbiter::*;
|
||||||
import Vector::*;
|
import Vector::*;
|
||||||
import DReg::*;
|
import DReg::*;
|
||||||
import DelayLine::*;
|
import DelayLine::*;
|
||||||
import Connectable::*;
|
|
||||||
|
|
||||||
typedef UInt#(2) Addr;
|
typedef UInt#(2) Addr;
|
||||||
|
|
||||||
|
@ -23,51 +22,70 @@ endinterface
|
||||||
|
|
||||||
(* synthesize, clock_prefix="clk_25mhz", reset_prefix="rst_btn" *)
|
(* synthesize, clock_prefix="clk_25mhz", reset_prefix="rst_btn" *)
|
||||||
module mkTop(Top);
|
module mkTop(Top);
|
||||||
Vector#(6, Reg#(Maybe#(MemArbiterOp#(Addr)))) wrin <- replicateM(mkDReg(tagged Invalid));
|
Vector#(2, Reg#(Maybe#(MemArbiterWrite#(Addr)))) wrin <- replicateM(mkDReg(tagged Invalid));
|
||||||
|
Vector#(4, Reg#(Maybe#(Addr))) rdin <- replicateM(mkDReg(tagged Invalid));
|
||||||
|
|
||||||
MemArbiter#(3, Addr) portA <- mkPriorityMemArbiter();
|
MemArbiter#(Addr) ret <- mkMemArbiter();
|
||||||
MemArbiter#(3, Addr) portB <- mkRoundRobinMemArbiter();
|
|
||||||
mkConnection(portA, portB);
|
|
||||||
|
|
||||||
let arbiters = append(portA.ports, portB.ports);
|
|
||||||
|
|
||||||
Reg#(Vector#(6, Bool)) ok <- mkReg(replicate(False));
|
Reg#(Vector#(6, Bool)) ok <- mkReg(replicate(False));
|
||||||
|
|
||||||
for (Integer i=0; i<6; i=i+1) begin
|
rule req_cpu (wrin[0] matches tagged Valid .req);
|
||||||
rule req (wrin[i] matches tagged Valid .req);
|
ret.cpu.request(req);
|
||||||
arbiters[i].request(req);
|
endrule
|
||||||
|
|
||||||
|
rule req_debugger (wrin[1] matches tagged Valid .req);
|
||||||
|
ret.debugger.request(req);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
rule req_palette (rdin[0] matches tagged Valid .addr);
|
||||||
|
ret.palette.request(addr);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
rule req_tile1 (rdin[1] matches tagged Valid .addr);
|
||||||
|
ret.tile1.request(addr);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
rule req_tile2 (rdin[2] matches tagged Valid .addr);
|
||||||
|
ret.tile2.request(addr);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
rule req_sprite (rdin[3] matches tagged Valid .addr);
|
||||||
|
ret.sprite.request(addr);
|
||||||
endrule
|
endrule
|
||||||
end
|
|
||||||
|
|
||||||
rule resp;
|
rule resp;
|
||||||
function Bool get(MemArbiterServer#(Addr) s);
|
Vector#(6, Bool) r = newVector;
|
||||||
return s.grant();
|
r[0] = ret.cpu.grant();
|
||||||
endfunction
|
r[1] = ret.debugger.grant();
|
||||||
ok <= map(get, arbiters);
|
r[2] = ret.palette.grant();
|
||||||
|
r[3] = ret.tile1.grant();
|
||||||
|
r[4] = ret.tile2.grant();
|
||||||
|
r[5] = ret.sprite.grant();
|
||||||
|
ok <= r;
|
||||||
endrule
|
endrule
|
||||||
|
|
||||||
method Action cpu(Bool write, Addr addr);
|
method Action cpu(Bool write, Addr addr);
|
||||||
wrin[0] <= tagged Valid MemArbiterOp{write: write, addr: addr};
|
wrin[0] <= tagged Valid MemArbiterWrite{write: write, addr: addr};
|
||||||
endmethod
|
endmethod
|
||||||
|
|
||||||
method Action debugger(Bool write, Addr addr);
|
method Action debugger(Bool write, Addr addr);
|
||||||
wrin[1] <= tagged Valid MemArbiterOp{write: write, addr: addr};
|
wrin[1] <= tagged Valid MemArbiterWrite{write: write, addr: addr};
|
||||||
endmethod
|
endmethod
|
||||||
|
|
||||||
method Action palette(Addr addr);
|
method Action palette(Addr addr);
|
||||||
wrin[2] <= tagged Valid MemArbiterOp{write: False, addr: addr};
|
rdin[0] <= tagged Valid addr;
|
||||||
endmethod
|
endmethod
|
||||||
|
|
||||||
method Action tile1(Addr addr);
|
method Action tile1(Addr addr);
|
||||||
wrin[3] <= tagged Valid MemArbiterOp{write: False, addr: addr};
|
rdin[1] <= tagged Valid addr;
|
||||||
endmethod
|
endmethod
|
||||||
|
|
||||||
method Action tile2(Addr addr);
|
method Action tile2(Addr addr);
|
||||||
wrin[4] <= tagged Valid MemArbiterOp{write: False, addr: addr};
|
rdin[2] <= tagged Valid addr;
|
||||||
endmethod
|
endmethod
|
||||||
|
|
||||||
method Action sprite(Addr addr);
|
method Action sprite(Addr addr);
|
||||||
wrin[5] <= tagged Valid MemArbiterOp{write: False, addr: addr};
|
rdin[3] <= tagged Valid addr;
|
||||||
endmethod
|
endmethod
|
||||||
|
|
||||||
method Bit#(6) grants();
|
method Bit#(6) grants();
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package Top;
|
package Top;
|
||||||
|
|
||||||
import VRAMCore::*;
|
import VRAM::*;
|
||||||
import ECP5_RAM::*;
|
import ECP5_RAM::*;
|
||||||
import TriState::*;
|
import TriState::*;
|
||||||
|
|
||||||
(* synthesize *)
|
(* synthesize *)
|
||||||
module mkTop(VRAMCore);
|
module mkTop(VRAM);
|
||||||
let _ret <- mkVRAMCore(112);
|
let _ret <- mkVRAM(112);
|
||||||
return _ret;
|
return _ret;
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
|
|
|
@ -3,45 +3,30 @@ package MemArbiter;
|
||||||
import Connectable::*;
|
import Connectable::*;
|
||||||
import Vector::*;
|
import Vector::*;
|
||||||
|
|
||||||
export MemArbiterOp(..);
|
export MemArbiterWrite(..);
|
||||||
export MemArbiterServer(..);
|
export MemArbiterServer(..);
|
||||||
export MemArbiterClient(..);
|
export MemArbiterClient(..);
|
||||||
export MemArbiter(..), mkPriorityMemArbiter, mkRoundRobinMemArbiter;
|
export MemArbiter(..);
|
||||||
|
export mkMemArbiter;
|
||||||
|
|
||||||
// A MemArbiterOp is an operation that a client is seeking permission
|
// A MemArbiterServer receives requests for memory access and emits
|
||||||
// to perform.
|
// grants.
|
||||||
typedef struct {
|
interface MemArbiterServer#(type request);
|
||||||
Bool write;
|
method Action request(request req);
|
||||||
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();
|
method Bool grant();
|
||||||
endinterface
|
endinterface
|
||||||
|
|
||||||
// A MemArbiterClient emits requests and receives grants.
|
// A MemArbiterClient emits requests for memory access and emits
|
||||||
interface MemArbiterClient#(type addr);
|
// grants.
|
||||||
method Maybe#(MemArbiterOp#(addr)) request();
|
interface MemArbiterClient#(type request);
|
||||||
|
method request request();
|
||||||
method Action grant();
|
method Action grant();
|
||||||
endinterface
|
endinterface
|
||||||
|
|
||||||
// Arbiter clients and servers can be connected in the obvious way.
|
instance Connectable#(MemArbiterClient#(req), MemArbiterServer#(req));
|
||||||
instance Connectable#(MemArbiterClient#(addr), MemArbiterServer#(addr));
|
module mkConnection(MemArbiterClient#(req) client, MemArbiterServer#(req) server, Empty ifc);
|
||||||
module mkConnection(MemArbiterClient#(addr) client, MemArbiterServer#(addr) server, Empty ifc);
|
rule send_request;
|
||||||
rule send_request (client.request matches tagged Valid .req);
|
server.request(client.request());
|
||||||
server.request(req);
|
|
||||||
endrule
|
endrule
|
||||||
|
|
||||||
rule send_grant (server.grant());
|
rule send_grant (server.grant());
|
||||||
|
@ -50,173 +35,157 @@ instance Connectable#(MemArbiterClient#(addr), MemArbiterServer#(addr));
|
||||||
endmodule
|
endmodule
|
||||||
endinstance
|
endinstance
|
||||||
|
|
||||||
// A MemArbiter manages concurrent access to a memory port.
|
typedef struct {
|
||||||
interface MemArbiter#(numeric type num_clients, type addr);
|
Bool write;
|
||||||
// ports allow clients to request memory access.
|
addr addr;
|
||||||
interface Vector#(num_clients, MemArbiterServer#(addr)) ports;
|
} MemArbiterWrite#(type addr) deriving (Bits, Eq);
|
||||||
|
|
||||||
// The following methods are to support arbiter chaining.
|
// A MemArbiter manages concurrent access to memory ports.
|
||||||
//
|
interface MemArbiter#(type addr);
|
||||||
// Suppose you're arbitrating access to a dual-port
|
interface MemArbiterServer#(MemArbiterWrite#(addr)) cpu;
|
||||||
// memory. Typically, such a memory specifies that if one port is
|
interface MemArbiterServer#(MemArbiterWrite#(addr)) debugger;
|
||||||
// writing to an address, the other must not concurrently read or
|
interface MemArbiterServer#(addr) palette;
|
||||||
// write that same address. This means the arbiters attached to
|
|
||||||
// each memory port must cooperate to avoid simultaneously granting
|
interface MemArbiterServer#(addr) tile1;
|
||||||
// conflicting requests from their clients.
|
interface MemArbiterServer#(addr) tile2;
|
||||||
//
|
interface MemArbiterServer#(addr) sprite;
|
||||||
// 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
|
endinterface
|
||||||
|
|
||||||
instance Connectable#(MemArbiter#(m, addr), MemArbiter#(n, addr));
|
// mkMemArbiter builds a GARY memory arbiter.
|
||||||
module mkConnection(MemArbiter#(m, addr) a, MemArbiter#(n, addr) b, Empty ifc);
|
//
|
||||||
|
// Port A arbitrates with strict priority: CPU requests go first, then
|
||||||
|
// the debugger, then the palette DAC.
|
||||||
|
//
|
||||||
|
// Port B does round-robin arbitration, giving each client a fair
|
||||||
|
// share of memory access.
|
||||||
|
module mkMemArbiter(MemArbiter#(addr))
|
||||||
|
provisos(Bits#(addr, _),
|
||||||
|
Eq#(addr),
|
||||||
|
Alias#(write_req, MemArbiterWrite#(addr)));
|
||||||
|
|
||||||
|
//////
|
||||||
|
// Port A users
|
||||||
|
|
||||||
|
RWire#(write_req) cpu_req <- mkRWire();
|
||||||
|
RWire#(write_req) debugger_req <- mkRWire();
|
||||||
|
PulseWire palette_req <- mkPulseWire();
|
||||||
|
|
||||||
|
PulseWire cpu_ok <- mkPulseWire();
|
||||||
|
PulseWire debugger_ok <- mkPulseWire();
|
||||||
|
PulseWire palette_ok <- mkPulseWire();
|
||||||
|
|
||||||
|
// Address written to by port A, if any. Used to block port B
|
||||||
|
// clients that are trying to read the same address.
|
||||||
|
RWire#(addr) written_addr <- mkRWire();
|
||||||
|
|
||||||
|
// We could be fancy with rule conditions to express the priorities
|
||||||
|
// between clients, but Bluespec has the preempts annotation to
|
||||||
|
// express the ranking directly.
|
||||||
|
(* preempts = "grant_cpu, (grant_debugger, grant_palette)" *)
|
||||||
|
(* preempts = "grant_debugger, grant_palette" *)
|
||||||
|
|
||||||
(* fire_when_enabled *)
|
(* fire_when_enabled *)
|
||||||
rule forward_conflict;
|
rule grant_cpu (cpu_req.wget matches tagged Valid .req);
|
||||||
b.conflict(a.granted_op);
|
cpu_ok.send();
|
||||||
endrule
|
if (req.write)
|
||||||
endmodule
|
written_addr.wset(req.addr);
|
||||||
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
|
endrule
|
||||||
|
|
||||||
Vector#(num_clients, MemArbiterServer#(addr)) _ifcs = newVector();
|
rule grant_debugger (debugger_req.wget matches tagged Valid .req);
|
||||||
for (Integer i=0; i<valueOf(num_clients); i=i+1)
|
debugger_ok.send();
|
||||||
_ifcs[i] = (interface MemArbiterServer#(addr);
|
if (req.write)
|
||||||
method request = reqs[i].wset;
|
written_addr.wset(req.addr);
|
||||||
method grant = grants[i];
|
endrule
|
||||||
endinterface);
|
|
||||||
|
|
||||||
interface ports = _ifcs;
|
rule grant_palette (palette_req);
|
||||||
method conflict = conflict_in.wset;
|
palette_ok.send();
|
||||||
method MemArbiterOp#(addr) granted_op() if (granted_op_out.wget() matches tagged Valid .op);
|
endrule
|
||||||
return op;
|
|
||||||
|
//////
|
||||||
|
// Port B users
|
||||||
|
|
||||||
|
Vector#(3, RWire#(addr)) portB_req <- replicateM(mkRWire);
|
||||||
|
Wire#(Vector#(3, Bool)) portB_grant <- mkBypassWire();
|
||||||
|
|
||||||
|
Vector#(3, Bool) init = replicate(False); init[0] = True;
|
||||||
|
Reg#(Vector#(3, Bool)) priority_vec <- mkReg(init);
|
||||||
|
|
||||||
|
rule grant_portB;
|
||||||
|
Vector#(3, Bool) grants = replicate(False);
|
||||||
|
Bool port_available = False;
|
||||||
|
|
||||||
|
// This algorithm is a little mystifying at first glance, but it
|
||||||
|
// works. priority_vec has one bool per client, only one of
|
||||||
|
// which is True. That True bit identifies the client with the
|
||||||
|
// highest priority on the next request.
|
||||||
|
//
|
||||||
|
// This loop goes through each client twice, using
|
||||||
|
// port_available to track whether a client can grab the port or
|
||||||
|
// not. When we start iterating, the port is marked unavailable
|
||||||
|
// until we reach the top priority client, at which point we
|
||||||
|
// mark the port available and keep scanning. That effectively
|
||||||
|
// makes the search for a requesting client start at the top
|
||||||
|
// priority one.
|
||||||
|
//
|
||||||
|
// As we loop back around a second time, the availability bool
|
||||||
|
// gets reset again, but if you take the example of the True bit
|
||||||
|
// being in the middle of the vector, and consider cases where
|
||||||
|
// the first requestor is before/after that starting point,
|
||||||
|
// you'll see that it all works out, and at the end of the loop
|
||||||
|
// we have a new bit vector where only one client is True - the
|
||||||
|
// one whose request is granted.
|
||||||
|
for (Integer i = 0; i < 6; i=i+1) begin
|
||||||
|
Integer idx = i % 3;
|
||||||
|
if (priority_vec[idx])
|
||||||
|
port_available = True;
|
||||||
|
let req = portB_req[idx].wget();
|
||||||
|
if (port_available && isValid(req) && req != written_addr.wget()) begin
|
||||||
|
port_available = False;
|
||||||
|
grants[idx] = True;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
portB_grant <= grants;
|
||||||
|
// If we granted a request, the grantee becomes the lowest
|
||||||
|
// priority client for the next round of requests. If nobody
|
||||||
|
// requested anything, keep the same priority as before.
|
||||||
|
if (any(id, grants))
|
||||||
|
priority_vec <= rotateR(grants);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
//////
|
||||||
|
// External interface
|
||||||
|
|
||||||
|
interface MemArbiterServer cpu;
|
||||||
|
method request = cpu_req.wset;
|
||||||
|
method grant = cpu_ok;
|
||||||
|
endinterface
|
||||||
|
|
||||||
|
interface MemArbiterServer debugger;
|
||||||
|
method request = debugger_req.wset;
|
||||||
|
method grant = debugger_ok;
|
||||||
|
endinterface
|
||||||
|
|
||||||
|
interface MemArbiterServer palette;
|
||||||
|
method Action request(addr);
|
||||||
|
palette_req.send();
|
||||||
endmethod
|
endmethod
|
||||||
endmodule
|
method grant = palette_ok;
|
||||||
|
endinterface
|
||||||
|
|
||||||
typedef struct {
|
interface MemArbiterServer tile1;
|
||||||
Vector#(n, Bool) grant_vec;
|
method request = portB_req[0].wset;
|
||||||
Maybe#(MemArbiterOp#(addr)) granted_op;
|
method grant = portB_grant[0];
|
||||||
} GrantResult#(numeric type n, type addr) deriving (Bits, Eq, FShow);
|
endinterface
|
||||||
|
|
||||||
// select_grant computes which one entry of requests should be
|
interface MemArbiterServer tile2;
|
||||||
// granted. Priority order is descending starting from
|
method request = portB_req[1].wset;
|
||||||
// requests[hipri].
|
method grant = portB_grant[1];
|
||||||
function GrantResult#(n, addr) select_grant(Vector#(n, Maybe#(MemArbiterOp#(addr))) requests,
|
endinterface
|
||||||
UInt#(TLog#(n)) hipri,
|
|
||||||
Maybe#(MemArbiterOp#(addr)) conflict)
|
|
||||||
provisos (Eq#(addr));
|
|
||||||
|
|
||||||
function onehot(idx);
|
interface MemArbiterServer sprite;
|
||||||
let ret = replicate(False);
|
method request = portB_req[2].wset;
|
||||||
ret[idx] = True;
|
method grant = portB_grant[2];
|
||||||
return ret;
|
endinterface
|
||||||
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
|
endmodule
|
||||||
|
|
||||||
endpackage
|
endpackage
|
||||||
|
|
|
@ -14,307 +14,282 @@ typedef UInt#(4) Addr;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
String name;
|
String name;
|
||||||
|
Maybe#(MemArbiterWrite#(Addr)) cpu;
|
||||||
|
Maybe#(MemArbiterWrite#(Addr)) debugger;
|
||||||
|
Maybe#(Addr) palette;
|
||||||
|
Maybe#(Addr) tile1;
|
||||||
|
Maybe#(Addr) tile2;
|
||||||
|
Maybe#(Addr) sprite;
|
||||||
|
|
||||||
Vector#(n, Maybe#(MemArbiterOp#(Addr))) reqs;
|
Vector#(6, Bool) want;
|
||||||
Maybe#(MemArbiterOp#(Addr)) conflict;
|
} TestCase deriving (Bits, Eq);
|
||||||
|
|
||||||
Vector#(n, Bool) want_grants;
|
function Maybe#(MemArbiterWrite#(Addr)) rwRead(Addr addr);
|
||||||
Maybe#(MemArbiterOp#(Addr)) want_granted_op;
|
return tagged Valid MemArbiterWrite{write: False, addr: addr};
|
||||||
} TestCase#(numeric type n) deriving (Bits, Eq);
|
|
||||||
|
|
||||||
function Maybe#(MemArbiterOp#(Addr)) read(Addr addr);
|
|
||||||
return tagged Valid MemArbiterOp{write: False, addr: addr};
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function Maybe#(MemArbiterOp#(Addr)) write(Addr addr);
|
function Maybe#(MemArbiterWrite#(Addr)) rwWrite(Addr addr);
|
||||||
return tagged Valid MemArbiterOp{write: True, addr: addr};
|
return tagged Valid MemArbiterWrite{write: True, addr: addr};
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function Maybe#(MemArbiterOp#(Addr)) idle();
|
function Maybe#(Addr) read(Addr addr);
|
||||||
|
return tagged Valid addr;
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function Maybe#(t) idle();
|
||||||
return tagged Invalid;
|
return tagged Invalid;
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function Maybe#(MemArbiterOp#(Addr)) noConflict();
|
function Vector#(6, Bool) grant(Integer granted_a, Integer granted_b);
|
||||||
return tagged Invalid;
|
let ret = replicate(False);
|
||||||
|
if (granted_a >= 0)
|
||||||
|
ret[granted_a] = True;
|
||||||
|
if (granted_b >= 0)
|
||||||
|
ret[granted_b+3] = True;
|
||||||
|
return ret;
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function Vector#(n, Bool) grant(Integer granted);
|
function TestCase testCase(String name,
|
||||||
function gen(idx);
|
Maybe#(MemArbiterWrite#(Addr)) cpu,
|
||||||
return idx == granted;
|
Maybe#(MemArbiterWrite#(Addr)) debugger,
|
||||||
endfunction
|
Maybe#(Addr) palette,
|
||||||
|
Maybe#(Addr) tile1,
|
||||||
return genWith(gen);
|
Maybe#(Addr) tile2,
|
||||||
endfunction
|
Maybe#(Addr) sprite,
|
||||||
|
Integer portA,
|
||||||
function Vector#(n, Bool) noGrant();
|
Integer portB);
|
||||||
return replicate(False);
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
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_granted_op);
|
|
||||||
return TestCase{
|
return TestCase{
|
||||||
name: name,
|
name: name,
|
||||||
reqs: reqs,
|
cpu: cpu,
|
||||||
conflict: conflict,
|
debugger: debugger,
|
||||||
want_grants: want_grants,
|
palette: palette,
|
||||||
want_granted_op: want_granted_op
|
tile1: tile1,
|
||||||
|
tile2: tile2,
|
||||||
|
sprite: sprite,
|
||||||
|
want: grant(portA, portB)
|
||||||
};
|
};
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
interface TB;
|
module mkTB();
|
||||||
method Action start();
|
Vector#(29, TestCase) tests = vec(
|
||||||
(* always_ready *)
|
testCase("All idle",
|
||||||
method Bool done();
|
idle, idle, idle,
|
||||||
endinterface
|
idle, idle, idle,
|
||||||
|
-1, -1),
|
||||||
|
|
||||||
module mkArbiterTB(MemArbiter#(n, Addr) dut, Vector#(m, TestCase#(n)) tests, TB ifc);
|
// Single client accesses at a time
|
||||||
let cycles <- mkCycleCounter();
|
testCase("CPU read", rwRead(1), idle, idle,
|
||||||
|
idle, idle, idle,
|
||||||
|
0, -1),
|
||||||
|
testCase("CPU write", rwWrite(1), idle, idle,
|
||||||
|
idle, idle, idle,
|
||||||
|
0, -1),
|
||||||
|
testCase("Debugger read",
|
||||||
|
idle, rwRead(1), idle,
|
||||||
|
idle, idle, idle,
|
||||||
|
1, -1),
|
||||||
|
testCase("Debugger write",
|
||||||
|
idle, rwWrite(1), idle,
|
||||||
|
idle, idle, idle,
|
||||||
|
1, -1),
|
||||||
|
testCase("Palette read",
|
||||||
|
idle, idle, read(1),
|
||||||
|
idle, idle, idle,
|
||||||
|
2, -1),
|
||||||
|
testCase("Tile1 read",
|
||||||
|
idle, idle, idle,
|
||||||
|
read(1), idle, idle,
|
||||||
|
-1, 0),
|
||||||
|
testCase("Tile2 read",
|
||||||
|
idle, idle, idle,
|
||||||
|
idle, read(1), idle,
|
||||||
|
-1, 1),
|
||||||
|
testCase("Sprite read",
|
||||||
|
idle, idle, idle,
|
||||||
|
idle, idle, read(1),
|
||||||
|
-1, 2),
|
||||||
|
|
||||||
Reg#(Bit#(TLog#(m))) idx <- mkReg(0);
|
// Strict priority on port A
|
||||||
Reg#(Bool) running <- mkReg(False);
|
testCase("CPU + Debugger + Palette",
|
||||||
|
rwRead(1), rwRead(2), read(3),
|
||||||
|
idle, idle, idle,
|
||||||
|
0, -1),
|
||||||
|
testCase("CPU + Palette",
|
||||||
|
rwRead(1), idle, read(3),
|
||||||
|
idle, idle, idle,
|
||||||
|
0, -1),
|
||||||
|
testCase("Debugger + Palette",
|
||||||
|
idle, rwRead(2), read(3),
|
||||||
|
idle, idle, idle,
|
||||||
|
1, -1),
|
||||||
|
|
||||||
for (Integer i=0; i<valueOf(n); i=i+1) begin
|
// Round-robin on port B
|
||||||
rule request (running && isValid(tests[idx].reqs[i]));
|
testCase("Sprite read", // to reset round robin
|
||||||
dut.ports[i].request(validValue(tests[idx].reqs[i]));
|
idle, idle, idle,
|
||||||
|
idle, idle, read(1),
|
||||||
|
-1, 2),
|
||||||
|
testCase("Tile1 + Tile2 + Sprite",
|
||||||
|
idle, idle, idle,
|
||||||
|
read(1), read(2), read(3),
|
||||||
|
-1, 0),
|
||||||
|
testCase("Tile1 + Tile2 + Sprite",
|
||||||
|
idle, idle, idle,
|
||||||
|
read(1), read(2), read(3),
|
||||||
|
-1, 1),
|
||||||
|
testCase("Tile1 + Tile2 + Sprite",
|
||||||
|
idle, idle, idle,
|
||||||
|
read(1), read(2), read(3),
|
||||||
|
-1, 2),
|
||||||
|
testCase("Tile1 + Tile2 + Sprite",
|
||||||
|
idle, idle, idle,
|
||||||
|
read(1), read(2), read(3),
|
||||||
|
-1, 0),
|
||||||
|
testCase("Tile1 + Sprite",
|
||||||
|
idle, idle, idle,
|
||||||
|
read(1), idle, read(3),
|
||||||
|
-1, 2),
|
||||||
|
testCase("Tile2 + Sprite",
|
||||||
|
idle, idle, idle,
|
||||||
|
idle, read(2), read(3),
|
||||||
|
-1, 1),
|
||||||
|
testCase("Tile2 + Sprite",
|
||||||
|
idle, idle, idle,
|
||||||
|
idle, read(2), read(3),
|
||||||
|
-1, 2),
|
||||||
|
|
||||||
|
// Inter-port conflicts
|
||||||
|
testCase("Read/read, no conflict",
|
||||||
|
rwRead(0), idle, idle,
|
||||||
|
read(0), idle, idle,
|
||||||
|
0, 0),
|
||||||
|
testCase("Write/read, no conflict",
|
||||||
|
rwWrite(1), idle, idle,
|
||||||
|
read(0), idle, idle,
|
||||||
|
0, 0),
|
||||||
|
testCase("Tile1 write conflict",
|
||||||
|
rwWrite(0), idle, idle,
|
||||||
|
read(0), idle, idle,
|
||||||
|
0, -1),
|
||||||
|
testCase("Tile2 write conflict",
|
||||||
|
rwWrite(0), idle, idle,
|
||||||
|
idle, read(0), idle,
|
||||||
|
0, -1),
|
||||||
|
testCase("Sprite write conflict",
|
||||||
|
rwWrite(0), idle, idle,
|
||||||
|
idle, idle, read(0),
|
||||||
|
0, -1),
|
||||||
|
testCase("Tile1 write conflict with debugger",
|
||||||
|
idle, rwWrite(0), idle,
|
||||||
|
read(0), idle, idle,
|
||||||
|
1, -1),
|
||||||
|
testCase("Sprite read", // to reset round robin
|
||||||
|
idle, idle, idle,
|
||||||
|
idle, idle, read(1),
|
||||||
|
-1, 2),
|
||||||
|
testCase("CPU write conflict, other port feasible",
|
||||||
|
rwWrite(0), idle, idle,
|
||||||
|
read(0), read(1), idle,
|
||||||
|
0, 1),
|
||||||
|
testCase("CPU write conflict, conflict resolved",
|
||||||
|
idle, idle, idle,
|
||||||
|
read(0), idle, idle,
|
||||||
|
-1, 0));
|
||||||
|
|
||||||
|
MemArbiter#(Addr) dut <- mkMemArbiter();
|
||||||
|
|
||||||
|
Reg#(UInt#(32)) idx <- mkReg(0);
|
||||||
|
|
||||||
|
rule display_test (idx == 0);
|
||||||
|
$display("RUN TestMemArbiter");
|
||||||
endrule
|
endrule
|
||||||
end
|
|
||||||
|
|
||||||
(* no_implicit_conditions, fire_when_enabled *)
|
(* no_implicit_conditions, fire_when_enabled *)
|
||||||
rule forbid (running && isValid(tests[idx].conflict));
|
rule input_cpu (tests[idx].cpu matches tagged Valid .req);
|
||||||
dut.conflict(validValue(tests[idx].conflict));
|
dut.cpu.request(req);
|
||||||
endrule
|
endrule
|
||||||
|
|
||||||
Wire#(Maybe#(MemArbiterOp#(Addr))) got_granted_op <- mkDWire(tagged Invalid);
|
(* no_implicit_conditions, fire_when_enabled *)
|
||||||
|
rule input_debugger (tests[idx].debugger matches tagged Valid .req);
|
||||||
(* fire_when_enabled *)
|
dut.debugger.request(req);
|
||||||
rule collect_granted_op (running);
|
|
||||||
got_granted_op <= tagged Valid dut.granted_op();
|
|
||||||
endrule
|
endrule
|
||||||
|
|
||||||
function Fmt req_s(Maybe#(MemArbiterOp#(Addr)) v);
|
(* no_implicit_conditions, fire_when_enabled *)
|
||||||
|
rule input_palette (tests[idx].palette matches tagged Valid .addr);
|
||||||
|
dut.palette.request(addr);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
(* no_implicit_conditions, fire_when_enabled *)
|
||||||
|
rule input_tile1 (tests[idx].tile1 matches tagged Valid .addr);
|
||||||
|
dut.tile1.request(addr);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
(* no_implicit_conditions, fire_when_enabled *)
|
||||||
|
rule input_tile2 (tests[idx].tile2 matches tagged Valid .addr);
|
||||||
|
dut.tile2.request(addr);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
(* no_implicit_conditions, fire_when_enabled *)
|
||||||
|
rule input_sprite (tests[idx].sprite matches tagged Valid .addr);
|
||||||
|
dut.sprite.request(addr);
|
||||||
|
endrule
|
||||||
|
|
||||||
|
function Fmt rw_str(Maybe#(MemArbiterWrite#(Addr)) v);
|
||||||
case (v) matches
|
case (v) matches
|
||||||
|
tagged Valid .req: begin
|
||||||
|
if (req.write)
|
||||||
|
return $format("Write(%0d)", req.addr);
|
||||||
|
else
|
||||||
|
return $format("Read(%0d) ", req.addr);
|
||||||
|
end
|
||||||
|
tagged Invalid: return $format("Idle ");
|
||||||
|
endcase
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function Fmt ro_str(Maybe#(Addr) v);
|
||||||
|
case (v) matches
|
||||||
|
tagged Valid .addr: return $format("Read(%0d) ", addr);
|
||||||
tagged Invalid: return $format("Idle ");
|
tagged Invalid: return $format("Idle ");
|
||||||
tagged Valid .req &&& req.write: return $format("Write(%0d)", req.addr);
|
|
||||||
tagged Valid .req: return $format("Read(%0d)", req.addr);
|
|
||||||
endcase
|
endcase
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
(* no_implicit_conditions, fire_when_enabled *)
|
(* no_implicit_conditions, fire_when_enabled *)
|
||||||
rule check (running);
|
rule check_grants;
|
||||||
|
Vector#(6, Bool) gotVec = newVector;
|
||||||
|
gotVec[0] = dut.cpu.grant();
|
||||||
|
gotVec[1] = dut.debugger.grant();
|
||||||
|
gotVec[2] = dut.palette.grant();
|
||||||
|
gotVec[3] = dut.tile1.grant();
|
||||||
|
gotVec[4] = dut.tile2.grant();
|
||||||
|
gotVec[5] = dut.sprite.grant();
|
||||||
|
|
||||||
let test = tests[idx];
|
let test = tests[idx];
|
||||||
let reqs = test.reqs;
|
let got = pack(reverse(gotVec));
|
||||||
let want_grants = test.want_grants;
|
let want = pack(reverse(test.want));
|
||||||
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);
|
$display("RUN %s (%0d)", test.name, idx+1);
|
||||||
if (got_grants != want_grants || got_granted_op != want_granted_op) begin
|
if (got != want) begin
|
||||||
$display("input:");
|
$display(" input: ",
|
||||||
for (Integer i=0; i<valueOf(n); i=i+1)
|
"0:", rw_str(test.cpu), " 1:", rw_str(test.debugger), " 2:", ro_str(test.palette),
|
||||||
$display(" ", $format("%0d", i), ": ", req_s(reqs[i]));
|
" 3:", ro_str(test.tile1), " 4:", ro_str(test.tile2), " 5:", ro_str(test.sprite));
|
||||||
$display(" conflict: ", fshow(test.conflict));
|
$display(" got : %03b %03b", got[5:3], got[2:0]);
|
||||||
|
$display(" want : %03b %03b", want[5:3], want[2:0]);
|
||||||
$display(" output:");
|
dynamicAssert(got == want, "wrong arbiter output");
|
||||||
$display(" grants: ", fshow(got_grants));
|
|
||||||
$display(" granted: ", fshow(got_granted_op));
|
|
||||||
|
|
||||||
$display(" want grants: ", fshow(tests[idx].want_grants));
|
|
||||||
$display(" want granted: ", fshow(want_granted_op));
|
|
||||||
dynamicAssert(False, "wrong arbiter output");
|
|
||||||
end
|
end
|
||||||
|
|
||||||
dynamicAssert(cycles == 1, "arbiter took more than 0 cycles");
|
|
||||||
|
|
||||||
$display("OK %s", tests[idx].name);
|
|
||||||
|
|
||||||
cycles.reset();
|
|
||||||
if (idx == fromInteger(valueOf(m)-1))
|
|
||||||
running <= False;
|
|
||||||
else
|
else
|
||||||
idx <= idx+1;
|
$display("OK %s", test.name);
|
||||||
endrule
|
endrule
|
||||||
|
|
||||||
method Action start() if (!running && idx == 0);
|
(* no_implicit_conditions, fire_when_enabled *)
|
||||||
cycles.reset();
|
rule advance_test;
|
||||||
running <= True;
|
let next = idx+1;
|
||||||
endmethod
|
let max = fromInteger(arrayLength(vectorToArray(tests)));
|
||||||
|
if (next == max) begin
|
||||||
method Bool done();
|
$display("OK TestMemArbiter");
|
||||||
return !running && idx != 0;
|
$finish;
|
||||||
endmethod
|
end
|
||||||
endmodule
|
else
|
||||||
|
idx <= next;
|
||||||
module mkTB(Empty);
|
endrule
|
||||||
///////////////////////////////
|
|
||||||
// Strict arbiter
|
|
||||||
|
|
||||||
let strictTests = vec(
|
|
||||||
// Simple grants
|
|
||||||
testCase("All idle",
|
|
||||||
vec(idle, idle, idle), noConflict,
|
|
||||||
noGrant, noConflict),
|
|
||||||
testCase("Port 0 read",
|
|
||||||
vec(read(1), idle, idle), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 0 write",
|
|
||||||
vec(write(1), idle, idle), noConflict,
|
|
||||||
grant(0), write(1)),
|
|
||||||
testCase("Port 1 read",
|
|
||||||
vec(idle, read(1), idle), noConflict,
|
|
||||||
grant(1), read(1)),
|
|
||||||
testCase("Port 1 write",
|
|
||||||
vec(idle, write(1), idle), noConflict,
|
|
||||||
grant(1), write(1)),
|
|
||||||
testCase("Port 2 read",
|
|
||||||
vec(idle, idle, read(1)), noConflict,
|
|
||||||
grant(2), read(1)),
|
|
||||||
testCase("Port 2 write",
|
|
||||||
vec(idle, idle, write(1)), noConflict,
|
|
||||||
grant(2), write(1)),
|
|
||||||
|
|
||||||
// Priorities
|
|
||||||
testCase("Port 0+1",
|
|
||||||
vec(read(1), read(2), idle), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 0+2",
|
|
||||||
vec(read(1), idle, read(2)), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 1+2",
|
|
||||||
vec(idle, read(1), read(2)), noConflict,
|
|
||||||
grant(1), read(1)),
|
|
||||||
testCase("Port 0+1+2",
|
|
||||||
vec(read(1), read(2), read(3)), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 0+1+2, overruled writes",
|
|
||||||
vec(read(1), write(1), write(2)), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
|
|
||||||
// Forbidden addrs
|
|
||||||
testCase("Port 0 read-write denied",
|
|
||||||
vec(read(1), read(2), idle), write(1),
|
|
||||||
grant(1), read(2)),
|
|
||||||
testCase("Port 0 write-write denied",
|
|
||||||
vec(write(1), read(2), idle), write(1),
|
|
||||||
grant(1), read(2)),
|
|
||||||
testCase("Port 0 write-read denied",
|
|
||||||
vec(write(1), read(2), idle), read(1),
|
|
||||||
grant(1), read(2)),
|
|
||||||
testCase("Port 0 no addr match",
|
|
||||||
vec(write(2), idle, idle), write(1),
|
|
||||||
grant(0), write(2)),
|
|
||||||
testCase("Port 0 denied, no alternatives",
|
|
||||||
vec(write(1), idle, idle), write(1),
|
|
||||||
noGrant, noConflict)
|
|
||||||
);
|
|
||||||
MemArbiter#(3, Addr) strict <- mkPriorityMemArbiter();
|
|
||||||
let strictTB <- mkArbiterTB(strict, strictTests);
|
|
||||||
|
|
||||||
///////////////////////////////
|
|
||||||
// Round-robin arbiter
|
|
||||||
let rrTests = vec(
|
|
||||||
// Simple grants
|
|
||||||
testCase("All idle",
|
|
||||||
vec(idle, idle, idle, idle), noConflict,
|
|
||||||
noGrant, noConflict),
|
|
||||||
testCase("Port 0 read",
|
|
||||||
vec(read(1), idle, idle, idle), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 0 write",
|
|
||||||
vec(write(1), idle, idle, idle), noConflict,
|
|
||||||
grant(0), write(1)),
|
|
||||||
testCase("Port 1 read",
|
|
||||||
vec(idle, read(1), idle, idle), noConflict,
|
|
||||||
grant(1), read(1)),
|
|
||||||
testCase("Port 1 write",
|
|
||||||
vec(idle, write(1), idle, idle), noConflict,
|
|
||||||
grant(1), write(1)),
|
|
||||||
testCase("Port 2 read",
|
|
||||||
vec(idle, idle, read(1), idle), noConflict,
|
|
||||||
grant(2), read(1)),
|
|
||||||
testCase("Port 2 write",
|
|
||||||
vec(idle, idle, write(1), idle), noConflict,
|
|
||||||
grant(2), write(1)),
|
|
||||||
testCase("Port 3 read",
|
|
||||||
vec(idle, idle, idle, read(1)), noConflict,
|
|
||||||
grant(3), read(1)),
|
|
||||||
testCase("Port 3 write",
|
|
||||||
vec(idle, idle, idle, write(1)), noConflict,
|
|
||||||
grant(3), write(1)),
|
|
||||||
|
|
||||||
// Priorities
|
|
||||||
testCase("Port 3 to reset RR",
|
|
||||||
vec(idle, idle, idle, read(1)), noConflict,
|
|
||||||
grant(3), read(1)),
|
|
||||||
testCase("Port 0+1 #1",
|
|
||||||
vec(read(1), read(2), idle, idle), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 0+1 #2",
|
|
||||||
vec(read(1), read(2), idle, idle), noConflict,
|
|
||||||
grant(1), read(2)),
|
|
||||||
testCase("Port 0+1 #3",
|
|
||||||
vec(read(1), read(2), idle, idle), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 0+2 #1",
|
|
||||||
vec(read(1), idle, read(2), idle), noConflict,
|
|
||||||
grant(2), read(2)),
|
|
||||||
testCase("Port 0+2 #2",
|
|
||||||
vec(read(1), idle, read(2), idle), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 0+1+2+3 #1",
|
|
||||||
vec(read(1), read(2), read(3), read(4)), noConflict,
|
|
||||||
grant(1), read(2)),
|
|
||||||
testCase("Port 0+1+2+3 #2",
|
|
||||||
vec(read(1), read(2), read(3), read(4)), noConflict,
|
|
||||||
grant(2), read(3)),
|
|
||||||
testCase("Port 0+1+2+3 #3",
|
|
||||||
vec(read(1), read(2), read(3), read(4)), noConflict,
|
|
||||||
grant(3), read(4)),
|
|
||||||
testCase("Port 0+1+2+3 #4",
|
|
||||||
vec(read(1), read(2), read(3), read(4)), noConflict,
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("Port 0+1+2+3 #5",
|
|
||||||
vec(read(1), read(2), read(3), read(4)), noConflict,
|
|
||||||
grant(1), read(2)),
|
|
||||||
|
|
||||||
// Forbidden addrs
|
|
||||||
testCase("Port 3 to reset RR",
|
|
||||||
vec(idle, idle, idle, read(1)), noConflict,
|
|
||||||
grant(3), read(1)),
|
|
||||||
testCase("RR with denied writes #1",
|
|
||||||
vec(read(1), write(2), read(3), read(4)), write(3),
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("RR with denied writes #2",
|
|
||||||
vec(read(1), write(2), read(3), read(4)), write(3),
|
|
||||||
grant(1), write(2)),
|
|
||||||
testCase("RR with denied writes #3",
|
|
||||||
vec(read(1), write(2), read(3), read(4)), write(3),
|
|
||||||
grant(3), read(4)),
|
|
||||||
testCase("RR with denied writes #4",
|
|
||||||
vec(read(1), write(2), read(3), read(4)), write(3),
|
|
||||||
grant(0), read(1)),
|
|
||||||
testCase("RR with denied writes #5",
|
|
||||||
vec(read(1), write(2), read(3), read(4)), write(3),
|
|
||||||
grant(1), write(2))
|
|
||||||
);
|
|
||||||
MemArbiter#(4, Addr) rr <- mkRoundRobinMemArbiter();
|
|
||||||
let rrTB <- mkArbiterTB(rr, rrTests);
|
|
||||||
|
|
||||||
runTest(100,
|
|
||||||
mkTest("MemArbiter", seq
|
|
||||||
mkTest("MemArbiter/Strict", seq
|
|
||||||
strictTB.start();
|
|
||||||
await(strictTB.done);
|
|
||||||
endseq);
|
|
||||||
mkTest("MemArbiter/RoundRobin", seq
|
|
||||||
rrTB.start();
|
|
||||||
await(rrTB.done);
|
|
||||||
endseq);
|
|
||||||
endseq));
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
endpackage
|
endpackage
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package VRAMCore;
|
package VRAM;
|
||||||
|
|
||||||
import Connectable::*;
|
|
||||||
import GetPut::*;
|
import GetPut::*;
|
||||||
import ClientServer::*;
|
import ClientServer::*;
|
||||||
import DReg::*;
|
import DReg::*;
|
||||||
|
@ -16,12 +15,11 @@ import ECP5_RAM::*;
|
||||||
|
|
||||||
export VRAMAddr;
|
export VRAMAddr;
|
||||||
export VRAMData;
|
export VRAMData;
|
||||||
|
export mkVRAM;
|
||||||
export VRAMRequest;
|
export VRAMRequest;
|
||||||
export VRAMResponse;
|
export VRAMResponse;
|
||||||
export VRAMClient;
|
|
||||||
export VRAMServer;
|
export VRAMServer;
|
||||||
export VRAMCore;
|
export VRAM;
|
||||||
export mkVRAMCore;
|
|
||||||
|
|
||||||
typedef Bit#(8) VRAMData;
|
typedef Bit#(8) VRAMData;
|
||||||
|
|
||||||
|
@ -135,30 +133,30 @@ typedef struct {
|
||||||
typedef Server#(VRAMRequest, VRAMResponse) VRAMServer;
|
typedef Server#(VRAMRequest, VRAMResponse) VRAMServer;
|
||||||
typedef Client#(VRAMRequest, VRAMResponse) VRAMClient;
|
typedef Client#(VRAMRequest, VRAMResponse) VRAMClient;
|
||||||
|
|
||||||
interface VRAMCore;
|
interface VRAM;
|
||||||
interface VRAMServer portA;
|
interface VRAMServer portA;
|
||||||
interface VRAMServer portB;
|
interface VRAMServer portB;
|
||||||
endinterface
|
endinterface
|
||||||
|
|
||||||
// mkVRAMCore creates a dual port VRAM of the specified size, using
|
// mkVRAM creates a dual port VRAM of the specified size, using ECP5
|
||||||
// ECP5 EBR memory primitives. The memory size must be a multiple of
|
// EBR memory primitives. The memory size must be a multiple of 4KiB,
|
||||||
// 4KiB, with a maximum of 128KiB.
|
// with a maximum of 128KiB.
|
||||||
//
|
//
|
||||||
// The returned VRAMCore servers implement flow control. As long as
|
// The returned VRAM servers implement flow control. As long as
|
||||||
// responses are processed as soon as they're available, each port can
|
// responses are processed as soon as they're available, each port can
|
||||||
// process one memory operation per cycle.
|
// process one memory operation per cycle.
|
||||||
//
|
//
|
||||||
// The VRAMCore does not prevent write-write or write-read conflicts
|
// The VRAM does not prevent write-write or write-read conflicts
|
||||||
// between the ports. The outcome of a simultaneous write to the same
|
// between the ports. The outcome of a simultaneous write to the same
|
||||||
// address is unspecified, as is the read output in a simultaneous
|
// address is unspecified, as is the read output in a simultaneous
|
||||||
// read and write of the same address. The caller must use external
|
// read and write of the same address. The caller must use external
|
||||||
// arbitration to avoid such accesses.
|
// arbitration to avoid such accesses.
|
||||||
module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
|
module mkVRAM(Integer num_kilobytes, VRAM ifc);
|
||||||
if (num_kilobytes > 128)
|
if (num_kilobytes > 128)
|
||||||
error("maximum VRAMCore size is 128KiB");
|
error("maximum VRAM size is 128KiB");
|
||||||
let num_bytes = num_kilobytes*1024;
|
let num_bytes = num_kilobytes*1024;
|
||||||
if (num_bytes % 4096 != 0)
|
if (num_bytes % 4096 != 0)
|
||||||
error("VRAMCore must be a multiple of 4096b");
|
error("VRAM must be a multiple of 4096b");
|
||||||
let num_byterams = num_bytes/4096;
|
let num_byterams = num_bytes/4096;
|
||||||
let num_arrays = ceil(fromInteger(num_byterams) / 8);
|
let num_arrays = ceil(fromInteger(num_byterams) / 8);
|
||||||
|
|
Loading…
Reference in New Issue