diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 377f3cc..7782fcf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,35 +19,29 @@ jobs: strategy: matrix: python-version: - - "3.7" - - "3.8" - - "3.9" - "3.10" - "3.11" - "3.12" + - "3.13" + - "3.14" include: - os: ubuntu-latest - # older versions need older OS - - python-version: "3.7" - os: ubuntu-22.04 - - - python-version: "3.8" - os: ubuntu-22.04 - runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v3 + - name: Install UV + uses: astral-sh/setup-uv@v6 + - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} + run: | + uv venv -p ${{ matrix.python-version }} .venv - name: Install dependencies run: | - python -m pip install -r tests/requirements.txt + uv sync --group test - name: Install run: | diff --git a/pyproject.toml b/pyproject.toml index eb3b8b4..e0bd74c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,12 @@ build-backend = "setuptools.build_meta" [project] name = "peakrdl-busdecoder" -dynamic = ["version"] -requires-python = ">=3.7" -dependencies = ["systemrdl-compiler ~= 1.29", "Jinja2>=2.11"] +version = "0.1.0" +requires-python = ">=3.10" +dependencies = [ + "jinja2>=3.1.6", + "systemrdl-compiler~=1.30.1", +] authors = [{ name = "Alex Mykyta" }] description = "Compile SystemRDL into a SystemVerilog control/status register (CSR) block" @@ -46,8 +49,21 @@ Tracker = "https://github.com/SystemRDL/PeakRDL-busdecoder/issues" Changelog = "https://github.com/SystemRDL/PeakRDL-busdecoder/releases" Documentation = "https://peakrdl-busdecoder.readthedocs.io/" -[tool.setuptools.dynamic] -version = { attr = "peakrdl_busdecoder.__about__.__version__" } +[dependency-groups] +docs = [ + "pygments-systemrdl>=1.3.0", + "sphinx-book-theme>=1.1.4", + "sphinxcontrib-wavedrom>=3.0.4", +] +test = [ + "parameterized>=0.9.0", + "pytest>=7.4.4", + "pytest-cov>=4.1.0", + "pytest-xdist>=3.5.0", +] +tools = [ + "ruff>=0.14.0", +] [project.entry-points."peakrdl.exporters"] busdecoder = "peakrdl_busdecoder.__peakrdl__:Exporter" diff --git a/src/peakrdl_regblock/__init__.py b/src/peakrdl_busdecoder/__init__.py similarity index 54% rename from src/peakrdl_regblock/__init__.py rename to src/peakrdl_busdecoder/__init__.py index 20efd41..5f21fc7 100644 --- a/src/peakrdl_regblock/__init__.py +++ b/src/peakrdl_busdecoder/__init__.py @@ -1,3 +1,3 @@ -from .__about__ import __version__ - from .exporter import BusDecoderExporter + +__all__ = ["BusDecoderExporter"] diff --git a/src/peakrdl_busdecoder/__peakrdl__.py b/src/peakrdl_busdecoder/__peakrdl__.py new file mode 100644 index 0000000..3e74291 --- /dev/null +++ b/src/peakrdl_busdecoder/__peakrdl__.py @@ -0,0 +1,120 @@ +from typing import TYPE_CHECKING +import functools + +from peakrdl.plugins.exporter import ExporterSubcommandPlugin +from peakrdl.config import schema +from peakrdl.plugins.entry_points import get_entry_points + +from .exporter import BusDecoderExporter +from .cpuif import BaseCpuif, apb3, apb4 +from .udps import ALL_UDPS + +if TYPE_CHECKING: + import argparse + from systemrdl.node import AddrmapNode + + +class Exporter(ExporterSubcommandPlugin): + short_desc = "Generate a SystemVerilog control/status register (CSR) block" + + udp_definitions = ALL_UDPS + + cfg_schema = { + "cpuifs": {"*": schema.PythonObjectImport()}, + } + + @functools.lru_cache() + def get_cpuifs(self) -> dict[str, type[BaseCpuif]]: + # All built-in CPUIFs + cpuifs: dict[str, type[BaseCpuif]] = { + # "passthrough": passthrough.PassthroughCpuif, + "apb3": apb3.APB3Cpuif, + "apb3-flat": apb3.APB3CpuifFlat, + "apb4": apb4.APB4Cpuif, + "apb4-flat": apb4.APB4CpuifFlat, + # "axi4-lite": axi4lite.AXI4Lite_Cpuif, + # "axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened, + } + + # Load any cpuifs specified via entry points + for ep, _ in get_entry_points("peakrdl_busdecoder.cpuif"): + name = ep.name + cpuif = ep.load() + if name in cpuifs: + raise RuntimeError( + f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it already exists" + ) + if not issubclass(cpuif, BaseCpuif): + raise RuntimeError( + f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a BaseCpuif class" + ) + cpuifs[name] = cpuif + + # Load any CPUIFs via config import + for name, cpuif in self.cfg["cpuifs"].items(): + if name in cpuifs: + raise RuntimeError( + f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it already exists" + ) + if not issubclass(cpuif, BaseCpuif): + raise RuntimeError( + f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a BaseCpuif class" + ) + cpuifs[name] = cpuif + + return cpuifs + + def add_exporter_arguments(self, arg_group: "argparse._ActionsContainer") -> None: + cpuifs = self.get_cpuifs() + + arg_group.add_argument( + "--cpuif", + choices=cpuifs.keys(), + default="apb3", + help="Select the CPU interface protocol to use [apb3]", + ) + + arg_group.add_argument( + "--module-name", + metavar="NAME", + default=None, + help="Override the SystemVerilog module name", + ) + + arg_group.add_argument( + "--package-name", + metavar="NAME", + default=None, + help="Override the SystemVerilog package name", + ) + + arg_group.add_argument( + "--addr-width", + type=int, + default=None, + help="""Override the CPU interface's address width. By default, + address width is sized to the contents of the busdecoder. + """, + ) + + arg_group.add_argument( + "--unroll", + action="store_true", + help="""Unroll arrayed addressable nodes into separate instances in + the CPU interface. By default, arrayed nodes are kept as arrays. + """, + ) + + def do_export(self, top_node: "AddrmapNode", options: "argparse.Namespace") -> None: + cpuifs = self.get_cpuifs() + + x = BusDecoderExporter() + x.export( + top_node, + options.output, + cpuif_cls=cpuifs[options.cpuif], + module_name=options.module_name, + package_name=options.package_name, + address_width=options.addr_width, + unroll=options.unroll, + ) diff --git a/src/peakrdl_regblock/addr_decode.py b/src/peakrdl_busdecoder/addr_decode.py similarity index 72% rename from src/peakrdl_regblock/addr_decode.py rename to src/peakrdl_busdecoder/addr_decode.py index d44965a..ff23807 100644 --- a/src/peakrdl_regblock/addr_decode.py +++ b/src/peakrdl_busdecoder/addr_decode.py @@ -1,18 +1,15 @@ -from typing import TYPE_CHECKING, Union, List, Optional +from typing import TYPE_CHECKING from systemrdl.node import FieldNode, RegNode from systemrdl.walker import WalkerAction from .utils import get_indexed_path -from .struct_generator import RDLStructGenerator from .forloop_generator import RDLForLoopGenerator -from .identifier_filter import kw_filter as kwf from .sv_int import SVInt if TYPE_CHECKING: from .exporter import BusDecoderExporter from systemrdl.node import AddrmapNode, AddressableNode - from systemrdl.node import RegfileNode, MemNode class AddressDecode: @@ -23,12 +20,6 @@ class AddressDecode: def top_node(self) -> "AddrmapNode": return self.exp.ds.top_node - def get_strobe_struct(self) -> str: - struct_gen = DecodeStructGenerator() - s = struct_gen.get_struct(self.top_node, "decoded_reg_strb_t") - assert s is not None # guaranteed to have at least one reg - return s - def get_implementation(self) -> str: gen = DecodeLogicGenerator(self) s = gen.get_content(self.top_node) @@ -36,7 +27,7 @@ class AddressDecode: return s def get_access_strobe( - self, node: Union[RegNode, FieldNode], reduce_substrobes: bool = True + self, node: RegNode | FieldNode, reduce_substrobes: bool = True ) -> str: """ Returns the Verilog string that represents the register/field's access strobe. @@ -73,77 +64,24 @@ class AddressDecode: return "decoded_reg_strb." + path -class DecodeStructGenerator(RDLStructGenerator): - def _enter_external_block(self, node: "AddressableNode") -> None: - self.add_member( - kwf(node.inst_name), - array_dimensions=node.array_dimensions, - ) - - def enter_Addrmap(self, node: "AddrmapNode") -> Optional[WalkerAction]: - assert node.external - self._enter_external_block(node) - return WalkerAction.SkipDescendants - - def exit_Addrmap(self, node: "AddrmapNode") -> None: - assert node.external - - def enter_Regfile(self, node: "RegfileNode") -> Optional[WalkerAction]: - if node.external: - self._enter_external_block(node) - return WalkerAction.SkipDescendants - super().enter_Regfile(node) - return WalkerAction.Continue - - def exit_Regfile(self, node: "RegfileNode") -> None: - if node.external: - return - super().exit_Regfile(node) - - def enter_Mem(self, node: "MemNode") -> Optional[WalkerAction]: - assert node.external - self._enter_external_block(node) - return WalkerAction.SkipDescendants - - def exit_Mem(self, node: "MemNode") -> None: - assert node.external - - def enter_Reg(self, node: "RegNode") -> None: - # if register is "wide", expand the strobe to be able to access the sub-words - n_subwords = node.get_property("regwidth") // node.get_property("accesswidth") - - self.add_member( - kwf(node.inst_name), - width=n_subwords, - array_dimensions=node.array_dimensions, - ) - - # Stub out - def exit_Reg(self, node: "RegNode") -> None: - pass - - def enter_Field(self, node: "FieldNode") -> None: - pass - - class DecodeLogicGenerator(RDLForLoopGenerator): def __init__(self, addr_decode: AddressDecode) -> None: self.addr_decode = addr_decode super().__init__() # List of address strides for each dimension - self._array_stride_stack = [] # type: List[int] + self._array_stride_stack: list[int] = [] def enter_AddressableComponent( self, node: "AddressableNode" - ) -> Optional[WalkerAction]: + ) -> WalkerAction | None: super().enter_AddressableComponent(node) if node.array_dimensions: assert node.array_stride is not None # Collect strides for each array dimension current_stride = node.array_stride - strides = [] + strides: list[int] = [] for dim in reversed(node.array_dimensions): strides.append(current_stride) current_stride *= dim diff --git a/src/peakrdl_busdecoder/cpuif/__init__.py b/src/peakrdl_busdecoder/cpuif/__init__.py new file mode 100644 index 0000000..c11d9e9 --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/__init__.py @@ -0,0 +1,3 @@ +from .base_cpuif import BaseCpuif + +__all__ = ["BaseCpuif"] diff --git a/src/peakrdl_busdecoder/cpuif/apb3/__init__.py b/src/peakrdl_busdecoder/cpuif/apb3/__init__.py new file mode 100644 index 0000000..6d92581 --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/apb3/__init__.py @@ -0,0 +1,4 @@ +from .apb3_cpuif import APB3Cpuif +from .apb3_cpuif_flat import APB3CpuifFlat + +__all__ = ["APB3Cpuif", "APB3CpuifFlat"] \ No newline at end of file diff --git a/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py b/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py new file mode 100644 index 0000000..a79d5c2 --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif.py @@ -0,0 +1,79 @@ +from systemrdl.node import AddressableNode +from ..base_cpuif import BaseCpuif + + +class APB3Cpuif(BaseCpuif): + template_path = "apb3_tmpl.sv" + is_interface = True + + def _port_declaration(self, child: AddressableNode) -> str: + base = f"apb3_intf.master m_apb_{child.inst_name}" + if not child.is_array: + return base + if child.current_idx is not None: + return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]" + return f"{base} [N_{child.inst_name.upper()}S]" + + @property + def port_declaration(self) -> str: + slave_ports: list[str] = ["apb3_intf.slave s_apb"] + master_ports: list[str] = list( + map(self._port_declaration, self.addressable_children) + ) + + return ",\n".join(slave_ports + master_ports) + + def signal( + self, + signal: str, + node: AddressableNode | None = None, + idx: str | int | None = None, + ) -> str: + if node is None: + # Node is none, so this is a slave signal + return f"s_apb.{signal}" + + # Master signal + base = f"m_apb_{node.inst_name}" + if not node.is_array: + return f"{base}.{signal}" + if node.current_idx is not None: + # This is a specific instance of an array + return f"{base}_{'_'.join(map(str, node.current_idx))}.{signal}" + if idx is not None: + return f"{base}[{idx}].{signal}" + + raise ValueError("Must provide an index for arrayed interface signals") + + def get_address_predicate(self, node: AddressableNode) -> str: + """ + Returns a SystemVerilog expression that evaluates to true when the + address on the bus matches the address range of the given node. + """ + + addr_mask = (1 << self.addr_width) - 1 + addr = node.absolute_address & addr_mask + size = node.size + if size == 0: + raise ValueError("Node size must be greater than 0") + if (addr % size) != 0: + raise ValueError("Node address must be aligned to its size") + + # Calculate the address range of the node + addr_start = addr + addr_end = addr + size - 1 + if addr_end > addr_mask: + raise ValueError("Node address range exceeds address width") + + if addr_start == addr_end: + return f"({self.signal('PADDR')} == 'h{addr_start:X})" + + return f"({self.signal('PADDR')} >= 'h{addr_start:X} && {self.signal('PADDR')} <= 'h{addr_end:X})" + + def get_address_decode_condition(self, node: AddressableNode) -> str: + """ + Returns a SystemVerilog expression that evaluates to true when the + address on the bus matches the address range of the given node. + """ + addr_pred = self.get_address_predicate(node) + return addr_pred diff --git a/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py b/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py new file mode 100644 index 0000000..45ee439 --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/apb3/apb3_cpuif_flat.py @@ -0,0 +1,62 @@ +from systemrdl.node import AddressableNode +from ..base_cpuif import BaseCpuif + + +class APB3CpuifFlat(BaseCpuif): + template_path = "apb3_tmpl.sv" + is_interface = False + + def _port_declaration(self, child: AddressableNode) -> list[str]: + return [ + f"input logic {self.signal('PCLK', child)}", + f"input logic {self.signal('PRESETn', child)}", + f"input logic {self.signal('PSELx', child)}", + f"input logic {self.signal('PENABLE', child)}", + f"input logic {self.signal('PWRITE', child)}", + f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}", + f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}", + f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}", + f"output logic {self.signal('PREADY', child)}", + f"output logic {self.signal('PSLVERR', child)}", + ] + + @property + def port_declaration(self) -> str: + slave_ports: list[str] = [ + f"input logic {self.signal('PCLK')}", + f"input logic {self.signal('PRESETn')}", + f"input logic {self.signal('PSELx')}", + f"input logic {self.signal('PENABLE')}", + f"input logic {self.signal('PWRITE')}", + f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}", + f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}", + f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}", + f"output logic {self.signal('PREADY')}", + f"output logic {self.signal('PSLVERR')}", + ] + master_ports: list[str] = [] + for child in self.addressable_children: + master_ports.extend(self._port_declaration(child)) + + return ",\n".join(slave_ports + master_ports) + + def signal( + self, + signal: str, + node: AddressableNode | None = None, + idx: str | int | None = None, + ) -> str: + if node is None: + # Node is none, so this is a slave signal + return f"s_apb_{signal}" + + # Master signal + base = f"m_apb_{node.inst_name}" + if not node.is_array: + return f"{base}_{signal}" + if node.current_idx is not None: + # This is a specific instance of an array + return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}" + if idx is not None: + return f"{base}_{signal}[{idx}]" + return f"{base}_{signal}[N_{node.inst_name.upper()}S]" diff --git a/src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv b/src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv new file mode 100644 index 0000000..0a07664 --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/apb3/apb3_tmpl.sv @@ -0,0 +1,115 @@ +{%- if cpuif.is_interface -%} +`ifndef SYNTHESIS + initial begin + assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH) + else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("PADDR")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH); + assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH) + else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH); + end +`endif +{% endif -%} + +//====================================================== +// APB Fanout +//====================================================== +{%- for child in cpuif.addressable_children -%} +{%- if child is array -%} +for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}} + assign {{self.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PCLK")}}; + assign {{self.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PRESETn")}}; + assign {{self.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PENABLE")}}; + assign {{self.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWRITE")}}; + assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing + assign {{self.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWDATA")}}; +end +{%- else -%} +assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}}; +assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}}; +assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}}; +assign {{self.signal("PWRITE", child)}} = {{self.signal("PWRITE")}}; +assign {{self.signal("PADDR", child)}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing +assign {{self.signal("PWDATA", child)}} = {{self.signal("PWDATA")}}; +{%- endif -%} +{%- endfor -%} + +//====================================================== +// Address Decode Logic +//====================================================== +always_comb begin + // Default all PSELx signals to 0 +{%- for child in cpuif.addressable_children -%} +{%- if child is array -%} + for (int {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin + {{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b0; + end +{%- else -%} + {{self.signal("PSELx", child)}} = 1'b0; +{%- endif -%} +{%- endfor -%} + + if ({{self.signal("PSELx")}}) begin +{%- for child in cpuif.addressable_children -%} +{%- if loop.first -%} + if ({{cpuif.get_address_decode_condition(child)}}) begin +{%- else -%} + end else if ({{cpuif.get_address_decode_condition(child)}}) begin +{%- endif -%} + // Address matched for {{child.inst_name}} +{%- if child is array -%} + for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin + {{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b1; + end +{%- else -%} + {{self.signal("PSELx", child)}} = 1'b1; +{%- endif -%} +{%- if loop.last -%} + end else begin + // No address matched +{%- endif -%} +{%- endfor -%} + end + end else begin + // PSELx is low, nothing to do + end +end + +//====================================================== +// Read Data Mux +//====================================================== +always_comb begin + // Default read data to 0 + {{self.signal("PRDATA")}} = '0; + {{self.signal("PREADY")}} = 1'b1; + {{self.signal("PSLVERR")}} = 1'b0; + + if ({{self.signal("PSELx")}} && !{{self.signal("PWRITE")}} && {{self.signal("PENABLE")}}) begin +{%- for child in cpuif.addressable_children -%} +{%- if loop.first -%} + if ({{cpuif.get_address_decode_condition(child)}}) begin +{%- else -%} + end else if ({{cpuif.get_address_decode_condition(child)}}) begin +{%- endif -%} + // Address matched for {{child.inst_name}} +{%- if child is array -%} + for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin + {{self.signal("PRDATA")}} = {{self.signal("PRDATA", child, f"{child.inst_name.lower()}_idx")}}; + {{self.signal("PREADY")}} = {{self.signal("PREADY", child, f"{child.inst_name.lower()}_idx")}}; + {{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child, f"{child.inst_name.lower()}_idx")}}; + end +{%- else -%} + {{self.signal("PRDATA")}} = {{self.signal("PRDATA", child)}}; + {{self.signal("PREADY")}} = {{self.signal("PREADY", child)}}; + {{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child)}}; +{%- endif -%} +{%- if loop.last -%} + end else begin + // No address matched + {{self.signal("PRDATA")}} = {'hdeadbeef}[{{ds.data_width - 1}}:0]; // Indicate error on no match + {{self.signal("PSLVERR")}} = 1'b1; // Indicate error on no match + end +{%- endif -%} +{%- endfor -%} + end else begin + // Not a read transfer, nothing to do + end +end \ No newline at end of file diff --git a/src/peakrdl_busdecoder/cpuif/apb4/__init__.py b/src/peakrdl_busdecoder/cpuif/apb4/__init__.py new file mode 100644 index 0000000..e182a3a --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/apb4/__init__.py @@ -0,0 +1,4 @@ +from .apb4_cpuif import APB4Cpuif +from .apb4_cpuif_flat import APB4CpuifFlat + +__all__ = ["APB4Cpuif", "APB4CpuifFlat"] \ No newline at end of file diff --git a/src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py b/src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py new file mode 100644 index 0000000..6661d07 --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif.py @@ -0,0 +1,81 @@ +from systemrdl.node import AddressableNode +from ..base_cpuif import BaseCpuif + + +class APB4Cpuif(BaseCpuif): + template_path = "apb4_tmpl.sv" + is_interface = True + + def _port_declaration(self, child: AddressableNode) -> str: + base = f"apb4_intf.master m_apb_{child.inst_name}" + if not child.is_array: + return base + if child.current_idx is not None: + return f"{base}_{'_'.join(map(str, child.current_idx))} [N_{child.inst_name.upper()}S]" + return f"{base} [N_{child.inst_name.upper()}S]" + + @property + def port_declaration(self) -> str: + """Returns the port declaration for the APB4 interface.""" + slave_ports: list[str] = ["apb4_intf.slave s_apb"] + master_ports: list[str] = list( + map(self._port_declaration, self.addressable_children) + ) + + return ",\n".join(slave_ports + master_ports) + + def signal( + self, + signal: str, + node: AddressableNode | None = None, + idx: str | int | None = None, + ) -> str: + """Returns the signal name for the given signal and node.""" + if node is None: + # Node is none, so this is a slave signal + return f"s_apb.{signal}" + + # Master signal + base = f"m_apb_{node.inst_name}" + if not node.is_array: + return f"{base}.{signal}" + if node.current_idx is not None: + # This is a specific instance of an array + return f"{base}_{'_'.join(map(str, node.current_idx))}.{signal}" + if idx is not None: + return f"{base}[{idx}].{signal}" + + raise ValueError("Must provide an index for arrayed interface signals") + + def get_address_predicate(self, node: AddressableNode) -> str: + """ + Returns a SystemVerilog expression that evaluates to true when the + address on the bus matches the address range of the given node. + """ + + addr_mask = (1 << self.addr_width) - 1 + addr = node.absolute_address & addr_mask + size = node.size + if size == 0: + raise ValueError("Node size must be greater than 0") + if (addr % size) != 0: + raise ValueError("Node address must be aligned to its size") + + # Calculate the address range of the node + addr_start = addr + addr_end = addr + size - 1 + if addr_end > addr_mask: + raise ValueError("Node address range exceeds address width") + + if addr_start == addr_end: + return f"({self.signal('PADDR')} == 'h{addr_start:X})" + + return f"({self.signal('PADDR')} >= 'h{addr_start:X} && {self.signal('PADDR')} <= 'h{addr_end:X})" + + def get_address_decode_condition(self, node: AddressableNode) -> str: + """ + Returns a SystemVerilog expression that evaluates to true when the + address on the bus matches the address range of the given node. + """ + addr_pred = self.get_address_predicate(node) + return addr_pred \ No newline at end of file diff --git a/src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py b/src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py new file mode 100644 index 0000000..7f28645 --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/apb4/apb4_cpuif_flat.py @@ -0,0 +1,66 @@ +from systemrdl.node import AddressableNode +from ..base_cpuif import BaseCpuif + + +class APB4CpuifFlat(BaseCpuif): + template_path = "apb4_tmpl.sv" + is_interface = False + + def _port_declaration(self, child: AddressableNode) -> list[str]: + return [ + f"input logic {self.signal('PCLK', child)}", + f"input logic {self.signal('PRESETn', child)}", + f"input logic {self.signal('PSELx', child)}", + f"input logic {self.signal('PENABLE', child)}", + f"input logic {self.signal('PWRITE', child)}", + f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR', child)}", + f"input logic [2:0] {self.signal('PPROT', child)}", + f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA', child)}", + f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB', child)}", + f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA', child)}", + f"output logic {self.signal('PREADY', child)}", + f"output logic {self.signal('PSLVERR', child)}", + ] + + @property + def port_declaration(self) -> str: + slave_ports: list[str] = [ + f"input logic {self.signal('PCLK')}", + f"input logic {self.signal('PRESETn')}", + f"input logic {self.signal('PSELx')}", + f"input logic {self.signal('PENABLE')}", + f"input logic {self.signal('PWRITE')}", + f"input logic [{self.addr_width - 1}:0] {self.signal('PADDR')}", + f"input logic [2:0] {self.signal('PPROT')}", + f"input logic [{self.data_width - 1}:0] {self.signal('PWDATA')}", + f"input logic [{self.data_width // 8 - 1}:0] {self.signal('PSTRB')}", + f"output logic [{self.data_width - 1}:0] {self.signal('PRDATA')}", + f"output logic {self.signal('PREADY')}", + f"output logic {self.signal('PSLVERR')}", + ] + master_ports: list[str] = [] + for child in self.addressable_children: + master_ports.extend(self._port_declaration(child)) + + return ",\n".join(slave_ports + master_ports) + + def signal( + self, + signal: str, + node: AddressableNode | None = None, + idx: str | int | None = None, + ) -> str: + if node is None: + # Node is none, so this is a slave signal + return f"s_apb_{signal}" + + # Master signal + base = f"m_apb_{node.inst_name}" + if not node.is_array: + return f"{base}_{signal}" + if node.current_idx is not None: + # This is a specific instance of an array + return f"{base}_{signal}_{'_'.join(map(str, node.current_idx))}" + if idx is not None: + return f"{base}_{signal}[{idx}]" + return f"{base}_{signal}[N_{node.inst_name.upper()}S]" diff --git a/src/peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv b/src/peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv new file mode 100644 index 0000000..4d5c318 --- /dev/null +++ b/src/peakrdl_busdecoder/cpuif/apb4/apb4_tmpl.sv @@ -0,0 +1,119 @@ +{%- if cpuif.is_interface -%} +`ifndef SYNTHESIS + initial begin + assert_bad_addr_width: assert($bits({{cpuif.signal("PADDR")}}) >= {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH) + else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("PADDR")}}), {{ds.package_name}}::{{ds.module_name|upper}}_MIN_ADDR_WIDTH); + assert_bad_data_width: assert($bits({{cpuif.signal("PWDATA")}}) == {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH) + else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("PWDATA")}}), {{ds.package_name}}::{{ds.module_name|upper}}_DATA_WIDTH); + end +`endif +{% endif -%} + +//====================================================== +// APB Fanout +//====================================================== +{%- for child in cpuif.addressable_children -%} +{%- if child is array -%} +for (genvar g_{{child.inst_name|lower}}_idx = 0; g_{{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; g_{{child.inst_name|lower}}_idx++) begin : g_passthrough_{{child.inst_name|lower}} + assign {{self.signal("PCLK", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PCLK")}}; + assign {{self.signal("PRESETn", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PRESETn")}}; + assign {{self.signal("PENABLE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PENABLE")}}; + assign {{self.signal("PWRITE", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWRITE")}}; + assign {{self.signal("PADDR", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing + assign {{self.signal("PPROT", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PPROT")}}; + assign {{self.signal("PWDATA", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PWDATA")}}; + assign {{self.signal("PSTRB", child, f"g_{child.inst_name.lower()}_idx")}} = {{self.signal("PSTRB")}}; +end +{%- else -%} +assign {{self.signal("PCLK", child)}} = {{self.signal("PCLK")}}; +assign {{self.signal("PRESETn", child)}} = {{self.signal("PRESETn")}}; +assign {{self.signal("PENABLE", child)}} = {{self.signal("PENABLE")}}; +assign {{self.signal("PWRITE", child)}} = {{self.signal("PWRITE")}}; +assign {{self.signal("PADDR", child)}} = {{self.signal("PADDR")}} [{{child.addr_width - 1}}:0]; // FIXME: Check slicing +assign {{self.signal("PPROT", child)}} = {{self.signal("PPROT")}}; +assign {{self.signal("PWDATA", child)}} = {{self.signal("PWDATA")}}; +assign {{self.signal("PSTRB", child)}} = {{self.signal("PSTRB")}}; +{%- endif -%} +{%- endfor -%} + +//====================================================== +// Address Decode Logic +//====================================================== +always_comb begin + // Default all PSELx signals to 0 +{%- for child in cpuif.addressable_children -%} +{%- if child is array -%} + for (int {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin + {{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b0; + end +{%- else -%} + {{self.signal("PSELx", child)}} = 1'b0; +{%- endif -%} +{%- endfor -%} + + if ({{self.signal("PSELx")}}) begin +{%- for child in cpuif.addressable_children -%} +{%- if loop.first -%} + if ({{cpuif.get_address_decode_condition(child)}}) begin +{%- else -%} + end else if ({{cpuif.get_address_decode_condition(child)}}) begin +{%- endif -%} + // Address matched for {{child.inst_name}} +{%- if child is array -%} + for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin + {{self.signal("PSELx", child, f"{child.inst_name.lower()}_idx")}} = 1'b1; + end +{%- else -%} + {{self.signal("PSELx", child)}} = 1'b1; +{%- endif -%} +{%- if loop.last -%} + end else begin + // No address matched +{%- endif -%} +{%- endfor -%} + end + end else begin + // PSELx is low, nothing to do + end +end + +//====================================================== +// Read Data Mux +//====================================================== +always_comb begin + // Default read data to 0 + {{self.signal("PRDATA")}} = '0; + {{self.signal("PREADY")}} = 1'b1; + {{self.signal("PSLVERR")}} = 1'b0; + + if ({{self.signal("PSELx")}} && !{{self.signal("PWRITE")}} && {{self.signal("PENABLE")}}) begin +{%- for child in cpuif.addressable_children -%} +{%- if loop.first -%} + if ({{cpuif.get_address_decode_condition(child)}}) begin +{%- else -%} + end else if ({{cpuif.get_address_decode_condition(child)}}) begin +{%- endif -%} + // Address matched for {{child.inst_name}} +{%- if child is array -%} + for (genvar {{child.inst_name|lower}}_idx = 0; {{child.inst_name|lower}}_idx < N_{{child.inst_name|upper}}S; {{child.inst_name|lower}}_idx++) begin + {{self.signal("PRDATA")}} = {{self.signal("PRDATA", child, f"{child.inst_name.lower()}_idx")}}; + {{self.signal("PREADY")}} = {{self.signal("PREADY", child, f"{child.inst_name.lower()}_idx")}}; + {{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child, f"{child.inst_name.lower()}_idx")}}; + end +{%- else -%} + {{self.signal("PRDATA")}} = {{self.signal("PRDATA", child)}}; + {{self.signal("PREADY")}} = {{self.signal("PREADY", child)}}; + {{self.signal("PSLVERR")}} = {{self.signal("PSLVERR", child)}}; +{%- endif -%} +{%- if loop.last -%} + end else begin + // No address matched + {{self.signal("PRDATA")}} = {'hdeadbeef}[{{ds.data_width - 1}}:0]; // Indicate error on no match + {{self.signal("PSLVERR")}} = 1'b1; // Indicate error on no match + end +{%- endif -%} +{%- endfor -%} + end else begin + // Not a read transfer, nothing to do + end +end \ No newline at end of file diff --git a/src/peakrdl_regblock/cpuif/axi4lite/__init__.py b/src/peakrdl_busdecoder/cpuif/axi4lite/__init__.py similarity index 98% rename from src/peakrdl_regblock/cpuif/axi4lite/__init__.py rename to src/peakrdl_busdecoder/cpuif/axi4lite/__init__.py index 25376d1..24e5b1a 100644 --- a/src/peakrdl_regblock/cpuif/axi4lite/__init__.py +++ b/src/peakrdl_busdecoder/cpuif/axi4lite/__init__.py @@ -1,4 +1,4 @@ -from ..base import CpuifBase +from ..base_cpuif import CpuifBase class AXI4Lite_Cpuif(CpuifBase): diff --git a/src/peakrdl_regblock/cpuif/axi4lite/axi4lite_tmpl.sv b/src/peakrdl_busdecoder/cpuif/axi4lite/axi4lite_tmpl.sv similarity index 100% rename from src/peakrdl_regblock/cpuif/axi4lite/axi4lite_tmpl.sv rename to src/peakrdl_busdecoder/cpuif/axi4lite/axi4lite_tmpl.sv diff --git a/src/peakrdl_regblock/cpuif/base.py b/src/peakrdl_busdecoder/cpuif/base_cpuif.py similarity index 61% rename from src/peakrdl_regblock/cpuif/base.py rename to src/peakrdl_busdecoder/cpuif/base_cpuif.py index 66935c8..f417960 100644 --- a/src/peakrdl_regblock/cpuif/base.py +++ b/src/peakrdl_busdecoder/cpuif/base_cpuif.py @@ -1,8 +1,9 @@ -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING import inspect import os import jinja2 as jj +from systemrdl.node import AddressableNode from ..utils import clog2, is_pow2, roundup_pow2 @@ -10,13 +11,22 @@ if TYPE_CHECKING: from ..exporter import BusDecoderExporter -class CpuifBase: +class BaseCpuif: # Path is relative to the location of the class that assigns this variable template_path = "" - def __init__(self, exp: "BusDecoderExporter"): + def __init__(self, exp: "BusDecoderExporter") -> None: self.exp = exp self.reset = exp.ds.top_node.cpuif_reset + self.unroll = exp.ds.cpuif_unroll + + @property + def addressable_children(self) -> list[AddressableNode]: + return [ + child + for child in self.exp.ds.top_node.children(unroll=self.unroll) + if isinstance(child, AddressableNode) + ] @property def addr_width(self) -> int: @@ -35,12 +45,17 @@ class CpuifBase: raise NotImplementedError() @property - def parameters(self) -> List[str]: + def parameters(self) -> list[str]: """ Optional list of additional parameters this CPU interface provides to the module's definition """ - return [] + array_parameters = [ + f"parameter N_{child.inst_name.upper()}S = {child.n_elements}" + for child in self.addressable_children + if self.check_is_array(child) + ] + return array_parameters def _get_template_path_class_dir(self) -> str: """ @@ -53,6 +68,9 @@ class CpuifBase: return class_dir raise RuntimeError + def check_is_array(self, node: AddressableNode) -> bool: + return node.is_array and not self.unroll + def get_implementation(self) -> str: class_dir = self._get_template_path_class_dir() loader = jj.FileSystemLoader(class_dir) @@ -60,14 +78,13 @@ class CpuifBase: loader=loader, undefined=jj.StrictUndefined, ) + jj_env.tests["array"] = self.check_is_array # type: ignore + jj_env.filters["clog2"] = clog2 # type: ignore + jj_env.filters["is_pow2"] = is_pow2 # type: ignore + jj_env.filters["roundup_pow2"] = roundup_pow2 # type: ignore context = { "cpuif": self, - "get_always_ff_event": self.exp.dereferencer.get_always_ff_event, - "get_resetsignal": self.exp.dereferencer.get_resetsignal, - "clog2": clog2, - "is_pow2": is_pow2, - "roundup_pow2": roundup_pow2, "ds": self.exp.ds, } diff --git a/src/peakrdl_busdecoder/dereferencer.py b/src/peakrdl_busdecoder/dereferencer.py new file mode 100644 index 0000000..221247b --- /dev/null +++ b/src/peakrdl_busdecoder/dereferencer.py @@ -0,0 +1,42 @@ +from typing import TYPE_CHECKING +from systemrdl.node import AddrmapNode, FieldNode, RegNode, AddressableNode + +if TYPE_CHECKING: + from .exporter import BusDecoderExporter, DesignState + from .addr_decode import AddressDecode + + +class Dereferencer: + """ + This class provides an interface to convert conceptual SystemRDL references + into Verilog identifiers + """ + + def __init__(self, exp: "BusDecoderExporter"): + self.exp = exp + + @property + def address_decode(self) -> "AddressDecode": + return self.exp.address_decode + + @property + def ds(self) -> "DesignState": + return self.exp.ds + + @property + def top_node(self) -> AddrmapNode: + return self.exp.ds.top_node + + def get_access_strobe( + self, obj: RegNode | FieldNode, reduce_substrobes: bool = True + ) -> str: + """ + Returns the Verilog string that represents the register's access strobe + """ + return self.address_decode.get_access_strobe(obj, reduce_substrobes) + + def get_external_block_access_strobe(self, obj: "AddressableNode") -> str: + """ + Returns the Verilog string that represents the external block's access strobe + """ + return self.address_decode.get_external_block_access_strobe(obj) diff --git a/src/peakrdl_busdecoder/exporter.py b/src/peakrdl_busdecoder/exporter.py new file mode 100644 index 0000000..8769dd6 --- /dev/null +++ b/src/peakrdl_busdecoder/exporter.py @@ -0,0 +1,184 @@ +import os +from typing import TYPE_CHECKING, Any + +import jinja2 as jj +from systemrdl.node import AddrmapNode, RootNode +from systemrdl.rdltypes.user_enum import UserEnum + +from .addr_decode import AddressDecode +from .dereferencer import Dereferencer +from .identifier_filter import kw_filter as kwf +from .utils import clog2 +from .scan_design import DesignScanner +from .validate_design import DesignValidator +from .cpuif import BaseCpuif +from .cpuif.apb4 import APB4Cpuif +from .sv_int import SVInt + +if TYPE_CHECKING: + pass + + +class BusDecoderExporter: + cpuif: BaseCpuif + address_decode: AddressDecode + dereferencer: Dereferencer + ds: "DesignState" + + def __init__(self, **kwargs: Any) -> None: + # Check for stray kwargs + if kwargs: + raise TypeError( + f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'" + ) + + loader = jj.ChoiceLoader( + [ + jj.FileSystemLoader(os.path.dirname(__file__)), + jj.PrefixLoader( + { + "base": jj.FileSystemLoader(os.path.dirname(__file__)), + }, + delimiter=":", + ), + ] + ) + + self.jj_env = jj.Environment( + loader=loader, + undefined=jj.StrictUndefined, + ) + + def export( + self, node: RootNode | AddrmapNode, output_dir: str, **kwargs: Any + ) -> None: + """ + Parameters + ---------- + node: AddrmapNode + Top-level SystemRDL node to export. + output_dir: str + Path to the output directory where generated SystemVerilog will be written. + Output includes two files: a module definition and package definition. + cpuif_cls: :class:`peakrdl_busdecoder.cpuif.CpuifBase` + Specify the class type that implements the CPU interface of your choice. + Defaults to AMBA APB4. + module_name: str + Override the SystemVerilog module name. By default, the module name + is the top-level node's name. + package_name: str + Override the SystemVerilog package name. By default, the package name + is the top-level node's name with a "_pkg" suffix. + address_width: int + Override the CPU interface's address width. By default, address width + is sized to the contents of the busdecoder. + unroll: bool + Unroll arrayed addressable nodes into separate instances in the CPU + interface. By default, arrayed nodes are kept as arrays. + """ + # If it is the root node, skip to top addrmap + if isinstance(node, RootNode): + top_node = node.top + else: + top_node = node + + self.ds = DesignState(top_node, kwargs) + + cpuif_cls: type[BaseCpuif] = kwargs.pop("cpuif_cls", None) or APB4Cpuif + + # Check for stray kwargs + if kwargs: + raise TypeError( + f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'" + ) + + # Construct exporter components + self.cpuif = cpuif_cls(self) + self.address_decode = AddressDecode(self) + self.dereferencer = Dereferencer(self) + + # Validate that there are no unsupported constructs + DesignValidator(self).do_validate() + + # Build Jinja template context + context = { + "cpuif": self.cpuif, + "address_decode": self.address_decode, + "ds": self.ds, + "kwf": kwf, + "SVInt": SVInt, + } + + # Write out design + os.makedirs(output_dir, exist_ok=True) + package_file_path = os.path.join(output_dir, self.ds.package_name + ".sv") + template = self.jj_env.get_template("package_tmpl.sv") + stream = template.stream(context) + stream.dump(package_file_path) + + module_file_path = os.path.join(output_dir, self.ds.module_name + ".sv") + template = self.jj_env.get_template("module_tmpl.sv") + stream = template.stream(context) + stream.dump(module_file_path) + + if hwif_report_file: + hwif_report_file.close() + + +class DesignState: + """ + Dumping ground for all sorts of variables that are relevant to a particular + design. + """ + + def __init__(self, top_node: AddrmapNode, kwargs: Any) -> None: + self.top_node = top_node + msg = top_node.env.msg + + # ------------------------ + # Extract compiler args + # ------------------------ + self.reuse_hwif_typedefs: bool = kwargs.pop("reuse_hwif_typedefs", True) + self.module_name: str = kwargs.pop("module_name", None) or kwf( + self.top_node.inst_name + ) + self.package_name: str = kwargs.pop("package_name", None) or ( + self.module_name + "_pkg" + ) + user_addr_width: int | None = kwargs.pop("address_width", None) + + self.cpuif_unroll: bool = kwargs.pop("cpuif_unroll", False) + + # ------------------------ + # Info about the design + # ------------------------ + self.cpuif_data_width = 0 + + # Track any referenced enums + self.user_enums: list[type[UserEnum]] = [] + + # Scan the design to fill in above variables + DesignScanner(self).do_scan() + + if self.cpuif_data_width == 0: + # Scanner did not find any registers in the design being exported, + # so the width is not known. + # Assume 32-bits + msg.warning( + "Addrmap being exported only contains external components. Unable to infer the CPUIF bus width. Assuming 32-bits.", + self.top_node.inst.def_src_ref, + ) + self.cpuif_data_width = 32 + + # ------------------------ + # Min address width encloses the total size AND at least 1 useful address bit + self.addr_width = max( + clog2(self.top_node.size), clog2(self.cpuif_data_width // 8) + 1 + ) + + if user_addr_width is not None: + if user_addr_width < self.addr_width: + msg.fatal( + f"User-specified address width shall be greater than or equal to {self.addr_width}." + ) + self.addr_width = user_addr_width diff --git a/src/peakrdl_regblock/forloop_generator.py b/src/peakrdl_busdecoder/forloop_generator.py similarity index 79% rename from src/peakrdl_regblock/forloop_generator.py rename to src/peakrdl_busdecoder/forloop_generator.py index 7ff0f4e..4fa27a7 100644 --- a/src/peakrdl_regblock/forloop_generator.py +++ b/src/peakrdl_busdecoder/forloop_generator.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional, List, Union +from typing import TYPE_CHECKING import textwrap from systemrdl.walker import RDLListener, RDLWalker, WalkerAction @@ -6,15 +6,16 @@ from systemrdl.walker import RDLListener, RDLWalker, WalkerAction if TYPE_CHECKING: from systemrdl.node import AddressableNode, Node -class Body: +class Body: def __init__(self) -> None: - self.children = [] # type: List[Union[str, Body]] + self.children: list[str | Body] = [] def __str__(self) -> str: - s = '\n'.join((str(x) for x in self.children)) + s = "\n".join((str(x) for x in self.children)) return s + class LoopBody(Body): def __init__(self, dim: int, iterator: str, i_type: str) -> None: super().__init__() @@ -26,7 +27,7 @@ class LoopBody(Body): s = super().__str__() return ( f"for({self.i_type} {self.iterator}=0; {self.iterator}<{self.dim}; {self.iterator}++) begin\n" - + textwrap.indent(s, " ") + + textwrap.indent(s, "\t") + "\nend" ) @@ -37,7 +38,7 @@ class ForLoopGenerator: def __init__(self) -> None: self._loop_level = 0 - self._stack = [] # type: List[Body] + self._stack: list[Body] = [] @property def current_loop(self) -> Body: @@ -65,7 +66,7 @@ class ForLoopGenerator: b = Body() self._stack.append(b) - def finish(self) -> Optional[str]: + def finish(self) -> str | None: b = self._stack.pop() assert not self._stack @@ -73,15 +74,17 @@ class ForLoopGenerator: return None return str(b) -class RDLForLoopGenerator(ForLoopGenerator, RDLListener): - def get_content(self, node: 'Node') -> Optional[str]: +class RDLForLoopGenerator(ForLoopGenerator, RDLListener): + def get_content(self, node: "Node") -> str | None: self.start() walker = RDLWalker() walker.walk(node, self, skip_top=True) return self.finish() - def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: + def enter_AddressableComponent( + self, node: "AddressableNode" + ) -> WalkerAction | None: if not node.array_dimensions: return None @@ -89,7 +92,7 @@ class RDLForLoopGenerator(ForLoopGenerator, RDLListener): self.push_loop(dim) return None - def exit_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: + def exit_AddressableComponent(self, node: "AddressableNode") -> WalkerAction | None: if not node.array_dimensions: return None diff --git a/src/peakrdl_regblock/identifier_filter.py b/src/peakrdl_busdecoder/identifier_filter.py similarity index 100% rename from src/peakrdl_regblock/identifier_filter.py rename to src/peakrdl_busdecoder/identifier_filter.py diff --git a/src/peakrdl_busdecoder/module_tmpl.sv b/src/peakrdl_busdecoder/module_tmpl.sv new file mode 100644 index 0000000..f2c9717 --- /dev/null +++ b/src/peakrdl_busdecoder/module_tmpl.sv @@ -0,0 +1,103 @@ + +//========================================================== +// Module: {{ds.module_name}} +// Description: CPU Interface Bus Decoder +// Author: PeakRDL-busdecoder +// License: GLPLv3 +// Date: {{current_date}} +// Version: {{version}} +// Links: +// - https://github.com/SystemRDL/PeakRDL-busdecoder +//========================================================== + + +module {{ds.module_name}} + {%- if cpuif.parameters %} #( + {{-cpuif.parameters|join(",\n")|indent(8)}} + ) {%- endif %} ( + {{-cpuif.port_declaration|indent(8)}} + ); + + //-------------------------------------------------------------------------- + // CPU Bus interface logic + //-------------------------------------------------------------------------- + logic cpuif_req; + logic [{{cpuif.addr_width-1}}:0] cpuif_wr_addr; + logic [{{cpuif.addr_width-1}}:0] cpuif_rd_addr; + + logic cpuif_wr_ack; + logic cpuif_wr_err; + logic [{{cpuif.data_width-1}}:0] cpuif_wr_data; + logic [{{cpuif.data_width//8-1}}:0] cpuif_wr_byte_en; + + logic cpuif_rd_ack; + logic cpuif_rd_err; + logic [{{cpuif.data_width-1}}:0] cpuif_rd_data; + + //-------------------------------------------------------------------------- + // Child instance signals + //-------------------------------------------------------------------------- + logic [{{cpuif.addressable_children | length}}-1:0] cpuif_wr_sel; + logic [{{cpuif.addressable_children | length}}-1:0] cpuif_rd_sel; + + //-------------------------------------------------------------------------- + // Slave <-> Internal CPUIF <-> Master + //-------------------------------------------------------------------------- + {{-cpuif.get_implementation()|indent}} + + //-------------------------------------------------------------------------- + // Write Address Decoder + //-------------------------------------------------------------------------- + always_comb begin + // Default all write select signals to 0 + cpuif_wr_sel = '0; + + if (cpuif_req && cpuif_wr_en) begin + // A write request is pending + {%- for child in cpuif.addressable_children -%} + {%- if loop.first -%} + if ({{cpuif.get_address_decode_condition(child)}}) begin + {%- else -%} + end else if ({{cpuif.get_address_decode_condition(child)}}) begin + {%- endif -%} + // Address matched for {{child.inst_name}} + cpuif_wr_sel[{{loop.index0}}] = 1'b1; + {%- endfor -%} + end else begin + // No address match, all select signals remain 0 + cpuif_wr_err = 1'b1; // Indicate error on no match + end + end else begin + // No write request, all select signals remain 0 + end + end + + //-------------------------------------------------------------------------- + // Read Address Decoder + //-------------------------------------------------------------------------- + always_comb begin + // Default all read select signals to 0 + cpuif_rd_sel = '0; + + + if (cpuif_req && !cpuif_wr_en) begin + // A read request is pending + {%- for child in cpuif.addressable_children -%} + {%- if loop.first -%} + if ({{cpuif.get_address_decode_condition(child)}}) begin + {%- else -%} + end else if ({{cpuif.get_address_decode_condition(child)}}) begin + {%- endif -%} + // Address matched for {{child.inst_name}} + cpuif_rd_sel[{{loop.index0}}] = 1'b1; + {%- endfor -%} + end else begin + // No address match, all select signals remain 0 + cpuif_rd_err = 1'b1; // Indicate error on no match + end + end else begin + // No read request, all select signals remain 0 + end + end +endmodule +{# (eof newline anchor) #} diff --git a/src/peakrdl_regblock/package_tmpl.sv b/src/peakrdl_busdecoder/package_tmpl.sv similarity index 82% rename from src/peakrdl_regblock/package_tmpl.sv rename to src/peakrdl_busdecoder/package_tmpl.sv index eb001d4..84ada70 100644 --- a/src/peakrdl_regblock/package_tmpl.sv +++ b/src/peakrdl_busdecoder/package_tmpl.sv @@ -6,9 +6,5 @@ package {{ds.package_name}}; localparam {{ds.module_name.upper()}}_DATA_WIDTH = {{ds.cpuif_data_width}}; localparam {{ds.module_name.upper()}}_MIN_ADDR_WIDTH = {{ds.addr_width}}; localparam {{ds.module_name.upper()}}_SIZE = {{SVInt(ds.top_node.size)}}; - - {{-hwif.get_extra_package_params()|indent}} - - {{-hwif.get_package_contents()|indent}} endpackage {# (eof newline anchor) #} diff --git a/src/peakrdl_busdecoder/scan_design.py b/src/peakrdl_busdecoder/scan_design.py new file mode 100644 index 0000000..08fb6cc --- /dev/null +++ b/src/peakrdl_busdecoder/scan_design.py @@ -0,0 +1,47 @@ +from typing import TYPE_CHECKING + +from systemrdl.walker import RDLListener, RDLWalker, WalkerAction +from systemrdl.node import RegNode + +if TYPE_CHECKING: + from systemrdl.node import Node, AddressableNode, AddrmapNode + from .exporter import DesignState + + +class DesignScanner(RDLListener): + """ + Scans through the register model and validates that any unsupported features + are not present. + + Also collects any information that is required prior to the start of the export process. + """ + + def __init__(self, ds: "DesignState") -> None: + self.ds = ds + self.msg = self.top_node.env.msg + + @property + def top_node(self) -> "AddrmapNode": + return self.ds.top_node + + def do_scan(self) -> None: + RDLWalker().walk(self.top_node, self) + if self.msg.had_error: + self.msg.fatal("Unable to export due to previous errors") + + def enter_Component(self, node: "Node") -> WalkerAction | None: + if node.external and (node != self.top_node): + # Do not inspect external components. None of my business + return WalkerAction.SkipDescendants + + # Collect any signals that are referenced by a property + for prop_name in node.list_properties(): + _ = node.get_property(prop_name) + + return WalkerAction.Continue + + def enter_AddressableComponent(self, node: "AddressableNode") -> None: + if node.external and node != self.top_node: + self.ds.has_external_addressable = True + if not isinstance(node, RegNode): + self.ds.has_external_block = True diff --git a/src/peakrdl_regblock/sv_int.py b/src/peakrdl_busdecoder/sv_int.py similarity index 84% rename from src/peakrdl_regblock/sv_int.py rename to src/peakrdl_busdecoder/sv_int.py index 496eacf..94cac01 100644 --- a/src/peakrdl_regblock/sv_int.py +++ b/src/peakrdl_busdecoder/sv_int.py @@ -1,7 +1,5 @@ -from typing import Optional - class SVInt: - def __init__(self, value: int, width: Optional[int] = None) -> None: + def __init__(self, value: int, width: int | None = None) -> None: self.value = value self.width = width diff --git a/src/peakrdl_regblock/udps/__init__.py b/src/peakrdl_busdecoder/udps/__init__.py similarity index 82% rename from src/peakrdl_regblock/udps/__init__.py rename to src/peakrdl_busdecoder/udps/__init__.py index 2eb9c20..72be45d 100644 --- a/src/peakrdl_regblock/udps/__init__.py +++ b/src/peakrdl_busdecoder/udps/__init__.py @@ -1,10 +1,11 @@ +from systemrdl.udp import UDPDefinition from .rw_buffering import BufferWrites, WBufferTrigger from .rw_buffering import BufferReads, RBufferTrigger from .extended_swacc import ReadSwacc, WriteSwacc from .fixedpoint import IntWidth, FracWidth from .signed import IsSigned -ALL_UDPS = [ +ALL_UDPS: list[type[UDPDefinition]] = [ BufferWrites, WBufferTrigger, BufferReads, diff --git a/src/peakrdl_regblock/udps/extended_swacc.py b/src/peakrdl_busdecoder/udps/extended_swacc.py similarity index 100% rename from src/peakrdl_regblock/udps/extended_swacc.py rename to src/peakrdl_busdecoder/udps/extended_swacc.py diff --git a/src/peakrdl_regblock/udps/fixedpoint.py b/src/peakrdl_busdecoder/udps/fixedpoint.py similarity index 100% rename from src/peakrdl_regblock/udps/fixedpoint.py rename to src/peakrdl_busdecoder/udps/fixedpoint.py diff --git a/src/peakrdl_regblock/udps/rw_buffering.py b/src/peakrdl_busdecoder/udps/rw_buffering.py similarity index 100% rename from src/peakrdl_regblock/udps/rw_buffering.py rename to src/peakrdl_busdecoder/udps/rw_buffering.py diff --git a/src/peakrdl_regblock/udps/signed.py b/src/peakrdl_busdecoder/udps/signed.py similarity index 100% rename from src/peakrdl_regblock/udps/signed.py rename to src/peakrdl_busdecoder/udps/signed.py diff --git a/src/peakrdl_regblock/utils.py b/src/peakrdl_busdecoder/utils.py similarity index 73% rename from src/peakrdl_regblock/utils.py rename to src/peakrdl_busdecoder/utils.py index b9d7a7f..dd515eb 100644 --- a/src/peakrdl_regblock/utils.py +++ b/src/peakrdl_busdecoder/utils.py @@ -1,5 +1,5 @@ import re -from typing import Match, Union, Optional +from typing import Match, overload from systemrdl.rdltypes.references import PropertyReference from systemrdl.node import Node, AddrmapNode @@ -7,6 +7,7 @@ from systemrdl.node import Node, AddrmapNode from .identifier_filter import kw_filter as kwf from .sv_int import SVInt + def get_indexed_path(top_node: Node, target_node: Node) -> str: """ TODO: Add words about indexing and why i'm doing this. Copy from logbook @@ -17,35 +18,42 @@ def get_indexed_path(top_node: Node, target_node: Node) -> str: class ReplaceUnknown: def __init__(self) -> None: self.i = 0 - def __call__(self, match: Match) -> str: - s = f'i{self.i}' + + def __call__(self, match: Match[str]) -> str: + s = f"i{self.i}" self.i += 1 return s - path = re.sub(r'!', ReplaceUnknown(), path) + + path = re.sub(r"!", ReplaceUnknown(), path) # Sanitize any SV keywords - def kw_filter_repl(m: Match) -> str: + def kw_filter_repl(m: Match[str]) -> str: return kwf(m.group(0)) - path = re.sub(r'\w+', kw_filter_repl, path) + + path = re.sub(r"\w+", kw_filter_repl, path) return path + def clog2(n: int) -> int: - return (n-1).bit_length() + return (n - 1).bit_length() + def is_pow2(x: int) -> bool: return (x > 0) and ((x & (x - 1)) == 0) -def roundup_pow2(x: int) -> int: - return 1<<(x-1).bit_length() -def ref_is_internal(top_node: AddrmapNode, ref: Union[Node, PropertyReference]) -> bool: +def roundup_pow2(x: int) -> int: + return 1 << (x - 1).bit_length() + + +def ref_is_internal(top_node: AddrmapNode, ref: Node | PropertyReference) -> bool: """ Determine whether the reference is internal to the top node. For the sake of this exporter, root signals are treated as internal. """ - current_node: Optional[Node] + # current_node: Optional[Node] if isinstance(ref, Node): current_node = ref elif isinstance(ref, PropertyReference): @@ -70,7 +78,11 @@ def ref_is_internal(top_node: AddrmapNode, ref: Union[Node, PropertyReference]) return True -def do_slice(value: Union[SVInt, str], high: int, low: int) -> Union[SVInt, str]: +@overload +def do_slice(value: SVInt, high: int, low: int) -> SVInt: ... +@overload +def do_slice(value: str, high: int, low: int) -> str: ... +def do_slice(value: SVInt | str, high: int, low: int) -> SVInt | str: if isinstance(value, str): # If string, assume this is an identifier. Append bit-slice if high == low: @@ -89,13 +101,18 @@ def do_slice(value: Union[SVInt, str], high: int, low: int) -> Union[SVInt, str] return SVInt(v, w) -def do_bitswap(value: Union[SVInt, str]) -> Union[SVInt, str]: + +@overload +def do_bitswap(value: SVInt) -> SVInt: ... +@overload +def do_bitswap(value: str) -> str: ... +def do_bitswap(value: SVInt | str) -> SVInt | str: if isinstance(value, str): # If string, assume this is an identifier. Wrap in a streaming operator return "{<<{" + value + "}}" else: # it is an SVInt literal. bitswap it - assert value.width is not None # width must be known! + assert value.width is not None # width must be known! v = value.value vswap = 0 for _ in range(value.width): diff --git a/src/peakrdl_regblock/validate_design.py b/src/peakrdl_busdecoder/validate_design.py similarity index 97% rename from src/peakrdl_regblock/validate_design.py rename to src/peakrdl_busdecoder/validate_design.py index 2f4e432..b12dd97 100644 --- a/src/peakrdl_regblock/validate_design.py +++ b/src/peakrdl_busdecoder/validate_design.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional, List, Union +from typing import TYPE_CHECKING from systemrdl.walker import RDLListener, RDLWalker, WalkerAction from systemrdl.rdltypes import PropertyReference @@ -24,7 +24,7 @@ class DesignValidator(RDLListener): self.ds = exp.ds self.msg = self.top_node.env.msg - self._contains_external_block_stack = [] # type: List[bool] + self._contains_external_block_stack: list[bool] = [] self.contains_external_block = False @property @@ -36,7 +36,7 @@ class DesignValidator(RDLListener): if self.msg.had_error: self.msg.fatal("Unable to export due to previous errors") - def enter_Component(self, node: "Node") -> Optional[WalkerAction]: + def enter_Component(self, node: "Node") -> WalkerAction | None: if node.external and (node != self.top_node): # Do not inspect external components. None of my business return WalkerAction.SkipDescendants @@ -100,7 +100,7 @@ class DesignValidator(RDLListener): def enter_Addrmap(self, node: AddrmapNode) -> None: self._check_sharedextbus(node) - def _check_sharedextbus(self, node: Union[RegfileNode, AddrmapNode]) -> None: + def _check_sharedextbus(self, node: RegfileNode | AddrmapNode) -> None: if node.get_property("sharedextbus"): self.msg.error( "This exporter does not support enabling the 'sharedextbus' property yet.", diff --git a/src/peakrdl_regblock/__about__.py b/src/peakrdl_regblock/__about__.py deleted file mode 100644 index edebc2e..0000000 --- a/src/peakrdl_regblock/__about__.py +++ /dev/null @@ -1,2 +0,0 @@ -version_info = (1, 1, 1) -__version__ = ".".join([str(n) for n in version_info]) diff --git a/src/peakrdl_regblock/__peakrdl__.py b/src/peakrdl_regblock/__peakrdl__.py deleted file mode 100644 index 5b7087a..0000000 --- a/src/peakrdl_regblock/__peakrdl__.py +++ /dev/null @@ -1,213 +0,0 @@ -from typing import TYPE_CHECKING, Dict, Type -import functools -import sys - -from peakrdl.plugins.exporter import ExporterSubcommandPlugin -from peakrdl.config import schema -from peakrdl.plugins.entry_points import get_entry_points - -from .exporter import BusDecoderExporter -from .cpuif import CpuifBase, apb3, apb4, axi4lite, passthrough, avalon -from .udps import ALL_UDPS - -if TYPE_CHECKING: - import argparse - from systemrdl.node import AddrmapNode - - -class Exporter(ExporterSubcommandPlugin): - short_desc = "Generate a SystemVerilog control/status register (CSR) block" - - udp_definitions = ALL_UDPS - - cfg_schema = { - "cpuifs": {"*": schema.PythonObjectImport()}, - "default_reset": schema.Choice(["rst", "rst_n", "arst", "arst_n"]), - } - - @functools.lru_cache() - def get_cpuifs(self) -> Dict[str, Type[CpuifBase]]: - # All built-in CPUIFs - cpuifs = { - "passthrough": passthrough.PassthroughCpuif, - "apb3": apb3.APB3_Cpuif, - "apb3-flat": apb3.APB3_Cpuif_flattened, - "apb4": apb4.APB4_Cpuif, - "apb4-flat": apb4.APB4_Cpuif_flattened, - "axi4-lite": axi4lite.AXI4Lite_Cpuif, - "axi4-lite-flat": axi4lite.AXI4Lite_Cpuif_flattened, - "avalon-mm": avalon.Avalon_Cpuif, - "avalon-mm-flat": avalon.Avalon_Cpuif_flattened, - } - - # Load any cpuifs specified via entry points - for ep, dist in get_entry_points("peakrdl_busdecoder.cpuif"): - name = ep.name - cpuif = ep.load() - if name in cpuifs: - raise RuntimeError( - f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it already exists" - ) - if not issubclass(cpuif, CpuifBase): - raise RuntimeError( - f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a CpuifBase class" - ) - cpuifs[name] = cpuif - - # Load any CPUIFs via config import - for name, cpuif in self.cfg["cpuifs"].items(): - if name in cpuifs: - raise RuntimeError( - f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it already exists" - ) - if not issubclass(cpuif, CpuifBase): - raise RuntimeError( - f"A plugin for 'peakrdl-busdecoder' tried to load cpuif '{name}' but it not a CpuifBase class" - ) - cpuifs[name] = cpuif - - return cpuifs - - def add_exporter_arguments(self, arg_group: "argparse._ActionsContainer") -> None: - cpuifs = self.get_cpuifs() - - arg_group.add_argument( - "--cpuif", - choices=cpuifs.keys(), - default="apb3", - help="Select the CPU interface protocol to use [apb3]", - ) - - arg_group.add_argument( - "--module-name", - metavar="NAME", - default=None, - help="Override the SystemVerilog module name", - ) - - arg_group.add_argument( - "--package-name", - metavar="NAME", - default=None, - help="Override the SystemVerilog package name", - ) - - arg_group.add_argument( - "--type-style", - dest="type_style", - choices=["lexical", "hier"], - default="lexical", - help="""Choose how HWIF struct type names are generated. - The 'lexical' style will use RDL lexical scope & type names where - possible and attempt to re-use equivalent type definitions. - The 'hier' style uses component's hierarchy as the struct type name. [lexical] - """, - ) - - arg_group.add_argument( - "--hwif-report", - action="store_true", - default=False, - help="Generate a HWIF report file", - ) - - arg_group.add_argument( - "--addr-width", - type=int, - default=None, - help="""Override the CPU interface's address width. By default, - address width is sized to the contents of the busdecoder. - """, - ) - - arg_group.add_argument( - "--rt-read-fanin", - action="store_true", - default=False, - help="Enable additional read path retiming. Good for register blocks with large readback fan-in", - ) - arg_group.add_argument( - "--rt-read-response", - action="store_true", - default=False, - help="Enable additional retiming stage between readback fan-in and cpu interface", - ) - arg_group.add_argument( - "--rt-external", - help="Retime outputs to external components. Specify a comma-separated list of: reg,regfile,mem,addrmap,all", - ) - - arg_group.add_argument( - "--default-reset", - choices=["rst", "rst_n", "arst", "arst_n"], - default=None, - help="""Choose the default style of reset signal if not explicitly - specified by the SystemRDL design. If unspecified, the default reset - is active-high and synchronous [rst]""", - ) - - def do_export(self, top_node: "AddrmapNode", options: "argparse.Namespace") -> None: - cpuifs = self.get_cpuifs() - - retime_external_reg = False - retime_external_regfile = False - retime_external_mem = False - retime_external_addrmap = False - if options.rt_external: - for key in options.rt_external.split(","): - key = key.strip().lower() - if key == "reg": - retime_external_reg = True - elif key == "regfile": - retime_external_regfile = True - elif key == "mem": - retime_external_mem = True - elif key == "addrmap": - retime_external_addrmap = True - elif key == "all": - retime_external_reg = True - retime_external_regfile = True - retime_external_mem = True - retime_external_addrmap = True - else: - print( - "error: invalid option for --rt-external: '%s'" % key, - file=sys.stderr, - ) - - # Get default reset. Favor command-line over cfg. Fall back to 'rst' - default_rst = options.default_reset or self.cfg["default_reset"] or "rst" - if default_rst == "rst": - default_reset_activelow = False - default_reset_async = False - elif default_rst == "rst_n": - default_reset_activelow = True - default_reset_async = False - elif default_rst == "arst": - default_reset_activelow = False - default_reset_async = True - elif default_rst == "arst_n": - default_reset_activelow = True - default_reset_async = True - else: - raise RuntimeError - - x = BusDecoderExporter() - x.export( - top_node, - options.output, - cpuif_cls=cpuifs[options.cpuif], - module_name=options.module_name, - package_name=options.package_name, - reuse_hwif_typedefs=(options.type_style == "lexical"), - retime_read_fanin=options.rt_read_fanin, - retime_read_response=options.rt_read_response, - retime_external_reg=retime_external_reg, - retime_external_regfile=retime_external_regfile, - retime_external_mem=retime_external_mem, - retime_external_addrmap=retime_external_addrmap, - generate_hwif_report=options.hwif_report, - address_width=options.addr_width, - default_reset_activelow=default_reset_activelow, - default_reset_async=default_reset_async, - ) diff --git a/src/peakrdl_regblock/cpuif/__init__.py b/src/peakrdl_regblock/cpuif/__init__.py deleted file mode 100644 index 08b2adc..0000000 --- a/src/peakrdl_regblock/cpuif/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .base import CpuifBase diff --git a/src/peakrdl_regblock/cpuif/apb3/__init__.py b/src/peakrdl_regblock/cpuif/apb3/__init__.py deleted file mode 100644 index 4b1adc1..0000000 --- a/src/peakrdl_regblock/cpuif/apb3/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -from ..base import CpuifBase - -class APB3_Cpuif(CpuifBase): - template_path = "apb3_tmpl.sv" - is_interface = True - - @property - def port_declaration(self) -> str: - return "apb3_intf.slave s_apb" - - def signal(self, name:str) -> str: - return "s_apb." + name.upper() - - -class APB3_Cpuif_flattened(APB3_Cpuif): - is_interface = False - - @property - def port_declaration(self) -> str: - lines = [ - "input wire " + self.signal("psel"), - "input wire " + self.signal("penable"), - "input wire " + self.signal("pwrite"), - f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"), - f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"), - "output logic " + self.signal("pready"), - f"output logic [{self.data_width-1}:0] " + self.signal("prdata"), - "output logic " + self.signal("pslverr"), - ] - return ",\n".join(lines) - - def signal(self, name:str) -> str: - return "s_apb_" + name diff --git a/src/peakrdl_regblock/cpuif/apb3/apb3_tmpl.sv b/src/peakrdl_regblock/cpuif/apb3/apb3_tmpl.sv deleted file mode 100644 index 33a3663..0000000 --- a/src/peakrdl_regblock/cpuif/apb3/apb3_tmpl.sv +++ /dev/null @@ -1,48 +0,0 @@ -{%- if cpuif.is_interface -%} -`ifndef SYNTHESIS - initial begin - assert_bad_addr_width: assert($bits({{cpuif.signal("paddr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH) - else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("paddr")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH); - assert_bad_data_width: assert($bits({{cpuif.signal("pwdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH) - else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("pwdata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH); - end -`endif - -{% endif -%} - -// Request -logic is_active; -always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - is_active <= '0; - cpuif_req <= '0; - cpuif_req_is_wr <= '0; - cpuif_addr <= '0; - cpuif_wr_data <= '0; - end else begin - if(~is_active) begin - if({{cpuif.signal("psel")}}) begin - is_active <= '1; - cpuif_req <= '1; - cpuif_req_is_wr <= {{cpuif.signal("pwrite")}}; - {%- if cpuif.data_width_bytes == 1 %} - cpuif_addr <= {{cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:0]; - {%- else %} - cpuif_addr <= { {{-cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0}; - {%- endif %} - cpuif_wr_data <= {{cpuif.signal("pwdata")}}; - end - end else begin - cpuif_req <= '0; - if(cpuif_rd_ack || cpuif_wr_ack) begin - is_active <= '0; - end - end - end -end -assign cpuif_wr_biten = '1; - -// Response -assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack; -assign {{cpuif.signal("prdata")}} = cpuif_rd_data; -assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err; diff --git a/src/peakrdl_regblock/cpuif/apb4/__init__.py b/src/peakrdl_regblock/cpuif/apb4/__init__.py deleted file mode 100644 index 45b2961..0000000 --- a/src/peakrdl_regblock/cpuif/apb4/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -from ..base import CpuifBase - -class APB4_Cpuif(CpuifBase): - template_path = "apb4_tmpl.sv" - is_interface = True - - @property - def port_declaration(self) -> str: - return "apb4_intf.slave s_apb" - - def signal(self, name:str) -> str: - return "s_apb." + name.upper() - - -class APB4_Cpuif_flattened(APB4_Cpuif): - is_interface = False - - @property - def port_declaration(self) -> str: - lines = [ - "input wire " + self.signal("psel"), - "input wire " + self.signal("penable"), - "input wire " + self.signal("pwrite"), - "input wire [2:0] " + self.signal("pprot"), - f"input wire [{self.addr_width-1}:0] " + self.signal("paddr"), - f"input wire [{self.data_width-1}:0] " + self.signal("pwdata"), - f"input wire [{self.data_width_bytes-1}:0] " + self.signal("pstrb"), - "output logic " + self.signal("pready"), - f"output logic [{self.data_width-1}:0] " + self.signal("prdata"), - "output logic " + self.signal("pslverr"), - ] - return ",\n".join(lines) - - def signal(self, name:str) -> str: - return "s_apb_" + name diff --git a/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv b/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv deleted file mode 100644 index 4293bec..0000000 --- a/src/peakrdl_regblock/cpuif/apb4/apb4_tmpl.sv +++ /dev/null @@ -1,51 +0,0 @@ -{%- if cpuif.is_interface -%} -`ifndef SYNTHESIS - initial begin - assert_bad_addr_width: assert($bits({{cpuif.signal("paddr")}}) >= {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH) - else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("paddr")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_MIN_ADDR_WIDTH); - assert_bad_data_width: assert($bits({{cpuif.signal("pwdata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH) - else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("pwdata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH); - end -`endif - -{% endif -%} - -// Request -logic is_active; -always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - is_active <= '0; - cpuif_req <= '0; - cpuif_req_is_wr <= '0; - cpuif_addr <= '0; - cpuif_wr_data <= '0; - cpuif_wr_biten <= '0; - end else begin - if(~is_active) begin - if({{cpuif.signal("psel")}}) begin - is_active <= '1; - cpuif_req <= '1; - cpuif_req_is_wr <= {{cpuif.signal("pwrite")}}; - {%- if cpuif.data_width_bytes == 1 %} - cpuif_addr <= {{cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:0]; - {%- else %} - cpuif_addr <= { {{-cpuif.signal("paddr")}}[{{cpuif.addr_width-1}}:{{clog2(cpuif.data_width_bytes)}}], {{clog2(cpuif.data_width_bytes)}}'b0}; - {%- endif %} - cpuif_wr_data <= {{cpuif.signal("pwdata")}}; - for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin - cpuif_wr_biten[i*8 +: 8] <= {8{ {{-cpuif.signal("pstrb")}}[i]}}; - end - end - end else begin - cpuif_req <= '0; - if(cpuif_rd_ack || cpuif_wr_ack) begin - is_active <= '0; - end - end - end -end - -// Response -assign {{cpuif.signal("pready")}} = cpuif_rd_ack | cpuif_wr_ack; -assign {{cpuif.signal("prdata")}} = cpuif_rd_data; -assign {{cpuif.signal("pslverr")}} = cpuif_rd_err | cpuif_wr_err; diff --git a/src/peakrdl_regblock/cpuif/avalon/__init__.py b/src/peakrdl_regblock/cpuif/avalon/__init__.py deleted file mode 100644 index 20d8a59..0000000 --- a/src/peakrdl_regblock/cpuif/avalon/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from ..base import CpuifBase -from ...utils import clog2 - -class Avalon_Cpuif(CpuifBase): - template_path = "avalon_tmpl.sv" - is_interface = True - - @property - def port_declaration(self) -> str: - return "avalon_mm_intf.agent avalon" - - def signal(self, name:str) -> str: - return "avalon." + name - - @property - def word_addr_width(self) -> int: - # Avalon agents use word addressing, therefore address width is reduced - return self.addr_width - clog2(self.data_width_bytes) - -class Avalon_Cpuif_flattened(Avalon_Cpuif): - is_interface = False - - @property - def port_declaration(self) -> str: - lines = [ - "input wire " + self.signal("read"), - "input wire " + self.signal("write"), - "output logic " + self.signal("waitrequest"), - f"input wire [{self.word_addr_width-1}:0] " + self.signal("address"), - f"input wire [{self.data_width-1}:0] " + self.signal("writedata"), - f"input wire [{self.data_width_bytes-1}:0] " + self.signal("byteenable"), - "output logic " + self.signal("readdatavalid"), - "output logic " + self.signal("writeresponsevalid"), - f"output logic [{self.data_width-1}:0] " + self.signal("readdata"), - "output logic [1:0] " + self.signal("response"), - ] - return ",\n".join(lines) - - def signal(self, name:str) -> str: - return "avalon_" + name diff --git a/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv b/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv deleted file mode 100644 index fe59b23..0000000 --- a/src/peakrdl_regblock/cpuif/avalon/avalon_tmpl.sv +++ /dev/null @@ -1,41 +0,0 @@ -{%- if cpuif.is_interface -%} -`ifndef SYNTHESIS - initial begin - assert_bad_addr_width: assert($bits({{cpuif.signal("address")}}) >= {{cpuif.word_addr_width}}) - else $error("Interface address width of %0d is too small. Shall be at least %0d bits", $bits({{cpuif.signal("address")}}), {{cpuif.word_addr_width}}); - assert_bad_data_width: assert($bits({{cpuif.signal("writedata")}}) == {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH) - else $error("Interface data width of %0d is incorrect. Shall be %0d bits", $bits({{cpuif.signal("writedata")}}), {{ds.package_name}}::{{ds.module_name.upper()}}_DATA_WIDTH); - end -`endif - -{% endif -%} - -// Request -always_comb begin - cpuif_req = {{cpuif.signal("read")}} | {{cpuif.signal("write")}}; - cpuif_req_is_wr = {{cpuif.signal("write")}}; - {%- if cpuif.data_width_bytes == 1 %} - cpuif_addr = {{cpuif.signal("address")}}; - {%- else %} - cpuif_addr = { {{-cpuif.signal("address")}}, {{clog2(cpuif.data_width_bytes)}}'b0}; - {%- endif %} - cpuif_wr_data = {{cpuif.signal("writedata")}}; - for(int i=0; i<{{cpuif.data_width_bytes}}; i++) begin - cpuif_wr_biten[i*8 +: 8] = {8{ {{-cpuif.signal("byteenable")}}[i]}}; - end - {{cpuif.signal("waitrequest")}} = (cpuif_req_stall_rd & {{cpuif.signal("read")}}) | (cpuif_req_stall_wr & {{cpuif.signal("write")}}); -end - -// Response -always_comb begin - {{cpuif.signal("readdatavalid")}} = cpuif_rd_ack; - {{cpuif.signal("writeresponsevalid")}} = cpuif_wr_ack; - {{cpuif.signal("readdata")}} = cpuif_rd_data; - if(cpuif_rd_err || cpuif_wr_err) begin - // SLVERR - {{cpuif.signal("response")}} = 2'b10; - end else begin - // OK - {{cpuif.signal("response")}} = 2'b00; - end -end diff --git a/src/peakrdl_regblock/cpuif/passthrough/__init__.py b/src/peakrdl_regblock/cpuif/passthrough/__init__.py deleted file mode 100644 index 0363860..0000000 --- a/src/peakrdl_regblock/cpuif/passthrough/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -from ..base import CpuifBase - -class PassthroughCpuif(CpuifBase): - template_path = "passthrough_tmpl.sv" - - @property - def port_declaration(self) -> str: - lines = [ - "input wire s_cpuif_req", - "input wire s_cpuif_req_is_wr", - f"input wire [{self.addr_width-1}:0] s_cpuif_addr", - f"input wire [{self.data_width-1}:0] s_cpuif_wr_data", - f"input wire [{self.data_width-1}:0] s_cpuif_wr_biten", - "output wire s_cpuif_req_stall_wr", - "output wire s_cpuif_req_stall_rd", - "output wire s_cpuif_rd_ack", - "output wire s_cpuif_rd_err", - f"output wire [{self.data_width-1}:0] s_cpuif_rd_data", - "output wire s_cpuif_wr_ack", - "output wire s_cpuif_wr_err", - ] - return ",\n".join(lines) diff --git a/src/peakrdl_regblock/cpuif/passthrough/passthrough_tmpl.sv b/src/peakrdl_regblock/cpuif/passthrough/passthrough_tmpl.sv deleted file mode 100644 index 8e5bb70..0000000 --- a/src/peakrdl_regblock/cpuif/passthrough/passthrough_tmpl.sv +++ /dev/null @@ -1,12 +0,0 @@ -assign cpuif_req = s_cpuif_req; -assign cpuif_req_is_wr = s_cpuif_req_is_wr; -assign cpuif_addr = s_cpuif_addr; -assign cpuif_wr_data = s_cpuif_wr_data; -assign cpuif_wr_biten = s_cpuif_wr_biten; -assign s_cpuif_req_stall_wr = cpuif_req_stall_wr; -assign s_cpuif_req_stall_rd = cpuif_req_stall_rd; -assign s_cpuif_rd_ack = cpuif_rd_ack; -assign s_cpuif_rd_err = cpuif_rd_err; -assign s_cpuif_rd_data = cpuif_rd_data; -assign s_cpuif_wr_ack = cpuif_wr_ack; -assign s_cpuif_wr_err = cpuif_wr_err; diff --git a/src/peakrdl_regblock/dereferencer.py b/src/peakrdl_regblock/dereferencer.py deleted file mode 100644 index 42c73ea..0000000 --- a/src/peakrdl_regblock/dereferencer.py +++ /dev/null @@ -1,271 +0,0 @@ -from typing import TYPE_CHECKING, Union, Optional -from systemrdl.node import AddrmapNode, FieldNode, SignalNode, RegNode, AddressableNode -from systemrdl.rdltypes import PropertyReference - -from .sv_int import SVInt - -if TYPE_CHECKING: - from .exporter import BusDecoderExporter, DesignState - from .hwif import Hwif - from .field_logic import FieldLogic - from .addr_decode import AddressDecode - - -class Dereferencer: - """ - This class provides an interface to convert conceptual SystemRDL references - into Verilog identifiers - """ - - def __init__(self, exp: "BusDecoderExporter"): - self.exp = exp - - @property - def hwif(self) -> "Hwif": - return self.exp.hwif - - @property - def address_decode(self) -> "AddressDecode": - return self.exp.address_decode - - @property - def field_logic(self) -> "FieldLogic": - return self.exp.field_logic - - @property - def ds(self) -> "DesignState": - return self.exp.ds - - @property - def top_node(self) -> AddrmapNode: - return self.exp.ds.top_node - - def get_value( - self, - obj: Union[int, FieldNode, SignalNode, PropertyReference], - width: Optional[int] = None, - ) -> Union[SVInt, str]: - """ - Returns the Verilog string that represents the readable value associated - with the object. - - If given a simple scalar value, then the corresponding Verilog literal is returned. - - If obj references a structural systemrdl object, then the corresponding Verilog - expression is returned that represents its value. - - The optional width argument can be provided to hint at the expression's desired bitwidth. - """ - if isinstance(obj, int): - # Is a simple scalar value - return SVInt(obj, width) - - if isinstance(obj, FieldNode): - if obj.implements_storage: - return self.field_logic.get_storage_identifier(obj) - - if self.hwif.has_value_input(obj): - return self.hwif.get_input_identifier(obj, width) - - # Field does not have a storage element, nor does it have a HW input - # must be a constant value as defined by its reset value - reset_value = obj.get_property("reset") - if reset_value is not None: - return self.get_value(reset_value, obj.width) - else: - # No reset value defined! - obj.env.msg.warning( - f"Field '{obj.inst_name}' is a constant but does not have a known value (missing reset). Assigning it a value of X.", - obj.inst.inst_src_ref, - ) - return "'X" - - if isinstance(obj, SignalNode): - # Signals are always inputs from the hwif - return self.hwif.get_input_identifier(obj, width) - - if isinstance(obj, PropertyReference): - if isinstance(obj.node, FieldNode): - return self.get_field_propref_value(obj.node, obj.name, width) - elif isinstance(obj.node, RegNode): - return self.get_reg_propref_value(obj.node, obj.name) - else: - raise RuntimeError - - raise RuntimeError(f"Unhandled reference to: {obj}") - - def get_field_propref_value( - self, - field: FieldNode, - prop_name: str, - width: Optional[int] = None, - ) -> Union[SVInt, str]: - # Value reduction properties. - # Wrap with the appropriate Verilog reduction operator - if prop_name == "anded": - val = self.get_value(field) - return f"&({val})" - elif prop_name == "ored": - val = self.get_value(field) - return f"|({val})" - elif prop_name == "xored": - val = self.get_value(field) - return f"^({val})" - - # references that directly access a property value - if prop_name in { - "decrvalue", - "enable", - "haltenable", - "haltmask", - "hwenable", - "hwmask", - "incrvalue", - "mask", - "reset", - "resetsignal", - }: - return self.get_value(field.get_property(prop_name), width) - - # Field Next - if prop_name == "next": - prop_value = field.get_property(prop_name) - if prop_value is None: - # unset by the user, points to the implied internal signal - return self.field_logic.get_field_combo_identifier(field, "next") - else: - return self.get_value(prop_value, width) - - # References to another component value, or an implied input - if prop_name in {"hwclr", "hwset"}: - prop_value = field.get_property(prop_name) - if prop_value is True: - # Points to inferred hwif input - return self.hwif.get_implied_prop_input_identifier(field, prop_name) - elif prop_value is False: - # This should never happen, as this is checked by the compiler's validator - raise RuntimeError - else: - return self.get_value(prop_value) - - # References to another component value, or an implied input - # May have a complementary partner property - complementary_pairs = { - "we": "wel", - "wel": "we", - "swwe": "swwel", - "swwel": "swwe", - } - if prop_name in complementary_pairs: - prop_value = field.get_property(prop_name) - if prop_value is True: - # Points to inferred hwif input - return self.hwif.get_implied_prop_input_identifier(field, prop_name) - elif prop_value is False: - # Try complementary property - prop_value = field.get_property(complementary_pairs[prop_name]) - if prop_value is True: - # Points to inferred hwif input - return f"!({self.hwif.get_implied_prop_input_identifier(field, complementary_pairs[prop_name])})" - elif prop_value is False: - # This should never happen, as this is checked by the compiler's validator - raise RuntimeError - else: - return f"!({self.get_value(prop_value)})" - else: - return self.get_value(prop_value, width) - - if prop_name == "swacc": - return self.field_logic.get_swacc_identifier(field) - if prop_name == "swmod": - return self.field_logic.get_swmod_identifier(field) - - # translate aliases - aliases = { - "saturate": "incrsaturate", - "threshold": "incrthreshold", - } - prop_name = aliases.get(prop_name, prop_name) - - # Counter properties - if prop_name == "incr": - return self.field_logic.get_counter_incr_strobe(field) - if prop_name == "decr": - return self.field_logic.get_counter_decr_strobe(field) - - if prop_name in { - "decrsaturate", - "decrthreshold", - "incrsaturate", - "incrthreshold", - "overflow", - "underflow", - }: - return self.field_logic.get_field_combo_identifier(field, prop_name) - - raise RuntimeError(f"Unhandled reference to: {field}->{prop_name}") - - def get_reg_propref_value(self, reg: RegNode, prop_name: str) -> str: - if prop_name in {"halt", "intr"}: - return self.hwif.get_implied_prop_output_identifier(reg, prop_name) - raise NotImplementedError - - def get_access_strobe( - self, obj: Union[RegNode, FieldNode], reduce_substrobes: bool = True - ) -> str: - """ - Returns the Verilog string that represents the register's access strobe - """ - return self.address_decode.get_access_strobe(obj, reduce_substrobes) - - def get_external_block_access_strobe(self, obj: "AddressableNode") -> str: - """ - Returns the Verilog string that represents the external block's access strobe - """ - return self.address_decode.get_external_block_access_strobe(obj) - - @property - def default_resetsignal_name(self) -> str: - s = "rst" - if self.ds.default_reset_async: - s = f"a{s}" - if self.ds.default_reset_activelow: - s = f"{s}_n" - return s - - def get_resetsignal(self, obj: Optional[SignalNode] = None) -> str: - """ - Returns a normalized active-high reset signal - """ - if isinstance(obj, SignalNode): - s = self.get_value(obj) - if obj.get_property("activehigh"): - return str(s) - else: - return f"~{s}" - - # No explicit reset signal specified. Fall back to default reset signal - s = self.default_resetsignal_name - if self.ds.default_reset_activelow: - s = f"~{s}" - return s - - def get_always_ff_event(self, resetsignal: Optional[SignalNode] = None) -> str: - if resetsignal is None: - # No explicit reset signal specified. Fall back to default reset signal - if self.ds.default_reset_async: - if self.ds.default_reset_activelow: - return f"@(posedge clk or negedge {self.default_resetsignal_name})" - else: - return f"@(posedge clk or posedge {self.default_resetsignal_name})" - else: - return "@(posedge clk)" - elif resetsignal.get_property("async") and resetsignal.get_property( - "activehigh" - ): - return f"@(posedge clk or posedge {self.get_value(resetsignal)})" - elif resetsignal.get_property("async") and not resetsignal.get_property( - "activehigh" - ): - return f"@(posedge clk or negedge {self.get_value(resetsignal)})" - return "@(posedge clk)" diff --git a/src/peakrdl_regblock/exporter.py b/src/peakrdl_regblock/exporter.py deleted file mode 100644 index 1c9cd5a..0000000 --- a/src/peakrdl_regblock/exporter.py +++ /dev/null @@ -1,306 +0,0 @@ -import os -from typing import TYPE_CHECKING, Union, Any, Type, Optional, Set, List -from collections import OrderedDict - -import jinja2 as jj -from systemrdl.node import AddrmapNode, RootNode - -from .addr_decode import AddressDecode -from .field_logic import FieldLogic -from .dereferencer import Dereferencer -from .readback import Readback -from .identifier_filter import kw_filter as kwf -from .utils import clog2 -from .scan_design import DesignScanner -from .validate_design import DesignValidator -from .cpuif import CpuifBase -from .cpuif.apb4 import APB4_Cpuif -from .hwif import Hwif -from .write_buffering import WriteBuffering -from .read_buffering import ReadBuffering -from .external_acks import ExternalWriteAckGenerator, ExternalReadAckGenerator -from .parity import ParityErrorReduceGenerator -from .sv_int import SVInt - -if TYPE_CHECKING: - from systemrdl.node import SignalNode - from systemrdl.rdltypes import UserEnum - - -class BusDecoderExporter: - hwif: Hwif - cpuif: CpuifBase - address_decode: AddressDecode - field_logic: FieldLogic - readback: Readback - write_buffering: WriteBuffering - read_buffering: ReadBuffering - dereferencer: Dereferencer - ds: "DesignState" - - def __init__(self, **kwargs: Any) -> None: - # Check for stray kwargs - if kwargs: - raise TypeError( - f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'" - ) - - loader = jj.ChoiceLoader( - [ - jj.FileSystemLoader(os.path.dirname(__file__)), - jj.PrefixLoader( - { - "base": jj.FileSystemLoader(os.path.dirname(__file__)), - }, - delimiter=":", - ), - ] - ) - - self.jj_env = jj.Environment( - loader=loader, - undefined=jj.StrictUndefined, - ) - - def export( - self, node: Union[RootNode, AddrmapNode], output_dir: str, **kwargs: Any - ) -> None: - """ - Parameters - ---------- - node: AddrmapNode - Top-level SystemRDL node to export. - output_dir: str - Path to the output directory where generated SystemVerilog will be written. - Output includes two files: a module definition and package definition. - cpuif_cls: :class:`peakrdl_busdecoder.cpuif.CpuifBase` - Specify the class type that implements the CPU interface of your choice. - Defaults to AMBA APB4. - module_name: str - Override the SystemVerilog module name. By default, the module name - is the top-level node's name. - package_name: str - Override the SystemVerilog package name. By default, the package name - is the top-level node's name with a "_pkg" suffix. - reuse_hwif_typedefs: bool - By default, the exporter will attempt to re-use hwif struct definitions for - nodes that are equivalent. This allows for better modularity and type reuse. - Struct type names are derived using the SystemRDL component's type - name and declared lexical scope path. - - If this is not desireable, override this parameter to ``False`` and structs - will be generated more naively using their hierarchical paths. - retime_read_fanin: bool - Set this to ``True`` to enable additional read path retiming. - For large register blocks that operate at demanding clock rates, this - may be necessary in order to manage large readback fan-in. - - The retiming flop stage is automatically placed in the most optimal point in the - readback path so that logic-levels and fanin are minimized. - - Enabling this option will increase read transfer latency by 1 clock cycle. - retime_read_response: bool - Set this to ``True`` to enable an additional retiming flop stage between - the readback mux and the CPU interface response logic. - This option may be beneficial for some CPU interfaces that implement the - response logic fully combinationally. Enabling this stage can better - isolate timing paths in the register file from the rest of your system. - - Enabling this when using CPU interfaces that already implement the - response path sequentially may not result in any meaningful timing improvement. - - Enabling this option will increase read transfer latency by 1 clock cycle. - retime_external_reg: bool - Retime outputs to external ``reg`` components. - retime_external_regfile: bool - Retime outputs to external ``regfile`` components. - retime_external_mem: bool - Retime outputs to external ``mem`` components. - retime_external_addrmap: bool - Retime outputs to external ``addrmap`` components. - generate_hwif_report: bool - If set, generates a hwif report that can help designers understand - the contents of the ``hwif_in`` and ``hwif_out`` structures. - address_width: int - Override the CPU interface's address width. By default, address width - is sized to the contents of the busdecoder. - default_reset_activelow: bool - If overriden to True, default reset is active-low instead of active-high. - default_reset_async: bool - If overriden to True, default reset is asynchronous instead of synchronous. - """ - # If it is the root node, skip to top addrmap - if isinstance(node, RootNode): - top_node = node.top - else: - top_node = node - - self.ds = DesignState(top_node, kwargs) - - cpuif_cls = kwargs.pop("cpuif_cls", None) or APB4_Cpuif # type: Type[CpuifBase] - generate_hwif_report = kwargs.pop("generate_hwif_report", False) # type: bool - - # Check for stray kwargs - if kwargs: - raise TypeError( - f"got an unexpected keyword argument '{list(kwargs.keys())[0]}'" - ) - - if generate_hwif_report: - path = os.path.join(output_dir, f"{self.ds.module_name}_hwif.rpt") - hwif_report_file = open(path, "w", encoding="utf-8") # pylint: disable=consider-using-with - else: - hwif_report_file = None - - # Construct exporter components - self.cpuif = cpuif_cls(self) - self.hwif = Hwif(self, hwif_report_file=hwif_report_file) - self.readback = Readback(self) - self.address_decode = AddressDecode(self) - self.field_logic = FieldLogic(self) - self.write_buffering = WriteBuffering(self) - self.read_buffering = ReadBuffering(self) - self.dereferencer = Dereferencer(self) - ext_write_acks = ExternalWriteAckGenerator(self) - ext_read_acks = ExternalReadAckGenerator(self) - parity = ParityErrorReduceGenerator(self) - - # Validate that there are no unsupported constructs - DesignValidator(self).do_validate() - - # Compute readback implementation early. - # Readback has the capability to disable retiming if the fanin is tiny. - # This affects the rest of the design's implementation, and must be known - # before any other templates are rendered - readback_implementation = self.readback.get_implementation() - - # Build Jinja template context - context = { - "cpuif": self.cpuif, - "hwif": self.hwif, - "write_buffering": self.write_buffering, - "read_buffering": self.read_buffering, - "get_resetsignal": self.dereferencer.get_resetsignal, - "default_resetsignal_name": self.dereferencer.default_resetsignal_name, - "address_decode": self.address_decode, - "field_logic": self.field_logic, - "readback_implementation": readback_implementation, - "ext_write_acks": ext_write_acks, - "ext_read_acks": ext_read_acks, - "parity": parity, - "get_always_ff_event": self.dereferencer.get_always_ff_event, - "ds": self.ds, - "kwf": kwf, - "SVInt": SVInt, - } - - # Write out design - os.makedirs(output_dir, exist_ok=True) - package_file_path = os.path.join(output_dir, self.ds.package_name + ".sv") - template = self.jj_env.get_template("package_tmpl.sv") - stream = template.stream(context) - stream.dump(package_file_path) - - module_file_path = os.path.join(output_dir, self.ds.module_name + ".sv") - template = self.jj_env.get_template("module_tmpl.sv") - stream = template.stream(context) - stream.dump(module_file_path) - - if hwif_report_file: - hwif_report_file.close() - - -class DesignState: - """ - Dumping ground for all sorts of variables that are relevant to a particular - design. - """ - - def __init__(self, top_node: AddrmapNode, kwargs: Any) -> None: - self.top_node = top_node - msg = top_node.env.msg - - # ------------------------ - # Extract compiler args - # ------------------------ - self.reuse_hwif_typedefs = kwargs.pop("reuse_hwif_typedefs", True) # type: bool - self.module_name = kwargs.pop("module_name", None) or kwf( - self.top_node.inst_name - ) # type: str - self.package_name = kwargs.pop("package_name", None) or ( - self.module_name + "_pkg" - ) # type: str - user_addr_width = kwargs.pop("address_width", None) # type: Optional[int] - - # Pipelining options - self.retime_read_fanin = kwargs.pop("retime_read_fanin", False) # type: bool - self.retime_read_response = kwargs.pop("retime_read_response", False) # type: bool - self.retime_external_reg = kwargs.pop("retime_external_reg", False) # type: bool - self.retime_external_regfile = kwargs.pop("retime_external_regfile", False) # type: bool - self.retime_external_mem = kwargs.pop("retime_external_mem", False) # type: bool - self.retime_external_addrmap = kwargs.pop("retime_external_addrmap", False) # type: bool - - # Default reset type - self.default_reset_activelow = kwargs.pop("default_reset_activelow", False) # type: bool - self.default_reset_async = kwargs.pop("default_reset_async", False) # type: bool - - # ------------------------ - # Info about the design - # ------------------------ - self.cpuif_data_width = 0 - - # Collections of signals that were actually referenced by the design - self.in_hier_signal_paths = set() # type: Set[str] - self.out_of_hier_signals = OrderedDict() # type: OrderedDict[str, SignalNode] - - self.has_writable_msb0_fields = False - self.has_buffered_write_regs = False - self.has_buffered_read_regs = False - - self.has_external_block = False - self.has_external_addressable = False - - self.has_paritycheck = False - - # Track any referenced enums - self.user_enums = [] # type: List[Type[UserEnum]] - - # Scan the design to fill in above variables - DesignScanner(self).do_scan() - - if self.cpuif_data_width == 0: - # Scanner did not find any registers in the design being exported, - # so the width is not known. - # Assume 32-bits - msg.warning( - "Addrmap being exported only contains external components. Unable to infer the CPUIF bus width. Assuming 32-bits.", - self.top_node.inst.def_src_ref, - ) - self.cpuif_data_width = 32 - - # ------------------------ - # Min address width encloses the total size AND at least 1 useful address bit - self.addr_width = max( - clog2(self.top_node.size), clog2(self.cpuif_data_width // 8) + 1 - ) - - if user_addr_width is not None: - if user_addr_width < self.addr_width: - msg.fatal( - f"User-specified address width shall be greater than or equal to {self.addr_width}." - ) - self.addr_width = user_addr_width - - @property - def min_read_latency(self) -> int: - n = 0 - if self.retime_read_fanin: - n += 1 - if self.retime_read_response: - n += 1 - return n - - @property - def min_write_latency(self) -> int: - n = 0 - return n diff --git a/src/peakrdl_regblock/external_acks.py b/src/peakrdl_regblock/external_acks.py deleted file mode 100644 index d23f9ae..0000000 --- a/src/peakrdl_regblock/external_acks.py +++ /dev/null @@ -1,58 +0,0 @@ -from typing import TYPE_CHECKING - -from systemrdl.walker import WalkerAction -from systemrdl.node import RegNode - -from .forloop_generator import RDLForLoopGenerator - -if TYPE_CHECKING: - from .exporter import BusDecoderExporter - from systemrdl.node import AddressableNode - - -class ExternalWriteAckGenerator(RDLForLoopGenerator): - def __init__(self, exp: "BusDecoderExporter") -> None: - super().__init__() - self.exp = exp - - def get_implementation(self) -> str: - content = self.get_content(self.exp.ds.top_node) - if content is None: - return "" - return content - - def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction: - super().enter_AddressableComponent(node) - - if node.external: - if not isinstance(node, RegNode) or node.has_sw_writable: - self.add_content( - f"wr_ack |= {self.exp.hwif.get_external_wr_ack(node)};" - ) - return WalkerAction.SkipDescendants - - return WalkerAction.Continue - - -class ExternalReadAckGenerator(RDLForLoopGenerator): - def __init__(self, exp: "BusDecoderExporter") -> None: - super().__init__() - self.exp = exp - - def get_implementation(self) -> str: - content = self.get_content(self.exp.ds.top_node) - if content is None: - return "" - return content - - def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction: - super().enter_AddressableComponent(node) - - if node.external: - if not isinstance(node, RegNode) or node.has_sw_readable: - self.add_content( - f"rd_ack |= {self.exp.hwif.get_external_rd_ack(node)};" - ) - return WalkerAction.SkipDescendants - - return WalkerAction.Continue diff --git a/src/peakrdl_regblock/field_logic/__init__.py b/src/peakrdl_regblock/field_logic/__init__.py deleted file mode 100644 index bd5e28b..0000000 --- a/src/peakrdl_regblock/field_logic/__init__.py +++ /dev/null @@ -1,576 +0,0 @@ -from typing import TYPE_CHECKING, Union - -from systemrdl.rdltypes import PrecedenceType, InterruptType - -from .bases import AssignmentPrecedence, NextStateConditional -from . import sw_onread -from . import sw_onwrite -from . import sw_singlepulse -from . import hw_write -from . import hw_set_clr -from . import hw_interrupts -from . import hw_interrupts_with_write - -from ..utils import get_indexed_path -from ..sv_int import SVInt - -from .generators import ( - CombinationalStructGenerator, - FieldStorageStructGenerator, - FieldLogicGenerator, -) - -if TYPE_CHECKING: - from typing import Dict, List - from systemrdl.node import AddrmapNode, FieldNode - from ..exporter import BusDecoderExporter, DesignState - - -class FieldLogic: - def __init__(self, exp: "BusDecoderExporter"): - self.exp = exp - - self._hw_conditionals = {} # type: Dict[int, List[NextStateConditional]] - self._sw_conditionals = {} # type: Dict[int, List[NextStateConditional]] - - self.init_conditionals() - - @property - def ds(self) -> "DesignState": - return self.exp.ds - - @property - def top_node(self) -> "AddrmapNode": - return self.exp.ds.top_node - - def get_storage_struct(self) -> str: - struct_gen = FieldStorageStructGenerator(self) - s = struct_gen.get_struct(self.top_node, "field_storage_t") - - # Only declare the storage struct if it exists - if s is None: - return "" - - return s + "\nfield_storage_t field_storage;" - - def get_combo_struct(self) -> str: - struct_gen = CombinationalStructGenerator(self) - s = struct_gen.get_struct(self.top_node, "field_combo_t") - - # Only declare the storage struct if it exists - if s is None: - return "" - - return s + "\nfield_combo_t field_combo;" - - def get_implementation(self) -> str: - gen = FieldLogicGenerator(self) - s = gen.get_content(self.top_node) - if s is None: - return "" - return s - - # --------------------------------------------------------------------------- - # Field utility functions - # --------------------------------------------------------------------------- - def get_storage_identifier(self, field: "FieldNode") -> str: - """ - Returns the Verilog string that represents the storage register element - for the referenced field - """ - assert field.implements_storage - path = get_indexed_path(self.top_node, field) - return f"field_storage.{path}.value" - - def get_next_q_identifier(self, field: "FieldNode") -> str: - """ - Returns the Verilog string that represents the storage register element - for the delayed 'next' input value - """ - assert field.implements_storage - path = get_indexed_path(self.top_node, field) - return f"field_storage.{path}.next_q" - - def get_field_combo_identifier(self, field: "FieldNode", name: str) -> str: - """ - Returns a Verilog string that represents a field's internal combinational - signal. - """ - assert field.implements_storage - path = get_indexed_path(self.top_node, field) - return f"field_combo.{path}.{name}" - - def get_counter_incr_strobe(self, field: "FieldNode") -> str: - """ - Return the Verilog string that represents the field's incr strobe signal. - """ - prop_value = field.get_property("incr") - if prop_value: - return str(self.exp.dereferencer.get_value(prop_value)) - - # unset by the user, points to the implied input signal - return self.exp.hwif.get_implied_prop_input_identifier(field, "incr") - - def get_counter_incrvalue(self, field: "FieldNode") -> Union[SVInt, str]: - """ - Return the string that represents the field's increment value - """ - incrvalue = field.get_property("incrvalue") - if incrvalue is not None: - return self.exp.dereferencer.get_value(incrvalue, field.width) - if field.get_property("incrwidth"): - return self.exp.hwif.get_implied_prop_input_identifier(field, "incrvalue") - return "1'b1" - - def get_counter_incrsaturate_value(self, field: "FieldNode") -> Union[SVInt, str]: - prop_value = field.get_property("incrsaturate") - if prop_value is True: - return self.exp.dereferencer.get_value(2**field.width - 1, field.width) - return self.exp.dereferencer.get_value(prop_value, field.width) - - def counter_incrsaturates(self, field: "FieldNode") -> bool: - """ - Returns True if the counter saturates - """ - return field.get_property("incrsaturate") is not False - - def get_counter_incrthreshold_value(self, field: "FieldNode") -> Union[SVInt, str]: - prop_value = field.get_property("incrthreshold") - if isinstance(prop_value, bool): - # No explicit value set. use max - return self.exp.dereferencer.get_value(2**field.width - 1, field.width) - return self.exp.dereferencer.get_value(prop_value, field.width) - - def get_counter_decr_strobe(self, field: "FieldNode") -> str: - """ - Return the Verilog string that represents the field's incr strobe signal. - """ - prop_value = field.get_property("decr") - if prop_value: - return str(self.exp.dereferencer.get_value(prop_value)) - - # unset by the user, points to the implied input signal - return self.exp.hwif.get_implied_prop_input_identifier(field, "decr") - - def get_counter_decrvalue(self, field: "FieldNode") -> Union[SVInt, str]: - """ - Return the string that represents the field's decrement value - """ - decrvalue = field.get_property("decrvalue") - if decrvalue is not None: - return self.exp.dereferencer.get_value(decrvalue, field.width) - if field.get_property("decrwidth"): - return self.exp.hwif.get_implied_prop_input_identifier(field, "decrvalue") - return "1'b1" - - def get_counter_decrsaturate_value(self, field: "FieldNode") -> Union[SVInt, str]: - prop_value = field.get_property("decrsaturate") - if prop_value is True: - return f"{field.width}'d0" - return self.exp.dereferencer.get_value(prop_value, field.width) - - def counter_decrsaturates(self, field: "FieldNode") -> bool: - """ - Returns True if the counter saturates - """ - return field.get_property("decrsaturate") is not False - - def get_counter_decrthreshold_value(self, field: "FieldNode") -> Union[SVInt, str]: - prop_value = field.get_property("decrthreshold") - if isinstance(prop_value, bool): - # No explicit value set. use min - return f"{field.width}'d0" - return self.exp.dereferencer.get_value(prop_value, field.width) - - def get_swacc_identifier(self, field: "FieldNode") -> str: - """ - Asserted when field is software accessed (read or write) - """ - buffer_reads = field.parent.get_property("buffer_reads") - buffer_writes = field.parent.get_property("buffer_writes") - if buffer_reads and buffer_writes: - rstrb = self.exp.read_buffering.get_trigger(field.parent) - wstrb = self.exp.write_buffering.get_write_strobe(field) - return f"{rstrb} || {wstrb}" - elif buffer_reads and not buffer_writes: - strb = self.exp.dereferencer.get_access_strobe(field) - rstrb = self.exp.read_buffering.get_trigger(field.parent) - return f"{rstrb} || ({strb} && decoded_req_is_wr)" - elif not buffer_reads and buffer_writes: - strb = self.exp.dereferencer.get_access_strobe(field) - wstrb = self.exp.write_buffering.get_write_strobe(field) - return f"{wstrb} || ({strb} && !decoded_req_is_wr)" - else: - strb = self.exp.dereferencer.get_access_strobe(field) - return strb - - def get_rd_swacc_identifier(self, field: "FieldNode") -> str: - """ - Asserted when field is software accessed (read) - """ - buffer_reads = field.parent.get_property("buffer_reads") - if buffer_reads: - rstrb = self.exp.read_buffering.get_trigger(field.parent) - return rstrb - else: - strb = self.exp.dereferencer.get_access_strobe(field) - return f"{strb} && !decoded_req_is_wr" - - def get_wr_swacc_identifier(self, field: "FieldNode") -> str: - """ - Asserted when field is software accessed (write) - """ - buffer_writes = field.parent.get_property("buffer_writes") - if buffer_writes: - wstrb = self.exp.write_buffering.get_write_strobe(field) - return wstrb - else: - strb = self.exp.dereferencer.get_access_strobe(field) - return f"{strb} && decoded_req_is_wr" - - def get_swmod_identifier(self, field: "FieldNode") -> str: - """ - Asserted when field is modified by software (written or read with a - set or clear side effect). - """ - w_modifiable = field.is_sw_writable - r_modifiable = field.get_property("onread") is not None - buffer_writes = field.parent.get_property("buffer_writes") - buffer_reads = field.parent.get_property("buffer_reads") - accesswidth = field.parent.get_property("accesswidth") - - astrb = self.exp.dereferencer.get_access_strobe(field) - - conditions = [] - if r_modifiable: - if buffer_reads: - rstrb = self.exp.read_buffering.get_trigger(field.parent) - else: - rstrb = f"{astrb} && !decoded_req_is_wr" - conditions.append(rstrb) - - if w_modifiable: - if buffer_writes: - wstrb = self.exp.write_buffering.get_write_strobe(field) - else: - wstrb = f"{astrb} && decoded_req_is_wr" - - # Due to 10.6.1-f, it is impossible for a field that is sw-writable to - # be split across subwords. - # Therefore it is ok to get the subword idx from only one of the bit offsets - # in order to compute the biten range - sidx = field.low // accesswidth - biten = self.get_wr_biten(field, sidx) - wstrb += f" && |({biten})" - - conditions.append(wstrb) - - if not conditions: - # Not sw modifiable - return "1'b0" - else: - return " || ".join(conditions) - - def get_parity_identifier(self, field: "FieldNode") -> str: - """ - Returns the identifier for the stored 'golden' parity value of the field - """ - path = get_indexed_path(self.top_node, field) - return f"field_storage.{path}.parity" - - def get_parity_error_identifier(self, field: "FieldNode") -> str: - """ - Returns the identifier for whether the field currently has a parity error - """ - path = get_indexed_path(self.top_node, field) - return f"field_combo.{path}.parity_error" - - def has_next_q(self, field: "FieldNode") -> bool: - """ - Some fields require a delayed version of their 'next' input signal in - order to do edge-detection. - - Returns True if this is the case. - """ - if field.get_property("intr type") in { - InterruptType.posedge, - InterruptType.negedge, - InterruptType.bothedge, - }: - return True - - return False - - def get_wbus_bitslice(self, field: "FieldNode", subword_idx: int = 0) -> str: - """ - Get the bitslice range string of the internal cpuif's data/biten bus - that corresponds to this field - """ - if field.parent.get_property("buffer_writes"): - # register is buffered. - # write buffer is the full width of the register. no need to deal with subwords - high = field.high - low = field.low - if field.msb < field.lsb: - # slice is for an msb0 field. - # mirror it - regwidth = field.parent.get_property("regwidth") - low = regwidth - 1 - low - high = regwidth - 1 - high - low, high = high, low - else: - # Regular non-buffered register - # For normal fields this ends up passing-through the field's low/high - # values unchanged. - # For fields within a wide register (accesswidth < regwidth), low/high - # may be shifted down and clamped depending on which sub-word is being accessed - accesswidth = field.parent.get_property("accesswidth") - - # Shift based on subword - high = field.high - (subword_idx * accesswidth) - low = field.low - (subword_idx * accesswidth) - - # clamp to accesswidth - high = max(min(high, accesswidth), 0) - low = max(min(low, accesswidth), 0) - - if field.msb < field.lsb: - # slice is for an msb0 field. - # mirror it - bus_width = self.exp.cpuif.data_width - low = bus_width - 1 - low - high = bus_width - 1 - high - low, high = high, low - - return f"[{high}:{low}]" - - def get_wr_biten(self, field: "FieldNode", subword_idx: int = 0) -> str: - """ - Get the bit-enable slice that corresponds to this field - """ - if field.parent.get_property("buffer_writes"): - # Is buffered. Use value from write buffer - # No need to check msb0 ordering. Bus is pre-swapped, and bitslice - # accounts for it - bslice = self.get_wbus_bitslice(field) - wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field) - return wbuf_prefix + ".biten" + bslice - else: - # Regular non-buffered register - bslice = self.get_wbus_bitslice(field, subword_idx) - - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - value = "decoded_wr_biten_bswap" + bslice - else: - value = "decoded_wr_biten" + bslice - return value - - def get_wr_data(self, field: "FieldNode", subword_idx: int = 0) -> str: - """ - Get the write data slice that corresponds to this field - """ - if field.parent.get_property("buffer_writes"): - # Is buffered. Use value from write buffer - # No need to check msb0 ordering. Bus is pre-swapped, and bitslice - # accounts for it - bslice = self.get_wbus_bitslice(field) - wbuf_prefix = self.exp.write_buffering.get_wbuf_prefix(field) - return wbuf_prefix + ".data" + bslice - else: - # Regular non-buffered register - bslice = self.get_wbus_bitslice(field, subword_idx) - - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - value = "decoded_wr_data_bswap" + bslice - else: - value = "decoded_wr_data" + bslice - return value - - # --------------------------------------------------------------------------- - # Field Logic Conditionals - # --------------------------------------------------------------------------- - def add_hw_conditional( - self, conditional: NextStateConditional, precedence: AssignmentPrecedence - ) -> None: - """ - Register a NextStateConditional action for hardware-triggered field updates. - Categorizing conditionals correctly by hw/sw ensures that the RDL precedence - property can be reliably honored. - - The ``precedence`` argument determines the conditional assignment's priority over - other assignments of differing precedence. - - If multiple conditionals of the same precedence are registered, they are - searched sequentially and only the first to match the given field is used. - """ - if precedence not in self._hw_conditionals: - self._hw_conditionals[precedence] = [] - self._hw_conditionals[precedence].append(conditional) - - def add_sw_conditional( - self, conditional: NextStateConditional, precedence: AssignmentPrecedence - ) -> None: - """ - Register a NextStateConditional action for software-triggered field updates. - Categorizing conditionals correctly by hw/sw ensures that the RDL precedence - property can be reliably honored. - - The ``precedence`` argument determines the conditional assignment's priority over - other assignments of differing precedence. - - If multiple conditionals of the same precedence are registered, they are - searched sequentially and only the first to match the given field is used. - """ - if precedence not in self._sw_conditionals: - self._sw_conditionals[precedence] = [] - self._sw_conditionals[precedence].append(conditional) - - def init_conditionals(self) -> None: - """ - Initialize all possible conditionals here. - - Remember: The order in which conditionals are added matters within the - same assignment precedence. - """ - - self.add_sw_conditional( - sw_onread.ClearOnRead(self.exp), AssignmentPrecedence.SW_ONREAD - ) - self.add_sw_conditional( - sw_onread.SetOnRead(self.exp), AssignmentPrecedence.SW_ONREAD - ) - - self.add_sw_conditional( - sw_onwrite.Write(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - self.add_sw_conditional( - sw_onwrite.WriteSet(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - self.add_sw_conditional( - sw_onwrite.WriteClear(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - self.add_sw_conditional( - sw_onwrite.WriteZeroToggle(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - self.add_sw_conditional( - sw_onwrite.WriteZeroClear(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - self.add_sw_conditional( - sw_onwrite.WriteZeroSet(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - self.add_sw_conditional( - sw_onwrite.WriteOneToggle(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - self.add_sw_conditional( - sw_onwrite.WriteOneClear(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - self.add_sw_conditional( - sw_onwrite.WriteOneSet(self.exp), AssignmentPrecedence.SW_ONWRITE - ) - - self.add_sw_conditional( - sw_singlepulse.Singlepulse(self.exp), AssignmentPrecedence.SW_SINGLEPULSE - ) - - self.add_hw_conditional( - hw_interrupts_with_write.PosedgeStickybitWE(self.exp), - AssignmentPrecedence.HW_WRITE, - ) - self.add_hw_conditional( - hw_interrupts_with_write.PosedgeStickybitWEL(self.exp), - AssignmentPrecedence.HW_WRITE, - ) - self.add_hw_conditional( - hw_interrupts_with_write.NegedgeStickybitWE(self.exp), - AssignmentPrecedence.HW_WRITE, - ) - self.add_hw_conditional( - hw_interrupts_with_write.NegedgeStickybitWEL(self.exp), - AssignmentPrecedence.HW_WRITE, - ) - self.add_hw_conditional( - hw_interrupts_with_write.BothedgeStickybitWE(self.exp), - AssignmentPrecedence.HW_WRITE, - ) - self.add_hw_conditional( - hw_interrupts_with_write.BothedgeStickybitWEL(self.exp), - AssignmentPrecedence.HW_WRITE, - ) - self.add_hw_conditional( - hw_interrupts_with_write.StickyWE(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_interrupts_with_write.StickyWEL(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_interrupts_with_write.StickybitWE(self.exp), - AssignmentPrecedence.HW_WRITE, - ) - self.add_hw_conditional( - hw_interrupts_with_write.StickybitWEL(self.exp), - AssignmentPrecedence.HW_WRITE, - ) - self.add_hw_conditional( - hw_interrupts.PosedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_interrupts.NegedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_interrupts.BothedgeStickybit(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_interrupts.Sticky(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_interrupts.Stickybit(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_write.WEWrite(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_write.WELWrite(self.exp), AssignmentPrecedence.HW_WRITE - ) - self.add_hw_conditional( - hw_write.AlwaysWrite(self.exp), AssignmentPrecedence.HW_WRITE - ) - - self.add_hw_conditional( - hw_set_clr.HWClear(self.exp), AssignmentPrecedence.HWCLR - ) - - self.add_hw_conditional(hw_set_clr.HWSet(self.exp), AssignmentPrecedence.HWSET) - - def _get_X_conditionals( - self, conditionals: "Dict[int, List[NextStateConditional]]", field: "FieldNode" - ) -> "List[NextStateConditional]": - result = [] - precedences = sorted(conditionals.keys(), reverse=True) - for precedence in precedences: - for conditional in conditionals[precedence]: - if conditional.is_match(field): - result.append(conditional) - break - return result - - def get_conditionals(self, field: "FieldNode") -> "List[NextStateConditional]": - """ - Get a list of NextStateConditional objects that apply to the given field. - - The returned list is sorted in priority order - the conditional with highest - precedence is first in the list. - """ - sw_precedence = field.get_property("precedence") == PrecedenceType.sw - result = [] - - if sw_precedence: - result.extend(self._get_X_conditionals(self._sw_conditionals, field)) - - result.extend(self._get_X_conditionals(self._hw_conditionals, field)) - - if not sw_precedence: - result.extend(self._get_X_conditionals(self._sw_conditionals, field)) - - return result diff --git a/src/peakrdl_regblock/field_logic/bases.py b/src/peakrdl_regblock/field_logic/bases.py deleted file mode 100644 index d126df8..0000000 --- a/src/peakrdl_regblock/field_logic/bases.py +++ /dev/null @@ -1,114 +0,0 @@ -from typing import TYPE_CHECKING, List -import enum - -from ..utils import get_indexed_path - -if TYPE_CHECKING: - from systemrdl.node import FieldNode - - from ..exporter import BusDecoderExporter - - -class AssignmentPrecedence(enum.IntEnum): - """ - Enumeration of standard assignment precedence groups. - Each value represents the precedence of a single conditional assignment - category that determines a field's next state. - - Higher value denotes higher precedence - - Important: If inserting custom intermediate assignment rules, do not rely on the absolute - value of the enumeration. Insert your rules relative to an existing precedence: - FieldBuilder.add_hw_conditional(MyConditional, HW_WE + 1) - """ - - # Software access assignment groups - SW_ONREAD = 5000 - SW_ONWRITE = 4000 - SW_SINGLEPULSE = 3000 - - # Hardware access assignment groups - HW_WRITE = 3000 - HWSET = 2000 - HWCLR = 1000 - COUNTER_INCR_DECR = 0 - - -class SVLogic: - """ - Represents a SystemVerilog logic signal - """ - - def __init__(self, name: str, width: int, default_assignment: str) -> None: - self.name = name - self.width = width - self.default_assignment = default_assignment - - def __eq__(self, o: object) -> bool: - if not isinstance(o, SVLogic): - return False - - return ( - o.name == self.name - and o.width == self.width - and o.default_assignment == self.default_assignment - ) - - -class NextStateConditional: - """ - Describes a single conditional action that determines the next state of a field - Provides information to generate the following content: - if() begin - - end - """ - - # Optional comment to emit next to the conditional - comment = "" - - def __init__(self, exp: "BusDecoderExporter"): - self.exp = exp - - def is_match(self, field: "FieldNode") -> bool: - """ - Returns True if this conditional is relevant to the field. If so, - it instructs the FieldBuilder that Verilog for this conditional shall - be emitted - """ - raise NotImplementedError - - def get_field_path(self, field: "FieldNode") -> str: - return get_indexed_path(self.exp.ds.top_node, field) - - def get_predicate(self, field: "FieldNode") -> str: - """ - Returns the rendered conditional text - """ - raise NotImplementedError - - def get_assignments(self, field: "FieldNode") -> List[str]: - """ - Returns a list of rendered assignment strings - This will basically always be two: - .next = - .load_next = '1; - """ - raise NotImplementedError - - def get_extra_combo_signals(self, field: "FieldNode") -> List[SVLogic]: - """ - Return any additional combinational signals that this conditional - will assign if present. - """ - return [] - - -class NextStateUnconditional(NextStateConditional): - """ - Use this class if predicate can never evaluate to false. - This will be generated as an 'else' clause, or a direct assignment - """ - - # Explanation text for use in error message about conflicts - unconditional_explanation = "" diff --git a/src/peakrdl_regblock/field_logic/generators.py b/src/peakrdl_regblock/field_logic/generators.py deleted file mode 100644 index 04e474f..0000000 --- a/src/peakrdl_regblock/field_logic/generators.py +++ /dev/null @@ -1,393 +0,0 @@ -from typing import TYPE_CHECKING, List, Optional - -from collections import OrderedDict - -from systemrdl.walker import WalkerAction -from systemrdl.node import RegNode, RegfileNode, MemNode, AddrmapNode - -from ..struct_generator import RDLStructGenerator -from ..forloop_generator import RDLForLoopGenerator -from ..utils import get_indexed_path, clog2 -from ..identifier_filter import kw_filter as kwf -from .bases import NextStateUnconditional - -if TYPE_CHECKING: - from . import FieldLogic - from systemrdl.node import FieldNode, AddressableNode - from .bases import SVLogic - -class CombinationalStructGenerator(RDLStructGenerator): - - def __init__(self, field_logic: 'FieldLogic'): - super().__init__() - self.field_logic = field_logic - - def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: - super().enter_AddressableComponent(node) - - if node.external: - return WalkerAction.SkipDescendants - return WalkerAction.Continue - - def enter_Field(self, node: 'FieldNode') -> None: - # If a field doesn't implement storage, it is not relevant here - if not node.implements_storage: - return - - # collect any extra combo signals that this field requires - extra_combo_signals = OrderedDict() # type: OrderedDict[str, SVLogic] - for conditional in self.field_logic.get_conditionals(node): - for signal in conditional.get_extra_combo_signals(node): - if signal.name in extra_combo_signals: - # Assert that subsequent declarations of the same signal - # are identical - assert signal == extra_combo_signals[signal.name] - else: - extra_combo_signals[signal.name] = signal - - self.push_struct(kwf(node.inst_name)) - self.add_member("next", node.width) - self.add_member("load_next") - for signal in extra_combo_signals.values(): - self.add_member(signal.name, signal.width) - if node.is_up_counter: - self.add_up_counter_members(node) - if node.is_down_counter: - self.add_down_counter_members(node) - if node.get_property('paritycheck'): - self.add_member("parity_error") - self.pop_struct() - - def add_up_counter_members(self, node: 'FieldNode') -> None: - self.add_member('incrthreshold') - if self.field_logic.counter_incrsaturates(node): - self.add_member('incrsaturate') - else: - self.add_member('overflow') - - def add_down_counter_members(self, node: 'FieldNode') -> None: - self.add_member('decrthreshold') - if self.field_logic.counter_decrsaturates(node): - self.add_member('decrsaturate') - else: - self.add_member('underflow') - - -class FieldStorageStructGenerator(RDLStructGenerator): - - def __init__(self, field_logic: 'FieldLogic') -> None: - super().__init__() - self.field_logic = field_logic - - def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: - super().enter_AddressableComponent(node) - - if node.external: - return WalkerAction.SkipDescendants - return WalkerAction.Continue - - def enter_Field(self, node: 'FieldNode') -> None: - self.push_struct(kwf(node.inst_name)) - - if node.implements_storage: - self.add_member("value", node.width) - if node.get_property('paritycheck'): - self.add_member("parity") - - if self.field_logic.has_next_q(node): - self.add_member("next_q", node.width) - - self.pop_struct() - - -class FieldLogicGenerator(RDLForLoopGenerator): - i_type = "genvar" - def __init__(self, field_logic: 'FieldLogic') -> None: - super().__init__() - self.field_logic = field_logic - self.exp = field_logic.exp - self.ds = self.exp.ds - self.field_storage_template = self.exp.jj_env.get_template( - "field_logic/templates/field_storage.sv" - ) - self.external_reg_template = self.exp.jj_env.get_template( - "field_logic/templates/external_reg.sv" - ) - self.external_block_template = self.exp.jj_env.get_template( - "field_logic/templates/external_block.sv" - ) - self.intr_fields = [] # type: List[FieldNode] - self.halt_fields = [] # type: List[FieldNode] - self.msg = self.ds.top_node.env.msg - - - def enter_AddressableComponent(self, node: 'AddressableNode') -> Optional[WalkerAction]: - super().enter_AddressableComponent(node) - - if node.external and not isinstance(node, RegNode): - # Is an external block - self.assign_external_block_outputs(node) - - # Do not recurse - return WalkerAction.SkipDescendants - - return WalkerAction.Continue - - def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: - self.intr_fields = [] - self.halt_fields = [] - - if node.external: - self.assign_external_reg_outputs(node) - # Do not recurse to fields - return WalkerAction.SkipDescendants - - return WalkerAction.Continue - - - def enter_Field(self, node: 'FieldNode') -> None: - if node.implements_storage: - self.generate_field_storage(node) - - self.assign_field_outputs(node) - - if node.get_property('intr'): - self.intr_fields.append(node) - if node.get_property('haltenable') or node.get_property('haltmask'): - self.halt_fields.append(node) - - - def exit_Reg(self, node: 'RegNode') -> None: - # Assign register's intr output - if self.intr_fields: - strs = [] - for field in self.intr_fields: - enable = field.get_property('enable') - mask = field.get_property('mask') - F = self.exp.dereferencer.get_value(field) - if enable: - E = self.exp.dereferencer.get_value(enable) - s = f"|({F} & {E})" - elif mask: - M = self.exp.dereferencer.get_value(mask) - s = f"|({F} & ~{M})" - else: - s = f"|{F}" - strs.append(s) - - self.add_content( - f"assign {self.exp.hwif.get_implied_prop_output_identifier(node, 'intr')} =" - ) - self.add_content( - " " - + "\n || ".join(strs) - + ";" - ) - - # Assign register's halt output - if self.halt_fields: - strs = [] - for field in self.halt_fields: - enable = field.get_property('haltenable') - mask = field.get_property('haltmask') - F = self.exp.dereferencer.get_value(field) - if enable: - E = self.exp.dereferencer.get_value(enable) - s = f"|({F} & {E})" - elif mask: - M = self.exp.dereferencer.get_value(mask) - s = f"|({F} & ~{M})" - else: - s = f"|{F}" - strs.append(s) - - self.add_content( - f"assign {self.exp.hwif.get_implied_prop_output_identifier(node, 'halt')} =" - ) - self.add_content( - " " - + "\n || ".join(strs) - + ";" - ) - - - def generate_field_storage(self, node: 'FieldNode') -> None: - conditionals = self.field_logic.get_conditionals(node) - extra_combo_signals = OrderedDict() - unconditional: Optional[NextStateUnconditional] = None - new_conditionals = [] - for conditional in conditionals: - for signal in conditional.get_extra_combo_signals(node): - extra_combo_signals[signal.name] = signal - - if isinstance(conditional, NextStateUnconditional): - if unconditional is not None: - # Too inconvenient to validate this early. Easier to validate here in-place generically - self.msg.fatal( - "Field has multiple conflicting properties that unconditionally set its state:\n" - f" * {conditional.unconditional_explanation}\n" - f" * {unconditional.unconditional_explanation}", - node.inst.inst_src_ref - ) - unconditional = conditional - else: - new_conditionals.append(conditional) - conditionals = new_conditionals - - resetsignal = node.get_property('resetsignal') - - reset_value = node.get_property('reset') - if reset_value is not None: - reset_value_str = self.exp.dereferencer.get_value(reset_value, node.width) - else: - # 5.9.1-g: If no reset value given, the field is not reset, even if it has a resetsignal. - reset_value_str = None - resetsignal = None - - context = { - 'node': node, - 'reset': reset_value_str, - 'field_logic': self.field_logic, - 'extra_combo_signals': extra_combo_signals, - 'conditionals': conditionals, - 'unconditional': unconditional, - 'resetsignal': resetsignal, - 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event, - 'get_value': self.exp.dereferencer.get_value, - 'get_resetsignal': self.exp.dereferencer.get_resetsignal, - 'get_input_identifier': self.exp.hwif.get_input_identifier, - 'ds': self.ds, - } - self.add_content(self.field_storage_template.render(context)) - - - def assign_field_outputs(self, node: 'FieldNode') -> None: - # Field value output - if self.exp.hwif.has_value_output(node): - output_identifier = self.exp.hwif.get_output_identifier(node) - value = self.exp.dereferencer.get_value(node) - self.add_content( - f"assign {output_identifier} = {value};" - ) - - # Inferred logical reduction outputs - if node.get_property('anded'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "anded") - value = self.exp.dereferencer.get_field_propref_value(node, "anded") - self.add_content( - f"assign {output_identifier} = {value};" - ) - if node.get_property('ored'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "ored") - value = self.exp.dereferencer.get_field_propref_value(node, "ored") - self.add_content( - f"assign {output_identifier} = {value};" - ) - if node.get_property('xored'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "xored") - value = self.exp.dereferencer.get_field_propref_value(node, "xored") - self.add_content( - f"assign {output_identifier} = {value};" - ) - - # Software access strobes - if node.get_property('swmod'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swmod") - value = self.field_logic.get_swmod_identifier(node) - self.add_content( - f"assign {output_identifier} = {value};" - ) - if node.get_property('swacc'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "swacc") - value = self.field_logic.get_swacc_identifier(node) - self.add_content( - f"assign {output_identifier} = {value};" - ) - if node.get_property('rd_swacc'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "rd_swacc") - value = self.field_logic.get_rd_swacc_identifier(node) - self.add_content( - f"assign {output_identifier} = {value};" - ) - if node.get_property('wr_swacc'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "wr_swacc") - value = self.field_logic.get_wr_swacc_identifier(node) - self.add_content( - f"assign {output_identifier} = {value};" - ) - - # Counter thresholds - if node.get_property('incrthreshold') is not False: # (explicitly not False. Not 0) - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "incrthreshold") - value = self.field_logic.get_field_combo_identifier(node, 'incrthreshold') - self.add_content( - f"assign {output_identifier} = {value};" - ) - if node.get_property('decrthreshold') is not False: # (explicitly not False. Not 0) - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "decrthreshold") - value = self.field_logic.get_field_combo_identifier(node, 'decrthreshold') - self.add_content( - f"assign {output_identifier} = {value};" - ) - - # Counter events - if node.get_property('overflow'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "overflow") - value = self.field_logic.get_field_combo_identifier(node, 'overflow') - self.add_content( - f"assign {output_identifier} = {value};" - ) - if node.get_property('underflow'): - output_identifier = self.exp.hwif.get_implied_prop_output_identifier(node, "underflow") - value = self.field_logic.get_field_combo_identifier(node, 'underflow') - self.add_content( - f"assign {output_identifier} = {value};" - ) - - - def assign_external_reg_outputs(self, node: 'RegNode') -> None: - prefix = "hwif_out." + get_indexed_path(self.exp.ds.top_node, node) - strb = self.exp.dereferencer.get_access_strobe(node) - - width = min(self.exp.cpuif.data_width, node.get_property('regwidth')) - if width != self.exp.cpuif.data_width: - bslice = f"[{width - 1}:0]" - else: - bslice = "" - - context = { - "has_sw_writable": node.has_sw_writable, - "has_sw_readable": node.has_sw_readable, - "prefix": prefix, - "strb": strb, - "bslice": bslice, - "retime": self.ds.retime_external_reg, - 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event, - "get_resetsignal": self.exp.dereferencer.get_resetsignal, - "resetsignal": self.exp.ds.top_node.cpuif_reset, - } - self.add_content(self.external_reg_template.render(context)) - - def assign_external_block_outputs(self, node: 'AddressableNode') -> None: - prefix = "hwif_out." + get_indexed_path(self.exp.ds.top_node, node) - strb = self.exp.dereferencer.get_external_block_access_strobe(node) - addr_width = clog2(node.size) - - retime = False - if isinstance(node, RegfileNode): - retime = self.ds.retime_external_regfile - elif isinstance(node, MemNode): - retime = self.ds.retime_external_mem - elif isinstance(node, AddrmapNode): - retime = self.ds.retime_external_addrmap - - context = { - "prefix": prefix, - "strb": strb, - "addr_width": addr_width, - "retime": retime, - 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event, - "get_resetsignal": self.exp.dereferencer.get_resetsignal, - "resetsignal": self.exp.ds.top_node.cpuif_reset, - } - self.add_content(self.external_block_template.render(context)) diff --git a/src/peakrdl_regblock/field_logic/hw_interrupts.py b/src/peakrdl_regblock/field_logic/hw_interrupts.py deleted file mode 100644 index dbebdd3..0000000 --- a/src/peakrdl_regblock/field_logic/hw_interrupts.py +++ /dev/null @@ -1,162 +0,0 @@ -from typing import TYPE_CHECKING, List - -from systemrdl.rdltypes import InterruptType - -from .bases import NextStateConditional - -if TYPE_CHECKING: - from systemrdl.node import FieldNode - - -class Sticky(NextStateConditional): - """ - Normal multi-bit sticky - """ - comment = "multi-bit sticky" - def is_match(self, field: 'FieldNode') -> bool: - return ( - field.is_hw_writable - and field.get_property('sticky') - ) - - def get_predicate(self, field: 'FieldNode') -> str: - I = self.exp.hwif.get_input_identifier(field) - R = self.exp.field_logic.get_storage_identifier(field) - return f"({R} == '0) && ({I} != '0)" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - I = self.exp.hwif.get_input_identifier(field) - return [ - f"next_c = {I};", - "load_next_c = '1;", - ] - - -class Stickybit(NextStateConditional): - """ - Normal stickybit - """ - comment = "stickybit" - def is_match(self, field: 'FieldNode') -> bool: - return ( - field.is_hw_writable - and field.get_property('stickybit') - and field.get_property('intr type') in {None, InterruptType.level} - ) - - def get_predicate(self, field: 'FieldNode') -> str: - F = self.exp.hwif.get_input_identifier(field) - if field.width == 1: - return str(F) - else: - return f"{F} != '0" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - if field.width == 1: - return [ - "next_c = '1;", - "load_next_c = '1;", - ] - else: - I = self.exp.hwif.get_input_identifier(field) - R = self.exp.field_logic.get_storage_identifier(field) - return [ - f"next_c = {R} | {I};", - "load_next_c = '1;", - ] - -class PosedgeStickybit(NextStateConditional): - """ - Positive edge stickybit - """ - comment = "posedge stickybit" - def is_match(self, field: 'FieldNode') -> bool: - return ( - field.is_hw_writable - and field.get_property('stickybit') - and field.get_property('intr type') == InterruptType.posedge - ) - - def get_predicate(self, field: 'FieldNode') -> str: - I = self.exp.hwif.get_input_identifier(field) - Iq = self.exp.field_logic.get_next_q_identifier(field) - return f"(~{Iq} & {I}) != '0" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - if field.width == 1: - return [ - "next_c = '1;", - "load_next_c = '1;", - ] - else: - I = self.exp.hwif.get_input_identifier(field) - Iq = self.exp.field_logic.get_next_q_identifier(field) - R = self.exp.field_logic.get_storage_identifier(field) - return [ - f"next_c = {R} | (~{Iq} & {I});", - "load_next_c = '1;", - ] - -class NegedgeStickybit(NextStateConditional): - """ - Negative edge stickybit - """ - comment = "negedge stickybit" - def is_match(self, field: 'FieldNode') -> bool: - return ( - field.is_hw_writable - and field.get_property('stickybit') - and field.get_property('intr type') == InterruptType.negedge - ) - - def get_predicate(self, field: 'FieldNode') -> str: - I = self.exp.hwif.get_input_identifier(field) - Iq = self.exp.field_logic.get_next_q_identifier(field) - return f"({Iq} & ~{I}) != '0" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - if field.width == 1: - return [ - "next_c = '1;", - "load_next_c = '1;", - ] - else: - I = self.exp.hwif.get_input_identifier(field) - Iq = self.exp.field_logic.get_next_q_identifier(field) - R = self.exp.field_logic.get_storage_identifier(field) - return [ - f"next_c = {R} | ({Iq} & ~{I});", - "load_next_c = '1;", - ] - -class BothedgeStickybit(NextStateConditional): - """ - edge-sensitive stickybit - """ - comment = "bothedge stickybit" - def is_match(self, field: 'FieldNode') -> bool: - return ( - field.is_hw_writable - and field.get_property('stickybit') - and field.get_property('intr type') == InterruptType.bothedge - ) - - def get_predicate(self, field: 'FieldNode') -> str: - I = self.exp.hwif.get_input_identifier(field) - Iq = self.exp.field_logic.get_next_q_identifier(field) - return f"{Iq} != {I}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - if field.width == 1: - return [ - "next_c = '1;", - "load_next_c = '1;", - ] - else: - I = self.exp.hwif.get_input_identifier(field) - Iq = self.exp.field_logic.get_next_q_identifier(field) - R = self.exp.field_logic.get_storage_identifier(field) - return [ - f"next_c = {R} | ({Iq} ^ {I});", - "load_next_c = '1;", - ] diff --git a/src/peakrdl_regblock/field_logic/hw_interrupts_with_write.py b/src/peakrdl_regblock/field_logic/hw_interrupts_with_write.py deleted file mode 100644 index 7545533..0000000 --- a/src/peakrdl_regblock/field_logic/hw_interrupts_with_write.py +++ /dev/null @@ -1,187 +0,0 @@ -from typing import List, TYPE_CHECKING - -from .hw_interrupts import ( - Sticky, Stickybit, - PosedgeStickybit, NegedgeStickybit, BothedgeStickybit -) -from .hw_write import WEWrite, WELWrite - -if TYPE_CHECKING: - from systemrdl.node import FieldNode - - -class StickyWE(Sticky, WEWrite): - """ - Normal multi-bit sticky with write enable - """ - comment = "multi-bit sticky with WE" - def is_match(self, field: 'FieldNode') -> bool: - return ( - Sticky.is_match(self, field) - and WEWrite.is_match(self, field) - ) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = Sticky.get_predicate(self, field) - WE = WEWrite.get_predicate(self, field) - return f"{BASE} && {WE}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return Sticky.get_assignments(self, field) - -class StickyWEL(Sticky, WELWrite): - """ - Normal multi-bit sticky with write enable low - """ - comment = "multi-bit sticky with WEL" - def is_match(self, field: 'FieldNode') -> bool: - return ( - Sticky.is_match(self, field) - and WELWrite.is_match(self, field) - ) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = Sticky.get_predicate(self, field) - WEL = WELWrite.get_predicate(self, field) - return f"{BASE} && {WEL}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return Sticky.get_assignments(self, field) - -class StickybitWE(Stickybit, WEWrite): - """ - Normal stickybiti with write enable - """ - comment = "stickybit with WE" - def is_match(self, field: 'FieldNode') -> bool: - return ( - Stickybit.is_match(self, field) - and WEWrite.is_match(self, field) - ) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = Stickybit.get_predicate(self, field) - WE = WEWrite.get_predicate(self, field) - return f"{BASE} && {WE}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return Stickybit.get_assignments(self, field) - -class StickybitWEL(Stickybit, WELWrite): - """ - Normal stickybiti with write enable low - """ - comment = "stickybit with WEL" - def is_match(self, field: 'FieldNode') -> bool: - return Stickybit.is_match(self, field) \ - and WELWrite.is_match(self, field) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = Stickybit.get_predicate(self, field) - WEL = WELWrite.get_predicate(self, field) - return f"{BASE} && {WEL}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return Stickybit.get_assignments(self, field) - -class PosedgeStickybitWE(PosedgeStickybit, WEWrite): - """ - Positive edge stickybit with write enable - """ - comment = "posedge stickybit with WE" - def is_match(self, field: 'FieldNode') -> bool: - return PosedgeStickybit.is_match(self, field) \ - and WEWrite.is_match(self, field) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = PosedgeStickybit.get_predicate(self, field) - WE = WEWrite.get_predicate(self, field) - return f"{BASE} && {WE}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return PosedgeStickybit.get_assignments(self, field) - -class PosedgeStickybitWEL(PosedgeStickybit, WELWrite): - """ - Positive edge stickybit with write enable low - """ - comment = "posedge stickybit with WEL" - def is_match(self, field: 'FieldNode') -> bool: - return PosedgeStickybit.is_match(self, field) \ - and WELWrite.is_match(self, field) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = PosedgeStickybit.get_predicate(self, field) - WEL = WELWrite.get_predicate(self, field) - return f"{BASE} && {WEL}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return PosedgeStickybit.get_assignments(self, field) - -class NegedgeStickybitWE(NegedgeStickybit, WEWrite): - """ - Negative edge stickybit with write enable - """ - comment = "negedge stickybit with WE" - def is_match(self, field: 'FieldNode') -> bool: - return NegedgeStickybit.is_match(self, field) \ - and WEWrite.is_match(self, field) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = NegedgeStickybit.get_predicate(self, field) - WE = WEWrite.get_predicate(self, field) - return f"{BASE} && {WE}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return NegedgeStickybit.get_assignments(self, field) - -class NegedgeStickybitWEL(NegedgeStickybit, WELWrite): - """ - Negative edge stickybit with write enable low - """ - comment = "negedge stickybit with WEL" - def is_match(self, field: 'FieldNode') -> bool: - return NegedgeStickybit.is_match(self, field) \ - and WELWrite.is_match(self, field) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = NegedgeStickybit.get_predicate(self, field) - WEL = WELWrite.get_predicate(self, field) - return f"{BASE} && {WEL}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return NegedgeStickybit.get_assignments(self, field) - -class BothedgeStickybitWE(BothedgeStickybit, WEWrite): - """ - edge-sensitive stickybit with write enable - """ - comment = "bothedge stickybit with WE" - def is_match(self, field: 'FieldNode') -> bool: - return BothedgeStickybit.is_match(self, field) \ - and WEWrite.is_match(self, field) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = BothedgeStickybit.get_predicate(self, field) - WE = WEWrite.get_predicate(self, field) - return f"{BASE} && {WE}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return BothedgeStickybit.get_assignments(self, field) - -class BothedgeStickybitWEL(BothedgeStickybit, WELWrite): - """ - edge-sensitive stickybit with write enable low - """ - comment = "bothedge stickybit with WEL" - def is_match(self, field: 'FieldNode') -> bool: - return BothedgeStickybit.is_match(self, field) \ - and WELWrite.is_match(self, field) - - def get_predicate(self, field: 'FieldNode') -> str: - BASE = BothedgeStickybit.get_predicate(self, field) - WEL = WELWrite.get_predicate(self, field) - return f"{BASE} && {WEL}" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return BothedgeStickybit.get_assignments(self, field) diff --git a/src/peakrdl_regblock/field_logic/hw_set_clr.py b/src/peakrdl_regblock/field_logic/hw_set_clr.py deleted file mode 100644 index 982f1b3..0000000 --- a/src/peakrdl_regblock/field_logic/hw_set_clr.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import TYPE_CHECKING, List - -from .bases import NextStateConditional - -if TYPE_CHECKING: - from systemrdl.node import FieldNode - - -class HWSet(NextStateConditional): - comment = "HW Set" - def is_match(self, field: 'FieldNode') -> bool: - return bool(field.get_property('hwset')) - - def get_predicate(self, field: 'FieldNode') -> str: - prop = field.get_property('hwset') - if isinstance(prop, bool): - identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "hwset") - else: - # signal or field - identifier = str(self.exp.dereferencer.get_value(prop)) - return identifier - - def get_assignments(self, field: 'FieldNode') -> List[str]: - hwmask = field.get_property('hwmask') - hwenable = field.get_property('hwenable') - R = self.exp.field_logic.get_storage_identifier(field) - if hwmask is not None: - M = self.exp.dereferencer.get_value(hwmask) - next_val = f"{R} | ~{M}" - elif hwenable is not None: - E = self.exp.dereferencer.get_value(hwenable) - next_val = f"{R} | {E}" - else: - next_val = "'1" - - return [ - f"next_c = {next_val};", - "load_next_c = '1;", - ] - - -class HWClear(NextStateConditional): - comment = "HW Clear" - def is_match(self, field: 'FieldNode') -> bool: - return bool(field.get_property('hwclr')) - - def get_predicate(self, field: 'FieldNode') -> str: - prop = field.get_property('hwclr') - if isinstance(prop, bool): - identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "hwclr") - else: - # signal or field - identifier = str(self.exp.dereferencer.get_value(prop)) - return identifier - - def get_assignments(self, field: 'FieldNode') -> List[str]: - hwmask = field.get_property('hwmask') - hwenable = field.get_property('hwenable') - R = self.exp.field_logic.get_storage_identifier(field) - if hwmask is not None: - M = self.exp.dereferencer.get_value(hwmask) - next_val = f"{R} & {M}" - elif hwenable is not None: - E = self.exp.dereferencer.get_value(hwenable) - next_val = f"{R} & ~{E}" - else: - next_val = "'0" - - return [ - f"next_c = {next_val};", - "load_next_c = '1;", - ] diff --git a/src/peakrdl_regblock/field_logic/hw_write.py b/src/peakrdl_regblock/field_logic/hw_write.py deleted file mode 100644 index 5a629ce..0000000 --- a/src/peakrdl_regblock/field_logic/hw_write.py +++ /dev/null @@ -1,95 +0,0 @@ -from typing import TYPE_CHECKING, List - -from .bases import NextStateConditional, NextStateUnconditional - -if TYPE_CHECKING: - from systemrdl.node import FieldNode - - -class AlwaysWrite(NextStateUnconditional): - """ - hw writable, without any qualifying we/wel - """ - comment = "HW Write" - unconditional_explanation = "A hardware-writable field without a write-enable (we/wel) will always update the field value" - - def is_match(self, field: 'FieldNode') -> bool: - return ( - field.is_hw_writable - and not field.get_property('we') - and not field.get_property('wel') - ) - - def get_assignments(self, field: 'FieldNode') -> List[str]: - hwmask = field.get_property('hwmask') - hwenable = field.get_property('hwenable') - I = str(self.exp.hwif.get_input_identifier(field)) - R = self.exp.field_logic.get_storage_identifier(field) - if hwmask is not None: - M = self.exp.dereferencer.get_value(hwmask) - next_val = f"{I} & ~{M} | {R} & {M}" - elif hwenable is not None: - E = self.exp.dereferencer.get_value(hwenable) - next_val = f"{I} & {E} | {R} & ~{E}" - else: - next_val = I - - return [ - f"next_c = {next_val};", - "load_next_c = '1;", - ] - - -class _QualifiedWrite(NextStateConditional): - def get_assignments(self, field: 'FieldNode') -> List[str]: - hwmask = field.get_property('hwmask') - hwenable = field.get_property('hwenable') - I = str(self.exp.hwif.get_input_identifier(field)) - R = self.exp.field_logic.get_storage_identifier(field) - if hwmask is not None: - M = self.exp.dereferencer.get_value(hwmask) - next_val = f"{I} & ~{M} | {R} & {M}" - elif hwenable is not None: - E = self.exp.dereferencer.get_value(hwenable) - next_val = f"{I} & {E} | {R} & ~{E}" - else: - next_val = I - - return [ - f"next_c = {next_val};", - "load_next_c = '1;", - ] - -class WEWrite(_QualifiedWrite): - comment = "HW Write - we" - def is_match(self, field: 'FieldNode') -> bool: - return ( - field.is_hw_writable - and bool(field.get_property('we')) - ) - - def get_predicate(self, field: 'FieldNode') -> str: - prop = field.get_property('we') - if isinstance(prop, bool): - identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "we") - else: - # signal or field - identifier = str(self.exp.dereferencer.get_value(prop)) - return identifier - -class WELWrite(_QualifiedWrite): - comment = "HW Write - wel" - def is_match(self, field: 'FieldNode') -> bool: - return ( - field.is_hw_writable - and bool(field.get_property('wel')) - ) - - def get_predicate(self, field: 'FieldNode') -> str: - prop = field.get_property('wel') - if isinstance(prop, bool): - identifier = self.exp.hwif.get_implied_prop_input_identifier(field, "wel") - else: - # signal or field - identifier = str(self.exp.dereferencer.get_value(prop)) - return f"!{identifier}" diff --git a/src/peakrdl_regblock/field_logic/sw_onread.py b/src/peakrdl_regblock/field_logic/sw_onread.py deleted file mode 100644 index ba998c7..0000000 --- a/src/peakrdl_regblock/field_logic/sw_onread.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import TYPE_CHECKING, List - -from systemrdl.rdltypes import OnReadType - -from .bases import NextStateConditional - -if TYPE_CHECKING: - from systemrdl.node import FieldNode - -class _OnRead(NextStateConditional): - onreadtype = None # type: OnReadType - def is_match(self, field: 'FieldNode') -> bool: - return field.get_property('onread') == self.onreadtype - - def get_predicate(self, field: 'FieldNode') -> str: - if field.parent.get_property('buffer_reads'): - # Is buffered read. Use alternate strobe - rstrb = self.exp.read_buffering.get_trigger(field.parent) - return rstrb - else: - # is regular register - strb = self.exp.dereferencer.get_access_strobe(field) - return f"{strb} && !decoded_req_is_wr" - - -class ClearOnRead(_OnRead): - comment = "SW clear on read" - onreadtype = OnReadType.rclr - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return [ - "next_c = '0;", - "load_next_c = '1;", - ] - - -class SetOnRead(_OnRead): - comment = "SW set on read" - onreadtype = OnReadType.rset - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return [ - "next_c = '1;", - "load_next_c = '1;", - ] diff --git a/src/peakrdl_regblock/field_logic/sw_onwrite.py b/src/peakrdl_regblock/field_logic/sw_onwrite.py deleted file mode 100644 index 7d11945..0000000 --- a/src/peakrdl_regblock/field_logic/sw_onwrite.py +++ /dev/null @@ -1,129 +0,0 @@ -from typing import TYPE_CHECKING, List, Optional - -from systemrdl.rdltypes import OnWriteType - -from .bases import NextStateConditional - -if TYPE_CHECKING: - from systemrdl.node import FieldNode - -# TODO: implement sw=w1 "write once" fields - -class _OnWrite(NextStateConditional): - onwritetype: Optional[OnWriteType] = None - def is_match(self, field: 'FieldNode') -> bool: - return field.is_sw_writable and field.get_property('onwrite') == self.onwritetype - - def get_predicate(self, field: 'FieldNode') -> str: - if field.parent.get_property('buffer_writes'): - # Is buffered write. Use alternate strobe - wstrb = self.exp.write_buffering.get_write_strobe(field) - - if field.get_property('swwe') or field.get_property('swwel'): - # dereferencer will wrap swwel complement if necessary - qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe') - return f"{wstrb} && {qualifier}" - - return wstrb - else: - # is regular register - strb = self.exp.dereferencer.get_access_strobe(field) - - if field.get_property('swwe') or field.get_property('swwel'): - # dereferencer will wrap swwel complement if necessary - qualifier = self.exp.dereferencer.get_field_propref_value(field, 'swwe') - return f"{strb} && decoded_req_is_wr && {qualifier}" - - return f"{strb} && decoded_req_is_wr" - - def get_assignments(self, field: 'FieldNode') -> List[str]: - accesswidth = field.parent.get_property("accesswidth") - - # Due to 10.6.1-f, it is impossible for a field with an onwrite action to - # be split across subwords. - # Therefore it is ok to get the subword idx from only one of the bit offsets - sidx = field.low // accesswidth - - # field does not get split between subwords - R = self.exp.field_logic.get_storage_identifier(field) - D = self.exp.field_logic.get_wr_data(field, sidx) - S = self.exp.field_logic.get_wr_biten(field, sidx) - lines = [ - f"next_c = {self.get_onwrite_rhs(R, D, S)};", - "load_next_c = '1;", - ] - return lines - - def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str: - raise NotImplementedError - - -#------------------------------------------------------------------------------- -class WriteOneSet(_OnWrite): - comment = "SW write 1 set" - onwritetype = OnWriteType.woset - - def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str: - return f"{reg} | ({data} & {strb})" - -class WriteOneClear(_OnWrite): - comment = "SW write 1 clear" - onwritetype = OnWriteType.woclr - - def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str: - return f"{reg} & ~({data} & {strb})" - -class WriteOneToggle(_OnWrite): - comment = "SW write 1 toggle" - onwritetype = OnWriteType.wot - - def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str: - return f"{reg} ^ ({data} & {strb})" - -class WriteZeroSet(_OnWrite): - comment = "SW write 0 set" - onwritetype = OnWriteType.wzs - - def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str: - return f"{reg} | (~{data} & {strb})" - -class WriteZeroClear(_OnWrite): - comment = "SW write 0 clear" - onwritetype = OnWriteType.wzc - - def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str: - return f"{reg} & ({data} | ~{strb})" - -class WriteZeroToggle(_OnWrite): - comment = "SW write 0 toggle" - onwritetype = OnWriteType.wzt - - def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str: - return f"{reg} ^ (~{data} & {strb})" - -class WriteClear(_OnWrite): - comment = "SW write clear" - onwritetype = OnWriteType.wclr - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return [ - "next_c = '0;", - "load_next_c = '1;", - ] - -class WriteSet(_OnWrite): - comment = "SW write set" - onwritetype = OnWriteType.wset - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return [ - "next_c = '1;", - "load_next_c = '1;", - ] - -class Write(_OnWrite): - comment = "SW write" - onwritetype = None - - def get_onwrite_rhs(self, reg: str, data: str, strb: str) -> str: - return f"({reg} & ~{strb}) | ({data} & {strb})" diff --git a/src/peakrdl_regblock/field_logic/sw_singlepulse.py b/src/peakrdl_regblock/field_logic/sw_singlepulse.py deleted file mode 100644 index 9ed476b..0000000 --- a/src/peakrdl_regblock/field_logic/sw_singlepulse.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import TYPE_CHECKING, List - -from .bases import NextStateUnconditional - -if TYPE_CHECKING: - from systemrdl.node import FieldNode - -class Singlepulse(NextStateUnconditional): - comment = "singlepulse clears back to 0" - unconditional_explanation = "The 'singlepulse' property unconditionally clears a field when not written" - - def is_match(self, field: 'FieldNode') -> bool: - return field.get_property('singlepulse') - - def get_assignments(self, field: 'FieldNode') -> List[str]: - return [ - "next_c = '0;", - "load_next_c = '1;", - ] diff --git a/src/peakrdl_regblock/field_logic/templates/counter_macros.sv b/src/peakrdl_regblock/field_logic/templates/counter_macros.sv deleted file mode 100644 index 2714bce..0000000 --- a/src/peakrdl_regblock/field_logic/templates/counter_macros.sv +++ /dev/null @@ -1,48 +0,0 @@ -{% macro up_counter(field) -%} - if({{field_logic.get_counter_incr_strobe(node)}}) begin // increment - {%- if field_logic.counter_incrsaturates(node) %} - if((({{node.width+1}})'(next_c) + {{field_logic.get_counter_incrvalue(node)}}) > {{field_logic.get_counter_incrsaturate_value(node)}}) begin // up-counter saturated - next_c = {{field_logic.get_counter_incrsaturate_value(node)}}; - end else begin - next_c = next_c + {{field_logic.get_counter_incrvalue(node)}}; - end - {%- else %} - {{field_logic.get_field_combo_identifier(node, "overflow")}} = ((({{node.width+1}})'(next_c) + {{field_logic.get_counter_incrvalue(node)}}) > {{get_value(2**node.width - 1, node.width)}}); - next_c = next_c + {{field_logic.get_counter_incrvalue(node)}}; - {%- endif %} - load_next_c = '1; - {%- if not field_logic.counter_incrsaturates(node) %} - end else begin - {{field_logic.get_field_combo_identifier(node, "overflow")}} = '0; - {%- endif %} - end - {{field_logic.get_field_combo_identifier(node, "incrthreshold")}} = ({{field_logic.get_storage_identifier(node)}} >= {{field_logic.get_counter_incrthreshold_value(node)}}); - {%- if field_logic.counter_incrsaturates(node) %} - {{field_logic.get_field_combo_identifier(node, "incrsaturate")}} = ({{field_logic.get_storage_identifier(node)}} >= {{field_logic.get_counter_incrsaturate_value(node)}}); - {%- endif %} -{%- endmacro %} - - -{% macro down_counter(field) -%} - if({{field_logic.get_counter_decr_strobe(node)}}) begin // decrement - {%- if field_logic.counter_decrsaturates(node) %} - if(({{node.width+1}})'(next_c) < ({{field_logic.get_counter_decrvalue(node)}} + {{field_logic.get_counter_decrsaturate_value(node)}})) begin // down-counter saturated - next_c = {{field_logic.get_counter_decrsaturate_value(node)}}; - end else begin - next_c = next_c - {{field_logic.get_counter_decrvalue(node)}}; - end - {%- else %} - {{field_logic.get_field_combo_identifier(node, "underflow")}} = (next_c < ({{field_logic.get_counter_decrvalue(node)}})); - next_c = next_c - {{field_logic.get_counter_decrvalue(node)}}; - {%- endif %} - load_next_c = '1; - {%- if not field_logic.counter_decrsaturates(node) %} - end else begin - {{field_logic.get_field_combo_identifier(node, "underflow")}} = '0; - {%- endif %} - end - {{field_logic.get_field_combo_identifier(node, "decrthreshold")}} = ({{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_counter_decrthreshold_value(node)}}); - {%- if field_logic.counter_decrsaturates(node) %} - {{field_logic.get_field_combo_identifier(node, "decrsaturate")}} = ({{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_counter_decrsaturate_value(node)}}); - {%- endif %} -{%- endmacro %} diff --git a/src/peakrdl_regblock/field_logic/templates/external_block.sv b/src/peakrdl_regblock/field_logic/templates/external_block.sv deleted file mode 100644 index 5c5914a..0000000 --- a/src/peakrdl_regblock/field_logic/templates/external_block.sv +++ /dev/null @@ -1,31 +0,0 @@ -{% if retime -%} - - -always_ff {{get_always_ff_event(resetsignal)}} begin - if({{get_resetsignal(resetsignal)}}) begin - {{prefix}}.req <= '0; - {{prefix}}.addr <= '0; - {{prefix}}.req_is_wr <= '0; - {{prefix}}.wr_data <= '0; - {{prefix}}.wr_biten <= '0; - end else begin - {{prefix}}.req <= {{strb}}; - {{prefix}}.addr <= decoded_addr[{{addr_width-1}}:0]; - {{prefix}}.req_is_wr <= decoded_req_is_wr; - {{prefix}}.wr_data <= decoded_wr_data; - {{prefix}}.wr_biten <= decoded_wr_biten; - end -end - - -{%- else -%} - - -assign {{prefix}}.req = {{strb}}; -assign {{prefix}}.addr = decoded_addr[{{addr_width-1}}:0]; -assign {{prefix}}.req_is_wr = decoded_req_is_wr; -assign {{prefix}}.wr_data = decoded_wr_data; -assign {{prefix}}.wr_biten = decoded_wr_biten; - - -{%- endif %} diff --git a/src/peakrdl_regblock/field_logic/templates/external_reg.sv b/src/peakrdl_regblock/field_logic/templates/external_reg.sv deleted file mode 100644 index 1a47515..0000000 --- a/src/peakrdl_regblock/field_logic/templates/external_reg.sv +++ /dev/null @@ -1,46 +0,0 @@ -{% if retime -%} - - -always_ff {{get_always_ff_event(resetsignal)}} begin - if({{get_resetsignal(resetsignal)}}) begin - {{prefix}}.req <= '0; - {{prefix}}.req_is_wr <= '0; - {%- if has_sw_writable %} - {{prefix}}.wr_data <= '0; - {{prefix}}.wr_biten <= '0; - {%- endif %} - end else begin - {%- if has_sw_readable and has_sw_writable %} - {{prefix}}.req <= {{strb}}; - {%- elif has_sw_readable and not has_sw_writable %} - {{prefix}}.req <= !decoded_req_is_wr ? {{strb}} : '0; - {%- elif not has_sw_readable and has_sw_writable %} - {{prefix}}.req <= decoded_req_is_wr ? {{strb}} : '0; - {%- endif %} - {{prefix}}.req_is_wr <= decoded_req_is_wr; - {%- if has_sw_writable %} - {{prefix}}.wr_data <= decoded_wr_data{{bslice}}; - {{prefix}}.wr_biten <= decoded_wr_biten{{bslice}}; - {%- endif %} - end -end - - -{%- else -%} - - -{%- if has_sw_readable and has_sw_writable %} -assign {{prefix}}.req = {{strb}}; -{%- elif has_sw_readable and not has_sw_writable %} -assign {{prefix}}.req = !decoded_req_is_wr ? {{strb}} : '0; -{%- elif not has_sw_readable and has_sw_writable %} -assign {{prefix}}.req = decoded_req_is_wr ? {{strb}} : '0; -{%- endif %} -assign {{prefix}}.req_is_wr = decoded_req_is_wr; -{%- if has_sw_writable %} -assign {{prefix}}.wr_data = decoded_wr_data{{bslice}}; -assign {{prefix}}.wr_biten = decoded_wr_biten{{bslice}}; -{%- endif %} - - -{%- endif %} diff --git a/src/peakrdl_regblock/field_logic/templates/field_storage.sv b/src/peakrdl_regblock/field_logic/templates/field_storage.sv deleted file mode 100644 index 4e2dca0..0000000 --- a/src/peakrdl_regblock/field_logic/templates/field_storage.sv +++ /dev/null @@ -1,86 +0,0 @@ -{%- import 'field_logic/templates/counter_macros.sv' as counter_macros with context -%} -// Field: {{node.get_path()}} -always_comb begin - automatic logic [{{node.width-1}}:0] next_c; - automatic logic load_next_c; - next_c = {{field_logic.get_storage_identifier(node)}}; - load_next_c = '0; - - {%- for signal in extra_combo_signals %} - {{field_logic.get_field_combo_identifier(node, signal.name)}} = {{signal.default_assignment}}; - {%- endfor %} - {% for conditional in conditionals %} - {%- if not loop.first %} else {% endif %}if({{conditional.get_predicate(node)}}) begin // {{conditional.comment}} - {%- for assignment in conditional.get_assignments(node) %} - {{assignment|indent}} - {%- endfor %} - end - {%- endfor %} - {%- if unconditional %} - {%- if conditionals %} else begin // {{unconditional.comment}} - {%- for assignment in unconditional.get_assignments(node) %} - {{assignment|indent}} - {%- endfor %} - end - {%- else %} - // {{unconditional.comment}} - {%- for assignment in unconditional.get_assignments(node) %} - {{assignment|indent}} - {%- endfor %} - {%- endif %} - {%- endif %} - - {%- if node.is_up_counter %} - {{counter_macros.up_counter(node)}} - {%- endif %} - - {%- if node.is_down_counter %} - {{counter_macros.down_counter(node)}} - {%- endif %} - {{field_logic.get_field_combo_identifier(node, "next")}} = next_c; - {{field_logic.get_field_combo_identifier(node, "load_next")}} = load_next_c; - - {%- if node.get_property('paritycheck') %} - {{field_logic.get_parity_error_identifier(node)}} = ({{field_logic.get_parity_identifier(node)}} != ^{{field_logic.get_storage_identifier(node)}}); - {%- endif %} -end - - - -{%- if reset is not none %} -always_ff {{get_always_ff_event(resetsignal)}} begin - if({{get_resetsignal(resetsignal)}}) begin - {{field_logic.get_storage_identifier(node)}} <= {{reset}}; - {%- if node.get_property('paritycheck') %} - {{field_logic.get_parity_identifier(node)}} <= ^{{reset}}; - {%- endif %} - {%- if field_logic.has_next_q(node) %} - {{field_logic.get_next_q_identifier(node)}} <= {{reset}}; - {%- endif %} - end else begin - if({{field_logic.get_field_combo_identifier(node, "load_next")}}) begin - {{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_field_combo_identifier(node, "next")}}; - {%- if node.get_property('paritycheck') %} - {{field_logic.get_parity_identifier(node)}} <= ^{{field_logic.get_field_combo_identifier(node, "next")}}; - {%- endif %} - end - {%- if field_logic.has_next_q(node) %} - {{field_logic.get_next_q_identifier(node)}} <= {{get_input_identifier(node)}}; - {%- endif %} - end -end - - -{%- else %} -always_ff @(posedge clk) begin - if({{field_logic.get_field_combo_identifier(node, "load_next")}}) begin - {{field_logic.get_storage_identifier(node)}} <= {{field_logic.get_field_combo_identifier(node, "next")}}; - {%- if node.get_property('paritycheck') %} - {{field_logic.get_parity_identifier(node)}} <= ^{{field_logic.get_field_combo_identifier(node, "next")}}; - {%- endif %} - end - {%- if field_logic.has_next_q(node) %} - {{field_logic.get_next_q_identifier(node)}} <= {{get_input_identifier(node)}}; - {%- endif %} -end -{%- endif %} diff --git a/src/peakrdl_regblock/hwif/__init__.py b/src/peakrdl_regblock/hwif/__init__.py deleted file mode 100644 index 3f040f0..0000000 --- a/src/peakrdl_regblock/hwif/__init__.py +++ /dev/null @@ -1,253 +0,0 @@ -from typing import TYPE_CHECKING, Union, Optional, TextIO - -from systemrdl.node import AddrmapNode, SignalNode, FieldNode, RegNode, AddressableNode -from systemrdl.rdltypes import PropertyReference - -from ..utils import get_indexed_path -from ..identifier_filter import kw_filter as kwf -from ..sv_int import SVInt - -from .generators import InputStructGenerator_Hier, OutputStructGenerator_Hier -from .generators import InputStructGenerator_TypeScope, OutputStructGenerator_TypeScope -from .generators import EnumGenerator - -if TYPE_CHECKING: - from ..exporter import BusDecoderExporter, DesignState - - -class Hwif: - """ - Defines how the hardware input/output signals are generated: - - Field outputs - - Field inputs - - Signal inputs (except those that are promoted to the top) - """ - - def __init__(self, exp: "BusDecoderExporter", hwif_report_file: Optional[TextIO]): - self.exp = exp - - self.has_input_struct = False - self.has_output_struct = False - - self.hwif_report_file = hwif_report_file - - if not self.ds.reuse_hwif_typedefs: - self._gen_in_cls = InputStructGenerator_Hier - self._gen_out_cls = OutputStructGenerator_Hier - else: - self._gen_in_cls = InputStructGenerator_TypeScope - self._gen_out_cls = OutputStructGenerator_TypeScope - - @property - def ds(self) -> "DesignState": - return self.exp.ds - - @property - def top_node(self) -> AddrmapNode: - return self.exp.ds.top_node - - def get_extra_package_params(self) -> str: - lines = [""] - - for param in self.top_node.inst.parameters: - value = param.get_value() - if isinstance(value, int): - lines.append(f"localparam {param.name} = {SVInt(value)};") - elif isinstance(value, str): - lines.append(f"localparam {param.name} = {value};") - - return "\n".join(lines) - - def get_package_contents(self) -> str: - """ - If this hwif requires a package, generate the string - """ - lines = [""] - - gen_in = self._gen_in_cls(self) - structs_in = gen_in.get_struct( - self.top_node, f"{self.top_node.inst_name}__in_t" - ) - if structs_in is not None: - self.has_input_struct = True - lines.append(structs_in) - else: - self.has_input_struct = False - - gen_out = self._gen_out_cls(self) - structs_out = gen_out.get_struct( - self.top_node, f"{self.top_node.inst_name}__out_t" - ) - if structs_out is not None: - self.has_output_struct = True - lines.append(structs_out) - else: - self.has_output_struct = False - - gen_enum = EnumGenerator() - enums = gen_enum.get_enums(self.ds.user_enums) - if enums is not None: - lines.append(enums) - - return "\n\n".join(lines) - - @property - def port_declaration(self) -> str: - """ - Returns the declaration string for all I/O ports in the hwif group - """ - - # Assume get_package_declaration() is always called prior to this - assert self.has_input_struct is not None - assert self.has_output_struct is not None - - lines = [] - if self.has_input_struct: - type_name = f"{self.top_node.inst_name}__in_t" - lines.append(f"input {self.ds.package_name}::{type_name} hwif_in") - if self.has_output_struct: - type_name = f"{self.top_node.inst_name}__out_t" - lines.append(f"output {self.ds.package_name}::{type_name} hwif_out") - - return ",\n".join(lines) - - # --------------------------------------------------------------------------- - # hwif utility functions - # --------------------------------------------------------------------------- - def has_value_input(self, obj: Union[FieldNode, SignalNode]) -> bool: - """ - Returns True if the object infers an input wire in the hwif - """ - if isinstance(obj, FieldNode): - return obj.is_hw_writable - elif isinstance(obj, SignalNode): - # Signals are implicitly always inputs - return True - else: - raise RuntimeError - - def has_value_output(self, obj: FieldNode) -> bool: - """ - Returns True if the object infers an output wire in the hwif - """ - return obj.is_hw_readable - - def get_input_identifier( - self, - obj: Union[FieldNode, SignalNode, PropertyReference], - width: Optional[int] = None, - ) -> Union[SVInt, str]: - """ - Returns the identifier string that best represents the input object. - - if obj is: - Field: the fields hw input value port - Signal: signal input value - Prop reference: - could be an implied hwclr/hwset/swwe/swwel/we/wel input - - raises an exception if obj is invalid - """ - if isinstance(obj, FieldNode): - next_value = obj.get_property("next") - if next_value is not None: - # 'next' property replaces the inferred input signal - return self.exp.dereferencer.get_value(next_value, width) - # Otherwise, use inferred - path = get_indexed_path(self.top_node, obj) - return "hwif_in." + path + ".next" - elif isinstance(obj, SignalNode): - if obj.get_path() in self.ds.out_of_hier_signals: - return kwf(obj.inst_name) - path = get_indexed_path(self.top_node, obj) - return "hwif_in." + path - elif isinstance(obj, PropertyReference): - assert isinstance(obj.node, FieldNode) - return self.get_implied_prop_input_identifier(obj.node, obj.name) - - raise RuntimeError(f"Unhandled reference to: {obj}") - - def get_external_rd_data(self, node: AddressableNode) -> str: - """ - Returns the identifier string for an external component's rd_data signal - """ - path = get_indexed_path(self.top_node, node) - return "hwif_in." + path + ".rd_data" - - def get_external_rd_ack(self, node: AddressableNode) -> str: - """ - Returns the identifier string for an external component's rd_ack signal - """ - path = get_indexed_path(self.top_node, node) - return "hwif_in." + path + ".rd_ack" - - def get_external_wr_ack(self, node: AddressableNode) -> str: - """ - Returns the identifier string for an external component's wr_ack signal - """ - path = get_indexed_path(self.top_node, node) - return "hwif_in." + path + ".wr_ack" - - def get_implied_prop_input_identifier(self, field: FieldNode, prop: str) -> str: - assert prop in { - "hwclr", - "hwset", - "swwe", - "swwel", - "we", - "wel", - "incr", - "decr", - "incrvalue", - "decrvalue", - } - path = get_indexed_path(self.top_node, field) - return "hwif_in." + path + "." + prop - - def get_output_identifier(self, obj: Union[FieldNode, PropertyReference]) -> str: - """ - Returns the identifier string that best represents the output object. - - if obj is: - Field: the fields hw output value port - Property ref: this is also part of the struct - - raises an exception if obj is invalid - """ - if isinstance(obj, FieldNode): - path = get_indexed_path(self.top_node, obj) - return "hwif_out." + path + ".value" - elif isinstance(obj, PropertyReference): - # TODO: this might be dead code. - # not sure when anything would call this function with a prop ref - # when dereferencer's get_value is more useful here - assert obj.node.get_property(obj.name) - assert isinstance(obj.node, (RegNode, FieldNode)) - return self.get_implied_prop_output_identifier(obj.node, obj.name) - - raise RuntimeError(f"Unhandled reference to: {obj}") - - def get_implied_prop_output_identifier( - self, node: Union[FieldNode, RegNode], prop: str - ) -> str: - if isinstance(node, FieldNode): - assert prop in { - "anded", - "ored", - "xored", - "swmod", - "swacc", - "incrthreshold", - "decrthreshold", - "overflow", - "underflow", - "rd_swacc", - "wr_swacc", - } - elif isinstance(node, RegNode): - assert prop in { - "intr", - "halt", - } - path = get_indexed_path(self.top_node, node) - return "hwif_out." + path + "." + prop diff --git a/src/peakrdl_regblock/hwif/generators.py b/src/peakrdl_regblock/hwif/generators.py deleted file mode 100644 index e53f0fe..0000000 --- a/src/peakrdl_regblock/hwif/generators.py +++ /dev/null @@ -1,385 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List, Type - -from systemrdl.node import FieldNode, RegNode, AddrmapNode, MemNode -from systemrdl.walker import WalkerAction - -from ..struct_generator import RDLFlatStructGenerator -from ..identifier_filter import kw_filter as kwf -from ..sv_int import SVInt -from ..utils import clog2 - -if TYPE_CHECKING: - from systemrdl.node import Node, SignalNode, AddressableNode, RegfileNode - from . import Hwif - from systemrdl.rdltypes import UserEnum - -class HWIFStructGenerator(RDLFlatStructGenerator): - def __init__(self, hwif: 'Hwif', hwif_name: str) -> None: - super().__init__() - self.hwif = hwif - self.top_node = hwif.top_node - - self.hwif_report_stack = [hwif_name] - - def push_struct(self, type_name: str, inst_name: str, array_dimensions: Optional[List[int]] = None, packed: bool = False) -> None: # type: ignore - super().push_struct(type_name, inst_name, array_dimensions, packed) - - if array_dimensions: - array_suffix = "".join([f"[0:{dim-1}]" for dim in array_dimensions]) - segment = inst_name + array_suffix - else: - segment = inst_name - self.hwif_report_stack.append(segment) - - def pop_struct(self) -> None: - super().pop_struct() - self.hwif_report_stack.pop() - - def add_member(self, name: str, width: int = 1, *, lsb: int = 0, signed: bool = False) -> None: # type: ignore # pylint: disable=arguments-differ - super().add_member(name, width, lsb=lsb, signed=signed) - - if width > 1 or lsb != 0: - suffix = f"[{lsb+width-1}:{lsb}]" - else: - suffix = "" - - path = ".".join(self.hwif_report_stack) - if self.hwif.hwif_report_file: - self.hwif.hwif_report_file.write(f"{path}.{name}{suffix}\n") - -#------------------------------------------------------------------------------- - -class InputStructGenerator_Hier(HWIFStructGenerator): - def __init__(self, hwif: 'Hwif') -> None: - super().__init__(hwif, "hwif_in") - - def get_typdef_name(self, node:'Node', suffix: str = "") -> str: - base = node.get_rel_path( - self.top_node.parent, - hier_separator="__", - array_suffix="x", - empty_array_suffix="x" - ) - return f'{base}{suffix}__in_t' - - def enter_Signal(self, node: 'SignalNode') -> None: - # only emit the signal if design scanner detected it is actually being used - path = node.get_path() - if path in self.hwif.ds.in_hier_signal_paths: - self.add_member(kwf(node.inst_name), node.width) - - def _add_external_block_members(self, node: 'AddressableNode') -> None: - self.add_member("rd_ack") - self.add_member("rd_data", self.hwif.ds.cpuif_data_width) - self.add_member("wr_ack") - - def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]: - super().enter_Addrmap(node) - assert node.external - self._add_external_block_members(node) - return WalkerAction.SkipDescendants - - def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]: - super().enter_Regfile(node) - if node.external: - self._add_external_block_members(node) - return WalkerAction.SkipDescendants - return WalkerAction.Continue - - def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: - super().enter_Mem(node) - assert node.external - self._add_external_block_members(node) - return WalkerAction.SkipDescendants - - def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: - super().enter_Reg(node) - if node.external: - width = min(self.hwif.ds.cpuif_data_width, node.get_property('regwidth')) - n_subwords = node.get_property("regwidth") // node.get_property("accesswidth") - if node.has_sw_readable: - self.add_member("rd_ack") - self.add_external_reg_rd_data(node, width, n_subwords) - if node.has_sw_writable: - self.add_member("wr_ack") - return WalkerAction.SkipDescendants - - return WalkerAction.Continue - - def add_external_reg_rd_data(self, node: 'RegNode', width: int, n_subwords: int) -> None: - if n_subwords == 1: - # External reg is 1 sub-word. Add a packed struct to represent it - type_name = self.get_typdef_name(node, "__fields") - self.push_struct(type_name, "rd_data", packed=True) - current_bit = width - 1 - for field in reversed(list(node.fields())): - if not field.is_sw_readable: - continue - if field.high < current_bit: - # Add padding - self.add_member( - f"_reserved_{current_bit}_{field.high + 1}", - current_bit - field.high - ) - self.add_member( - kwf(field.inst_name), - field.width - ) - current_bit = field.low - 1 - - # Add end padding if needed - if current_bit != -1: - self.add_member( - f"_reserved_{current_bit}_0", - current_bit + 1 - ) - self.pop_struct() - else: - # Multiple sub-words. Cannot generate a struct - self.add_member("rd_data", width) - - def enter_Field(self, node: 'FieldNode') -> None: - type_name = self.get_typdef_name(node) - self.push_struct(type_name, kwf(node.inst_name)) - - # Provide input to field's next value if it is writable by hw, and it - # was not overridden by the 'next' property - if node.is_hw_writable and node.get_property('next') is None: - # Get the field's LSB index (can be nonzero for fixed-point values) - fracwidth = node.get_property("fracwidth") - lsb = 0 if fracwidth is None else -fracwidth - - # get the signedness of the field - signed = node.get_property("is_signed") - - self.add_member("next", node.width, lsb=lsb, signed=signed) - - # Generate implied inputs - for prop_name in ["we", "wel", "swwe", "swwel", "hwclr", "hwset"]: - # if property is boolean and true, implies a corresponding input signal on the hwif - if node.get_property(prop_name) is True: - self.add_member(prop_name) - - # Generate any implied counter inputs - if node.is_up_counter: - if not node.get_property('incr'): - # User did not provide their own incr component reference. - # Imply an input - self.add_member('incr') - - width = node.get_property('incrwidth') - if width: - # Implies a corresponding incrvalue input - self.add_member('incrvalue', width) - - if node.is_down_counter: - if not node.get_property('decr'): - # User did not provide their own decr component reference. - # Imply an input - self.add_member('decr') - - width = node.get_property('decrwidth') - if width: - # Implies a corresponding decrvalue input - self.add_member('decrvalue', width) - - def exit_Field(self, node: 'FieldNode') -> None: - self.pop_struct() - - -class OutputStructGenerator_Hier(HWIFStructGenerator): - def __init__(self, hwif: 'Hwif') -> None: - super().__init__(hwif, "hwif_out") - - def get_typdef_name(self, node:'Node', suffix: str = "") -> str: - base = node.get_rel_path( - self.top_node.parent, - hier_separator="__", - array_suffix="x", - empty_array_suffix="x" - ) - return f'{base}{suffix}__out_t' - - def _add_external_block_members(self, node: 'AddressableNode') -> None: - self.add_member("req") - self.add_member("addr", clog2(node.size)) - self.add_member("req_is_wr") - self.add_member("wr_data", self.hwif.ds.cpuif_data_width) - self.add_member("wr_biten", self.hwif.ds.cpuif_data_width) - - def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]: - super().enter_Addrmap(node) - assert node.external - self._add_external_block_members(node) - return WalkerAction.SkipDescendants - - def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]: - super().enter_Regfile(node) - if node.external: - self._add_external_block_members(node) - return WalkerAction.SkipDescendants - return WalkerAction.Continue - - def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: - super().enter_Mem(node) - assert node.external - self._add_external_block_members(node) - return WalkerAction.SkipDescendants - - def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: - super().enter_Reg(node) - if node.external: - width = min(self.hwif.ds.cpuif_data_width, node.get_property('regwidth')) - n_subwords = node.get_property("regwidth") // node.get_property("accesswidth") - self.add_member("req", n_subwords) - self.add_member("req_is_wr") - if node.has_sw_writable: - self.add_external_reg_wr_data("wr_data", node, width, n_subwords) - self.add_external_reg_wr_data("wr_biten", node, width, n_subwords) - return WalkerAction.SkipDescendants - - return WalkerAction.Continue - - def add_external_reg_wr_data(self, name: str, node: 'RegNode', width: int, n_subwords: int) -> None: - if n_subwords == 1: - # External reg is 1 sub-word. Add a packed struct to represent it - type_name = self.get_typdef_name(node, "__fields") - self.push_struct(type_name, name, packed=True) - current_bit = width - 1 - for field in reversed(list(node.fields())): - if not field.is_sw_writable: - continue - if field.high < current_bit: - # Add padding - self.add_member( - f"_reserved_{current_bit}_{field.high + 1}", - current_bit - field.high - ) - self.add_member( - kwf(field.inst_name), - field.width - ) - current_bit = field.low - 1 - - # Add end padding if needed - if current_bit != -1: - self.add_member( - f"_reserved_{current_bit}_0", - current_bit + 1 - ) - self.pop_struct() - else: - # Multiple sub-words. Cannot generate a struct - self.add_member(name, width) - - def enter_Field(self, node: 'FieldNode') -> None: - type_name = self.get_typdef_name(node) - self.push_struct(type_name, kwf(node.inst_name)) - - # Expose field's value if it is readable by hw - if node.is_hw_readable: - # Get the node's LSB index (can be nonzero for fixed-point values) - fracwidth = node.get_property("fracwidth") - lsb = 0 if fracwidth is None else -fracwidth - - # get the signedness of the field - signed = node.get_property("is_signed") - - self.add_member("value", node.width, lsb=lsb, signed=signed) - - # Generate output bit signals enabled via property - for prop_name in ["anded", "ored", "xored", "swmod", "swacc", "overflow", "underflow", "rd_swacc", "wr_swacc"]: - if node.get_property(prop_name): - self.add_member(prop_name) - - if node.get_property('incrthreshold') is not False: # (explicitly not False. Not 0) - self.add_member('incrthreshold') - if node.get_property('decrthreshold') is not False: # (explicitly not False. Not 0) - self.add_member('decrthreshold') - - def exit_Field(self, node: 'FieldNode') -> None: - self.pop_struct() - - def exit_Reg(self, node: 'RegNode') -> None: - if node.is_interrupt_reg: - self.add_member('intr') - if node.is_halt_reg: - self.add_member('halt') - super().exit_Reg(node) - -#------------------------------------------------------------------------------- -class InputStructGenerator_TypeScope(InputStructGenerator_Hier): - def get_typdef_name(self, node:'Node', suffix: str = "") -> str: - scope_path = node.get_global_type_name("__") - if scope_path is None: - # Unable to determine a reusable type name. Fall back to hierarchical path - # Add prefix to prevent collision when mixing namespace methods - scope_path = "xtern__" + super().get_typdef_name(node) - - if node.external: - # Node generates alternate external signals - extra_suffix = "__external" - else: - extra_suffix = "" - - return f'{scope_path}{extra_suffix}{suffix}__in_t' - -class OutputStructGenerator_TypeScope(OutputStructGenerator_Hier): - def get_typdef_name(self, node:'Node', suffix: str = "") -> str: - scope_path = node.get_global_type_name("__") - if scope_path is None: - # Unable to determine a reusable type name. Fall back to hierarchical path - # Add prefix to prevent collision when mixing namespace methods - scope_path = "xtern__" + super().get_typdef_name(node) - - if node.external: - # Node generates alternate external signals - extra_suffix = "__external" - else: - extra_suffix = "" - - return f'{scope_path}{extra_suffix}{suffix}__out_t' - -#------------------------------------------------------------------------------- -class EnumGenerator: - """ - Generator for user-defined enum definitions - """ - - def get_enums(self, user_enums: List[Type['UserEnum']]) -> Optional[str]: - if not user_enums: - return None - - lines = [] - for user_enum in user_enums: - lines.append(self._enum_typedef(user_enum)) - - return '\n\n'.join(lines) - - @staticmethod - def _get_prefix(user_enum: Type['UserEnum']) -> str: - scope = user_enum.get_scope_path("__") - if scope: - return f"{scope}__{user_enum.type_name}" - else: - return user_enum.type_name - - def _enum_typedef(self, user_enum: Type['UserEnum']) -> str: - prefix = self._get_prefix(user_enum) - - lines = [] - max_value = 1 - for enum_member in user_enum: - lines.append(f" {prefix}__{enum_member.name} = {SVInt(enum_member.value)}") - max_value = max(max_value, enum_member.value) - - if max_value.bit_length() == 1: - datatype = "logic" - else: - datatype = f"logic [{max_value.bit_length() - 1}:0]" - - return ( - f"typedef enum {datatype} {{\n" - + ",\n".join(lines) - + f"\n}} {prefix}_e;" - ) diff --git a/src/peakrdl_regblock/module_tmpl.sv b/src/peakrdl_regblock/module_tmpl.sv deleted file mode 100644 index 246ead1..0000000 --- a/src/peakrdl_regblock/module_tmpl.sv +++ /dev/null @@ -1,293 +0,0 @@ -// Generated by PeakRDL-busdecoder - A free and open-source SystemVerilog generator -// https://github.com/SystemRDL/PeakRDL-busdecoder - -module {{ds.module_name}} - {%- if cpuif.parameters %} #( - {{",\n ".join(cpuif.parameters)}} - ) {%- endif %} ( - input wire clk, - input wire {{default_resetsignal_name}}, - - {%- for signal in ds.out_of_hier_signals.values() %} - {%- if signal.width == 1 %} - input wire {{kwf(signal.inst_name)}}, - {%- else %} - input wire [{{signal.width-1}}:0] {{kwf(signal.inst_name)}}, - {%- endif %} - {%- endfor %} - - {%- if ds.has_paritycheck %} - - output logic parity_error, - {%- endif %} - - {{cpuif.port_declaration|indent(8)}} - {%- if hwif.has_input_struct or hwif.has_output_struct %},{% endif %} - - {{hwif.port_declaration|indent(8)}} - ); - - //-------------------------------------------------------------------------- - // CPU Bus interface logic - //-------------------------------------------------------------------------- - logic cpuif_req; - logic cpuif_req_is_wr; - logic [{{cpuif.addr_width-1}}:0] cpuif_addr; - logic [{{cpuif.data_width-1}}:0] cpuif_wr_data; - logic [{{cpuif.data_width-1}}:0] cpuif_wr_biten; - logic cpuif_req_stall_wr; - logic cpuif_req_stall_rd; - - logic cpuif_rd_ack; - logic cpuif_rd_err; - logic [{{cpuif.data_width-1}}:0] cpuif_rd_data; - - logic cpuif_wr_ack; - logic cpuif_wr_err; - - {{cpuif.get_implementation()|indent}} - - logic cpuif_req_masked; -{%- if ds.has_external_addressable %} - logic external_req; - logic external_pending; - logic external_wr_ack; - logic external_rd_ack; - always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - external_pending <= '0; - end else begin - if(external_req & ~external_wr_ack & ~external_rd_ack) external_pending <= '1; - else if(external_wr_ack | external_rd_ack) external_pending <= '0; - `ifndef SYNTHESIS - assert_bad_ext_wr_ack: assert(!external_wr_ack || (external_pending | external_req)) - else $error("An external wr_ack strobe was asserted when no external request was active"); - assert_bad_ext_rd_ack: assert(!external_rd_ack || (external_pending | external_req)) - else $error("An external rd_ack strobe was asserted when no external request was active"); - `endif - end - end -{%- endif %} -{% if ds.min_read_latency == ds.min_write_latency %} - // Read & write latencies are balanced. Stalls not required - {%- if ds.has_external_addressable %} - // except if external - assign cpuif_req_stall_rd = external_pending; - assign cpuif_req_stall_wr = external_pending; - {%- else %} - assign cpuif_req_stall_rd = '0; - assign cpuif_req_stall_wr = '0; - {%- endif %} -{%- elif ds.min_read_latency > ds.min_write_latency %} - // Read latency > write latency. May need to delay next write that follows a read - logic [{{ds.min_read_latency - ds.min_write_latency - 1}}:0] cpuif_req_stall_sr; - always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - cpuif_req_stall_sr <= '0; - end else if(cpuif_req && !cpuif_req_is_wr) begin - cpuif_req_stall_sr <= '1; - end else begin - cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1); - end - end - {%- if ds.has_external_addressable %} - assign cpuif_req_stall_rd = external_pending; - assign cpuif_req_stall_wr = cpuif_req_stall_sr[0] | external_pending; - {%- else %} - assign cpuif_req_stall_rd = '0; - assign cpuif_req_stall_wr = cpuif_req_stall_sr[0]; - {%- endif %} -{%- else %} - // Write latency > read latency. May need to delay next read that follows a write - logic [{{ds.min_write_latency - ds.min_read_latency - 1}}:0] cpuif_req_stall_sr; - always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - cpuif_req_stall_sr <= '0; - end else if(cpuif_req && cpuif_req_is_wr) begin - cpuif_req_stall_sr <= '1; - end else begin - cpuif_req_stall_sr <= (cpuif_req_stall_sr >> 'd1); - end - end - {%- if ds.has_external_addressable %} - assign cpuif_req_stall_rd = cpuif_req_stall_sr[0] | external_pending; - assign cpuif_req_stall_wr = external_pending; - {%- else %} - assign cpuif_req_stall_rd = cpuif_req_stall_sr[0]; - assign cpuif_req_stall_wr = '0; - {%- endif %} -{%- endif %} - assign cpuif_req_masked = cpuif_req - & !(!cpuif_req_is_wr & cpuif_req_stall_rd) - & !(cpuif_req_is_wr & cpuif_req_stall_wr); - - //-------------------------------------------------------------------------- - // Address Decode - //-------------------------------------------------------------------------- - {{address_decode.get_strobe_struct()|indent}} - decoded_reg_strb_t decoded_reg_strb; -{%- if ds.has_external_addressable %} - logic decoded_strb_is_external; -{% endif %} -{%- if ds.has_external_block %} - logic [{{cpuif.addr_width-1}}:0] decoded_addr; -{% endif %} - logic decoded_req; - logic decoded_req_is_wr; - logic [{{cpuif.data_width-1}}:0] decoded_wr_data; - logic [{{cpuif.data_width-1}}:0] decoded_wr_biten; - - always_comb begin - {%- if ds.has_external_addressable %} - automatic logic is_external; - is_external = '0; - {%- endif %} - {{address_decode.get_implementation()|indent(8)}} - {%- if ds.has_external_addressable %} - decoded_strb_is_external = is_external; - external_req = is_external; - {%- endif %} - end - - // Pass down signals to next stage -{%- if ds.has_external_block %} - assign decoded_addr = cpuif_addr; -{% endif %} - assign decoded_req = cpuif_req_masked; - assign decoded_req_is_wr = cpuif_req_is_wr; - assign decoded_wr_data = cpuif_wr_data; - assign decoded_wr_biten = cpuif_wr_biten; -{% if ds.has_writable_msb0_fields %} - // bitswap for use by fields with msb0 ordering - logic [{{cpuif.data_width-1}}:0] decoded_wr_data_bswap; - logic [{{cpuif.data_width-1}}:0] decoded_wr_biten_bswap; - assign decoded_wr_data_bswap = {<<{decoded_wr_data}}; - assign decoded_wr_biten_bswap = {<<{decoded_wr_biten}}; -{%- endif %} - -{%- if ds.has_buffered_write_regs %} - - //-------------------------------------------------------------------------- - // Write double-buffers - //-------------------------------------------------------------------------- - {{write_buffering.get_storage_struct()|indent}} - - {{write_buffering.get_implementation()|indent}} -{%- endif %} - //-------------------------------------------------------------------------- - // Field logic - //-------------------------------------------------------------------------- - {{field_logic.get_combo_struct()|indent}} - - {{field_logic.get_storage_struct()|indent}} - - {{field_logic.get_implementation()|indent}} - -{%- if ds.has_paritycheck %} - - //-------------------------------------------------------------------------- - // Parity Error - //-------------------------------------------------------------------------- - always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - parity_error <= '0; - end else begin - automatic logic err; - err = '0; - {{parity.get_implementation()|indent(12)}} - parity_error <= err; - end - end -{%- endif %} - -{%- if ds.has_buffered_read_regs %} - - //-------------------------------------------------------------------------- - // Read double-buffers - //-------------------------------------------------------------------------- - {{read_buffering.get_storage_struct()|indent}} - - {{read_buffering.get_implementation()|indent}} -{%- endif %} - - //-------------------------------------------------------------------------- - // Write response - //-------------------------------------------------------------------------- -{%- if ds.has_external_addressable %} - always_comb begin - automatic logic wr_ack; - wr_ack = '0; - {{ext_write_acks.get_implementation()|indent(8)}} - external_wr_ack = wr_ack; - end - assign cpuif_wr_ack = external_wr_ack | (decoded_req & decoded_req_is_wr & ~decoded_strb_is_external); -{%- else %} - assign cpuif_wr_ack = decoded_req & decoded_req_is_wr; -{%- endif %} - // Writes are always granted with no error response - assign cpuif_wr_err = '0; - - //-------------------------------------------------------------------------- - // Readback - //-------------------------------------------------------------------------- -{%- if ds.has_external_addressable %} - logic readback_external_rd_ack_c; - always_comb begin - automatic logic rd_ack; - rd_ack = '0; - {{ext_read_acks.get_implementation()|indent(8)}} - readback_external_rd_ack_c = rd_ack; - end - - logic readback_external_rd_ack; - {%- if ds.retime_read_fanin %} - always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - readback_external_rd_ack <= '0; - end else begin - readback_external_rd_ack <= readback_external_rd_ack_c; - end - end - - {%- else %} - - assign readback_external_rd_ack = readback_external_rd_ack_c; - {%- endif %} -{%- endif %} - - logic readback_err; - logic readback_done; - logic [{{cpuif.data_width-1}}:0] readback_data; -{{readback_implementation|indent}} -{% if ds.retime_read_response %} - always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - cpuif_rd_ack <= '0; - cpuif_rd_data <= '0; - cpuif_rd_err <= '0; - {%- if ds.has_external_addressable %} - external_rd_ack <= '0; - {%- endif %} - end else begin - {%- if ds.has_external_addressable %} - external_rd_ack <= readback_external_rd_ack; - cpuif_rd_ack <= readback_done | readback_external_rd_ack; - {%- else %} - cpuif_rd_ack <= readback_done; - {%- endif %} - cpuif_rd_data <= readback_data; - cpuif_rd_err <= readback_err; - end - end -{% else %} - {%- if ds.has_external_addressable %} - assign external_rd_ack = readback_external_rd_ack; - assign cpuif_rd_ack = readback_done | readback_external_rd_ack; - {%- else %} - assign cpuif_rd_ack = readback_done; - {%- endif %} - assign cpuif_rd_data = readback_data; - assign cpuif_rd_err = readback_err; -{%- endif %} -endmodule -{# (eof newline anchor) #} diff --git a/src/peakrdl_regblock/parity.py b/src/peakrdl_regblock/parity.py deleted file mode 100644 index 7687423..0000000 --- a/src/peakrdl_regblock/parity.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import TYPE_CHECKING - -from systemrdl.walker import WalkerAction - - -from .forloop_generator import RDLForLoopGenerator - -if TYPE_CHECKING: - from .exporter import BusDecoderExporter - from systemrdl.node import FieldNode, AddressableNode - - -class ParityErrorReduceGenerator(RDLForLoopGenerator): - def __init__(self, exp: "BusDecoderExporter") -> None: - super().__init__() - self.exp = exp - - def get_implementation(self) -> str: - content = self.get_content(self.exp.ds.top_node) - if content is None: - return "" - return content - - def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction: - super().enter_AddressableComponent(node) - if node.external: - return WalkerAction.SkipDescendants - return WalkerAction.Continue - - def enter_Field(self, node: "FieldNode") -> None: - if node.get_property("paritycheck") and node.implements_storage: - self.add_content( - f"err |= {self.exp.field_logic.get_parity_error_identifier(node)};" - ) diff --git a/src/peakrdl_regblock/read_buffering/__init__.py b/src/peakrdl_regblock/read_buffering/__init__.py deleted file mode 100644 index d6554c4..0000000 --- a/src/peakrdl_regblock/read_buffering/__init__.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import TYPE_CHECKING, Union - -from systemrdl.node import AddrmapNode, RegNode, SignalNode - -from .storage_generator import RBufStorageStructGenerator -from .implementation_generator import RBufLogicGenerator -from ..utils import get_indexed_path -from ..sv_int import SVInt - -if TYPE_CHECKING: - from ..exporter import BusDecoderExporter - - -class ReadBuffering: - def __init__(self, exp: "BusDecoderExporter"): - self.exp = exp - - @property - def top_node(self) -> "AddrmapNode": - return self.exp.ds.top_node - - def get_storage_struct(self) -> str: - struct_gen = RBufStorageStructGenerator() - s = struct_gen.get_struct(self.top_node, "rbuf_storage_t") - assert s is not None - return s + "\nrbuf_storage_t rbuf_storage;" - - def get_implementation(self) -> str: - gen = RBufLogicGenerator(self) - s = gen.get_content(self.top_node) - assert s is not None - return s - - def get_trigger(self, node: RegNode) -> str: - trigger = node.get_property("rbuffer_trigger") - - if isinstance(trigger, RegNode): - # Trigger is a register. - # trigger when lowermost address of the register is written - regwidth = trigger.get_property("regwidth") - accesswidth = trigger.get_property("accesswidth") - strb_prefix = self.exp.dereferencer.get_access_strobe( - trigger, reduce_substrobes=False - ) - - if accesswidth < regwidth: - return f"{strb_prefix}[0] && !decoded_req_is_wr" - else: - return f"{strb_prefix} && !decoded_req_is_wr" - elif isinstance(trigger, SignalNode): - s = self.exp.dereferencer.get_value(trigger) - if trigger.get_property("activehigh"): - return str(s) - else: - return f"~{s}" - else: - # Trigger is a field or propref bit - return str(self.exp.dereferencer.get_value(trigger)) - - def get_rbuf_data(self, node: RegNode) -> str: - return "rbuf_storage." + get_indexed_path(self.top_node, node) + ".data" diff --git a/src/peakrdl_regblock/read_buffering/implementation_generator.py b/src/peakrdl_regblock/read_buffering/implementation_generator.py deleted file mode 100644 index ea36d8b..0000000 --- a/src/peakrdl_regblock/read_buffering/implementation_generator.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import TYPE_CHECKING - -from systemrdl.component import Reg -from systemrdl.node import RegNode - -from ..forloop_generator import RDLForLoopGenerator - -if TYPE_CHECKING: - from . import ReadBuffering - -class RBufLogicGenerator(RDLForLoopGenerator): - i_type = "genvar" - def __init__(self, rbuf: 'ReadBuffering') -> None: - super().__init__() - self.rbuf = rbuf - self.exp = rbuf.exp - self.template = self.exp.jj_env.get_template( - "read_buffering/template.sv" - ) - - def enter_Reg(self, node: RegNode) -> None: - super().enter_Reg(node) - assert isinstance(node.inst, Reg) - - if not node.get_property('buffer_reads'): - return - - context = { - 'node': node, - 'rbuf': self.rbuf, - 'get_assignments': self.get_assignments, - } - self.add_content(self.template.render(context)) - - - - def get_assignments(self, node: RegNode) -> str: - data = self.rbuf.get_rbuf_data(node) - bidx = 0 - s = [] - for field in node.fields(): - if bidx < field.low: - # zero padding before field - s.append(f"{data}[{field.low-1}:{bidx}] <= '0;") - - value = self.exp.dereferencer.get_value(field) - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - value = f"{{<<{{{value}}}}}" - s.append(f"{data}[{field.high}:{field.low}] <= {value};") - - bidx = field.high + 1 - - regwidth = node.get_property('regwidth') - if bidx < regwidth: - # zero padding after last field - s.append(f"{data}[{regwidth-1}:{bidx}] <= '0;") - - return "\n".join(s) diff --git a/src/peakrdl_regblock/read_buffering/storage_generator.py b/src/peakrdl_regblock/read_buffering/storage_generator.py deleted file mode 100644 index c6a6d6d..0000000 --- a/src/peakrdl_regblock/read_buffering/storage_generator.py +++ /dev/null @@ -1,18 +0,0 @@ -from systemrdl.node import FieldNode, RegNode - -from ..struct_generator import RDLStructGenerator - -class RBufStorageStructGenerator(RDLStructGenerator): - - def enter_Field(self, node: FieldNode) -> None: - # suppress parent class's field behavior - pass - - def enter_Reg(self, node: RegNode) -> None: - super().enter_Reg(node) - - if not node.get_property('buffer_reads'): - return - - regwidth = node.get_property('regwidth') - self.add_member("data", regwidth) diff --git a/src/peakrdl_regblock/read_buffering/template.sv b/src/peakrdl_regblock/read_buffering/template.sv deleted file mode 100644 index a97b351..0000000 --- a/src/peakrdl_regblock/read_buffering/template.sv +++ /dev/null @@ -1,5 +0,0 @@ -always_ff @(posedge clk) begin - if({{rbuf.get_trigger(node)}}) begin - {{get_assignments(node)|indent(8)}} - end -end diff --git a/src/peakrdl_regblock/readback/__init__.py b/src/peakrdl_regblock/readback/__init__.py deleted file mode 100644 index 5e24d3b..0000000 --- a/src/peakrdl_regblock/readback/__init__.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import TYPE_CHECKING -import math - -from .generators import ReadbackAssignmentGenerator - -if TYPE_CHECKING: - from ..exporter import BusDecoderExporter, DesignState - from systemrdl.node import AddrmapNode - - -class Readback: - def __init__(self, exp: "BusDecoderExporter"): - self.exp = exp - - @property - def ds(self) -> "DesignState": - return self.exp.ds - - @property - def top_node(self) -> "AddrmapNode": - return self.exp.ds.top_node - - def get_implementation(self) -> str: - gen = ReadbackAssignmentGenerator(self.exp) - array_assignments = gen.get_content(self.top_node) - array_size = gen.current_offset - - # Enabling the fanin stage doesnt make sense if readback fanin is - # small. This also avoids pesky corner cases - if array_size < 4: - self.ds.retime_read_fanin = False - - context = { - "array_assignments": array_assignments, - "array_size": array_size, - "get_always_ff_event": self.exp.dereferencer.get_always_ff_event, - "get_resetsignal": self.exp.dereferencer.get_resetsignal, - "cpuif": self.exp.cpuif, - "ds": self.ds, - } - - if self.ds.retime_read_fanin: - # If adding a fanin pipeline stage, goal is to try to - # split the fanin path in the middle so that fanin into the stage - # and the following are roughly balanced. - fanin_target = math.sqrt(array_size) - - # Size of fanin group to consume per fanin element - fanin_stride = math.floor(fanin_target) - - # Number of array elements to reduce to. - # Round up to an extra element in case there is some residual - fanin_array_size = math.ceil(array_size / fanin_stride) - - # leftovers are handled in an extra array element - fanin_residual_stride = array_size % fanin_stride - - if fanin_residual_stride != 0: - # If there is a partial fanin element, reduce the number of - # loops performed in the bulk fanin stage - fanin_loop_iter = fanin_array_size - 1 - else: - fanin_loop_iter = fanin_array_size - - context["fanin_stride"] = fanin_stride - context["fanin_array_size"] = fanin_array_size - context["fanin_residual_stride"] = fanin_residual_stride - context["fanin_loop_iter"] = fanin_loop_iter - - template = self.exp.jj_env.get_template("readback/templates/readback.sv") - return template.render(context) diff --git a/src/peakrdl_regblock/readback/generators.py b/src/peakrdl_regblock/readback/generators.py deleted file mode 100644 index 7bfa1f3..0000000 --- a/src/peakrdl_regblock/readback/generators.py +++ /dev/null @@ -1,449 +0,0 @@ -from typing import TYPE_CHECKING, List - -from systemrdl.node import RegNode, AddressableNode -from systemrdl.walker import WalkerAction - -from ..forloop_generator import RDLForLoopGenerator, LoopBody - -from ..utils import do_bitswap, do_slice - -if TYPE_CHECKING: - from ..exporter import BusDecoderExporter - - -class ReadbackLoopBody(LoopBody): - def __init__(self, dim: int, iterator: str, i_type: str) -> None: - super().__init__(dim, iterator, i_type) - self.n_regs = 0 - - def __str__(self) -> str: - # replace $i#sz token when stringifying - s = super().__str__() - token = f"${self.iterator}sz" - s = s.replace(token, str(self.n_regs)) - return s - - -class ReadbackAssignmentGenerator(RDLForLoopGenerator): - i_type = "genvar" - loop_body_cls = ReadbackLoopBody - - def __init__(self, exp: "BusDecoderExporter") -> None: - super().__init__() - self.exp = exp - - # The readback array collects all possible readback values into a flat - # array. The array width is equal to the CPUIF bus width. Each entry in - # the array represents an aligned read access. - self.current_offset = 0 - self.start_offset_stack = [] # type: List[int] - self.dim_stack = [] # type: List[int] - - @property - def current_offset_str(self) -> str: - """ - Derive a string that represents the current offset being assigned. - This consists of: - - The current integer offset - - multiplied index of any enclosing loop - - The integer offset from "current_offset" is static and is monotonically - incremented as more register assignments are processed. - - The component of the offset from loops is added by multiplying the current - loop index by the loop size. - Since the loop's size is not known at this time, it is emitted as a - placeholder token like: $i0sz, $i1sz, $i2sz, etc - These tokens can be replaced once the loop body has been completed and the - size of its contents is known. - """ - offset_parts = [] - for i in range(self._loop_level): - offset_parts.append(f"i{i} * $i{i}sz") - offset_parts.append(str(self.current_offset)) - return " + ".join(offset_parts) - - def push_loop(self, dim: int) -> None: - super().push_loop(dim) - self.start_offset_stack.append(self.current_offset) - self.dim_stack.append(dim) - - def pop_loop(self) -> None: - start_offset = self.start_offset_stack.pop() - dim = self.dim_stack.pop() - - # Number of registers enclosed in this loop - n_regs = self.current_offset - start_offset - self.current_loop.n_regs = n_regs # type: ignore - - super().pop_loop() - - # Advance current scope's offset to account for loop's contents - self.current_offset = start_offset + n_regs * dim - - def enter_AddressableComponent(self, node: "AddressableNode") -> WalkerAction: - super().enter_AddressableComponent(node) - - if node.external and not isinstance(node, RegNode): - # External block - strb = self.exp.hwif.get_external_rd_ack(node) - data = self.exp.hwif.get_external_rd_data(node) - self.add_content( - f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;" - ) - self.current_offset += 1 - return WalkerAction.SkipDescendants - - return WalkerAction.Continue - - def enter_Reg(self, node: RegNode) -> WalkerAction: - if not node.has_sw_readable: - return WalkerAction.SkipDescendants - - if node.external: - self.process_external_reg(node) - return WalkerAction.SkipDescendants - - accesswidth = node.get_property("accesswidth") - regwidth = node.get_property("regwidth") - rbuf = node.get_property("buffer_reads") - if rbuf: - trigger = node.get_property("rbuffer_trigger") - is_own_trigger = isinstance(trigger, RegNode) and trigger == node - if is_own_trigger: - if accesswidth < regwidth: - self.process_buffered_reg_with_bypass(node, regwidth, accesswidth) - else: - # bypass cancels out. Behaves like a normal reg - self.process_reg(node) - else: - self.process_buffered_reg(node, regwidth, accesswidth) - elif accesswidth < regwidth: - self.process_wide_reg(node, accesswidth) - else: - self.process_reg(node) - - return WalkerAction.SkipDescendants - - def process_external_reg(self, node: RegNode) -> None: - strb = self.exp.hwif.get_external_rd_ack(node) - data = self.exp.hwif.get_external_rd_data(node) - regwidth = node.get_property("regwidth") - if regwidth < self.exp.cpuif.data_width: - self.add_content( - f"assign readback_array[{self.current_offset_str}][{self.exp.cpuif.data_width - 1}:{regwidth}] = '0;" - ) - self.add_content( - f"assign readback_array[{self.current_offset_str}][{regwidth - 1}:0] = {strb} ? {data} : '0;" - ) - else: - self.add_content( - f"assign readback_array[{self.current_offset_str}] = {strb} ? {data} : '0;" - ) - - self.current_offset += 1 - - def process_reg(self, node: RegNode) -> None: - current_bit = 0 - rd_strb = ( - f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)" - ) - # Fields are sorted by ascending low bit - for field in node.fields(): - if not field.is_sw_readable: - continue - - # insert reserved assignment before this field if needed - if field.low != current_bit: - self.add_content( - f"assign readback_array[{self.current_offset_str}][{field.low - 1}:{current_bit}] = '0;" - ) - - value = self.exp.dereferencer.get_value(field) - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - value = do_bitswap(value) - - self.add_content( - f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;" - ) - - current_bit = field.high + 1 - - # Insert final reserved assignment if needed - bus_width = self.exp.cpuif.data_width - if current_bit < bus_width: - self.add_content( - f"assign readback_array[{self.current_offset_str}][{bus_width - 1}:{current_bit}] = '0;" - ) - - self.current_offset += 1 - - def process_buffered_reg( - self, node: RegNode, regwidth: int, accesswidth: int - ) -> None: - rbuf = self.exp.read_buffering.get_rbuf_data(node) - - if accesswidth < regwidth: - # Is wide reg - n_subwords = regwidth // accesswidth - astrb = self.exp.dereferencer.get_access_strobe( - node, reduce_substrobes=False - ) - for i in range(n_subwords): - rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)" - bslice = f"[{(i + 1) * accesswidth - 1}:{i * accesswidth}]" - self.add_content( - f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;" - ) - self.current_offset += 1 - - else: - # Is regular reg - rd_strb = f"({self.exp.dereferencer.get_access_strobe(node)} && !decoded_req_is_wr)" - self.add_content( - f"assign readback_array[{self.current_offset_str}][{regwidth - 1}:0] = {rd_strb} ? {rbuf} : '0;" - ) - - bus_width = self.exp.cpuif.data_width - if regwidth < bus_width: - self.add_content( - f"assign readback_array[{self.current_offset_str}][{bus_width - 1}:{regwidth}] = '0;" - ) - - self.current_offset += 1 - - def process_buffered_reg_with_bypass( - self, node: RegNode, regwidth: int, accesswidth: int - ) -> None: - """ - Special case for a buffered register when the register is its own trigger. - First sub-word shall bypass the read buffer and assign directly. - Subsequent subwords assign from the buffer. - Caller guarantees this is a wide reg - """ - astrb = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False) - - # Generate assignments for first sub-word - bidx = 0 - rd_strb = f"({astrb}[0] && !decoded_req_is_wr)" - for field in node.fields(): - if not field.is_sw_readable: - continue - - if field.low >= accesswidth: - # field is not in this subword. - break - - if bidx < field.low: - # insert padding before - self.add_content( - f"assign readback_array[{self.current_offset_str}][{field.low - 1}:{bidx}] = '0;" - ) - - if field.high >= accesswidth: - # field gets truncated - r_low = field.low - r_high = accesswidth - 1 - f_low = 0 - f_high = accesswidth - 1 - field.low - - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - # Mirror the low/high indexes - f_low = field.width - 1 - f_low - f_high = field.width - 1 - f_high - f_low, f_high = f_high, f_low - value = do_bitswap( - do_slice(self.exp.dereferencer.get_value(field), f_high, f_low) - ) - else: - value = do_slice( - self.exp.dereferencer.get_value(field), f_high, f_low - ) - - self.add_content( - f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;" - ) - bidx = accesswidth - else: - # field fits in subword - value = self.exp.dereferencer.get_value(field) - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - value = do_bitswap(value) - self.add_content( - f"assign readback_array[{self.current_offset_str}][{field.high}:{field.low}] = {rd_strb} ? {value} : '0;" - ) - bidx = field.high + 1 - - # pad up remainder of subword - if bidx < accesswidth: - self.add_content( - f"assign readback_array[{self.current_offset_str}][{accesswidth - 1}:{bidx}] = '0;" - ) - self.current_offset += 1 - - # Assign remainder of subwords from read buffer - n_subwords = regwidth // accesswidth - rbuf = self.exp.read_buffering.get_rbuf_data(node) - for i in range(1, n_subwords): - rd_strb = f"({astrb}[{i}] && !decoded_req_is_wr)" - bslice = f"[{(i + 1) * accesswidth - 1}:{i * accesswidth}]" - self.add_content( - f"assign readback_array[{self.current_offset_str}] = {rd_strb} ? {rbuf}{bslice} : '0;" - ) - self.current_offset += 1 - - def process_wide_reg(self, node: RegNode, accesswidth: int) -> None: - bus_width = self.exp.cpuif.data_width - - subword_idx = 0 - current_bit = 0 # Bit-offset within the wide register - access_strb = self.exp.dereferencer.get_access_strobe( - node, reduce_substrobes=False - ) - # Fields are sorted by ascending low bit - for field in node.fields(): - if not field.is_sw_readable: - continue - - # insert zero assignment before this field if needed - if field.low >= accesswidth * (subword_idx + 1): - # field does not start in this subword - if current_bit > accesswidth * subword_idx: - # current subword had content. Assign remainder - low = current_bit % accesswidth - high = bus_width - 1 - self.add_content( - f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;" - ) - self.current_offset += 1 - - # Advance to subword that contains the start of the field - subword_idx = field.low // accesswidth - current_bit = accesswidth * subword_idx - - if current_bit != field.low: - # assign zero up to start of this field - low = current_bit % accesswidth - high = (field.low % accesswidth) - 1 - self.add_content( - f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;" - ) - current_bit = field.low - - # Assign field - # loop until the entire field's assignments have been generated - field_pos = field.low - while current_bit <= field.high: - # Assign the field - rd_strb = f"({access_strb}[{subword_idx}] && !decoded_req_is_wr)" - if (field_pos == field.low) and ( - field.high < accesswidth * (subword_idx + 1) - ): - # entire field fits into this subword - low = field.low - accesswidth * subword_idx - high = field.high - accesswidth * subword_idx - - value = self.exp.dereferencer.get_value(field) - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - value = do_bitswap(value) - - self.add_content( - f"assign readback_array[{self.current_offset_str}][{high}:{low}] = {rd_strb} ? {value} : '0;" - ) - - current_bit = field.high + 1 - - if current_bit == accesswidth * (subword_idx + 1): - # Field ends at the subword boundary - subword_idx += 1 - self.current_offset += 1 - elif field.high >= accesswidth * (subword_idx + 1): - # only a subset of the field can fit into this subword - # high end gets truncated - - # assignment slice - r_low = field_pos - accesswidth * subword_idx - r_high = accesswidth - 1 - - # field slice - f_low = field_pos - field.low - f_high = accesswidth * (subword_idx + 1) - 1 - field.low - - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - # Mirror the low/high indexes - f_low = field.width - 1 - f_low - f_high = field.width - 1 - f_high - f_low, f_high = f_high, f_low - - value = do_bitswap( - do_slice( - self.exp.dereferencer.get_value(field), f_high, f_low - ) - ) - else: - value = do_slice( - self.exp.dereferencer.get_value(field), f_high, f_low - ) - - self.add_content( - f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;" - ) - - # advance to the next subword - subword_idx += 1 - current_bit = accesswidth * subword_idx - field_pos = current_bit - self.current_offset += 1 - else: - # only a subset of the field can fit into this subword - # finish field - - # assignment slice - r_low = field_pos - accesswidth * subword_idx - r_high = field.high - accesswidth * subword_idx - - # field slice - f_low = field_pos - field.low - f_high = field.high - field.low - - if field.msb < field.lsb: - # Field gets bitswapped since it is in [low:high] orientation - # Mirror the low/high indexes - f_low = field.width - 1 - f_low - f_high = field.width - 1 - f_high - f_low, f_high = f_high, f_low - - value = do_bitswap( - do_slice( - self.exp.dereferencer.get_value(field), f_high, f_low - ) - ) - else: - value = do_slice( - self.exp.dereferencer.get_value(field), f_high, f_low - ) - - self.add_content( - f"assign readback_array[{self.current_offset_str}][{r_high}:{r_low}] = {rd_strb} ? {value} : '0;" - ) - - current_bit = field.high + 1 - if current_bit == accesswidth * (subword_idx + 1): - # Field ends at the subword boundary - subword_idx += 1 - self.current_offset += 1 - - # insert zero assignment after the last field if needed - if current_bit > accesswidth * subword_idx: - # current subword had content. Assign remainder - low = current_bit % accesswidth - high = bus_width - 1 - self.add_content( - f"assign readback_array[{self.current_offset_str}][{high}:{low}] = '0;" - ) - self.current_offset += 1 diff --git a/src/peakrdl_regblock/readback/templates/readback.sv b/src/peakrdl_regblock/readback/templates/readback.sv deleted file mode 100644 index e44c9ed..0000000 --- a/src/peakrdl_regblock/readback/templates/readback.sv +++ /dev/null @@ -1,79 +0,0 @@ -{% if array_assignments is not none %} -// Assign readback values to a flattened array -logic [{{cpuif.data_width-1}}:0] readback_array[{{array_size}}]; -{{array_assignments}} - - -{%- if ds.retime_read_fanin %} - -// fanin stage -logic [{{cpuif.data_width-1}}:0] readback_array_c[{{fanin_array_size}}]; -for(genvar g=0; g<{{fanin_loop_iter}}; g++) begin - always_comb begin - automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; - readback_data_var = '0; - for(int i=g*{{fanin_stride}}; i<((g+1)*{{fanin_stride}}); i++) readback_data_var |= readback_array[i]; - readback_array_c[g] = readback_data_var; - end -end -{%- if fanin_residual_stride == 1 %} -assign readback_array_c[{{fanin_array_size-1}}] = readback_array[{{array_size-1}}]; -{%- elif fanin_residual_stride > 1 %} -always_comb begin - automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; - readback_data_var = '0; - for(int i={{(fanin_array_size-1) * fanin_stride}}; i<{{array_size}}; i++) readback_data_var |= readback_array[i]; - readback_array_c[{{fanin_array_size-1}}] = readback_data_var; -end -{%- endif %} - -logic [{{cpuif.data_width-1}}:0] readback_array_r[{{fanin_array_size}}]; -logic readback_done_r; -always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - for(int i=0; i<{{fanin_array_size}}; i++) readback_array_r[i] <= '0; - readback_done_r <= '0; - end else begin - readback_array_r <= readback_array_c; - {%- if ds.has_external_addressable %} - readback_done_r <= decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external; - {%- else %} - readback_done_r <= decoded_req & ~decoded_req_is_wr; - {%- endif %} - end -end - -// Reduce the array -always_comb begin - automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; - readback_done = readback_done_r; - readback_err = '0; - readback_data_var = '0; - for(int i=0; i<{{fanin_array_size}}; i++) readback_data_var |= readback_array_r[i]; - readback_data = readback_data_var; -end - -{%- else %} - -// Reduce the array -always_comb begin - automatic logic [{{cpuif.data_width-1}}:0] readback_data_var; - {%- if ds.has_external_addressable %} - readback_done = decoded_req & ~decoded_req_is_wr & ~decoded_strb_is_external; - {%- else %} - readback_done = decoded_req & ~decoded_req_is_wr; - {%- endif %} - readback_err = '0; - readback_data_var = '0; - for(int i=0; i<{{array_size}}; i++) readback_data_var |= readback_array[i]; - readback_data = readback_data_var; -end -{%- endif %} - - - -{%- else %} -assign readback_done = decoded_req & ~decoded_req_is_wr; -assign readback_data = '0; -assign readback_err = '0; -{% endif %} diff --git a/src/peakrdl_regblock/scan_design.py b/src/peakrdl_regblock/scan_design.py deleted file mode 100644 index 785b34f..0000000 --- a/src/peakrdl_regblock/scan_design.py +++ /dev/null @@ -1,126 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -from systemrdl.walker import RDLListener, RDLWalker, WalkerAction -from systemrdl.node import SignalNode, RegNode - -if TYPE_CHECKING: - from systemrdl.node import Node, FieldNode, AddressableNode, AddrmapNode - from .exporter import DesignState - - -class DesignScanner(RDLListener): - """ - Scans through the register model and validates that any unsupported features - are not present. - - Also collects any information that is required prior to the start of the export process. - """ - - def __init__(self, ds: "DesignState") -> None: - self.ds = ds - self.msg = self.top_node.env.msg - - @property - def top_node(self) -> "AddrmapNode": - return self.ds.top_node - - def _get_out_of_hier_field_reset(self) -> None: - current_node: Optional[Node] - current_node = self.top_node.parent - while current_node is not None: - for signal in current_node.signals(): - if signal.get_property("field_reset"): - path = signal.get_path() - self.ds.out_of_hier_signals[path] = signal - return - current_node = current_node.parent - - def do_scan(self) -> None: - # Collect cpuif reset, if any. - cpuif_reset = self.top_node.cpuif_reset - if cpuif_reset is not None: - path = cpuif_reset.get_path() - rel_path = cpuif_reset.get_rel_path(self.top_node) - if rel_path.startswith("^"): - self.ds.out_of_hier_signals[path] = cpuif_reset - else: - self.ds.in_hier_signal_paths.add(path) - - # collect out-of-hier field_reset, if any - self._get_out_of_hier_field_reset() - - # Ensure addrmap is not a bridge. This concept does not make sense for - # terminal components. - if self.top_node.get_property("bridge"): - self.msg.error( - "BusDecoder generator does not support exporting bridge address maps", - self.top_node.inst.property_src_ref.get( - "bridge", self.top_node.inst.inst_src_ref - ), - ) - - RDLWalker().walk(self.top_node, self) - if self.msg.had_error: - self.msg.fatal("Unable to export due to previous errors") - - def enter_Component(self, node: "Node") -> Optional[WalkerAction]: - if node.external and (node != self.top_node): - # Do not inspect external components. None of my business - return WalkerAction.SkipDescendants - - # Collect any signals that are referenced by a property - for prop_name in node.list_properties(): - value = node.get_property(prop_name) - if isinstance(value, SignalNode): - path = value.get_path() - rel_path = value.get_rel_path(self.top_node) - if rel_path.startswith("^"): - self.ds.out_of_hier_signals[path] = value - else: - self.ds.in_hier_signal_paths.add(path) - - if prop_name == "encode": - if value not in self.ds.user_enums: - self.ds.user_enums.append(value) - - return WalkerAction.Continue - - def enter_AddressableComponent(self, node: "AddressableNode") -> None: - if node.external and node != self.top_node: - self.ds.has_external_addressable = True - if not isinstance(node, RegNode): - self.ds.has_external_block = True - - def enter_Reg(self, node: "RegNode") -> None: - # The CPUIF's bus width is sized according to the largest accesswidth in the design - accesswidth = node.get_property("accesswidth") - self.ds.cpuif_data_width = max(self.ds.cpuif_data_width, accesswidth) - - self.ds.has_buffered_write_regs = self.ds.has_buffered_write_regs or bool( - node.get_property("buffer_writes") - ) - self.ds.has_buffered_read_regs = self.ds.has_buffered_read_regs or bool( - node.get_property("buffer_reads") - ) - - def enter_Signal(self, node: "SignalNode") -> None: - if node.get_property("field_reset"): - path = node.get_path() - self.ds.in_hier_signal_paths.add(path) - - def enter_Field(self, node: "FieldNode") -> None: - if node.is_sw_writable and (node.msb < node.lsb): - self.ds.has_writable_msb0_fields = True - - if node.get_property("paritycheck") and node.implements_storage: - self.ds.has_paritycheck = True - - if node.get_property("reset") is None: - self.msg.warning( - f"Field '{node.inst_name}' includes parity check logic, but " - "its reset value was not defined. Will result in an undefined " - "value on the module's 'parity_error' output.", - self.top_node.inst.property_src_ref.get( - "paritycheck", self.top_node.inst.inst_src_ref - ), - ) diff --git a/src/peakrdl_regblock/struct_generator.py b/src/peakrdl_regblock/struct_generator.py deleted file mode 100644 index 50dcf48..0000000 --- a/src/peakrdl_regblock/struct_generator.py +++ /dev/null @@ -1,292 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List -import textwrap -from collections import OrderedDict - -from systemrdl.walker import RDLListener, RDLWalker, WalkerAction - -from .identifier_filter import kw_filter as kwf - -if TYPE_CHECKING: - from typing import Union - - from systemrdl.node import AddrmapNode, RegfileNode, RegNode, FieldNode, Node, MemNode - - -class _StructBase: - def __init__(self) -> None: - self.children = [] # type: List[Union[str, _StructBase]] - - def __str__(self) -> str: - s = '\n'.join((str(x) for x in self.children)) - return textwrap.indent(s, " ") - - -class _AnonymousStruct(_StructBase): - def __init__(self, inst_name: str, array_dimensions: Optional[List[int]] = None): - super().__init__() - self.inst_name = inst_name - self.array_dimensions = array_dimensions - - def __str__(self) -> str: - if self.array_dimensions: - suffix = "[" + "][".join((str(n) for n in self.array_dimensions)) + "]" - else: - suffix = "" - - return ( - "struct {\n" - + super().__str__() - + f"\n}} {self.inst_name}{suffix};" - ) - - -class _TypedefStruct(_StructBase): - def __init__(self, type_name: str, inst_name: Optional[str] = None, array_dimensions: Optional[List[int]] = None, packed: bool = False): - super().__init__() - self.type_name = type_name - self.inst_name = inst_name - self.array_dimensions = array_dimensions - self.packed = packed - - def __str__(self) -> str: - if self.packed: - return ( - "typedef struct packed {\n" - + super().__str__() - + f"\n}} {self.type_name};" - ) - else: - return ( - "typedef struct {\n" - + super().__str__() - + f"\n}} {self.type_name};" - ) - - @property - def instantiation(self) -> str: - if self.array_dimensions: - suffix = "[" + "][".join((str(n) for n in self.array_dimensions)) + "]" - else: - suffix = "" - - return f"{self.type_name} {self.inst_name}{suffix};" - -#------------------------------------------------------------------------------- - -class StructGenerator: - - def __init__(self) -> None: - self._struct_stack = [] # type: List[_StructBase] - - @property - def current_struct(self) -> _StructBase: - return self._struct_stack[-1] - - - def push_struct(self, inst_name: str, array_dimensions: Optional[List[int]] = None) -> None: - s = _AnonymousStruct(inst_name, array_dimensions) - self._struct_stack.append(s) - - - def add_member( - self, - name: str, - width: int = 1, - array_dimensions: Optional[List[int]] = None, - *, - lsb: int = 0, - signed: bool = False, - ) -> None: - if array_dimensions: - suffix = "[" + "][".join((str(n) for n in array_dimensions)) + "]" - else: - suffix = "" - - if signed: - sign = "signed " - else: - # the default 'logic' type is unsigned per SV LRM 6.11.3 - sign = "" - - if width == 1 and lsb == 0: - m = f"logic {sign}{name}{suffix};" - else: - m = f"logic {sign}[{lsb+width-1}:{lsb}] {name}{suffix};" - self.current_struct.children.append(m) - - - def pop_struct(self) -> None: - s = self._struct_stack.pop() - - if s.children: - # struct is not empty. Attach it to the parent - self.current_struct.children.append(s) - - - def start(self, type_name: str) -> None: - assert not self._struct_stack - s = _TypedefStruct(type_name) - self._struct_stack.append(s) - - def finish(self) -> Optional[str]: - s = self._struct_stack.pop() - assert not self._struct_stack - - if not s.children: - return None - return str(s) - - -class RDLStructGenerator(StructGenerator, RDLListener): - """ - Struct generator that naively translates an RDL node tree into a single - struct typedef containing nested anonymous structs - - This can be extended to add more intelligent behavior - """ - - def get_struct(self, node: 'Node', type_name: str) -> Optional[str]: - self.start(type_name) - - walker = RDLWalker() - walker.walk(node, self, skip_top=True) - - return self.finish() - - - def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]: - self.push_struct(kwf(node.inst_name), node.array_dimensions) - return WalkerAction.Continue - - def exit_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]: - self.pop_struct() - return WalkerAction.Continue - - def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]: - self.push_struct(kwf(node.inst_name), node.array_dimensions) - return WalkerAction.Continue - - def exit_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]: - self.pop_struct() - return WalkerAction.Continue - - def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: - self.push_struct(kwf(node.inst_name), node.array_dimensions) - return WalkerAction.Continue - - def exit_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: - self.pop_struct() - return WalkerAction.Continue - - def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: - self.push_struct(kwf(node.inst_name), node.array_dimensions) - return WalkerAction.Continue - - def exit_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: - self.pop_struct() - return WalkerAction.Continue - - def enter_Field(self, node: 'FieldNode') -> Optional[WalkerAction]: - self.add_member(kwf(node.inst_name), node.width) - return WalkerAction.Continue - -#------------------------------------------------------------------------------- - -class FlatStructGenerator(StructGenerator): - - def __init__(self) -> None: - super().__init__() - self.typedefs = OrderedDict() # type: OrderedDict[str, _TypedefStruct] - - def push_struct(self, type_name: str, inst_name: str, array_dimensions: Optional[List[int]] = None, packed = False) -> None: # type: ignore # pylint: disable=arguments-renamed - s = _TypedefStruct(type_name, inst_name, array_dimensions, packed) - self._struct_stack.append(s) - - def pop_struct(self) -> None: - s = self._struct_stack.pop() - assert isinstance(s, _TypedefStruct) - - if s.children: - # struct is not empty. Attach it to the parent - self.current_struct.children.append(s.instantiation) - - # Add to collection of struct definitions - if s.type_name not in self.typedefs: - self.typedefs[s.type_name] = s - - def finish(self) -> Optional[str]: - s = self._struct_stack.pop() - assert isinstance(s, _TypedefStruct) - assert not self._struct_stack - - # no children, no struct. - if not s.children: - return None - - # Add to collection of struct definitions - if s.type_name not in self.typedefs: - self.typedefs[s.type_name] = s - - all_structs = [str(s) for s in self.typedefs.values()] - - return "\n\n".join(all_structs) - - -class RDLFlatStructGenerator(FlatStructGenerator, RDLListener): - """ - Struct generator that naively translates an RDL node tree into a flat list - of typedefs - - This can be extended to add more intelligent behavior - """ - - def get_typdef_name(self, node:'Node') -> str: - raise NotImplementedError - - def get_struct(self, node: 'Node', type_name: str) -> Optional[str]: - self.start(type_name) - - walker = RDLWalker() - walker.walk(node, self, skip_top=True) - - return self.finish() - - def enter_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]: - type_name = self.get_typdef_name(node) - self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions) - return WalkerAction.Continue - - def exit_Addrmap(self, node: 'AddrmapNode') -> Optional[WalkerAction]: - self.pop_struct() - return WalkerAction.Continue - - def enter_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]: - type_name = self.get_typdef_name(node) - self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions) - return WalkerAction.Continue - - def exit_Regfile(self, node: 'RegfileNode') -> Optional[WalkerAction]: - self.pop_struct() - return WalkerAction.Continue - - def enter_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: - type_name = self.get_typdef_name(node) - self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions) - return WalkerAction.Continue - - def exit_Mem(self, node: 'MemNode') -> Optional[WalkerAction]: - self.pop_struct() - return WalkerAction.Continue - - def enter_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: - type_name = self.get_typdef_name(node) - self.push_struct(type_name, kwf(node.inst_name), node.array_dimensions) - return WalkerAction.Continue - - def exit_Reg(self, node: 'RegNode') -> Optional[WalkerAction]: - self.pop_struct() - return WalkerAction.Continue - - def enter_Field(self, node: 'FieldNode') -> Optional[WalkerAction]: - self.add_member(kwf(node.inst_name), node.width) - return WalkerAction.Continue diff --git a/src/peakrdl_regblock/write_buffering/__init__.py b/src/peakrdl_regblock/write_buffering/__init__.py deleted file mode 100644 index 934b5e6..0000000 --- a/src/peakrdl_regblock/write_buffering/__init__.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import TYPE_CHECKING, Union - -from systemrdl.node import AddrmapNode, RegNode, FieldNode, SignalNode - -from .storage_generator import WBufStorageStructGenerator -from .implementation_generator import WBufLogicGenerator -from ..utils import get_indexed_path -from ..sv_int import SVInt - -if TYPE_CHECKING: - from ..exporter import BusDecoderExporter - - -class WriteBuffering: - def __init__(self, exp: "BusDecoderExporter"): - self.exp = exp - - @property - def top_node(self) -> "AddrmapNode": - return self.exp.ds.top_node - - def get_storage_struct(self) -> str: - struct_gen = WBufStorageStructGenerator(self) - s = struct_gen.get_struct(self.top_node, "wbuf_storage_t") - assert s is not None - return s + "\nwbuf_storage_t wbuf_storage;" - - def get_implementation(self) -> str: - gen = WBufLogicGenerator(self) - s = gen.get_content(self.top_node) - assert s is not None - return s - - def get_wbuf_prefix(self, node: Union[RegNode, FieldNode]) -> str: - if isinstance(node, FieldNode): - node = node.parent - wbuf_prefix = "wbuf_storage." + get_indexed_path(self.top_node, node) - return wbuf_prefix - - def get_write_strobe(self, node: Union[RegNode, FieldNode]) -> str: - prefix = self.get_wbuf_prefix(node) - return f"{prefix}.pending && {self.get_trigger(node)}" - - def get_raw_trigger(self, node: "RegNode") -> Union[SVInt, str]: - trigger = node.get_property("wbuffer_trigger") - - if isinstance(trigger, RegNode): - # Trigger is a register. - # trigger when uppermost address of the register is written - regwidth = trigger.get_property("regwidth") - accesswidth = trigger.get_property("accesswidth") - strb_prefix = self.exp.dereferencer.get_access_strobe( - trigger, reduce_substrobes=False - ) - - if accesswidth < regwidth: - n_subwords = regwidth // accesswidth - return f"{strb_prefix}[{n_subwords - 1}] && decoded_req_is_wr" - else: - return f"{strb_prefix} && decoded_req_is_wr" - elif isinstance(trigger, SignalNode): - s = self.exp.dereferencer.get_value(trigger) - if trigger.get_property("activehigh"): - return s - else: - return f"~{s}" - else: - # Trigger is a field or propref bit - return self.exp.dereferencer.get_value(trigger) - - def get_trigger(self, node: Union[RegNode, FieldNode]) -> Union[SVInt, str]: - if isinstance(node, FieldNode): - node = node.parent - trigger = node.get_property("wbuffer_trigger") - - if isinstance(trigger, RegNode) and trigger == node: - # register is its own trigger - # use the delayed trigger signal - return self.get_wbuf_prefix(node) + ".trigger_q" - else: - return self.get_raw_trigger(node) diff --git a/src/peakrdl_regblock/write_buffering/implementation_generator.py b/src/peakrdl_regblock/write_buffering/implementation_generator.py deleted file mode 100644 index c2dad95..0000000 --- a/src/peakrdl_regblock/write_buffering/implementation_generator.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import TYPE_CHECKING -from collections import namedtuple - -from systemrdl.component import Reg -from systemrdl.node import RegNode - -from ..forloop_generator import RDLForLoopGenerator - -if TYPE_CHECKING: - from . import WriteBuffering - -class WBufLogicGenerator(RDLForLoopGenerator): - i_type = "genvar" - def __init__(self, wbuf: 'WriteBuffering') -> None: - super().__init__() - self.wbuf = wbuf - self.exp = wbuf.exp - self.template = self.exp.jj_env.get_template( - "write_buffering/template.sv" - ) - - def enter_Reg(self, node: 'RegNode') -> None: - super().enter_Reg(node) - assert isinstance(node.inst, Reg) - - if not node.get_property('buffer_writes'): - return - - regwidth = node.get_property('regwidth') - accesswidth = node.get_property('accesswidth') - strb_prefix = self.exp.dereferencer.get_access_strobe(node, reduce_substrobes=False) - Segment = namedtuple("Segment", ["strobe", "bslice"]) - segments = [] - if accesswidth < regwidth: - n_subwords = regwidth // accesswidth - for i in range(n_subwords): - strobe = strb_prefix + f"[{i}]" - if node.inst.is_msb0_order: - bslice = f"[{regwidth - (accesswidth * i) - 1}: {regwidth - (accesswidth * (i+1))}]" - else: - bslice = f"[{(accesswidth * (i + 1)) - 1}:{accesswidth * i}]" - segments.append(Segment(strobe, bslice)) - else: - segments.append(Segment(strb_prefix, "")) - - trigger = node.get_property('wbuffer_trigger') - is_own_trigger = (isinstance(trigger, RegNode) and trigger == node) - - context = { - 'wbuf': self.wbuf, - 'wbuf_prefix': self.wbuf.get_wbuf_prefix(node), - 'segments': segments, - 'node': node, - 'cpuif': self.exp.cpuif, - 'get_resetsignal': self.exp.dereferencer.get_resetsignal, - 'get_always_ff_event': self.exp.dereferencer.get_always_ff_event, - 'is_own_trigger': is_own_trigger, - } - self.add_content(self.template.render(context)) diff --git a/src/peakrdl_regblock/write_buffering/storage_generator.py b/src/peakrdl_regblock/write_buffering/storage_generator.py deleted file mode 100644 index 6b48472..0000000 --- a/src/peakrdl_regblock/write_buffering/storage_generator.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import TYPE_CHECKING - -from systemrdl.node import FieldNode, RegNode - -from ..struct_generator import RDLStructGenerator - -if TYPE_CHECKING: - from . import WriteBuffering - -class WBufStorageStructGenerator(RDLStructGenerator): - def __init__(self, wbuf: 'WriteBuffering') -> None: - super().__init__() - self.wbuf = wbuf - - def enter_Field(self, node: FieldNode) -> None: - # suppress parent class's field behavior - pass - - def enter_Reg(self, node: RegNode) -> None: - super().enter_Reg(node) - - if not node.get_property('buffer_writes'): - return - - regwidth = node.get_property('regwidth') - self.add_member("data", regwidth) - self.add_member("biten", regwidth) - self.add_member("pending") - - trigger = node.get_property('wbuffer_trigger') - if isinstance(trigger, RegNode) and trigger == node: - self.add_member("trigger_q") diff --git a/src/peakrdl_regblock/write_buffering/template.sv b/src/peakrdl_regblock/write_buffering/template.sv deleted file mode 100644 index 78bbf46..0000000 --- a/src/peakrdl_regblock/write_buffering/template.sv +++ /dev/null @@ -1,31 +0,0 @@ -always_ff {{get_always_ff_event(cpuif.reset)}} begin - if({{get_resetsignal(cpuif.reset)}}) begin - {{wbuf_prefix}}.pending <= '0; - {{wbuf_prefix}}.data <= '0; - {{wbuf_prefix}}.biten <= '0; - {%- if is_own_trigger %} - {{wbuf_prefix}}.trigger_q <= '0; - {%- endif %} - end else begin - if({{wbuf.get_trigger(node)}}) begin - {{wbuf_prefix}}.pending <= '0; - {{wbuf_prefix}}.data <= '0; - {{wbuf_prefix}}.biten <= '0; - end - {%- for segment in segments %} - if({{segment.strobe}} && decoded_req_is_wr) begin - {{wbuf_prefix}}.pending <= '1; - {%- if node.inst.is_msb0_order %} - {{wbuf_prefix}}.data{{segment.bslice}} <= ({{wbuf_prefix}}.data{{segment.bslice}} & ~decoded_wr_biten_bswap) | (decoded_wr_data_bswap & decoded_wr_biten_bswap); - {{wbuf_prefix}}.biten{{segment.bslice}} <= {{wbuf_prefix}}.biten{{segment.bslice}} | decoded_wr_biten_bswap; - {%- else %} - {{wbuf_prefix}}.data{{segment.bslice}} <= ({{wbuf_prefix}}.data{{segment.bslice}} & ~decoded_wr_biten) | (decoded_wr_data & decoded_wr_biten); - {{wbuf_prefix}}.biten{{segment.bslice}} <= {{wbuf_prefix}}.biten{{segment.bslice}} | decoded_wr_biten; - {%- endif %} - end - {%- endfor %} - {%- if is_own_trigger %} - {{wbuf_prefix}}.trigger_q <= {{wbuf.get_raw_trigger(node)}}; - {%- endif %} - end -end diff --git a/tests/lib/cpuifs/base.py b/tests/lib/cpuifs/base.py index 8dac707..2a79d34 100644 --- a/tests/lib/cpuifs/base.py +++ b/tests/lib/cpuifs/base.py @@ -4,7 +4,7 @@ import inspect import jinja2 as jj -from peakrdl_busdecoder.cpuif.base import CpuifBase +from peakrdl_busdecoder.cpuif.base_cpuif import CpuifBase from ..sv_line_anchor import SVLineAnchor diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..8a39374 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1255 @@ +version = 1 +revision = 1 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] + +[[package]] +name = "accessible-pygments" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903 }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/5f/2cdf6f7aca3b20d3f316e9f505292e1f256a32089bd702034c29ebde6242/antlr4_python3_runtime-4.13.2.tar.gz", hash = "sha256:909b647e1d2fc2b70180ac586df3933e38919c85f98ccc656a96cd3f25ef3916", size = 117467 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/03/a851e84fcbb85214dc637b6378121ef9a0dd61b4c65264675d8a5c9b1ae7/antlr4_python3_runtime-4.13.2-py3-none-any.whl", hash = "sha256:fe3835eb8d33daece0e799090eda89719dbccee7aa39ef94eed3818cafa5a7e8", size = 144462 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392 }, +] + +[[package]] +name = "cairocffi" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/c5/1a4dc131459e68a173cbdab5fad6b524f53f9c1ef7861b7698e998b837cc/cairocffi-1.7.1.tar.gz", hash = "sha256:2e48ee864884ec4a3a34bfa8c9ab9999f688286eb714a15a43ec9d068c36557b", size = 88096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d8/ba13451aa6b745c49536e87b6bf8f629b950e84bd0e8308f7dc6883b67e2/cairocffi-1.7.1-py3-none-any.whl", hash = "sha256:9803a0e11f6c962f3b0ae2ec8ba6ae45e957a146a004697a1ac1bbf16b073b3f", size = 75611 }, +] + +[[package]] +name = "cairosvg" +version = "2.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cairocffi" }, + { name = "cssselect2" }, + { name = "defusedxml" }, + { name = "pillow" }, + { name = "tinycss2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/b9/5106168bd43d7cd8b7cc2a2ee465b385f14b63f4c092bb89eee2d48c8e67/cairosvg-2.8.2.tar.gz", hash = "sha256:07cbf4e86317b27a92318a4cac2a4bb37a5e9c1b8a27355d06874b22f85bef9f", size = 8398590 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/48/816bd4aaae93dbf9e408c58598bc32f4a8c65f4b86ab560864cb3ee60adb/cairosvg-2.8.2-py3-none-any.whl", hash = "sha256:eab46dad4674f33267a671dce39b64be245911c901c70d65d2b7b0821e852bf5", size = 45773 }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286 }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283 }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504 }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811 }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402 }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217 }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079 }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475 }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829 }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211 }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036 }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184 }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790 }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344 }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560 }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613 }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476 }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374 }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597 }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574 }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971 }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972 }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078 }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076 }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820 }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635 }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271 }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048 }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529 }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097 }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983 }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519 }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572 }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963 }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361 }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932 }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557 }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762 }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230 }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043 }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446 }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101 }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948 }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422 }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499 }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928 }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302 }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909 }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402 }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780 }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320 }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487 }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049 }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793 }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300 }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244 }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828 }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926 }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328 }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650 }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687 }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773 }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013 }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593 }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354 }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480 }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584 }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443 }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437 }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487 }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726 }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/98/f3b8013223728a99b908c9344da3aa04ee6e3fa235f19409033eda92fb78/charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", size = 207695 }, + { url = "https://files.pythonhosted.org/packages/21/40/5188be1e3118c82dcb7c2a5ba101b783822cfb413a0268ed3be0468532de/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", size = 147153 }, + { url = "https://files.pythonhosted.org/packages/37/60/5d0d74bc1e1380f0b72c327948d9c2aca14b46a9efd87604e724260f384c/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", size = 160428 }, + { url = "https://files.pythonhosted.org/packages/85/9a/d891f63722d9158688de58d050c59dc3da560ea7f04f4c53e769de5140f5/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", size = 157627 }, + { url = "https://files.pythonhosted.org/packages/65/1a/7425c952944a6521a9cfa7e675343f83fd82085b8af2b1373a2409c683dc/charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", size = 152388 }, + { url = "https://files.pythonhosted.org/packages/f0/c9/a2c9c2a355a8594ce2446085e2ec97fd44d323c684ff32042e2a6b718e1d/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", size = 150077 }, + { url = "https://files.pythonhosted.org/packages/3b/38/20a1f44e4851aa1c9105d6e7110c9d020e093dfa5836d712a5f074a12bf7/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", size = 161631 }, + { url = "https://files.pythonhosted.org/packages/a4/fa/384d2c0f57edad03d7bec3ebefb462090d8905b4ff5a2d2525f3bb711fac/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", size = 159210 }, + { url = "https://files.pythonhosted.org/packages/33/9e/eca49d35867ca2db336b6ca27617deed4653b97ebf45dfc21311ce473c37/charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", size = 153739 }, + { url = "https://files.pythonhosted.org/packages/2a/91/26c3036e62dfe8de8061182d33be5025e2424002125c9500faff74a6735e/charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", size = 99825 }, + { url = "https://files.pythonhosted.org/packages/e2/c6/f05db471f81af1fa01839d44ae2a8bfeec8d2a8b4590f16c4e7393afd323/charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", size = 107452 }, + { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483 }, + { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520 }, + { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876 }, + { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083 }, + { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295 }, + { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379 }, + { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018 }, + { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430 }, + { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600 }, + { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616 }, + { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108 }, + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655 }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223 }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366 }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104 }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830 }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854 }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670 }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501 }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173 }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822 }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543 }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326 }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008 }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196 }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819 }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350 }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644 }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468 }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187 }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699 }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580 }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366 }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342 }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995 }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640 }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636 }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939 }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580 }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870 }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797 }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224 }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086 }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400 }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987 }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388 }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148 }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958 }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819 }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754 }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860 }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877 }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108 }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752 }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497 }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392 }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102 }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505 }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898 }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831 }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937 }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021 }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626 }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682 }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402 }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320 }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536 }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425 }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103 }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290 }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515 }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020 }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769 }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901 }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413 }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820 }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941 }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519 }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375 }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699 }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512 }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147 }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320 }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575 }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568 }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174 }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447 }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779 }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604 }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497 }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350 }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111 }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746 }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541 }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170 }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029 }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259 }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592 }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768 }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995 }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546 }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544 }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308 }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920 }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434 }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403 }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469 }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731 }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302 }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578 }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629 }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162 }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517 }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632 }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520 }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455 }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287 }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946 }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009 }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804 }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384 }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047 }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266 }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767 }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931 }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186 }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470 }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626 }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386 }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852 }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534 }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784 }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905 }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922 }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cssselect2" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tinycss2" }, + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/86/fd7f58fc498b3166f3a7e8e0cddb6e620fe1da35b02248b1bd59e95dbaaa/cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a", size = 35716 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/e7/aa315e6a749d9b96c2504a1ba0ba031ba2d0517e972ce22682e3fccecb09/cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e", size = 15454 }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, +] + +[[package]] +name = "execnet" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631 }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057 }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050 }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681 }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705 }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524 }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282 }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745 }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571 }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056 }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932 }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631 }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058 }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287 }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940 }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887 }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692 }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471 }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923 }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572 }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077 }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876 }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622 }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374 }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980 }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990 }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784 }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588 }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041 }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658 }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066 }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639 }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569 }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284 }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801 }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769 }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642 }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619 }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408 }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005 }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048 }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821 }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606 }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043 }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747 }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341 }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073 }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661 }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069 }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670 }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598 }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261 }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835 }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733 }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672 }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819 }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426 }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, +] + +[[package]] +name = "parameterized" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/49/00c0c0cc24ff4266025a53e41336b79adaa5a4ebfad214f433d623f9865e/parameterized-0.9.0.tar.gz", hash = "sha256:7fc905272cefa4f364c1a3429cbbe9c0f98b793988efb5bf90aac80f08db09b1", size = 24351 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2f/804f58f0b856ab3bf21617cccf5b39206e6c4c94c2cd227bde125ea6105f/parameterized-0.9.0-py2.py3-none-any.whl", hash = "sha256:4e0758e3d41bea3bbd05ec14fc2c24736723f243b28d702081aef438c9372b1b", size = 20475 }, +] + +[[package]] +name = "peakrdl-busdecoder" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "jinja2" }, + { name = "systemrdl-compiler" }, +] + +[package.optional-dependencies] +cli = [ + { name = "peakrdl-cli" }, +] + +[package.dev-dependencies] +docs = [ + { name = "pygments-systemrdl" }, + { name = "sphinx-book-theme" }, + { name = "sphinxcontrib-wavedrom" }, +] +test = [ + { name = "parameterized" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, +] +tools = [ + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "jinja2", specifier = ">=3.1.6" }, + { name = "peakrdl-cli", marker = "extra == 'cli'", specifier = ">=1.2.3" }, + { name = "systemrdl-compiler", specifier = "~=1.30.1" }, +] +provides-extras = ["cli"] + +[package.metadata.requires-dev] +docs = [ + { name = "pygments-systemrdl", specifier = ">=1.3.0" }, + { name = "sphinx-book-theme", specifier = ">=1.1.4" }, + { name = "sphinxcontrib-wavedrom", specifier = ">=3.0.4" }, +] +test = [ + { name = "parameterized", specifier = ">=0.9.0" }, + { name = "pytest", specifier = ">=7.4.4" }, + { name = "pytest-cov", specifier = ">=4.1.0" }, + { name = "pytest-xdist", specifier = ">=3.5.0" }, +] +tools = [{ name = "ruff", specifier = ">=0.14.0" }] + +[[package]] +name = "peakrdl-cli" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "systemrdl-compiler" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/c8/85baad7853c8995407ce04e98aaff713bc0d82de1f38721679f033c53d0b/peakrdl_cli-1.5.0.tar.gz", hash = "sha256:fb11961bc4d5636239e02b3392072025e7310afaceba34d834adbb279c770f65", size = 17559 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/0d/8b3023ad5fe1b8fdaeb15a8a1f54dc9de384e398ebd374e234f783b4c100/peakrdl_cli-1.5.0-py3-none-any.whl", hash = "sha256:e6d86a41f8e8cdf09021e0abfdad604f6213f8d3aaf7ea6579ccbb6f047c81ca", size = 22772 }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554 }, + { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548 }, + { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742 }, + { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087 }, + { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350 }, + { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840 }, + { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005 }, + { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372 }, + { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090 }, + { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988 }, + { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899 }, + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531 }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560 }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978 }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168 }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053 }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273 }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043 }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516 }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768 }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055 }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079 }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800 }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296 }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726 }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652 }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787 }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236 }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950 }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358 }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079 }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324 }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067 }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328 }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652 }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443 }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474 }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038 }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094 }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503 }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574 }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060 }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407 }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841 }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450 }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055 }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110 }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547 }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554 }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132 }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001 }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814 }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124 }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186 }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546 }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102 }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803 }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520 }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116 }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597 }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246 }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336 }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699 }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789 }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386 }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911 }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383 }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385 }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129 }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580 }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860 }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694 }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888 }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330 }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089 }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206 }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370 }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500 }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835 }, + { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556 }, + { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625 }, + { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207 }, + { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939 }, + { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166 }, + { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482 }, + { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566 }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618 }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248 }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963 }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170 }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505 }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598 }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140 }, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/ea/3ab478cccacc2e8ef69892c42c44ae547bae089f356c4b47caf61730958d/pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d", size = 2400673 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6", size = 4640157 }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, +] + +[[package]] +name = "pygments-systemrdl" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/82/04ef8859a91b47ef3087fed40d6019a043552949feb5a324e8cfc1ef6722/pygments_systemrdl-1.3.0.tar.gz", hash = "sha256:32ad6c4c20e392bec0ff0205034fd92136b9001f1132abb049ea80a4e6d5c4b2", size = 12413 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/b8/f7b02c0cb8a4fca3c3a17dc2d0422b91535ce0b0907c1d72598ac5e1977c/pygments_systemrdl-1.3.0-py3-none-any.whl", hash = "sha256:0d4a97d7bd2c36447c460f6e6d2163f8c9578b5cdc09fbe79a7c43aeffa310b0", size = 6406 }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750 }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424 }, +] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227 }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019 }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646 }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793 }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293 }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872 }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828 }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415 }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561 }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826 }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577 }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556 }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114 }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638 }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463 }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986 }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543 }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763 }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738 }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742 }, +] + +[[package]] +name = "ruff" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/b9/9bd84453ed6dd04688de9b3f3a4146a1698e8faae2ceeccce4e14c67ae17/ruff-0.14.0.tar.gz", hash = "sha256:62ec8969b7510f77945df916de15da55311fade8d6050995ff7f680afe582c57", size = 5452071 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/4e/79d463a5f80654e93fa653ebfb98e0becc3f0e7cf6219c9ddedf1e197072/ruff-0.14.0-py3-none-linux_armv6l.whl", hash = "sha256:58e15bffa7054299becf4bab8a1187062c6f8cafbe9f6e39e0d5aface455d6b3", size = 12494532 }, + { url = "https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8", size = 13160768 }, + { url = "https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8", size = 12363376 }, + { url = "https://files.pythonhosted.org/packages/42/e2/1ffef5a1875add82416ff388fcb7ea8b22a53be67a638487937aea81af27/ruff-0.14.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba9a8925e90f861502f7d974cc60e18ca29c72bb0ee8bfeabb6ade35a3abde7", size = 12608055 }, + { url = "https://files.pythonhosted.org/packages/4a/32/986725199d7cee510d9f1dfdf95bf1efc5fa9dd714d0d85c1fb1f6be3bc3/ruff-0.14.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e41f785498bd200ffc276eb9e1570c019c1d907b07cfb081092c8ad51975bbe7", size = 12318544 }, + { url = "https://files.pythonhosted.org/packages/9a/ed/4969cefd53315164c94eaf4da7cfba1f267dc275b0abdd593d11c90829a3/ruff-0.14.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30a58c087aef4584c193aebf2700f0fbcfc1e77b89c7385e3139956fa90434e2", size = 14001280 }, + { url = "https://files.pythonhosted.org/packages/ab/ad/96c1fc9f8854c37681c9613d825925c7f24ca1acfc62a4eb3896b50bacd2/ruff-0.14.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f8d07350bc7af0a5ce8812b7d5c1a7293cf02476752f23fdfc500d24b79b783c", size = 15027286 }, + { url = "https://files.pythonhosted.org/packages/b3/00/1426978f97df4fe331074baf69615f579dc4e7c37bb4c6f57c2aad80c87f/ruff-0.14.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eec3bbbf3a7d5482b5c1f42d5fc972774d71d107d447919fca620b0be3e3b75e", size = 14451506 }, + { url = "https://files.pythonhosted.org/packages/58/d5/9c1cea6e493c0cf0647674cca26b579ea9d2a213b74b5c195fbeb9678e15/ruff-0.14.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16b68e183a0e28e5c176d51004aaa40559e8f90065a10a559176713fcf435206", size = 13437384 }, + { url = "https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e", size = 13447976 }, + { url = "https://files.pythonhosted.org/packages/3b/c0/ac42f546d07e4f49f62332576cb845d45c67cf5610d1851254e341d563b6/ruff-0.14.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c958f66ab884b7873e72df38dcabee03d556a8f2ee1b8538ee1c2bbd619883dd", size = 13682850 }, + { url = "https://files.pythonhosted.org/packages/5f/c4/4b0c9bcadd45b4c29fe1af9c5d1dc0ca87b4021665dfbe1c4688d407aa20/ruff-0.14.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7eb0499a2e01f6e0c285afc5bac43ab380cbfc17cd43a2e1dd10ec97d6f2c42d", size = 12449825 }, + { url = "https://files.pythonhosted.org/packages/4b/a8/e2e76288e6c16540fa820d148d83e55f15e994d852485f221b9524514730/ruff-0.14.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c63b2d99fafa05efca0ab198fd48fa6030d57e4423df3f18e03aa62518c565f", size = 12272599 }, + { url = "https://files.pythonhosted.org/packages/18/14/e2815d8eff847391af632b22422b8207704222ff575dec8d044f9ab779b2/ruff-0.14.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:668fce701b7a222f3f5327f86909db2bbe99c30877c8001ff934c5413812ac02", size = 13193828 }, + { url = "https://files.pythonhosted.org/packages/44/c6/61ccc2987cf0aecc588ff8f3212dea64840770e60d78f5606cd7dc34de32/ruff-0.14.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a86bf575e05cb68dcb34e4c7dfe1064d44d3f0c04bbc0491949092192b515296", size = 13628617 }, + { url = "https://files.pythonhosted.org/packages/73/e6/03b882225a1b0627e75339b420883dc3c90707a8917d2284abef7a58d317/ruff-0.14.0-py3-none-win32.whl", hash = "sha256:7450a243d7125d1c032cb4b93d9625dea46c8c42b4f06c6b709baac168e10543", size = 12367872 }, + { url = "https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl", hash = "sha256:ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2", size = 13464628 }, + { url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730", size = 12565142 }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274 }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679 }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.11'" }, + { name = "babel", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "imagesize", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741 }, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydata-sphinx-theme" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/19/d002ed96bdc7738c15847c730e1e88282d738263deac705d5713b4d8fa94/sphinx_book_theme-1.1.4.tar.gz", hash = "sha256:73efe28af871d0a89bd05856d300e61edce0d5b2fbb7984e84454be0fedfe9ed", size = 439188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl", hash = "sha256:843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1", size = 433952 }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "sphinxcontrib-wavedrom" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cairosvg" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "wavedrom" }, + { name = "xcffib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/b5/c09b6ac2b0ac8455ec65ab73b62aaebd77ca6ac7eaee47730f9a4b67812b/sphinxcontrib-wavedrom-3.0.4.tar.gz", hash = "sha256:d334c7541afd917c0c128e1545316cc5d5f61c8df50f17477cb5070909b0d4aa", size = 209122 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/ec/4d733b2ad6b6b55bda555ed3184d6525de3371426bb522106b217b56b35a/sphinxcontrib_wavedrom-3.0.4-py3-none-any.whl", hash = "sha256:1131126ab00c05984cf5c20c3f164b0200209fff7c8c0ad66dd36b019fc3cb95", size = 10676 }, +] + +[[package]] +name = "svgwrite" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/c1/263d4e93b543390d86d8eb4fc23d9ce8a8d6efd146f9427364109004fa9b/svgwrite-1.4.3.zip", hash = "sha256:a8fbdfd4443302a6619a7f76bc937fc683daf2628d9b737c891ec08b8ce524c3", size = 189516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/15/640e399579024a6875918839454025bb1d5f850bb70d96a11eabb644d11c/svgwrite-1.4.3-py3-none-any.whl", hash = "sha256:bb6b2b5450f1edbfa597d924f9ac2dd099e625562e492021d7dd614f65f8a22d", size = 67122 }, +] + +[[package]] +name = "systemrdl-compiler" +version = "1.30.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "colorama" }, + { name = "markdown" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/31/8ca0c5748e7dd566088e9ad84ca129627dc54421ba4397ade3bcc3beea84/systemrdl_compiler-1.30.1.tar.gz", hash = "sha256:ba8411aa53052da52fe5f4de3061905360154a48c1855236aecbb377b08b9654", size = 483996 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/10/cb0bc743910fcf80859c90cf0b0754527c2e5cf6caadfc7e3bab3bb1d166/systemrdl_compiler-1.30.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:51f2c8c56bc4552b5751f08e2bef511ba10cb5a65cab79e3c4b44591d176eb99", size = 1170794 }, + { url = "https://files.pythonhosted.org/packages/a7/fa/d26431aa07b0d1d1784c6ab5077736b832a30dc647212e8535f1484c18ed/systemrdl_compiler-1.30.1-cp37-abi3-macosx_11_0_x86_64.whl", hash = "sha256:276f4fce1254c5f57da4cfd7cacd0872d84134a1bb210a46bd7f79d04a7e0329", size = 1174880 }, + { url = "https://files.pythonhosted.org/packages/47/ee/9c76699bb24adf5400a7fea305b9fcc46b70b12aa70eaa6f4e00cf17ad1e/systemrdl_compiler-1.30.1-cp37-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:281f411e74663d4c7bf2fd73db63ba1887d5cc55accbe67fa373aaba05848613", size = 10844382 }, + { url = "https://files.pythonhosted.org/packages/a5/9f/de0b43c4e662256aa1955d116e7703b647fc4b83289d6a57baea9a540a38/systemrdl_compiler-1.30.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f639d551567a136ff1ed5d62b17162e5c2e2cd51c0c4c1488cde0712e73cb4a", size = 11019356 }, + { url = "https://files.pythonhosted.org/packages/ed/93/7d00173f47396a49be7f99a3f8239707620e86253702236d330fae627134/systemrdl_compiler-1.30.1-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:a43e8f1a0afeb35f56a748471fc1d0cac60cf0f04cefd14fedb1b53f8abeab6f", size = 11260909 }, + { url = "https://files.pythonhosted.org/packages/48/18/be05a311f89838e30aca12cb5b10d4ed5d9a91ec3c8c11b78250e6dbc8c7/systemrdl_compiler-1.30.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c9ef2b2480c02e30f6627731d700f174039c1407bb5ae33b1817d06a39e080b0", size = 11301190 }, + { url = "https://files.pythonhosted.org/packages/af/71/9c47aed48dc2be6ce36fa41b092749fce8228812186040277929fe94b750/systemrdl_compiler-1.30.1-cp37-abi3-win32.whl", hash = "sha256:b8583e0210619fbcfebea2e03fa780fd7604188b83afbba47fd8e559944f8667", size = 922325 }, + { url = "https://files.pythonhosted.org/packages/46/6a/08297441b88c0e0f5b352964d3376bd386f17b4194e077d356e8346ff322/systemrdl_compiler-1.30.1-cp37-abi3-win_amd64.whl", hash = "sha256:59d580b235dc30224c3ae6038fac05c0c6c0d7f96abe6ad3979550c89a6bec99", size = 955160 }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236 }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084 }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832 }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052 }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555 }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128 }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445 }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165 }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891 }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796 }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121 }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070 }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859 }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296 }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124 }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698 }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819 }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766 }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771 }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586 }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792 }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909 }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946 }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705 }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244 }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637 }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925 }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045 }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835 }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109 }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930 }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964 }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065 }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088 }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193 }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488 }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669 }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709 }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563 }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756 }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408 }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795 }, +] + +[[package]] +name = "wavedrom" +version = "2.0.3.post3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "six" }, + { name = "svgwrite" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/71/6739e3abac630540aaeaaece4584c39f88b5f8658ce6ca517efec455e3de/wavedrom-2.0.3.post3.tar.gz", hash = "sha256:327b4d5dca593c81257c202fea516f7a908747fb11527c359f034f5b7af7f47b", size = 137737 } + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + +[[package]] +name = "xcffib" +version = "1.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/b7/199f9af664ef7474e823bea13d24d119784562f52f919e5a03ae1b2e90df/xcffib-1.11.2.tar.gz", hash = "sha256:e27e1bad25452824736d967d4db8a32b366606d682a5b963185f629598c5f5dd", size = 111390 }