PySHDL 0.2.4__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyshdl-0.2.4 → pyshdl-0.3.0}/PKG-INFO +1 -1
- {pyshdl-0.2.4 → pyshdl-0.3.0}/pyproject.toml +1 -1
- pyshdl-0.3.0/src/SHDL/bus_compiler/__init__.py +10 -0
- pyshdl-0.3.0/src/SHDL/bus_compiler/analyzer.py +382 -0
- pyshdl-0.3.0/src/SHDL/bus_compiler/codegen.py +576 -0
- pyshdl-0.3.0/src/SHDL/bus_compiler/compiler.py +63 -0
- pyshdl-0.3.0/src/SHDL/bus_compiler/graph.py +143 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/driver/circuit.py +72 -45
- {pyshdl-0.2.4 → pyshdl-0.3.0}/uv.lock +1 -1
- {pyshdl-0.2.4 → pyshdl-0.3.0}/.github/workflows/deploy-docs.yml +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/.github/workflows/publish.yml +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/.github/workflows/test.yml +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/.gitignore +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/LICENSE +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/README.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/README.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/architecture/_category_.json +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/architecture/base-shdl.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/architecture/compiler-internals.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/architecture/flattening-pipeline.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/architecture/overview.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/architecture/pyshdl-internals.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/_category_.json +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/breakpoints.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/commands.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/common-problems.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/debug-build.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/getting-started.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/hierarchy.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/inspection.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/overview.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/python-api.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/scripting.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/debugger/waveforms.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/examples/8-bit-adder.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/examples/_category_.json +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/examples/comparator.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/examples/decoder.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/examples/full-adder.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/examples/half-adder.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/examples/multiplexer.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/examples/register.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/getting-started/_category_.json +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/getting-started/first-circuit.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/getting-started/installation.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/getting-started/using-pyshdl.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/intro.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/_category_.json +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/components.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/connections.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/constants.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/errors.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/generators.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/imports.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/lexical-elements.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/overview.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/signals.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docs/language-reference/standard-gates.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/docusaurus.config.ts +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/package.json +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/sidebars.ts +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/src/components/HomepageFeatures/index.tsx +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/src/components/HomepageFeatures/styles.module.css +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/src/css/custom.css +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/src/pages/index.module.css +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/src/pages/index.tsx +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/src/pages/markdown-page.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/.nojekyll +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/img/docusaurus-social-card.jpg +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/img/docusaurus.png +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/img/favicon.ico +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/img/halfAdder.png +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/img/logo.svg +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/img/undraw_docusaurus_mountain.svg +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/img/undraw_docusaurus_react.svg +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/static/img/undraw_docusaurus_tree.svg +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/docs/tsconfig.json +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/add100.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/addSub16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/adder16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/alu.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/and16inputs.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/bitwise_and16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/bitwise_not16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/bitwise_or16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/bitwise_xor16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/clock.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/demux.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/flagsZN.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/fullAdder.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/mux2.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/mux2_16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/mux8.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/mux8_16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/negate16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/or16inputs.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/reg16.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/SHDL_components/shift1.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/examples/interacting.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDB/__init__.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/__init__.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/__init__.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/analyzer.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/ast.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/cli.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/codegen.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/compiler.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/debug_codegen.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/debug_info_gen.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/lexer.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/compiler/parser.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/debugger/__init__.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/debugger/circuit.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/debugger/cli.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/debugger/controller.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/debugger/debuginfo.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/debugger/sourcemap.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/debugger/symbols.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/driver/__init__.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/driver/exceptions.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/errors.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/flattener/__init__.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/flattener/ast.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/flattener/flattener.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/flattener/lexer.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/flattener/parser.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/flattener/tokens.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/py.typed +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/semantic/__init__.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/semantic/analyzer.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/semantic/connection.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/semantic/resolver.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/semantic/type_check.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/semantic/warnings.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/src/SHDL/source_map.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/README.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/TEST_REPORT.md +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_adder4.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_adder8.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_bitwise.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_comparator.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_constants.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_decoder.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_gates.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_generators.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_half_full_adder.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/circuits/test_mux.shdl +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/conftest.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/test_debugger.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/test_errors.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/test_shdl.py +0 -0
- {pyshdl-0.2.4 → pyshdl-0.3.0}/tests/test_shdl_comprehensive.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySHDL
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: SHDL (Simple Hardware Description Language) is a minimal HDL designed for creating digital circuits and easily simulating them. It compiles directly to C for maximum performance and portability. PySHDL is the Python interface for SHDL.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rafa-rrayes/SHDL
|
|
6
6
|
Project-URL: Repository, https://github.com/rafa-rrayes/SHDL
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "PySHDL"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "SHDL (Simple Hardware Description Language) is a minimal HDL designed for creating digital circuits and easily simulating them. It compiles directly to C for maximum performance and portability. PySHDL is the Python interface for SHDL."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Signal-Width-Aware Code Generator for SHDL
|
|
3
|
+
|
|
4
|
+
Packs gates that process corresponding bits of the same bus together,
|
|
5
|
+
enabling word-level C operations instead of individual bit extractions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .compiler import BusCompiler
|
|
9
|
+
|
|
10
|
+
__all__ = ["BusCompiler"]
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bus Pattern Detection and Topological Sorting.
|
|
3
|
+
|
|
4
|
+
Uses partition refinement to group gates that process corresponding bits
|
|
5
|
+
of the same bus, enabling word-width C operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
|
|
11
|
+
from .graph import ConnectionGraph, GateNode, WireRef, OutputSink
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class BusSource:
|
|
16
|
+
"""Describes how a bus group input is sourced."""
|
|
17
|
+
kind: str # port_aligned, port_broadcast, bus_group, constant, mixed
|
|
18
|
+
ref: str = "" # port name or bus group name
|
|
19
|
+
broadcast_bit: int = 0 # for broadcast: which bit (1-based)
|
|
20
|
+
per_bit: list = field(default_factory=list) # for mixed: list of WireRef per gate
|
|
21
|
+
shift: int = 0 # for port_aligned: right shift amount (0-based)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class BusGroup:
|
|
26
|
+
"""A group of gates that can be evaluated as a word-width operation."""
|
|
27
|
+
name: str
|
|
28
|
+
primitive: str
|
|
29
|
+
width: int
|
|
30
|
+
gates: list[GateNode] # ordered by bit position
|
|
31
|
+
bit_indices: list[int] # the bit index each gate corresponds to
|
|
32
|
+
input_sources: dict[str, BusSource] = field(default_factory=dict)
|
|
33
|
+
is_feedback: bool = False
|
|
34
|
+
scc_id: int = -1 # which SCC this group belongs to (-1 = none)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class AnalysisResult:
|
|
39
|
+
bus_groups: list[BusGroup] # topologically sorted
|
|
40
|
+
singleton_gates: list[GateNode] # gates not in any bus group
|
|
41
|
+
output_sinks: list[OutputSink]
|
|
42
|
+
input_ports: dict[str, int]
|
|
43
|
+
output_ports: dict[str, int]
|
|
44
|
+
# Maps gate instance name -> (group_name, position_in_group)
|
|
45
|
+
gate_to_group: dict[str, tuple[str, int]] = field(default_factory=dict)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BusAnalyzer:
|
|
49
|
+
"""Analyzes a ConnectionGraph to detect bus-width operation patterns."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, graph: ConnectionGraph):
|
|
52
|
+
self.graph = graph
|
|
53
|
+
|
|
54
|
+
def analyze(self) -> AnalysisResult:
|
|
55
|
+
# Filter out VCC/GND pseudo-gates
|
|
56
|
+
all_gates = [
|
|
57
|
+
g for g in self.graph.gates.values()
|
|
58
|
+
if g.primitive not in ("__VCC__", "__GND__")
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
# Step 1: Partition refinement to find bus groups
|
|
62
|
+
group_of = self._partition_refinement(all_gates)
|
|
63
|
+
|
|
64
|
+
# Step 2: Assign bit positions
|
|
65
|
+
positions = self._assign_positions(all_gates)
|
|
66
|
+
|
|
67
|
+
# Step 3: Build BusGroup objects from partition results
|
|
68
|
+
groups_by_id: dict[str, list[GateNode]] = defaultdict(list)
|
|
69
|
+
for gate in all_gates:
|
|
70
|
+
groups_by_id[group_of[gate.name]].append(gate)
|
|
71
|
+
|
|
72
|
+
bus_groups: list[BusGroup] = []
|
|
73
|
+
grouped_gates: set[str] = set()
|
|
74
|
+
gate_group_map: dict[str, str] = {} # gate_name -> group_name
|
|
75
|
+
group_counter = 0
|
|
76
|
+
|
|
77
|
+
for gid, gates in groups_by_id.items():
|
|
78
|
+
if len(gates) < 2:
|
|
79
|
+
continue # singleton
|
|
80
|
+
|
|
81
|
+
# Sort by bit position
|
|
82
|
+
gate_bits = []
|
|
83
|
+
for gate in gates:
|
|
84
|
+
bit = positions.get(gate.name, 1)
|
|
85
|
+
gate_bits.append((bit, gate))
|
|
86
|
+
gate_bits.sort(key=lambda x: x[0])
|
|
87
|
+
|
|
88
|
+
# Deduplicate bit positions
|
|
89
|
+
seen_bits: set[int] = set()
|
|
90
|
+
deduped = []
|
|
91
|
+
for bit, gate in gate_bits:
|
|
92
|
+
if bit not in seen_bits:
|
|
93
|
+
seen_bits.add(bit)
|
|
94
|
+
deduped.append((bit, gate))
|
|
95
|
+
gate_bits = deduped
|
|
96
|
+
|
|
97
|
+
if len(gate_bits) < 2:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
group_counter += 1
|
|
101
|
+
prim = gates[0].primitive
|
|
102
|
+
name = f"bus_{prim.lower()}_{group_counter}"
|
|
103
|
+
|
|
104
|
+
group = BusGroup(
|
|
105
|
+
name=name,
|
|
106
|
+
primitive=prim,
|
|
107
|
+
width=len(gate_bits),
|
|
108
|
+
gates=[g for _, g in gate_bits],
|
|
109
|
+
bit_indices=[b for b, _ in gate_bits],
|
|
110
|
+
)
|
|
111
|
+
bus_groups.append(group)
|
|
112
|
+
|
|
113
|
+
for gate in group.gates:
|
|
114
|
+
grouped_gates.add(gate.name)
|
|
115
|
+
gate_group_map[gate.name] = name
|
|
116
|
+
|
|
117
|
+
# Singletons
|
|
118
|
+
singletons = [g for g in all_gates if g.name not in grouped_gates]
|
|
119
|
+
|
|
120
|
+
# Step 4: Classify sources for each bus group
|
|
121
|
+
for group in bus_groups:
|
|
122
|
+
self._classify_sources(group, gate_group_map)
|
|
123
|
+
|
|
124
|
+
# Step 5: SCC detection
|
|
125
|
+
scc_map = self._detect_feedback(bus_groups)
|
|
126
|
+
|
|
127
|
+
# Step 6: Topological sort
|
|
128
|
+
sorted_groups = self._topological_sort(bus_groups, scc_map)
|
|
129
|
+
|
|
130
|
+
# Build gate_to_group map
|
|
131
|
+
gate_to_group = {}
|
|
132
|
+
for group in sorted_groups:
|
|
133
|
+
for i, gate in enumerate(group.gates):
|
|
134
|
+
gate_to_group[gate.name] = (group.name, i)
|
|
135
|
+
|
|
136
|
+
return AnalysisResult(
|
|
137
|
+
bus_groups=sorted_groups,
|
|
138
|
+
singleton_gates=singletons,
|
|
139
|
+
output_sinks=self.graph.output_sinks,
|
|
140
|
+
input_ports=self.graph.input_ports,
|
|
141
|
+
output_ports=self.graph.output_ports,
|
|
142
|
+
gate_to_group=gate_to_group,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _partition_refinement(self, all_gates: list[GateNode]) -> dict[str, str]:
|
|
146
|
+
"""Global partition refinement using group IDs instead of primitive types."""
|
|
147
|
+
# Round 0: group by primitive type
|
|
148
|
+
group_of: dict[str, str] = {}
|
|
149
|
+
for gate in all_gates:
|
|
150
|
+
group_of[gate.name] = gate.primitive
|
|
151
|
+
|
|
152
|
+
for _ in range(100): # safety bound
|
|
153
|
+
fingerprints: dict[str, tuple] = {}
|
|
154
|
+
for gate in all_gates:
|
|
155
|
+
fp = [gate.primitive]
|
|
156
|
+
for port in sorted(gate.inputs.keys()):
|
|
157
|
+
wire = gate.inputs[port]
|
|
158
|
+
if wire.kind == "port_input":
|
|
159
|
+
fp.append(("port", wire.name))
|
|
160
|
+
elif wire.kind == "gate_output":
|
|
161
|
+
fp.append(("gate", group_of.get(wire.name, "?")))
|
|
162
|
+
elif wire.kind == "constant":
|
|
163
|
+
fp.append(("const", wire.name))
|
|
164
|
+
fingerprints[gate.name] = tuple(fp)
|
|
165
|
+
|
|
166
|
+
new_group_of: dict[str, str] = {}
|
|
167
|
+
fp_to_id: dict[tuple, str] = {}
|
|
168
|
+
for gate in all_gates:
|
|
169
|
+
fp = fingerprints[gate.name]
|
|
170
|
+
if fp not in fp_to_id:
|
|
171
|
+
fp_to_id[fp] = f"G{len(fp_to_id)}"
|
|
172
|
+
new_group_of[gate.name] = fp_to_id[fp]
|
|
173
|
+
|
|
174
|
+
if new_group_of == group_of:
|
|
175
|
+
break # converged
|
|
176
|
+
group_of = new_group_of
|
|
177
|
+
|
|
178
|
+
return group_of
|
|
179
|
+
|
|
180
|
+
def _assign_positions(self, all_gates: list[GateNode]) -> dict[str, int]:
|
|
181
|
+
"""Iterative bit position assignment with cycle safety.
|
|
182
|
+
|
|
183
|
+
Only uses multi-bit port inputs for position info (single-bit ports
|
|
184
|
+
like 'clk' are broadcasts and don't carry position).
|
|
185
|
+
"""
|
|
186
|
+
positions: dict[str, int] = {}
|
|
187
|
+
|
|
188
|
+
# Direct: gates with multi-bit port_input wires get position from bit_index
|
|
189
|
+
for gate in all_gates:
|
|
190
|
+
for wire in gate.inputs.values():
|
|
191
|
+
if wire.kind == "port_input" and self.graph.input_ports.get(wire.name, 1) > 1:
|
|
192
|
+
positions[gate.name] = wire.bit_index
|
|
193
|
+
break
|
|
194
|
+
|
|
195
|
+
# Propagate: inherit from already-assigned source gates
|
|
196
|
+
changed = True
|
|
197
|
+
while changed:
|
|
198
|
+
changed = False
|
|
199
|
+
for gate in all_gates:
|
|
200
|
+
if gate.name in positions:
|
|
201
|
+
continue
|
|
202
|
+
for wire in gate.inputs.values():
|
|
203
|
+
if wire.kind == "gate_output" and wire.name in positions:
|
|
204
|
+
positions[gate.name] = positions[wire.name]
|
|
205
|
+
changed = True
|
|
206
|
+
break
|
|
207
|
+
|
|
208
|
+
# Fallback: gates with only single-bit port inputs get position 1
|
|
209
|
+
for gate in all_gates:
|
|
210
|
+
if gate.name not in positions:
|
|
211
|
+
for wire in gate.inputs.values():
|
|
212
|
+
if wire.kind == "port_input":
|
|
213
|
+
positions[gate.name] = wire.bit_index
|
|
214
|
+
break
|
|
215
|
+
|
|
216
|
+
return positions
|
|
217
|
+
|
|
218
|
+
def _classify_sources(self, group: BusGroup, gate_group_map: dict[str, str]):
|
|
219
|
+
"""Classify each input port source for a bus group."""
|
|
220
|
+
input_port_names = ["A", "B"] if group.primitive != "NOT" else ["A"]
|
|
221
|
+
|
|
222
|
+
for port_name in input_port_names:
|
|
223
|
+
wires = []
|
|
224
|
+
for gate in group.gates:
|
|
225
|
+
wire = gate.inputs.get(port_name)
|
|
226
|
+
wires.append(wire)
|
|
227
|
+
|
|
228
|
+
if not wires or wires[0] is None:
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
source = self._classify_wire_list(wires, group.bit_indices, gate_group_map)
|
|
232
|
+
group.input_sources[port_name] = source
|
|
233
|
+
|
|
234
|
+
def _classify_wire_list(
|
|
235
|
+
self, wires: list[WireRef], bit_indices: list[int],
|
|
236
|
+
gate_group_map: dict[str, str]
|
|
237
|
+
) -> BusSource:
|
|
238
|
+
"""Classify a list of wires (one per gate in the group)."""
|
|
239
|
+
if not wires or wires[0] is None:
|
|
240
|
+
return BusSource(kind="mixed", per_bit=wires)
|
|
241
|
+
|
|
242
|
+
# Check: all constant?
|
|
243
|
+
if all(w and w.kind == "constant" for w in wires):
|
|
244
|
+
val = wires[0].name
|
|
245
|
+
if all(w.name == val for w in wires):
|
|
246
|
+
return BusSource(kind="constant", ref=val)
|
|
247
|
+
|
|
248
|
+
# Check: all from same port?
|
|
249
|
+
if all(w and w.kind == "port_input" for w in wires):
|
|
250
|
+
port_name = wires[0].name
|
|
251
|
+
if all(w.name == port_name for w in wires):
|
|
252
|
+
if all(w.bit_index == bi for w, bi in zip(wires, bit_indices)):
|
|
253
|
+
# Check contiguity: bit_indices must be consecutive
|
|
254
|
+
contiguous = all(
|
|
255
|
+
bit_indices[i+1] == bit_indices[i] + 1
|
|
256
|
+
for i in range(len(bit_indices) - 1)
|
|
257
|
+
)
|
|
258
|
+
if contiguous:
|
|
259
|
+
shift = bit_indices[0] - 1 # 1-based to 0-based
|
|
260
|
+
return BusSource(kind="port_aligned", ref=port_name, shift=shift)
|
|
261
|
+
# Non-contiguous: fall through to mixed
|
|
262
|
+
if all(w.bit_index == wires[0].bit_index for w in wires):
|
|
263
|
+
return BusSource(kind="port_broadcast", ref=port_name,
|
|
264
|
+
broadcast_bit=wires[0].bit_index)
|
|
265
|
+
|
|
266
|
+
# Check: all from gate outputs in the same group?
|
|
267
|
+
if all(w and w.kind == "gate_output" for w in wires):
|
|
268
|
+
src_groups = set()
|
|
269
|
+
for w in wires:
|
|
270
|
+
grp = gate_group_map.get(w.name)
|
|
271
|
+
if grp:
|
|
272
|
+
src_groups.add(grp)
|
|
273
|
+
else:
|
|
274
|
+
src_groups.add(None)
|
|
275
|
+
|
|
276
|
+
if len(src_groups) == 1 and None not in src_groups:
|
|
277
|
+
group_name = src_groups.pop()
|
|
278
|
+
return BusSource(kind="bus_group", ref=group_name)
|
|
279
|
+
|
|
280
|
+
# Fallback: mixed
|
|
281
|
+
return BusSource(kind="mixed", per_bit=list(wires))
|
|
282
|
+
|
|
283
|
+
def _detect_feedback(self, groups: list[BusGroup]) -> dict[str, int]:
|
|
284
|
+
"""Detect feedback loops (SCCs). Returns gate_group_name -> scc_id mapping."""
|
|
285
|
+
group_map = {g.name: g for g in groups}
|
|
286
|
+
adj: dict[str, set[str]] = defaultdict(set)
|
|
287
|
+
|
|
288
|
+
for group in groups:
|
|
289
|
+
for source in group.input_sources.values():
|
|
290
|
+
if source.kind == "bus_group" and source.ref in group_map:
|
|
291
|
+
adj[source.ref].add(group.name)
|
|
292
|
+
|
|
293
|
+
# Tarjan's SCC
|
|
294
|
+
index_counter = [0]
|
|
295
|
+
stack = []
|
|
296
|
+
on_stack = set()
|
|
297
|
+
indices = {}
|
|
298
|
+
lowlinks = {}
|
|
299
|
+
sccs: list[list[str]] = []
|
|
300
|
+
|
|
301
|
+
def strongconnect(v):
|
|
302
|
+
indices[v] = index_counter[0]
|
|
303
|
+
lowlinks[v] = index_counter[0]
|
|
304
|
+
index_counter[0] += 1
|
|
305
|
+
stack.append(v)
|
|
306
|
+
on_stack.add(v)
|
|
307
|
+
|
|
308
|
+
for w in adj.get(v, set()):
|
|
309
|
+
if w not in indices:
|
|
310
|
+
strongconnect(w)
|
|
311
|
+
lowlinks[v] = min(lowlinks[v], lowlinks[w])
|
|
312
|
+
elif w in on_stack:
|
|
313
|
+
lowlinks[v] = min(lowlinks[v], indices[w])
|
|
314
|
+
|
|
315
|
+
if lowlinks[v] == indices[v]:
|
|
316
|
+
scc = []
|
|
317
|
+
while True:
|
|
318
|
+
w = stack.pop()
|
|
319
|
+
on_stack.discard(w)
|
|
320
|
+
scc.append(w)
|
|
321
|
+
if w == v:
|
|
322
|
+
break
|
|
323
|
+
sccs.append(scc)
|
|
324
|
+
|
|
325
|
+
for name in group_map:
|
|
326
|
+
if name not in indices:
|
|
327
|
+
strongconnect(name)
|
|
328
|
+
|
|
329
|
+
# Build SCC map and mark feedback groups
|
|
330
|
+
scc_map: dict[str, int] = {}
|
|
331
|
+
for scc_id, scc in enumerate(sccs):
|
|
332
|
+
if len(scc) > 1:
|
|
333
|
+
for name in scc:
|
|
334
|
+
if name in group_map:
|
|
335
|
+
group_map[name].is_feedback = True
|
|
336
|
+
group_map[name].scc_id = scc_id
|
|
337
|
+
scc_map[name] = scc_id
|
|
338
|
+
|
|
339
|
+
return scc_map
|
|
340
|
+
|
|
341
|
+
def _topological_sort(
|
|
342
|
+
self, groups: list[BusGroup], scc_map: dict[str, int]
|
|
343
|
+
) -> list[BusGroup]:
|
|
344
|
+
"""Topologically sort bus groups, removing SCC back-edges."""
|
|
345
|
+
from collections import deque
|
|
346
|
+
|
|
347
|
+
group_map = {g.name: g for g in groups}
|
|
348
|
+
|
|
349
|
+
# Build forward deps and reverse adjacency
|
|
350
|
+
deps: dict[str, set[str]] = {g.name: set() for g in groups}
|
|
351
|
+
rdeps: dict[str, set[str]] = {g.name: set() for g in groups}
|
|
352
|
+
for group in groups:
|
|
353
|
+
for source in group.input_sources.values():
|
|
354
|
+
if source.kind == "bus_group" and source.ref in group_map:
|
|
355
|
+
src_name = source.ref
|
|
356
|
+
if (group.name in scc_map and src_name in scc_map
|
|
357
|
+
and scc_map[group.name] == scc_map[src_name]):
|
|
358
|
+
continue
|
|
359
|
+
deps[group.name].add(src_name)
|
|
360
|
+
rdeps[src_name].add(group.name)
|
|
361
|
+
|
|
362
|
+
# Kahn's algorithm with reverse adjacency
|
|
363
|
+
in_degree = {g.name: len(deps[g.name]) for g in groups}
|
|
364
|
+
queue = deque(name for name, deg in in_degree.items() if deg == 0)
|
|
365
|
+
result = []
|
|
366
|
+
|
|
367
|
+
while queue:
|
|
368
|
+
name = queue.popleft()
|
|
369
|
+
result.append(group_map[name])
|
|
370
|
+
for dependent in rdeps.get(name, set()):
|
|
371
|
+
in_degree[dependent] -= 1
|
|
372
|
+
if in_degree[dependent] == 0:
|
|
373
|
+
queue.append(dependent)
|
|
374
|
+
|
|
375
|
+
# Add any remaining
|
|
376
|
+
if len(result) < len(groups):
|
|
377
|
+
visited = {g.name for g in result}
|
|
378
|
+
for g in groups:
|
|
379
|
+
if g.name not in visited:
|
|
380
|
+
result.append(g)
|
|
381
|
+
|
|
382
|
+
return result
|