From b2b2c1400955e156d02911210f0621766ad0a8ae Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 7 Sep 2024 16:00:45 -0700 Subject: [PATCH] vram/VRAM: finish the top-level VRAM module Well, for now at least. It can build 112KiB and 128KiB memories that seem to synthesize to something reasonable. --- experiments/vram/Top.bsv | 31 ++---------- vram/VRAM.bsv | 104 ++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 63 deletions(-) diff --git a/experiments/vram/Top.bsv b/experiments/vram/Top.bsv index c2509b5..94c5324 100644 --- a/experiments/vram/Top.bsv +++ b/experiments/vram/Top.bsv @@ -4,35 +4,10 @@ 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; +module mkTop(VRAM); + let _ret <- mkVRAM(112); + return _ret; endmodule endpackage diff --git a/vram/VRAM.bsv b/vram/VRAM.bsv index bd1b83e..f70abb2 100644 --- a/vram/VRAM.bsv +++ b/vram/VRAM.bsv @@ -7,11 +7,19 @@ import BRAM::*; import Vector::*; import FIFOF::*; import SpecialFIFOs::*; +import Real::*; +import Printf::*; import DelayLine::*; import ECP5_RAM::*; -typedef UInt#(17) VRAMAddr; +export VRAMAddr; +export VRAMData; +export mkVRAM; +export VRAMRequest; +export VRAMResponse; +export VRAMServer; +export VRAM; typedef Bit#(8) VRAMData; @@ -19,9 +27,7 @@ typedef Bit#(8) VRAMData; // address bits. typedef UInt#(12) ByteAddr; -// The difference between ByteRAM_Addr and VRAMAddr is the chip -// select ID. -typedef UInt#(5) ChipAddr; +typedef UInt#(3) ChipAddr; // ByteRAM is two EBRs glued together to make a whole-byte memory. typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM; @@ -30,14 +36,14 @@ typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM; // 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); +module mkByteRAM(ChipAddr 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); + 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 @@ -48,7 +54,7 @@ module mkByteRAM(UInt#(3) chip_addr, ByteRAM ifc); endinterface interface EBRPort portB; - method Action put(UInt#(3) chip_select, Bool write, ByteAddr addr, VRAMData data_in); + 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 @@ -59,6 +65,10 @@ module mkByteRAM(UInt#(3) chip_addr, ByteRAM ifc); 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"); @@ -67,11 +77,11 @@ module mkByteRAMArray(Integer num_chips, ByteRAM ifc); 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)); +// mkVRAM 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 VRAM 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 VRAM 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 mkVRAM(Integer num_kilobytes, VRAM ifc); + if (num_kilobytes > 128) + error("maximum VRAM size is 128KiB"); + let num_bytes = num_kilobytes*1024; + if (num_bytes % 4096 != 0) + error("VRAM must be a multiple of 4096b"); + let num_byterams = num_bytes/4096; + let num_arrays = ceil(fromInteger(num_byterams) / 8); - 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)); + function Tuple3#(ArrayAddr, ChipAddr, ByteAddr) split_addr(VRAMAddr a); + if (num_bytes < 128*1024) + a = a % fromInteger(num_bytes); + match {.top, .byteaddr} = split(pack(a)); + Tuple2#(Bit#(SizeOf#(ArrayAddr)), Bit#(SizeOf#(ChipAddr))) route = split(top); + return tuple3(unpack(tpl_1(route)), unpack(tpl_2(route)), unpack(byteaddr)); endfunction - ByteRAM blocks[num_4kB_blocks]; - for (Integer i=0; i