def test_cas_tccd(self): # Verify tCCD. def main_generator(dut): yield from dut.bm_drivers[2].read() yield from dut.bm_drivers[3].read() ready = {2: dut.bank_machines[2].cmd.ready, 3: dut.bank_machines[3].cmd.ready} # Wait for activate while not ((yield ready[2]) or (yield ready[3])): yield # Invalidate command that was ready if (yield ready[2]): yield from dut.bm_drivers[2].nop() else: yield from dut.bm_drivers[3].nop() yield # Wait for the second activate; start from 1 for the previous cycle cas_time = 1 while not ((yield ready[2]) or (yield ready[3])): cas_time += 1 yield self.assertEqual(cas_time, 3) dut = MultiplexerDUT(timing_settings=dict(tCCD=3)) generators = [ main_generator(dut), timeout_generator(50), ] run_simulation(dut, generators)
def test_fsm_anti_starvation(self): # Check that anti-starvation works according to controller settings. def main_generator(dut): yield from dut.bm_drivers[2].read() yield from dut.bm_drivers[3].write() # Go to WRITE # anti starvation does not work for 1st read, as read_time_en already starts as 1 # READ -> RTW -> WRITE while (yield from dut.fsm_state()) != "WRITE": yield # wait for write anti starvation for _ in range(dut.settings.write_time): self.assertEqual((yield from dut.fsm_state()), "WRITE") yield self.assertEqual((yield from dut.fsm_state()), "WTR") # WRITE -> WTR -> READ while (yield from dut.fsm_state()) != "READ": yield # Wait for read anti starvation for _ in range(dut.settings.read_time): self.assertEqual((yield from dut.fsm_state()), "READ") yield self.assertEqual((yield from dut.fsm_state()), "RTW") dut = MultiplexerDUT() generators = [ main_generator(dut), timeout_generator(100), ] run_simulation(dut, generators)
def test_steer_write_correct_phases(self): # Check that correct phases are being used during WRITE. def main_generator(dut): yield from dut.bm_drivers[2].write() yield from dut.bm_drivers[3].activate() while not (yield dut.bank_machines[2].cmd.ready): yield yield # fsm starts in READ for phase in range(dut.settings.phy.nphases): if phase == dut.settings.phy.wrphase: self.assertEqual((yield dut.dfi.phases[phase].bank), 2) elif phase == dut.settings.phy.wrcmdphase: self.assertEqual((yield dut.dfi.phases[phase].bank), 3) else: self.assertEqual((yield dut.dfi.phases[phase].bank), 0) dut = MultiplexerDUT() generators = [ main_generator(dut), timeout_generator(50), ] run_simulation(dut, generators)
def test_single_phase_cmd_req(self): # Verify that, for a single phase, commands are sent sequentially. def main_generator(dut): yield from dut.bm_drivers[2].write() yield from dut.bm_drivers[3].activate() ready = {2: dut.bank_machines[2].cmd.ready, 3: dut.bank_machines[3].cmd.ready} # Activate should appear first while not ((yield ready[2]) or (yield ready[3])): yield yield from dut.bm_drivers[3].nop() yield self.assertEqual((yield dut.dfi.phases[0].bank), 3) # Then write while not (yield ready[2]): yield yield from dut.bm_drivers[2].nop() yield self.assertEqual((yield dut.dfi.phases[0].bank), 2) dut = MultiplexerDUT(phy_settings=dict(nphases=1)) generators = [ main_generator(dut), timeout_generator(50), ] run_simulation(dut, generators)
def test_fsm_write_to_read_latency(self): # Verify the timing of WRITE to READ transition. def main_generator(dut): write_latency = math.ceil(dut.settings.phy.cwl / dut.settings.phy.nphases) wtr = dut.settings.timing.tWTR + write_latency + dut.settings.timing.tCCD or 0 expected = "w" + (wtr - 1) * ">" + "r" states = "" # Simulate until we are in WRITE yield from dut.bm_drivers[0].write() while (yield from dut.fsm_state()) != "WRITE": yield # Set read_available=1 yield from dut.bm_drivers[0].read() yield for _ in range(len(expected)): state = (yield from dut.fsm_state()) states += { "READ": "r", "WRITE": "w", }.get(state, ">") yield self.assertEqual(states, expected) dut = MultiplexerDUT() generators = [ main_generator(dut), timeout_generator(50), ] run_simulation(dut, generators)
def crossbar_stress_test(self, dut, ports, n_banks, n_ops, clocks=None): # Runs simulation with multiple masters writing and reading to multiple banks controller = ControllerStub( dut.interface, write_latency=dut.settings.phy.write_latency, read_latency=dut.settings.phy.read_latency) # Store data produced per master produced = defaultdict(list) prng = random.Random(42) def master(dut, driver, num): # Choose operation types based on port mode ops_choice = { "both": ["w", "r"], "write": ["w"], "read": ["r"], }[driver.port.mode] for i in range(n_ops): bank = prng.randrange(n_banks) # We will later distinguish data by its row address row = num col = 0x20 * num + i addr = dut.addr_port(bank=bank, row=row, col=col) addr_iface = dut.addr_iface(row=row, col=col) if prng.choice(ops_choice) == "w": yield from driver.write(addr, data=i) produced[num].append( self.W(bank, addr_iface, data=i, we=0xff)) else: yield from driver.read(addr) produced[num].append(self.R(bank, addr_iface, data=None)) yield from driver.wait_all() generators = defaultdict(list) for i, port in enumerate(ports): driver = NativePortDriver(port) generators[port.clock_domain].append(master(dut, driver, i)) generators[port.clock_domain].extend(driver.generators()) generators["sys"] += controller.generators() generators["sys"].append(timeout_generator(80 * n_ops)) sim_kwargs = {} if clocks is not None: sim_kwargs["clocks"] = clocks run_simulation(dut, generators, **sim_kwargs) # Split controller data by master, as this is what we want to compare consumed = defaultdict(list) for data in controller.data: master = data.addr >> (dut.settings.geom.colbits - dut.address_align) if isinstance(data, self.R): # Master couldn't know the data when it was sending data = data._replace(data=None) consumed[master].append(data) return produced, consumed, controller.data
def crossbar_test(self, dut, generators, timeout=100, **kwargs): # Runs simulation with a controller stub (passive generators) and user generators if not isinstance(generators, list): generators = [generators] controller = ControllerStub(dut.interface, write_latency=dut.settings.phy.write_latency, read_latency=dut.settings.phy.read_latency, **kwargs) generators += [*controller.generators(), timeout_generator(timeout)] run_simulation(dut, generators) return controller.data
def bankmachine_commands_test(self, dut, requests, generators=None): # Perform a test by simulating requests producer and return registered commands commands = [] def producer(dut): for req in requests: yield dut.bankmachine.req.addr.eq(req["addr"]) yield dut.bankmachine.req.we.eq(req["we"]) yield dut.bankmachine.req.valid.eq(1) yield while not (yield dut.bankmachine.req.ready): yield yield dut.bankmachine.req.valid.eq(0) for _ in range(req.get("delay", 0)): yield def req_consumer(dut): for req in requests: if req["we"]: signal = dut.bankmachine.req.wdata_ready else: signal = dut.bankmachine.req.rdata_valid while not (yield signal): yield yield @passive def cmd_consumer(dut): while True: while not (yield dut.bankmachine.cmd.valid): yield yield dut.bankmachine.cmd.ready.eq(1) yield commands.append((yield from dut.get_cmd())) yield dut.bankmachine.cmd.ready.eq(0) yield all_generators = [ producer(dut), req_consumer(dut), cmd_consumer(dut), timeout_generator(50 * len(requests)), ] if generators is not None: all_generators += [g(dut) for g in generators] run_simulation(dut, all_generators) return commands
def test_write_datapath(self): # Verify that data is transmitted from native interface to DFI. def main_generator(dut): yield from dut.bm_drivers[2].write() # 16bits * 2 (DDR) * 1 (phases) yield dut.interface.wdata.eq(0xbaadf00d) yield dut.interface.wdata_we.eq(0xf) while not (yield dut.bank_machines[2].cmd.ready): yield yield self.assertEqual((yield dut.dfi.phases[0].wrdata), 0xbaadf00d) self.assertEqual((yield dut.dfi.phases[0].wrdata_en), 1) self.assertEqual((yield dut.dfi.phases[0].address), 2) self.assertEqual((yield dut.dfi.phases[0].bank), 2) dut = MultiplexerDUT(phy_settings=dict(nphases=1)) generators = [ main_generator(dut), timeout_generator(50), ] run_simulation(dut, generators)
def test_correct_period_length(self): # Verify that period length is correct by measuring time between CSR changes. period_bits = 5 period = 2**period_bits n_per_period = {0: 3, 1: 6, 2: 9} timeline = {} for p, n in n_per_period.items(): for i in range(n): margin = 10 timeline[period * p + margin + i] = "write" def main_generator(dut): # Keep the values always up to date yield dut.bandwidth.update.re.eq(1) # Wait until we have the data from 1st period while (yield dut.bandwidth.nwrites.status) != 3: yield # Count time to next period cycles = 0 while (yield dut.bandwidth.nwrites.status) != 6: cycles += 1 yield self.assertEqual(cycles, period) dut = BandwidthDUT(period_bits=period_bits) cmd_driver = CommandDriver(dut.cmd) generators = [ main_generator(dut), cmd_driver.timeline_generator(timeline.items()), timeout_generator(period * 3), ] run_simulation(dut, generators)
def test_requests_from_multiple_bankmachines(self): # Check complex communication scenario with requests from multiple bank machines # The communication is greatly simplified - data path is completely ignored, no responses # from PHY are simulated. Each bank machine performs a sequence of requests, bank machines # are ordered randomly and the DFI command data is checked to verify if all the commands # have been sent if correct per-bank order. # Tequests sequence on given bank machines bm_sequences = { 0: "awwwwwwp", 1: "arrrrrrp", 2: "arwrwrwp", 3: "arrrwwwp", 4: "awparpawp", 5: "awwparrrrp", } # convert to lists to use .pop() bm_sequences = {bm_num: list(seq) for bm_num, seq in bm_sequences.items()} def main_generator(bank_machines, drivers): # work on a copy bm_seq = copy.deepcopy(bm_sequences) def non_empty(): return list(filter(lambda n: len(bm_seq[n]) > 0, bm_seq.keys())) # Artificially perform the work of LiteDRAMCrossbar by always picking only one request prng = random.Random(42) while len(non_empty()) > 0: # Pick random bank machine bm_num = prng.choice(non_empty()) # Set given request request_char = bm_seq[bm_num].pop(0) yield from drivers[bm_num].request(request_char) yield # Wait for ready while not (yield bank_machines[bm_num].cmd.ready): yield # Disable it yield from drivers[bm_num].nop() for _ in range(16): yield # Gather data on DFI DFISnapshot = namedtuple("DFICapture", ["cmd", "bank", "address", "wrdata_en", "rddata_en"]) dfi_snapshots = [] @passive def dfi_monitor(dfi): while True: # Capture current state of DFI lines phases = [] for i, p in enumerate(dfi.phases): # Transform cas/ras/we to command name cas_n, ras_n, we_n = (yield p.cas_n), (yield p.ras_n), (yield p.we_n) captured = {"cmd": dfi_cmd_to_char(cas_n, ras_n, we_n)} # Capture rest of fields for field in DFISnapshot._fields: if field != "cmd": captured[field] = (yield getattr(p, field)) phases.append(DFISnapshot(**captured)) dfi_snapshots.append(phases) yield dut = MultiplexerDUT() generators = [ main_generator(dut.bank_machines, dut.bm_drivers), dfi_monitor(dut.dfi), timeout_generator(200), ] run_simulation(dut, generators) # Check captured DFI data with the description for snap in dfi_snapshots: for i, phase_snap in enumerate(snap): if phase_snap.cmd == "_": continue # Distinguish bank machines by the bank number bank = phase_snap.bank # Find next command for the given bank cmd = bm_sequences[bank].pop(0) # Check if the captured data is correct self.assertEqual(phase_snap.cmd, cmd) if cmd in ["w", "r"]: # Addresses are artificially forced to bank numbers in drivers self.assertEqual(phase_snap.address, bank) if cmd == "w": self.assertEqual(phase_snap.wrdata_en, 1) if cmd == "r": self.assertEqual(phase_snap.rddata_en, 1)