From 9889e9ee75d190168a8bf87a382112b367b211be Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 11 Feb 2025 16:01:08 +0000 Subject: [PATCH 1/6] Update README to use pdm install --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 27fe390f..f64e146b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,12 @@ # ChipFlow library -WIP - +This is a small library to make working with the ChipFlow platform easy and straightforward. ## Installation and usage This library uses [PDM](https://pdm.fming.dev/) for dependency management, testing, building, and publishing. Install PDM first. Then run: ``` -pdm lock pdm install ``` From fc9ea00596143be7e932bc510a0cf3c471970a86 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 11 Feb 2025 16:10:17 +0000 Subject: [PATCH 2/6] Add pin-lock functionality and remove providers In order to remove the need for users to define their own SiliconStep and pin layout.This introduces a number of mechanisms: - PinSignature: a mechanism for Components to label wiring that should be routed to pads/pins and the requriments. - Pin lock mechanism: addition of `pins` subcommand to `chipflow` cli tool. This inspects the top level components and maps the ports to pins, with pin behaviour defined by the package definition. - Package definitions - basis for package definitions in chipflow-lib - General purpose SiliconStep Still remaining follow on changes are: - Encoding power, clock, heartbeat and jtag pin locations in package definitions. - Pin lock for FPGA boards - Removal of need for user to define other steps (sim, software, board, export verilog) --- chipflow_lib/__init__.py | 142 ++++++++- chipflow_lib/cli.py | 208 ++++--------- chipflow_lib/errors.py | 3 + chipflow_lib/pin_lock.py | 169 +++++++++++ chipflow_lib/platforms/__init__.py | 3 + chipflow_lib/platforms/silicon.py | 175 ++++++++--- chipflow_lib/platforms/sim.py | 4 - chipflow_lib/platforms/utils.py | 421 ++++++++++++++++++++++++++ chipflow_lib/providers/__init__.py | 0 chipflow_lib/providers/board_ulx3s.py | 150 --------- chipflow_lib/providers/silicon.py | 132 -------- chipflow_lib/providers/sim.py | 68 ----- chipflow_lib/steps/silicon.py | 85 ++++-- 13 files changed, 989 insertions(+), 571 deletions(-) create mode 100644 chipflow_lib/errors.py create mode 100644 chipflow_lib/pin_lock.py create mode 100644 chipflow_lib/platforms/utils.py delete mode 100644 chipflow_lib/providers/__init__.py delete mode 100644 chipflow_lib/providers/board_ulx3s.py delete mode 100644 chipflow_lib/providers/silicon.py delete mode 100644 chipflow_lib/providers/sim.py diff --git a/chipflow_lib/__init__.py b/chipflow_lib/__init__.py index 21b1cc91..09adb0ba 100644 --- a/chipflow_lib/__init__.py +++ b/chipflow_lib/__init__.py @@ -1,4 +1,144 @@ -# SPDX-License-Identifier: BSD-2-Clause +import importlib.metadata +import jsonschema +import os +import sys +import tomli + +__version__ = importlib.metadata.version("chipflow_lib") + class ChipFlowError(Exception): pass + + +def _get_cls_by_reference(reference, context): + module_ref, _, class_ref = reference.partition(":") + try: + module_obj = importlib.import_module(module_ref) + except ModuleNotFoundError as e: + raise ChipFlowError(f"Module `{module_ref}` referenced by {context} is not found") from e + try: + return getattr(module_obj, class_ref) + except AttributeError as e: + raise ChipFlowError(f"Module `{module_ref}` referenced by {context} does not define " + f"`{class_ref}`") from e + + +def _ensure_chipflow_root(): + if "CHIPFLOW_ROOT" not in os.environ: + os.environ["CHIPFLOW_ROOT"] = os.getcwd() + if os.environ["CHIPFLOW_ROOT"] not in sys.path: + sys.path.append(os.environ["CHIPFLOW_ROOT"]) + return os.environ["CHIPFLOW_ROOT"] + + +# TODO: convert to pydantic, one truth of source for the schema +config_schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://chipflow.io/meta/chipflow.toml.schema.json", + "title": "chipflow.toml", + "type": "object", + "required": [ + "chipflow" + ], + "properties": { + "chipflow": { + "type": "object", + "required": [ + "steps", + "silicon" + ], + "additionalProperties": False, + "properties": { + "project_name": { + "type": "string", + }, + "top": { + "type": "object", + }, + "steps": { + "type": "object", + }, + "clocks": { + "type": "object", + "patternPropertues": { + ".+": {"type": "string"} + }, + }, + "resets": { + "type": "object", + "patternPropertues": { + ".+": {"type": "string"} + }, + }, + "silicon": { + "type": "object", + "required": [ + "process", + "package", + ], + "additionalProperties": False, + "properties": { + "process": { + "enum": ["sky130", "gf180", "customer1", "gf130bcd", "ihp_sg13g2"] + }, + "package": { + "enum": ["caravel", "cf20", "pga144"] + }, + "pads": {"$ref": "#/$defs/pin"}, + "power": {"$ref": "#/$defs/pin"}, + "debug": { + "type": "object", + "properties": { + "heartbeat": {"type": "boolean"} + } + } + }, + }, + }, + }, + }, + "$defs": { + "pin": { + "type": "object", + "additionalProperties": False, + "minProperties": 1, + "patternProperties": { + ".+": { + "type": "object", + "required": [ + "type", + "loc", + ], + "additionalProperties": False, + "properties": { + "type": { + "enum": ["io", "i", "o", "oe", "clock", "reset", "power", "ground"] + }, + "loc": { + "type": "string", + "pattern": "^[NSWE]?[0-9]+$" + }, + } + } + } + } + } +} + + +def _parse_config(): + chipflow_root = _ensure_chipflow_root() + config_file = f"{chipflow_root}/chipflow.toml" + return _parse_config_file(config_file) + + +def _parse_config_file(config_file): + with open(config_file, "rb") as f: + config_dict = tomli.load(f) + + try: + jsonschema.validate(config_dict, config_schema) + return config_dict + except jsonschema.ValidationError as e: + raise ChipFlowError(f"Syntax error in `chipflow.toml` at `{'.'.join(e.path)}`: {e.message}") diff --git a/chipflow_lib/cli.py b/chipflow_lib/cli.py index bf189416..193c4a92 100644 --- a/chipflow_lib/cli.py +++ b/chipflow_lib/cli.py @@ -1,173 +1,83 @@ # SPDX-License-Identifier: BSD-2-Clause - -import os -import sys -import inspect -import importlib import argparse -import tomli -import jsonschema - -from . import ChipFlowError - - -def _get_cls_by_reference(reference, context): - module_ref, _, class_ref = reference.partition(":") - try: - module_obj = importlib.import_module(module_ref) - except ModuleNotFoundError as e: - raise ChipFlowError(f"Module `{module_ref}` referenced by {context} is not found") - try: - return getattr(module_obj, class_ref) - except AttributeError as e: - raise ChipFlowError(f"Module `{module_ref}` referenced by {context} does not define " - f"`{class_ref}`") from None - - -def _ensure_chipflow_root(): - if "CHIPFLOW_ROOT" not in os.environ: - os.environ["CHIPFLOW_ROOT"] = os.getcwd() - if os.environ["CHIPFLOW_ROOT"] not in sys.path: - sys.path.append(os.environ["CHIPFLOW_ROOT"]) - return os.environ["CHIPFLOW_ROOT"] +import inspect +import sys +import traceback +import logging +from pprint import pformat -config_schema = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://chipflow.io/meta/chipflow.toml.schema.json", - "title": "chipflow.toml", - "type": "object", - "required": [ - "chipflow" - ], - "properties": { - "chipflow": { - "type": "object", - "required": [ - "steps", - "silicon" - ], - "additionalProperties": False, - "properties": { - "project_id": { - "type": "integer", - }, - "steps": { - "type": "object", - }, - "silicon": { - "type": "object", - "required": [ - "process", - "pad_ring", - "pads", - ], - "additionalProperties": False, - "properties": { - "process": { - "enum": ["sky130", "gf180", "customer1", "gf130bcd", "ihp_sg13g2"] - }, - "pad_ring": { - "enum": ["caravel", "cf20", "pga144"] - }, - "pads": { - "type": "object", - "additionalProperties": False, - "minProperties": 1, - "patternProperties": { - ".+": { - "type": "object", - "required": [ - "type", - "loc", - ], - "additionalProperties": False, - "properties": { - "type": { - "enum": ["io", "i", "o", "oe", "clk"] - }, - "loc": { - "type": "string", - "pattern": "^[NSWE]?[0-9]+$" - }, - } - } - } - }, - "power": { - "type": "object", - "additionalProperties": False, - "patternProperties": { - ".+": { - "type": "object", - "required": [ - "loc", - ], - "additionalProperties": False, - "properties": { - "loc": { - "type": "string", - "pattern": "^[NSWE]?[0-9]+$" - }, - } - } - } - }, - } - }, - }, - } - } -} +from . import ( + ChipFlowError, + _get_cls_by_reference, + _parse_config, +) +from .pin_lock import PinCommand -def _parse_config(): - chipflow_root = _ensure_chipflow_root() - config_file = f"{chipflow_root}/chipflow.toml" - return _parse_config_file(config_file) +logging.basicConfig(stream=sys.stdout, level=logging.WARNING) -def _parse_config_file(config_file): - with open(config_file, "rb") as f: - config_dict = tomli.load(f) - - try: - jsonschema.validate(config_dict, config_schema) - return config_dict - except jsonschema.ValidationError as e: - raise ChipFlowError(f"Syntax error in `chipflow.toml` at `{'.'.join(e.path)}`: {e.message}") +class UnexpectedError(ChipFlowError): + pass def run(argv=sys.argv[1:]): config = _parse_config() - steps = {} + commands = {} + commands["pin"] = PinCommand(config) + for step_name, step_reference in config["chipflow"]["steps"].items(): step_cls = _get_cls_by_reference(step_reference, context=f"step `{step_name}`") try: - steps[step_name] = step_cls(config) + commands[step_name] = step_cls(config) except Exception: raise ChipFlowError(f"Encountered error while initializing step `{step_name}` " - f"using `{step_reference}`") - - parser = argparse.ArgumentParser() - step_argument = parser.add_subparsers(dest="step", required=True) - for step_name, step_cls in steps.items(): - step_subparser = step_argument.add_parser(step_name, help=inspect.getdoc(step_cls)) + f"using `{step_reference}`") + + parser = argparse.ArgumentParser( + prog="chipflow", + description="Command line tool for interacting with the ChipFlow platform") + + parser.add_argument( + "--verbose", "-v", + dest="log_level", + action="append_const", + const=10, + help="increase verbosity of messages; can be supplied multiple times to increase verbosity" + ) + + command_argument = parser.add_subparsers(dest="command", required=True) + for command_name, command in commands.items(): + command_subparser = command_argument.add_parser(command_name, help=inspect.getdoc(command)) try: - step_cls.build_cli_parser(step_subparser) + command.build_cli_parser(command_subparser) except Exception: raise ChipFlowError(f"Encountered error while building CLI argument parser for " - f"step `{step_name}`") + f"step `{command_name}`") + # each verbose flag increases versbosity (e.g. -v -v, -vv, --verbose --verbose) + # cute trick using append_const and summing args = parser.parse_args(argv) - try: - steps[args.step].run_cli(args) - except ChipFlowError: - raise - except Exception: - raise ChipFlowError(f"Encountered error while running CLI for step `{args.step}`") - + if args.log_level: + log_level = max(logging.DEBUG, logging.WARNING - sum(args.log_level)) + logging.getLogger().setLevel(log_level) -if __name__ == '__main__': - run() + try: + try: + commands[args.command].run_cli(args) + except ChipFlowError: + raise + except Exception as e: + # convert to ChipFlowError so all handling is same. + raise UnexpectedError( + f"Unexpected error, please report to ChipFlow:\n" + f"args =\n{pformat(args)}\n" + f"traceback =\n{''.join(traceback.format_exception(e))}" + ) from e + except ChipFlowError as e: + cmd = args.command + if hasattr(args, "action"): + cmd += f" {args.action}" + print(f"Error while executing `{cmd}`: {e}") diff --git a/chipflow_lib/errors.py b/chipflow_lib/errors.py new file mode 100644 index 00000000..6dcb7917 --- /dev/null +++ b/chipflow_lib/errors.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: BSD-2-Clause +class ChipFlowError(Exception): + pass diff --git a/chipflow_lib/pin_lock.py b/chipflow_lib/pin_lock.py new file mode 100644 index 00000000..934f7ad4 --- /dev/null +++ b/chipflow_lib/pin_lock.py @@ -0,0 +1,169 @@ +# SPDX-License-Identifier: BSD-2-Clause +import inspect +import logging + +from pprint import pformat +from pathlib import Path +from typing import Any, List, Dict, Tuple + +from chipflow_lib import _parse_config, ChipFlowError +from chipflow_lib.platforms import PACKAGE_DEFINITIONS, PIN_ANNOTATION_SCHEMA, top_interfaces +from chipflow_lib.platforms.utils import LockFile, Package, PortMap, Port + +# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) +logger = logging.getLogger(__name__) + + +def count_member_pins(name: str, member: Dict[str, Any]) -> int: + "Counts the pins from amaranth metadata" + logger.debug( + f"count_pins {name} {member['type']} " + f"{member['annotations'] if 'annotations' in member else 'no annotations'}" + ) + if member['type'] == 'interface' and 'annotations' in member \ + and PIN_ANNOTATION_SCHEMA in member['annotations']: + return member['annotations'][PIN_ANNOTATION_SCHEMA]['width'] + elif member['type'] == 'interface': + width = 0 + for n, v in member['members'].items(): + width += count_member_pins('_'.join([name, n]), v) + return width + elif member['type'] == 'port': + return member['width'] + + +def allocate_pins(name: str, member: Dict[str, Any], pins: List[str]) -> Tuple[Dict[str, Port], List[str]]: + "Allocate pins based of Amaranth member metadata" + + pin_map = {} + logger.debug(f"allocate_pins: name={name}, pins={pins}") + logger.debug(f"member={pformat(member)}") + + if member['type'] == 'interface' and 'annotations' in member \ + and PIN_ANNOTATION_SCHEMA in member['annotations']: + logger.debug("matched PinSignature {sig}") + name = name + sig = member['annotations'][PIN_ANNOTATION_SCHEMA] + width = sig['width'] + pin_map[name] = {'pins': pins[0:width], + 'direction': sig['direction'], + 'type': 'io'} + logger.debug(f"added '{name}':{pin_map[name]} to pin_map") + return pin_map, pins[width:] + elif member['type'] == 'interface': + for k, v in member['members'].items(): + n = '_'.join([name, k]) + _map, pins = allocate_pins(n, v, pins) + pin_map |= _map + logger.debug(f"{pin_map},{_map}") + return pin_map, pins + elif member['type'] == 'port': + logger.warning(f"Port '{name}' has no PinSignature, pin allocation likely to be wrong") + name = name + width = member['width'] + pin_map[name] = {'pins': pins[0:width], + 'direction': member['dir'], + 'type': 'io'} + logger.debug(f"added '{name}':{pin_map[name]} to pin_map") + return pin_map, pins[width:] + else: + logging.debug(f"Shouldnt get here. member = {member}") + assert False + + +def lock_pins() -> None: + config = _parse_config() + used_pins = set() + oldlock = None + + lockfile = Path('pins.lock') + if lockfile.exists(): + json_string = lockfile.read_text() + oldlock = LockFile.model_validate_json(json_string) + + print(f"Locking pins: {'using pins.lock' if lockfile.exists() else ''}") + package_name = config["chipflow"]["silicon"]["package"] + + if package_name not in PACKAGE_DEFINITIONS: + logger.debug(f"Package '{package_name} is unknown") + package_type = PACKAGE_DEFINITIONS[package_name] + + package = Package(package_type=package_type) + for d in ("pads", "power"): + logger.debug(f"Checking [chipflow.silicon.{d}]:") + _map = {} + for k, v in config["chipflow"]["silicon"][d].items(): + pin = str(v['loc']) + used_pins.add(pin) + port = oldlock.package.check_pad(k, v) if oldlock else None + if port and port.pins != [pin]: + raise ChipFlowError( + f"chipflow.toml conflicts with pins.lock: " + f"{k} had pin {port.pins}, now {[pin]}." + ) + package.add_pad(k, v) + + logger.debug(f'Pins in use: {package_type.sortpins(used_pins)}') + + unallocated = package_type.pins - used_pins + + logger.debug(f"unallocated pins = {package_type.sortpins(unallocated)}") + + _, interfaces = top_interfaces(config) + + logger.debug(f"All interfaces:\n{pformat(interfaces)}") + + port_map = PortMap({}) + # we try to keep pins together for each interface + for component, iface in interfaces.items(): + for k, v in iface['interface']['members'].items(): + logger.debug(f"Interface {component}.{k}:") + logger.debug(pformat(v)) + width = count_member_pins(k, v) + logger.debug(f" {k}: total {width} pins") + old_ports = oldlock.port_map.get_ports(component, k) if oldlock else None + if old_ports: + logger.debug(f" {component}.{k} found in pins.lock, reusing") + logger.debug(pformat(old_ports)) + old_width = sum([len(p.pins) for p in old_ports.values()]) + if old_width != width: + raise ChipFlowError( + f"top level interface has changed size. " + f"Old size = {old_width}, new size = {width}" + ) + port_map.add_ports(component, k, old_ports) + else: + pins = package_type.allocate(unallocated, width) + if len(pins) == 0: + raise ChipFlowError("No pins were allocated by {package}") + logger.debug(f"allocated range: {pins}") + unallocated = unallocated - set(pins) + _map, _ = allocate_pins(k, v, pins) + port_map.add_ports(component, k, _map) + + newlock = LockFile(package=package, port_map=port_map, metadata=interfaces) + + with open('pins.lock', 'w') as f: + f.write(newlock.model_dump_json(indent=2, serialize_as_any=True)) + + +class PinCommand: + def __init__(self, config): + self.config = config + + def build_cli_parser(self, parser): + action_argument = parser.add_subparsers(dest="action") + action_argument.add_parser( + "lock", help=inspect.getdoc(self.lock).splitlines()[0]) + + def run_cli(self, args): + logger.debug(f"command {args}") + if args.action == "lock": + self.lock() + + def lock(self): + """Lock the pin map for the design. + + Will attempt to reuse previous pin positions. + """ + lock_pins() diff --git a/chipflow_lib/platforms/__init__.py b/chipflow_lib/platforms/__init__.py index e69de29b..9cf52f75 100644 --- a/chipflow_lib/platforms/__init__.py +++ b/chipflow_lib/platforms/__init__.py @@ -0,0 +1,3 @@ +from .silicon import * +from .sim import * +from .utils import * diff --git a/chipflow_lib/platforms/silicon.py b/chipflow_lib/platforms/silicon.py index 32eb6c7a..1a32624f 100644 --- a/chipflow_lib/platforms/silicon.py +++ b/chipflow_lib/platforms/silicon.py @@ -1,65 +1,114 @@ # SPDX-License-Identifier: BSD-2-Clause - +import logging import os import subprocess -from amaranth import * -from amaranth.lib import io +from dataclasses import dataclass + +from amaranth import Module, Signal, Cat, ClockDomain, ClockSignal, ResetSignal + +from amaranth.lib import wiring, io +from amaranth.lib.cdc import FFSynchronizer +from amaranth.lib.wiring import Component, In from amaranth.back import rtlil from amaranth.hdl import Fragment from amaranth.hdl._ir import PortDirection from .. import ChipFlowError - +from .utils import load_pinlock, Port __all__ = ["SiliconPlatformPort", "SiliconPlatform"] +logger = logging.getLogger(__name__) + + +def make_hashable(cls): + def __hash__(self): + return hash(id(self)) + + def __eq__(self, obj): + return id(self) == id(obj) + + cls.__hash__ = __hash__ + cls.__eq__ = __eq__ + return cls + + +HeartbeatSignature = wiring.Signature({"heartbeat_i": In(1)}) + + +@make_hashable +@dataclass +class Heartbeat(Component): + clock_domain: str = "sync" + counter_size: int = 23 + name: str = "heartbeat" + + def __init__(self, ports): + super().__init__(HeartbeatSignature) + self.ports = ports + + def elaborate(self, platform): + m = Module() + # Heartbeat LED (to confirm clock/reset alive) + heartbeat_ctr = Signal(self.counter_size) + getattr(m.d, self.clock_domain).__iadd__(heartbeat_ctr.eq(heartbeat_ctr + 1)) + + heartbeat_buffer = io.Buffer("o", self.ports.heartbeat) + m.submodules.heartbeat_buffer = heartbeat_buffer + m.d.comb += heartbeat_buffer.o.eq(heartbeat_ctr[-1]) + return m + class SiliconPlatformPort(io.PortLike): - def __init__(self, name, direction, width, *, invert=False): - if not isinstance(name, str): - raise TypeError(f"Name must be a string, not {name!r}") - if not (isinstance(width, int) and width >= 0): - raise TypeError(f"Width must be a non-negative integer, not {width!r}") - if not isinstance(invert, bool): - raise TypeError(f"'invert' must be a bool, not {invert!r}") - - self._direction = io.Direction(direction) + def __init__(self, + component: str, + name: str, + port: Port, + *, + invert: bool = False): + self._direction = io.Direction(port.direction) self._invert = invert - self._i = self._o = self._oe = None + self._i = self._o = self._oe = Signal(1) if self._direction in (io.Direction.Input, io.Direction.Bidir): - self._i = Signal(width, name=f"{name}__i") + self._i = Signal(port.width, name=f"{component}_{name}__i") if self._direction in (io.Direction.Output, io.Direction.Bidir): - self._o = Signal(width, name=f"{name}__o") - self._oe = Signal(width, name=f"{name}__oe") + self._o = Signal(port.width, name=f"{component}_{name}__o") + self._oe = Signal(1, name=f"{component}_{name}__oe") + self._pins = port.pins + logger.debug(f"Created SiliconPlatformPort {name}, width={len(port.pins)},dir{self._direction}") @property def i(self): if self._i is None: raise AttributeError("SiliconPlatformPort with output direction does not have an " - "input signal") + "input signal") return self._i @property def o(self): if self._o is None: raise AttributeError("SiliconPlatformPort with input direction does not have an " - "output signal") + "output signal") return self._o @property def oe(self): if self._oe is None: raise AttributeError("SiliconPlatformPort with input direction does not have an " - "output enable signal") + "output enable signal") return self._oe @property def direction(self): return self._direction + @property + def pins(self): + return self._pins + @property def invert(self): return self._invert @@ -73,7 +122,7 @@ def __len__(self): if self._direction is io.Direction.Bidir: assert len(self._i) == len(self._o) == len(self._oe) return len(self._i) - assert False # :nocov: + assert False # :nocov: def __getitem__(self, key): result = object.__new__(type(self)) @@ -103,6 +152,11 @@ def __add__(self, other): result._direction = direction return result + def __repr__(self): + return (f"SiliconPlatformPort(direction={repr(self._direction)}, width={len(self)}, " + f"i={repr(self._i)}, o={repr(self._o)}, oe={repr(self._oe)}, " + f"invert={repr(self._invert)})") + class IOBuffer(io.Buffer): def elaborate(self, platform): @@ -160,39 +214,58 @@ def elaborate(self, platform): class SiliconPlatform: - def __init__(self, pads): - self._pads = pads + def __init__(self, config): + self._config = config self._ports = {} self._files = {} - def request(self, name): + def instantiate_ports(self, m: Module): + if hasattr(self, "pinlock"): + return + + pinlock = load_pinlock() + for component, iface in pinlock.port_map.items(): + for k, v in iface.items(): + for name, port in v.items(): + self._ports[name] = SiliconPlatformPort(component, name, port) + + for clock, name in self._config["chipflow"]["clocks"].items(): + if name not in pinlock.package.clocks: + raise ChipFlowError("Unable to find clock {name} in pinlock") + + port_data = pinlock.package.clocks[name] + port = SiliconPlatformPort(component, name, port_data, invert=True) + self._ports[name] = port + if clock == 'default': + clock = 'sync' + setattr(m.domains, clock, ClockDomain(name=clock)) + clk_buffer = io.Buffer("i", port) + setattr(m.submodules, "clk_buffer_" + clock, clk_buffer) + m.d.comb += ClockSignal().eq(clk_buffer.i) + + for reset, name in self._config["chipflow"]["resets"].items(): + port_data = pinlock.package.resets[name] + port = SiliconPlatformPort(component, name, port_data) + self._ports[name] = port + rst_buffer = io.Buffer("i", port) + setattr(m.submodules, reset, rst_buffer) + setattr(m.submodules, reset + "_sync", FFSynchronizer(rst_buffer.i, ResetSignal())) + + self.pinlock = pinlock + + def request(self, name=None, **kwargs): if "$" in name: raise NameError(f"Reserved character `$` used in pad name `{name}`") - if name not in self._pads: - raise NameError(f"Pad `{name}` is not defined in chipflow.toml") - if name in self._ports: - raise NameError(f"Pad `{name}` has already been requested") - - pad_type = self._pads[name]["type"] - # `clk` is used for clock tree synthesis, but treated as `i` in frontend - if pad_type in ("i", "clk"): - direction = io.Direction.Input - elif pad_type in ("o", "oe"): - direction = io.Direction.Output - elif pad_type == "io": - direction = io.Direction.Bidir - else: - assert False - - self._ports[name] = port = SiliconPlatformPort(name, direction, 1) - return port + if name not in self._ports: + raise NameError(f"Pad `{name}` is not present in the pin lock") + return self._ports[name] def get_io_buffer(self, buffer): if isinstance(buffer, io.Buffer): result = IOBuffer(buffer.direction, buffer.port) elif isinstance(buffer, io.FFBuffer): result = FFBuffer(buffer.direction, buffer.port, - i_domain=buffer.i_domain, o_domain=buffer.o_domain) + i_domain=buffer.i_domain, o_domain=buffer.o_domain) else: raise TypeError(f"Unsupported buffer type {buffer!r}") @@ -215,7 +288,7 @@ def add_file(self, filename, content): def _check_clock_domains(self, fragment, sync_domain=None): for clock_domain in fragment.domains.values(): if clock_domain.name != "sync" or (sync_domain is not None and - clock_domain is not sync_domain): + clock_domain is not sync_domain): raise ChipFlowError("Only a single clock domain, called 'sync', may be used") sync_domain = clock_domain @@ -252,10 +325,10 @@ def build(self, elaboratable, name="top"): filename_b = filename.encode("utf-8") if filename.endswith(".v") or filename.endswith(".vh"): yosys_script.append(b"read_verilog -defer <<" + filename_b + b"\n" + - content + b"\n" + filename_b) + content + b"\n" + filename_b) elif filename.endswith(".sv"): yosys_script.append(b"read_verilog -defer -sv <<" + filename_b + b"\n" + - content + b"\n" + filename_b) + content + b"\n" + filename_b) else: raise ValueError(f"File `{filename}` is not supported by the build platform") yosys_script += [ @@ -275,3 +348,13 @@ def build(self, elaboratable, name="top"): "-o", output_rtlil.replace("\\", "/") ]) return output_rtlil + + def default_clock(m, platform, clock, reset): + # Clock generation + m.domains.sync = ClockDomain() + + clk = platform.request(clock) + m.d.comb += ClockSignal().eq(clk.i) + m.submodules.rst_sync = FFSynchronizer( + ~platform.request(reset).i, + ResetSignal()) diff --git a/chipflow_lib/platforms/sim.py b/chipflow_lib/platforms/sim.py index 19d5540e..d87286d5 100644 --- a/chipflow_lib/platforms/sim.py +++ b/chipflow_lib/platforms/sim.py @@ -1,7 +1,5 @@ # SPDX-License-Identifier: BSD-2-Clause -import argparse -import sys import os from pathlib import Path @@ -13,8 +11,6 @@ class SimPlatform: - from ..providers import sim as providers - def __init__(self): self.build_dir = os.path.join(os.environ['CHIPFLOW_ROOT'], 'build', 'sim') self.extra_files = dict() diff --git a/chipflow_lib/platforms/utils.py b/chipflow_lib/platforms/utils.py new file mode 100644 index 00000000..c8bcbe6b --- /dev/null +++ b/chipflow_lib/platforms/utils.py @@ -0,0 +1,421 @@ +import abc +import enum +import itertools +import logging +import pathlib +import pydantic + +from collections import OrderedDict, deque +from collections.abc import MutableMapping +from pprint import pformat +from typing import Set, List, Dict, Optional, Union, Literal + +from amaranth.lib import wiring, io, meta +from amaranth.lib.wiring import In, Out +from pydantic import BaseModel, ConfigDict + +from .. import ChipFlowError, _ensure_chipflow_root, _get_cls_by_reference + + +__all__ = ['PIN_ANNOTATION_SCHEMA', 'PinSignature', + 'OutputPinSignature', 'InputPinSignature', 'BidirPinSignature', + 'load_pinlock', "PACKAGE_DEFINITIONS", 'top_interfaces'] + + +logger = logging.getLogger(__name__) + + +def _chipflow_schema_uri(name: str, version: int) -> str: + return f"https://api.chipflow.com/schemas/{version}/{name}" + + +class _PinAnnotationModel(BaseModel): + model_config = ConfigDict(use_enum_values=True) + direction: io.Direction + width: int + + @classmethod + def _annotation_schema(cls): + schema = _PinAnnotationModel.model_json_schema() + schema['$schema'] = "https://json-schema.org/draft/2020-12/schema" + schema['$id'] = _chipflow_schema_uri("pin-annotation", 0) + return schema + + def __init__(self, **kwargs): + kwargs['url'] = _chipflow_schema_uri("pin-annotation", 0) + super().__init__(**kwargs) + + +class _PinAnnotation(meta.Annotation): + schema = _PinAnnotationModel._annotation_schema() + + def __init__(self, **kwargs): + self.model = _PinAnnotationModel(**kwargs) + + @property + def origin(self): # type: ignore + return self.model + + def as_json(self): # type: ignore + return self.model.model_dump() + + +PIN_ANNOTATION_SCHEMA = str(_chipflow_schema_uri("pin-annotation", 0)) + + +class PinSignature(wiring.Signature): + """Amaranth Signtaure used to decorate wires that would + usually be brought out onto a pin on the package. + """ + + def __init__(self, direction, width=1, init=None): + self._direction = direction + self._width = width + self._init = init + match direction: + case io.Direction.Bidir: + sig = { + "o": Out(width), + "oe": Out(1), + "i": In(width) + } + case io.Direction.Input: + sig = {"i": In(width)} + case io.Direction.Output: + sig = {"o": Out(width)} + case _: + assert False + + super().__init__(sig) + + def annotations(self, *args): + annotations = wiring.Signature.annotations(self, *args) + pin_annotation = _PinAnnotation(direction=self._direction, width=self._width) + return annotations + (pin_annotation,) + + def __repr__(self): + return f"PinSignature({self._direction}, {self._width})" + + +def OutputPinSignature(width, **kwargs): + return PinSignature(io.Direction.Output, width=width, **kwargs) + + +def InputPinSignature(width, **kwargs): + return PinSignature(io.Direction.Input, width=width, **kwargs) + + +def BidirPinSignature(width, **kwargs): + return PinSignature(io.Direction.Bidir, width=width, **kwargs) + + +Pin = Union[tuple, str] +PinSet = Set[Pin] +PinList = List[Pin] +Pins = Union[PinSet, PinList] + + +class _Side(enum.IntEnum): + N = 1 + E = 2 + S = 3 + W = 4 + + def __str__(self): + return f'{self.name}' + + +def _group_consecutive_items(ordering: PinList, lst: PinList) -> OrderedDict[int, List[PinList]]: + if not lst: + return {} + + grouped = [] + last = lst[0] + current_group = [last] + + logger.debug(f"_group_consecutive_items starting with {current_group}") + + for item in lst[1:]: + idx = ordering.index(last) + next = ordering[idx + 1] if idx < len(ordering) - 1 else None + logger.debug(f"inspecting {item}, index {idx}, next {next}") + if item == next: + current_group.append(item) + logger.debug("found consecutive, adding to current group") + else: + logger.debug("found nonconsecutive, creating new group") + grouped.append(current_group) + current_group = [item] + last = item + + grouped.append(current_group) + d = {} + for g in grouped: + # logger.debug(f"adding to group {len(g)} pins {g}") + d.setdefault(len(g), []).append(g) + return d + + +def _find_contiguous_sequence(ordering: PinList, lst: PinList, total: int) -> PinList: + """Find the next sequence of n consecutive numbers in a sorted list + + Args: + lst: Sorted list of numbers + n: Length of consecutive sequence to find + + Returns: + A slice indexing the first sequence of n consecutive numbers found within the given list + if unable to find a consecutive list, allocate as contigously as possible + """ + if not lst or len(lst) < total: + raise ChipFlowError("Invalid request to find_contiguous_argument") + + grouped = _group_consecutive_items(ordering, lst) + + ret = [] + n = total + + # start with longest contiguous section, then continue into following sections + keys = deque(grouped.keys()) + best = max(keys) + start = keys.index(best) + keys.rotate(start) + + for k in keys: + for g in grouped[k]: + assert n + len(ret) == total + if k >= n: + ret += g[0:min(n, k)] + return ret + else: + n = n - k + ret += g[0:k] + + return ret + + +class _BasePackageDef(pydantic.BaseModel, abc.ABC): + """ + Abstract base class for the definition of a package + """ + # Used by pydantic to differentate when deserialising, + # override appropriately when you subclass + type: Literal["_BasePackageDef"] = "_BasePackageDef" + name: str + + @property + @abc.abstractmethod + def pins(self) -> PinSet: + ... + + @abc.abstractmethod + def allocate(self, available: PinSet, width: int) -> PinList: + ... + + def to_string(pins: Pins): + return [''.join(map(str, t)) for t in pins] + + def sortpins(self, pins: Pins) -> PinList: + return list(pins).sort() + + +class _BareDiePackageDef(_BasePackageDef): + """Definition of a package with pins on four sides, labelled north, south, east, west + with an integer identifier within each side. + """ + + # Used by pydantic to differentate when deserialising + type: Literal["_QuadPackageDef"] = "_QuadPackageDef" + + width: int + height: int + + def model_post_init(self, __context): + self._ordered_pins = sorted( + list(itertools.product((_Side.N, _Side.S), range(self.width))) + + list(itertools.product((_Side.W, _Side.E), range(self.height)))) + return super().model_post_init(__context) + + @property + def pins(self) -> PinSet: + return set(self._ordered_pins) + + def allocate(self, available: PinSet, width: int) -> PinList: + avail_n = self.sortpins(available) + logger.debug(f"_BareDiePackageDef.allocate {width} from {len(avail_n)} remaining") + ret = _find_contiguous_sequence(self._ordered_pins, avail_n, width) + logger.debug(f"_BareDiePackageDef.returned {ret}") + assert len(ret) == width + return ret + + +class _QuadPackageDef(_BasePackageDef): + """Definiton of a PGA package with `size` pins + + This is package with `size` pins, numbered, with the assumption that adjacent pins + are numbered close together. + """ + + # Used by pydantic to differentate when deserialising + type: Literal["_PGAPackageDef"] = "_PGAPackageDef" + + width:int + height: int + + def model_post_init(self, __context): + self._ordered_pins = sorted( + [str(i) for i in range(1, self.width * 2 + self.height * 2)]) + return super().model_post_init(__context) + + + @property + def pins(self) -> PinSet: + return set(self._ordered_pins) + + def allocate(self, available: Set[str], width: int) -> List[str]: + avail_n = sorted(available) + logger.debug(f"QuadPackageDef.allocate {width} from {len(avail_n)} remaining: {available}") + ret = _find_contiguous_sequence(self._ordered_pins, avail_n, width) + logger.debug(f"QuadPackageDef.returned {ret}") + assert len(ret) == width + return ret + + def sortpins(self, pins: Union[List[str], Set[str]]) -> List[str]: + return sorted(list(pins), key=int) + + +# Add any new package types to both PACKAGE_DEFINITIONS and the PackageDef union +PACKAGE_DEFINITIONS = { + "pga144": _QuadPackageDef(name="pga144", width=36, height=36), + "cf20": _BareDiePackageDef(name="cf20", width=7, height=3) +} + +PackageDef = Union[_QuadPackageDef, _BasePackageDef] + + +class Port(pydantic.BaseModel): + type: str + pins: List[str] + direction: Optional[str] = None + + @property + def width(self): + return len(self.pins) + + +class Package(pydantic.BaseModel): + package_type: PackageDef = pydantic.Field(discriminator="type") + power: Dict[str, Port] = {} + clocks: Dict[str, Port] = {} + resets: Dict[str, Port] = {} + + def check_pad(self, name: str, defn: dict): + match defn: + case {"type": "clock"}: + return self.clocks[name] if name in self.clocks else None + case {"type": "reset"}: + return self.resets[name] if name in self.clocks else None + case {"type": "power"}: + return self.power[name] if name in self.power else None + case {"type": "ground"}: + return self.power[name] if name in self.power else None + case _: + return None + + def add_pad(self, name: str, defn: dict): + match defn: + case {"type": "clock", "loc": loc}: + self.clocks[name] = Port(type="clock", pins=[loc], direction=io.Direction.Input) + case {"type": "reset", "loc": loc}: + self.resets[name] = Port(type="reset", pins=[loc], direction=io.Direction.Input) + case {"type": "power", "loc": loc}: + self.power[name] = Port(type="power", pins=[loc]) + case {"type": "ground", "loc": loc}: + self.power[name] = Port(type="ground", pins=[loc]) + case _: + pass + + +_Interface = Dict[str, Dict[str, Port]] + + +class PortMap(pydantic.RootModel[Dict[str, _Interface]], MutableMapping): + def __getitem__(self, key: str): + return self.root[key] + + def __setitem__(self, key: str, value: _Interface): + self.root[key] = value + + def __delitem__(self, key): + del self.root[key] + + def __iter__(self): + return iter(self.root) + + def __len__(self): + return len(self.root) + + def add_port(self, component: str, interface: str, port_name: str, port: Port): + if component not in self: + self[component] = {} + if interface not in self[component]: + self[component][interface] = {} + self[component][interface][port_name] = port + + def add_ports(self, component: str, interface: str, ports: Dict[str, Port]): + if component not in self: + self[component] = {} + self[component][interface] = ports + + def get_ports(self, component: str, name: str) -> Dict[str, Port]: + if component not in self: + return None + return self[component][name] + + +class LockFile(pydantic.BaseModel): + """ + Representation of a pin lock file. + + Attributes: + package: Information about package, power, clocks, reset etc + port_map: Mapping of components to interfaces to port + metadata: Amaranth metadata, for reference + """ + package: Package + port_map: PortMap + metadata: dict + + +def load_pinlock(): + chipflow_root = _ensure_chipflow_root() + lockfile = pathlib.Path(chipflow_root, 'pins.lock') + if lockfile.exists(): + json = lockfile.read_text() + return LockFile.model_validate_json(json) + raise ChipFlowError("Lockfile pins.lock not found. Run `chipflow pin lock`") + + +def top_interfaces(config): + interfaces = {} + top_components = config["chipflow"]["top"].items() + component_configs = {} + top = {} + + for name, conf in top_components: + if '.' in name: + assert conf is dict + logger.debug("Config found for {name}") + component_configs[name.split('.')[0]] = conf + + for name, ref in top_components: + cls = _get_cls_by_reference(ref, context=f"top component: {name}") + if name in component_configs: + top[name] = cls(component_configs[name]) + else: + top[name] = cls() + logger.debug(f"top members for {name}:\n{pformat(top[name].metadata.origin.signature.members)}") + # logger.debug(f"adding\n'{name}':{pformat(top[name].metadata.as_json())} to interfaces") + interfaces[name] = top[name].metadata.as_json() + + return top, interfaces diff --git a/chipflow_lib/providers/__init__.py b/chipflow_lib/providers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/chipflow_lib/providers/board_ulx3s.py b/chipflow_lib/providers/board_ulx3s.py deleted file mode 100644 index 7c38c008..00000000 --- a/chipflow_lib/providers/board_ulx3s.py +++ /dev/null @@ -1,150 +0,0 @@ -# SPDX-License-Identifier: BSD-2-Clause - -from amaranth import * -from amaranth_boards.ulx3s import * -from amaranth.lib.cdc import ResetSynchronizer -from amaranth.lib import io, wiring -from amaranth.lib.wiring import In - -from amaranth_soc import gpio - -from amaranth_orchard.memory.spimemio import QSPIPins -from amaranth_orchard.io.uart import UARTPins -from amaranth_orchard.memory.hyperram import HyperRAMPins - - -class QSPIFlashProvider(Elaboratable): - def __init__(self): - self.pins = QSPIPins() - - def elaborate(self, platform): - m = Module() - - flash = platform.request("spi_flash", dir=dict(cs='-', copi='-', cipo='-', wp='-', hold='-')) - # Flash clock requires a special primitive to access in ECP5 - m.submodules.usrmclk = Instance( - "USRMCLK", - i_USRMCLKI=self.pins.clk_o, - i_USRMCLKTS=ResetSignal(), # tristate in reset for programmer accesss - a_keep=1, - ) - # IO pins and buffers - m.submodules += Instance( - "OBZ", - o_O=flash.cs.io, - i_I=self.pins.csn_o, - i_T=ResetSignal(), - ) - # Pins in order - data_pins = ["copi", "cipo", "wp", "hold"] - - for i in range(4): - m.submodules += Instance( - "BB", - io_B=getattr(flash, data_pins[i]).io, - i_I=self.pins.d_o[i], - i_T=~self.pins.d_oe[i], - o_O=self.pins.d_i[i] - ) - return m - - -class LEDGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(8) - - def elaborate(self, platform): - m = Module() - for i in range(8): - led = io.Buffer("o", platform.request("led", i, dir="-")) - m.submodules[f"led_{i}"] = led - m.d.comb += led.o.eq(self.pins[i].o) - return m - - -class ButtonGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(2) - - def elaborate(self, platform): - m = Module() - for i in range(2): - btn = io.Buffer("i", platform.request("button_fire", i, dir="-")) - m.submodules[f"btn_{i}"] = btn - m.d.comb += self.pins[i].i.eq(btn.i) - return m - - -class UARTProvider(Elaboratable): - def __init__(self): - self.pins = UARTPins() - - def elaborate(self, platform): - m = Module() - uart_pins = platform.request("uart", 0, dir=dict(rx="-", tx="-", rts="-", dtr="-")) - 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 += [ - uart_tx.o.eq(self.pins.tx_o), - self.pins.rx_i.eq(uart_rx.i), - ] - return m - - -class HyperRAMProvider(Elaboratable): - def __init__(self): - self.pins = HyperRAMPins(cs_count=4) - - def elaborate(self, platform): - # Dual HyperRAM PMOD, starting at GPIO 0+/- - platform.add_resources([ - Resource( - "hyperram", - 0, - Subsignal("csn", Pins("9- 9+ 10- 10+", conn=("gpio", 0), dir='o')), - Subsignal("rstn", Pins("8+", conn=("gpio", 0), dir='o')), - Subsignal("clk", Pins("8-", conn=("gpio", 0), dir='o')), - Subsignal("rwds", Pins("7+", conn=("gpio", 0), dir='io')), - - Subsignal("dq", Pins("3- 2- 1- 0- 0+ 1+ 2+ 3+", conn=("gpio", 0), dir='io')), - - Attrs(IO_TYPE="LVCMOS33"), - ) - ]) - - hyperram = platform.request("hyperram", 0) - - m = Module() - m.d.comb += [ - hyperram.clk.o.eq(self.pins.clk_o), - hyperram.csn.o.eq(self.pins.csn_o), - hyperram.rstn.o.eq(self.pins.rstn_o), - - hyperram.rwds.o.eq(self.pins.rwds_o), - hyperram.rwds.oe.eq(self.pins.rwds_oe), - self.pins.rwds_i.eq(hyperram.rwds.i), - - hyperram.dq.o.eq(self.pins.dq_o), - hyperram.dq.oe.eq(self.pins.dq_oe), - self.pins.dq_i.eq(hyperram.dq.i), - ] - return m - - -class JTAGProvider(Elaboratable): - def __init__(self, cpu): - pass - - def elaborate(self, platform): - return Module() # JTAG is not connected anywhere - - -class ClockResetProvider(Elaboratable): - def __init__(self): - pass - - def elaborate(self, platform): - m = Module() - m.submodules.clk25 = clk25 = io.Buffer("i", platform.request("clk25", dir="-")) - m.d.comb += ClockSignal("sync").eq(clk25.i) - m.submodules.btn_pwr = btn_pwr = io.Buffer("i", platform.request("button_pwr", dir="-")) - m.submodules += ResetSynchronizer(btn_pwr.i, domain="sync") - return m diff --git a/chipflow_lib/providers/silicon.py b/chipflow_lib/providers/silicon.py deleted file mode 100644 index 71e04293..00000000 --- a/chipflow_lib/providers/silicon.py +++ /dev/null @@ -1,132 +0,0 @@ -# SPDX-License-Identifier: BSD-2-Clause - -from amaranth import * -from amaranth.lib.cdc import FFSynchronizer -from amaranth.lib import wiring -from amaranth.lib.wiring import In - -from amaranth_soc import gpio - -from amaranth_orchard.memory.spimemio import QSPIPins -from amaranth_orchard.io.uart import UARTPins -from amaranth_orchard.memory.hyperram import HyperRAMPins - - -class QSPIFlashProvider(Elaboratable): - def __init__(self): - self.pins = QSPIPins() - - def elaborate(self, platform): - m = Module() - m.d.comb += [ - platform.request("flash_clk").o.eq(self.pins.clk_o), - platform.request("flash_csn").o.eq(self.pins.csn_o), - ] - for index in range(4): - pin = platform.request(f"flash_d{index}") - m.d.comb += [ - self.pins.d_i[index].eq(pin.i), - pin.o.eq(self.pins.d_o[index]), - pin.oe.eq(self.pins.d_oe[index]) - ] - return m - - -class LEDGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(8) - - def elaborate(self, platform): - m = Module() - for index in range(8): - pin = platform.request(f"gpio_{index}") - m.d.comb += [ - self.pins[index].i.eq(pin.i), - pin.o.eq(self.pins[index].o), - pin.oe.eq(self.pins[index].oe), - ] - return m - - -class ButtonGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(2) - - def elaborate(self, platform): - m = Module() - for index in range(2): - pin = platform.request(f"btn_{index}") - m.d.comb += [ - self.pins[index].i.eq(pin.i), - pin.o.eq(self.pins[index].o), - pin.oe.eq(self.pins[index].oe), - ] - return m - - -class UARTProvider(Elaboratable): - def __init__(self): - self.pins = UARTPins() - - def elaborate(self, platform): - m = Module() - m.d.comb += [ - platform.request("uart_tx").o.eq(self.pins.tx_o), - self.pins.rx_i.eq(platform.request("uart_rx").i), - ] - return m - - -class HyperRAMProvider(Elaboratable): - def __init__(self): - self.pins = HyperRAMPins(cs_count=4) - - def elaborate(self, platform): - m = Module() - m.d.comb += [ - platform.request("ram_clk").o.eq(self.pins.clk_o), - platform.request("ram_rstn").o.eq(self.pins.rstn_o), - ] - - for index in range(4): - platform.request(f"ram_csn_{index}").o.eq(self.pins.csn_o[index]), - - rwds = platform.request("ram_rwds") - m.d.comb += [ - rwds.o.eq(self.pins.rwds_o), - rwds.oe.eq(self.pins.rwds_oe), - self.pins.rwds_i.eq(rwds.i), - ] - - for index in range(8): - dq = platform.request(f"ram_dq_{index}") - m.d.comb += [ - dq.o.eq(self.pins.dq_o[index]), - dq.oe.eq(self.pins.dq_oe[index]), - self.pins.dq_i[index].eq(dq.i), - ] - - return m - - -class JTAGProvider(Elaboratable): - def __init__(self, cpu): - self.cpu = cpu - - def elaborate(self, platform): - m = Module() - m.d.comb += [ - self.cpu.jtag_tck.eq(platform.request("jtag_tck").i), - self.cpu.jtag_tdi.eq(platform.request("jtag_tdi").i), - self.cpu.jtag_tms.eq(platform.request("jtag_tms").i), - platform.request("jtag_tdo").o.eq(self.cpu.jtag_tdo), - ] - return m - - -class ClockResetProvider(Elaboratable): - def elaborate(self, platform): - m = Module() - m.d.comb += ClockSignal("sync").eq(platform.request("sys_clk").i) - m.submodules.rst_sync = FFSynchronizer( - ~platform.request("sys_rstn").i, - ResetSignal("sync")) - return m diff --git a/chipflow_lib/providers/sim.py b/chipflow_lib/providers/sim.py deleted file mode 100644 index bdf9bef2..00000000 --- a/chipflow_lib/providers/sim.py +++ /dev/null @@ -1,68 +0,0 @@ -# SPDX-License-Identifier: BSD-2-Clause - -from amaranth import * -from amaranth.lib import wiring -from amaranth.lib.wiring import In - -from amaranth_soc import gpio - -from amaranth_orchard.memory.spimemio import QSPIPins -from amaranth_orchard.io.uart import UARTPins -from amaranth_orchard.memory.hyperram import HyperRAMPins - - -class QSPIFlashProvider(Elaboratable): - def __init__(self): - self.pins = QSPIPins() - - def elaborate(self, platform): - return platform.add_model("spiflash_model", self.pins, edge_det=['clk_o', 'csn_o']) - - -class LEDGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(8) - - def elaborate(self, platform): - return Module() - - -class ButtonGPIOProvider(wiring.Component): - pins: In(gpio.PinSignature()).array(2) - - def elaborate(self, platform): - m = Module() - for i in range(2): - m.d.comb += self.pins[i].i.eq(platform.buttons[i]) - return m - - -class UARTProvider(Elaboratable): - def __init__(self): - self.pins = UARTPins() - - def elaborate(self, platform): - return platform.add_model("uart_model", self.pins, edge_det=[]) - - -class HyperRAMProvider(Elaboratable): - def __init__(self): - self.pins = HyperRAMPins(cs_count=4) - - def elaborate(self, platform): - return platform.add_model("hyperram_model", hram, edge_det=['clk_o']) - - -class JTAGProvider(Elaboratable): - def __init__(self, cpu): - pass - - def elaborate(self, platform): - return Module() # JTAG is not connected anywhere - - -class ClockResetProvider(Elaboratable): - def elaborate(self, platform): - m = Module() - m.d.comb += ClockSignal("sync").eq(platform.clk) - m.d.comb += ResetSignal("sync").eq(platform.rst) - return m diff --git a/chipflow_lib/steps/silicon.py b/chipflow_lib/steps/silicon.py index 1116ff8c..0ecbd187 100644 --- a/chipflow_lib/steps/silicon.py +++ b/chipflow_lib/steps/silicon.py @@ -1,32 +1,61 @@ # SPDX-License-Identifier: BSD-2-Clause +import argparse +import importlib.metadata +import inspect +import json +import logging import os +import requests +import subprocess import sys import time -import json -import pprint -import inspect -import argparse -import subprocess -import importlib.metadata -import requests +import dotenv + +from amaranth import * from .. import ChipFlowError -from ..platforms.silicon import SiliconPlatform +from ..platforms import SiliconPlatform, top_interfaces + + +logger = logging.getLogger(__name__) + + +class SiliconTop(Elaboratable): + def __init__(self, config={}): + self._config = config + + def elaborate(self, platform: SiliconPlatform): + m = Module() + + platform.instantiate_ports(m) + + # heartbeat led (to confirm clock/reset alive) + if ("config" in self._config["chipflow"]["silicon"] and + self._config["chipflow"]["silicon"]["debug"]["heartbeat"]): + heartbeat_ctr = Signal(23) + m.d.sync += heartbeat_ctr.eq(heartbeat_ctr + 1) + m.d.comb += platform.request("heartbeat").o.eq(heartbeat_ctr[-1]) + + top, interfaces = top_interfaces(self._config) + for n, t in top.items(): + setattr(m.submodules, n, t) + + return m class SiliconStep: """Prepare and submit the design for an ASIC.""" - def __init__(self, config): - self.project_id = config["chipflow"].get("project_id") + self.config = config + self.project_name = config["chipflow"].get("project_name") self.silicon_config = config["chipflow"]["silicon"] - self.platform = SiliconPlatform(pads=self.silicon_config["pads"]) + self.platform = SiliconPlatform(config) def build_cli_parser(self, parser): action_argument = parser.add_subparsers(dest="action") - prepare_subparser = action_argument.add_parser( + action_argument.add_parser( "prepare", help=inspect.getdoc(self.prepare).splitlines()[0]) submit_subparser = action_argument.add_parser( "submit", help=inspect.getdoc(self.submit).splitlines()[0]) @@ -36,7 +65,7 @@ def build_cli_parser(self, parser): def run_cli(self, args): if args.action == "submit" and not args.dry_run: - if self.project_id is None: + if self.project_name is None: raise ChipFlowError( "Key `chipflow.project_id` is not defined in chipflow.toml; " "see https://chipflow.io/beta for details on how to join the beta") @@ -55,11 +84,12 @@ def prepare(self): Returns the path to the RTLIL file. """ - raise NotImplementedError + return self.platform.build(SiliconTop(self.config), name=self.config["chipflow"]["project_name"]) def submit(self, rtlil_path, *, dry_run=False): """Submit the design to the ChipFlow cloud builder. """ + dotenv.load_dotenv() git_head = subprocess.check_output( ["git", "-C", os.environ["CHIPFLOW_ROOT"], "rev-parse", "HEAD"], @@ -69,8 +99,8 @@ def submit(self, rtlil_path, *, dry_run=False): "status", "--porcelain", "--untracked-files=no"])) submission_name = git_head if git_dirty: + logging.warning("Git tree is dirty, submitting anyway!") submission_name += f"-dirty.{time.strftime('%Y%m%d%M%H%S', time.gmtime())}" - dep_versions = { "python": sys.version.split()[0] } @@ -87,18 +117,27 @@ def submit(self, rtlil_path, *, dry_run=False): except importlib.metadata.PackageNotFoundError: dep_versions[package] = None data = { - "projectId": self.project_id, + "projectId": self.project_name, "name": submission_name, } + + pads = {} + for iface, port in self.platform._ports.items(): + width = len(port.pins) + print(f"iface={iface}, port={port}, dir={port.direction}, width={width}") + if width > 1: + for i in range(width): + padname = f"{iface}{i}" + print(f"padname={padname}, port={port}, loc={port.pins[i:i+1]}, " + f"dir={port.direction}, width={width}") + pads[padname] = {'loc': port.pins[i:i+1], 'dir': port.direction} + config = { "dependency_versions": dep_versions, "silicon": { "process": self.silicon_config["process"], - "pad_ring": self.silicon_config["pad_ring"], - "pads": { - pad_name: self.silicon_config["pads"][pad_name] - for pad_name in self.platform._ports - }, + "package": self.silicon_config["package"], + "pads": pads, "power": self.silicon_config.get("power", {}) } } @@ -107,6 +146,8 @@ def submit(self, rtlil_path, *, dry_run=False): print(f"files['config']=\n{json.dumps(config, indent=2)}") return + logger.info(f"Submitting {submission_name} for project {self.project_name}") + resp = requests.post( os.environ.get("CHIPFLOW_API_ENDPOINT", "https://app.chipflow-infra.com/api/builds"), auth=(os.environ["CHIPFLOW_API_KEY_ID"], os.environ["CHIPFLOW_API_KEY_SECRET"]), @@ -135,3 +176,5 @@ def submit(self, rtlil_path, *, dry_run=False): else: print(f"{resp_data['msg']} (#{resp_data['id']}: {resp_data['name']}); " f"{resp_data['url']}") + else: + ChipFlowError(f"Unexpected response from API: {resp}") From e415a2654820ae54ce14169f50560714164b03b6 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 11 Feb 2025 16:30:04 +0000 Subject: [PATCH 3/6] tests: Update tests for pin-lock functionality --- tests/fixtures/chipflow-flexic.toml | 35 ---------- tests/fixtures/mock.toml | 25 +++++++ tests/fixtures/mock_top.py | 47 +++++++++++++ tests/test_silicon_platform.py | 15 ++-- tests/test_silicon_step.py | 21 +++--- tests/test_utils.py | 105 ++++++++++++++++++++++++++++ 6 files changed, 199 insertions(+), 49 deletions(-) delete mode 100644 tests/fixtures/chipflow-flexic.toml create mode 100644 tests/fixtures/mock.toml create mode 100644 tests/fixtures/mock_top.py create mode 100644 tests/test_utils.py diff --git a/tests/fixtures/chipflow-flexic.toml b/tests/fixtures/chipflow-flexic.toml deleted file mode 100644 index 2a1bbaa5..00000000 --- a/tests/fixtures/chipflow-flexic.toml +++ /dev/null @@ -1,35 +0,0 @@ -[chipflow] -project_id = 123 - -[chipflow.steps] -silicon = "thermostat.steps.silicon:MySiliconStep" - -[chipflow.silicon] -process = "customer1" -pad_ring = "cf20" - -[chipflow.silicon.pads] -threshold_0 = { type = "i", loc = "S1" } -threshold_1 = { type = "i", loc = "S2" } -threshold_2 = { type = "i", loc = "S3" } -threshold_3 = { type = "i", loc = "S4" } -threshold_4 = { type = "i", loc = "S5" } -threshold_5 = { type = "i", loc = "S6" } -threshold_6 = { type = "i", loc = "S7" } - -sclk = { type = "o", loc = "W3" } -sdi = { type = "i", loc = "W2" } -sdo = { type = "o", loc = "W1" } - -cool = { type = "o", loc = "E1" } -heat = { type = "o", loc = "E3" } - -ssn = { type = "o", loc = "N2" } -sys_clk = { type = "clk", loc = "N3" } -sys_rstn = { type = "i", loc = "N4" } - -[chipflow.silicon.power] -vss = { loc = "N1" } -vssio = { loc = "N5" } -vddio = { loc = "N6" } -vdd = { loc = "N7" } diff --git a/tests/fixtures/mock.toml b/tests/fixtures/mock.toml new file mode 100644 index 00000000..72e319e7 --- /dev/null +++ b/tests/fixtures/mock.toml @@ -0,0 +1,25 @@ +[chipflow] +project_name = "proj-name" + +[chipflow.steps] +silicon = "chipflow_lib.steps.silicon:SiliconStep" + +[chipflow.silicon] +process = "ihp_sg13g2" +package = "pga144" + +[chipflow.clocks] +default = 'sys_clk' + +[chipflow.resets] +default = 'sys_rst_n' + +[chipflow.silicon.pads] +sys_clk = { type = "clk", loc = "N3" } +sys_rst_n = { type = "i", loc = "N4" } + +[chipflow.silicon.power] +vss = { loc = "N1" } +vssio = { loc = "N5" } +vddio = { loc = "N6" } +vdd = { loc = "N7" } diff --git a/tests/fixtures/mock_top.py b/tests/fixtures/mock_top.py new file mode 100644 index 00000000..185cae41 --- /dev/null +++ b/tests/fixtures/mock_top.py @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: BSD-2-Clause +from amaranth import Module +from amaranth.lib import wiring +from amaranth.lib.wiring import In, Out + +from chipflow_lib.platforms import InputPinSignature, OutputPinSignature, BidirPinSignature + +__all__ = ["MockTop"] + +TestSignature1 = wiring.Signature({ + "a": In(InputPinSignature(1)), + "b": In(InputPinSignature(5)), + "c": Out(OutputPinSignature(1)), + "d": Out(OutputPinSignature(10)), + "e": In(BidirPinSignature(1)), + "f": In(BidirPinSignature(7)), +}) + +TestSignature2 = wiring.Signature({ + "a": Out(OutputPinSignature(1)), + "b": Out(OutputPinSignature(5)), + "c": In(InputPinSignature(1)), + "d": In(InputPinSignature(10)), + "e": Out(BidirPinSignature(1)), + "f": Out(BidirPinSignature(7)), +}) + + +# --------- + +class MockTop(wiring.Component): + def __init__(self): + # Top level interfaces + + interfaces = { + "test1" : Out(TestSignature1), + "test2": Out(TestSignature2) + } + + super().__init__(interfaces) + + def elaborate(self, platform): + m = Module() + for inpin, outpin in zip(self.test1.members, self.test2.members): + m.d.comb += inpin.eq(outpin) + + return m \ No newline at end of file diff --git a/tests/test_silicon_platform.py b/tests/test_silicon_platform.py index 495bbe2c..9795e0e0 100644 --- a/tests/test_silicon_platform.py +++ b/tests/test_silicon_platform.py @@ -2,10 +2,11 @@ # SPDX-License-Identifier: BSD-2-Clause import os - import unittest + +import tomli + from amaranth import * -from amaranth.hdl import Fragment from amaranth.hdl._ir import Design from chipflow_lib import ChipFlowError @@ -15,19 +16,23 @@ class SiliconPlatformTestCase(unittest.TestCase): def setUp(self): os.environ["CHIPFLOW_ROOT"] = os.path.dirname(os.path.dirname(__file__)) + current_dir = os.path.dirname(__file__) + customer_config = f"{current_dir}/fixtures/mock.toml" + with open(customer_config, "rb") as f: + self.config = tomli.load(f) def test_sync_domain_works(self): m = Module() m.domains += ClockDomain("sync") - fragment = SiliconPlatform(pads={})._prepare(m) + fragment = SiliconPlatform(self.config)._prepare(m) self.assertIsInstance(fragment, Design) def test_subfragment_works(self): m = Module() m.submodules += Module() - fragment = SiliconPlatform(pads={})._prepare(m) + fragment = SiliconPlatform(self.config)._prepare(m) self.assertIsInstance(fragment, Design) def test_wrong_clock_domain_name(self): @@ -37,4 +42,4 @@ def test_wrong_clock_domain_name(self): with self.assertRaisesRegex( ChipFlowError, r"^Only a single clock domain, called 'sync', may be used$"): - SiliconPlatform(pads={}).build(m) + SiliconPlatform(self.config).build(m) diff --git a/tests/test_silicon_step.py b/tests/test_silicon_step.py index ef8fce24..0df19433 100644 --- a/tests/test_silicon_step.py +++ b/tests/test_silicon_step.py @@ -1,11 +1,11 @@ # SPDX-License-Identifier: BSD-2-Clause - import io import json import os import tomli import unittest from contextlib import redirect_stdout +from pprint import pformat from unittest.mock import patch from chipflow_lib.steps.silicon import SiliconStep @@ -29,7 +29,7 @@ def json(self): "msg": "msg", "url": "https://example.com/build-url/", "name": "name", - "id": 123, + "id": "proj-name", }, 200) return MockResponse(None, 404) @@ -41,9 +41,10 @@ def setUp(self): os.environ["CHIPFLOW_API_KEY_ID"] = "keyid" os.environ["CHIPFLOW_API_KEY_SECRET"] = "keysecret" + @patch('dotenv.load_dotenv') @patch('requests.post', side_effect=mocked_requests_post) - def test_submit_happy_path(self, mock_requests_post): - customer_config = f"{current_dir}/fixtures/chipflow-flexic.toml" + def test_submit_happy_path(self, mock_requests_post, mock_dotenv): + customer_config = f"{current_dir}/fixtures/mock.toml" with open(customer_config, "rb") as f: config_dict = tomli.load(f) @@ -53,7 +54,7 @@ def test_submit_happy_path(self, mock_requests_post): with redirect_stdout(f): silicon_step.submit(current_dir + "/fixtures/mock.rtlil") output = f.getvalue() - assert 'msg (#123: name); https://example.com/build-url/' in output, "The printed output is correct." + assert 'msg (#proj-name: name); https://example.com/build-url/' in output, "The printed output is correct." args = mock_requests_post.call_args_list[0][0] kwargs = mock_requests_post.call_args_list[0][1] @@ -63,7 +64,7 @@ def test_submit_happy_path(self, mock_requests_post): rtlil = files["rtlil"].read() assert args[0] == 'https://app.chipflow-infra.com/api/builds' assert kwargs["auth"] == ("keyid", "keysecret") - assert data["projectId"] == 123 + assert data["projectId"] == 'proj-name' assert isinstance(data["name"], str), "Name is a string" assert list(config["dependency_versions"]) == [ "python", @@ -73,10 +74,10 @@ def test_submit_happy_path(self, mock_requests_post): "amaranth-orchard", "amaranth-vexriscv", ], "We have entries for the the dependency versions" + print(pformat(config)) assert config["silicon"] == { - 'process': 'customer1', - 'pad_ring': - 'cf20', + 'process': 'ihp_sg13g2', + 'package': 'pga144', 'pads': {}, 'power': { 'vss': {'loc': 'N1'}, @@ -86,3 +87,5 @@ def test_submit_happy_path(self, mock_requests_post): } } assert rtlil == b"fake-rtlil", "The RTL file was passed through." + + assert mock_dotenv.called diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 00000000..e0f77280 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: BSD-2-Clause +import itertools +import logging +import pytest #noqa + +from pprint import pformat + +from amaranth.lib import io + +from chipflow_lib.platforms.utils import PinSignature, OutputPinSignature, InputPinSignature, BidirPinSignature, _PinAnnotation, _PinAnnotationModel +from chipflow_lib.platforms.utils import PinList, _group_consecutive_items,_find_contiguous_sequence, _Side + + +logger = logging.getLogger(__name__) + + +def gen_quad_pins(width, height) -> PinList: + return sorted( + [e for e in itertools.product((_Side.N, _Side.S), range(width))] + + [e for e in itertools.product((_Side.W, _Side.E), range(height))] + ) + + +def test_group_consecutive_items_null(): + ordering = gen_quad_pins(50,60) + pins = ordering.copy() + groups = _group_consecutive_items(pins,pins) + assert len(groups.keys()) == 1 + assert len(ordering) in groups.keys() + +def test_group_consecutive_items_nonconsecutive(): + ordering = gen_quad_pins(50,60) + pins = ordering[0:6] + ordering[7:70] + ordering[71:180] + ordering[181:] + logger.debug(f"{ordering} {pins}") + groups = _group_consecutive_items(ordering,pins) + logger.debug(f"\n{pformat(groups)}") + assert len(ordering) == 50*2 + 60*2 + assert len(groups.keys()) == 4 + assert sum(groups.keys()) == len(ordering) - 3 + assert 6 in groups.keys() + assert 70 - 7 in groups.keys() + assert 180 - 71 in groups.keys() + assert len(ordering) -181 in groups.keys() + +def test_find_contiguous_sequence(): + ordering = gen_quad_pins(50,60) + pins = ordering[0:6] + ordering[7:70] + ordering[71:180] + ordering[181:] + seq = _find_contiguous_sequence(ordering, pins, 120) + logger.debug(f"\n{pformat(seq)}") + logger.debug(f"{ordering[71:180] + ordering[181:191]}") + assert len(seq) == 120 + assert seq == ordering[71:180] + ordering[181:192] + + +def test_pin_signature(): + sig_bidir = PinSignature(io.Direction.Bidir, width=8) + assert isinstance(sig_bidir, PinSignature) + assert sig_bidir._direction == io.Direction.Bidir + assert sig_bidir._width == 8 + assert "o" in sig_bidir.members + assert "oe" in sig_bidir.members + assert "i" in sig_bidir.members + + sig_output = OutputPinSignature(width=4) + assert isinstance(sig_output, PinSignature) + assert sig_output._direction == io.Direction.Output + assert sig_output._width == 4 + assert "o" in sig_output.members + assert "oe" not in sig_output.members + assert "i" not in sig_output.members + + sig_input = InputPinSignature(width=2) + assert isinstance(sig_input, PinSignature) + assert sig_input._direction == io.Direction.Input + assert sig_input._width == 2 + assert "o" not in sig_input.members + assert "oe" not in sig_output.members + assert "i" in sig_input.members + + sig_bidir_fn = BidirPinSignature(width=1) + assert isinstance(sig_bidir_fn, PinSignature) + assert sig_bidir_fn._direction == io.Direction.Bidir + assert sig_bidir_fn._width == 1 + assert "o" in sig_bidir_fn.members + assert "oe" in sig_bidir_fn.members + assert "i" in sig_bidir_fn.members + +def test_pin_annotation_model(): + model = _PinAnnotationModel(direction=io.Direction.Output, width=32) + assert model.direction == "o" + assert model.width == 32 + +def test_pin_annotation(): + annotation = _PinAnnotation(direction=io.Direction.Input, width=16) + assert isinstance(annotation, _PinAnnotation) + assert annotation.model.direction == "i" + assert annotation.model.width == 16 + +def test_pin_annotation_as_json(): + annotation = _PinAnnotation(direction=io.Direction.Bidir, width=8) + json_output = annotation.as_json() + print(f"json_output: {json_output}") # Debug print using print() + assert isinstance(json_output, dict) + assert json_output["direction"] == "io" + assert json_output["width"] == 8 \ No newline at end of file From 3a84216ba88f3186637baba6ad87d6846bff3466 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 11 Feb 2025 16:33:13 +0000 Subject: [PATCH 4/6] Update pyproject.toml and add pdm.lock --- .github/workflows/main.yaml | 1 - .gitignore | 2 +- pdm.lock | 1397 +++++++++++++++++++++++++++++++++++ pyproject.toml | 51 +- 4 files changed, 1433 insertions(+), 18 deletions(-) create mode 100644 pdm.lock diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 3932ee8e..c7980699 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -19,7 +19,6 @@ jobs: uses: pdm-project/setup-pdm@v4 - name: Install dependencies run: | - pdm lock --dev pdm install - name: Run tests run: | diff --git a/.gitignore b/.gitignore index d7b78032..584bee2d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,13 @@ __pycache__/ *.egg-info /dist +/log* # pdm /.pdm-plugins /.pdm-python /.pdm-build /.venv -/pdm.lock # pytest /.pytest_cache diff --git a/pdm.lock b/pdm.lock new file mode 100644 index 00000000..80606cf1 --- /dev/null +++ b/pdm.lock @@ -0,0 +1,1397 @@ +# This file is @generated by PDM. +# It is not intended for manual editing. + +[metadata] +groups = ["default", "lint", "test"] +strategy = ["inherit_metadata"] +lock_version = "4.5.0" +content_hash = "sha256:c101435debd2c023d993687fb5bf233f633eecf35b47e75dc57b0e4dc6c38a12" + +[[metadata.targets]] +requires_python = "~=3.10" + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +requires_python = ">=3.9" +summary = "A collection of accessible pygments styles" +groups = ["default"] +dependencies = [ + "pygments>=1.5", +] +files = [ + {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"}, + {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"}, +] + +[[package]] +name = "alabaster" +version = "0.7.16" +requires_python = ">=3.9" +summary = "A light, configurable Sphinx theme" +groups = ["default", "test"] +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + +[[package]] +name = "amaranth" +version = "0.5.4" +requires_python = "~=3.8" +summary = "Amaranth hardware definition language" +groups = ["default"] +dependencies = [ + "Jinja2~=3.0", + "importlib-resources; python_version < \"3.9\"", + "jschon~=0.11.1", + "pyvcd<0.5,>=0.2.2", +] +files = [ + {file = "amaranth-0.5.4-py3-none-any.whl", hash = "sha256:ce7473b4220acc78474474fd132177ca545fb144d4e69e1c7dbfc2ed7d32bcf3"}, + {file = "amaranth-0.5.4.tar.gz", hash = "sha256:a0ea7ffe358ab00d5524b53c43277d279723437be146c8250e26f6b349b8a4fd"}, +] + +[[package]] +name = "amaranth-boards" +version = "0.1.dev253" +requires_python = "~=3.9" +git = "https://github.com/amaranth-lang/amaranth-boards" +revision = "9d97c4816288c9c2cc304d9280c2c63178d50d2f" +summary = "Board and connector definitions for Amaranth HDL" +groups = ["default"] +dependencies = [ + "amaranth<0.7,>=0.5", +] + +[[package]] +name = "amaranth-soc" +version = "0.1a1.dev24" +requires_python = "~=3.9" +git = "https://github.com/amaranth-lang/amaranth-soc" +revision = "5c43cf58f15d9cd9c69ff83c97997708d386b2dc" +summary = "System on Chip toolkit for Amaranth HDL" +groups = ["default"] +dependencies = [ + "amaranth<0.6,>=0.5", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +requires_python = ">=3.8" +summary = "Reusable constraint types to use with typing.Annotated" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.9\"", +] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "astroid" +version = "3.3.8" +requires_python = ">=3.9.0" +summary = "An abstract syntax tree for Python with inference support." +groups = ["default"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.11\"", +] +files = [ + {file = "astroid-3.3.8-py3-none-any.whl", hash = "sha256:187ccc0c248bfbba564826c26f070494f7bc964fd286b6d9fff4420e55de828c"}, + {file = "astroid-3.3.8.tar.gz", hash = "sha256:a88c7994f914a4ea8572fac479459f4955eeccc877be3f2d959a33273b0cf40b"}, +] + +[[package]] +name = "attrs" +version = "24.2.0" +requires_python = ">=3.7" +summary = "Classes Without Boilerplate" +groups = ["default"] +dependencies = [ + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[[package]] +name = "babel" +version = "2.17.0" +requires_python = ">=3.8" +summary = "Internationalization utilities" +groups = ["default", "test"] +dependencies = [ + "pytz>=2015.7; python_version < \"3.9\"", +] +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.3" +requires_python = ">=3.7.0" +summary = "Screen-scraping library" +groups = ["default"] +dependencies = [ + "soupsieve>1.2", + "typing-extensions>=4.0.0", +] +files = [ + {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, + {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +requires_python = ">=3.6" +summary = "Python package for providing Mozilla's CA Bundle." +groups = ["default", "test"] +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.0" +requires_python = ">=3.7.0" +summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +groups = ["default", "test"] +files = [ + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, +] + +[[package]] +name = "click" +version = "8.1.7" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +groups = ["default"] +dependencies = [ + "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", +] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[[package]] +name = "cloudpickle" +version = "3.1.0" +requires_python = ">=3.8" +summary = "Pickler class to extend the standard pickle.Pickler functionality" +groups = ["default"] +files = [ + {file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"}, + {file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +summary = "Cross-platform colored terminal text." +groups = ["default", "lint", "test"] +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.10" +requires_python = ">=3.9" +summary = "Code coverage measurement for Python" +groups = ["lint", "test"] +files = [ + {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, + {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, + {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, + {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, + {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, + {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, + {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, + {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, + {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, + {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, + {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, + {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, + {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, + {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, + {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, +] + +[[package]] +name = "coverage" +version = "7.6.10" +extras = ["toml"] +requires_python = ">=3.9" +summary = "Code coverage measurement for Python" +groups = ["lint", "test"] +dependencies = [ + "coverage==7.6.10", + "tomli; python_full_version <= \"3.11.0a6\"", +] +files = [ + {file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"}, + {file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"}, + {file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"}, + {file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"}, + {file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"}, + {file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"}, + {file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"}, + {file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"}, + {file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"}, + {file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"}, + {file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"}, + {file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"}, + {file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"}, + {file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"}, + {file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"}, + {file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"}, + {file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"}, + {file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"}, + {file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"}, + {file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"}, + {file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"}, + {file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"}, + {file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"}, + {file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"}, + {file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"}, + {file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"}, + {file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"}, + {file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"}, + {file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"}, + {file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"}, + {file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"}, + {file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"}, + {file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"}, +] + +[[package]] +name = "docutils" +version = "0.21.2" +requires_python = ">=3.9" +summary = "Docutils -- Python Documentation Utilities" +groups = ["default", "test"] +files = [ + {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, + {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, +] + +[[package]] +name = "doit" +version = "0.36.0" +requires_python = ">=3.8" +summary = "doit - Automation Tool" +groups = ["default"] +dependencies = [ + "cloudpickle", + "importlib-metadata>=4.4", +] +files = [ + {file = "doit-0.36.0-py3-none-any.whl", hash = "sha256:ebc285f6666871b5300091c26eafdff3de968a6bd60ea35dd1e3fc6f2e32479a"}, + {file = "doit-0.36.0.tar.gz", hash = "sha256:71d07ccc9514cb22fe59d98999577665eaab57e16f644d04336ae0b4bae234bc"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +requires_python = ">=3.7" +summary = "Backport of PEP 654 (exception groups)" +groups = ["lint", "test"] +marker = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[[package]] +name = "idna" +version = "3.10" +requires_python = ">=3.6" +summary = "Internationalized Domain Names in Applications (IDNA)" +groups = ["default", "test"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +summary = "Getting image size from png/jpeg/jpeg2000/gif file" +groups = ["default", "test"] +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +requires_python = ">=3.8" +summary = "Read metadata from Python packages" +groups = ["default"] +dependencies = [ + "typing-extensions>=3.6.4; python_version < \"3.8\"", + "zipp>=3.20", +] +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +requires_python = ">=3.8" +summary = "Read resources from Python packages" +groups = ["default"] +dependencies = [ + "zipp>=3.1.0; python_version < \"3.10\"", +] +files = [ + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +requires_python = ">=3.7" +summary = "brain-dead simple config-ini parsing" +groups = ["lint", "test"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +requires_python = ">=3.7" +summary = "A very fast and expressive template engine." +groups = ["default", "test"] +dependencies = [ + "MarkupSafe>=2.0", +] +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[[package]] +name = "jschon" +version = "0.11.1" +requires_python = "~=3.8" +summary = "A JSON toolkit for Python developers." +groups = ["default"] +dependencies = [ + "rfc3986", +] +files = [ + {file = "jschon-0.11.1-py3-none-any.whl", hash = "sha256:2350e8b6747b17358022960f91208bea70de448b914827af3184d30e20500f0f"}, + {file = "jschon-0.11.1.tar.gz", hash = "sha256:c0ca0beab1f1694a03d726b91ed75ec604a7787af3ae91ead765f78215bf149f"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +requires_python = ">=3.8" +summary = "An implementation of JSON Schema validation for Python" +groups = ["default"] +dependencies = [ + "attrs>=22.2.0", + "importlib-resources>=1.4.0; python_version < \"3.9\"", + "jsonschema-specifications>=2023.03.6", + "pkgutil-resolve-name>=1.3.10; python_version < \"3.9\"", + "referencing>=0.28.4", + "rpds-py>=0.7.1", +] +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +requires_python = ">=3.9" +summary = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +groups = ["default"] +dependencies = [ + "referencing>=0.31.0", +] +files = [ + {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, + {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +requires_python = ">=3.9" +summary = "Safely add untrusted strings to HTML/XML markup." +groups = ["default", "test"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "packaging" +version = "24.2" +requires_python = ">=3.8" +summary = "Core utilities for Python packages" +groups = ["default", "lint", "test"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +requires_python = ">=3.8" +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +groups = ["default"] +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +requires_python = ">=3.8" +summary = "plugin and hook calling mechanisms for python" +groups = ["lint", "test"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +requires_python = ">=3.8" +summary = "Data validation using Python type hints" +groups = ["default"] +dependencies = [ + "annotated-types>=0.6.0", + "pydantic-core==2.27.2", + "typing-extensions>=4.12.2", +] +files = [ + {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, + {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +requires_python = ">=3.8" +summary = "Core functionality for Pydantic validation and serialization" +groups = ["default"] +dependencies = [ + "typing-extensions!=4.7.0,>=4.6.0", +] +files = [ + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, + {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a"}, + {file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9"}, + {file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4"}, + {file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048"}, + {file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474"}, + {file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc"}, + {file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0"}, + {file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2"}, + {file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4"}, + {file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9"}, + {file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b"}, + {file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e"}, + {file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee"}, + {file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993"}, + {file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630"}, + {file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362"}, + {file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e"}, + {file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9"}, + {file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2"}, + {file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35"}, + {file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39"}, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +requires_python = ">=3.9" +summary = "Bootstrap-based Sphinx theme from the PyData community" +groups = ["default"] +dependencies = [ + "Babel", + "accessible-pygments", + "beautifulsoup4", + "docutils!=0.17.0", + "pygments>=2.7", + "sphinx>=6.1", + "typing-extensions", +] +files = [ + {file = "pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde"}, + {file = "pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7"}, +] + +[[package]] +name = "pygments" +version = "2.19.1" +requires_python = ">=3.8" +summary = "Pygments is a syntax highlighting package written in Python." +groups = ["default", "test"] +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] + +[[package]] +name = "pytest" +version = "8.3.4" +requires_python = ">=3.8" +summary = "pytest: simple powerful testing with Python" +groups = ["lint", "test"] +dependencies = [ + "colorama; sys_platform == \"win32\"", + "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", + "iniconfig", + "packaging", + "pluggy<2,>=1.5", + "tomli>=1; python_version < \"3.11\"", +] +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +requires_python = ">=3.9" +summary = "Pytest plugin for measuring coverage." +groups = ["lint", "test"] +dependencies = [ + "coverage[toml]>=7.5", + "pytest>=4.6", +] +files = [ + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, +] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +requires_python = ">=3.8" +summary = "Read key-value pairs from a .env file and set them as environment variables" +groups = ["default"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[[package]] +name = "pyvcd" +version = "0.4.1" +requires_python = ">=3.7" +summary = "Python VCD file support" +groups = ["default"] +files = [ + {file = "pyvcd-0.4.1-py2.py3-none-any.whl", hash = "sha256:3a4c71d4dce741f1155a2ed11a6278390a0816293068f6162ad9658d20f75578"}, + {file = "pyvcd-0.4.1.tar.gz", hash = "sha256:dc6275e95a7949b8236086ab2e6d03afede73441243ec5109c9ea89077f3d696"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +requires_python = ">=3.8" +summary = "YAML parser and emitter for Python" +groups = ["default"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "referencing" +version = "0.35.1" +requires_python = ">=3.8" +summary = "JSON Referencing + Python" +groups = ["default"] +dependencies = [ + "attrs>=22.2.0", + "rpds-py>=0.7.0", +] +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +requires_python = ">=3.8" +summary = "Python HTTP for Humans." +groups = ["default", "test"] +dependencies = [ + "certifi>=2017.4.17", + "charset-normalizer<4,>=2", + "idna<4,>=2.5", + "urllib3<3,>=1.21.1", +] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +requires_python = ">=3.7" +summary = "Validating URI References per RFC 3986" +groups = ["default"] +files = [ + {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, + {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, +] + +[[package]] +name = "rpds-py" +version = "0.21.0" +requires_python = ">=3.9" +summary = "Python bindings to Rust's persistent data structures (rpds)" +groups = ["default"] +files = [ + {file = "rpds_py-0.21.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a017f813f24b9df929674d0332a374d40d7f0162b326562daae8066b502d0590"}, + {file = "rpds_py-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:20cc1ed0bcc86d8e1a7e968cce15be45178fd16e2ff656a243145e0b439bd250"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad116dda078d0bc4886cb7840e19811562acdc7a8e296ea6ec37e70326c1b41c"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:808f1ac7cf3b44f81c9475475ceb221f982ef548e44e024ad5f9e7060649540e"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de552f4a1916e520f2703ec474d2b4d3f86d41f353e7680b597512ffe7eac5d0"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:efec946f331349dfc4ae9d0e034c263ddde19414fe5128580f512619abed05f1"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b80b4690bbff51a034bfde9c9f6bf9357f0a8c61f548942b80f7b66356508bf5"}, + {file = "rpds_py-0.21.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085ed25baac88953d4283e5b5bd094b155075bb40d07c29c4f073e10623f9f2e"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8efac2a1273eed2354397a51216ae1e198ecbce9036fba4e7610b308b6153"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:95a5bad1ac8a5c77b4e658671642e4af3707f095d2b78a1fdd08af0dfb647624"}, + {file = "rpds_py-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3e53861b29a13d5b70116ea4230b5f0f3547b2c222c5daa090eb7c9c82d7f664"}, + {file = "rpds_py-0.21.0-cp310-none-win32.whl", hash = "sha256:ea3a6ac4d74820c98fcc9da4a57847ad2cc36475a8bd9683f32ab6d47a2bd682"}, + {file = "rpds_py-0.21.0-cp310-none-win_amd64.whl", hash = "sha256:b8f107395f2f1d151181880b69a2869c69e87ec079c49c0016ab96860b6acbe5"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5555db3e618a77034954b9dc547eae94166391a98eb867905ec8fcbce1308d95"}, + {file = "rpds_py-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97ef67d9bbc3e15584c2f3c74bcf064af36336c10d2e21a2131e123ce0f924c9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab2c2a26d2f69cdf833174f4d9d86118edc781ad9a8fa13970b527bf8236027"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e8921a259f54bfbc755c5bbd60c82bb2339ae0324163f32868f63f0ebb873d9"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a7ff941004d74d55a47f916afc38494bd1cfd4b53c482b77c03147c91ac0ac3"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5145282a7cd2ac16ea0dc46b82167754d5e103a05614b724457cffe614f25bd8"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de609a6f1b682f70bb7163da745ee815d8f230d97276db049ab447767466a09d"}, + {file = "rpds_py-0.21.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40c91c6e34cf016fa8e6b59d75e3dbe354830777fcfd74c58b279dceb7975b75"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d2132377f9deef0c4db89e65e8bb28644ff75a18df5293e132a8d67748397b9f"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0a9e0759e7be10109645a9fddaaad0619d58c9bf30a3f248a2ea57a7c417173a"}, + {file = "rpds_py-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e20da3957bdf7824afdd4b6eeb29510e83e026473e04952dca565170cd1ecc8"}, + {file = "rpds_py-0.21.0-cp311-none-win32.whl", hash = "sha256:f71009b0d5e94c0e86533c0b27ed7cacc1239cb51c178fd239c3cfefefb0400a"}, + {file = "rpds_py-0.21.0-cp311-none-win_amd64.whl", hash = "sha256:e168afe6bf6ab7ab46c8c375606298784ecbe3ba31c0980b7dcbb9631dcba97e"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:30b912c965b2aa76ba5168fd610087bad7fcde47f0a8367ee8f1876086ee6d1d"}, + {file = "rpds_py-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca9989d5d9b1b300bc18e1801c67b9f6d2c66b8fd9621b36072ed1df2c977f72"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f54e7106f0001244a5f4cf810ba8d3f9c542e2730821b16e969d6887b664266"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fed5dfefdf384d6fe975cc026886aece4f292feaf69d0eeb716cfd3c5a4dd8be"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:590ef88db231c9c1eece44dcfefd7515d8bf0d986d64d0caf06a81998a9e8cab"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f983e4c2f603c95dde63df633eec42955508eefd8d0f0e6d236d31a044c882d7"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b229ce052ddf1a01c67d68166c19cb004fb3612424921b81c46e7ea7ccf7c3bf"}, + {file = "rpds_py-0.21.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ebf64e281a06c904a7636781d2e973d1f0926a5b8b480ac658dc0f556e7779f4"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:998a8080c4495e4f72132f3d66ff91f5997d799e86cec6ee05342f8f3cda7dca"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:98486337f7b4f3c324ab402e83453e25bb844f44418c066623db88e4c56b7c7b"}, + {file = "rpds_py-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a78d8b634c9df7f8d175451cfeac3810a702ccb85f98ec95797fa98b942cea11"}, + {file = "rpds_py-0.21.0-cp312-none-win32.whl", hash = "sha256:a58ce66847711c4aa2ecfcfaff04cb0327f907fead8945ffc47d9407f41ff952"}, + {file = "rpds_py-0.21.0-cp312-none-win_amd64.whl", hash = "sha256:e860f065cc4ea6f256d6f411aba4b1251255366e48e972f8a347cf88077b24fd"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ee4eafd77cc98d355a0d02f263efc0d3ae3ce4a7c24740010a8b4012bbb24937"}, + {file = "rpds_py-0.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:688c93b77e468d72579351a84b95f976bd7b3e84aa6686be6497045ba84be560"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c38dbf31c57032667dd5a2f0568ccde66e868e8f78d5a0d27dcc56d70f3fcd3b"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d6129137f43f7fa02d41542ffff4871d4aefa724a5fe38e2c31a4e0fd343fb0"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520ed8b99b0bf86a176271f6fe23024323862ac674b1ce5b02a72bfeff3fff44"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaeb25ccfb9b9014a10eaf70904ebf3f79faaa8e60e99e19eef9f478651b9b74"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af04ac89c738e0f0f1b913918024c3eab6e3ace989518ea838807177d38a2e94"}, + {file = "rpds_py-0.21.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b9b76e2afd585803c53c5b29e992ecd183f68285b62fe2668383a18e74abe7a3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5afb5efde74c54724e1a01118c6e5c15e54e642c42a1ba588ab1f03544ac8c7a"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:52c041802a6efa625ea18027a0723676a778869481d16803481ef6cc02ea8cb3"}, + {file = "rpds_py-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee1e4fc267b437bb89990b2f2abf6c25765b89b72dd4a11e21934df449e0c976"}, + {file = "rpds_py-0.21.0-cp313-none-win32.whl", hash = "sha256:0c025820b78817db6a76413fff6866790786c38f95ea3f3d3c93dbb73b632202"}, + {file = "rpds_py-0.21.0-cp313-none-win_amd64.whl", hash = "sha256:320c808df533695326610a1b6a0a6e98f033e49de55d7dc36a13c8a30cfa756e"}, + {file = "rpds_py-0.21.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2c51d99c30091f72a3c5d126fad26236c3f75716b8b5e5cf8effb18889ced928"}, + {file = "rpds_py-0.21.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cbd7504a10b0955ea287114f003b7ad62330c9e65ba012c6223dba646f6ffd05"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dcc4949be728ede49e6244eabd04064336012b37f5c2200e8ec8eb2988b209c"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f414da5c51bf350e4b7960644617c130140423882305f7574b6cf65a3081cecb"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9afe42102b40007f588666bc7de82451e10c6788f6f70984629db193849dced1"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b929c2bb6e29ab31f12a1117c39f7e6d6450419ab7464a4ea9b0b417174f044"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8404b3717da03cbf773a1d275d01fec84ea007754ed380f63dfc24fb76ce4592"}, + {file = "rpds_py-0.21.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e12bb09678f38b7597b8346983d2323a6482dcd59e423d9448108c1be37cac9d"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58a0e345be4b18e6b8501d3b0aa540dad90caeed814c515e5206bb2ec26736fd"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c3761f62fcfccf0864cc4665b6e7c3f0c626f0380b41b8bd1ce322103fa3ef87"}, + {file = "rpds_py-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c2b2f71c6ad6c2e4fc9ed9401080badd1469fa9889657ec3abea42a3d6b2e1ed"}, + {file = "rpds_py-0.21.0-cp39-none-win32.whl", hash = "sha256:b21747f79f360e790525e6f6438c7569ddbfb1b3197b9e65043f25c3c9b489d8"}, + {file = "rpds_py-0.21.0-cp39-none-win_amd64.whl", hash = "sha256:0626238a43152918f9e72ede9a3b6ccc9e299adc8ade0d67c5e142d564c9a83d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:6b4ef7725386dc0762857097f6b7266a6cdd62bfd209664da6712cb26acef035"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6bc0e697d4d79ab1aacbf20ee5f0df80359ecf55db33ff41481cf3e24f206919"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da52d62a96e61c1c444f3998c434e8b263c384f6d68aca8274d2e08d1906325c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:98e4fe5db40db87ce1c65031463a760ec7906ab230ad2249b4572c2fc3ef1f9f"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30bdc973f10d28e0337f71d202ff29345320f8bc49a31c90e6c257e1ccef4333"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:faa5e8496c530f9c71f2b4e1c49758b06e5f4055e17144906245c99fa6d45356"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32eb88c30b6a4f0605508023b7141d043a79b14acb3b969aa0b4f99b25bc7d4a"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a89a8ce9e4e75aeb7fa5d8ad0f3fecdee813802592f4f46a15754dcb2fd6b061"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:241e6c125568493f553c3d0fdbb38c74babf54b45cef86439d4cd97ff8feb34d"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:3b766a9f57663396e4f34f5140b3595b233a7b146e94777b97a8413a1da1be18"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:af4a644bf890f56e41e74be7d34e9511e4954894d544ec6b8efe1e21a1a8da6c"}, + {file = "rpds_py-0.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3e30a69a706e8ea20444b98a49f386c17b26f860aa9245329bab0851ed100677"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:031819f906bb146561af051c7cef4ba2003d28cff07efacef59da973ff7969ba"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b876f2bc27ab5954e2fd88890c071bd0ed18b9c50f6ec3de3c50a5ece612f7a6"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc5695c321e518d9f03b7ea6abb5ea3af4567766f9852ad1560f501b17588c7b"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4de1da871b5c0fd5537b26a6fc6814c3cc05cabe0c941db6e9044ffbb12f04a"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878f6fea96621fda5303a2867887686d7a198d9e0f8a40be100a63f5d60c88c9"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8eeec67590e94189f434c6d11c426892e396ae59e4801d17a93ac96b8c02a6c"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff2eba7f6c0cb523d7e9cff0903f2fe1feff8f0b2ceb6bd71c0e20a4dcee271"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a429b99337062877d7875e4ff1a51fe788424d522bd64a8c0a20ef3021fdb6ed"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:d167e4dbbdac48bd58893c7e446684ad5d425b407f9336e04ab52e8b9194e2ed"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:4eb2de8a147ffe0626bfdc275fc6563aa7bf4b6db59cf0d44f0ccd6ca625a24e"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e78868e98f34f34a88e23ee9ccaeeec460e4eaf6db16d51d7a9b883e5e785a5e"}, + {file = "rpds_py-0.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4991ca61656e3160cdaca4851151fd3f4a92e9eba5c7a530ab030d6aee96ec89"}, + {file = "rpds_py-0.21.0.tar.gz", hash = "sha256:ed6378c9d66d0de903763e7706383d60c33829581f0adff47b6535f1802fa6db"}, +] + +[[package]] +name = "ruff" +version = "0.9.5" +requires_python = ">=3.7" +summary = "An extremely fast Python linter and code formatter, written in Rust." +groups = ["lint"] +files = [ + {file = "ruff-0.9.5-py3-none-linux_armv6l.whl", hash = "sha256:d466d2abc05f39018d53f681fa1c0ffe9570e6d73cde1b65d23bb557c846f442"}, + {file = "ruff-0.9.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38840dbcef63948657fa7605ca363194d2fe8c26ce8f9ae12eee7f098c85ac8a"}, + {file = "ruff-0.9.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d56ba06da53536b575fbd2b56517f6f95774ff7be0f62c80b9e67430391eeb36"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7cb2a01da08244c50b20ccfaeb5972e4228c3c3a1989d3ece2bc4b1f996001"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96d5c76358419bc63a671caac70c18732d4fd0341646ecd01641ddda5c39ca0b"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:deb8304636ed394211f3a6d46c0e7d9535b016f53adaa8340139859b2359a070"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:df455000bf59e62b3e8c7ba5ed88a4a2bc64896f900f311dc23ff2dc38156440"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de92170dfa50c32a2b8206a647949590e752aca8100a0f6b8cefa02ae29dce80"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d28532d73b1f3f627ba88e1456f50748b37f3a345d2be76e4c653bec6c3e393"}, + {file = "ruff-0.9.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c746d7d1df64f31d90503ece5cc34d7007c06751a7a3bbeee10e5f2463d52d2"}, + {file = "ruff-0.9.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11417521d6f2d121fda376f0d2169fb529976c544d653d1d6044f4c5562516ee"}, + {file = "ruff-0.9.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b9d71c3879eb32de700f2f6fac3d46566f644a91d3130119a6378f9312a38e1"}, + {file = "ruff-0.9.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2e36c61145e70febcb78483903c43444c6b9d40f6d2f800b5552fec6e4a7bb9a"}, + {file = "ruff-0.9.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2f71d09aeba026c922aa7aa19a08d7bd27c867aedb2f74285a2639644c1c12f5"}, + {file = "ruff-0.9.5-py3-none-win32.whl", hash = "sha256:134f958d52aa6fdec3b294b8ebe2320a950d10c041473c4316d2e7d7c2544723"}, + {file = "ruff-0.9.5-py3-none-win_amd64.whl", hash = "sha256:78cc6067f6d80b6745b67498fb84e87d32c6fc34992b52bffefbdae3442967d6"}, + {file = "ruff-0.9.5-py3-none-win_arm64.whl", hash = "sha256:18a29f1a005bddb229e580795627d297dfa99f16b30c7039e73278cf6b5f9fa9"}, + {file = "ruff-0.9.5.tar.gz", hash = "sha256:11aecd7a633932875ab3cb05a484c99970b9d52606ce9ea912b690b02653d56c"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +summary = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +groups = ["default", "test"] +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +requires_python = ">=3.8" +summary = "A modern CSS selector implementation for Beautiful Soup." +groups = ["default"] +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "sphinx" +version = "7.4.7" +requires_python = ">=3.9" +summary = "Python documentation generator" +groups = ["default", "test"] +dependencies = [ + "Jinja2>=3.1", + "Pygments>=2.17", + "alabaster~=0.7.14", + "babel>=2.13", + "colorama>=0.4.6; sys_platform == \"win32\"", + "docutils<0.22,>=0.20", + "imagesize>=1.3", + "importlib-metadata>=6.0; python_version < \"3.10\"", + "packaging>=23.0", + "requests>=2.30.0", + "snowballstemmer>=2.2", + "sphinxcontrib-applehelp", + "sphinxcontrib-devhelp", + "sphinxcontrib-htmlhelp>=2.0.0", + "sphinxcontrib-jsmath", + "sphinxcontrib-qthelp", + "sphinxcontrib-serializinghtml>=1.1.9", + "tomli>=2; python_version < \"3.11\"", +] +files = [ + {file = "sphinx-7.4.7-py3-none-any.whl", hash = "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239"}, + {file = "sphinx-7.4.7.tar.gz", hash = "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe"}, +] + +[[package]] +name = "sphinx-autoapi" +version = "3.5.0" +requires_python = ">=3.8" +summary = "Sphinx API documentation generator" +groups = ["default"] +dependencies = [ + "Jinja2", + "PyYAML", + "astroid>=2.7; python_version < \"3.12\"", + "astroid>=3; python_version >= \"3.12\"", + "sphinx>=6.1.0", + "stdlib-list; python_version < \"3.10\"", +] +files = [ + {file = "sphinx_autoapi-3.5.0-py3-none-any.whl", hash = "sha256:8676db32dded669dc6be9100696652640dc1e883e45b74710d74eb547a310114"}, + {file = "sphinx_autoapi-3.5.0.tar.gz", hash = "sha256:10dcdf86e078ae1fb144f653341794459e86f5b23cf3e786a735def71f564089"}, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.1.3" +requires_python = ">=3.9" +summary = "A clean book theme for scientific explanations and documentation with Sphinx" +groups = ["default"] +dependencies = [ + "pydata-sphinx-theme>=0.15.2", + "sphinx>=5", +] +files = [ + {file = "sphinx_book_theme-1.1.3-py3-none-any.whl", hash = "sha256:a554a9a7ac3881979a87a2b10f633aa2a5706e72218a10f71be38b3c9e831ae9"}, + {file = "sphinx_book_theme-1.1.3.tar.gz", hash = "sha256:1f25483b1846cb3d353a6bc61b3b45b031f4acf845665d7da90e01ae0aef5b4d"}, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, + {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, + {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, + {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +requires_python = ">=3.5" +summary = "A sphinx extension which renders display math in HTML via JavaScript" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, + {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +requires_python = ">=3.9" +summary = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +groups = ["default", "test"] +files = [ + {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, + {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, +] + +[[package]] +name = "tomli" +version = "2.2.1" +requires_python = ">=3.8" +summary = "A lil' TOML parser" +groups = ["default", "lint", "test"] +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["default"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +requires_python = ">=3.8" +summary = "HTTP library with thread-safe connection pooling, file post, and more." +groups = ["default", "test"] +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[[package]] +name = "wasmtime" +version = "25.0.0" +requires_python = ">=3.8" +summary = "A WebAssembly runtime powered by Wasmtime" +groups = ["default"] +dependencies = [ + "importlib-resources>=5.10", +] +files = [ + {file = "wasmtime-25.0.0-py3-none-any.whl", hash = "sha256:22aa59fc6e01deec8a6703046f82466090d5811096a3bb5c169907e36c842af1"}, + {file = "wasmtime-25.0.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:13e9a718e9d580c1738782cc19f4dcb9fb068f7e51778ea621fd664f4433525b"}, + {file = "wasmtime-25.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5bdf1214ee3ee78a4a8a92da339f4c4c8c109e65af881b37f4adfc05d02af426"}, + {file = "wasmtime-25.0.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:b4364e14d44e3b7afe6a40bf608e9d0d2c40b09dece441d20f4f6e31906b729c"}, + {file = "wasmtime-25.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:a07445073cf36a6e5d1dc28246a897dcbdaa537ba8be8805be65422ecca297eb"}, + {file = "wasmtime-25.0.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:53d5f614348a28aabdf80ae4f6fdfa803031af1f74ada03826fd4fd43aeee6c8"}, + {file = "wasmtime-25.0.0-py3-none-win_amd64.whl", hash = "sha256:f8a2a213b9179965db2d2eedececd69a37e287e902330509afae51c71a3a6842"}, +] + +[[package]] +name = "yowasp-nextpnr-ecp5" +version = "0.7.0.11.post528.dev0" +summary = "nextpnr-ecp5 FPGA place and route tool" +groups = ["default"] +dependencies = [ + "yowasp-runtime~=1.1", +] +files = [ + {file = "yowasp_nextpnr_ecp5-0.7.0.11.post528.dev0-py3-none-any.whl", hash = "sha256:102d3e81004c402e4422121197fa4eaf5090bd2d6d7e483312f2191233165e66"}, +] + +[[package]] +name = "yowasp-runtime" +version = "1.68" +requires_python = "~=3.8" +summary = "Common runtime for YoWASP packages" +groups = ["default"] +dependencies = [ + "importlib-resources; python_version < \"3.9\"", + "platformdirs<5,>=3.0", + "wasmtime<29,>=21.0.0", +] +files = [ + {file = "yowasp_runtime-1.68-py3-none-any.whl", hash = "sha256:e6a5029dd6bc8608cb6715404a58e212d42046e4e0056da0a56667c1381e028f"}, +] + +[[package]] +name = "yowasp-yosys" +version = "0.48.0.0.post834" +summary = "Yosys Open SYnthesis Suite" +groups = ["default"] +dependencies = [ + "click", + "importlib-resources; python_version < \"3.9\"", + "yowasp-runtime~=1.12", +] +files = [ + {file = "yowasp_yosys-0.48.0.0.post834-py3-none-any.whl", hash = "sha256:6a53cdb48083c7ac88dc9e5312e72e92f5afd69aeea412274505c82f8d8d374c"}, +] + +[[package]] +name = "zipp" +version = "3.21.0" +requires_python = ">=3.9" +summary = "Backport of pathlib-compatible object wrapper for zip files" +groups = ["default"] +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +] diff --git a/pyproject.toml b/pyproject.toml index d17d51c0..8a82f5ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,9 +2,6 @@ # Project metadata -[tool.pdm.version] -source = "scm" - [project] name = "chipflow-lib" dynamic = ["version"] @@ -16,13 +13,11 @@ authors = [ ] license = {file = "LICENSE.md"} -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "amaranth>=0.5,<0.7", "amaranth-soc @ git+https://github.com/amaranth-lang/amaranth-soc", "amaranth-boards @ git+https://github.com/amaranth-lang/amaranth-boards", - "amaranth-stdio @ git+https://github.com/amaranth-lang/amaranth-stdio", - "amaranth-orchard @ git+https://github.com/ChipFlow/amaranth-orchard", "yowasp-yosys>=0.41.0.0", "yowasp-nextpnr-ecp5>=0.7", "yowasp-runtime", @@ -30,6 +25,10 @@ dependencies = [ "jsonschema>=4.17.3", "doit>=0.36.0", "requests>=2.30.0", + "python-dotenv>=1.0.1", + "pydantic>=2.10.6", + "sphinx-book-theme>=1.1.3", + "sphinx-autoapi>=3.5.0", ] [project.scripts] @@ -43,17 +42,37 @@ build-backend = "pdm.backend" # Development workflow configuration -[tool.pdm.dev-dependencies] -test = [ - "pytest>=7.2.0", - "sphinx>=7.1.2", -] -lint = [ - "pycodestyle>=2.11", -] +[tool.pyright] +diagnosticMode=false +typeCheckingMode = "off" +reportInvalidTypeForm = false +reportMissingImports = false +reportUnboundVariable = false + +[tool.ruff] +include = ["pyproject.toml", "**/*.py", "chipflow.toml"] + +[tool.ruff.lint] +ignore = ['F403', 'F405'] + +[tool.pdm.version] +source = "scm" [tool.pdm.scripts] +test-cov.cmd = "pytest --cov=chipflow_lib --cov-report=html" test.cmd = "pytest" test-docs.cmd = "sphinx-build -b doctest docs/ docs/_build" -lint.cmd = "pycodestyle --config=./.pycodestyle chipflow_lib" -document.cmd = "sphinx-build docs/ docs/_build/ -W --keep-going" +lint.cmd = "ruff check" +docs.cmd = "sphinx-build docs/ docs/_build/ -W --keep-going" + + +[dependency-groups] +lint = [ + "ruff", + "pytest-cov", +] +test = [ + "pytest>=7.2.0", + "sphinx>=7.1.2", + "pytest-cov>=6.0.0", +] From 9b913ab83d435a509d0eb2109578547da6710017 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 11 Feb 2025 16:34:28 +0000 Subject: [PATCH 5/6] docs: update for pin-lock and tweak CI for docs --- .github/workflows/deploy-docs.yml | 30 +++++++++++ .github/workflows/main.yaml | 45 ----------------- .github/workflows/preview-docs.yml | 36 +++++++++++++ docs/_static/chipflow-logo.png | Bin 0 -> 92159 bytes docs/_static/chipflow-logo.svg | 18 +++++++ docs/chipflow-toml-guide.rst | 70 ++++++++++++++++++-------- docs/conf.py | 78 ++++++++++++++++++++++++++--- docs/example-chipflow.toml | 67 +++++++++++++------------ docs/index.rst | 15 ++++-- 9 files changed, 251 insertions(+), 108 deletions(-) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 .github/workflows/preview-docs.yml create mode 100644 docs/_static/chipflow-logo.png create mode 100644 docs/_static/chipflow-logo.svg diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..c19b712f --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,30 @@ +# .github/workflows/deploy.yml +name: Deploy +on: + push: + branches: + - main +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup PDM + uses: pdm-project/setup-pdm@v4 + with: + python-version: 3.12 + cache: true + + - name: Install dependencies + run: pdm install + + - name: Build docs + run: pdm docs + + - uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: docs/_build + branch: gh-pages + clean-exclude: pr-preview + force: false diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index c7980699..42792e40 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -39,48 +39,3 @@ jobs: - name: Check source code licenses run: | docker run --platform=linux/amd64 -v ${PWD}:/src ghcr.io/google/addlicense -v -check -l BSD-2-Clause -c "ChipFlow" -s=only -ignore **/__init__.py **/*.py - - build-docs: - needs: test - runs-on: ubuntu-22.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up PDM - uses: pdm-project/setup-pdm@v4 - with: - python-version: 3.9 - - name: Install deps - run: pdm install - - name: Build docs - run: pdm run document - - name: Upload docs artifact - uses: actions/upload-artifact@v4 - with: - name: docs - path: docs/_build - - publish-docs: - needs: build-docs - if: ${{ github.repository == 'chipflow/chipflow-lib' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Download docs artifact - uses: actions/download-artifact@v4 - with: - name: docs - path: docs/ - - name: Publish "latest" docs - if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/main' }} - uses: JamesIves/github-pages-deploy-action@releases/v4 - with: - repository-name: chipflow/chipflow.github.io - ssh-key: ${{ secrets.PAGES_DEPLOY_KEY }} - branch: main - folder: docs/ - target-folder: chipflow-lib/latest/ - \ No newline at end of file diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml new file mode 100644 index 00000000..81dbb37f --- /dev/null +++ b/.github/workflows/preview-docs.yml @@ -0,0 +1,36 @@ +# .github/workflows/preview.yml +name: Deploy PR previews +concurrency: preview-${{ github.ref }} +on: + pull_request: + types: + - opened + - reopened + - synchronize + - closed +jobs: + deploy-preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup PDM + uses: pdm-project/setup-pdm@v4 + with: + python-version: 3.12 + cache: true + + - name: Install dependencies + run: pdm install + + - name: Build docs + run: pdm docs + if: github.event.action != 'closed' + + - uses: rossjrw/pr-preview-action@v1 + with: + source-dir: docs/_build + preview-branch: gh-pages + umbrella-dir: pr-preview + action: auto + pages-base-url: chipflow-lib.docs.chipflow-infra.com diff --git a/docs/_static/chipflow-logo.png b/docs/_static/chipflow-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..81ced7d5e8b56e8e9192c028a44c34816377b9b7 GIT binary patch literal 92159 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 literal 0 HcmV?d00001 diff --git a/docs/_static/chipflow-logo.svg b/docs/_static/chipflow-logo.svg new file mode 100644 index 00000000..9d3aa488 --- /dev/null +++ b/docs/_static/chipflow-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/chipflow-toml-guide.rst b/docs/chipflow-toml-guide.rst index 0990e83e..07d7cbc0 100644 --- a/docs/chipflow-toml-guide.rst +++ b/docs/chipflow-toml-guide.rst @@ -1,5 +1,5 @@ -Intro to chipflow.toml -====================== +Intro to ``chipflow.toml`` +========================== The ``chipflow.toml`` file provides configuration for your design with the ChipFlow platform. @@ -14,25 +14,55 @@ Let's start with a typical example: # Assert that example-chipflow.toml matches the current config schema. If # this test fails, then its likely that the content in this file will need # to be updated. - from chipflow_lib.cli import _parse_config_file + from chipflow_lib import _parse_config_file _parse_config_file("docs/example-chipflow.toml") -project_id -=========== +``[chipflow]`` +-------------- -The ``project_id`` is set to the project ID which you can get from the ChipFlow app project page. +.. code-block:: TOML + + [chipflow] + project_name = "my_project" + + +The ``project_name`` is a human-readable identifier for this project. If not set, the tool and library will use the project name configured in ``pyproject.toml``. + +``[chipflow.steps]`` +-------------------- + +The ``steps`` section allows overriding or addition to the standard steps available from `chipflow_lib`_. + +For example, if you want to override the standard silicon preparation step, you could derive from :class:`chipflow_lib.steps.silicon.SiliconStep`, add your custom functionality +and add the following to your `chipflow.toml`, with the appropriate Python `qualified name`_ : + +.. code-block:: TOML + + [chipflow.stepe] + silicon = "my_design.steps.silicon:SiliconStep" -steps -===== -The ``steps`` define the Python class which will be used as an entry point to these parts of the ChipFlow process. You probably won't need to change these if you're starting from an example repository. -silicon -======= +.. _chipflow_lib: https://github.com/ChipFlow/chipflow-lib] +.. _qualified name: https://docs.python.org/3/glossary.html#term-qualified-name -The ``silicon`` section sets the Foundry ``process`` we are targeting for manufacturing, and the physical ``pad_ring`` we want to place our design inside. -You'll choose the ``process`` and ``pad_ring`` based in the requirements of your design. + +``[chipflow.clocks]`` +--------------------- + +``[chipflow.silicon]`` +---------------------- + +.. code-block:: TOML + + [chipflow.silicon] + process = "ihp_sg13g2" + package = "pga144" + + +The ``silicon`` section sets the Foundry ``process`` we are targeting for manufacturing, and the physical ``package`` we want to place our design inside. +You'll choose the ``process`` and ``package`` based in the requirements of your design. Available processes ------------------- @@ -56,12 +86,8 @@ Available pad rings +----------+-----------+--------------------+------------------------------------+ | Pad ring | Pad count | Pad locations | Notes | +==========+===========+====================+====================================+ -|| caravel || TBC || TBC || The `Caravel Harness`_ contains | -|| || || || additional logic which wraps your | -|| || || || design. | -|| || || || It handles its own power pins. | +----------+-----------+--------------------+------------------------------------+ -|| cf20 || 20 || ``N1`` ... ``N7`` || | +|| cf20 || 20 || ``N1`` ... ``N7`` || Bare die package with 20 pins | || || || ``S1`` ... ``S7`` || | || || || ``E1`` ... ``E3`` || | || || || ``W1`` ... ``W3`` || | @@ -74,10 +100,12 @@ Available pad rings +----------+-----------+--------------------+------------------------------------+ + + silicon.pads ============ -The ``silicon.pads`` section lists the pads we will be using. +The ``silicon.pads`` section lists special pads. In general you are unlikely to need to add to this. For each pad, there's a label which is used by our design, and what ``type`` and ``loc`` each pad should be. @@ -86,8 +114,8 @@ type The ``type`` for each pad can be set to one of: -clk - External clock. +clock + External clock input. i Input. diff --git a/docs/conf.py b/docs/conf.py index 5950dec3..14e581d2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,16 +4,23 @@ # Add parent folder to path so we can pick up module import os import sys +sys.path.insert(0, os.path.abspath('../../chipflow_lib')) + +from chipflow_lib import __version__ + doctest_path = [os.path.abspath('..')] # -- Project information -project = 'ChipFlow' -copyright = 'ChipFlow' -author = 'ChipFlow' +project = 'chipflow-lib' +copyright = 'ChipFlow Limited, 2021-2025' +author = 'ChipFlow Platform Team' release = 'alpha' -version = '0.1.0' +version = __version__ + +master_doc = "index" + # -- General configuration @@ -21,15 +28,72 @@ 'sphinx.ext.duration', 'sphinx.ext.doctest', 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', + 'autoapi.extension', + 'sphinx.ext.napoleon' +] + +html_theme = 'sphinx_book_theme' +html_logo = '_static/chipflow-logo.svg' +html_title = "ChipFlow Platform Documentation" + + +html_sidebars = { + '**': [ + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + ] +} + +html_static_path = ['_static'] + +html_theme_options = { + "home_page_in_toc": True, + "repository_url": "https://github.com/ChipFlow/chipflow-lib", + "repository_branch": "master", + "path_to_docs": "docs", + "use_repository_button": True, + "use_edit_page_button": True, + "use_issues_button": True, + "show_navbar_depth": 3, + # "announcement": "v3.0.0 is now out! See the Changelog for details", +} + +autodoc_typehints = 'description' + +autoapi_dirs = ["../chipflow_lib"] +autoapi_options = [ + 'members', + 'undoc-members', + 'show-inheritance', + 'show-module-summary', + 'imported-members', ] intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'sphinx': ('https://www.sphinx-doc.org/en/master/', None), + 'py': ('https://docs.python.org/3/', None), + 'amaranth': ('https://amaranth-lang.org/docs/amaranth/v0.5.4/', None), + # 'chipflow': ('https://docs.chipflow.io/', None), } intersphinx_disabled_domains = ['std'] +# Napoleon settings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = True +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True + +rst_prolog = """ +.. role:: py(code) + :language: python +""" + # -- Options for EPUB output epub_show_urls = 'footnote' diff --git a/docs/example-chipflow.toml b/docs/example-chipflow.toml index 5db4cfa0..3c7c31a5 100644 --- a/docs/example-chipflow.toml +++ b/docs/example-chipflow.toml @@ -1,38 +1,43 @@ [chipflow] -project_id = 8 +project_name = "test-chip" + +[chipflow.top] +soc = "my_design.design:MySoC" [chipflow.steps] -sim = "my_design.steps.sim:MySimStep" -board = "my_design.steps.board:MyBoardStep" -silicon = "my_design.steps.silicon:MySiliconStep" -software = "my_design.steps.software:MySoftwareStep" +silicon = "chipflow_lib.steps.silicon:SiliconStep" + +[chipflow.clocks] +default = 'sys_clk' + +[chipflow.resets] +default = 'sys_rst_n' [chipflow.silicon] -process = "sky130" -pad_ring = "caravel" +process = "gf130bcd" +package = "pga144" [chipflow.silicon.pads] -sys_clk = { type = "clk", loc = "0" } -sys_rstn = { type = "i", loc = "1" } -uart_tx = { type = "o", loc = "2" } -uart_rx = { type = "i", loc = "3" } -flash_clk = { type = "o", loc = "4" } -flash_csn = { type = "o", loc = "5" } -flash_d0 = { type = "io", loc = "6" } -flash_d1 = { type = "io", loc = "7" } -flash_d2 = { type = "io", loc = "8" } -flash_d3 = { type = "io", loc = "9" } -gpio_0 = { type = "io", loc = "10" } -gpio_1 = { type = "io", loc = "11" } -gpio_2 = { type = "io", loc = "12" } -gpio_3 = { type = "io", loc = "13" } -gpio_4 = { type = "io", loc = "14" } -gpio_5 = { type = "io", loc = "15" } -gpio_6 = { type = "io", loc = "16" } -gpio_7 = { type = "io", loc = "17" } -btn_0 = { type = "io", loc = "18" } -btn_1 = { type = "io", loc = "19" } -jtag_tck = { type = "i", loc = "33" } -jtag_tms = { type = "i", loc = "34" } -jtag_tdi = { type = "i", loc = "35" } -jtag_tdo = { type = "o", loc = "36" } +# System +sys_clk = { type = "clock", loc = "114" } +sys_rst_n = { type = "reset", loc = "115" } + +[chipflow.silicon.power] +dvss0 = { type = "power", loc = "1" } +dvdd0 = { type = "ground", loc = "9" } +vss0 = { type = "power", loc = "17" } +vdd0 = { type = "ground", loc = "25" } +dvss1 = { type = "power", loc = "33" } +dvdd1 = { type = "ground", loc = "41" } +vss1 = { type = "power", loc = "49" } +vdd1 = { type = "ground", loc = "57" } +dvss2 = { type = "power", loc = "65" } +dvdd2 = { type = "ground", loc = "73" } +vss2 = { type = "power", loc = "81" } +vdd2 = { type = "ground", loc = "89" } +dvss3 = { type = "power", loc = "97" } +dvdd3 = { type = "ground", loc = "105" } +vss3 = { type = "power", loc = "113" } +vdd3 = { type = "ground", loc = "121" } +dvss4 = { type = "power", loc = "129" } +dvdd4 = { type = "ground", loc = "137" } diff --git a/docs/index.rst b/docs/index.rst index a61fc9fe..eaece24e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,9 +1,16 @@ -chipflow-lib documentation -========================== +ChipFlow Library Documentation +------------------------------ + +.. image: _static/chipflow-logo.svg + :width: 200px + :class: sd-m-auto + :name: landing-page-logo + + +.. rubric: ChipFlow IC Design Platform -Contents --------- .. toctree:: chipflow-toml-guide + From d04d76b3fdbbdecc31f46cc64acd236c38081c58 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 11 Feb 2025 16:35:47 +0000 Subject: [PATCH 6/6] chore: ignore .coverage --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 584bee2d..aa32746c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__/ /.pdm-python /.pdm-build /.venv +/.coverage # pytest /.pytest_cache