test: add frontend.adaptation tests for different conversion ratios

This commit is contained in:
Jędrzej Boczar 2020-03-23 14:17:01 +01:00
parent ebdbcacc1d
commit 1f8868e6e9
2 changed files with 289 additions and 93 deletions

View file

@ -5,6 +5,7 @@
import os
import random
import itertools
from operator import or_
from migen import *
@ -24,6 +25,15 @@ def seed_to_data(seed, random=True, nbits=32):
return data
@passive
def timeout_generator(ticks):
# raise exception after given timeout effectively stopping simulation
# because of @passive, simulation can end even if this generator is still running
for _ in range(ticks):
yield
raise TimeoutError("Timeout after %d ticks" % ticks)
class DRAMMemory:
def __init__(self, width, depth, init=[]):
self.width = width
@ -311,6 +321,118 @@ class MemoryTestDataMixin:
0x00, # 0x19
]
),
"8bit_to_32bit": dict(
pattern=[
# address, data
(0x00, 0x00),
(0x01, 0x11),
(0x02, 0x22),
(0x03, 0x33),
(0x10, 0x44),
(0x11, 0x55),
(0x12, 0x66),
(0x13, 0x77),
(0x08, 0x88),
(0x09, 0x99),
(0x0a, 0xaa),
(0x0b, 0xbb),
(0x0c, 0xcc),
(0x0d, 0xdd),
(0x0e, 0xee),
(0x0f, 0xff),
],
expected=[
# data, address
0x33221100, # 0x00
0x00000000, # 0x04
0xbbaa9988, # 0x08
0xffeeddcc, # 0x0c
0x77665544, # 0x10
0x00000000, # 0x14
0x00000000, # 0x18
0x00000000, # 0x1c
]
),
"8bit_to_32bit_not_aligned": dict(
pattern=[
# address, data
(0x00, 0x00),
(0x05, 0x11),
(0x0a, 0x22),
(0x0f, 0x33),
(0x1d, 0x44),
(0x15, 0x55),
(0x13, 0x66),
(0x18, 0x77),
],
expected=[
# data, address
0x00000000, # 0x00
0x00001100, # 0x04
0x00220000, # 0x08
0x33000000, # 0x0c
0x66000000, # 0x10
0x00005500, # 0x14
0x00000077, # 0x18
0x00440000, # 0x1c
]
),
"32bit_to_256bit": dict(
pattern=[
# address, data
(0x00, 0x00000000),
(0x01, 0x11111111),
(0x02, 0x22222222),
(0x03, 0x33333333),
(0x04, 0x44444444),
(0x05, 0x55555555),
(0x06, 0x66666666),
(0x07, 0x77777777),
(0x10, 0x88888888),
(0x11, 0x99999999),
(0x12, 0xaaaaaaaa),
(0x13, 0xbbbbbbbb),
(0x14, 0xcccccccc),
(0x15, 0xdddddddd),
(0x16, 0xeeeeeeee),
(0x17, 0xffffffff),
],
expected=[
# data, address
0x7777777766666666555555554444444433333333222222221111111100000000, # 0x00
0x0000000000000000000000000000000000000000000000000000000000000000, # 0x20
0xffffffffeeeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa9999999988888888, # 0x40
0x0000000000000000000000000000000000000000000000000000000000000000, # 0x60
]
),
"32bit_to_256bit_not_aligned": dict(
pattern=[
# address, data
(0x00, 0x00000000),
(0x01, 0x11111111),
(0x02, 0x22222222),
(0x03, 0x33333333),
(0x04, 0x44444444),
(0x05, 0x55555555),
(0x06, 0x66666666),
(0x07, 0x77777777),
(0x14, 0x88888888),
(0x15, 0x99999999),
(0x16, 0xaaaaaaaa),
(0x17, 0xbbbbbbbb),
(0x18, 0xcccccccc),
(0x19, 0xdddddddd),
(0x1a, 0xeeeeeeee),
(0x1b, 0xffffffff),
],
expected=[
# data, address
0x7777777766666666555555554444444433333333222222221111111100000000, # 0x00
0x0000000000000000000000000000000000000000000000000000000000000000, # 0x20
0xbbbbbbbbaaaaaaaa999999998888888800000000000000000000000000000000, # 0x40
0x00000000000000000000000000000000ffffffffeeeeeeeeddddddddcccccccc, # 0x60
]
),
"32bit_not_aligned": dict(
pattern=[
# address, data
@ -373,7 +495,39 @@ class MemoryTestDataMixin:
),
"32bit_long_sequential": dict(pattern=[], expected=[0] * 64),
}
# 32bit_long_sequential
for i in range(32):
data["32bit_long_sequential"]["pattern"].append((i, 64 + i))
data["32bit_long_sequential"]["expected"][i] = 64 + i
def half_width(data, from_width):
half_mask = 2**(from_width//2) - 1
chunks = [(val & half_mask, (val >> from_width//2) & half_mask) for val in data]
return list(itertools.chain.from_iterable(chunks))
# down conversion
data["64bit_to_16bit"] = dict(
pattern=data["64bit_to_32bit"]["pattern"].copy(),
expected=half_width(data["64bit_to_32bit"]["expected"], from_width=32),
)
data["64bit_to_8bit"] = dict(
pattern=data["64bit_to_16bit"]["pattern"].copy(),
expected=half_width(data["64bit_to_16bit"]["expected"], from_width=16),
)
# up conversion
data["8bit_to_16bit"] = dict(
pattern=data["8bit_to_32bit"]["pattern"].copy(),
expected=half_width(data["8bit_to_32bit"]["expected"], from_width=32),
)
data["32bit_to_128bit"] = dict(
pattern=data["32bit_to_256bit"]["pattern"].copy(),
expected=half_width(data["32bit_to_256bit"]["expected"], from_width=256),
)
data["32bit_to_64bit"] = dict(
pattern=data["32bit_to_128bit"]["pattern"].copy(),
expected=half_width(data["32bit_to_128bit"]["expected"], from_width=128),
)
return data

View file

@ -16,7 +16,7 @@ from litex.gen.sim import *
class ConverterDUT(Module):
def __init__(self, user_data_width, native_data_width):
def __init__(self, user_data_width, native_data_width, mem_depth):
# write port and converter
self.write_user_port = LiteDRAMNativeWritePort(address_width=32, data_width=user_data_width)
self.write_crossbar_port = LiteDRAMNativeWritePort(address_width=32, data_width=native_data_width)
@ -32,12 +32,86 @@ class ConverterDUT(Module):
self.submodules += read_converter
# memory
self.memory = DRAMMemory(native_data_width, 128)
self.memory = DRAMMemory(native_data_width, mem_depth)
def write_up(self, address, data, we=None):
port = self.write_user_port
if we is None:
we = 2**port.wdata.we.nbits - 1
yield port.cmd.valid.eq(1)
yield port.cmd.we.eq(1)
yield port.cmd.addr.eq(address)
yield
while (yield port.cmd.ready) == 0:
yield
yield port.cmd.valid.eq(0)
yield
yield port.wdata.valid.eq(1)
yield port.wdata.data.eq(data)
yield port.wdata.we.eq(we)
yield
while (yield port.wdata.ready) == 0:
yield
yield port.wdata.valid.eq(0)
yield
def write_down(self, address, data, we=None):
# down converter must have all the data available along with cmd
# it will set user_port.cmd.ready only when it sends all input words
port = self.write_user_port
if we is None:
we = 2**port.wdata.we.nbits - 1
yield port.cmd.valid.eq(1)
yield port.cmd.we.eq(1)
yield port.cmd.addr.eq(address)
yield port.wdata.valid.eq(1)
yield port.wdata.data.eq(data)
yield port.wdata.we.eq(we)
yield
# ready goes up only after StrideConverter copied all words
while (yield port.cmd.ready) == 0:
yield
yield port.cmd.valid.eq(0)
yield
while (yield port.wdata.ready) == 0:
yield
yield port.wdata.valid.eq(0)
yield
def read(self, address, read_data=True):
port = self.read_user_port
yield port.cmd.valid.eq(1)
yield port.cmd.we.eq(0)
yield port.cmd.addr.eq(address)
yield
while (yield port.cmd.ready) == 0:
yield
yield port.cmd.valid.eq(0)
yield
if read_data:
while (yield port.rdata.valid) == 0:
yield
data = (yield port.rdata.data)
yield port.rdata.ready.eq(1)
yield
yield port.rdata.ready.eq(0)
yield
return data
class TestAdaptation(unittest.TestCase):
def test_up_converter(self):
write_data = [seed_to_data(i, nbits=32) for i in range(16)]
class TestAdaptation(MemoryTestDataMixin, unittest.TestCase):
def test_converter_down_ratio_must_be_integer(self):
with self.assertRaises(ValueError) as cm:
ConverterDUT(user_data_width=64, native_data_width=24, mem_depth=128)
self.assertIn("ratio must be an int", str(cm.exception).lower())
def test_converter_up_ratio_must_be_integer(self):
with self.assertRaises(ValueError) as cm:
ConverterDUT(user_data_width=32, native_data_width=48, mem_depth=128)
self.assertIn("ratio must be an int", str(cm.exception).lower())
def converter_readback_test(self, dut, pattern, mem_expected):
assert len(set(adr for adr, _ in pattern)) == len(pattern), "Pattern has duplicates!"
read_data = []
@passive
@ -48,102 +122,70 @@ class TestAdaptation(unittest.TestCase):
read_data.append((yield read_port.rdata.data))
yield
def main_generator(dut, pattern):
if dut.write_user_port.data_width > dut.write_crossbar_port.data_width:
write = dut.write_down
else:
write = dut.write_up
def main_generator(write_port, read_port):
# write
for i in range(16):
yield write_port.cmd.valid.eq(1)
yield write_port.cmd.we.eq(1)
yield write_port.cmd.addr.eq(i)
yield
while (yield write_port.cmd.ready) == 0:
yield
yield write_port.cmd.valid.eq(0)
yield
yield write_port.wdata.valid.eq(1)
yield write_port.wdata.data.eq(write_data[i])
yield write_port.wdata.we.eq(0b1111)
yield
while (yield write_port.wdata.ready) == 0:
yield
yield write_port.wdata.valid.eq(0)
yield
for adr, data in pattern:
yield from write(adr, data)
# read
for i in range(16):
yield read_port.cmd.valid.eq(1)
yield read_port.cmd.we.eq(0)
yield read_port.cmd.addr.eq(i)
yield
while (yield read_port.cmd.ready) == 0:
yield
yield read_port.cmd.valid.eq(0)
yield
# delay
for i in range(32):
yield
dut = ConverterDUT(user_data_width=32, native_data_width=128)
generators = [
main_generator(dut.write_user_port, dut.read_user_port),
read_handler(dut.read_user_port),
dut.memory.write_handler(dut.write_crossbar_port),
dut.memory.read_handler(dut.read_crossbar_port)
]
run_simulation(dut, generators)
self.assertEqual(write_data, read_data)
def test_down_converter(self):
write_data = [seed_to_data(i, nbits=64) for i in range(8)]
read_data = []
@passive
def read_handler(read_port):
yield read_port.rdata.ready.eq(1)
while True:
if (yield read_port.rdata.valid):
read_data.append((yield read_port.rdata.data))
yield
def main_generator(write_port, read_port):
# write
for i in range(8):
yield write_port.cmd.valid.eq(1)
yield write_port.cmd.we.eq(1)
yield write_port.cmd.addr.eq(i)
yield write_port.wdata.valid.eq(1)
yield write_port.wdata.data.eq(write_data[i])
yield write_port.wdata.we.eq(0xff)
yield
while (yield write_port.cmd.ready) == 0:
yield
while (yield write_port.wdata.ready) == 0:
yield
yield
# read
yield read_port.rdata.ready.eq(1)
for i in range(8):
yield read_port.cmd.valid.eq(1)
yield read_port.cmd.we.eq(0)
yield read_port.cmd.addr.eq(i)
yield
while (yield read_port.cmd.ready) == 0:
yield
yield read_port.cmd.valid.eq(0)
yield
for adr, _ in pattern:
yield from dut.read(adr, read_data=False)
# latency delay
for i in range(32):
for _ in range(32):
yield
dut = ConverterDUT(user_data_width=64, native_data_width=32)
generators = [
main_generator(dut.write_user_port, dut.read_user_port),
main_generator(dut, pattern),
read_handler(dut.read_user_port),
dut.memory.write_handler(dut.write_crossbar_port),
dut.memory.read_handler(dut.read_crossbar_port)
dut.memory.read_handler(dut.read_crossbar_port),
timeout_generator(5000),
]
run_simulation(dut, generators)
self.assertEqual(write_data, read_data)
self.assertEqual(dut.memory.mem, mem_expected)
self.assertEqual(read_data, [data for adr, data in pattern])
def test_converter_1to1(self):
data = self.pattern_test_data["64bit"]
dut = ConverterDUT(user_data_width=64, native_data_width=64, mem_depth=len(data["expected"]))
self.converter_readback_test(dut, data["pattern"], data["expected"])
def test_converter_2to1(self):
data = self.pattern_test_data["64bit_to_32bit"]
dut = ConverterDUT(user_data_width=64, native_data_width=32, mem_depth=len(data["expected"]))
self.converter_readback_test(dut, data["pattern"], data["expected"])
def test_converter_4to1(self):
data = self.pattern_test_data["32bit_to_8bit"]
dut = ConverterDUT(user_data_width=32, native_data_width=8, mem_depth=len(data["expected"]))
self.converter_readback_test(dut, data["pattern"], data["expected"])
def test_converter_8to1(self):
data = self.pattern_test_data["64bit_to_8bit"]
dut = ConverterDUT(user_data_width=64, native_data_width=8, mem_depth=len(data["expected"]))
self.converter_readback_test(dut, data["pattern"], data["expected"])
def test_converter_1to2(self):
data = self.pattern_test_data["8bit_to_16bit"]
dut = ConverterDUT(user_data_width=8, native_data_width=16, mem_depth=len(data["expected"]))
self.converter_readback_test(dut, data["pattern"], data["expected"])
def test_converter_1to4(self):
data = self.pattern_test_data["32bit_to_128bit"]
dut = ConverterDUT(user_data_width=32, native_data_width=128, mem_depth=len(data["expected"]))
self.converter_readback_test(dut, data["pattern"], data["expected"])
def test_converter_1to8(self):
data = self.pattern_test_data["32bit_to_256bit"]
dut = ConverterDUT(user_data_width=32, native_data_width=256, mem_depth=len(data["expected"]))
self.converter_readback_test(dut, data["pattern"], data["expected"])
# # TODO: implement case when user does not write all words (LiteDRAMNativeWritePortUpConverter)
# def test_converter_up_not_aligned(self):
# data = self.pattern_test_data["8bit_to_32bit_not_aligned"]
# dut = ConverterDUT(user_data_width=8, native_data_width=32, mem_depth=len(data["expected"]))
# self.converter_readback_test(dut, data["pattern"], data["expected"])