PySHDL 0.3.0__tar.gz → 0.3.1__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.
Files changed (156) hide show
  1. {pyshdl-0.3.0 → pyshdl-0.3.1}/PKG-INFO +1 -1
  2. {pyshdl-0.3.0 → pyshdl-0.3.1}/pyproject.toml +1 -1
  3. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/bus_compiler/analyzer.py +24 -5
  4. pyshdl-0.3.1/src/SHDL/bus_compiler/compiler.py +137 -0
  5. pyshdl-0.3.1/src/SHDL/bus_compiler/debug_codegen.py +313 -0
  6. pyshdl-0.3.1/src/SHDL/bus_compiler/debug_info_gen.py +96 -0
  7. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/debugger/circuit.py +17 -18
  8. pyshdl-0.3.1/tests/test_bus_compiler.py +683 -0
  9. {pyshdl-0.3.0 → pyshdl-0.3.1}/uv.lock +1 -1
  10. pyshdl-0.3.0/src/SHDL/bus_compiler/compiler.py +0 -63
  11. {pyshdl-0.3.0 → pyshdl-0.3.1}/.github/workflows/deploy-docs.yml +0 -0
  12. {pyshdl-0.3.0 → pyshdl-0.3.1}/.github/workflows/publish.yml +0 -0
  13. {pyshdl-0.3.0 → pyshdl-0.3.1}/.github/workflows/test.yml +0 -0
  14. {pyshdl-0.3.0 → pyshdl-0.3.1}/.gitignore +0 -0
  15. {pyshdl-0.3.0 → pyshdl-0.3.1}/LICENSE +0 -0
  16. {pyshdl-0.3.0 → pyshdl-0.3.1}/README.md +0 -0
  17. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/README.md +0 -0
  18. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/architecture/_category_.json +0 -0
  19. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/architecture/base-shdl.md +0 -0
  20. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/architecture/compiler-internals.md +0 -0
  21. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/architecture/flattening-pipeline.md +0 -0
  22. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/architecture/overview.md +0 -0
  23. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/architecture/pyshdl-internals.md +0 -0
  24. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/_category_.json +0 -0
  25. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/breakpoints.md +0 -0
  26. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/commands.md +0 -0
  27. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/common-problems.md +0 -0
  28. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/debug-build.md +0 -0
  29. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/getting-started.md +0 -0
  30. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/hierarchy.md +0 -0
  31. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/inspection.md +0 -0
  32. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/overview.md +0 -0
  33. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/python-api.md +0 -0
  34. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/scripting.md +0 -0
  35. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/debugger/waveforms.md +0 -0
  36. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/examples/8-bit-adder.md +0 -0
  37. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/examples/_category_.json +0 -0
  38. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/examples/comparator.md +0 -0
  39. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/examples/decoder.md +0 -0
  40. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/examples/full-adder.md +0 -0
  41. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/examples/half-adder.md +0 -0
  42. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/examples/multiplexer.md +0 -0
  43. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/examples/register.md +0 -0
  44. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/getting-started/_category_.json +0 -0
  45. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/getting-started/first-circuit.md +0 -0
  46. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/getting-started/installation.md +0 -0
  47. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/getting-started/using-pyshdl.md +0 -0
  48. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/intro.md +0 -0
  49. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/_category_.json +0 -0
  50. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/components.md +0 -0
  51. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/connections.md +0 -0
  52. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/constants.md +0 -0
  53. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/errors.md +0 -0
  54. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/generators.md +0 -0
  55. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/imports.md +0 -0
  56. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/lexical-elements.md +0 -0
  57. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/overview.md +0 -0
  58. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/signals.md +0 -0
  59. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docs/language-reference/standard-gates.md +0 -0
  60. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/docusaurus.config.ts +0 -0
  61. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/package.json +0 -0
  62. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/sidebars.ts +0 -0
  63. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/src/components/HomepageFeatures/index.tsx +0 -0
  64. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/src/components/HomepageFeatures/styles.module.css +0 -0
  65. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/src/css/custom.css +0 -0
  66. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/src/pages/index.module.css +0 -0
  67. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/src/pages/index.tsx +0 -0
  68. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/src/pages/markdown-page.md +0 -0
  69. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/.nojekyll +0 -0
  70. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/img/docusaurus-social-card.jpg +0 -0
  71. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/img/docusaurus.png +0 -0
  72. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/img/favicon.ico +0 -0
  73. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/img/halfAdder.png +0 -0
  74. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/img/logo.svg +0 -0
  75. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/img/undraw_docusaurus_mountain.svg +0 -0
  76. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/img/undraw_docusaurus_react.svg +0 -0
  77. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/static/img/undraw_docusaurus_tree.svg +0 -0
  78. {pyshdl-0.3.0 → pyshdl-0.3.1}/docs/tsconfig.json +0 -0
  79. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/add100.shdl +0 -0
  80. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/addSub16.shdl +0 -0
  81. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/adder16.shdl +0 -0
  82. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/alu.shdl +0 -0
  83. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/and16inputs.shdl +0 -0
  84. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/bitwise_and16.shdl +0 -0
  85. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/bitwise_not16.shdl +0 -0
  86. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/bitwise_or16.shdl +0 -0
  87. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/bitwise_xor16.shdl +0 -0
  88. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/clock.shdl +0 -0
  89. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/demux.shdl +0 -0
  90. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/flagsZN.shdl +0 -0
  91. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/fullAdder.shdl +0 -0
  92. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/mux2.shdl +0 -0
  93. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/mux2_16.shdl +0 -0
  94. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/mux8.shdl +0 -0
  95. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/mux8_16.shdl +0 -0
  96. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/negate16.shdl +0 -0
  97. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/or16inputs.shdl +0 -0
  98. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/reg16.shdl +0 -0
  99. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/SHDL_components/shift1.shdl +0 -0
  100. {pyshdl-0.3.0 → pyshdl-0.3.1}/examples/interacting.py +0 -0
  101. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDB/__init__.py +0 -0
  102. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/__init__.py +0 -0
  103. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/bus_compiler/__init__.py +0 -0
  104. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/bus_compiler/codegen.py +0 -0
  105. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/bus_compiler/graph.py +0 -0
  106. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/__init__.py +0 -0
  107. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/analyzer.py +0 -0
  108. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/ast.py +0 -0
  109. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/cli.py +0 -0
  110. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/codegen.py +0 -0
  111. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/compiler.py +0 -0
  112. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/debug_codegen.py +0 -0
  113. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/debug_info_gen.py +0 -0
  114. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/lexer.py +0 -0
  115. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/compiler/parser.py +0 -0
  116. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/debugger/__init__.py +0 -0
  117. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/debugger/cli.py +0 -0
  118. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/debugger/controller.py +0 -0
  119. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/debugger/debuginfo.py +0 -0
  120. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/debugger/sourcemap.py +0 -0
  121. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/debugger/symbols.py +0 -0
  122. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/driver/__init__.py +0 -0
  123. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/driver/circuit.py +0 -0
  124. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/driver/exceptions.py +0 -0
  125. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/errors.py +0 -0
  126. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/flattener/__init__.py +0 -0
  127. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/flattener/ast.py +0 -0
  128. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/flattener/flattener.py +0 -0
  129. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/flattener/lexer.py +0 -0
  130. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/flattener/parser.py +0 -0
  131. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/flattener/tokens.py +0 -0
  132. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/py.typed +0 -0
  133. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/semantic/__init__.py +0 -0
  134. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/semantic/analyzer.py +0 -0
  135. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/semantic/connection.py +0 -0
  136. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/semantic/resolver.py +0 -0
  137. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/semantic/type_check.py +0 -0
  138. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/semantic/warnings.py +0 -0
  139. {pyshdl-0.3.0 → pyshdl-0.3.1}/src/SHDL/source_map.py +0 -0
  140. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/README.md +0 -0
  141. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/TEST_REPORT.md +0 -0
  142. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_adder4.shdl +0 -0
  143. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_adder8.shdl +0 -0
  144. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_bitwise.shdl +0 -0
  145. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_comparator.shdl +0 -0
  146. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_constants.shdl +0 -0
  147. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_decoder.shdl +0 -0
  148. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_gates.shdl +0 -0
  149. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_generators.shdl +0 -0
  150. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_half_full_adder.shdl +0 -0
  151. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/circuits/test_mux.shdl +0 -0
  152. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/conftest.py +0 -0
  153. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/test_debugger.py +0 -0
  154. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/test_errors.py +0 -0
  155. {pyshdl-0.3.0 → pyshdl-0.3.1}/tests/test_shdl.py +0 -0
  156. {pyshdl-0.3.0 → pyshdl-0.3.1}/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.0
3
+ Version: 0.3.1
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.0"
3
+ version = "0.3.1"
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 = [
@@ -114,12 +114,18 @@ class BusAnalyzer:
114
114
  grouped_gates.add(gate.name)
115
115
  gate_group_map[gate.name] = name
116
116
 
117
+ # Build position map: gate_name -> (group_name, position_in_group)
118
+ gate_position_map: dict[str, tuple[str, int]] = {}
119
+ for group in bus_groups:
120
+ for pos, gate in enumerate(group.gates):
121
+ gate_position_map[gate.name] = (group.name, pos)
122
+
117
123
  # Singletons
118
124
  singletons = [g for g in all_gates if g.name not in grouped_gates]
119
125
 
120
126
  # Step 4: Classify sources for each bus group
121
127
  for group in bus_groups:
122
- self._classify_sources(group, gate_group_map)
128
+ self._classify_sources(group, gate_group_map, gate_position_map)
123
129
 
124
130
  # Step 5: SCC detection
125
131
  scc_map = self._detect_feedback(bus_groups)
@@ -215,7 +221,8 @@ class BusAnalyzer:
215
221
 
216
222
  return positions
217
223
 
218
- def _classify_sources(self, group: BusGroup, gate_group_map: dict[str, str]):
224
+ def _classify_sources(self, group: BusGroup, gate_group_map: dict[str, str],
225
+ gate_position_map: dict[str, tuple[str, int]]):
219
226
  """Classify each input port source for a bus group."""
220
227
  input_port_names = ["A", "B"] if group.primitive != "NOT" else ["A"]
221
228
 
@@ -228,12 +235,14 @@ class BusAnalyzer:
228
235
  if not wires or wires[0] is None:
229
236
  continue
230
237
 
231
- source = self._classify_wire_list(wires, group.bit_indices, gate_group_map)
238
+ source = self._classify_wire_list(wires, group.bit_indices, gate_group_map,
239
+ gate_position_map)
232
240
  group.input_sources[port_name] = source
233
241
 
234
242
  def _classify_wire_list(
235
243
  self, wires: list[WireRef], bit_indices: list[int],
236
- gate_group_map: dict[str, str]
244
+ gate_group_map: dict[str, str],
245
+ gate_position_map: dict[str, tuple[str, int]]
237
246
  ) -> BusSource:
238
247
  """Classify a list of wires (one per gate in the group)."""
239
248
  if not wires or wires[0] is None:
@@ -275,7 +284,17 @@ class BusAnalyzer:
275
284
 
276
285
  if len(src_groups) == 1 and None not in src_groups:
277
286
  group_name = src_groups.pop()
278
- return BusSource(kind="bus_group", ref=group_name)
287
+ # Verify sequential alignment: source positions must be 0,1,2,...
288
+ positions = []
289
+ misaligned = False
290
+ for w in wires:
291
+ info = gate_position_map.get(w.name)
292
+ if info is None or info[0] != group_name:
293
+ misaligned = True
294
+ break
295
+ positions.append(info[1])
296
+ if not misaligned and positions == list(range(len(positions))):
297
+ return BusSource(kind="bus_group", ref=group_name)
279
298
 
280
299
  # Fallback: mixed
281
300
  return BusSource(kind="mixed", per_bit=list(wires))
@@ -0,0 +1,137 @@
1
+ """
2
+ Bus Compiler Orchestration.
3
+
4
+ Pipeline: Flattened Component -> ConnectionGraph -> BusAnalyzer -> BusCodeGenerator -> clang
5
+ """
6
+
7
+ import os
8
+ import subprocess
9
+ import tempfile
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ from ..compiler.compiler import CompileResult
14
+ from .graph import ConnectionGraph
15
+ from .analyzer import BusAnalyzer
16
+ from .codegen import BusCodeGenerator
17
+ from .debug_codegen import BusDebugCodeGenerator
18
+ from .debug_info_gen import BusDebugInfoBuilder
19
+
20
+
21
+ class BusCompiler:
22
+ """Compiles a flattened Component to C using bus-width operations."""
23
+
24
+ def compile(self, component) -> str:
25
+ """Generate C code from a flattened Component (expanded AST)."""
26
+ graph = ConnectionGraph.from_component(component)
27
+ analysis = BusAnalyzer(graph).analyze()
28
+ return BusCodeGenerator(analysis).generate()
29
+
30
+ def compile_debug(self, component) -> str:
31
+ """Generate C code with debug API from a flattened Component."""
32
+ graph = ConnectionGraph.from_component(component)
33
+ analysis = BusAnalyzer(graph).analyze()
34
+ return BusDebugCodeGenerator(analysis).generate()
35
+
36
+ def _analyze(self, component):
37
+ """Run the analysis pipeline, returning the AnalysisResult."""
38
+ graph = ConnectionGraph.from_component(component)
39
+ return BusAnalyzer(graph).analyze()
40
+
41
+ def compile_to_library(
42
+ self,
43
+ component,
44
+ output_path: str,
45
+ cc: str = "clang",
46
+ cflags: Optional[list[str]] = None,
47
+ ) -> CompileResult:
48
+ """Compile a flattened Component to a shared library."""
49
+ c_code = self.compile(component)
50
+
51
+ with tempfile.NamedTemporaryFile(
52
+ mode='w', suffix='.c', delete=False
53
+ ) as f:
54
+ f.write(c_code)
55
+ c_path = f.name
56
+
57
+ try:
58
+ default_flags = ["-O3", "-shared", "-fPIC"]
59
+ all_flags = default_flags + (cflags or [])
60
+ cmd = [cc] + all_flags + ["-o", output_path, c_path]
61
+
62
+ proc = subprocess.run(cmd, capture_output=True, text=True)
63
+
64
+ if proc.returncode != 0:
65
+ return CompileResult(
66
+ success=False,
67
+ c_code=c_code,
68
+ errors=[f"C compilation failed: {proc.stderr}"],
69
+ )
70
+
71
+ return CompileResult(
72
+ success=True,
73
+ c_code=c_code,
74
+ library_path=output_path,
75
+ )
76
+ finally:
77
+ os.unlink(c_path)
78
+
79
+ def compile_to_library_debug(
80
+ self,
81
+ component,
82
+ output_path: str,
83
+ component_name: str = "",
84
+ source_path: str = "",
85
+ cc: str = "clang",
86
+ cflags: Optional[list[str]] = None,
87
+ generate_shdb: bool = True,
88
+ ) -> CompileResult:
89
+ """Compile a flattened Component to a shared library with debug support.
90
+
91
+ Produces:
92
+ - A shared library with peek_gate, peek_gate_previous, get_cycle
93
+ - A .shdb file with gate names, ports, hierarchy info
94
+ """
95
+ analysis = self._analyze(component)
96
+ c_code = BusDebugCodeGenerator(analysis).generate()
97
+
98
+ with tempfile.NamedTemporaryFile(
99
+ mode='w', suffix='.c', delete=False
100
+ ) as f:
101
+ f.write(c_code)
102
+ c_path = f.name
103
+
104
+ debug_info_path = None
105
+
106
+ try:
107
+ # Generate .shdb if requested
108
+ if generate_shdb:
109
+ lib_path = Path(output_path)
110
+ shdb_path = lib_path.with_suffix('.shdb')
111
+ builder = BusDebugInfoBuilder(analysis, source_file=source_path)
112
+ builder.set_component_name(component_name)
113
+ builder.save(str(shdb_path))
114
+ debug_info_path = str(shdb_path)
115
+
116
+ # Compile with -g for C debug symbols, -O1 for debug builds
117
+ default_flags = ["-g", "-O1", "-shared", "-fPIC"]
118
+ all_flags = default_flags + (cflags or [])
119
+ cmd = [cc] + all_flags + ["-o", output_path, c_path]
120
+
121
+ proc = subprocess.run(cmd, capture_output=True, text=True)
122
+
123
+ if proc.returncode != 0:
124
+ return CompileResult(
125
+ success=False,
126
+ c_code=c_code,
127
+ errors=[f"C compilation failed: {proc.stderr}"],
128
+ )
129
+
130
+ return CompileResult(
131
+ success=True,
132
+ c_code=c_code,
133
+ library_path=output_path,
134
+ debug_info_path=debug_info_path,
135
+ )
136
+ finally:
137
+ os.unlink(c_path)
@@ -0,0 +1,313 @@
1
+ """
2
+ Debug-Aware Bus Code Generator.
3
+
4
+ Extends BusCodeGenerator with debug features:
5
+ - Gate name table mapping gate names to group/singleton + bit position
6
+ - peek_gate() / peek_gate_previous() for inspecting internal gates
7
+ - Cycle counter and get_cycle()
8
+ - Previous state tracking for breakpoint change detection
9
+
10
+ Key difference from parent: all bus group and singleton gate results are stored
11
+ in static file-scope variables (not locals in tick), so peek_gate can read them.
12
+ """
13
+
14
+ from ..compiler.ast import PrimitiveType
15
+ from .codegen import BusCodeGenerator, _select_c_type, _width_mask
16
+ from .analyzer import AnalysisResult, BusGroup
17
+
18
+
19
+ class BusDebugCodeGenerator(BusCodeGenerator):
20
+ """Bus code generator with debug API additions.
21
+
22
+ Overrides _emit_group_eval and _emit_unit to assign to static globals
23
+ instead of declaring locals, so gate values persist after tick() returns.
24
+ """
25
+
26
+ def __init__(self, analysis: AnalysisResult):
27
+ super().__init__(analysis)
28
+ self._group_list: list[BusGroup] = []
29
+ self._group_idx: dict[str, int] = {}
30
+
31
+ def generate(self) -> str:
32
+ self._emit_header()
33
+ self._emit_state_struct()
34
+ self._emit_gate_globals()
35
+ self._emit_dut_context_debug()
36
+ self._emit_gate_table()
37
+ # Parent's tick — our overrides make it assign to statics
38
+ self._emit_tick_function()
39
+ self._emit_api_functions_debug()
40
+ self._emit_debug_api()
41
+ return self.output.getvalue()
42
+
43
+ # ── Gate globals (static file-scope vars) ──
44
+
45
+ def _emit_gate_globals(self):
46
+ """Emit static variables for all groups and singletons."""
47
+ self._group_list = list(self.analysis.bus_groups)
48
+ self._group_idx = {g.name: i for i, g in enumerate(self._group_list)}
49
+
50
+ self._w("/* Bus group values (persist between tick calls for debug) */")
51
+ for group in self._group_list:
52
+ c_type = _select_c_type(group.width)
53
+ self._w(f"static {c_type} {group.name};")
54
+ self._w(f"static {c_type} dbg_prev_{group.name};")
55
+
56
+ if self.analysis.singleton_gates:
57
+ self._w()
58
+ self._w("/* Singleton gate values */")
59
+ for gate in self.analysis.singleton_gates:
60
+ self._w(f"static uint8_t s_{gate.name};")
61
+ self._w(f"static uint8_t dbg_prev_s_{gate.name};")
62
+
63
+ self._w()
64
+
65
+ # ── DUT context (debug version with previous + cycle) ──
66
+
67
+ def _emit_dut_context_debug(self):
68
+ self._w("typedef struct {")
69
+ self._indent()
70
+ self._w("State current;")
71
+ self._w("State previous;")
72
+ for name, width in self.analysis.input_ports.items():
73
+ self._w(f"{_select_c_type(width)} input_{name};")
74
+ for name, width in self.analysis.output_ports.items():
75
+ self._w(f"{_select_c_type(width)} output_{name};")
76
+ self._w("int outputs_valid;")
77
+ self._w("uint64_t cycle_count;")
78
+ self._dedent()
79
+ self._w("} DutContext;")
80
+ self._w()
81
+ self._w("static DutContext dut = {0};")
82
+ self._w()
83
+
84
+ # ── Gate table ──
85
+
86
+ def _emit_gate_table(self):
87
+ """Emit gate lookup table: name -> (is_group, index, bit_pos)."""
88
+ self._w("typedef struct {")
89
+ self._indent()
90
+ self._w("const char *name;")
91
+ self._w("uint8_t is_group;")
92
+ self._w("uint16_t idx;")
93
+ self._w("uint8_t bit_pos;")
94
+ self._dedent()
95
+ self._w("} GateEntry;")
96
+ self._w()
97
+
98
+ entries: list[tuple[str, int, int, int]] = []
99
+
100
+ for group in self._group_list:
101
+ gidx = self._group_idx[group.name]
102
+ for pos, gate in enumerate(group.gates):
103
+ entries.append((gate.name, 1, gidx, pos))
104
+
105
+ for i, gate in enumerate(self.analysis.singleton_gates):
106
+ entries.append((gate.name, 0, i, 0))
107
+
108
+ self._w("static const GateEntry GATE_TABLE[] = {")
109
+ self._indent()
110
+ for name, is_grp, idx, bit in entries:
111
+ self._w(f'{{"{name}", {is_grp}, {idx}, {bit}}},')
112
+ if not entries:
113
+ self._w('{NULL, 0, 0, 0},')
114
+ self._dedent()
115
+ self._w("};")
116
+ self._w(f"static const size_t NUM_GATES = {len(entries)};")
117
+ self._w()
118
+
119
+ # ── Override group/unit emit to assign to statics ──
120
+
121
+ def _emit_group_eval(self, group: BusGroup, is_feedback: bool):
122
+ """Assign to static global instead of declaring a local."""
123
+ var = group.name
124
+ self._group_vars[group.name] = var
125
+
126
+ ptype = PrimitiveType.from_string(group.primitive)
127
+ mask = _width_mask(group.width)
128
+
129
+ a_expr = self._source_to_expr(group.input_sources.get("A"), group, is_feedback)
130
+ b_expr = self._source_to_expr(group.input_sources.get("B"), group, is_feedback)
131
+
132
+ if ptype == PrimitiveType.NOT:
133
+ self._w(f"{var} = (~({a_expr})) & {mask};")
134
+ elif ptype == PrimitiveType.AND:
135
+ self._w(f"{var} = ({a_expr}) & ({b_expr});")
136
+ elif ptype == PrimitiveType.OR:
137
+ self._w(f"{var} = ({a_expr}) | ({b_expr});")
138
+ elif ptype == PrimitiveType.XOR:
139
+ self._w(f"{var} = ({a_expr}) ^ ({b_expr});")
140
+
141
+ def _emit_unit(self, unit):
142
+ """Assign to static global instead of declaring a local."""
143
+ if isinstance(unit, BusGroup):
144
+ self._emit_group_eval(unit, is_feedback=False)
145
+ else:
146
+ var = f"s_{unit.name}"
147
+ self._singleton_vars[unit.name] = var
148
+ expr = self._build_singleton_expr(unit)
149
+ self._w(f"{var} = {expr};")
150
+
151
+ # ── API functions (debug versions) ──
152
+
153
+ def _emit_api_functions_debug(self):
154
+ self._emit_reset_debug()
155
+ self._emit_poke() # parent's
156
+ self._emit_peek() # parent's
157
+ self._emit_step_debug()
158
+
159
+ def _emit_reset_debug(self):
160
+ self._w("void reset(void) {")
161
+ self._indent()
162
+ self._w("memset(&dut, 0, sizeof(dut));")
163
+ # Zero all static gate variables
164
+ for group in self._group_list:
165
+ self._w(f"{group.name} = 0;")
166
+ self._w(f"dbg_prev_{group.name} = 0;")
167
+ for gate in self.analysis.singleton_gates:
168
+ self._w(f"s_{gate.name} = 0;")
169
+ self._w(f"dbg_prev_s_{gate.name} = 0;")
170
+ self._dedent()
171
+ self._w("}")
172
+ self._w()
173
+
174
+ def _emit_step_debug(self):
175
+ self._w("void step(int cycles) {")
176
+ self._indent()
177
+ self._w("for (int i = 0; i < cycles; ++i) {")
178
+ self._indent()
179
+
180
+ # Save previous state for breakpoint detection
181
+ self._w("dut.previous = dut.current;")
182
+ for group in self._group_list:
183
+ self._w(f"dbg_prev_{group.name} = {group.name};")
184
+ for gate in self.analysis.singleton_gates:
185
+ self._w(f"dbg_prev_s_{gate.name} = s_{gate.name};")
186
+
187
+ self._w("tick();")
188
+ self._w("dut.cycle_count++;")
189
+ self._dedent()
190
+ self._w("}")
191
+ self._w("dut.outputs_valid = 1;")
192
+ self._dedent()
193
+ self._w("}")
194
+ self._w()
195
+
196
+ # ── Debug API functions ──
197
+
198
+ def _emit_debug_api(self):
199
+ self._emit_get_cycle()
200
+ self._emit_peek_gate()
201
+ self._emit_peek_gate_previous()
202
+ self._emit_get_num_gates()
203
+
204
+ def _emit_get_cycle(self):
205
+ self._w("uint64_t get_cycle(void) {")
206
+ self._indent()
207
+ self._w("return dut.cycle_count;")
208
+ self._dedent()
209
+ self._w("}")
210
+ self._w()
211
+
212
+ def _emit_peek_gate(self):
213
+ self._w("uint64_t peek_gate(const char *gate_name) {")
214
+ self._indent()
215
+
216
+ # Ensure circuit is evaluated
217
+ self._w("if (!dut.outputs_valid) {")
218
+ self._indent()
219
+ self._w("tick();")
220
+ self._w("dut.outputs_valid = 1;")
221
+ self._dedent()
222
+ self._w("}")
223
+ self._w()
224
+
225
+ self._w("for (size_t i = 0; i < NUM_GATES; i++) {")
226
+ self._indent()
227
+ self._w("if (strcmp(GATE_TABLE[i].name, gate_name) == 0) {")
228
+ self._indent()
229
+
230
+ self._w("if (GATE_TABLE[i].is_group) {")
231
+ self._indent()
232
+ self._w("uint64_t val;")
233
+ self._emit_group_switch("val", is_prev=False)
234
+ self._w("return (val >> GATE_TABLE[i].bit_pos) & 1ull;")
235
+ self._dedent()
236
+
237
+ self._w("} else {")
238
+ self._indent()
239
+ self._emit_singleton_switch(is_prev=False)
240
+ self._dedent()
241
+ self._w("}")
242
+
243
+ self._dedent()
244
+ self._w("}")
245
+ self._dedent()
246
+ self._w("}")
247
+ self._w()
248
+ self._w("return 0ull;")
249
+ self._dedent()
250
+ self._w("}")
251
+ self._w()
252
+
253
+ def _emit_peek_gate_previous(self):
254
+ self._w("uint64_t peek_gate_previous(const char *gate_name) {")
255
+ self._indent()
256
+
257
+ self._w("for (size_t i = 0; i < NUM_GATES; i++) {")
258
+ self._indent()
259
+ self._w("if (strcmp(GATE_TABLE[i].name, gate_name) == 0) {")
260
+ self._indent()
261
+
262
+ self._w("if (GATE_TABLE[i].is_group) {")
263
+ self._indent()
264
+ self._w("uint64_t val;")
265
+ self._emit_group_switch("val", is_prev=True)
266
+ self._w("return (val >> GATE_TABLE[i].bit_pos) & 1ull;")
267
+ self._dedent()
268
+
269
+ self._w("} else {")
270
+ self._indent()
271
+ self._emit_singleton_switch(is_prev=True)
272
+ self._dedent()
273
+ self._w("}")
274
+
275
+ self._dedent()
276
+ self._w("}")
277
+ self._dedent()
278
+ self._w("}")
279
+ self._w()
280
+ self._w("return 0ull;")
281
+ self._dedent()
282
+ self._w("}")
283
+ self._w()
284
+
285
+ def _emit_get_num_gates(self):
286
+ self._w("size_t get_num_gates(void) {")
287
+ self._indent()
288
+ self._w("return NUM_GATES;")
289
+ self._dedent()
290
+ self._w("}")
291
+ self._w()
292
+
293
+ # ── Helpers for peek_gate switch statements ──
294
+
295
+ def _emit_group_switch(self, target_var: str, is_prev: bool):
296
+ prefix = "dbg_prev_" if is_prev else ""
297
+ self._w("switch (GATE_TABLE[i].idx) {")
298
+ self._indent()
299
+ for idx, group in enumerate(self._group_list):
300
+ self._w(f"case {idx}: {target_var} = {prefix}{group.name}; break;")
301
+ self._w(f"default: {target_var} = 0; break;")
302
+ self._dedent()
303
+ self._w("}")
304
+
305
+ def _emit_singleton_switch(self, is_prev: bool):
306
+ prefix = "dbg_prev_s_" if is_prev else "s_"
307
+ self._w("switch (GATE_TABLE[i].idx) {")
308
+ self._indent()
309
+ for idx, gate in enumerate(self.analysis.singleton_gates):
310
+ self._w(f"case {idx}: return (uint64_t){prefix}{gate.name};")
311
+ self._w("default: return 0ull;")
312
+ self._dedent()
313
+ self._w("}")
@@ -0,0 +1,96 @@
1
+ """
2
+ Debug Info Generator for Bus Compiler.
3
+
4
+ Generates .shdb debug info files from bus compiler AnalysisResult,
5
+ matching the format expected by the debugger (DebugInfo).
6
+ """
7
+
8
+ from pathlib import Path
9
+ import json
10
+
11
+ from .analyzer import AnalysisResult
12
+
13
+
14
+ class BusDebugInfoBuilder:
15
+ """Builds .shdb debug info from bus compiler analysis."""
16
+
17
+ def __init__(self, analysis: AnalysisResult, source_file: str = ""):
18
+ self.analysis = analysis
19
+ self.source_file = source_file
20
+ self.component_name = ""
21
+
22
+ def set_component_name(self, name: str) -> "BusDebugInfoBuilder":
23
+ self.component_name = name
24
+ return self
25
+
26
+ def build(self) -> dict:
27
+ """Build the debug info dictionary."""
28
+ gates = {}
29
+
30
+ # Gates from bus groups
31
+ for group in self.analysis.bus_groups:
32
+ for pos, gate in enumerate(group.gates):
33
+ gates[gate.name] = {
34
+ "type": gate.primitive,
35
+ "lane": pos,
36
+ "chunk": 0, # Bus compiler doesn't use chunk/lane packing
37
+ "hierarchy_path": self._infer_hierarchy(gate.name),
38
+ "original_name": self._extract_original(gate.name),
39
+ "parent_instance": self._extract_parent(gate.name),
40
+ }
41
+
42
+ # Singleton gates
43
+ for gate in self.analysis.singleton_gates:
44
+ gates[gate.name] = {
45
+ "type": gate.primitive,
46
+ "lane": 0,
47
+ "chunk": 0,
48
+ "hierarchy_path": self._infer_hierarchy(gate.name),
49
+ "original_name": self._extract_original(gate.name),
50
+ "parent_instance": self._extract_parent(gate.name),
51
+ }
52
+
53
+ return {
54
+ "version": "1.0",
55
+ "component": self.component_name,
56
+ "source_file": self.source_file,
57
+ "ports": {
58
+ "inputs": [
59
+ {"name": name, "width": width, "source_line": 0, "source_column": 0}
60
+ for name, width in self.analysis.input_ports.items()
61
+ ],
62
+ "outputs": [
63
+ {"name": name, "width": width, "source_line": 0, "source_column": 0}
64
+ for name, width in self.analysis.output_ports.items()
65
+ ],
66
+ },
67
+ "hierarchy": {},
68
+ "gates": gates,
69
+ "connections": [],
70
+ "constants": {},
71
+ "source_map": {},
72
+ }
73
+
74
+ def save(self, path: str) -> None:
75
+ """Save to a .shdb file."""
76
+ data = self.build()
77
+ with open(path, "w") as f:
78
+ json.dump(data, f, indent=2)
79
+
80
+ def _infer_hierarchy(self, gate_name: str) -> str:
81
+ if "_" in gate_name:
82
+ parts = gate_name.split("_")
83
+ return f"{self.component_name}/" + "/".join(parts)
84
+ return f"{self.component_name}/{gate_name}"
85
+
86
+ def _extract_original(self, gate_name: str) -> str:
87
+ if "_" in gate_name:
88
+ return gate_name.split("_")[-1]
89
+ return gate_name
90
+
91
+ def _extract_parent(self, gate_name: str) -> str:
92
+ if "_" in gate_name:
93
+ parts = gate_name.split("_")
94
+ if len(parts) > 1:
95
+ return "_".join(parts[:-1])
96
+ return ""