rocket-engine 0.0.2b1__py3-none-any.whl

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 (241) hide show
  1. rocket_engine/__init__.py +8 -0
  2. rocket_engine/__main__.py +12 -0
  3. rocket_engine/adapters/README.md +54 -0
  4. rocket_engine/adapters/__init__.py +7 -0
  5. rocket_engine/adapters/api.py +39 -0
  6. rocket_engine/adapters/base.py +46 -0
  7. rocket_engine/adapters/cace/__init__.py +163 -0
  8. rocket_engine/adapters/circulax/README.md +10 -0
  9. rocket_engine/adapters/circulax/__init__.py +112 -0
  10. rocket_engine/adapters/circulax/_driver.py +190 -0
  11. rocket_engine/adapters/circulax/adapter.py +538 -0
  12. rocket_engine/adapters/circulax/results.py +291 -0
  13. rocket_engine/adapters/cli/README.md +34 -0
  14. rocket_engine/adapters/cli/__init__.py +305 -0
  15. rocket_engine/adapters/connectivity/__init__.py +196 -0
  16. rocket_engine/adapters/connectivity/results.py +214 -0
  17. rocket_engine/adapters/cosim/README.md +9 -0
  18. rocket_engine/adapters/cosim/__init__.py +1390 -0
  19. rocket_engine/adapters/cosim/results.py +214 -0
  20. rocket_engine/adapters/devsim/__init__.py +171 -0
  21. rocket_engine/adapters/digital_flow/README.md +9 -0
  22. rocket_engine/adapters/digital_flow/__init__.py +1121 -0
  23. rocket_engine/adapters/digital_flow/results.py +227 -0
  24. rocket_engine/adapters/digital_flow/state.py +462 -0
  25. rocket_engine/adapters/drc/README.klayout.md +7 -0
  26. rocket_engine/adapters/drc/README.md +7 -0
  27. rocket_engine/adapters/drc/__init__.py +1 -0
  28. rocket_engine/adapters/drc/klayout.py +952 -0
  29. rocket_engine/adapters/drc/magic.py +287 -0
  30. rocket_engine/adapters/drc/results.py +184 -0
  31. rocket_engine/adapters/drc/results_kl.py +188 -0
  32. rocket_engine/adapters/elmer/__init__.py +163 -0
  33. rocket_engine/adapters/envelope.py +120 -0
  34. rocket_engine/adapters/equivalence/__init__.py +156 -0
  35. rocket_engine/adapters/errors.py +181 -0
  36. rocket_engine/adapters/extract_magic/README.md +15 -0
  37. rocket_engine/adapters/extract_magic/__init__.py +425 -0
  38. rocket_engine/adapters/extract_magic/results.py +209 -0
  39. rocket_engine/adapters/formal_sby/README.md +7 -0
  40. rocket_engine/adapters/formal_sby/__init__.py +773 -0
  41. rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
  42. rocket_engine/adapters/formal_sby/results.py +185 -0
  43. rocket_engine/adapters/gdsfactory/__init__.py +194 -0
  44. rocket_engine/adapters/gdsfactory/_driver.py +459 -0
  45. rocket_engine/adapters/gdsfactory/adapter.py +609 -0
  46. rocket_engine/adapters/gdsfactory/results.py +181 -0
  47. rocket_engine/adapters/hdl_sim/README.md +7 -0
  48. rocket_engine/adapters/hdl_sim/__init__.py +369 -0
  49. rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
  50. rocket_engine/adapters/hdl_sim/results.py +178 -0
  51. rocket_engine/adapters/hdl_sim/verilator.py +167 -0
  52. rocket_engine/adapters/http/README.md +15 -0
  53. rocket_engine/adapters/http/__init__.py +458 -0
  54. rocket_engine/adapters/http/events.py +63 -0
  55. rocket_engine/adapters/ipc/README.md +9 -0
  56. rocket_engine/adapters/ipc/__init__.py +111 -0
  57. rocket_engine/adapters/lvs_netgen/README.md +20 -0
  58. rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
  59. rocket_engine/adapters/lvs_netgen/results.py +168 -0
  60. rocket_engine/adapters/mcp/README.md +19 -0
  61. rocket_engine/adapters/mcp/__init__.py +85 -0
  62. rocket_engine/adapters/mcp/api-schema.json +6107 -0
  63. rocket_engine/adapters/mcp/pull.py +246 -0
  64. rocket_engine/adapters/mcp/server.py +794 -0
  65. rocket_engine/adapters/meep/__init__.py +167 -0
  66. rocket_engine/adapters/meow/README.md +7 -0
  67. rocket_engine/adapters/meow/__init__.py +190 -0
  68. rocket_engine/adapters/meow/_driver.py +227 -0
  69. rocket_engine/adapters/meow/_envelope.py +108 -0
  70. rocket_engine/adapters/meow/adapter.py +491 -0
  71. rocket_engine/adapters/meow/results.py +218 -0
  72. rocket_engine/adapters/mutation/__init__.py +192 -0
  73. rocket_engine/adapters/ngspice/README.md +28 -0
  74. rocket_engine/adapters/ngspice/__init__.py +627 -0
  75. rocket_engine/adapters/ngspice/results.py +215 -0
  76. rocket_engine/adapters/postlayout/README.md +28 -0
  77. rocket_engine/adapters/postlayout/__init__.py +17 -0
  78. rocket_engine/adapters/postlayout/adapter.py +1062 -0
  79. rocket_engine/adapters/postlayout/results.py +330 -0
  80. rocket_engine/adapters/postlayout/translate.py +71 -0
  81. rocket_engine/adapters/profile.py +220 -0
  82. rocket_engine/adapters/sax/README.md +23 -0
  83. rocket_engine/adapters/sax/__init__.py +516 -0
  84. rocket_engine/adapters/sax/_driver.py +148 -0
  85. rocket_engine/adapters/sax/results.py +291 -0
  86. rocket_engine/adapters/stdio_console.py +1151 -0
  87. rocket_engine/adapters/syntax/README.md +5 -0
  88. rocket_engine/adapters/syntax/__init__.py +366 -0
  89. rocket_engine/adapters/syntax/results.py +181 -0
  90. rocket_engine/adapters/synth_yosys/README.md +7 -0
  91. rocket_engine/adapters/synth_yosys/__init__.py +655 -0
  92. rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
  93. rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
  94. rocket_engine/adapters/synth_yosys/results.py +173 -0
  95. rocket_engine/adapters/timing_sta/README.md +9 -0
  96. rocket_engine/adapters/timing_sta/__init__.py +726 -0
  97. rocket_engine/adapters/view_sync/__init__.py +185 -0
  98. rocket_engine/adapters/view_sync/results.py +189 -0
  99. rocket_engine/adapters/vlsir/__init__.py +142 -0
  100. rocket_engine/adapters/xschem/__init__.py +440 -0
  101. rocket_engine/adapters/xschem/deck.py +266 -0
  102. rocket_engine/clients/__init__.py +5 -0
  103. rocket_engine/clients/runtime.py +255 -0
  104. rocket_engine/composition.py +2279 -0
  105. rocket_engine/execution/README.md +30 -0
  106. rocket_engine/execution/__init__.py +150 -0
  107. rocket_engine/execution/apig/__init__.py +1 -0
  108. rocket_engine/execution/apig/pool.py +463 -0
  109. rocket_engine/execution/apig/submit.py +124 -0
  110. rocket_engine/execution/block_runs_scope.py +479 -0
  111. rocket_engine/execution/coalesce.py +414 -0
  112. rocket_engine/execution/console/__init__.py +1 -0
  113. rocket_engine/execution/console/apply_session.py +318 -0
  114. rocket_engine/execution/console/lifecycle.py +265 -0
  115. rocket_engine/execution/dispatch.py +205 -0
  116. rocket_engine/execution/errors.py +31 -0
  117. rocket_engine/execution/ports.py +797 -0
  118. rocket_engine/execution/pyexec/__init__.py +1 -0
  119. rocket_engine/execution/pyexec/apply_context.py +547 -0
  120. rocket_engine/execution/pyexec/approvals.py +173 -0
  121. rocket_engine/execution/pyexec/runner.py +404 -0
  122. rocket_engine/execution/pyexec/support.py +464 -0
  123. rocket_engine/execution/python_exec.py +897 -0
  124. rocket_engine/execution/queue.py +85 -0
  125. rocket_engine/execution/replay_safe.py +601 -0
  126. rocket_engine/execution/routing/__init__.py +1 -0
  127. rocket_engine/execution/routing/calls.py +364 -0
  128. rocket_engine/execution/routing/namespace.py +785 -0
  129. rocket_engine/execution/routing/ports.py +102 -0
  130. rocket_engine/execution/routing/tools.py +346 -0
  131. rocket_engine/execution/runner.py +311 -0
  132. rocket_engine/execution/runtime.py +1149 -0
  133. rocket_engine/execution/service.py +279 -0
  134. rocket_engine/execution/worker.py +464 -0
  135. rocket_engine/interfaces/README.md +26 -0
  136. rocket_engine/interfaces/__init__.py +58 -0
  137. rocket_engine/interfaces/apig_assets.py +407 -0
  138. rocket_engine/interfaces/celllink.py +461 -0
  139. rocket_engine/interfaces/documents.py +92 -0
  140. rocket_engine/interfaces/envelopes.py +131 -0
  141. rocket_engine/interfaces/errors.py +16 -0
  142. rocket_engine/interfaces/layout.py +410 -0
  143. rocket_engine/interfaces/layout_schema.yaml +278 -0
  144. rocket_engine/interfaces/paths.py +223 -0
  145. rocket_engine/interfaces/pdks.py +550 -0
  146. rocket_engine/interfaces/ports.py +72 -0
  147. rocket_engine/interfaces/references.py +115 -0
  148. rocket_engine/interfaces/refs.py +85 -0
  149. rocket_engine/interfaces/runparams.py +304 -0
  150. rocket_engine/interfaces/setup.py +552 -0
  151. rocket_engine/interfaces/setup_catalogue.yaml +243 -0
  152. rocket_engine/interfaces/setup_probes.py +66 -0
  153. rocket_engine/interfaces/vocabulary.py +58 -0
  154. rocket_engine/interfaces/yaml_dump.py +31 -0
  155. rocket_engine/library/README.md +22 -0
  156. rocket_engine/library/__init__.py +13 -0
  157. rocket_engine/library/apig_catalog.py +371 -0
  158. rocket_engine/library/catalog.py +185 -0
  159. rocket_engine/library/errors.py +16 -0
  160. rocket_engine/mutate/__init__.py +776 -0
  161. rocket_engine/mutate/operators.py +700 -0
  162. rocket_engine/ops/__init__.py +1 -0
  163. rocket_engine/ops/deferred.py +279 -0
  164. rocket_engine/ops/editors.py +404 -0
  165. rocket_engine/ops/evidence.py +194 -0
  166. rocket_engine/ops/gates.py +262 -0
  167. rocket_engine/ops/ports.py +76 -0
  168. rocket_engine/ops/queries.py +417 -0
  169. rocket_engine/ops/runs.py +553 -0
  170. rocket_engine/ops/submit.py +614 -0
  171. rocket_engine/project/README.md +27 -0
  172. rocket_engine/project/__init__.py +6 -0
  173. rocket_engine/project/canvas/__init__.py +55 -0
  174. rocket_engine/project/canvas/compile.py +109 -0
  175. rocket_engine/project/canvas/integrate.py +48 -0
  176. rocket_engine/project/canvas/schema.py +241 -0
  177. rocket_engine/project/canvas/templates.py +46 -0
  178. rocket_engine/project/canvas/validate.py +603 -0
  179. rocket_engine/project/edit/__init__.py +5 -0
  180. rocket_engine/project/edit/cas.py +87 -0
  181. rocket_engine/project/edit/transactions.py +282 -0
  182. rocket_engine/project/errors.py +16 -0
  183. rocket_engine/project/frontend13.py +685 -0
  184. rocket_engine/project/geometry/__init__.py +23 -0
  185. rocket_engine/project/geometry/ops.py +451 -0
  186. rocket_engine/project/geometry/snap.py +103 -0
  187. rocket_engine/project/ip/__init__.py +1 -0
  188. rocket_engine/project/ip/blocks.py +195 -0
  189. rocket_engine/project/ip/entry.schema.json +78 -0
  190. rocket_engine/project/ip/lifecycle.py +402 -0
  191. rocket_engine/project/ip/verbs.py +123 -0
  192. rocket_engine/project/layers/__init__.py +6 -0
  193. rocket_engine/project/layers/bindings.py +115 -0
  194. rocket_engine/project/layers/catalog.py +228 -0
  195. rocket_engine/project/layers/presets.py +43 -0
  196. rocket_engine/project/ledger/__init__.py +1 -0
  197. rocket_engine/project/ledger/docs.py +167 -0
  198. rocket_engine/project/ledger/entities.py +176 -0
  199. rocket_engine/project/ledger/events.py +29 -0
  200. rocket_engine/project/ledger/runs.py +257 -0
  201. rocket_engine/project/ledger/shared.py +174 -0
  202. rocket_engine/project/ledger/snapshots.py +385 -0
  203. rocket_engine/project/ledger/wires.py +552 -0
  204. rocket_engine/project/process/__init__.py +14 -0
  205. rocket_engine/project/process/model.py +193 -0
  206. rocket_engine/project/process/recipes.py +121 -0
  207. rocket_engine/project/schematic/__init__.py +1 -0
  208. rocket_engine/project/schematic/hierarchy.py +312 -0
  209. rocket_engine/project/schematic/instances.py +393 -0
  210. rocket_engine/project/schematic/sheets.py +198 -0
  211. rocket_engine/project/service.py +2941 -0
  212. rocket_engine/project/symbols/__init__.py +1 -0
  213. rocket_engine/project/symbols/cells.py +324 -0
  214. rocket_engine/project/symbols/linkage.py +356 -0
  215. rocket_engine/project/symbols/symbols.py +352 -0
  216. rocket_engine/project/templates/__init__.py +303 -0
  217. rocket_engine/project/templates/errors.py +25 -0
  218. rocket_engine/project/testbench/__init__.py +49 -0
  219. rocket_engine/project/testbench/blocks.py +619 -0
  220. rocket_engine/project/testbench/templates.py +134 -0
  221. rocket_engine/stores/README.md +12 -0
  222. rocket_engine/stores/__init__.py +6 -0
  223. rocket_engine/stores/ecs.py +1508 -0
  224. rocket_engine/stores/layers.py +1023 -0
  225. rocket_engine/stores/settings.py +1188 -0
  226. rocket_engine/stores/wireplace.py +1324 -0
  227. rocket_engine/workflows/README.md +18 -0
  228. rocket_engine/workflows/__init__.py +53 -0
  229. rocket_engine/workflows/capability_plan.py +102 -0
  230. rocket_engine/workflows/completion.py +228 -0
  231. rocket_engine/workflows/connectgraph.py +734 -0
  232. rocket_engine/workflows/connectgraph_emit.py +336 -0
  233. rocket_engine/workflows/gates.py +144 -0
  234. rocket_engine/workflows/photonic_wire.py +76 -0
  235. rocket_engine/workflows/stages.py +324 -0
  236. rocket_engine/workflows/viewsync.py +399 -0
  237. rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
  238. rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
  239. rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
  240. rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
  241. rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,776 @@
1
+ """MUTATE-HARNESS: systematic incorrect-design generator.
2
+
3
+ Each operator stages scratch COPIES of its base fixture files, then
4
+ mutates the copy — vendored and committed fixture trees are never
5
+ touched in place. Variant selection is `seed % len(variants)`, so
6
+ every mutation is reproducible from its seed. The OPERATORS table is
7
+ the operator→verdict map: each entry declares the engine operation
8
+ that rechecks it plus the expected verdict and rules.
9
+ """
10
+
11
+ import shutil
12
+ from pathlib import Path
13
+
14
+ from . import operators
15
+ from .operators import MutationError
16
+
17
+ __all__ = [
18
+ "OPERATORS",
19
+ "SERV_WATCHDOG_UNITS",
20
+ "MutationError",
21
+ "apply_operator",
22
+ "copy_base_files",
23
+ "operator_variant",
24
+ "stage_design_extras",
25
+ "stage_good_design",
26
+ ]
27
+
28
+ # VALIDATE-D staging watchdog for SERV runs. The good hello run
29
+ # finishes below 4M units (bisected: PASS at 4M, watchdog at 3M), so
30
+ # 5M keeps a margin over good while holding mutant sims to seconds
31
+ # instead of the 200M-harness default (33 s per hang).
32
+ SERV_WATCHDOG_UNITS = 5000000
33
+
34
+ # SERV sim file list (VALIDATE-D): vendored rel paths under
35
+ # fixtures/upstream/serv, mirrored by the committed
36
+ # tests/adapters/fixtures/hdl/serv_sources.txt manifest (a validate
37
+ # test asserts the two stay equal). Staged with structure kept.
38
+ _SERV_FILES = [
39
+ "rtl/serv_aligner.v",
40
+ "rtl/serv_alu.v",
41
+ "rtl/serv_bufreg2.v",
42
+ "rtl/serv_bufreg.v",
43
+ "rtl/serv_compdec.v",
44
+ "rtl/serv_csr.v",
45
+ "rtl/serv_ctrl.v",
46
+ "rtl/serv_debug.v",
47
+ "rtl/serv_decode.v",
48
+ "rtl/serv_immdec.v",
49
+ "rtl/serv_mem_if.v",
50
+ "rtl/serv_rf_if.v",
51
+ "rtl/serv_rf_ram_if.v",
52
+ "rtl/serv_rf_ram.v",
53
+ "rtl/serv_rf_top.v",
54
+ "rtl/serv_state.v",
55
+ "rtl/serv_synth_wrapper.v",
56
+ "rtl/serv_top.v",
57
+ "servile/servile_arbiter.v",
58
+ "servile/servile_mux.v",
59
+ "servile/servile_rf_mem_if.v",
60
+ "servile/servile.v",
61
+ "servant/servant.v",
62
+ "servant/servant_mux.v",
63
+ "servant/servant_ram.v",
64
+ "servant/servant_timer.v",
65
+ "servant/servant_gpio.v",
66
+ "bench/servant_sim.v",
67
+ "bench/uart_decoder.v",
68
+ ]
69
+
70
+ _PICO_FILES = ["picorv32.v", "testbench_ez.v"]
71
+
72
+ # VALIDATE-A bandgap snapshot (2amlogic sky130-bandgap): the
73
+ # output-voltage-tc netlist that the signoff envelope 1a grades
74
+ # 45/45 PASS on DR-005 [1.176, 1.224] V. Staged flat.
75
+ _BANDGAP_SNAPSHOT = "20260817-015751-13476b7.spice"
76
+ _BANDGAP_FILES = [_BANDGAP_SNAPSHOT]
77
+ _BANDGAP_BASE = (
78
+ "fixtures/upstream/sky130-bandgap/sim/output-voltage-tc/netlist-snapshots"
79
+ )
80
+ # DR-005 ratified accuracy row: 1.20 V +/-2% untrimmed.
81
+ _BANDGAP_VREF_MIN = 1.176
82
+ _BANDGAP_VREF_MAX = 1.224
83
+
84
+ # VALIDATE-P MZI pair: the SAX-notebook balanced circuit
85
+ # (tests/adapters/fixtures/sax/mzi.yml) plus the cspdk reference
86
+ # si340 MZI GDS as layout truth. Documented CORNERSTONE min rules
87
+ # for GDS 3/0: 350 nm feature, 200 nm gap (49th-call guidelines
88
+ # section 5.3-5.4, Table 4). Staged flat as mzi.yml + mzi_ref.gds.
89
+ _MZI_CIRCUIT = "mzi.yml"
90
+ _MZI_REF_GDS = "fixtures/upstream/cspdk/tests/gds_ref_si340/mzi.gds"
91
+ _MZI_STAGED_GDS = "mzi_ref.gds"
92
+ _MZI_TECH = "cornerstone-si340"
93
+ _MZI_CELL = "mzi"
94
+ _MZI_RULES = {"layer": [3, 0], "width_um": 0.35, "space_um": 0.2}
95
+ _MZI_RULES_REF = (
96
+ "CORNERSTONE-49th-Call-Design-Guidelines-1.pdf section 5.3-5.4 "
97
+ "Table 4 (vendored cspdk references/)"
98
+ )
99
+ _MZI_TIMEOUT_S = 120
100
+
101
+ # VALIDATE-M sigma-delta seam pairs: the digital wrapper sdm_rx.v
102
+ # (fixtures/upstream/ttsky-sigma-delta/rtl) plus the committed
103
+ # Verilog bench and the cleaned PEX bitstream vector (the one-way
104
+ # seam handoff), and the analog macro sd_top PEX netlist
105
+ # (fixtures/upstream/ttsky-sigma-delta/spice). Staged flat.
106
+ _SDM_FILES = ["sdm_rx.v"]
107
+ _SDM_BASE = "fixtures/upstream/ttsky-sigma-delta/rtl"
108
+ _SDM_BENCH = "tests/adapters/fixtures/hdl/sdm_rx_bench.v"
109
+ _SDM_VECTOR = "fixtures/upstream/ttsky-sigma-delta/rtl/tb/vectors/pex_bits.txt"
110
+ _SDM_STAGED_VECTOR = "pex_bits_clean.txt"
111
+ _SDM_TIMEOUT_S = 120
112
+ _SDTOP_SNAPSHOT = "sd_top_pex.spice"
113
+ _SDTOP_FILES = [_SDTOP_SNAPSHOT]
114
+ _SDTOP_BASE = "fixtures/upstream/ttsky-sigma-delta/spice"
115
+ # Integrator monitor UA1 DC ~0.9 V mean (docs/09b Phase 1.1).
116
+ _SDTOP_INT_MIN = 0.8
117
+ _SDTOP_INT_MAX = 1.0
118
+ # Mixed-flow testbench wrapper: the PEX is a bare .subckt (no
119
+ # top-level instantiation), so the staged sim dir carries this
120
+ # bench alongside the macro copy. Stimuli mirror the acceptance
121
+ # sim/top_tb.py at DC mid-scale: 3.3 V analog / 1.8 V digital rails,
122
+ # 50 MHz clock, UA0 = VIN_MID 1.65 V through 50 ohms, 1 pF loads on
123
+ # the bitstream outputs, 50 fF on the integrator probe node. PEX
124
+ # port order: UA0 UO0 UO1 CLK VDPWR UA1 VGND VAPWR.
125
+ _SDTOP_TB_NAME = "sd_top_tb.spice"
126
+ _SDTOP_TB = """* sd_top mixed-flow testbench (staged; mirrors sim/top_tb.py stimuli)
127
+ VAP vapwr 0 3.3
128
+ VDP vdpwr 0 1.8
129
+ VGN vgnd 0 0
130
+ VCLK clk 0 PULSE(0 1.8 0 0.2n 0.2n 10n 20n)
131
+ VIN vsrc 0 1.65
132
+ RS vsrc ua0 50
133
+ CU0 uo0 0 1p
134
+ CU1 uo1 0 1p
135
+ CA1 ua1 0 50f
136
+ XDUT ua0 uo0 uo1 clk vdpwr ua1 vgnd vapwr sd_top
137
+ """
138
+ # Solver conditioning for the registered store: X99 (bias-startup
139
+ # PFET, w=5 l=50) sits outside every hv-FET model bin (lmax 20.2 um),
140
+ # so ngspice aborts ("could not find a valid modelname") once the
141
+ # wrapper instantiates the macro. The STAGED copy is clamped to the
142
+ # top bin edge; the settled int_dc mean moves 38 uV between l=15 and
143
+ # l=20, so the clamp is bench conditioning, not a design change.
144
+ _SDTOP_X99_OLD = (
145
+ "X99 VAPWR VGND bias_layout_0/nst VAPWR "
146
+ "sky130_fd_pr__pfet_g5v0d10v5 ad=1.45 pd=10.58 as=1.45 ps=10.58 w=5 l=50"
147
+ )
148
+ _SDTOP_X99_NEW = _SDTOP_X99_OLD.replace("l=50", "l=20")
149
+ # Settled-window clocked mean: the static loop has no in-band OP, so
150
+ # the probe is a 2 us 50 MHz transient recorded from 1 us on.
151
+ _SDTOP_ANALYSES = ["tran 0.5n 2u 1u"]
152
+ _SDTOP_OPTIONS = [
153
+ "wnflag=1",
154
+ "method=gear",
155
+ "reltol=1e-4",
156
+ "vntol=1e-6",
157
+ "abstol=1e-10",
158
+ "itl4=200",
159
+ ]
160
+
161
+ OPERATORS = {
162
+ "digital_reset_break": {
163
+ "domain": "digital",
164
+ "operation": "run_hdl_test",
165
+ "target_file": "counter.v",
166
+ "files": ["counter.v", "tb_pass.v"],
167
+ "base": "tests/adapters/fixtures/hdl",
168
+ "expected_verdict": "FAIL",
169
+ "expected_rules": ["assert_fatal"],
170
+ "variants": 2,
171
+ "doc": "Break the async reset (inverted or stuck); tb $fatal fires.",
172
+ },
173
+ "digital_truncation": {
174
+ "domain": "digital",
175
+ "operation": "run_hdl_test",
176
+ "target_file": "counter.v",
177
+ "files": ["counter.v", "tb_pass.v"],
178
+ "base": "tests/adapters/fixtures/hdl",
179
+ "expected_verdict": "FAIL",
180
+ "expected_rules": ["assert_fatal"],
181
+ "variants": 3,
182
+ "doc": "Narrow the count update; the sequence never reaches 10.",
183
+ },
184
+ "digital_unconnected_port": {
185
+ "domain": "digital",
186
+ "operation": "run_hdl_test",
187
+ "target_file": "tb_pass.v",
188
+ "files": ["counter.v", "tb_pass.v"],
189
+ "base": "tests/adapters/fixtures/hdl",
190
+ "expected_verdict": "FAIL",
191
+ "expected_rules": ["assert_fatal"],
192
+ "variants": 2,
193
+ "doc": "Leave a DUT port open in the harness; the count mismatches.",
194
+ },
195
+ "serv_reset_break": {
196
+ "domain": "digital",
197
+ "design": "serv",
198
+ "operation": "run_hdl_test",
199
+ "target_file": "servant/servant.v",
200
+ "files": list(_SERV_FILES),
201
+ "base": "fixtures/upstream/serv",
202
+ "expected_verdict": "FAIL",
203
+ "expected_rules": ["assert_fatal"],
204
+ "variants": 2,
205
+ "doc": "Hold the servant CPU in reset (stuck or inverted); watchdog fires.",
206
+ },
207
+ "serv_truncation": {
208
+ "domain": "digital",
209
+ "design": "serv",
210
+ "operation": "run_hdl_test",
211
+ "target_file": "rtl/serv_bufreg.v",
212
+ "files": list(_SERV_FILES),
213
+ "base": "fixtures/upstream/serv",
214
+ "expected_verdict": "FAIL",
215
+ "expected_rules": ["assert_fatal"],
216
+ "variants": 3,
217
+ "doc": "Narrow the data-bus address; MMIO unreachable, no HALT.",
218
+ },
219
+ "serv_unconnected_port": {
220
+ "domain": "digital",
221
+ "design": "serv",
222
+ "operation": "run_hdl_test",
223
+ "target_file": "servant/servant.v",
224
+ "files": list(_SERV_FILES),
225
+ "base": "fixtures/upstream/serv",
226
+ "expected_verdict": "FAIL",
227
+ "expected_rules": ["assert_fatal"],
228
+ "variants": 2,
229
+ "doc": "Leave a servant CPU port open; firmware never boots.",
230
+ },
231
+ "picorv32_reset_break": {
232
+ "domain": "digital",
233
+ "design": "picorv32",
234
+ "operation": "run_hdl_test",
235
+ "target_file": "testbench_ez.v",
236
+ "files": list(_PICO_FILES),
237
+ "base": "fixtures/upstream/picorv32",
238
+ "expected_verdict": "FAIL",
239
+ "expected_rules": ["assert_fatal"],
240
+ "variants": 2,
241
+ "doc": "Hold test_ez in reset (stuck or re-asserted); no progress.",
242
+ },
243
+ "picorv32_truncation": {
244
+ "domain": "digital",
245
+ "design": "picorv32",
246
+ "operation": "run_hdl_test",
247
+ "target_file": "picorv32.v",
248
+ "files": list(_PICO_FILES),
249
+ "base": "fixtures/upstream/picorv32",
250
+ "expected_verdict": "FAIL",
251
+ "expected_rules": ["assert_fatal"],
252
+ "variants": 3,
253
+ "doc": "Narrow the ALU add path; the ez counter never reaches 10.",
254
+ },
255
+ "picorv32_unconnected_port": {
256
+ "domain": "digital",
257
+ "design": "picorv32",
258
+ "operation": "run_hdl_test",
259
+ "target_file": "testbench_ez.v",
260
+ "files": list(_PICO_FILES),
261
+ "base": "fixtures/upstream/picorv32",
262
+ "expected_verdict": "FAIL",
263
+ "expected_rules": ["assert_fatal"],
264
+ "variants": 2,
265
+ "doc": "Leave a CPU port open in the ez bench; fetches stall.",
266
+ },
267
+ "analog_sizing_out_of_spec": {
268
+ "domain": "analog",
269
+ "operation": "run_spice",
270
+ "target_file": "rc_divider.spice",
271
+ "files": ["rc_divider.spice"],
272
+ "base": "tests/adapters/fixtures/ngspice",
273
+ "expected_verdict": "FAIL",
274
+ "expected_rules": ["measurement"],
275
+ "variants": 3,
276
+ "doc": "Move R2 off nominal; Vout leaves its [1.199, 1.201] V bound.",
277
+ },
278
+ "analog_open_net": {
279
+ "domain": "analog",
280
+ "operation": "run_spice",
281
+ "target_file": "rc_divider.spice",
282
+ "files": ["rc_divider.spice"],
283
+ "base": "tests/adapters/fixtures/ngspice",
284
+ "expected_verdict": "FAIL",
285
+ "expected_rules": ["measurement"],
286
+ "variants": 2,
287
+ "doc": "Lift R2 from the divider; out rises to the 1.8 V rail.",
288
+ },
289
+ "layout_drc_violation": {
290
+ "domain": "layout",
291
+ "operation": "run_drc",
292
+ "target_file": "good.mag",
293
+ "files": ["good.mag"],
294
+ "base": "tests/adapters/fixtures/drc",
295
+ "expected_verdict": "FAIL",
296
+ "expected_rules": ["spacing", "width"],
297
+ "variants": 2,
298
+ "doc": "Shift/shrink a rect into an scmos spacing/width violation.",
299
+ },
300
+ "netlist_lvs_mismatch": {
301
+ "domain": "layout",
302
+ "operation": "run_lvs",
303
+ "target_file": "schematic.spice",
304
+ "files": ["layout.spice", "schematic.spice"],
305
+ "base": "tests/mutate/fixtures/lvs_inv",
306
+ "expected_verdict": "FAIL",
307
+ "expected_rules": ["lvs_net_mismatch"],
308
+ "variants": 2,
309
+ "doc": "Rename a schematic bulk net; layout vs schematic mismatch.",
310
+ },
311
+ "photonic_geometry_mistune": {
312
+ "domain": "photonic",
313
+ "design": "mzi",
314
+ "operation": "run_optical",
315
+ "target_file": "mzi.yml",
316
+ "files": ["mzi.yml"],
317
+ "base": "tests/adapters/fixtures/sax",
318
+ "expected_verdict": "FAIL",
319
+ "expected_rules": ["transmission"],
320
+ "variants": 2,
321
+ "doc": "Lengthen the MZI bottom arm; cross-port target misses.",
322
+ },
323
+ "photonic_circuit_mistune": {
324
+ "domain": "photonic",
325
+ "design": "mzi",
326
+ "operation": "run_optical",
327
+ "target_file": "mzi.yml",
328
+ "files": ["mzi.yml"],
329
+ "base": "tests/adapters/fixtures/sax",
330
+ "expected_verdict": "FAIL",
331
+ "expected_rules": ["transmission"],
332
+ "variants": 1,
333
+ "doc": "Detune the output coupler; cross-port target misses.",
334
+ },
335
+ "bandgap_sizing_out_of_spec": {
336
+ "domain": "analog",
337
+ "design": "bandgap",
338
+ "operation": "run_spice",
339
+ "target_file": _BANDGAP_SNAPSHOT,
340
+ "files": list(_BANDGAP_FILES),
341
+ "base": _BANDGAP_BASE,
342
+ "expected_verdict": "FAIL",
343
+ "expected_rules": ["measurement"],
344
+ "variants": 3,
345
+ "doc": "Move n_r2 off 51; Vref leaves DR-005 [1.176, 1.224] V.",
346
+ },
347
+ "bandgap_open_net": {
348
+ "domain": "analog",
349
+ "design": "bandgap",
350
+ "operation": "run_spice",
351
+ "target_file": _BANDGAP_SNAPSHOT,
352
+ "files": list(_BANDGAP_FILES),
353
+ "base": _BANDGAP_BASE,
354
+ "expected_verdict": "FAIL",
355
+ "expected_rules": ["measurement"],
356
+ "variants": 2,
357
+ "doc": "Lift XR2A from VOUT; feedback cut, Vref misses.",
358
+ },
359
+ "sdm_reset_break": {
360
+ "domain": "digital",
361
+ "design": "sdm_rx",
362
+ "operation": "run_hdl_test",
363
+ "target_file": "sdm_rx.v",
364
+ "files": list(_SDM_FILES),
365
+ "base": _SDM_BASE,
366
+ "expected_verdict": "FAIL",
367
+ "expected_rules": ["assert_fatal"],
368
+ "variants": 2,
369
+ "doc": "Break the sinc3-path reset (inverted or stuck); bench $fatal fires.",
370
+ },
371
+ "sdm_truncation": {
372
+ "domain": "digital",
373
+ "design": "sdm_rx",
374
+ "operation": "run_hdl_test",
375
+ "target_file": "sdm_rx.v",
376
+ "files": list(_SDM_FILES),
377
+ "base": _SDM_BASE,
378
+ "expected_verdict": "FAIL",
379
+ "expected_rules": ["assert_fatal"],
380
+ "variants": 3,
381
+ "doc": "Narrow data_fast; DC full scale never reaches 25^3.",
382
+ },
383
+ "sdm_unconnected_port": {
384
+ "domain": "digital",
385
+ "design": "sdm_rx",
386
+ "operation": "run_hdl_test",
387
+ "target_file": "sdm_rx.v",
388
+ "files": list(_SDM_FILES),
389
+ "base": _SDM_BASE,
390
+ "expected_verdict": "FAIL",
391
+ "expected_rules": ["assert_fatal"],
392
+ "variants": 2,
393
+ "doc": "Break the bitstream input path (bit_ce/bit_i as if unconnected).",
394
+ },
395
+ "sdtop_sizing_out_of_spec": {
396
+ "domain": "analog",
397
+ "design": "sd_top",
398
+ "operation": "run_spice",
399
+ "target_file": _SDTOP_SNAPSHOT,
400
+ "files": list(_SDTOP_FILES),
401
+ "base": _SDTOP_BASE,
402
+ "expected_verdict": "FAIL",
403
+ "expected_rules": ["measurement"],
404
+ "variants": 3,
405
+ "doc": "Move RIN l off 102.57; integrator DC leaves [0.8, 1.0] V.",
406
+ },
407
+ "sdtop_open_net": {
408
+ "domain": "analog",
409
+ "design": "sd_top",
410
+ "operation": "run_spice",
411
+ "target_file": _SDTOP_SNAPSHOT,
412
+ "files": list(_SDTOP_FILES),
413
+ "base": _SDTOP_BASE,
414
+ "expected_verdict": "FAIL",
415
+ "expected_rules": ["measurement"],
416
+ "variants": 2,
417
+ "doc": "Lift X64 from UA0; input open, integrator DC misses.",
418
+ },
419
+ }
420
+
421
+
422
+ def operator_variant(name, seed):
423
+ """Variant index for (operator, seed): seed mod variant count.
424
+
425
+ Args:
426
+ name: Operator name selecting the variant count.
427
+ seed: Integer seed selecting the variant.
428
+
429
+ Returns:
430
+ Variant index in range.
431
+
432
+ Raises:
433
+ MutationError: Non-integer seed.
434
+ """
435
+ if isinstance(seed, bool) or not isinstance(seed, int):
436
+ raise MutationError(f"{name}: seed must be an int, got {seed!r}.")
437
+ return seed % OPERATORS[name]["variants"]
438
+
439
+
440
+ def copy_base_files(entry, src_dir, dest_dir):
441
+ """Copy an entry's base files into dest_dir; return staged names.
442
+
443
+ Repo-relative paths (SERV rtl/ + servile/ + servant/ + bench/)
444
+ keep their structure under dest_dir; flat fixtures stay flat.
445
+
446
+ Args:
447
+ entry: Operator table entry naming the base files.
448
+ src_dir: Directory the base files copy from.
449
+ dest_dir: Directory the copies stage into.
450
+
451
+ Returns:
452
+ Staged file names.
453
+
454
+ Raises:
455
+ FileNotFoundError: Missing base file under src_dir.
456
+ """
457
+ src = Path(src_dir)
458
+ dest = Path(dest_dir)
459
+ dest.mkdir(parents=True, exist_ok=True)
460
+ staged = []
461
+ for filename in entry["files"]:
462
+ out = dest / filename
463
+ out.parent.mkdir(parents=True, exist_ok=True)
464
+ shutil.copy(src / filename, out)
465
+ staged.append(filename)
466
+ return staged
467
+
468
+
469
+ def apply_operator(name, dest_dir, seed, src_dir):
470
+ """Stage base files into dest_dir and mutate the target copy.
471
+
472
+ Args:
473
+ name: Operator name to apply.
474
+ dest_dir: Directory the mutant stages into.
475
+ seed: Integer seed selecting the variant.
476
+ src_dir: Directory the base files copy from.
477
+
478
+ Returns:
479
+ JSON-native evidence (operator, seed, variant, expected
480
+ verdict/rules, plus operator specifics).
481
+
482
+ Raises:
483
+ KeyError: Unknown operator.
484
+ MutationError: Drifted probe site (fixture changed).
485
+ FileNotFoundError: Missing base file under src_dir.
486
+ """
487
+ entry = OPERATORS[name]
488
+ variant = operator_variant(name, seed)
489
+ dest = Path(dest_dir)
490
+ staged = copy_base_files(entry, src_dir, dest)
491
+ target = dest / entry["target_file"]
492
+ mutate = getattr(operators, name)
493
+ mutated, extra = mutate(target.read_text(encoding="utf-8"), variant)
494
+ target.write_text(mutated, encoding="utf-8")
495
+ evidence = {
496
+ "operator": name,
497
+ "domain": entry["domain"],
498
+ "operation": entry["operation"],
499
+ "seed": seed,
500
+ "variant": variant,
501
+ "target_file": entry["target_file"],
502
+ "staged_files": staged,
503
+ "expected_verdict": entry["expected_verdict"],
504
+ "expected_rules": list(entry["expected_rules"]),
505
+ "expected_rule": extra.pop("expected_rule", entry["expected_rules"][0]),
506
+ }
507
+ evidence.update(extra)
508
+ return evidence
509
+
510
+
511
+ # VALIDATE-D/A/P/M design staging: base files come from
512
+ # apply_operator / copy_base_files; the extras below (committed
513
+ # harness or monitor or bench, vendored firmware or vector, watchdog,
514
+ # defines, the bandgap/sd_top run_spice job shapes, or the mzi
515
+ # circuit+layout pair) complete a runnable sim dir. Shared by
516
+ # tests/validate, scripts/mutate.py, and scripts/scenarios.py.
517
+
518
+ _DESIGNS = ("serv", "picorv32", "bandgap", "mzi", "sdm_rx", "sd_top")
519
+ _HARNESS = "tests/adapters/fixtures/hdl/serv_harness.v"
520
+ _MONITOR = "tests/adapters/fixtures/hdl/picorv32_monitor.v"
521
+ _FIRMWARE = "fixtures/upstream/serv/sw/hello_uart.hex"
522
+ _WATCHDOG_DEFAULT = "parameter watchdog_units = 200000000"
523
+
524
+
525
+ def _design_entry(design):
526
+ for entry in OPERATORS.values():
527
+ if entry.get("design") == design:
528
+ return entry
529
+ raise KeyError(f"unknown vendored design: {design!r}.")
530
+
531
+
532
+ def stage_design_extras(design, dest_dir, root):
533
+ """Copy a design's extras into a staged sim dir; return job args.
534
+
535
+ Base files must already be staged (via apply_operator for a
536
+ mutant). Digital designs return sources/defines/timeout_s/
537
+ workdir for a run_hdl_test job (plus watchdog_units for SERV);
538
+ the bandgap/sd_top return sources/measurements/analyses/options/
539
+ params/corner/temp_c/timeout_s/workdir for a run_spice job
540
+ (the PDK pin is resolved by the caller from its own store, so
541
+ a missing store skips loudly at the call site, never here);
542
+ the mzi returns sources/workdir/timeout_s for a run_optical
543
+ job plus a layout sidecar (staged reference GDS + documented
544
+ CORNERSTONE rules) for a run_drc job; sdm_rx pairs the vendored
545
+ DUT with the committed Verilog bench + the cleaned PEX vector.
546
+ Missing pieces raise FileNotFoundError; a drifted harness
547
+ watchdog raises MutationError — never a silent
548
+ unmutated-shaped run.
549
+
550
+ Args:
551
+ design: Vendored design name (serv/picorv32/bandgap/mzi/
552
+ sdm_rx/sd_top).
553
+ dest_dir: Staged sim dir receiving the extras.
554
+ root: Repo root the extras copy from.
555
+
556
+ Returns:
557
+ Runnable job args for the design's operation (plus the mzi
558
+ layout sidecar).
559
+
560
+ Raises:
561
+ KeyError: Unknown design.
562
+ FileNotFoundError: Missing harness/firmware/vector/base
563
+ piece.
564
+ MutationError: Drifted probe or shim site.
565
+ """
566
+ if design not in _DESIGNS:
567
+ raise KeyError(f"unknown vendored design: {design!r}.")
568
+ root = Path(root)
569
+ dest = Path(dest_dir)
570
+ dest.mkdir(parents=True, exist_ok=True)
571
+ if design == "mzi":
572
+ sources = [str(dest / name) for name in _staged_names(design, dest)]
573
+ ref_gds = root / _MZI_REF_GDS
574
+ if not ref_gds.is_file():
575
+ raise FileNotFoundError(
576
+ f"mzi staging: cspdk reference GDS missing: {ref_gds} "
577
+ "(scripts/fetch_fixtures.py --only cspdk)"
578
+ )
579
+ staged_gds = dest / _MZI_STAGED_GDS
580
+ shutil.copy(ref_gds, staged_gds)
581
+ return {
582
+ "sources": sources,
583
+ "workdir": str(dest),
584
+ "timeout_s": _MZI_TIMEOUT_S,
585
+ "layout": {
586
+ "gds_file": str(staged_gds),
587
+ "cell": _MZI_CELL,
588
+ "tech": _MZI_TECH,
589
+ "rules": {
590
+ "layer": list(_MZI_RULES["layer"]),
591
+ "width_um": _MZI_RULES["width_um"],
592
+ "space_um": _MZI_RULES["space_um"],
593
+ },
594
+ "rules_ref": _MZI_RULES_REF,
595
+ },
596
+ }
597
+ if design == "bandgap":
598
+ sources = [str(dest / name) for name in _staged_names(design, dest)]
599
+ return {
600
+ "sources": sources,
601
+ "workdir": str(dest),
602
+ "timeout_s": 300,
603
+ "measurements": [
604
+ {
605
+ "name": "vref",
606
+ "expr": "v(vref)",
607
+ "unit": "V",
608
+ "min": _BANDGAP_VREF_MIN,
609
+ "max": _BANDGAP_VREF_MAX,
610
+ }
611
+ ],
612
+ "analyses": ["op"],
613
+ "options": ["wnflag=1"],
614
+ "params": {"vsup": 3.3},
615
+ "corner": "tt",
616
+ "temp_c": 27,
617
+ }
618
+ if design == "sd_top":
619
+ sources = [str(dest / name) for name in _staged_names(design, dest)]
620
+ tb_dest = dest / _SDTOP_TB_NAME
621
+ tb_dest.write_text(_SDTOP_TB, encoding="utf-8")
622
+ sources.append(str(tb_dest))
623
+ # Model-bin clamp on the STAGED macro copy only (the vendored
624
+ # PEX is never touched): see _SDTOP_X99_OLD. Mutants rewrite
625
+ # X64, never X99, so the site must still occur exactly once.
626
+ pex_path = dest / _SDTOP_SNAPSHOT
627
+ pex_text = pex_path.read_text(encoding="utf-8")
628
+ if pex_text.count(_SDTOP_X99_OLD) != 1:
629
+ raise MutationError(
630
+ "sd_top staging: X99 clamp site drifted "
631
+ f"({_SDTOP_X99_OLD[:24]!r} occurs "
632
+ f"{pex_text.count(_SDTOP_X99_OLD)}x, want 1)."
633
+ )
634
+ pex_path.write_text(
635
+ pex_text.replace(_SDTOP_X99_OLD, _SDTOP_X99_NEW),
636
+ encoding="utf-8",
637
+ )
638
+ return {
639
+ "sources": sources,
640
+ "workdir": str(dest),
641
+ "timeout_s": 300,
642
+ "measurements": [
643
+ {
644
+ "name": "int_dc",
645
+ "expr": "mean(v(ua1))",
646
+ "unit": "V",
647
+ "min": _SDTOP_INT_MIN,
648
+ "max": _SDTOP_INT_MAX,
649
+ }
650
+ ],
651
+ "analyses": list(_SDTOP_ANALYSES),
652
+ "options": list(_SDTOP_OPTIONS),
653
+ "params": {"vsup": 3.3},
654
+ "corner": "tt",
655
+ "temp_c": 27,
656
+ }
657
+ if design == "sdm_rx":
658
+ bench_src = root / _SDM_BENCH
659
+ if not bench_src.is_file():
660
+ raise FileNotFoundError(f"sdm_rx bench missing: {bench_src}")
661
+ bench_dest = dest / Path(_SDM_BENCH).name
662
+ shutil.copy(bench_src, bench_dest)
663
+ vector_src = root / _SDM_VECTOR
664
+ if not vector_src.is_file():
665
+ raise FileNotFoundError(f"sdm_rx PEX vector missing: {vector_src}")
666
+ lines = vector_src.read_text(encoding="utf-8").splitlines()
667
+ clean = [line.strip() for line in lines[1:] if line.strip() in ("0", "1")]
668
+ if len(clean) != 2048:
669
+ raise MutationError(
670
+ f"sdm_rx staging: PEX vector has {len(clean)} bits, want 2048."
671
+ )
672
+ (dest / _SDM_STAGED_VECTOR).write_text("\n".join(clean) + "\n", encoding="utf-8")
673
+ # iverilog-14 elaboration shim (behavior-preserving, no value
674
+ # change): the vendored sdm_rx.v declares `fast_strobe /
675
+ # prec_strobe` after their first use (line 172 vs 135), which
676
+ # the vendor's older iverilog accepted but oss-cad-suite
677
+ # iverilog 14.0 rejects ("declaration after use"). Move the
678
+ # declaration earlier in the STAGED copy only (vendored bytes
679
+ # stay pristine); mutants patch the same way after apply_operator.
680
+ dut_path = dest / "sdm_rx.v"
681
+ dut_text = dut_path.read_text(encoding="utf-8")
682
+ strobe_decl = " reg fast_strobe, prec_strobe;\n"
683
+ anchor = " reg [15:0] osr_fast, osr_prec; // active copies, load on clear\n"
684
+ if dut_text.count(strobe_decl) != 1 or dut_text.count(anchor) != 1:
685
+ raise MutationError(
686
+ "sdm_rx staging: strobe-decl shim site drifted "
687
+ f"({dut_text.count(strobe_decl)}x decl, {dut_text.count(anchor)}x anchor)."
688
+ )
689
+ dut_text = dut_text.replace(strobe_decl, "")
690
+ dut_text = dut_text.replace(anchor, anchor + strobe_decl)
691
+ dut_path.write_text(dut_text, encoding="utf-8")
692
+ sources = [str(dest / name) for name in _staged_names(design, dest)]
693
+ sources.append(str(bench_dest))
694
+ return {
695
+ "sources": sources,
696
+ "defines": [],
697
+ "timeout_s": _SDM_TIMEOUT_S,
698
+ "watchdog_units": None,
699
+ "workdir": str(dest),
700
+ }
701
+ if design == "serv":
702
+ harness_src = root / _HARNESS
703
+ if not harness_src.is_file():
704
+ raise FileNotFoundError(f"SERV harness missing: {harness_src}")
705
+ text = harness_src.read_text(encoding="utf-8")
706
+ if text.count(_WATCHDOG_DEFAULT) != 1:
707
+ raise MutationError(
708
+ f"serv staging: watchdog site occurs "
709
+ f"{text.count(_WATCHDOG_DEFAULT)}x, want 1."
710
+ )
711
+ text = text.replace(
712
+ _WATCHDOG_DEFAULT,
713
+ f"parameter watchdog_units = {SERV_WATCHDOG_UNITS}",
714
+ )
715
+ harness_dest = dest / Path(_HARNESS).name
716
+ harness_dest.write_text(text, encoding="utf-8")
717
+ hex_src = root / _FIRMWARE
718
+ if not hex_src.is_file():
719
+ raise FileNotFoundError(f"SERV firmware missing: {hex_src}")
720
+ shutil.copy(hex_src, dest / Path(_FIRMWARE).name)
721
+ sources = [str(dest / name) for name in _staged_names(design, dest)]
722
+ sources.append(str(harness_dest))
723
+ return {
724
+ "sources": sources,
725
+ "defines": [],
726
+ "timeout_s": 300,
727
+ "watchdog_units": SERV_WATCHDOG_UNITS,
728
+ "workdir": str(dest),
729
+ }
730
+ monitor_src = root / _MONITOR
731
+ if not monitor_src.is_file():
732
+ raise FileNotFoundError(f"picorv32 monitor missing: {monitor_src}")
733
+ monitor_dest = dest / Path(_MONITOR).name
734
+ shutil.copy(monitor_src, monitor_dest)
735
+ sources = [str(dest / name) for name in _staged_names(design, dest)]
736
+ sources.append(str(monitor_dest))
737
+ return {
738
+ "sources": sources,
739
+ "defines": ["COMPRESSED_ISA"],
740
+ "timeout_s": 120,
741
+ "watchdog_units": None,
742
+ "workdir": str(dest),
743
+ }
744
+
745
+
746
+ def _staged_names(design, dest):
747
+ entry = _design_entry(design)
748
+ names = []
749
+ for filename in entry["files"]:
750
+ name = filename
751
+ if not (Path(dest) / name).is_file():
752
+ raise FileNotFoundError(f"{design} staging: base file missing: {name}")
753
+ names.append(name)
754
+ return names
755
+
756
+
757
+ def stage_good_design(design, dest_dir, root):
758
+ """Stage an unmutated design sim dir; return runnable job args.
759
+
760
+ Args:
761
+ design: Vendored design name to stage unmutated.
762
+ dest_dir: Staged sim dir receiving base files + extras.
763
+ root: Repo root the base files copy from.
764
+
765
+ Returns:
766
+ Runnable job args for the design's operation.
767
+
768
+ Raises:
769
+ KeyError: Unknown design.
770
+ FileNotFoundError: Missing base file or extra.
771
+ MutationError: Drifted probe or shim site.
772
+ """
773
+ entry = _design_entry(design)
774
+ dest = Path(dest_dir)
775
+ copy_base_files(entry, Path(root) / entry["base"], dest)
776
+ return stage_design_extras(design, dest, root)