package VRAMCore; import GetPut::*; import ClientServer::*; import BRAMCore::*; import Real::*; import FIFOF::*; import SpecialFIFOs::*; 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; typedef struct { ChipAddr chip; ByteAddr addr; } EBRAddr deriving (Bits, Eq, FShow); typedef struct { EBRAddr addr; Maybe#(data) data; } VRAMInternalRequest#(type data) deriving (Bits, Eq, FShow); typedef struct { VRAMAddr addr; Maybe#(VRAMData) data; } VRAMRequest deriving (Bits, Eq, FShow); typedef struct { VRAMData data; } VRAMResponse deriving (Bits, Eq, FShow); interface VRAMCoreInternal#(type data); interface Server#(VRAMInternalRequest#(data), data) portA; interface Server#(VRAMInternalRequest#(data), data) portB; endinterface 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, VRAMCoreInternal#(Bit#(4)) ifc); let _ret; if (genC()) _ret <- mkNibbleRAM_Sim(chip_addr); else _ret <- mkNibbleRAM_ECP5(chip_addr); 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 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, VRAMCoreInternal#(VRAMData) ifc); VRAMCoreInternal#(Bit#(4)) upper <- mkNibbleRAM(chip_addr); VRAMCoreInternal#(Bit#(4)) lower <- mkNibbleRAM(chip_addr); 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 endinterface 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 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. // // 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); if (num_chips > 8) error("mkByteRAMArray can only array 8 raw ByteRAMs"); VRAMCoreInternal#(VRAMData) blocks[num_chips]; for (Integer i=0; i 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 Tuple2#(ArrayAddr, EBRAddr) split_addr(VRAMAddr a); return unpack(pack(a)); endfunction VRAMCoreInternal#(VRAMData) arrays[num_arrays]; for (Integer i=0; i