diff --git a/software/glasgow/applet/interface/uart/__init__.py b/software/glasgow/applet/interface/uart/__init__.py index c7f81bfc9..e0e961172 100644 --- a/software/glasgow/applet/interface/uart/__init__.py +++ b/software/glasgow/applet/interface/uart/__init__.py @@ -10,7 +10,7 @@ from glasgow.support.arepl import AsyncInteractiveConsole from glasgow.support.logging import dump_hex from glasgow.support.endpoint import ServerEndpoint -from glasgow.gateware.uart import UART +from glasgow.gateware.uart import ExternalUART from glasgow.abstract import AbstractAssembly, GlasgowPin from glasgow.applet import GlasgowAppletV2 @@ -117,7 +117,8 @@ def elaborate(self, platform): # TODO: `uart.bit_cyc` is only used to set the width of the register; the actual initial # value is zero (same as `self.bit_cyc`); this is a footgun and should be fixed by rewriting # the UART to use lib.wiring - m.submodules.uart = uart = UART(self.ports, + m.submodules.uart = uart = ExternalUART( + self.ports, bit_cyc=(1 << len(self.manual_cyc)) - 1, parity=self.parity) m.submodules.auto_baud = auto_baud = UARTAutoBaud() diff --git a/software/glasgow/applet/interface/uart_analyzer/__init__.py b/software/glasgow/applet/interface/uart_analyzer/__init__.py index b4c6d1859..2044adaeb 100644 --- a/software/glasgow/applet/interface/uart_analyzer/__init__.py +++ b/software/glasgow/applet/interface/uart_analyzer/__init__.py @@ -11,7 +11,7 @@ from amaranth.lib.wiring import In, Out from glasgow.gateware.ports import PortGroup -from glasgow.gateware.uart import UART +from glasgow.gateware.uart import ExternalUART from glasgow.gateware.stream import Queue from glasgow.abstract import AbstractAssembly, GlasgowPin, ClockDivisor from glasgow.applet import GlasgowAppletV2, GlasgowAppletError @@ -51,8 +51,10 @@ def elaborate(self, platform): channels = [] for index, pin in enumerate(self._port): - m.submodules[f"ch{index}"] = uart = UART(PortGroup(rx=pin), - bit_cyc=(1 << len(self.periods[index])) - 1, parity=self._parity) + m.submodules[f"ch{index}"] = uart = ExternalUART( + PortGroup(rx=pin), + bit_cyc=(1 << len(self.periods[index])) - 1, + parity=self._parity) m.d.comb += uart.bit_cyc.eq(self.periods[index] + 1) channels.append(uart) diff --git a/software/glasgow/applet/interface/uart_pinout/__init__.py b/software/glasgow/applet/interface/uart_pinout/__init__.py new file mode 100644 index 000000000..36b406af3 --- /dev/null +++ b/software/glasgow/applet/interface/uart_pinout/__init__.py @@ -0,0 +1,473 @@ +import asyncio +import logging + +from amaranth import Module, Mux, Signal, Cat, Const, unsigned +from amaranth.lib import stream, enum, io, wiring +from amaranth.lib.cdc import FFSynchronizer +from amaranth.lib.wiring import In, Out + +from glasgow.abstract import GlasgowPin +from glasgow.applet import GlasgowAppletError, GlasgowAppletV2 +from glasgow.gateware.uart import UART + +# This should be able to hold most sensible values +MAX_BAUD_WIDTH = 32 +# Keep sorted +_DEFAULT_BAUDS = [ + 9600, + 19200, + 38400, + 57600, + 115200, + 230400, + 460800, + 921600, +] + + +class _Parity(enum.Enum, shape=unsigned(2)): + NoParity = 0x00 + Even = 0x01 + Odd = 0x02 + + +class ModifiableUARTBus(wiring.Component): + """ + This class will expose the same interface as the UARTBus over in gateware, + but we just use Signals instead of ports since we're iterating over the + ports. Essentially the UART that uses this bus will be reading from + different pins at different times but always be using the same input/ + output + """ + + def __init__(self, ports): + + self._ports = ports + pincount = len(ports.pins) + + super().__init__( + { + # Leave all OE at 0 when this isn't enabled + "i_enable": In(1), + # Masks are used to select the pins for rx/tx + "i_tx_mask": In(pincount), + "i_rx_mask": In(pincount), + } + ) + + self.pin_values = Signal(pincount) + + # Implement UARTBus over in gateware, these fields are expected + self.rx_i = Signal(1) + self.has_rx = True + self.tx_o = Signal(1, init=1) + self.has_tx = True + + def elaborate(self, platform): + m = Module() + + pins = [] + + for i, port in enumerate(self._ports.pins): + m.submodules[f"pins{i}_buffer"] = pin = io.Buffer("io", port) + pins.append(pin) + + pincount = len(pins) + + self.pin_values = Signal(pincount) + m.submodules += FFSynchronizer(Cat(pin.i for pin in pins), self.pin_values) + + # TXOE will be used for the pin .oe values: setting a bit in TXOE enables + # the output on that bit. We use tx_mask to set the bit. + txoe = Signal(pincount) + # TX will be used to set the .o values. We basically just track tx_bit + # with this and use TXOE to determine which one should actually be enabled + tx = Signal(pincount) + + m.d.comb += [ + Cat(pin.oe for pin in pins).eq(txoe), + Cat(pin.o for pin in pins).eq(tx), + ] + + m.d.comb += self.rx_i.eq( + ((self.pin_values & self.i_rx_mask) != 0) | (self.i_rx_mask == 0) + ) + + ALL_PINS_HIGH = Const((1 << pincount) - 1, shape=tx.shape()) + ALL_PINS_LOW = Const(0, shape=tx.shape()) + + # tx is based on tx_o and the mask when enabled, note that we control + # .oe in the next line, so even though we're setting all pins not all + # have output enabled + m.d.comb += tx.eq( + Mux( + self.i_enable, + Mux(self.tx_o, ALL_PINS_HIGH, ALL_PINS_LOW), + ALL_PINS_HIGH, + ) + ) + + # txoe will only enable a pin when we are enabled, otherwise disable everything + m.d.comb += txoe.eq(Mux(self.i_enable, self.i_tx_mask, ALL_PINS_LOW)) + + return m + + +class UARTPinoutComponent(wiring.Component): + baud_ticks: In(MAX_BAUD_WIDTH) + nstopbits: In(4) + enable: In(1) + i_stream: In(stream.Signature(8)) + o_stream: Out(stream.Signature(8)) + + def __init__(self, ports, parity=_Parity.NoParity): + super().__init__() + self.bus = ModifiableUARTBus(ports) + + self.parity = ( + "none" + if parity == _Parity.NoParity + else ("even" if parity == _Parity.Even else "odd") + ) + + npins = len(ports.pins) + + self.rx_mask = Signal(npins) + self.tx_mask = Signal(npins) + + def elaborate(self, platform): + + m = Module() + + m.submodules.internal_uart = uart = UART( + self.bus, _DEFAULT_BAUDS[-1], parity=self.parity + ) + + m.d.comb += [ + self.bus.i_enable.eq(self.enable), + self.bus.i_rx_mask.eq(self.rx_mask), + self.bus.i_tx_mask.eq(self.tx_mask), + uart.bit_cyc.eq(self.baud_ticks), + uart.tx_data.eq(self.i_stream.payload), + uart.tx_ack.eq(self.i_stream.valid), + self.i_stream.ready.eq(uart.tx_rdy), + self.o_stream.payload.eq(uart.rx_data), + self.o_stream.valid.eq(uart.rx_rdy), + uart.rx_ack.eq(self.o_stream.ready), + ] + + return m + + +class UARTPinoutInterface: + def __init__(self, logger, assembly, *, pins, data=b"\x0d", rx_delay_sec=0.05): + self._logger = logger + self._sys_clk_period = assembly.sys_clk_period + self._trace(f"sys_clk_period[{self._sys_clk_period}]") + + self._ports = ports = assembly.add_port_group(pins=pins) + assembly.use_pulls({pins: "high"}) + self._component = component = assembly.add_submodule(UARTPinoutComponent(ports)) + + self._rx_delay_sec = rx_delay_sec + self._data = data + + self._rrx_mask = assembly.add_rw_register(component.rx_mask) + self._rtx_mask = assembly.add_rw_register(component.tx_mask) + self._renable = assembly.add_rw_register(component.enable) + self._rbaud_ticks = assembly.add_rw_register(component.baud_ticks) + # self._rparity = assembly.add_rw_register(component.parity) + self._rnstopbits = assembly.add_rw_register(component.nstopbits) + + self._pipe = assembly.add_inout_pipe( + component.o_stream, + component.i_stream, + ) + + def _log(self, lvl, msg, *args): + self._logger.log(lvl, "uart-pinout: " + msg, *args) + + def _dbg(self, msg, *args): + self._log(logging.DEBUG, msg, *args) + + def _err(self, msg, *args): + self._log(logging.ERROR, msg, *args) + + def _warn(self, msg, *args): + self._log(logging.WARN, msg, *args) + + def _trace(self, msg, *args): + self._log(logging.TRACE, msg, *args) + + def _info(self, msg, *args): + self._log(logging.INFO, msg, *args) + + def set_rx_delay_ms(self, ms): + self._rx_delay_sec = ms / 1000.0 + + def set_data(self, data): + self._data = data + + async def set_rx_pin(self, rx): + self._dbg(f"Setting RX pin to {rx}") + await self._rrx_mask.set(1 << rx) + + async def set_tx_pin(self, tx): + self._dbg(f"Setting TX pin to {tx}") + await self._rtx_mask.set(1 << tx) + + # async def set_parity(self, parity): + # await self._rparity.set(parity.value) + + async def set_nstopbits(self, nstopbits): + self._dbg(f"Setting nstopbits to {nstopbits}") + await self._rnstopbits.set(nstopbits) + + async def set_baud(self, baud): + # Convert the baud rate to the number of ticks of the system clock we need. + # There will be some error here, but I think it's generally not a big deal. + baud_ticks = round((1.0 / baud) / self._sys_clk_period) + self._trace(f"Setting baud to {baud}, ticks {baud_ticks}") + await self._rbaud_ticks.set(baud_ticks) + + async def try_baud(self, baud): + await self.set_baud(baud) + return await self.run() + + async def transact(self): + """ + Try to transmit the data at a given baud rate and listen for an echo + """ + + if not self._data: + raise GlasgowAppletError("try_baud called but empty data") + + self._dbg(f"Transacting data: {self._data}") + + await self._renable.set(1) + res = b"" + await self._pipe.send(self._data) + await self._pipe.flush() + + # We aren't guaranteed to get a perfect echo, so queue up reads one + # byte at a time + for _ in range(len(self._data)): + read = self._pipe.recv(1) + try: + data = await asyncio.wait_for(read, timeout=self._rx_delay_sec) + res += data + except TimeoutError: + self._trace(f"Timed out waiting for RX data, got {res}") + break + + await self._renable.set(0) + self._trace(f"Got res: {res}") + + return res or None + + +class UARTPinoutApplet(GlasgowAppletV2): + logger = logging.getLogger(__name__) + + help = "attemps to automatically determine UART pinout and baud rate" + description = """ + This applet works by simply sending data at various baud rates and waiting + for data back. If the UART you are testing does not ever echo data, this + cannot detect that UART! + """ + + @classmethod + def add_build_arguments(cls, parser, access): + access.add_voltage_argument(parser) + access.add_pins_argument(parser, "pins", width=range(2, 17), required=True) + + @classmethod + def add_run_arguments(cls, parser): + parser.add_argument( + "-d", "--data-hex", default="0d", help="Data to send as hex (default 0d)" + ) + parser.add_argument( + "-s", + "--data-ascii", + default=None, + help="Data to send as ASCII with \\ escapes valid", + ), + parser.add_argument( + "--rx-delay-ms", + default=0, + type=int, + help="Time to wait RX delay in ms, if not set this is determined from the chosen bauds", + ) + parser.add_argument( + "-b", + "--bauds", + default=None, + help="Comma separated list of bauds to try", + action="append", + ) + parser.add_argument( + "-e", + "--exclude-pins", + default=None, + action="append", + help="Comma separated list of pin numbers to exclude. Useful for using less pins without rebuilding the applet.", + ) + + parser.add_argument( + "-T", + "--tx", + default=None, + help="Set the TX pin and look for the RX pin", + ) + + parser.add_argument( + "-R", + "--rx", + default=None, + help="Set the RX pin and look for the TX pin", + ) + + def build(self, args): + + with self.assembly.add_applet(self): + self.assembly.use_voltage(args.voltage) + + self.uart_pinout_iface = UARTPinoutInterface( + self.logger, + self.assembly, + pins=args.pins, + ) + + def _dbg(self, msg, *args): + self.uart_pinout_iface._dbg(msg, *args) + + def _trace(self, msg, *args): + self.uart_pinout_iface._trace(msg, *args) + + def _info(self, msg, *args): + self.uart_pinout_iface._info(msg, *args) + + def calculate_abs_delay(self, bauds, data): + """ + Get a absolute delay that will account for the slowest baud rate + """ + + slowest = min(bauds) + # Assuming 1 start bit, 8 data bits, 1 stop bit and, for good measure, 1 parity bit + bits_per_byte = 11 + bits_for_data = bits_per_byte * len(data) + + longest_msg_time_ms = round((1000.0 / slowest) * bits_for_data) + # From some experimentation, it makes sense to set a lower bound here + wait_ms = max(50, longest_msg_time_ms * 2) + return wait_ms + + def get_data(self, args): + """ + Retrieve the command line provided data + """ + if args.data_ascii: + # Wow + return args.data_ascii.encode().decode("unicode_escape").encode() + try: + return bytes.fromhex(args.data_hex) + except ValueError: + raise GlasgowAppletError(f"invalid hex: {args.data_hex}") + + def _get_pin_idx(self, raw, pins, flag): + """ + Get the index into the pin array for the given GlasgowPin spec + """ + try: + pin = GlasgowPin.parse(raw)[0] + except (ValueError, IndexError): + raise GlasgowAppletError(f"Invalid pin spec for {flag}: {raw}") + + self._trace(f"Looking for pin {pin}") + + try: + return pins.index(pin) + except ValueError: + raise GlasgowAppletError(f"Exclude pin {raw} (from {flag}) not in pin set") + + + def _make_exclude_pins(self, exclude, pins): + indices = [] + for p in exclude: + indices.append(self._get_pin_idx(p, pins, "-e/--exclude-pins")) + return indices + + + async def run(self, args): + data = self.get_data(args) + bauds = ( + _DEFAULT_BAUDS + if not args.bauds + else sorted([int(e) for x in args.bauds for e in x.split(",")]) + ) + + for it in bauds: + if it.bit_length() > MAX_BAUD_WIDTH: + raise GlasgowAppletError( + f"invalid baud passed to --bauds, {it} has a larger bit width ({it.bit_length()}) than the max allowed value ({MAX_BAUD_WIDTH})" + ) + + pins = args.pins + npins = len(pins) + + if args.exclude_pins: + exclude = self._make_exclude_pins(args.exclude_pins, pins) + self._dbg(f"Excluding pins: {exclude}") + else: + exclude = [] + + abs_delay = args.rx_delay_ms + + if abs_delay == 0: + abs_delay = self.calculate_abs_delay(bauds, data) + self._dbg(f"Absolute delay set to {abs_delay}ms") + + if args.tx is not None: + tx_options = [self._get_pin_idx(args.tx, pins, "-T/--tx")] + else: + tx_options = range(npins) + + if args.rx is not None: + rx_options = [self._get_pin_idx(args.rx, pins, "-R/--rx")] + else: + rx_options = range(npins) + + self.uart_pinout_iface.set_rx_delay_ms(abs_delay) + self.uart_pinout_iface.set_data(data) + # The gateware UART only supports 1 stop bit + await self.uart_pinout_iface.set_nstopbits(1) + + for tx in tx_options: + + if tx in exclude: + continue + + await self.uart_pinout_iface.set_tx_pin(tx) + + for rx in rx_options: + + if rx == tx or rx in exclude: + continue + + await self.uart_pinout_iface.set_rx_pin(rx) + + for it in bauds: + await self.uart_pinout_iface.set_baud(it) + res = await self.uart_pinout_iface.transact() + if res: + if res == data: + print("** ", end="") + print( + f"TX[{pins[tx]}] RX[{pins[rx]}] BAUD[{it}] DATA[{res.hex()}]" + ) + + @classmethod + def tests(cls): + from . import test + + return test.UARTPinoutAppletTestCase diff --git a/software/glasgow/applet/interface/uart_pinout/test.py b/software/glasgow/applet/interface/uart_pinout/test.py new file mode 100644 index 000000000..7eb50a7a4 --- /dev/null +++ b/software/glasgow/applet/interface/uart_pinout/test.py @@ -0,0 +1,118 @@ +from amaranth import Elaboratable, Module +from glasgow.gateware.uart import ExternalUART +from glasgow.simulation.assembly import SimulationAssembly +from glasgow.applet import GlasgowAppletV2TestCase, synthesis_test +from glasgow.applet.interface.uart_pinout import UARTPinoutApplet, UARTPinoutInterface + + +class UARTPinoutAppletTestCase(GlasgowAppletV2TestCase, applet=UARTPinoutApplet): + + @synthesis_test + def test_build(self): + self.assertBuilds(args=["--pins", "A0:3"]) + + def test_run(self): + + assembly = SimulationAssembly() + + pins = "A0:5" + baud = 115200 + baud_ticks = round((1.0 / baud) / assembly.sys_clk_period) + + iface = UARTPinoutInterface(assembly._logger, assembly, pins=pins) + + # Echo will just send RX to TX at 115200 baud. + class Echo(Elaboratable): + def elaborate(self, platform): + m = Module() + # Use B0 and B1 for the ports, we'll forward ports in A to them + # appropriately. + uart = ExternalUART( + ports=assembly.add_port_group(rx="B0", tx="B1"), + bit_cyc=baud.bit_length(), + ) + m.submodules.test_uart = uart + + b0 = assembly.get_pin("B0") + a5 = assembly.get_pin("A5") + + b1 = assembly.get_pin("B1") + a3 = assembly.get_pin("A3") + + m.d.comb += uart.bit_cyc.eq(baud_ticks) + + # B0 == A5 + m.d.comb += b0.i.eq((a5.o & a5.oe) | (~a5.oe)) + + # A3 == B1 + m.d.comb += a3.i.eq((b1.o & b1.oe) | (~b1.oe)) + + # TX == RX + m.d.comb += uart.tx_data.eq(uart.rx_data) + + with m.If(uart.rx_rdy): + m.d.comb += uart.tx_ack.eq(1) + m.d.comb += uart.rx_ack.eq(1) + + return m + + assembly.add_submodule(Echo()) + + async def testbench(ctx): + + # Set all test pins high since UARTs idle high, ignore A3 because + # we're driving that elsewhere. + for i in range(6): + if i == 3: + continue + pin = f"A{i}" + pin = assembly.get_pin(pin) + ctx.set(pin.i, 1) + + data = bytes.fromhex("0FF00FF0") + + await iface._rnstopbits.set(1) + iface.set_data(data) + # No need for a big delay with the test + iface.set_rx_delay_ms(1) + await iface.set_baud(baud) + + await iface.set_tx_pin(0) + await iface.set_rx_pin(1) + result = await iface.transact() + assert result is None, f"expected None got {result.hex()}" + + # Correct tx, but wrong rx + await iface.set_tx_pin(5) + result = await iface.transact() + assert result is None, f"expected None got {result.hex()}" + + # Correct rx, but wrong tx + await iface.set_tx_pin(2) + await iface.set_rx_pin(3) + result = await iface.transact() + assert result is None, f"expected None got {result.hex()}" + + # Both correct + await iface.set_tx_pin(5) + await iface.set_rx_pin(3) + result = await iface.transact() + assert result == data, f"expected {data.hex()} got {result.hex()}" + + # Cases with correct pins but incorrect bauds + await iface.set_tx_pin(5) + await iface.set_rx_pin(3) + + # Wrong baud (half) but correct pins + await iface.set_baud(int(baud / 2)) + result = await iface.transact() + expected = bytes.fromhex("F0F0") + assert result == expected, f"expected {expected.hex()} got {result.hex()}" + + # Wrong baud (double) but correct pins + await iface.set_baud(baud * 2) + result = await iface.transact() + expected = bytes.fromhex("F000") + assert result == expected, f"expected {expected.hex()} got {result.hex()}" + + assembly.run(testbench, vcd_file="test_uart_pinout_run.vcd") diff --git a/software/glasgow/applet/program/m16c/__init__.py b/software/glasgow/applet/program/m16c/__init__.py index e24b07be0..828f99220 100644 --- a/software/glasgow/applet/program/m16c/__init__.py +++ b/software/glasgow/applet/program/m16c/__init__.py @@ -80,7 +80,7 @@ def __init__(self, ports, out_fifo, in_fifo, bit_cyc, reset, mode, max_bit_cyc): self.reset = reset self.mode = mode - self.uart = UART(ports, bit_cyc=max_bit_cyc) + self.uart = ExternalUART(ports, bit_cyc=max_bit_cyc) def elaborate(self, platform): m = Module() diff --git a/software/glasgow/applet/sensor/pmsx003/__init__.py b/software/glasgow/applet/sensor/pmsx003/__init__.py index 4727cab1f..53702de56 100644 --- a/software/glasgow/applet/sensor/pmsx003/__init__.py +++ b/software/glasgow/applet/sensor/pmsx003/__init__.py @@ -27,7 +27,8 @@ def __init__(self, ports, in_fifo, out_fifo): def elaborate(self, platform): m = Module() - m.submodules.uart = uart = UART(self.ports, + m.submodules.uart = uart = ExternalUART( + self.ports, bit_cyc=int(platform.default_clk_frequency // 9600)) m.d.comb += [ self.in_fifo.w_data.eq(uart.rx_data), diff --git a/software/glasgow/gateware/uart.py b/software/glasgow/gateware/uart.py index e6b35e373..aaaa18911 100644 --- a/software/glasgow/gateware/uart.py +++ b/software/glasgow/gateware/uart.py @@ -3,7 +3,7 @@ from amaranth.lib.cdc import FFSynchronizer -__all__ = ["UART"] +__all__ = ["UART", "ExternalUART"] class UARTBus(Elaboratable): @@ -12,6 +12,7 @@ class UARTBus(Elaboratable): Provides synchronization. """ + def __init__(self, ports): self.ports = ports @@ -90,7 +91,8 @@ class UART(Elaboratable): Transmit acknowledgement. If active when ``tx_rdy`` is active, ``tx_rdy`` is reset, ``tx_data`` is sampled, and the transmit state machine starts transmitting a frame. """ - def __init__(self, ports, bit_cyc, data_bits=8, parity="none", max_bit_cyc=None): + + def __init__(self, bus, bit_cyc, data_bits=8, parity="none", max_bit_cyc=None): if max_bit_cyc is not None: self.max_bit_cyc = max_bit_cyc else: @@ -102,18 +104,18 @@ def __init__(self, ports, bit_cyc, data_bits=8, parity="none", max_bit_cyc=None) self.bit_cyc = Signal(range(self.max_bit_cyc + 1), init=bit_cyc) self.rx_data = Signal(data_bits) - self.rx_rdy = Signal() - self.rx_ack = Signal() + self.rx_rdy = Signal() + self.rx_ack = Signal() self.rx_ferr = Signal() self.rx_perr = Signal() - self.rx_ovf = Signal() - self.rx_err = Signal() + self.rx_ovf = Signal() + self.rx_err = Signal() self.tx_data = Signal(data_bits) - self.tx_rdy = Signal() - self.tx_ack = Signal() + self.tx_rdy = Signal() + self.tx_ack = Signal() - self.bus = UARTBus(ports) + self.bus = bus def elaborate(self, platform): m = Module() @@ -139,7 +141,7 @@ def calc_parity(sig, kind): if self.bus.has_rx: rx_start = Signal() rx_timer = Signal(range(self.max_bit_cyc)) - rx_stb = Signal() + rx_stb = Signal() rx_shreg = Signal(self.data_bits) rx_bitno = Signal(range(len(rx_shreg))) @@ -199,11 +201,11 @@ def calc_parity(sig, kind): ### if self.bus.has_tx: - tx_start = Signal() - tx_timer = Signal(range(self.max_bit_cyc)) - tx_stb = Signal() - tx_shreg = Signal(self.data_bits) - tx_bitno = Signal(range(len(tx_shreg))) + tx_start = Signal() + tx_timer = Signal(range(self.max_bit_cyc)) + tx_stb = Signal() + tx_shreg = Signal(self.data_bits) + tx_bitno = Signal(range(len(tx_shreg))) tx_parity = Signal() with m.If(tx_start | (tx_timer == 0)): @@ -222,7 +224,9 @@ def calc_parity(sig, kind): self.bus.tx_o.eq(0), ] if self.parity != "none": - m.d.sync += tx_parity.eq(calc_parity(self.tx_data, self.parity)) + m.d.sync += tx_parity.eq( + calc_parity(self.tx_data, self.parity) + ) m.next = "START" with m.Else(): m.d.sync += self.bus.tx_o.eq(1) @@ -230,7 +234,7 @@ def calc_parity(sig, kind): with m.If(tx_stb): m.d.sync += [ self.bus.tx_o.eq(tx_shreg[0]), - tx_shreg.eq(Cat(tx_shreg[1:], C(0,1))), + tx_shreg.eq(Cat(tx_shreg[1:], C(0, 1))), ] m.next = "DATA" with m.State("DATA"): @@ -239,7 +243,7 @@ def calc_parity(sig, kind): with m.If(tx_bitno != len(tx_shreg) - 1): m.d.sync += [ self.bus.tx_o.eq(tx_shreg[0]), - tx_shreg.eq(Cat(tx_shreg[1:], C(0,1))), + tx_shreg.eq(Cat(tx_shreg[1:], C(0, 1))), ] with m.Else(): if self.parity == "none": @@ -250,10 +254,16 @@ def calc_parity(sig, kind): m.next = "PARITY" with m.State("PARITY"): with m.If(tx_stb): - m.d.sync += self.bus.tx_o.eq(1), + m.d.sync += (self.bus.tx_o.eq(1),) m.next = "STOP" with m.State("STOP"): with m.If(tx_stb): m.next = "IDLE" return m + + +class ExternalUART(UART): + def __init__(self, ports, *args, **kwargs): + bus = UARTBus(ports) + super().__init__(bus, *args, **kwargs) diff --git a/software/pyproject.toml b/software/pyproject.toml index c11517486..1ecf64287 100644 --- a/software/pyproject.toml +++ b/software/pyproject.toml @@ -90,6 +90,7 @@ benchmark = "glasgow.applet.internal.benchmark:BenchmarkApplet" analyzer = "glasgow.applet.interface.analyzer:AnalyzerApplet" uart = "glasgow.applet.interface.uart:UARTApplet" uart-analyzer = "glasgow.applet.interface.uart_analyzer:UARTAnalyzerApplet" +uart-pinout = "glasgow.applet.interface.uart_pinout:UARTPinoutApplet" spi-analyzer = "glasgow.applet.interface.spi_analyzer:SPIAnalyzerApplet" spi-controller = "glasgow.applet.interface.spi_controller:SPIControllerApplet" i2c-controller = "glasgow.applet.interface.i2c_controller:I2CControllerApplet"