diff --git a/experiments/vram/Top.bsv b/experiments/vram/Top.bsv new file mode 100644 index 0000000..c2509b5 --- /dev/null +++ b/experiments/vram/Top.bsv @@ -0,0 +1,38 @@ +package Top; + +import VRAM::*; +import ECP5_RAM::*; +import TriState::*; + +(* always_enabled *) +interface Top; + method Action phi2(bit v); + method Action we(bit we); + method Action addr(UInt#(24) addr); + interface InOut#(Bit#(8)) data(); +endinterface + +(* synthesize *) +module mkTop(Top); + Reg#(PortReq) reqA <- mkRegU(); + Reg#(VRAMData) respA <- mkRegU(); + + let _ret <- mkByteRAMArray(8); + + rule putA; + _ret.portA.put(reqA.chip_select, reqA.write, reqA.addr, reqA.datain); + endrule + + rule getA; + respA <= _ret.portA.read(); + endrule + + method portA_read = respA._read; + method Action portA_put(cs, w, a, d); + reqA <= PortReq{chip_select: cs, write: w, addr: a, datain: d}; + endmethod + method portB_read = _ret.portB.read; + method portB_put = _ret.portB.put; +endmodule + +endpackage diff --git a/vram/VRAM.bsv b/vram/VRAM.bsv new file mode 100644 index 0000000..bd1b83e --- /dev/null +++ b/vram/VRAM.bsv @@ -0,0 +1,181 @@ +package VRAM; + +import GetPut::*; +import ClientServer::*; +import DReg::*; +import BRAM::*; +import Vector::*; +import FIFOF::*; +import SpecialFIFOs::*; + +import DelayLine::*; +import ECP5_RAM::*; + +typedef UInt#(17) VRAMAddr; + +typedef Bit#(8) VRAMData; + +// Each byte RAM we build below can address 4096 bytes, which is 12 +// address bits. +typedef UInt#(12) ByteAddr; + +// The difference between ByteRAM_Addr and VRAMAddr is the chip +// select ID. +typedef UInt#(5) ChipAddr; + +// ByteRAM is two EBRs glued together to make a whole-byte memory. +typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM; + +// 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(UInt#(3) chip_addr, ByteRAM ifc); + 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(UInt#(3) 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(UInt#(3) 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 + +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 32) + error("maximum number of blocks is 32 (128KiB)"); + UInt#(TAdd#(SizeOf#(VRAMAddr), 1)) max_request_addr = fromInteger((4096 * num_4kB_blocks)); + + function Tuple2#(ChipAddr, ByteAddr) split_addr(VRAMAddr a); + UInt#(TAdd#(SizeOf#(VRAMAddr), 1)) expanded = extend(a); + VRAMAddr wrapped = truncate(expanded % max_request_addr); + match {.chip, .off} = split(pack(wrapped)); + return tuple2(unpack(chip), unpack(off)); + endfunction + + ByteRAM blocks[num_4kB_blocks]; + for (Integer i=0; i