diff --git a/litedram/frontend/bist.py b/litedram/frontend/bist.py index a7d98ff..b674148 100644 --- a/litedram/frontend/bist.py +++ b/litedram/frontend/bist.py @@ -1,3 +1,5 @@ +"""Built In Self Test (BIST) modules for testing liteDRAM functionality.""" + from functools import reduce from operator import xor @@ -11,6 +13,23 @@ from litedram.frontend.dma import LiteDRAMDMAWriter, LiteDRAMDMAReader @CEInserter() class LFSR(Module): + """Linear-Feedback Shift Register to generate a pseudo-random sequence. + + Parameters + ---------- + n_out : int + Width of the output data signal. + n_state : int + ??? + taps : list of int + ??? + + Attributes + ---------- + o : in + Output data + """ + def __init__(self, n_out, n_state=31, taps=[27, 30]): self.o = Signal(n_out) @@ -26,12 +45,25 @@ class LFSR(Module): self.sync += [ state.eq(Cat(*curval[:n_state])), - self.o.eq(Cat(*curval)) + self.o.eq(Cat(*curval)), ] @CEInserter() class Counter(Module): + """Simple incremental counter. + + Parameters + ---------- + n_out : int + Width of the output data signal. + + Attributes + ---------- + o : in + Output data + """ + def __init__(self, n_out): self.o = Signal(n_out) @@ -41,6 +73,7 @@ class Counter(Module): class _LiteDRAMBISTGenerator(Module): + def __init__(self, dram_port, random): self.start = Signal() self.done = Signal() @@ -59,11 +92,10 @@ class _LiteDRAMBISTGenerator(Module): self.submodules += fsm fsm.act("IDLE", - self.done.eq(1), If(self.start, NextValue(cmd_counter, 0), - NextState("RUN") - ) + NextState("RUN"), + ), ) fsm.act("RUN", dma.sink.valid.eq(1), @@ -71,17 +103,38 @@ class _LiteDRAMBISTGenerator(Module): gen.ce.eq(1), NextValue(cmd_counter, cmd_counter + 1), If(cmd_counter == (self.length-1), - NextState("IDLE") - ) - ) + NextState("DONE"), + ), + ), + ) + fsm.act("DONE", + self.done.eq(1), ) self.comb += [ dma.sink.address.eq(self.base + cmd_counter), - dma.sink.data.eq(gen.o) + dma.sink.data.eq(gen.o), ] class LiteDRAMBISTGenerator(Module, AutoCSR): + """litex module to generate a given pattern in memory.abs + + Attributes + ---------- + reset : in + Reset the module. + start : in + Start the generation. + + base : in + DRAM address to start from. + length : in + Number of DRAM words to write. + + done : out + The module has completed writing the pattern. + """ + def __init__(self, dram_port, random=True): self.reset = CSR() self.start = CSR() @@ -98,37 +151,43 @@ class LiteDRAMBISTGenerator(Module, AutoCSR): reset_sync = BusSynchronizer(1, "sys", cd) start_sync = PulseSynchronizer("sys", cd) - done_sync = BusSynchronizer(1, cd, "sys") - self.submodules += reset_sync, start_sync, done_sync - - base_sync = BusSynchronizer(dram_port.aw, "sys", cd) - length_sync = BusSynchronizer(dram_port.aw, "sys", cd) - self.submodules += base_sync, length_sync - + self.submodules += reset_sync, start_sync self.comb += [ reset_sync.i.eq(self.reset.re), core.reset.eq(reset_sync.o), start_sync.i.eq(self.start.re), core.start.eq(start_sync.o), + ] + done_sync = BusSynchronizer(1, cd, "sys") + self.submodules += done_sync + self.comb += [ done_sync.i.eq(core.done), self.done.status.eq(done_sync.o), + ] + base_sync = BusSynchronizer(dram_port.aw, "sys", cd) + length_sync = BusSynchronizer(dram_port.aw, "sys", cd) + self.submodules += base_sync, length_sync + self.comb += [ base_sync.i.eq(self.base.storage), core.base.eq(base_sync.o), length_sync.i.eq(self.length.storage), - core.length.eq(length_sync.o) + core.length.eq(length_sync.o), ] class _LiteDRAMBISTChecker(Module, AutoCSR): + def __init__(self, dram_port, random): self.start = Signal() self.done = Signal() + self.base = Signal(dram_port.aw) self.length = Signal(dram_port.aw) + self.err_count = Signal(32) # # # @@ -138,64 +197,98 @@ class _LiteDRAMBISTChecker(Module, AutoCSR): self.submodules.gen = gen = gen_cls(dram_port.dw) # address - cmd_counter = Signal(dram_port.aw) + self._cmd_counter = cmd_counter = Signal(dram_port.aw) cmd_fsm = FSM(reset_state="IDLE") self.submodules += cmd_fsm cmd_fsm.act("IDLE", If(self.start, NextValue(cmd_counter, 0), - NextState("RUN") - ) + NextState("RUN"), + ), ) cmd_fsm.act("RUN", dma.sink.valid.eq(1), If(dma.sink.ready, NextValue(cmd_counter, cmd_counter + 1), If(cmd_counter == (self.length-1), - NextState("IDLE") - ) - ) + NextState("DONE") + ), + ), ) + cmd_fsm.act("DONE") self.comb += dma.sink.address.eq(self.base + cmd_counter) # data - data_counter = Signal(dram_port.aw) + self._data_counter = data_counter = Signal(dram_port.aw) data_fsm = FSM(reset_state="IDLE") self.submodules += data_fsm + self.error = error = Signal() + self.actual = actual = Signal(dram_port.aw) + self.expect = expect = Signal(dram_port.aw) + self.comb += [ + actual.eq(dma.source.data), + expect.eq(gen.o), + error.eq(dma.source.valid & (expect != actual)), + ] + data_fsm.act("IDLE", If(self.start, NextValue(data_counter, 0), NextValue(self.err_count, 0), - NextState("RUN") - ) + NextState("RUN"), + ), ) data_fsm.act("RUN", dma.source.ready.eq(1), If(dma.source.valid, gen.ce.eq(1), NextValue(data_counter, data_counter + 1), - If(dma.source.data != gen.o, - NextValue(self.err_count, self.err_count + 1) + If(error, + NextValue(self.err_count, self.err_count + 1), ), If(data_counter == (self.length-1), - NextState("IDLE") - ) - ) + NextState("DONE"), + ), + ), ) + data_fsm.act("DONE") - self.comb += self.done.eq(cmd_fsm.ongoing("IDLE") & - data_fsm.ongoing("IDLE")) + self.comb += self.done.eq( + cmd_fsm.ongoing("DONE") & data_fsm.ongoing("DONE")) class LiteDRAMBISTChecker(Module, AutoCSR): + """litex module to check a given pattern in memory. + + Attributes + ---------- + reset : in + Reset the module + start : in + Start the checking + + base : in + DRAM address to start from. + length : in + Number of DRAM words to check. + + done : out + The module has completed checking + + err_count : out + Number of DRAM words which don't match. + """ + def __init__(self, dram_port, random=True): self.reset = CSR() self.start = CSR() - self.done = CSRStatus() + self.base = CSRStorage(dram_port.aw) self.length = CSRStorage(dram_port.aw) + + self.done = CSRStatus() self.err_count = CSRStatus(32) # # # @@ -205,32 +298,39 @@ class LiteDRAMBISTChecker(Module, AutoCSR): core = ResetInserter()(_LiteDRAMBISTChecker(dram_port, random)) self.submodules.core = ClockDomainsRenamer(cd)(core) + #reset_sync = PulseSynchronizer("sys", cd) reset_sync = BusSynchronizer(1, "sys", cd) start_sync = PulseSynchronizer("sys", cd) - done_sync = BusSynchronizer(1, cd, "sys") - self.submodules += reset_sync, start_sync, done_sync - - base_sync = BusSynchronizer(dram_port.aw, "sys", cd) - length_sync = BusSynchronizer(dram_port.aw, "sys", cd) - err_count_sync = BusSynchronizer(32, cd, "sys") - self.submodules += base_sync, length_sync, err_count_sync - + self.submodules += reset_sync, start_sync self.comb += [ reset_sync.i.eq(self.reset.re), core.reset.eq(reset_sync.o), start_sync.i.eq(self.start.re), core.start.eq(start_sync.o), + ] + done_sync = BusSynchronizer(1, cd, "sys") + self.submodules += done_sync + self.comb += [ done_sync.i.eq(core.done), self.done.status.eq(done_sync.o), + ] + base_sync = BusSynchronizer(dram_port.aw, "sys", cd) + length_sync = BusSynchronizer(dram_port.aw, "sys", cd) + self.submodules += base_sync, length_sync + self.comb += [ base_sync.i.eq(self.base.storage), core.base.eq(base_sync.o), length_sync.i.eq(self.length.storage), core.length.eq(length_sync.o), - - err_count_sync.i.eq(core.err_count), - self.err_count.status.eq(err_count_sync.o) + ] + + err_count_sync = BusSynchronizer(32, cd, "sys") + self.submodules += err_count_sync + self.comb += [ + err_count_sync.i.eq(core.err_count), + self.err_count.status.eq(err_count_sync.o), ] diff --git a/litedram/frontend/dma.py b/litedram/frontend/dma.py index 13e1b7e..0a4b522 100644 --- a/litedram/frontend/dma.py +++ b/litedram/frontend/dma.py @@ -1,9 +1,37 @@ +"""Direct Memory Access (DMA) reader and writer modules.""" + from litex.gen import * from litex.soc.interconnect import stream class LiteDRAMDMAReader(Module): + """Read data from DRAM memory. + + For every address written to the sink, one DRAM word will be produced on + the source. + + Parameters + ---------- + port : dram_port + Port on the DRAM memory controller to read from. + + fifo_depth : int + How many request results the output FIFO can contain (and thus how many + read requests can be outstanding at once). + + fifo_buffered : bool + ??? + + Attributes + ---------- + sink : Record("address") + Sink for DRAM addresses to be read. + + source : Record("data") + Source for DRAM word results from reading. + """ + def __init__(self, port, fifo_depth=16, fifo_buffered=False): self.sink = sink = stream.Endpoint([("address", port.aw)]) self.source = source = stream.Endpoint([("data", port.dw)]) @@ -48,6 +76,25 @@ class LiteDRAMDMAReader(Module): class LiteDRAMDMAWriter(Module): + """Write data to DRAM memory. + + Parameters + ---------- + port : dram_port + Port on the DRAM memory controller to write to. + + fifo_depth : int + How many requests the input FIFO can contain (and thus how many write + requests can be outstanding at once). + + fifo_buffered : bool + ??? + + Attributes + ---------- + sink : Record("address", "data") + Sink for DRAM addresses and DRAM data word to be written too. + """ def __init__(self, port, fifo_depth=16, fifo_buffered=False): self.sink = sink = stream.Endpoint([("address", port.aw), ("data", port.dw)]) diff --git a/test/bist_async_tb.py b/test/bist_async_tb.py index f721ed6..3930f50 100755 --- a/test/bist_async_tb.py +++ b/test/bist_async_tb.py @@ -12,9 +12,11 @@ from litedram.frontend.bist import LiteDRAMBISTGenerator from litedram.frontend.bist import LiteDRAMBISTChecker from litedram.frontend.adaptation import LiteDRAMPortCDC - from litedram.phy.model import SDRAMPHYModel +from test.common import * + + class SimModule(SDRAMModule): # geometry nbanks = 2 @@ -70,20 +72,14 @@ def main_generator(dut): for i in range(100): yield # init - yield dut.generator.reset.storage.eq(1) - yield dut.checker.reset.storage.eq(1) - yield - yield dut.generator.reset.storage.eq(0) - yield dut.checker.reset.storage.eq(0) - yield + yield from reset_bist_module(dut.generator) + yield from reset_bist_module(dut.checker) # write yield dut.generator.base.storage.eq(16) yield dut.generator.length.storage.eq(16) for i in range(32): yield - yield dut.generator.shoot.re.eq(1) - yield - yield dut.generator.shoot.re.eq(0) + yield from toggle_re(dut.generator.start) for i in range(32): yield while((yield dut.generator.done.status) == 0): @@ -93,9 +89,7 @@ def main_generator(dut): yield dut.checker.length.storage.eq(16) for i in range(32): yield - yield dut.checker.shoot.re.eq(1) - yield - yield dut.checker.shoot.re.eq(0) + yield from toggle_re(dut.generator.start) for i in range(32): yield while((yield dut.checker.done.status) == 0): diff --git a/test/bist_tb.py b/test/bist_tb.py index 6580b4c..6f65aa0 100755 --- a/test/bist_tb.py +++ b/test/bist_tb.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import random + from litex.gen import * from litex.soc.interconnect.stream import * @@ -8,58 +10,172 @@ from litedram.common import LiteDRAMWritePort, LiteDRAMReadPort from litedram.frontend.bist import LiteDRAMBISTGenerator from litedram.frontend.bist import LiteDRAMBISTChecker -from test.common import DRAMMemory +from test.common import * class TB(Module): def __init__(self): self.write_port = LiteDRAMWritePort(aw=32, dw=32) self.read_port = LiteDRAMReadPort(aw=32, dw=32) - self.submodules.generator = LiteDRAMBISTGenerator(self.write_port) - self.submodules.checker = LiteDRAMBISTChecker(self.read_port) + self.submodules.generator = LiteDRAMBISTGenerator(self.write_port, random=True) + self.submodules.checker = LiteDRAMBISTChecker(self.read_port, random=True) + + +def main_generator(dut, mem): + # Populate memory with random data + random.seed(0) + for i in range(0, len(mem.mem)): + mem.mem[i] = random.randint(0, 2**mem.width) -def main_generator(dut): - # init - yield dut.generator.reset.storage.eq(1) - yield dut.checker.reset.storage.eq(1) - yield - yield dut.generator.reset.storage.eq(0) - yield dut.checker.reset.storage.eq(0) - yield # write + yield from reset_bist_module(dut.generator) + yield dut.generator.base.storage.eq(16) yield dut.generator.length.storage.eq(64) for i in range(8): yield - yield dut.generator.shoot.re.eq(1) + yield dut.generator.start.re.eq(1) yield - yield dut.generator.shoot.re.eq(0) + yield dut.generator.start.re.eq(0) for i in range(8): yield while((yield dut.generator.done.status) == 0): yield - # read + done = yield dut.generator.done.status + assert done, done + + # read with no errors + yield from reset_bist_module(dut.checker) + errors = yield dut.checker.err_count.status + assert errors == 0, errors + yield dut.checker.base.storage.eq(16) yield dut.checker.length.storage.eq(64) for i in range(8): yield - yield dut.checker.shoot.re.eq(1) - yield - yield dut.checker.shoot.re.eq(0) + yield from toggle_re(dut.checker.start) for i in range(8): yield while((yield dut.checker.done.status) == 0): yield - # check - print("errors {:d}".format((yield dut.checker.error_count.status))) + done = yield dut.checker.done.status + assert done, done + errors = yield dut.checker.err_count.status + assert errors == 0, errors + yield + yield + + # read with one error + yield from reset_bist_module(dut.checker) + errors = yield dut.checker.err_count.status + assert errors == 0, errors + + print("mem.mem[20]", hex(mem.mem[20])) + assert mem.mem[20] == 0xffff000f, hex(mem.mem[20]) + mem.mem[20] = 0x200 # Make position 20 an error + + yield dut.checker.base.storage.eq(16) + yield dut.checker.length.storage.eq(64) + for i in range(8): + yield + yield from toggle_re(dut.checker.start) + for i in range(8): + yield + while((yield dut.checker.done.status) == 0): + yield + done = yield dut.checker.done.status + assert done, done + errors = yield dut.checker.err_count.status + assert errors == 1, errors + + yield + yield + + # read with two errors + yield from reset_bist_module(dut.checker) + errors = yield dut.checker.err_count.status + assert errors == 0, errors + + print("mem.mem[21]", hex(mem.mem[21])) + assert mem.mem[21] == 0xfff1ff1f, hex(mem.mem[21]) + mem.mem[21] = 0x210 # Make position 21 an error + + yield dut.checker.base.storage.eq(16) + yield dut.checker.length.storage.eq(64) + for i in range(8): + yield + yield from toggle_re(dut.checker.start) + for i in range(8): + yield + while((yield dut.checker.done.status) == 0): + yield + done = yield dut.checker.done.status + assert done, done + errors = yield dut.checker.err_count.status + assert errors == 2, errors + + yield + yield + + # read with two errors but halting on the first one + yield from reset_bist_module(dut.checker) + errors = yield dut.checker.err_count.status + assert errors == 0, errors + + yield dut.checker.base.storage.eq(16) + yield dut.checker.length.storage.eq(64) + for i in range(8): + yield + yield from toggle_re(dut.checker.start) + for i in range(8): + yield + while((yield dut.checker.core.error) == 0): + yield + + err_addr = yield dut.checker.core._data_counter + dut.checker.core.base + assert err_addr == 20, err_addr + err_expect = yield dut.checker.core.expect + assert err_expect == 0xffff000f, hex(err_expect) + err_actual = yield dut.checker.core.actual + assert err_actual == 0x200, err_actual + yield + errors = yield dut.checker.core.err_count + assert errors == 1, errors + + while((yield dut.checker.core.error) == 0): + yield + + err_addr = yield dut.checker.core._data_counter + dut.checker.core.base + assert err_addr == 21, err_addr + err_expect = yield dut.checker.core.expect + assert err_expect == 0xfff1ff1f, hex(err_expect) + err_actual = yield dut.checker.core.actual + assert err_actual == 0x210, hex(err_actual) + yield + errors = yield dut.checker.core.err_count + assert errors == 2, errors + + while((yield dut.checker.done.status) == 0): + yield + + done = yield dut.checker.done.status + assert done, done + errors = yield dut.checker.err_count.status + assert errors == 2, errors + + yield + yield + if __name__ == "__main__": tb = TB() mem = DRAMMemory(32, 128) generators = { - "sys" : [main_generator(tb), - mem.write_generator(tb.write_port), - mem.read_generator(tb.read_port)] + "sys" : [ + main_generator(tb, mem), + mem.write_generator(tb.write_port), + mem.read_generator(tb.read_port), + ], } clocks = {"sys": 10} run_simulation(tb, generators, clocks, vcd_name="sim.vcd") diff --git a/test/common.py b/test/common.py index e8f0f2d..5c12f26 100644 --- a/test/common.py +++ b/test/common.py @@ -1,6 +1,28 @@ from litex.gen import * +def toggle_re(reg): + resig = reg.re + # Check that reset isn't set + reval = yield resig + assert not reval, reval + yield resig.eq(1) + yield + yield resig.eq(0) + + +def reset_bist_module(module): + # Toggle the reset + yield from toggle_re(module.reset) + # Takes 8 more clock cycles for the reset to have an effect + for i in range(8): + yield + + # Check some initial conditions are correct after reset. + done = yield module.done.status + assert not done, done + + def seed_to_data(seed, random=True, nbits=32): if nbits == 32: if random: