2021-05-25 10:42:22 -04:00
|
|
|
#
|
|
|
|
# This file is part of LiteDRAM.
|
|
|
|
#
|
|
|
|
# Copyright (c) 2021 Antmicro <www.antmicro.com>
|
|
|
|
# SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
|
|
|
import unittest
|
|
|
|
import itertools
|
|
|
|
|
|
|
|
from migen import *
|
|
|
|
|
2021-06-01 06:46:21 -04:00
|
|
|
from litedram.phy.utils import Serializer, Deserializer, Latency, chunks, bit, ConstBitSlip
|
2021-05-25 10:42:22 -04:00
|
|
|
|
2021-05-31 05:00:17 -04:00
|
|
|
from test.phy_common import run_simulation
|
2021-05-25 10:42:22 -04:00
|
|
|
|
|
|
|
|
|
|
|
class TestSimSerializers(unittest.TestCase):
|
|
|
|
def run_simulation(self, dut, generators, **kwargs):
|
|
|
|
clocks = {
|
|
|
|
"sys": (64, 31),
|
|
|
|
"sys_11_25": (64, 29), # aligned to sys8x_90 (phase shift of 11.25)
|
|
|
|
"sys2x": (32, 15),
|
|
|
|
"sys8x": ( 8, 3),
|
|
|
|
"sys8x_ddr": ( 4, 1),
|
|
|
|
"sys8x_90": ( 8, 1),
|
|
|
|
"sys8x_90_ddr": ( 4, 3),
|
|
|
|
}
|
|
|
|
run_simulation(dut, generators, clocks, **kwargs)
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def data_generator(i, datas):
|
|
|
|
for data in datas:
|
|
|
|
yield i.eq(data)
|
|
|
|
yield
|
|
|
|
yield i.eq(0)
|
|
|
|
yield
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def data_checker(o, datas, n, latency, yield1=False):
|
|
|
|
if yield1:
|
|
|
|
yield
|
|
|
|
for _ in range(latency):
|
|
|
|
yield
|
|
|
|
for _ in range(n):
|
|
|
|
datas.append((yield o))
|
|
|
|
yield
|
|
|
|
yield
|
|
|
|
|
|
|
|
def serializer_test(self, *, data_width, datas, clk, clkdiv, latency, clkgen=None, clkcheck=None, **kwargs):
|
|
|
|
clkgen = clkgen if clkgen is not None else clkdiv
|
|
|
|
clkcheck = clkcheck if clkcheck is not None else clk
|
|
|
|
|
|
|
|
received = []
|
|
|
|
dut = Serializer(clk=clk, clkdiv=clkdiv, i_dw=data_width, o_dw=1)
|
|
|
|
generators = {
|
|
|
|
clkgen: self.data_generator(dut.i, datas),
|
|
|
|
clkcheck: self.data_checker(dut.o, received, n=len(datas) * data_width, latency=latency * data_width, yield1=True),
|
|
|
|
}
|
|
|
|
self.run_simulation(dut, generators, **kwargs)
|
|
|
|
|
|
|
|
received = list(chunks(received, data_width))
|
|
|
|
datas = [[bit(i, d) for i in range(data_width)] for d in datas]
|
|
|
|
self.assertEqual(received, datas)
|
|
|
|
|
|
|
|
def deserializer_test(self, *, data_width, datas, clk, clkdiv, latency, clkgen=None, clkcheck=None, **kwargs):
|
|
|
|
clkgen = clkgen if clkgen is not None else clk
|
|
|
|
clkcheck = clkcheck if clkcheck is not None else clkdiv
|
|
|
|
|
|
|
|
datas = [[bit(i, d) for i in range(data_width)] for d in datas]
|
|
|
|
|
|
|
|
received = []
|
|
|
|
dut = Deserializer(clk=clk, clkdiv=clkdiv, i_dw=1, o_dw=data_width)
|
|
|
|
generators = {
|
|
|
|
clkgen: self.data_generator(dut.i, itertools.chain(*datas)),
|
|
|
|
clkcheck: self.data_checker(dut.o, received, n=len(datas), latency=latency),
|
|
|
|
}
|
|
|
|
|
|
|
|
self.run_simulation(dut, generators, **kwargs)
|
|
|
|
|
|
|
|
received = [[bit(i, d) for i in range(data_width)] for d in received]
|
|
|
|
self.assertEqual(received, datas)
|
|
|
|
|
|
|
|
DATA_8 = [0b11001100, 0b11001100, 0b00110011, 0b00110011, 0b10101010]
|
|
|
|
DATA_16 = [0b1100110011001100, 0b0011001100110011, 0b0101010101010101]
|
|
|
|
|
|
|
|
ARGS_8 = dict(
|
|
|
|
data_width = 8,
|
|
|
|
datas = DATA_8,
|
|
|
|
clkdiv = "sys",
|
|
|
|
clk = "sys8x",
|
|
|
|
latency = Serializer.LATENCY,
|
|
|
|
)
|
|
|
|
|
|
|
|
ARGS_16 = dict(
|
|
|
|
data_width = 16,
|
|
|
|
datas = DATA_16,
|
|
|
|
clkdiv = "sys",
|
|
|
|
clk = "sys8x_ddr",
|
|
|
|
latency = Serializer.LATENCY,
|
|
|
|
)
|
|
|
|
|
|
|
|
def _s(default, **kwargs):
|
|
|
|
def test(self):
|
|
|
|
new = default.copy()
|
|
|
|
new.update(kwargs)
|
|
|
|
self.serializer_test(**new)
|
|
|
|
return test
|
|
|
|
|
|
|
|
def _d(default, **kwargs):
|
|
|
|
def test(self):
|
|
|
|
new = default.copy()
|
|
|
|
new["latency"] = Deserializer.LATENCY
|
|
|
|
new.update(kwargs)
|
|
|
|
self.deserializer_test(**new)
|
|
|
|
return test
|
|
|
|
|
|
|
|
test_sim_serializer_8 = _s(ARGS_8)
|
|
|
|
test_sim_serializer_8_phase90 = _s(ARGS_8, clkdiv="sys_11_25", clk="sys8x_90")
|
|
|
|
# when clkgen and clkdiv are not phase aligned (clkdiv is delayed), there will be lower latency
|
|
|
|
test_sim_serializer_8_phase90_gen0 = _s(ARGS_8, clkdiv="sys_11_25", clk="sys8x_90", clkgen="sys", latency=Serializer.LATENCY - 1)
|
|
|
|
test_sim_serializer_8_phase90_check0 = _s(ARGS_8, clkdiv="sys_11_25", clk="sys8x_90", clkcheck="sys8x")
|
|
|
|
|
|
|
|
test_sim_serializer_16 = _s(ARGS_16)
|
|
|
|
test_sim_serializer_16_phase90 = _s(ARGS_16, clkdiv="sys_11_25", clk="sys8x_90_ddr")
|
|
|
|
test_sim_serializer_16_phase90_gen0 = _s(ARGS_16, clkdiv="sys_11_25", clk="sys8x_90_ddr", clkgen="sys", latency=Serializer.LATENCY - 1)
|
|
|
|
test_sim_serializer_16_phase90_check0 = _s(ARGS_16, clkdiv="sys_11_25", clk="sys8x_90_ddr", clkcheck="sys8x_ddr")
|
|
|
|
|
|
|
|
# for phase aligned clocks the latency will be bigger (preferably avoid phase aligned reading?)
|
|
|
|
test_sim_deserializer_8 = _d(ARGS_8, latency=Deserializer.LATENCY + 1)
|
|
|
|
test_sim_deserializer_8_check90 = _d(ARGS_8, clkcheck="sys_11_25")
|
|
|
|
test_sim_deserializer_8_gen90_check90 = _d(ARGS_8, clkcheck="sys_11_25", clkgen="sys8x_90")
|
|
|
|
test_sim_deserializer_8_phase90 = _d(ARGS_8, clkdiv="sys_11_25", clk="sys8x_90", latency=Deserializer.LATENCY + 1)
|
|
|
|
test_sim_deserializer_8_phase90_check0 = _d(ARGS_8, clkdiv="sys_11_25", clk="sys8x_90", clkcheck="sys", latency=Deserializer.LATENCY + 1)
|
|
|
|
|
|
|
|
test_sim_deserializer_16 = _d(ARGS_16, latency=Deserializer.LATENCY + 1)
|
|
|
|
test_sim_deserializer_16_check90 = _d(ARGS_16, clkcheck="sys_11_25")
|
|
|
|
test_sim_deserializer_16_gen90_check90 = _d(ARGS_16, clkcheck="sys_11_25", clkgen="sys8x_90_ddr")
|
|
|
|
test_sim_deserializer_16_phase90 = _d(ARGS_16, clkdiv="sys_11_25", clk="sys8x_90_ddr", latency=Deserializer.LATENCY + 1)
|
|
|
|
test_sim_deserializer_16_phase90_check0 = _d(ARGS_16, clkdiv="sys_11_25", clk="sys8x_90_ddr", clkcheck="sys", latency=Deserializer.LATENCY + 1)
|
|
|
|
|
|
|
|
|
|
|
|
class LatencyTests(unittest.TestCase):
|
|
|
|
def test_latency_default_zero(self):
|
|
|
|
l = Latency()
|
|
|
|
self.assertEqual((l._sys.numerator, l._sys.denominator), (0, 1))
|
|
|
|
|
|
|
|
def test_latency_sys_init(self):
|
|
|
|
for n in [0, 1, 2, 4, 9]:
|
|
|
|
l = Latency(sys=n)
|
|
|
|
self.assertEqual((l._sys.numerator, l._sys.denominator), (n, 1))
|
|
|
|
|
|
|
|
def test_latency_get_sys(self):
|
|
|
|
l = Latency()
|
|
|
|
self.assertEqual(l.sys, 0)
|
|
|
|
for n in [0, 1, 2, 4, 9]:
|
|
|
|
l = Latency(sys=n)
|
|
|
|
self.assertEqual(l.sys, n)
|
|
|
|
|
|
|
|
def test_latency_get_sysNx(self):
|
|
|
|
l = Latency()
|
|
|
|
self.assertEqual(l.sys2x, 0)
|
|
|
|
for n in [0, 1, 2, 4, 9]:
|
|
|
|
l = Latency(sys=n)
|
|
|
|
self.assertEqual(l.sys2x, 2*n)
|
|
|
|
self.assertEqual(l.sys3x, 3*n)
|
|
|
|
|
|
|
|
def test_latency_set_sysNx(self):
|
|
|
|
for n in [3, 6, 9]:
|
|
|
|
l = Latency(sys3x=n)
|
|
|
|
self.assertEqual(l.sys, n // 3)
|
|
|
|
|
|
|
|
def test_latency_add(self):
|
|
|
|
l = Latency(sys=2) + Latency(sys2x=3)
|
|
|
|
self.assertEqual(l.sys2x, 4+3)
|
|
|
|
l = Latency(sys2x=2, sys4x=3) + Latency(sys4x=1)
|
|
|
|
self.assertEqual(l.sys4x, 8)
|
|
|
|
self.assertEqual(l.sys2x, 4)
|
|
|
|
self.assertEqual(l.sys, 2)
|
|
|
|
|
|
|
|
def test_latency_error_on_get_fraction(self):
|
|
|
|
l = Latency(sys=1, sys2x=1)
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
l.sys # would have to be 1.5
|
|
|
|
l = Latency(sys3x=1, sys2x=1)
|
|
|
|
for attr in "sys sys2x sys3x sys4x sys5x".split():
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
getattr(l, attr)
|
|
|
|
l.sys6x # ok
|
2021-06-01 06:46:21 -04:00
|
|
|
|
|
|
|
class TestConstBitslip(unittest.TestCase):
|
|
|
|
class Dut(Module):
|
|
|
|
def __init__(self, dw, **kwargs):
|
|
|
|
self.i = Signal(dw)
|
|
|
|
self.o = Signal(dw)
|
|
|
|
bs = ConstBitSlip(dw, **kwargs)
|
|
|
|
self.submodules += bs
|
|
|
|
self.comb += [
|
|
|
|
self.o.eq(bs.o),
|
|
|
|
bs.i.eq(self.i),
|
|
|
|
]
|
|
|
|
|
|
|
|
def test_register(self):
|
|
|
|
outputs = {
|
|
|
|
0: [0b0011, 0b0000],
|
|
|
|
1: [0b0110, 0b0000],
|
|
|
|
2: [0b1100, 0b0000],
|
|
|
|
3: [0b1000, 0b0001],
|
|
|
|
}
|
|
|
|
|
|
|
|
for slp, out in outputs.items():
|
|
|
|
with self.subTest(slp=slp):
|
|
|
|
def generator(dut):
|
|
|
|
yield dut.i.eq(0b0011)
|
|
|
|
yield
|
|
|
|
self.assertEqual((yield dut.o), 0)
|
|
|
|
yield dut.i.eq(0)
|
|
|
|
yield
|
|
|
|
self.assertEqual((yield dut.o), out[0])
|
|
|
|
yield
|
|
|
|
self.assertEqual((yield dut.o), out[1])
|
|
|
|
yield
|
|
|
|
self.assertEqual((yield dut.o), 0)
|
|
|
|
|
|
|
|
dut = self.Dut(dw=4, slp=slp, cycles=1)
|
|
|
|
run_simulation(dut, generator(dut))
|
|
|
|
|
|
|
|
def test_no_register(self):
|
|
|
|
outputs = {
|
|
|
|
0: [0b0011, 0b0000],
|
|
|
|
1: [0b0110, 0b0000],
|
|
|
|
2: [0b1100, 0b0000],
|
|
|
|
3: [0b1000, 0b0001],
|
|
|
|
}
|
|
|
|
|
|
|
|
for slp, out in outputs.items():
|
|
|
|
with self.subTest(slp=slp):
|
|
|
|
def generator(dut):
|
|
|
|
self.assertEqual((yield dut.o), 0)
|
|
|
|
yield dut.i.eq(0b0011)
|
|
|
|
yield
|
|
|
|
self.assertEqual((yield dut.o), out[0])
|
|
|
|
yield dut.i.eq(0)
|
|
|
|
yield
|
|
|
|
self.assertEqual((yield dut.o), out[1])
|
|
|
|
yield
|
|
|
|
self.assertEqual((yield dut.o), 0)
|
|
|
|
|
|
|
|
dut = self.Dut(dw=4, slp=slp, cycles=1, register=False)
|
|
|
|
run_simulation(dut, generator(dut))
|