Skip to content

Commit

Permalink
access.direct: implement selective pin inversion with --pin-x 0#.
Browse files Browse the repository at this point in the history
Amaranth supports inversion for any pin and for any I/O core on the I/O
buffer level. This mechanism obviates the need for ad-hoc inversion in
any applets, but was not exposed until now. Any pin can now be inverted
via the command-line interface using one of the following syntaxes:

* single pin: `--pin-x 0#`
* pin range:  `--pins-x 0:8#`      (inverts all of them)
* pin list:   `--pins-x 0,1#,2#,3` (inverts only specified pins)

Pull-ups configured for a pin with inversion get converted to pull-downs
and vice versa.
  • Loading branch information
whitequark committed Jul 23, 2024
1 parent 3454c82 commit ecca4f6
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 37 deletions.
41 changes: 28 additions & 13 deletions software/glasgow/access/direct/arguments.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import functools
import re
import functools
from dataclasses import dataclass

from .. import AccessArguments


@dataclass(frozen=True)
class PinArgument:
number: int
invert: bool = False

def __str__(self):
return f"{self.number}{'#' if self.invert else ''}"


class DirectArguments(AccessArguments):
# First, define some state-less methods that just add arguments to an argparse instance.

Expand Down Expand Up @@ -34,9 +44,9 @@ def _add_port_voltage_arguments(self, parser, default):
help="do not change I/O port voltage")

def _mandatory_pin_number(self, arg):
if not re.match(r"^[0-9]+$", arg):
if not re.match(r"^[0-9]+#?$", arg):
self._arg_error(f"{arg} is not a valid pin number")
return int(arg)
return PinArgument(int(arg.replace("#", "")), invert=arg.endswith("#"))

def _optional_pin_number(self, arg):
if arg == "-":
Expand All @@ -46,6 +56,7 @@ def _optional_pin_number(self, arg):
def _add_pin_argument(self, parser, name, default, required):
help = f"bind the applet I/O line {name!r} to pin NUM"
if default is not None:
default = PinArgument(default)
help += " (default: %(default)s)"

if required:
Expand All @@ -61,25 +72,29 @@ def _add_pin_argument(self, parser, name, default, required):

def _pin_set(self, width, arg):
if arg == "":
numbers = []
elif re.match(r"^[0-9]+:[0-9]+$", arg):
first, last = map(int, arg.split(":"))
numbers = list(range(first, last + 1))
elif re.match(r"^[0-9]+(,[0-9]+)*$", arg):
numbers = list(map(int, arg.split(",")))
pin_args = []
elif re.match(r"^[0-9]+:[0-9]+#?$", arg):
first, last = map(int, arg.replace("#", "").split(":"))
pin_args = [PinArgument(int(number), invert=arg.endswith("#"))
for number in range(first, last + 1)]
elif re.match(r"((^|,)[0-9]+#?)+$", arg):
pin_args = [PinArgument(int(number.replace("#", "")), invert=number.endswith("#"))
for number in arg.split(",")]
else:
self._arg_error(f"{arg} is not a valid pin number set")
if len(numbers) not in width:
if len(pin_args) not in width:
if len(width) == 1:
width_desc = str(width[0])
else:
width_desc = f"{width.start}..{width.stop - 1}"
self._arg_error(f"set {arg} includes {len(numbers)} pins, but {width_desc} pins are required")
return numbers
self._arg_error(f"set {arg} includes {len(pin_args)} pins, but "
f"{width_desc} pins are required")
return pin_args

def _add_pin_set_argument(self, parser, name, width, default, required):
help = f"bind the applet I/O lines {name!r} to pins SET"
if default is not None:
default = [PinArgument(number) for number in default]
if default:
help += " (default: %(default)s)"
else:
Expand Down Expand Up @@ -113,7 +128,7 @@ def add_build_arguments(self, parser):

def add_pin_argument(self, parser, name, default=None, required=False):
if default is True:
default = str(self._get_free(self._free_pins))
default = self._get_free(self._free_pins)
self._add_pin_argument(parser, name, default, required)

def add_pin_set_argument(self, parser, name, width, default=None, required=False):
Expand Down
43 changes: 27 additions & 16 deletions software/glasgow/access/direct/demultiplexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,41 +98,52 @@ async def claim_interface(self, applet, mux_interface, args, pull_low=set(), pul
elif hasattr(args, "keep_voltage") and args.keep_voltage:
applet.logger.info("port voltage unchanged")

device_pull_low = set()
device_pull_high = set()
for pin_arg in pull_low:
(device_pull_high if pin_arg.invert else device_pull_low).add(pin_arg.number)
for pin_arg in pull_high:
(device_pull_low if pin_arg.invert else device_pull_high).add(pin_arg.number)
if self.device.has_pulls:
if self.device.revision == "C0":
if pull_low or pull_high:
applet.logger.error("Glasgow revC0 has severe restrictions on use of configurable "
"pull resistors; device may require power cycling")
await self.device.set_pulls(args.port_spec, pull_low, pull_high)
applet.logger.error(
"Glasgow revC0 has severe restrictions on use of configurable "
"pull resistors; device may require power cycling")
await self.device.set_pulls(args.port_spec, device_pull_low, device_pull_high)
else:
# Don't touch the pulls; they're either in the power-on reset high-Z state, or
# they have been touched by the user, and we've warned about that above.
pass

elif hasattr(args, "port_spec"):
await self.device.set_pulls(args.port_spec, pull_low, pull_high)
if pull_low or pull_high:
applet.logger.info("port(s) %s pull resistors configured",
", ".join(sorted(args.port_spec)))
else:
applet.logger.debug("port(s) %s pull resistors disabled",
", ".join(sorted(args.port_spec)))

elif pull_low or pull_high:
await self.device.set_pulls(args.port_spec, device_pull_low, device_pull_high)
device_pull_desc = []
if device_pull_high:
device_pull_desc.append(f"pull-up on {', '.join(map(str, device_pull_high))}")
if device_pull_low:
device_pull_desc.append(f"pull-down on {', '.join(map(str, device_pull_low))}")
if not device_pull_desc:
device_pull_desc.append("disabled")
applet.logger.debug("port(s) %s pull resistors: %s",
", ".join(sorted(args.port_spec)),
"; ".join(device_pull_desc))

elif device_pull_low or device_pull_high:
# Some applets request pull resistors for bidirectional pins (e.g. I2C). Such applets
# cannot work on revA/B because of the level shifters and the applet should require
# an appropriate revision.
# Some applets, though, request pull resistors for unidirectional, DUT-controlled pins
# (e.g. NAND flash). Such applets can still work on revA/B with appropriate external
# pull resistors, so we spend some additional effort to allow for that.
if pull_low:
if device_pull_low:
applet.logger.warning("port(s) %s requires external pull-down resistors on pins %s",
", ".join(sorted(args.port_spec)),
", ".join(map(str, pull_low)))
if pull_high:
", ".join(map(str, device_pull_low)))
if device_pull_high:
applet.logger.warning("port(s) %s requires external pull-up resistors on pins %s",
", ".join(sorted(args.port_spec)),
", ".join(map(str, pull_high)))
", ".join(map(str, device_pull_high)))

await iface.reset()
return iface
Expand Down
16 changes: 8 additions & 8 deletions software/glasgow/access/direct/multiplexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,18 +198,18 @@ def elaborate(self, platform):
return m

def get_pin_name(self, pin):
port, bit, request = self._pins[pin]
port, bit, request = self._pins[pin.number]
return f"{port}{bit}"

def get_port_impl(self, pin, *, name):
port, bit, request = self._pins[pin]
self.logger.debug("assigning applet port '%s' to device pin %s",
name, self.get_pin_name(pin))
port, bit, request = self._pins[pin.number]
self.logger.debug("assigning applet port '%s' to device pin %s%s",
name, self.get_pin_name(pin), " (inverted)" if pin.invert else "")
pin_components = request(bit)
if hasattr(pin_components, "oe"):
return GlasgowPlatformPort(io=pin_components.io, oe=pin_components.oe)
else:
return GlasgowPlatformPort(io=pin_components.io)
return GlasgowPlatformPort(
io=~pin_components.io if pin.invert else pin_components.io,
oe=getattr(pin_components, "oe", None)
)

def get_in_fifo(self, **kwargs):
fifo = self._fx2_crossbar.get_in_fifo(self._pipe_num, **kwargs, reset=self.reset)
Expand Down

0 comments on commit ecca4f6

Please sign in to comment.