uart: add stall_interval parameter and tests
This commit is contained in:
parent
7f36072e90
commit
60da631fa7
|
|
@ -15,6 +15,7 @@ uart:
|
|||
port: "auto"
|
||||
baudrate: 3000000
|
||||
clock_freq: 100000000
|
||||
stall_interval: 16
|
||||
chunk_size: 256
|
||||
```
|
||||
Inside this configuration, the following parameters may be set:
|
||||
|
|
@ -25,7 +26,9 @@ Inside this configuration, the following parameters may be set:
|
|||
|
||||
- `clock_freq` _(required)_: The frequency of the clock provided to the `manta` module, in Hertz (Hz). This is used to calculate an appropriate prescaler onboard the FPGA to acheive the desired baudrate. Manta will throw an error if this clock frequency does not allow you to achieve your desired baudrate.
|
||||
|
||||
- `chunk_size` _(optional)_: The number of read requests to send at a time. Since the FPGA responds to read requests almost instantly, sending them in batches prevents the host machine's input buffer from overflowing. Defaults to 256, reduce this if Manta reports that bytes are being dropped.
|
||||
- `stall_interval` _(optional)_: The number of read requests to send before sending a stall byte. This prevents packets from being dropped if the FPGA's baudrate is less than the USB-Serial adapter's baudrate. This is usually caused by a mismatch between the clock frequency of the USB-Serial adapter and the FPGA fabric. See issue [#18](https://github.com/fischermoseley/manta/issues/18) on GitHub. Defaults to 16, reduce this if Manta reports that bytes are being dropped.
|
||||
|
||||
- `chunk_size` _(optional)_: The number of read requests to send at a time. Since the FPGA responds to read requests almost instantly, sending them in batches prevents the host machine's input buffer from overflowing. Defaults to 256, Reduce this if Manta reports that bytes are being dropped, and decreasing `stall_interval` did not work.
|
||||
|
||||
### Amaranth-Native Designs
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class UARTInterface(Elaboratable):
|
|||
the FPGA.
|
||||
"""
|
||||
|
||||
def __init__(self, port, baudrate, clock_freq, chunk_size=256):
|
||||
def __init__(self, port, baudrate, clock_freq, stall_interval=16, chunk_size=256):
|
||||
"""
|
||||
This function is the main mechanism for configuring a UART Interface
|
||||
in an Amaranth-native design.
|
||||
|
|
@ -37,11 +37,20 @@ class UARTInterface(Elaboratable):
|
|||
appropriate prescaler onboard the FPGA to acheive the desired
|
||||
baudrate.
|
||||
|
||||
stall_interval (Optional[int]): The number of read requests to send
|
||||
before sending a stall byte. This prevents packets from being
|
||||
dropped if the FPGA's baudrate is less than the USB-Serial
|
||||
adapter's baudrate. This is usually caused by a mismatch
|
||||
between the clock frequency of the USB-Serial adapter and the
|
||||
FPGA fabric. See issue #18 on GitHub. Reduce this if Manta
|
||||
reports that bytes are being dropped.
|
||||
|
||||
chunk_size (Optional[int]): The number of read requests to send at
|
||||
a time. Since the FPGA responds to read requests almost
|
||||
instantly, sending them in batches prevents the host machine's
|
||||
input buffer from overflowing. Reduce this if Manta reports
|
||||
that bytes are being dropped.
|
||||
that bytes are being dropped, and decreasing `stall_interval`
|
||||
did not work.
|
||||
|
||||
Raises:
|
||||
ValueError: The baudrate is not acheivable with the clock frequency
|
||||
|
|
@ -52,8 +61,9 @@ class UARTInterface(Elaboratable):
|
|||
self._port = port
|
||||
self._baudrate = baudrate
|
||||
self._clock_freq = clock_freq
|
||||
self._chunk_size = chunk_size
|
||||
self._clocks_per_baud = int(self._clock_freq // self._baudrate)
|
||||
self._chunk_size = chunk_size
|
||||
self._stall_interval = stall_interval
|
||||
self._check_config()
|
||||
|
||||
# Top-Level Ports
|
||||
|
|
@ -70,24 +80,27 @@ class UARTInterface(Elaboratable):
|
|||
baudrate = config.get("baudrate")
|
||||
|
||||
# Warn if unrecognized options have been given
|
||||
recognized_options = ["port", "clock_freq", "baudrate", "chunk_size"]
|
||||
recognized_options = [
|
||||
"port",
|
||||
"clock_freq",
|
||||
"baudrate",
|
||||
"chunk_size",
|
||||
"stall_interval",
|
||||
]
|
||||
for option in config:
|
||||
if option not in recognized_options:
|
||||
warn(
|
||||
f"Ignoring unrecognized option '{option}' in UART interface config."
|
||||
)
|
||||
|
||||
if "chunk_size" in config:
|
||||
return cls(port, baudrate, clock_freq, config["chunk_size"])
|
||||
|
||||
else:
|
||||
return cls(port, baudrate, clock_freq)
|
||||
return cls(**config)
|
||||
|
||||
def to_config(self):
|
||||
return {
|
||||
"port": self._port,
|
||||
"baudrate": self._baudrate,
|
||||
"clock_freq": self._clock_freq,
|
||||
"stall_interval": self._stall_interval,
|
||||
"chunk_size": self._chunk_size,
|
||||
}
|
||||
|
||||
|
|
@ -204,9 +217,10 @@ class UARTInterface(Elaboratable):
|
|||
# Encode addrs into read requests
|
||||
bytes_out = "".join([f"R{a:04X}\r\n" for a in addr_chunk])
|
||||
|
||||
# Add a \n after every 32 packets, see:
|
||||
# Add a \n after every N packets, see:
|
||||
# https://github.com/fischermoseley/manta/issues/18
|
||||
bytes_out = "\n".join(split_into_chunks(bytes_out, 7 * 32))
|
||||
bytes_out = split_into_chunks(bytes_out, 7 * self._stall_interval)
|
||||
bytes_out = "\n".join(bytes_out)
|
||||
|
||||
ser.write(bytes_out.encode("ascii"))
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
import os
|
||||
from random import getrandbits
|
||||
|
||||
import pytest
|
||||
from amaranth import *
|
||||
from amaranth.lib import io
|
||||
from amaranth_boards.icestick import ICEStickPlatform
|
||||
from amaranth_boards.nexys4ddr import Nexys4DDRPlatform
|
||||
|
||||
from manta import *
|
||||
from manta.utils import *
|
||||
|
||||
|
||||
class UARTBaudrateMismatchTest(Elaboratable):
|
||||
def __init__(self, platform, port, baudrate, percent_slowdown, stall_interval):
|
||||
self.platform = platform
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
self.slowed_baudrate = baudrate * (1 - (percent_slowdown / 100))
|
||||
self.stall_interval = stall_interval
|
||||
|
||||
def elaborate(self, platform):
|
||||
# Since we know that all the tests will be called only after the FPGA
|
||||
# is programmed, we can just push all the wiring into the elaborate
|
||||
# method, instead of needing to define Manta in the __init__() method
|
||||
|
||||
self.manta = manta = Manta()
|
||||
manta.cores.mem = MemoryCore(
|
||||
"bidirectional",
|
||||
width=16,
|
||||
depth=1024,
|
||||
)
|
||||
|
||||
# Set the RTL to a slower baudrate. Later, we'll manually set the
|
||||
# UARTInterface's _baudrate attribute back to the non-slowed baudrate
|
||||
manta.interface = UARTInterface(
|
||||
port=self.port,
|
||||
baudrate=self.slowed_baudrate,
|
||||
clock_freq=platform.default_clk_frequency,
|
||||
stall_interval=self.stall_interval,
|
||||
)
|
||||
|
||||
m = Module()
|
||||
m.submodules.manta = self.manta
|
||||
|
||||
uart_pins = platform.request("uart", dir={"tx": "-", "rx": "-"})
|
||||
m.submodules.uart_rx = uart_rx = io.Buffer("i", uart_pins.rx)
|
||||
m.submodules.uart_tx = uart_tx = io.Buffer("o", uart_pins.tx)
|
||||
|
||||
m.d.comb += self.manta.interface.rx.eq(uart_rx.i)
|
||||
m.d.comb += uart_tx.o.eq(self.manta.interface.tx)
|
||||
|
||||
return m
|
||||
|
||||
def build_and_program(self):
|
||||
self.platform.build(self, do_program=True)
|
||||
|
||||
def fill_memory(self):
|
||||
self.addrs = list(range(1024))
|
||||
self.datas = [getrandbits(16) for _ in self.addrs]
|
||||
self.manta.cores.mem.write(self.addrs, self.datas)
|
||||
|
||||
def verify_memory(self, batches):
|
||||
datas = self.manta.cores.mem.read(self.addrs * batches)
|
||||
if datas != (self.datas * batches):
|
||||
raise ValueError("Data written does not match data read back!")
|
||||
|
||||
def verify(self):
|
||||
self.build_and_program()
|
||||
|
||||
# Set the class back to the normal baudrate, which will be used when
|
||||
# the port is opened
|
||||
self.manta.interface._baudrate = self.baudrate
|
||||
|
||||
# Write a bunch of data
|
||||
self.fill_memory()
|
||||
|
||||
# Read it back a few times, see if it's good
|
||||
self.verify_memory(10)
|
||||
|
||||
|
||||
# Nexys4DDR Tests
|
||||
nexys4ddr_pass_cases = [
|
||||
(3e6, 0, 1024), # No clock mismatch, with no mitigation
|
||||
(3e6, 0, 16), # No clock mismatch, with mitigation
|
||||
(3e6, 1, 16), # Light clock mismatch, with light mitigation
|
||||
(3e6, 2, 7), # Heavy clock mismatch, with heavy mitigation
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(not xilinx_tools_installed(), reason="no toolchain installed")
|
||||
@pytest.mark.parametrize(
|
||||
"baudrate, percent_slowdown, stall_interval", nexys4ddr_pass_cases
|
||||
)
|
||||
def test_baudrate_mismatch_xilinx_passes(baudrate, percent_slowdown, stall_interval):
|
||||
UARTBaudrateMismatchTest(
|
||||
platform=Nexys4DDRPlatform(),
|
||||
port=os.environ["NEXYS4DDR_PORT"],
|
||||
baudrate=baudrate,
|
||||
percent_slowdown=percent_slowdown,
|
||||
stall_interval=stall_interval,
|
||||
).verify()
|
||||
|
||||
|
||||
nexys4ddr_fail_cases = [
|
||||
(3e6, 1, 1024), # Light clock mismatch, no mitigation
|
||||
(3e6, 2, 1024), # Heavy clock mismatch, no mitigation
|
||||
(3e6, 2, 16), # Heavy clock mismatch, light mitigation
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(not xilinx_tools_installed(), reason="no toolchain installed")
|
||||
@pytest.mark.parametrize(
|
||||
"baudrate, percent_slowdown, stall_interval", nexys4ddr_fail_cases
|
||||
)
|
||||
def test_baudrate_mismatch_xilinx_fails(baudrate, percent_slowdown, stall_interval):
|
||||
with pytest.raises(ValueError, match="Only got"):
|
||||
UARTBaudrateMismatchTest(
|
||||
platform=Nexys4DDRPlatform(),
|
||||
port=os.environ["NEXYS4DDR_PORT"],
|
||||
baudrate=baudrate,
|
||||
percent_slowdown=percent_slowdown,
|
||||
stall_interval=stall_interval,
|
||||
).verify()
|
||||
|
||||
|
||||
# IceStick Tests
|
||||
ice40_pass_cases = [
|
||||
(115200, 0, 1024), # No clock mismatch, with no mitigation
|
||||
(115200, 0, 16), # No clock mismatch, with mitigation
|
||||
(115200, 1, 16), # Light clock mismatch, with light mitigation
|
||||
(115200, 2, 7), # Heavy clock mismatch, with heavy mitigation
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(not ice40_tools_installed(), reason="no toolchain installed")
|
||||
@pytest.mark.parametrize("baudrate, percent_slowdown, stall_interval", ice40_pass_cases)
|
||||
def test_baudrate_mismatch_ice40_passes(baudrate, percent_slowdown, stall_interval):
|
||||
UARTBaudrateMismatchTest(
|
||||
platform=ICEStickPlatform(),
|
||||
port=os.environ["ICESTICK_PORT"],
|
||||
baudrate=baudrate,
|
||||
percent_slowdown=percent_slowdown,
|
||||
stall_interval=stall_interval,
|
||||
).verify()
|
||||
|
||||
|
||||
ice40_fail_cases = [
|
||||
(115200, 1, 1024), # Light clock mismatch, no mitigation
|
||||
(115200, 2, 1024), # Heavy clock mismatch, no mitigation
|
||||
(115200, 2, 16), # Heavy clock mismatch, light mitigation
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(not ice40_tools_installed(), reason="no toolchain installed")
|
||||
@pytest.mark.parametrize("baudrate, percent_slowdown, stall_interval", ice40_fail_cases)
|
||||
def test_baudrate_mismatch_ice40_fails(baudrate, percent_slowdown, stall_interval):
|
||||
with pytest.raises(ValueError, match="Only got"):
|
||||
UARTBaudrateMismatchTest(
|
||||
platform=ICEStickPlatform(),
|
||||
port=os.environ["ICESTICK_PORT"],
|
||||
baudrate=baudrate,
|
||||
percent_slowdown=percent_slowdown,
|
||||
stall_interval=stall_interval,
|
||||
).verify()
|
||||
Loading…
Reference in New Issue