gary/vram/VRAMCore.bsv

232 lines
7.9 KiB
Plaintext

package VRAMCore;
import GetPut::*;
import ClientServer::*;
import BRAMCore::*;
import Real::*;
import DelayLine::*;
import ECP5_RAM::*;
export VRAMAddr;
export VRAMData;
export VRAMRequest(..);
export VRAMResponse(..);
export VRAMCore(..);
export mkVRAMCore;
typedef Bit#(8) VRAMData;
typedef UInt#(17) VRAMAddr;
typedef UInt#(2) ArrayAddr;
typedef UInt#(3) ChipAddr;
typedef UInt#(12) ByteAddr;
// 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, FShow);
typedef struct {
VRAMData data;
} VRAMResponse deriving (Bits, Eq, FShow);
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);
interface EBRPort portA;
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
upper.portA.put(chip_select, write, addr, truncate(data_in>>4));
lower.portA.put(chip_select, write, addr, truncate(data_in));
endmethod
method VRAMData read();
return (extend(upper.portA.read())<<4) | (extend(lower.portA.read()));
endmethod
endinterface
interface EBRPort portB;
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
upper.portB.put(chip_select, write, addr, truncate(data_in>>4));
lower.portB.put(chip_select, write, addr, truncate(data_in));
endmethod
method VRAMData read();
return (extend(upper.portB.read())<<4) | (extend(lower.portB.read()));
endmethod
endinterface
endmodule : mkByteRAM
// 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.
module mkByteRAMArray(Integer num_chips, ByteRAM ifc);
if (num_chips > 8)
error("mkByteRAMArray can only array 8 raw ByteRAMs");
ByteRAM blocks[num_chips];
for (Integer i=0; i<num_chips; i=i+1)
blocks[i] <- mkByteRAM(fromInteger(i));
DelayLine#(ChipAddr) read_chip_A <- mkDelayLine(1);
DelayLine#(ChipAddr) read_chip_B <- mkDelayLine(1);
interface EBRPort portA;
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
for (Integer i=0; i<num_chips; i=i+1)
blocks[i].portA.put(chip_select, write, addr, data_in);
if (!write)
read_chip_A <= chip_select;
endmethod
method VRAMData read();
if (read_chip_A.ready)
if (read_chip_A <= fromInteger(num_chips-1))
return blocks[read_chip_A].portA.read();
else
return 0;
else
return 0;
endmethod
endinterface
interface EBRPort portB;
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
for (Integer i=0; i<num_chips; i=i+1)
blocks[i].portB.put(chip_select, write, addr, data_in);
if (!write)
read_chip_B <= chip_select;
endmethod
method VRAMData read();
if (read_chip_B.ready)
if (read_chip_B <= fromInteger(num_chips-1))
return blocks[read_chip_B].portB.read();
else
return 0;
else
return 0;
endmethod
endinterface
endmodule
interface VRAMCore;
interface Server#(VRAMRequest, VRAMResponse) portA;
interface Server#(VRAMRequest, VRAMResponse) portB;
endinterface
// 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.
//
// The returned VRAMCore servers implement flow control. As long as
// responses are processed as soon as they're available, each port can
// process one memory operation per cycle.
//
// The VRAMCore does not prevent write-write or write-read conflicts
// 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.
module mkVRAMCore(Integer num_kilobytes, VRAMCore ifc);
if (num_kilobytes > 128)
error("maximum VRAMCore size is 128KiB");
let num_bytes = num_kilobytes*1024;
if (num_bytes % 4096 != 0)
error("VRAMCore must be a multiple of 4096b");
let num_byterams = num_bytes/4096;
let num_arrays = ceil(fromInteger(num_byterams) / 8);
function Tuple3#(ArrayAddr, ChipAddr, ByteAddr) split_addr(VRAMAddr a);
return unpack(pack(a));
endfunction
ByteRAM arrays[num_arrays];
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
Reg#(Maybe#(ArrayAddr)) inflight_A[2] <- mkCReg(2, tagged Invalid);
Reg#(Maybe#(ArrayAddr)) inflight_B[2] <- mkCReg(2, tagged Invalid);
interface Server portA;
interface Put request;
method Action put(VRAMRequest req) if (inflight_A[1] matches tagged Invalid);
match {.array, .chip, .byteaddr} = split_addr(req.addr);
arrays[array].portA.put(chip, isValid(req.data), byteaddr, fromMaybe(0, req.data));
if (!isValid(req.data))
inflight_A[1] <= tagged Valid array;
endmethod
endinterface
interface Get response;
method ActionValue#(VRAMResponse) get() if (inflight_A[0] matches tagged Valid .array);
inflight_A[0] <= tagged Invalid;
return VRAMResponse{data: arrays[array].portA.read()};
endmethod
endinterface
endinterface
interface Server portB;
interface Put request;
method Action put(VRAMRequest req) if (inflight_B[1] matches tagged Invalid);
match {.array, .chip, .byteaddr} = split_addr(req.addr);
arrays[array].portB.put(0, isValid(req.data), byteaddr, fromMaybe(0, req.data));
if (!isValid(req.data))
inflight_B[1] <= tagged Valid array;
endmethod
endinterface
interface Get response;
method ActionValue#(VRAMResponse) get() if (inflight_B[0] matches tagged Valid .array);
inflight_B[0] <= tagged Invalid;
return VRAMResponse{data: arrays[array].portB.read()};
endmethod
endinterface
endinterface
endmodule
endpackage