Compare commits
No commits in common. "b2b2c1400955e156d02911210f0621766ad0a8ae" and "6e84e9c689cf60f93220f68d2d089de7b29a3adc" have entirely different histories.
b2b2c14009
...
6e84e9c689
|
@ -4,10 +4,35 @@ import VRAM::*;
|
||||||
import ECP5_RAM::*;
|
import ECP5_RAM::*;
|
||||||
import TriState::*;
|
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 *)
|
(* synthesize *)
|
||||||
module mkTop(VRAM);
|
module mkTop(Top);
|
||||||
let _ret <- mkVRAM(112);
|
Reg#(PortReq) reqA <- mkRegU();
|
||||||
return _ret;
|
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
|
endmodule
|
||||||
|
|
||||||
endpackage
|
endpackage
|
||||||
|
|
10
tasks.py
10
tasks.py
|
@ -50,7 +50,7 @@ def find_verilog_modules(c, target_dir, modules):
|
||||||
module_path = None
|
module_path = None
|
||||||
verilog_path = Path(module).with_suffix(".v")
|
verilog_path = Path(module).with_suffix(".v")
|
||||||
# Try preferred libpaths first.
|
# Try preferred libpaths first.
|
||||||
for p in preferred_libpaths:
|
for p in libpaths:
|
||||||
f = p / verilog_path
|
f = p / verilog_path
|
||||||
if f.is_file():
|
if f.is_file():
|
||||||
module_path = f
|
module_path = f
|
||||||
|
@ -214,7 +214,6 @@ def synth(c, target):
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def test(c, target):
|
def test(c, target):
|
||||||
to_run = []
|
|
||||||
for target in expand_test_target(target):
|
for target in expand_test_target(target):
|
||||||
out_info, out_sim, out_bsc = ensure_build_dirs(target, "info", "sim", "bsc")
|
out_info, out_sim, out_bsc = ensure_build_dirs(target, "info", "sim", "bsc")
|
||||||
libdirs = bluespec_libdirs(target, "sim")
|
libdirs = bluespec_libdirs(target, "sim")
|
||||||
|
@ -227,11 +226,8 @@ def test(c, target):
|
||||||
testdata_tgt = testdata_tgt.relative_to(out_sim, walk_up=True)
|
testdata_tgt = testdata_tgt.relative_to(out_sim, walk_up=True)
|
||||||
testdata.unlink(missing_ok=True)
|
testdata.unlink(missing_ok=True)
|
||||||
testdata.symlink_to(testdata_tgt, target_is_directory=True)
|
testdata.symlink_to(testdata_tgt, target_is_directory=True)
|
||||||
to_run.append((out_sim, f"./TB -V {target.stem}.vcd"))
|
with c.cd(out_sim):
|
||||||
for d, cmd in to_run:
|
c.run(f"./TB -V {target.stem}.vcd")
|
||||||
print("")
|
|
||||||
with c.cd(d):
|
|
||||||
c.run(cmd)
|
|
||||||
|
|
||||||
@task
|
@task
|
||||||
def clean(c):
|
def clean(c):
|
||||||
|
|
104
vram/VRAM.bsv
104
vram/VRAM.bsv
|
@ -7,19 +7,11 @@ import BRAM::*;
|
||||||
import Vector::*;
|
import Vector::*;
|
||||||
import FIFOF::*;
|
import FIFOF::*;
|
||||||
import SpecialFIFOs::*;
|
import SpecialFIFOs::*;
|
||||||
import Real::*;
|
|
||||||
import Printf::*;
|
|
||||||
|
|
||||||
import DelayLine::*;
|
import DelayLine::*;
|
||||||
import ECP5_RAM::*;
|
import ECP5_RAM::*;
|
||||||
|
|
||||||
export VRAMAddr;
|
typedef UInt#(17) VRAMAddr;
|
||||||
export VRAMData;
|
|
||||||
export mkVRAM;
|
|
||||||
export VRAMRequest;
|
|
||||||
export VRAMResponse;
|
|
||||||
export VRAMServer;
|
|
||||||
export VRAM;
|
|
||||||
|
|
||||||
typedef Bit#(8) VRAMData;
|
typedef Bit#(8) VRAMData;
|
||||||
|
|
||||||
|
@ -27,7 +19,9 @@ typedef Bit#(8) VRAMData;
|
||||||
// address bits.
|
// address bits.
|
||||||
typedef UInt#(12) ByteAddr;
|
typedef UInt#(12) ByteAddr;
|
||||||
|
|
||||||
typedef UInt#(3) ChipAddr;
|
// 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.
|
// ByteRAM is two EBRs glued together to make a whole-byte memory.
|
||||||
typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM;
|
typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM;
|
||||||
|
@ -36,14 +30,14 @@ typedef EBR#(ByteAddr, VRAMData, ByteAddr, VRAMData) ByteRAM;
|
||||||
// block. Like the underlying ECP5 EBRs, callers must bring their own
|
// block. Like the underlying ECP5 EBRs, callers must bring their own
|
||||||
// flow control to read out responses one cycle after putting a read
|
// flow control to read out responses one cycle after putting a read
|
||||||
// request.
|
// request.
|
||||||
module mkByteRAM(ChipAddr chip_addr, ByteRAM ifc);
|
module mkByteRAM(UInt#(3) chip_addr, ByteRAM ifc);
|
||||||
EBRPortConfig cfg = defaultValue;
|
EBRPortConfig cfg = defaultValue;
|
||||||
cfg.chip_select_addr = chip_addr;
|
cfg.chip_select_addr = chip_addr;
|
||||||
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) upper <- mkEBRCore(cfg, cfg);
|
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) upper <- mkEBRCore(cfg, cfg);
|
||||||
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) lower <- mkEBRCore(cfg, cfg);
|
EBR#(ByteAddr, Bit#(4), ByteAddr, Bit#(4)) lower <- mkEBRCore(cfg, cfg);
|
||||||
|
|
||||||
interface EBRPort portA;
|
interface EBRPort portA;
|
||||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
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));
|
upper.portA.put(chip_select, write, addr, truncate(data_in>>4));
|
||||||
lower.portA.put(chip_select, write, addr, truncate(data_in));
|
lower.portA.put(chip_select, write, addr, truncate(data_in));
|
||||||
endmethod
|
endmethod
|
||||||
|
@ -54,7 +48,7 @@ module mkByteRAM(ChipAddr chip_addr, ByteRAM ifc);
|
||||||
endinterface
|
endinterface
|
||||||
|
|
||||||
interface EBRPort portB;
|
interface EBRPort portB;
|
||||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
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));
|
upper.portB.put(chip_select, write, addr, truncate(data_in>>4));
|
||||||
lower.portB.put(chip_select, write, addr, truncate(data_in));
|
lower.portB.put(chip_select, write, addr, truncate(data_in));
|
||||||
endmethod
|
endmethod
|
||||||
|
@ -65,10 +59,6 @@ module mkByteRAM(ChipAddr chip_addr, ByteRAM ifc);
|
||||||
endinterface
|
endinterface
|
||||||
endmodule : mkByteRAM
|
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);
|
module mkByteRAMArray(Integer num_chips, ByteRAM ifc);
|
||||||
if (num_chips > 8)
|
if (num_chips > 8)
|
||||||
error("mkByteRAMArray can only array 8 raw ByteRAMs");
|
error("mkByteRAMArray can only array 8 raw ByteRAMs");
|
||||||
|
@ -77,11 +67,11 @@ module mkByteRAMArray(Integer num_chips, ByteRAM ifc);
|
||||||
for (Integer i=0; i<num_chips; i=i+1)
|
for (Integer i=0; i<num_chips; i=i+1)
|
||||||
blocks[i] <- mkByteRAM(fromInteger(i));
|
blocks[i] <- mkByteRAM(fromInteger(i));
|
||||||
|
|
||||||
DelayLine#(ChipAddr) read_chip_A <- mkDelayLine(1);
|
DelayLine#(UInt#(3)) read_chip_A <- mkDelayLine(1);
|
||||||
DelayLine#(ChipAddr) read_chip_B <- mkDelayLine(1);
|
DelayLine#(UInt#(3)) read_chip_B <- mkDelayLine(1);
|
||||||
|
|
||||||
interface EBRPort portA;
|
interface EBRPort portA;
|
||||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
method Action put(UInt#(3) chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
||||||
for (Integer i=0; i<num_chips; i=i+1)
|
for (Integer i=0; i<num_chips; i=i+1)
|
||||||
blocks[i].portA.put(chip_select, write, addr, data_in);
|
blocks[i].portA.put(chip_select, write, addr, data_in);
|
||||||
if (write)
|
if (write)
|
||||||
|
@ -99,7 +89,7 @@ module mkByteRAMArray(Integer num_chips, ByteRAM ifc);
|
||||||
endinterface
|
endinterface
|
||||||
|
|
||||||
interface EBRPort portB;
|
interface EBRPort portB;
|
||||||
method Action put(ChipAddr chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
method Action put(UInt#(3) chip_select, Bool write, ByteAddr addr, VRAMData data_in);
|
||||||
for (Integer i=0; i<num_chips; i=i+1)
|
for (Integer i=0; i<num_chips; i=i+1)
|
||||||
blocks[i].portB.put(chip_select, write, addr, data_in);
|
blocks[i].portB.put(chip_select, write, addr, data_in);
|
||||||
if (write)
|
if (write)
|
||||||
|
@ -117,10 +107,6 @@ module mkByteRAMArray(Integer num_chips, ByteRAM ifc);
|
||||||
endinterface
|
endinterface
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
typedef UInt#(2) ArrayAddr;
|
|
||||||
|
|
||||||
typedef UInt#(17) VRAMAddr;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
VRAMAddr addr;
|
VRAMAddr addr;
|
||||||
Maybe#(VRAMData) data;
|
Maybe#(VRAMData) data;
|
||||||
|
@ -138,58 +124,38 @@ interface VRAM;
|
||||||
interface VRAMServer portB;
|
interface VRAMServer portB;
|
||||||
endinterface
|
endinterface
|
||||||
|
|
||||||
// mkVRAM creates a dual port VRAM of the specified size, using ECP5
|
module mkVRAM(Integer num_4kB_blocks, VRAM ifc);
|
||||||
// EBR memory primitives. The memory size must be a multiple of 4KiB,
|
if (num_4kB_blocks > 32)
|
||||||
// with a maximum of 128KiB.
|
error("maximum number of blocks is 32 (128KiB)");
|
||||||
//
|
UInt#(TAdd#(SizeOf#(VRAMAddr), 1)) max_request_addr = fromInteger((4096 * num_4kB_blocks));
|
||||||
// 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 Tuple3#(ArrayAddr, ChipAddr, ByteAddr) split_addr(VRAMAddr a);
|
function Tuple2#(ChipAddr, ByteAddr) split_addr(VRAMAddr a);
|
||||||
if (num_bytes < 128*1024)
|
UInt#(TAdd#(SizeOf#(VRAMAddr), 1)) expanded = extend(a);
|
||||||
a = a % fromInteger(num_bytes);
|
VRAMAddr wrapped = truncate(expanded % max_request_addr);
|
||||||
match {.top, .byteaddr} = split(pack(a));
|
match {.chip, .off} = split(pack(wrapped));
|
||||||
Tuple2#(Bit#(SizeOf#(ArrayAddr)), Bit#(SizeOf#(ChipAddr))) route = split(top);
|
return tuple2(unpack(chip), unpack(off));
|
||||||
return tuple3(unpack(tpl_1(route)), unpack(tpl_2(route)), unpack(byteaddr));
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
ByteRAM arrays[num_arrays];
|
ByteRAM blocks[num_4kB_blocks];
|
||||||
for (Integer i=0; i<num_arrays; i=i+1) begin
|
for (Integer i=0; i<num_4kB_blocks; i=i+1)
|
||||||
let array_size = min(num_byterams - (i*8), 8);
|
blocks[i] <- mkByteRAM(0);
|
||||||
arrays[i] <- mkByteRAMArray(array_size);
|
|
||||||
end
|
|
||||||
|
|
||||||
Reg#(Maybe#(ArrayAddr)) inflight_A[2] <- mkCReg(2, tagged Invalid);
|
Reg#(Maybe#(ChipAddr)) inflight_A[2] <- mkCReg(2, tagged Invalid);
|
||||||
Reg#(Maybe#(ArrayAddr)) inflight_B[2] <- mkCReg(2, tagged Invalid);
|
Reg#(Maybe#(ChipAddr)) inflight_B[2] <- mkCReg(2, tagged Invalid);
|
||||||
|
|
||||||
interface VRAMServer portA;
|
interface VRAMServer portA;
|
||||||
interface Put request;
|
interface Put request;
|
||||||
method Action put(VRAMRequest req) if (inflight_A[1] matches tagged Invalid);
|
method Action put(VRAMRequest req) if (inflight_A[1] matches tagged Invalid);
|
||||||
match {.array, .chip, .byteaddr} = split_addr(req.addr);
|
match {.chip, .off} = split_addr(req.addr);
|
||||||
arrays[array].portA.put(chip, isValid(req.data), byteaddr, fromMaybe(0, req.data));
|
blocks[chip].portA.put(0, isValid(req.data), off, fromMaybe(0, req.data));
|
||||||
if (!isValid(req.data))
|
if (!isValid(req.data))
|
||||||
inflight_A[1] <= tagged Valid array;
|
inflight_A[1] <= tagged Valid chip;
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
interface Get response;
|
interface Get response;
|
||||||
method ActionValue#(VRAMResponse) get() if (inflight_A[0] matches tagged Valid .array);
|
method ActionValue#(VRAMResponse) get() if (inflight_A[0] matches tagged Valid .chip);
|
||||||
inflight_A[0] <= tagged Invalid;
|
inflight_A[0] <= tagged Invalid;
|
||||||
return VRAMResponse{data: arrays[array].portA.read()};
|
return VRAMResponse{data: blocks[chip].portA.read()};
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
endinterface
|
endinterface
|
||||||
|
@ -197,16 +163,16 @@ module mkVRAM(Integer num_kilobytes, VRAM ifc);
|
||||||
interface VRAMServer portB;
|
interface VRAMServer portB;
|
||||||
interface Put request;
|
interface Put request;
|
||||||
method Action put(VRAMRequest req) if (inflight_B[1] matches tagged Invalid);
|
method Action put(VRAMRequest req) if (inflight_B[1] matches tagged Invalid);
|
||||||
match {.array, .chip, .byteaddr} = split_addr(req.addr);
|
match {.chip, .off} = split_addr(req.addr);
|
||||||
arrays[array].portB.put(0, isValid(req.data), byteaddr, fromMaybe(0, req.data));
|
blocks[chip].portB.put(0, isValid(req.data), off, fromMaybe(0, req.data));
|
||||||
if (!isValid(req.data))
|
if (!isValid(req.data))
|
||||||
inflight_B[1] <= tagged Valid array;
|
inflight_B[1] <= tagged Valid chip;
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
interface Get response;
|
interface Get response;
|
||||||
method ActionValue#(VRAMResponse) get() if (inflight_B[0] matches tagged Valid .array);
|
method ActionValue#(VRAMResponse) get() if (inflight_B[0] matches tagged Valid .chip);
|
||||||
inflight_B[0] <= tagged Invalid;
|
inflight_B[0] <= tagged Invalid;
|
||||||
return VRAMResponse{data: arrays[array].portB.read()};
|
return VRAMResponse{data: blocks[chip].portB.read()};
|
||||||
endmethod
|
endmethod
|
||||||
endinterface
|
endinterface
|
||||||
endinterface
|
endinterface
|
||||||
|
|
Loading…
Reference in New Issue