Skip to content

Commit

Permalink
Add payload executor test
Browse files Browse the repository at this point in the history
  • Loading branch information
mtdudek authored and tmichalak committed Aug 8, 2023
1 parent 4560197 commit 5815487
Showing 1 changed file with 340 additions and 0 deletions.
340 changes: 340 additions & 0 deletions tests/test_payload_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,100 @@ def refresher(self):
counter += 1
yield

class PayloadExecutorDDR5DUT(Module):
def __init__(self, payload,
data_width=128, scratchpad_depth=8, payload_depth=32, instruction_width=32,
bankbits=5, rowbits=18, colbits=10, nranks=1, dfi_databits=2*16, nphases=4, rdphase=2,
with_refresh=True, refresh_delay=3):
# store to be able to extract from dut later
self.params = locals()
self.payload = payload

assert len(payload) <= payload_depth, '{} vs {}'.format(len(payload), payload_depth)
self.mem_scratchpad = Memory(data_width, scratchpad_depth)
self.mem_payload = Memory(instruction_width, payload_depth, init=payload)
self.specials += self.mem_scratchpad, self.mem_payload

dfi_params = dict(addressbits=max(rowbits, colbits), bankbits=8, nranks=nranks,
databits=dfi_databits, nphases=nphases, memtype="DDR5", with_sub_channels=True, strobes=4)
self.refresher_reset = Signal()
self.submodules.dfii = DFIInjector(**dfi_params)
self.submodules.dfi_switch = DFISwitch(
with_refresh = with_refresh,
dfii = self.dfii,
refresher_reset = self.refresher_reset)

self.submodules.payload_executor = PayloadExecutor(
self.mem_payload, self.mem_scratchpad, self.dfi_switch,
nranks=nranks, bankbits=bankbits, rowbits=rowbits, colbits=colbits, rdphase=2)

self.dfi_history: list[HistoryEntry] = []
self.runtime_cycles = 0 # time when memory controller is disconnected
self.execution_cycles = 0 # time when actually executing the payload

def get_generators(self):
return [self.dfi_monitor(), self.cycles_counter(), self.refresher()]

@passive
def dfi_monitor(self, dfi=None):
if dfi is None:
dfi = self.dfii.ext_dfi
time = 0
while True:
for i, phase in enumerate(dfi.phases):
cas = 1 - (yield phase.cas_n)
ras = 1 - (yield phase.ras_n)
we = 1 - (yield phase.we_n)
entry = None
for op, desc in DFI_COMMANDS.items():
if (cas, ras, we) == (desc['cas'], desc['ras'], desc['we']):
cmd = DFICmd(cas=cas, ras=ras, we=we)
entry = HistoryEntry(time=time, phase=i, cmd=cmd)
assert entry is not None, 'Unknown DFI command: cas={}, ras={}, we={}'.format(cas, ras, we)
if entry.cmd.op_code != OpCode.NOOP: # omit NOOPs
self.dfi_history.append(entry)
yield
time += 1

@passive
def cycles_counter(self):
self.execution_cycles = 0
self.runtime_cycles = 0
while not (yield self.payload_executor.start):
yield
yield
while not (yield self.payload_executor.ready):
yield
self.runtime_cycles += 1
if (yield self.payload_executor.executing):
self.execution_cycles += 1

@passive
def refresher(self):
if not self.params['with_refresh']:
return

counter = 0
while True:
if (yield self.refresher_reset):
counter = 0
yield
else:
if counter == self.params['refresh_delay']:
counter = 0
yield self.dfii.slave.phases[0].cs_n.eq(0)
yield self.dfii.slave.phases[0].cas_n.eq(0)
yield self.dfii.slave.phases[0].ras_n.eq(0)
yield self.dfii.slave.phases[0].we_n.eq(1)
yield
yield self.dfii.slave.phases[0].cs_n.eq(1)
yield self.dfii.slave.phases[0].cas_n.eq(1)
yield self.dfii.slave.phases[0].ras_n.eq(1)
yield self.dfii.slave.phases[0].we_n.eq(1)
else:
counter += 1
yield

class TestPayloadExecutor(unittest.TestCase):
def run_payload(self, dut, **kwargs):
def generator(dut):
Expand Down Expand Up @@ -647,6 +741,252 @@ def generator(dut, switch_at):
dut.dfi_switch.add_csrs()
run_simulation(dut, [generator(dut, switch_at), *dut.get_generators()])

class TestPayloadExecutorDDR5(unittest.TestCase):
def run_payload(self, dut, **kwargs):
def generator(dut):
count = 0
yield dut.dfii._control.fields.mode_2n.eq(0)
yield dut.dfii._control.fields.reset_n.eq(1)
yield dut.payload_executor.start.eq(1)
yield
yield dut.payload_executor.start.eq(0)
yield

while not (yield dut.payload_executor.ready) and count < 100:
count += 1
yield

run_simulation(dut, [generator(dut), *dut.get_generators()], **kwargs)

def assert_history(self, history, op_codes):
history_ops = [entry.cmd.op_code for entry in history]
self.assertEqual(history_ops, op_codes)

def test_timeslice_0_noop_legal(self):
# Check that encoding NOOP with timeslice=0 is legal (STOP instruction)
Encoder(bankbits=5)(OpCode.NOOP, timeslice=0)

def test_timeslice_0_other_illegal(self):
# Check that encoding DFI instructions with timeslice=0 is results in an error
with self.assertRaises(AssertionError):
Encoder(bankbits=5)(OpCode.ACT, timeslice=0)

def test_payload_simple(self):
# Check that DFI instuctions in a simple payload are sent in correct order
encoder = Encoder(bankbits=5)
payload = [
encoder(OpCode.ACT, timeslice=10, address=encoder.address(bank=1, row=100)),
encoder(OpCode.READ, timeslice=3, address=encoder.address(bank=1, col=13)),
encoder(OpCode.PRE, timeslice=10, address=encoder.address(bank=1)),
encoder(OpCode.REF, timeslice=15),
]

dut = PayloadExecutorDDR5DUT(payload)
self.run_payload(dut)

# compare DFI history to what payload should yield
op_codes = [OpCode.ACT, OpCode.READ, OpCode.PRE, OpCode.REF]
self.assert_history(dut.dfi_history, op_codes)

def test_payload_loop(self):
# Check that LOOP is executed correctly
encoder = Encoder(bankbits=5)
payload = [
encoder(OpCode.ACT, timeslice=10, address=encoder.address(bank=0, row=100)),
encoder(OpCode.READ, timeslice=30, address=encoder.address(bank=0, col=200)),
encoder(OpCode.LOOP, count=8 - 1, jump=1), # to READ col=200
encoder(OpCode.PRE, timeslice=40, address=encoder.address(bank=0)),
encoder(OpCode.REF, timeslice=50),
encoder(OpCode.REF, timeslice=50),
encoder(OpCode.LOOP, count=5 - 1, jump=2), # to first REF
]

dut = PayloadExecutorDDR5DUT(payload)
self.run_payload(dut, vcd_name="test_payload_loop.vcd")

op_codes = [OpCode.ACT] + 8*[OpCode.READ] + [OpCode.PRE] + 5*2*[OpCode.REF]
self.assert_history(dut.dfi_history, op_codes)

def test_stop(self):
# Check that STOP terminates execution
encoder = Encoder(bankbits=5)
payload = [
encoder(OpCode.ACT, timeslice=10, address=encoder.address(bank=1, row=100)),
encoder(OpCode.READ, timeslice=10, address=encoder.address(bank=1, col=13)),
encoder(OpCode.READ, timeslice=30, address=encoder.address(bank=1, col=20)),
encoder(OpCode.NOOP, timeslice=0), # STOP instruction
encoder(OpCode.READ, timeslice=30, address=encoder.address(bank=1, col=20)),
encoder(OpCode.READ, timeslice=30, address=encoder.address(bank=1, col=20)),
encoder(OpCode.PRE, timeslice=10, address=encoder.address(bank=1)),
]

dut = PayloadExecutorDDR5DUT(payload)
self.run_payload(dut)

op_codes = [OpCode.ACT] + 2*[OpCode.READ]
self.assert_history(dut.dfi_history, op_codes)

def test_execution_cycles_with_stop(self):
# Check that execution time is correct with STOP instruction
encoder = Encoder(bankbits=5)
payload = [
encoder(OpCode.ACT, timeslice=1, address=encoder.address(bank=1, row=100)),
encoder(OpCode.READ, timeslice=1, address=encoder.address(bank=1, col=20)),
encoder(OpCode.PRE, timeslice=1, address=encoder.address(bank=1)),
encoder(OpCode.NOOP, timeslice=0), # STOP, takes 1 cycle
encoder(OpCode.ACT, timeslice=10, address=encoder.address(bank=1, row=100)),
]

dut = PayloadExecutorDDR5DUT(payload)
self.run_payload(dut)

op_codes = [OpCode.ACT, OpCode.READ, OpCode.PRE]
self.assert_history(dut.dfi_history, op_codes)
self.assertEqual(dut.execution_cycles, 4)

def test_execution_cycles_default_stop(self):
# Check execution time with no explicit STOP, but rest of memory is filled with zeros (=STOP)
encoder = Encoder(bankbits=5)
payload = [
encoder(OpCode.ACT, timeslice=1, address=encoder.address(bank=1, row=100)),
encoder(OpCode.READ, timeslice=1, address=encoder.address(bank=1, col=20)),
encoder(OpCode.PRE, timeslice=1, address=encoder.address(bank=1)),
]

dut = PayloadExecutorDDR5DUT(payload)
self.run_payload(dut)

op_codes = [OpCode.ACT, OpCode.READ, OpCode.PRE]
self.assert_history(dut.dfi_history, op_codes)
self.assertEqual(dut.execution_cycles, 4)

def test_execution_cycles_no_stop(self):
# Check execution time when there is no STOP instruction (rest of memory filled with NOOPs)
encoder = Encoder(bankbits=5)
payload = [
encoder(OpCode.ACT, timeslice=1, address=encoder.address(bank=1, row=100)),
encoder(OpCode.READ, timeslice=1, address=encoder.address(bank=1, col=20)),
encoder(OpCode.PRE, timeslice=1, address=encoder.address(bank=1)),
]

depth = 16
payload += [encoder(OpCode.NOOP, timeslice=1)] * (depth - len(payload))
dut = PayloadExecutorDDR5DUT(payload, payload_depth=depth)
self.run_payload(dut)

op_codes = [OpCode.ACT, OpCode.READ, OpCode.PRE]
self.assert_history(dut.dfi_history, op_codes)
self.assertEqual(dut.execution_cycles, depth)

def test_execution_cycles_longer(self):
# Check execution time with timeslices longer than 1
encoder = Encoder(bankbits=5)
payload = [
encoder.I(OpCode.ACT, timeslice=7, address=encoder.address(bank=1, row=100)),
encoder.I(OpCode.READ, timeslice=3, address=encoder.address(bank=1, col=20)),
encoder.I(OpCode.PRE, timeslice=5, address=encoder.address(bank=1)),
encoder.I(OpCode.REF, timeslice=10),
encoder.I(OpCode.NOOP, timeslice=0), # STOP
]

dut = PayloadExecutorDDR5DUT(encoder(payload))
self.run_payload(dut)

op_codes = [OpCode.ACT, OpCode.READ, OpCode.PRE, OpCode.REF]
self.assert_history(dut.dfi_history, op_codes)
self.assertEqual(dut.execution_cycles, sum(max(1, i.timeslice) for i in payload))

def test_execution_refresh_delay(self):
# Check that payload execution is started after refresh command
encoder = Encoder(bankbits=5)
payload = [
encoder.I(OpCode.ACT, timeslice=9, address=encoder.address(bank=1, row=100)),
encoder.I(OpCode.PRE, timeslice=10, address=encoder.address(bank=1)),
encoder.I(OpCode.NOOP, timeslice=0), # STOP
]
switch_latency = 1
for refresh_delay in [0, 2, 4, 11]:
with self.subTest(refresh_delay=refresh_delay):
dut = PayloadExecutorDDR5DUT(encoder(payload), refresh_delay=refresh_delay)
self.run_payload(dut)

op_codes = [OpCode.ACT, OpCode.PRE]
self.assert_history(dut.dfi_history, op_codes)
self.assertEqual(dut.execution_cycles, 20)
self.assertEqual(dut.runtime_cycles, 20 + max(1, refresh_delay) + switch_latency)

def test_refresh_counter(self):
def generator(dut):
# wait for some refresh commands to be issued by MC
for _ in range(45):
yield

# start execution, this should wait for the next refresh, then latch refresh count
yield dut.payload_executor.start.eq(1)
yield
yield dut.payload_executor.start.eq(0)
yield

while not (yield dut.payload_executor.ready):
yield

# read refresh count CSR twice
at_transition = (yield from dut.dfi_switch._refresh_count.read())
yield from dut.dfi_switch._refresh_update.write(1)
yield
forced = (yield from dut.dfi_switch._refresh_count.read())
yield

# refreshes during waiting time, +1 between start.eq(1) and actual transition
self.assertEqual(at_transition, 4+1)
self.assertEqual(forced, at_transition + 3) # for payload

encoder = Encoder(bankbits=5)
payload = [
encoder.I(OpCode.NOOP, timeslice=2),
encoder.I(OpCode.REF, timeslice=8),
encoder.I(OpCode.REF, timeslice=8),
encoder.I(OpCode.REF, timeslice=8),
encoder.I(OpCode.NOOP, timeslice=0), # STOP
]

dut = PayloadExecutorDDR5DUT(encoder(payload), refresh_delay=10-1)
dut.dfi_switch.add_csrs()
run_simulation(dut, [generator(dut), *dut.get_generators()],
vcd_name=f"test_refresh_counter.vcd")

def test_switch_at_refresh(self):
def generator(dut, switch_at):
yield from dut.dfi_switch._at_refresh.write(switch_at)
self.assertEqual((yield dut.dfi_switch.refresh_counter.counter), 0)

# start execution, this should wait for the next refresh, then latch refresh count
yield dut.payload_executor.start.eq(1)
yield
yield dut.payload_executor.start.eq(0)
yield

while not (yield dut.payload_executor.ready):
yield

# +1 for payload
self.assertEqual((yield dut.dfi_switch.refresh_counter.counter), switch_at + 1)

encoder = Encoder(bankbits=5)
payload = [
encoder.I(OpCode.NOOP, timeslice=10),
encoder.I(OpCode.REF, timeslice=8),
encoder.I(OpCode.NOOP, timeslice=10),
encoder.I(OpCode.NOOP, timeslice=0), # STOP
]

for switch_at in [5, 7, 10]:
with self.subTest(switch_at=switch_at):
dut = PayloadExecutorDDR5DUT(encoder(payload), refresh_delay=10-1)
dut.dfi_switch.add_csrs()
run_simulation(dut, [generator(dut, switch_at), *dut.get_generators()],
vcd_name=f"test_switch_at_refresh_{switch_at}.vcd")

# Interactive tests --------------------------------------------------------------------------------

def run_payload_executor(dut: PayloadExecutorDUT, *, print_period=1):
Expand Down

0 comments on commit 5815487

Please sign in to comment.