mirror of
https://github.com/enjoy-digital/litedram.git
synced 2025-01-04 09:52:25 -05:00
test: add frontend.adaptation tests for different conversion ratios
This commit is contained in:
parent
ebdbcacc1d
commit
1f8868e6e9
2 changed files with 289 additions and 93 deletions
154
test/common.py
154
test/common.py
|
@ -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
|
||||
|
|
|
@ -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"])
|
||||
|
|
Loading…
Reference in a new issue