diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 2ebf0f373..3a002e1b7 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,18 +11,17 @@ jobs: strategy: matrix: python-version: - - '3.8' - '3.9' - '3.10' - '3.11' - '3.12' - - 'pypy-3.8' + - '3.13' - 'pypy-3.9' - 'pypy-3.10' allow-failure: - false include: - - python-version: '3.13-dev' + - python-version: '3.14-dev' allow-failure: true continue-on-error: ${{ matrix.allow-failure }} name: 'test (${{ matrix.python-version }})' diff --git a/amaranth/_toolchain/yosys.py b/amaranth/_toolchain/yosys.py index 7bff19c09..4184a74d4 100644 --- a/amaranth/_toolchain/yosys.py +++ b/amaranth/_toolchain/yosys.py @@ -4,15 +4,8 @@ import subprocess import warnings import pathlib -from importlib import metadata as importlib_metadata -try: - from importlib import resources as importlib_resources - try: - importlib_resources.files # py3.9+ stdlib - except AttributeError: - import importlib_resources # py3.8- shim -except ImportError: - importlib_resources = None +import importlib.metadata +import importlib.resources from . import has_tool, require_tool @@ -109,23 +102,21 @@ class _BuiltinYosys(YosysBinary): @classmethod def available(cls): - if importlib_metadata is None or importlib_resources is None: - return False try: - importlib_metadata.version(cls.YOSYS_PACKAGE) + importlib.metadata.version(cls.YOSYS_PACKAGE) return True - except importlib_metadata.PackageNotFoundError: + except importlib.metadata.PackageNotFoundError: return False @classmethod def version(cls): - version = importlib_metadata.version(cls.YOSYS_PACKAGE) + version = importlib.metadata.version(cls.YOSYS_PACKAGE) match = re.match(r"^(\d+)\.(\d+)\.(?:\d+)(?:\.(\d+))?(?:\.post(\d+))?", version) return (int(match[1]), int(match[2]), int(match[3] or 0), int(match[4] or 0)) @classmethod def data_dir(cls): - return importlib_resources.files(cls.YOSYS_PACKAGE) / "share" + return importlib.resources.files(cls.YOSYS_PACKAGE) / "share" @classmethod def run(cls, args, stdin="", *, ignore_warnings=False, src_loc_at=0): diff --git a/amaranth/back/rtlil.py b/amaranth/back/rtlil.py index 28f428c1a..f2e319521 100644 --- a/amaranth/back/rtlil.py +++ b/amaranth/back/rtlil.py @@ -1,4 +1,4 @@ -from typing import Iterable +from collections.abc import Iterable from contextlib import contextmanager import io diff --git a/amaranth/build/res.py b/amaranth/build/res.py index c3c90fe53..5e56f1402 100644 --- a/amaranth/build/res.py +++ b/amaranth/build/res.py @@ -311,9 +311,7 @@ def add_clock_constraint(self, clock, period=None, frequency=None): clocks[clock] = frequency def iter_signal_clock_constraints(self): - for signal, frequency in self._clocks.items(): - yield signal, frequency + yield from self._clocks.items() def iter_port_clock_constraints(self): - for port, frequency in self._io_clocks.items(): - yield port, frequency + yield from self._io_clocks.items() diff --git a/amaranth/hdl/_ir.py b/amaranth/hdl/_ir.py index 03963231f..59327747a 100644 --- a/amaranth/hdl/_ir.py +++ b/amaranth/hdl/_ir.py @@ -706,7 +706,7 @@ def __init__(self, netlist: _nir.Netlist, design: Design, *, all_undef_to_ff=Fal # SignalDict from Signal to dict from (module index, ClockDomain | None) to NetlistDriver self.drivers = _ast.SignalDict() self.io_ports: dict[_ast.IOPort, int] = {} - self.rhs_cache: dict[int, Tuple[_nir.Value, bool, _ast.Value]] = {} + self.rhs_cache: dict[int, tuple[_nir.Value, bool, _ast.Value]] = {} self.matches_cache = {} self.priority_match_cache = {} self.fragment_module_idx: dict[Fragment, int] = {} @@ -817,7 +817,7 @@ def unify_shapes_bitwise(self, operand_b = self.extend(operand_b, signed_b, shape.width) return (operand_a, operand_b, shape.signed) - def emit_rhs(self, module_idx: int, value: _ast.Value) -> Tuple[_nir.Value, bool]: + def emit_rhs(self, module_idx: int, value: _ast.Value) -> tuple[_nir.Value, bool]: """Emits a RHS value, returns a tuple of (value, is_signed)""" try: result, signed, value = self.rhs_cache[id(value)] diff --git a/amaranth/hdl/_nir.py b/amaranth/hdl/_nir.py index c15247a1a..3c0af0613 100644 --- a/amaranth/hdl/_nir.py +++ b/amaranth/hdl/_nir.py @@ -1,4 +1,5 @@ -from typing import Iterable, Any +from typing import Any +from collections.abc import Iterable import enum from ._ast import SignalDict diff --git a/amaranth/lib/enum.py b/amaranth/lib/enum.py index af90ed210..0beadacb7 100644 --- a/amaranth/lib/enum.py +++ b/amaranth/lib/enum.py @@ -29,11 +29,6 @@ class EnumType(ShapeCastable, py_enum.EnumMeta): can be specified by passing the ``view_class=`` keyword argument when creating the enum class. """ - # TODO: remove this shim once py3.8 support is dropped - @classmethod - def __prepare__(metacls, name, bases, shape=None, view_class=None, **kwargs): - return super().__prepare__(name, bases, **kwargs) - def __new__(metacls, name, bases, namespace, shape=None, view_class=None, **kwargs): if shape is not None: shape = Shape.cast(shape) diff --git a/amaranth/lib/meta.py b/amaranth/lib/meta.py index b00555791..0b2faadc6 100644 --- a/amaranth/lib/meta.py +++ b/amaranth/lib/meta.py @@ -141,6 +141,6 @@ def _extract_schemas(package, *, base_uri, path="schema/"): f"Schema $id {schema['$id']} must be {base_uri}/{relative_path}" schema_filename.parent.mkdir(parents=True, exist_ok=True) - with open(pathlib.Path(path) / relative_path, "wt") as schema_file: + with open(pathlib.Path(path) / relative_path, "w") as schema_file: json.dump(schema, schema_file, indent=2) print(f"Extracted {schema['$id']} to {schema_filename}") diff --git a/docs/install.rst b/docs/install.rst index 8be01f9ce..42fa667a4 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -23,7 +23,7 @@ System requirements .. |yosys-version| replace:: 0.40 (or newer) -Amaranth HDL requires Python 3.8; it works on CPython_ 3.8 (or newer), and works faster on PyPy3.8_ 7.3.7 (or newer). Installation requires pip_ 23.0 (or newer). +Amaranth HDL requires Python 3.9; it works on CPython_ 3.9 (or newer), and works faster on PyPy3.9_ 7.3.7 (or newer). Installation requires pip_ 23.0 (or newer). For most workflows, Amaranth requires Yosys_ |yosys-version|. A `compatible version of Yosys `_ is distributed via PyPI_ for most popular platforms, so it is usually not necessary to install Yosys separately. @@ -34,7 +34,7 @@ Synthesizing, placing and routing an Amaranth design for an FPGA requires the FP .. TODO: Link to FPGA family docs here .. _CPython: https://www.python.org/ -.. _PyPy3.8: https://www.pypy.org/ +.. _PyPy3.9: https://www.pypy.org/ .. _pip: https://pip.pypa.io/en/stable/ .. _Yosys: https://yosyshq.net/yosys/ .. _amaranth-yosys: https://pypi.org/project/amaranth-yosys/ diff --git a/pyproject.toml b/pyproject.toml index 2f0c4dbb7..60a1ea849 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,9 +13,8 @@ readme = "README.md" authors = [{name = "Amaranth HDL contributors"}] license = { text = "BSD-2-clause" } -requires-python = "~=3.8" +requires-python = "~=3.9" dependencies = [ - "importlib_resources; python_version<'3.9'", # for amaranth._toolchain.yosys "jschon~=0.11.1", # for amaranth.lib.meta "pyvcd>=0.2.2,<0.5", # for amaranth.sim.pysim "Jinja2~=3.0", # for amaranth.build @@ -47,10 +46,6 @@ amaranth-rpc = "amaranth.rpc:main" requires = ["pdm-backend~=2.3.0"] build-backend = "pdm.backend" -[tool.pdm] -# Remove this once we no longer support Python 3.8. -ignore_package_warnings = ["sphinx*", "alabaster"] - [tool.pdm.build] # If amaranth 0.3 is checked out with git (e.g. as a part of a persistent editable install or # a git worktree cached by tools like poetry), it can have an empty `nmigen` directory left over,