2023-12-28 23:22:29 +01:00
|
|
|
from amaranth import *
|
|
|
|
|
from amaranth_boards.nexys4ddr import Nexys4DDRPlatform
|
|
|
|
|
from amaranth_boards.icestick import ICEStickPlatform
|
|
|
|
|
from manta import Manta
|
|
|
|
|
from manta.utils import *
|
|
|
|
|
import pytest
|
|
|
|
|
from random import randint
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IOCoreLoopbackTest(Elaboratable):
|
|
|
|
|
def __init__(self, platform, port):
|
|
|
|
|
self.platform = platform
|
|
|
|
|
self.port = port
|
|
|
|
|
|
|
|
|
|
self.config = self.platform_specific_config()
|
2024-01-14 21:51:52 +01:00
|
|
|
self.manta = Manta(self.config)
|
2023-12-28 23:22:29 +01:00
|
|
|
|
|
|
|
|
def platform_specific_config(self):
|
|
|
|
|
return {
|
|
|
|
|
"cores": {
|
|
|
|
|
"io_core": {
|
|
|
|
|
"type": "io",
|
|
|
|
|
"inputs": {"probe0": 1, "probe1": 2, "probe2": 8, "probe3": 20},
|
|
|
|
|
"outputs": {
|
|
|
|
|
"probe4": {"width": 1, "initial_value": 1},
|
|
|
|
|
"probe5": {
|
|
|
|
|
"width": 2,
|
|
|
|
|
"initial_value": 2,
|
|
|
|
|
},
|
|
|
|
|
"probe6": 8,
|
|
|
|
|
"probe7": {"width": 20, "initial_value": 65538},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"uart": {
|
|
|
|
|
"port": self.port,
|
|
|
|
|
"baudrate": 3e6,
|
|
|
|
|
"clock_freq": self.platform.default_clk_frequency,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-18 22:50:26 +01:00
|
|
|
def get_probe(self, name):
|
|
|
|
|
# This is a hack! And should be removed once the full Amaranth-native
|
|
|
|
|
# API is built out
|
|
|
|
|
for i in self.manta.io_core._inputs:
|
|
|
|
|
if i.name == name:
|
|
|
|
|
return i
|
|
|
|
|
|
|
|
|
|
for o in self.manta.io_core._outputs:
|
|
|
|
|
if o.name == name:
|
|
|
|
|
return o
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
2023-12-28 23:22:29 +01:00
|
|
|
def elaborate(self, platform):
|
|
|
|
|
m = Module()
|
2024-01-14 21:51:52 +01:00
|
|
|
m.submodules.manta = self.manta
|
2023-12-28 23:22:29 +01:00
|
|
|
|
|
|
|
|
uart_pins = platform.request("uart")
|
|
|
|
|
|
2024-02-18 22:50:26 +01:00
|
|
|
probe0 = self.get_probe("probe0")
|
|
|
|
|
probe1 = self.get_probe("probe1")
|
|
|
|
|
probe2 = self.get_probe("probe2")
|
|
|
|
|
probe3 = self.get_probe("probe3")
|
|
|
|
|
probe4 = self.get_probe("probe4")
|
|
|
|
|
probe5 = self.get_probe("probe5")
|
|
|
|
|
probe6 = self.get_probe("probe6")
|
|
|
|
|
probe7 = self.get_probe("probe7")
|
|
|
|
|
|
2023-12-28 23:22:29 +01:00
|
|
|
m.d.comb += [
|
2024-02-18 22:50:26 +01:00
|
|
|
probe0.eq(probe4),
|
|
|
|
|
probe1.eq(probe5),
|
|
|
|
|
probe2.eq(probe6),
|
|
|
|
|
probe3.eq(probe7),
|
2024-01-14 21:51:52 +01:00
|
|
|
self.manta.interface.rx.eq(uart_pins.rx.i),
|
|
|
|
|
uart_pins.tx.o.eq(self.manta.interface.tx),
|
2023-12-28 23:22:29 +01:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
return m
|
|
|
|
|
|
|
|
|
|
def build_and_program(self):
|
|
|
|
|
self.platform.build(self, do_program=True)
|
|
|
|
|
|
|
|
|
|
def verify_output_probe_initial_values(self):
|
|
|
|
|
"""
|
|
|
|
|
Test that all output probes take their expected initial values.
|
|
|
|
|
We can't really test for the same of input probes, since the
|
|
|
|
|
strobe register pulses every time the get_probe() method is called.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Test that all output probes take their initial values
|
|
|
|
|
inputs = self.config["cores"]["io_core"]["inputs"]
|
|
|
|
|
outputs = self.config["cores"]["io_core"]["outputs"]
|
|
|
|
|
|
|
|
|
|
for name, attrs in outputs.items():
|
2024-01-14 21:51:52 +01:00
|
|
|
actual = self.manta.io_core.get_probe(name)
|
2023-12-28 23:22:29 +01:00
|
|
|
|
|
|
|
|
if isinstance(attrs, dict):
|
|
|
|
|
if "initial_value" in attrs:
|
|
|
|
|
expected = attrs["initial_value"]
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
expected = 0
|
|
|
|
|
|
|
|
|
|
if actual != expected:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Output probe {name} took initial value of {actual} instead of {expected}."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def verify_probes_update(self):
|
|
|
|
|
"""
|
|
|
|
|
This design ties all the output probes to input probes, so this
|
|
|
|
|
test sets the outputs to random values, and verifies the inputs match
|
|
|
|
|
"""
|
|
|
|
|
inputs = self.config["cores"]["io_core"]["inputs"]
|
|
|
|
|
outputs = self.config["cores"]["io_core"]["outputs"]
|
|
|
|
|
|
|
|
|
|
# The config is specified in such a way that the first output is
|
|
|
|
|
# connected to the first output, the second output is connected
|
|
|
|
|
# to the second input, and so on...
|
|
|
|
|
for input, output in zip(inputs, outputs):
|
|
|
|
|
width = self.config["cores"]["io_core"]["inputs"][input]
|
|
|
|
|
value = randint(0, 2**width - 1)
|
|
|
|
|
|
2024-01-14 21:51:52 +01:00
|
|
|
self.manta.io_core.set_probe(output, value)
|
|
|
|
|
readback = self.manta.io_core.get_probe(input)
|
2023-12-28 23:22:29 +01:00
|
|
|
|
|
|
|
|
if readback != value:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"Reading {output} through {input} yielded {readback} instead of {value}!"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
print(
|
|
|
|
|
f"Reading {output} through {input} yielded {readback} as expected."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def verify(self):
|
|
|
|
|
self.build_and_program()
|
|
|
|
|
self.verify_output_probe_initial_values()
|
|
|
|
|
self.verify_probes_update()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(not xilinx_tools_installed(), reason="no toolchain installed")
|
|
|
|
|
def test_output_probe_initial_values_xilinx():
|
2024-01-08 06:39:44 +01:00
|
|
|
IOCoreLoopbackTest(Nexys4DDRPlatform(), "/dev/ttyUSB1").verify()
|
2023-12-28 23:22:29 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(not ice40_tools_installed(), reason="no toolchain installed")
|
|
|
|
|
def test_output_probe_initial_values_ice40():
|
2024-02-19 00:50:51 +01:00
|
|
|
IOCoreLoopbackTest(ICEStickPlatform(), "/dev/ttyUSB2").verify()
|