2024-09-08 18:26:59 +02:00
|
|
|
package VRAMCore;
|
2024-09-06 19:04:50 +02:00
|
|
|
|
|
|
|
import GetPut::*;
|
|
|
|
import ClientServer::*;
|
2024-09-08 18:26:59 +02:00
|
|
|
import BRAMCore::*;
|
2024-09-08 01:00:45 +02:00
|
|
|
import Real::*;
|
2024-09-19 06:31:34 +02:00
|
|
|
import FIFOF::*;
|
|
|
|
import SpecialFIFOs::*;
|
2024-09-06 19:04:50 +02:00
|
|
|
|
|
|
|
import DelayLine::*;
|
|
|
|
import ECP5_RAM::*;
|
|
|
|
|
2024-09-08 01:00:45 +02:00
|
|
|
export VRAMAddr;
|
|
|
|
export VRAMData;
|
2024-09-08 18:26:59 +02:00
|
|
|
export VRAMRequest(..);
|
|
|
|
export VRAMResponse(..);
|
|
|
|
export VRAMCore(..);
|
2024-09-08 18:26:59 +02:00
|
|
|
export mkVRAMCore;
|
2024-09-06 19:04:50 +02:00
|
|
|
|
|
|
|
typedef Bit#(8) VRAMData;
|
|
|
|
|
2024-09-08 18:26:59 +02:00
|
|
|
typedef UInt#(17) VRAMAddr;
|
|
|
|
typedef UInt#(2) ArrayAddr;
|
2024-09-08 01:00:45 +02:00
|
|
|
typedef UInt#(3) ChipAddr;
|
2024-09-08 18:26:59 +02:00
|
|
|
typedef UInt#(12) ByteAddr;
|
2024-09-19 06:31:34 +02:00
|
|
|
typedef struct {
|
|
|
|
ChipAddr chip;
|
|
|
|
ByteAddr addr;
|
|
|
|
} EBRAddr deriving (Bits, Eq, FShow);
|
2024-09-06 19:04:50 +02:00
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
typedef struct {
|
|
|
|
EBRAddr addr;
|
|
|
|
Maybe#(data) data;
|
|
|
|
} VRAMInternalRequest#(type data) deriving (Bits, Eq, FShow);
|
2024-09-06 19:04:50 +02:00
|
|
|
|
2024-09-08 18:26:59 +02:00
|
|
|
typedef struct {
|
|
|
|
VRAMAddr addr;
|
|
|
|
Maybe#(VRAMData) data;
|
2024-09-15 01:40:14 +02:00
|
|
|
} VRAMRequest deriving (Bits, Eq, FShow);
|
2024-09-08 18:26:59 +02:00
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
VRAMData data;
|
2024-09-15 01:40:14 +02:00
|
|
|
} VRAMResponse deriving (Bits, Eq, FShow);
|
2024-09-08 18:26:59 +02:00
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
interface VRAMCoreInternal#(type data);
|
|
|
|
interface Server#(VRAMInternalRequest#(data), data) portA;
|
|
|
|
interface Server#(VRAMInternalRequest#(data), data) portB;
|
|
|
|
endinterface
|
|
|
|
|
2024-09-08 18:26:59 +02:00
|
|
|
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
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
module mkNibbleRAM(ChipAddr chip_addr, VRAMCoreInternal#(Bit#(4)) ifc);
|
2024-09-08 18:26:59 +02:00
|
|
|
let _ret;
|
|
|
|
if (genC())
|
|
|
|
_ret <- mkNibbleRAM_Sim(chip_addr);
|
|
|
|
else
|
|
|
|
_ret <- mkNibbleRAM_ECP5(chip_addr);
|
2024-09-19 06:31:34 +02:00
|
|
|
|
|
|
|
interface Server portA;
|
|
|
|
interface Put request;
|
|
|
|
method Action put(req);
|
|
|
|
_ret.portA.put(req.addr.chip, isValid(req.data), req.addr.addr, fromMaybe(0, req.data));
|
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
interface Get response;
|
|
|
|
method ActionValue#(Bit#(4)) get();
|
|
|
|
return _ret.portA.read();
|
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
endinterface
|
|
|
|
|
|
|
|
interface Server portB;
|
|
|
|
interface Put request;
|
|
|
|
method Action put(req);
|
|
|
|
_ret.portB.put(req.addr.chip, isValid(req.data), req.addr.addr, fromMaybe(0, req.data));
|
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
interface Get response;
|
|
|
|
method ActionValue#(Bit#(4)) get();
|
|
|
|
return _ret.portB.read();
|
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
endinterface
|
2024-09-08 18:26:59 +02:00
|
|
|
endmodule
|
|
|
|
|
2024-09-06 19:04:50 +02:00
|
|
|
// 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.
|
2024-09-19 06:31:34 +02:00
|
|
|
module mkByteRAM(ChipAddr chip_addr, VRAMCoreInternal#(VRAMData) ifc);
|
|
|
|
VRAMCoreInternal#(Bit#(4)) upper <- mkNibbleRAM(chip_addr);
|
|
|
|
VRAMCoreInternal#(Bit#(4)) lower <- mkNibbleRAM(chip_addr);
|
2024-09-06 19:04:50 +02:00
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
interface Server portA;
|
|
|
|
interface Put request;
|
|
|
|
method Action put(req);
|
|
|
|
Maybe#(Bit#(4)) ud = tagged Invalid;
|
|
|
|
Maybe#(Bit#(4)) ld = tagged Invalid;
|
|
|
|
if (req.data matches tagged Valid .data) begin
|
|
|
|
ud = tagged Valid data[7:4];
|
|
|
|
ld = tagged Valid data[3:0];
|
|
|
|
end
|
|
|
|
upper.portA.request.put(VRAMInternalRequest{
|
|
|
|
addr: req.addr,
|
|
|
|
data: ud
|
|
|
|
});
|
|
|
|
lower.portA.request.put(VRAMInternalRequest{
|
|
|
|
addr: req.addr,
|
|
|
|
data: ld
|
|
|
|
});
|
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
interface Get response;
|
|
|
|
method ActionValue#(VRAMData) get();
|
|
|
|
let u <- upper.portA.response.get();
|
|
|
|
let l <- lower.portA.response.get();
|
|
|
|
return {u, l};
|
|
|
|
endmethod
|
|
|
|
endinterface
|
2024-09-06 19:04:50 +02:00
|
|
|
endinterface
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
interface Server portB;
|
|
|
|
interface Put request;
|
|
|
|
method Action put(req);
|
|
|
|
Maybe#(Bit#(4)) ud = tagged Invalid;
|
|
|
|
Maybe#(Bit#(4)) ld = tagged Invalid;
|
|
|
|
if (req.data matches tagged Valid .data) begin
|
|
|
|
ud = tagged Valid data[7:4];
|
|
|
|
ld = tagged Valid data[3:0];
|
|
|
|
end
|
|
|
|
upper.portB.request.put(VRAMInternalRequest{
|
|
|
|
addr: req.addr,
|
|
|
|
data: ud
|
|
|
|
});
|
|
|
|
lower.portB.request.put(VRAMInternalRequest{
|
|
|
|
addr: req.addr,
|
|
|
|
data: ld
|
|
|
|
});
|
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
interface Get response;
|
|
|
|
method ActionValue#(VRAMData) get();
|
|
|
|
let u <- upper.portB.response.get();
|
|
|
|
let l <- lower.portB.response.get();
|
|
|
|
return {u, l};
|
|
|
|
endmethod
|
|
|
|
endinterface
|
2024-09-06 19:04:50 +02:00
|
|
|
endinterface
|
|
|
|
endmodule : mkByteRAM
|
|
|
|
|
2024-09-08 01:00:45 +02:00
|
|
|
// mkByteRAMArray arrays up to 8 mkByteRAMs together, using the
|
|
|
|
// hardwired chip select lines to route inputs appropriately and a mux
|
|
|
|
// tree to collect outputs. With num_chips=8, the resulting ByteRAM is
|
|
|
|
// 32768x8b.
|
2024-09-19 06:31:34 +02:00
|
|
|
//
|
|
|
|
// The returned ByteRAM _does_ provide flow control: both read() and
|
|
|
|
// put() are guarded. If reads are consumed as soon as they're
|
|
|
|
// available, the RAM can process a put() every cycle.
|
|
|
|
module mkByteRAMArray(Integer num_chips, VRAMCoreInternal#(VRAMData) ifc);
|
2024-09-06 19:04:50 +02:00
|
|
|
if (num_chips > 8)
|
|
|
|
error("mkByteRAMArray can only array 8 raw ByteRAMs");
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
VRAMCoreInternal#(VRAMData) blocks[num_chips];
|
2024-09-06 19:04:50 +02:00
|
|
|
for (Integer i=0; i<num_chips; i=i+1)
|
|
|
|
blocks[i] <- mkByteRAM(fromInteger(i));
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
FIFOF#(ChipAddr) read_chip_A <- mkPipelineFIFOF();
|
|
|
|
FIFOF#(ChipAddr) read_chip_B <- mkPipelineFIFOF();
|
2024-09-06 19:04:50 +02:00
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
interface Server portA;
|
|
|
|
interface Put request;
|
|
|
|
method Action put(req) if (read_chip_A.notFull);
|
|
|
|
for (Integer i=0; i<num_chips; i=i+1)
|
|
|
|
blocks[i].portA.request.put(req);
|
|
|
|
if (!isValid(req.data))
|
|
|
|
read_chip_A.enq(req.addr.chip);
|
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
interface Get response;
|
|
|
|
method ActionValue#(VRAMData) get();
|
|
|
|
read_chip_A.deq();
|
|
|
|
let res <- blocks[read_chip_A.first].portA.response.get();
|
|
|
|
return res;
|
|
|
|
endmethod
|
|
|
|
endinterface
|
2024-09-06 19:04:50 +02:00
|
|
|
endinterface
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
interface Server portB;
|
|
|
|
interface Put request;
|
|
|
|
method Action put(req) if (read_chip_B.notFull);
|
|
|
|
for (Integer i=0; i<num_chips; i=i+1)
|
|
|
|
blocks[i].portB.request.put(req);
|
|
|
|
if (!isValid(req.data))
|
|
|
|
read_chip_B.enq(req.addr.chip);
|
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
interface Get response;
|
|
|
|
method ActionValue#(VRAMData) get();
|
|
|
|
read_chip_B.deq();
|
|
|
|
let res <- blocks[read_chip_B.first].portB.response.get();
|
|
|
|
return res;
|
|
|
|
endmethod
|
|
|
|
endinterface
|
2024-09-06 19:04:50 +02:00
|
|
|
endinterface
|
|
|
|
endmodule
|
|
|
|
|
2024-09-08 18:26:59 +02:00
|
|
|
interface VRAMCore;
|
2024-09-08 18:26:59 +02:00
|
|
|
interface Server#(VRAMRequest, VRAMResponse) portA;
|
|
|
|
interface Server#(VRAMRequest, VRAMResponse) portB;
|
2024-09-06 19:04:50 +02:00
|
|
|
endinterface
|
|
|
|
|
2024-09-08 18:26:59 +02:00
|
|
|
// mkVRAMCore creates a dual port VRAM of the specified size, using
|
|
|
|
// ECP5 EBR memory primitives. The memory size must be a multiple of
|
|
|
|
// 4KiB, with a maximum of 128KiB.
|
2024-09-08 01:00:45 +02:00
|
|
|
//
|
2024-09-08 18:26:59 +02:00
|
|
|
// The returned VRAMCore servers implement flow control. As long as
|
2024-09-08 01:00:45 +02:00
|
|
|
// responses are processed as soon as they're available, each port can
|
|
|
|
// process one memory operation per cycle.
|
|
|
|
//
|
2024-09-08 18:26:59 +02:00
|
|
|
// The VRAMCore does not prevent write-write or write-read conflicts
|
2024-09-08 01:00:45 +02:00
|
|
|
// between the ports. The outcome of a simultaneous write to the same
|
|
|
|
// address is unspecified, as is the read output in a simultaneous
|
|
|
|
// read and write of the same address. The caller must use external
|
|
|
|
// arbitration to avoid such accesses.
|
2024-09-08 18:26:59 +02:00
|
|
|
module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
|
2024-09-08 01:00:45 +02:00
|
|
|
if (num_kilobytes > 128)
|
2024-09-08 18:26:59 +02:00
|
|
|
error("maximum VRAMCore size is 128KiB");
|
2024-09-08 01:00:45 +02:00
|
|
|
let num_bytes = num_kilobytes*1024;
|
|
|
|
if (num_bytes % 4096 != 0)
|
2024-09-08 18:26:59 +02:00
|
|
|
error("VRAMCore must be a multiple of 4096b");
|
2024-09-08 01:00:45 +02:00
|
|
|
let num_byterams = num_bytes/4096;
|
|
|
|
let num_arrays = ceil(fromInteger(num_byterams) / 8);
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
function Tuple2#(ArrayAddr, EBRAddr) split_addr(VRAMAddr a);
|
2024-09-08 18:26:59 +02:00
|
|
|
return unpack(pack(a));
|
2024-09-06 19:04:50 +02:00
|
|
|
endfunction
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
VRAMCoreInternal#(VRAMData) arrays[num_arrays];
|
2024-09-08 01:00:45 +02:00
|
|
|
for (Integer i=0; i<num_arrays; i=i+1) begin
|
|
|
|
let array_size = min(num_byterams - (i*8), 8);
|
|
|
|
arrays[i] <- mkByteRAMArray(array_size);
|
|
|
|
end
|
2024-09-06 19:04:50 +02:00
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
FIFOF#(ArrayAddr) array_addr_A <- mkPipelineFIFOF();
|
|
|
|
FIFOF#(ArrayAddr) array_addr_B <- mkPipelineFIFOF();
|
2024-09-06 19:04:50 +02:00
|
|
|
|
2024-09-08 18:26:59 +02:00
|
|
|
interface Server portA;
|
2024-09-06 19:04:50 +02:00
|
|
|
interface Put request;
|
2024-09-19 06:31:34 +02:00
|
|
|
method Action put(req);
|
|
|
|
match {.array, .addr} = split_addr(req.addr);
|
|
|
|
arrays[array].portA.request.put(VRAMInternalRequest{
|
|
|
|
addr: addr,
|
|
|
|
data: req.data
|
|
|
|
});
|
2024-09-06 19:04:50 +02:00
|
|
|
if (!isValid(req.data))
|
2024-09-19 06:31:34 +02:00
|
|
|
array_addr_A.enq(array);
|
2024-09-06 19:04:50 +02:00
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
interface Get response;
|
2024-09-19 06:31:34 +02:00
|
|
|
method ActionValue#(VRAMResponse) get();
|
|
|
|
array_addr_A.deq();
|
|
|
|
let ret <- arrays[array_addr_A.first].portA.response.get();
|
|
|
|
return VRAMResponse{data: ret};
|
2024-09-06 19:04:50 +02:00
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
endinterface
|
|
|
|
|
2024-09-08 18:26:59 +02:00
|
|
|
interface Server portB;
|
2024-09-06 19:04:50 +02:00
|
|
|
interface Put request;
|
2024-09-19 06:31:34 +02:00
|
|
|
method Action put(req);
|
|
|
|
match {.array, .addr} = split_addr(req.addr);
|
|
|
|
arrays[array].portB.request.put(VRAMInternalRequest{
|
|
|
|
addr: addr,
|
|
|
|
data: req.data
|
|
|
|
});
|
2024-09-06 19:04:50 +02:00
|
|
|
if (!isValid(req.data))
|
2024-09-19 06:31:34 +02:00
|
|
|
array_addr_B.enq(array);
|
2024-09-06 19:04:50 +02:00
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
interface Get response;
|
2024-09-19 06:31:34 +02:00
|
|
|
method ActionValue#(VRAMResponse) get();
|
|
|
|
array_addr_B.deq();
|
|
|
|
let ret <- arrays[array_addr_B.first].portB.response.get();
|
|
|
|
return VRAMResponse{data: ret};
|
2024-09-06 19:04:50 +02:00
|
|
|
endmethod
|
|
|
|
endinterface
|
|
|
|
endinterface
|
|
|
|
endmodule
|
|
|
|
|
|
|
|
endpackage
|