circuitgenome 0.1.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.
Files changed (31) hide show
  1. circuitgenome-0.1.0/LICENSE +21 -0
  2. circuitgenome-0.1.0/PKG-INFO +464 -0
  3. circuitgenome-0.1.0/README.md +412 -0
  4. circuitgenome-0.1.0/circuitgenome/__init__.py +4 -0
  5. circuitgenome-0.1.0/circuitgenome/cli.py +126 -0
  6. circuitgenome-0.1.0/circuitgenome/synthesizer/__init__.py +13 -0
  7. circuitgenome-0.1.0/circuitgenome/synthesizer/bias_pruning.py +121 -0
  8. circuitgenome-0.1.0/circuitgenome/synthesizer/cmfb_compatibility.py +69 -0
  9. circuitgenome-0.1.0/circuitgenome/synthesizer/config/opamp_modules.yaml +830 -0
  10. circuitgenome-0.1.0/circuitgenome/synthesizer/config/opamp_topologies.yaml +437 -0
  11. circuitgenome-0.1.0/circuitgenome/synthesizer/loader.py +103 -0
  12. circuitgenome-0.1.0/circuitgenome/synthesizer/models.py +151 -0
  13. circuitgenome-0.1.0/circuitgenome/synthesizer/net_aliasing.py +74 -0
  14. circuitgenome-0.1.0/circuitgenome/synthesizer/netlist.py +159 -0
  15. circuitgenome-0.1.0/circuitgenome/synthesizer/output_compatibility.py +75 -0
  16. circuitgenome-0.1.0/circuitgenome/synthesizer/polarity_compatibility.py +50 -0
  17. circuitgenome-0.1.0/circuitgenome/synthesizer/synthesizer.py +296 -0
  18. circuitgenome-0.1.0/circuitgenome/synthesizer/tail_current_compatibility.py +92 -0
  19. circuitgenome-0.1.0/circuitgenome/visualizer/__init__.py +0 -0
  20. circuitgenome-0.1.0/circuitgenome/visualizer/app.py +115 -0
  21. circuitgenome-0.1.0/circuitgenome/visualizer/graph.py +142 -0
  22. circuitgenome-0.1.0/circuitgenome.egg-info/PKG-INFO +464 -0
  23. circuitgenome-0.1.0/circuitgenome.egg-info/SOURCES.txt +29 -0
  24. circuitgenome-0.1.0/circuitgenome.egg-info/dependency_links.txt +1 -0
  25. circuitgenome-0.1.0/circuitgenome.egg-info/entry_points.txt +2 -0
  26. circuitgenome-0.1.0/circuitgenome.egg-info/requires.txt +5 -0
  27. circuitgenome-0.1.0/circuitgenome.egg-info/top_level.txt +1 -0
  28. circuitgenome-0.1.0/pyproject.toml +45 -0
  29. circuitgenome-0.1.0/setup.cfg +4 -0
  30. circuitgenome-0.1.0/tests/test_synthesizer.py +1367 -0
  31. circuitgenome-0.1.0/tests/test_visualizer.py +185 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Analog ML
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,464 @@
1
+ Metadata-Version: 2.4
2
+ Name: circuitgenome
3
+ Version: 0.1.0
4
+ Summary: A Python toolkit for analog circuit topology synthesis and recognition, focused on op-amp design.
5
+ Author: Analog ML
6
+ Maintainer: Analog ML
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 Analog ML
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Homepage, https://github.com/analog-ml/CircuitGenome
30
+ Project-URL: Repository, https://github.com/analog-ml/CircuitGenome
31
+ Project-URL: Issues, https://github.com/analog-ml/CircuitGenome/issues
32
+ Project-URL: Changelog, https://github.com/analog-ml/CircuitGenome/blob/main/CHANGELOG.md
33
+ Keywords: analog,circuit-design,spice,eda,op-amp,topology-synthesis
34
+ Classifier: Development Status :: 3 - Alpha
35
+ Classifier: Intended Audience :: Science/Research
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Programming Language :: Python :: 3.9
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
44
+ Requires-Python: >=3.9
45
+ Description-Content-Type: text/markdown
46
+ License-File: LICENSE
47
+ Requires-Dist: pyyaml>=6.0
48
+ Provides-Extra: viz
49
+ Requires-Dist: streamlit>=1.30; extra == "viz"
50
+ Requires-Dist: pyvis>=0.3; extra == "viz"
51
+ Dynamic: license-file
52
+
53
+ # CircuitGenome
54
+
55
+ A Python toolkit for analog circuit topology synthesis and recognition, focused on op-amp design.
56
+
57
+ ## Modules
58
+
59
+ ### 1. Topology Synthesizer *(available)*
60
+ Constructs complete op-amp circuits from modular building blocks. Given a topology configuration (number of stages, output type), it enumerates every valid combination of module variants and emits SPICE netlists.
61
+
62
+ ### 2. Subcircuit Recognizer *(coming soon)*
63
+ Takes a flat SPICE netlist and identifies structural subcircuits (differential pairs, cascode mirrors, etc.) at multiple hierarchy levels.
64
+
65
+ ### 3. Functional Block Recognizer *(coming soon)*
66
+ Takes a flat SPICE netlist and identifies which functional roles (input stage, load, bias, compensation, etc.) each part of the circuit plays.
67
+
68
+ ---
69
+
70
+ ## Installation
71
+
72
+ ```bash
73
+ pip install -e .
74
+ ```
75
+
76
+ Requires Python 3.9+ and PyYAML.
77
+
78
+ ---
79
+
80
+ ## Topology Synthesizer
81
+
82
+ The synthesizer works by combining **module variants** according to a **topology template**. Each module category has a fixed port interface; variants differ only in their internal implementation.
83
+
84
+ ### Module categories
85
+
86
+ | Category | Variants |
87
+ |---|---|
88
+ | Input pair | PMOS/NMOS differential pair, with/without source degeneration, inverter-based |
89
+ | Load | Resistor (VDD-side / GND-side), PMOS/NMOS active (current mirror), PMOS/NMOS current source, folded cascode (PMOS/NMOS-input, single-output & differential-output), telescopic cascode (PMOS/NMOS) |
90
+ | Tail current | Current mirror (PMOS/NMOS), cascode current mirror (PMOS/NMOS), resistor (VDD-side / GND-side) |
91
+ | Bias generation | Diode-connected MOSFET ladder, magic battery (current mirror), resistor ladder |
92
+ | CMFB | Resistive-sense 5T OTA, differential-difference amplifier (DDA) — present only when `load` has a differential-output cascode (`output_cardinality: "differential"`) |
93
+ | Compensation | Miller cap, Miller cap + nulling resistor, indirect |
94
+ | Second stage | Common-source, common-drain (source follower), differential OTA |
95
+
96
+ **Input pair**
97
+
98
+ ![Input pair variants](gallery/modules-implementations/input_pair+load+tail_current/input_pair.svg)
99
+
100
+ **Load**
101
+
102
+ ![Load variants](gallery/modules-implementations/input_pair+load+tail_current/load.svg)
103
+
104
+ **Tail current**
105
+
106
+ ![Tail current variants](gallery/modules-implementations/input_pair+load+tail_current/tail_current.svg)
107
+
108
+ **Bias generation**
109
+
110
+ ![Bias generation variants](gallery/modules-implementations/bias_generation+cmfb/bias_generation.svg)
111
+
112
+ **CMFB**
113
+
114
+ ![CMFB variants](gallery/modules-implementations/bias_generation+cmfb/cmfb.svg)
115
+
116
+ ### Topology templates
117
+
118
+ | Name | Stages | Output | Compensation |
119
+ |---|---|---|---|
120
+ | `one_stage_opamp` | 1 | Single-ended | — |
121
+ | `two_stage_opamp_single_ended` | 2 | Single-ended | — |
122
+ | `two_stage_opamp_fully_differential` | 2 | Fully differential | — |
123
+ | `three_stage_opamp_nmc_single_ended` | 3 | Single-ended | Nested Miller (NMC) |
124
+ | `three_stage_opamp_rnmc_single_ended` | 3 | Single-ended | Reversed Nested Miller (RNMC) |
125
+ | `three_stage_opamp_nmc_fully_differential` | 3 | Fully differential | Nested Miller (NMC) |
126
+ | `three_stage_opamp_rnmc_fully_differential` | 3 | Fully differential | Reversed Nested Miller (RNMC) |
127
+
128
+ Of the 5 × 12 × 6 = 360 possible `input_pair` / `load` / `tail_current`
129
+ combinations, only 144 have compatible PMOS/NMOS polarities (see "Polarity
130
+ compatibility filter" below) — the rest are filtered out by
131
+ `enumerate_circuits`. Of those 144, 72 use `inverter_based_input`, whose
132
+ self-biased design never references its `tail` port: the "Tail-current
133
+ compatibility filter" below collapses those 72 combinations' 6
134
+ `tail_current` choices down to 1 canonical choice (72 -> 12), leaving **84**
135
+ effective combinations (the 72 combinations using a `differential_pair_*`
136
+ variant are unaffected). Of those 84, the "Output-cardinality compatibility
137
+ filter" below further splits them by which output type the `load` supports:
138
+ **70** are valid for single-ended topologies and **56** are valid for
139
+ fully-differential topologies. A 1-stage topology therefore yields
140
+ **210 unique circuits** (70 × 3). A 2-stage single-ended topology yields
141
+ **1890 circuits** (70 × 3 × 3 × 3); a 2-stage fully-differential topology
142
+ also has a `cmfb` slot, but (per the "CMFB compatibility filter" below) only
143
+ the 14-of-56 combinations using a `"differential"`-cardinality `load` keep
144
+ both `cmfb` variants -- 28 + 42 = 70 effective load/cmfb combinations, so it
145
+ yields **17 010 circuits** (70 × 3⁵). Each 3-stage single-ended topology adds
146
+ two more `second_stage` slots (gm2, gm3) and two `compensation` slots (Cm1,
147
+ Cm2), yielding **17 010 circuits** (70 × 3⁵). Each 3-stage
148
+ fully-differential topology duplicates those four slots per output path
149
+ (keeping the single `cmfb` slot), yielding **1 377 810 circuits** (70 × 3⁹).
150
+
151
+ ### Polarity compatibility filter
152
+
153
+ A circuit only has a real DC current path if its `input_pair`, `load`, and
154
+ `tail_current` agree on polarity. For example, `differential_pair_nmos`
155
+ draws current out of `out1`/`out2` into the tail, so it needs a `load` that
156
+ *sources* current into `out1`/`out2` from vdd and a `tail_current` that
157
+ *sinks* the tail node to gnd — pairing it with `active_load_nmos` (which also
158
+ sinks to gnd) or `current_mirror_tail_pmos` (which also sources into the
159
+ tail) leaves a node with no current path.
160
+
161
+ Each `input_pair`, `load`, and `tail_current` variant declares a `polarity`
162
+ field in `opamp_modules.yaml`: `pmos_input`, `nmos_input`, or omitted for
163
+ variants that work with either (`inverter_based_input`, and currently all
164
+ `bias_generation` variants). `enumerate_circuits` skips any combination where
165
+ `load`/`tail_current`'s `polarity` (if set) doesn't match `input_pair`'s. To
166
+ extend the filter to a new or edited variant, just add the matching
167
+ `polarity:` tag in YAML — no code changes needed
168
+ (`circuitgenome/synthesizer/polarity_compatibility.py`).
169
+
170
+ ### Output-cardinality compatibility filter
171
+
172
+ `load.in1`/`in2` (the folding nodes fed by `input_pair.out1`/`out2`) and
173
+ `load.out`/`out1`/`out2` (the load's actual output node(s)) are wired to
174
+ *separate* nets by every topology. Whether the output-side ports get a net at
175
+ all depends on the topology's `output_type`: `load.out1`/`out2` are wired to
176
+ `net_loadout1`/`net_loadout2` only in `fully_differential` topologies (sensed
177
+ by `cmfb`/`second_stage*`/`comp*`), and `load.out`/`out2` are wired to the
178
+ stage's output node only in `single_ended` topologies.
179
+
180
+ Folded-cascode/telescopic-cascode loads with a single output
181
+ (`folded_cascode_load_*_input_single_output`,
182
+ `telescopic_cascode_load_{pmos,nmos}`) declare `out` as mandatory, so they'd
183
+ be left floating in a `fully_differential` topology. Folded-cascode loads
184
+ with differential outputs (`folded_cascode_load_*_input_differential_output`)
185
+ declare `out1`/`out2` as mandatory cascode-output nodes, so they'd be left
186
+ floating in a `single_ended` topology (where `net_loadout1`/`net_loadout2`
187
+ aren't defined).
188
+
189
+ These 6 `load` variants declare an `output_cardinality` field in
190
+ `opamp_modules.yaml`: `"single"` (compatible only with `single_ended`) or
191
+ `"differential"` (compatible only with `fully_differential`); the other 6
192
+ `load` variants (resistor/active/current-source) declare `out1`/`out2` as
193
+ `alias_of: in1`/`in2` — a net-merge pass (`net_aliasing.py`) collapses their
194
+ `out1`/`out2` net back onto `in1`/`in2`'s after assembly, restoring a single
195
+ shared in/out node regardless of `output_type`. They're untagged and
196
+ compatible with either. `enumerate_circuits` skips any combination where
197
+ `load`'s `output_cardinality` (if set) doesn't match the topology's
198
+ `output_type`. To extend the filter to a new or edited `load` variant, just
199
+ add the matching `output_cardinality:` tag in YAML — no code changes needed
200
+ (`circuitgenome/synthesizer/output_compatibility.py`).
201
+
202
+ ### CMFB compatibility filter
203
+
204
+ `fully_differential` topologies have a `cmfb` slot, wired
205
+ `cmfb.out -> net_cmfb_out -> load.bias_cmfb`. Of the 12 `load` variants, only
206
+ the 2 tagged `output_cardinality: "differential"`
207
+ (`folded_cascode_load_*_input_differential_output`) declare `bias_cmfb` as a
208
+ real consumer (gating `mn3`/`mn4` or `mp1`/`mp2`); the other 10 declare it
209
+ `optional` and never reference it, so `net_cmfb_out` would drive nothing.
210
+
211
+ For a `load` whose `output_cardinality` isn't `"differential"`,
212
+ `enumerate_circuits` only allows the canonical `resistive_sense_cmfb` variant
213
+ through (avoiding a duplicate-circuit enumeration of `dda_cmfb`), then prunes
214
+ it to an empty placeholder — it contributes no devices, `cmfb.bias` is no
215
+ longer a needed bias rail, and the `vcm_ref` external port is left
216
+ unconnected for these circuits. To extend: tag a new or edited `load` variant
217
+ with `output_cardinality: "differential"` (and give it a real `bias_cmfb`
218
+ consumer) to make it a genuine `cmfb` consumer — no code changes needed
219
+ (`circuitgenome/synthesizer/cmfb_compatibility.py`).
220
+
221
+ ### Tail-current compatibility filter
222
+
223
+ Every topology has a `tail_current` slot, wired `input_pair.tail ->
224
+ net_tail <- tail_current.out`. Of the 5 `input_pair` variants, only the 4
225
+ `differential_pair_*` variants reference their `tail` port from a device
226
+ terminal; `inverter_based_input` — two back-to-back CMOS inverters — is
227
+ self-biased by design and never references `tail`, so without this filter
228
+ `net_tail` would be a floating, single-terminal node and `tail_current` would
229
+ drive nothing.
230
+
231
+ For an `input_pair` that doesn't reference `tail`, `enumerate_circuits` only
232
+ allows the canonical `current_mirror_tail_pmos` variant through (avoiding a
233
+ duplicate-circuit enumeration of the other 5 `tail_current` variants), then
234
+ prunes it to an empty placeholder — it contributes no devices, `net_tail` is
235
+ no longer floating, and `tail_current.bias` is no longer a needed bias rail.
236
+ To extend: wire a new or edited `input_pair` variant's tail-side device
237
+ terminal(s) to `tail` to make it a genuine `tail_current` consumer — no code
238
+ changes needed (`circuitgenome/synthesizer/tail_current_compatibility.py`).
239
+
240
+ ### Three-stage compensation schemes
241
+
242
+ Both 3-stage templates reuse the existing `second_stage` modules for the
243
+ second (gm2) and third (gm3) gain stages, and the existing `compensation`
244
+ modules for the two Miller capacitors Cm1/Cm2 — no new module variants are
245
+ required.
246
+
247
+ - **Nested Miller (NMC)** — both Cm1 and Cm2 return to the final output node:
248
+ Cm1 spans gm2+gm3 (outer loop), Cm2 spans gm3 only (inner loop).
249
+ - **Reversed Nested Miller (RNMC)** — Cm1 spans gm3 only (gm2's output to the
250
+ final output), while Cm2 spans gm2 only (gm1's output to gm2's output)
251
+ instead of returning to the final output. This reduces output-node loading,
252
+ which is useful when gm3 is a low-gain buffer.
253
+
254
+ ---
255
+
256
+ ## CLI Usage
257
+
258
+ ### List available topologies
259
+
260
+ ```bash
261
+ circuitgenome synthesize --list-topologies
262
+ ```
263
+
264
+ ```
265
+ one_stage_opamp (stages=1, output=single_ended)
266
+ two_stage_opamp_single_ended (stages=2, output=single_ended)
267
+ two_stage_opamp_fully_differential (stages=2, output=fully_differential)
268
+ three_stage_opamp_nmc_single_ended (stages=3, output=single_ended, compensation=nested_miller)
269
+ three_stage_opamp_rnmc_single_ended (stages=3, output=single_ended, compensation=reversed_nested_miller)
270
+ three_stage_opamp_nmc_fully_differential (stages=3, output=fully_differential, compensation=nested_miller)
271
+ three_stage_opamp_rnmc_fully_differential (stages=3, output=fully_differential, compensation=reversed_nested_miller)
272
+ ```
273
+
274
+ ### List available module variants
275
+
276
+ ```bash
277
+ circuitgenome synthesize --list-modules
278
+ ```
279
+
280
+ ### Generate circuits
281
+
282
+ ```bash
283
+ # All 1-stage single-ended variants, flat SPICE
284
+ circuitgenome synthesize --stages 1 --output-dir ./circuits/
285
+
286
+ # All 2-stage single-ended variants, both flat and hierarchical SPICE
287
+ circuitgenome synthesize --stages 2 --output-type single_ended --format both --output-dir ./circuits/
288
+
289
+ # Dry run — count circuits without writing files
290
+ circuitgenome synthesize --stages 2 --dry-run
291
+
292
+ # Specific topology by name
293
+ circuitgenome synthesize --topology two_stage_opamp_fully_differential --output-dir ./circuits/
294
+
295
+ # 3-stage, nested Miller compensation, single-ended
296
+ circuitgenome synthesize --topology three_stage_opamp_nmc_single_ended --output-dir ./circuits/
297
+
298
+ # Dry run — count all 3-stage variants (NMC + RNMC, single-ended + fully differential)
299
+ circuitgenome synthesize --stages 3 --dry-run
300
+ ```
301
+
302
+ ### Visualize topologies
303
+
304
+ ```bash
305
+ circuitgenome visualize
306
+ ```
307
+
308
+ Launches a Streamlit web UI for browsing topologies and module variants: pick
309
+ a topology, swap each slot's module variant, and see the resulting block
310
+ diagram (and SPICE netlist, for valid combinations) update live. Requires the
311
+ `viz` extra:
312
+
313
+ ```bash
314
+ pip install circuitgenome[viz]
315
+ ```
316
+
317
+ ![Topology Explorer screenshot](docs/images/topology_visualizer.png)
318
+
319
+ #### CLI options
320
+
321
+ | Flag | Description | Default |
322
+ |---|---|---|
323
+ | `--stages 1\|2\|3` | Filter by number of stages | all |
324
+ | `--output-type single_ended\|fully_differential` | Filter by output type | all |
325
+ | `--topology NAME` | Use one specific topology | all |
326
+ | `--format flat\|hierarchical\|both` | SPICE output format | `flat` |
327
+ | `--output-dir PATH` | Directory for output files | `.` |
328
+ | `--dry-run` | Count circuits without writing | off |
329
+ | `--list-topologies` | Print topology names and exit | — |
330
+ | `--list-modules` | Print module variants and exit | — |
331
+
332
+ ### Output format
333
+
334
+ Each generated circuit gets its own `.ckt` file. For `--format both`, two files are written per circuit:
335
+
336
+ **Flat SPICE** (`circuit_0001_flat.ckt`) — all devices in a single `.subckt` block:
337
+
338
+ ```spice
339
+ .subckt circuit_0001 ibias in1 in2 out vdd! gnd!
340
+ m1_input_pair net_diff1 in1 net_tail net_tail pmos
341
+ m2_input_pair net_mid in2 net_tail net_tail pmos
342
+ r1_load vdd! net_diff1 1k
343
+ r2_load vdd! net_mid 1k
344
+ m1_tail_current net_tail_bias net_tail_bias vdd! vdd! pmos
345
+ m2_tail_current net_tail net_tail_bias vdd! vdd! pmos
346
+ mp1_bias_gen ibias ibias vdd! vdd! pmos
347
+ mn1_bias_gen net_bias1 net_bias1 gnd! gnd! nmos
348
+ mn2_bias_gen net_bias2 net_bias2 gnd! gnd! nmos
349
+ mn3_bias_gen net_bias3 net_bias3 gnd! gnd! nmos
350
+ mn4_bias_gen net_bias4 net_bias4 gnd! gnd! nmos
351
+ c1_compensation net_mid out 1p
352
+ mn1_second_stage out net_mid gnd! gnd! nmos
353
+ mp1_second_stage out net_bias1 vdd! vdd! pmos
354
+ .ends
355
+ ```
356
+
357
+ **Hierarchical SPICE** (`circuit_0001_hier.ckt`) — one `.subckt` per module, top-level uses `X` instances:
358
+
359
+ ```spice
360
+ .subckt differential_pair_pmos in1 in2 out1 out2 tail vdd gnd
361
+ m1 out1 in1 tail tail pmos
362
+ m2 out2 in2 tail tail pmos
363
+ .ends
364
+
365
+ .subckt resistor_load_vdd in1 in2 out1 out2 vdd gnd
366
+ r1 vdd in1 1k
367
+ r2 vdd in2 1k
368
+ .ends
369
+
370
+ * ... (one block per module variant used)
371
+
372
+ .subckt circuit_0001 ibias in1 in2 out vdd! gnd!
373
+ Xinput_pair in1 in2 net_diff1 net_mid net_tail vdd! gnd! differential_pair_pmos
374
+ Xload net_diff1 net_mid net_diff1 net_mid vdd! gnd! resistor_load_vdd
375
+ Xtail_current net_tail net_tail_bias vdd! gnd! current_mirror_tail_pmos
376
+ Xbias_gen ibias net_bias1 net_bias2 net_bias3 net_bias4 vdd! gnd! diode_connected_mosfet_bias
377
+ Xcompensation net_mid out miller_cap
378
+ Xsecond_stage net_mid out net_bias1 vdd! gnd! common_source
379
+ .ends
380
+ ```
381
+
382
+ ---
383
+
384
+ ## Python API
385
+
386
+ ```python
387
+ from circuitgenome import synthesize
388
+ from circuitgenome.synthesizer import to_flat_spice, to_hierarchical_spice
389
+
390
+ # Generate all 2-stage single-ended circuits
391
+ circuits = synthesize({"stages": 2, "output_type": "single_ended"})
392
+ print(f"{len(circuits)} circuits generated")
393
+
394
+ # Inspect the first circuit
395
+ c = circuits[0]
396
+ print(c.topology) # "two_stage_opamp_single_ended"
397
+ print(c.variant_map) # {"input_pair": <ModuleVariant>, "load": <ModuleVariant>, ...}
398
+
399
+ # Serialize to SPICE
400
+ flat = to_flat_spice(c, name="my_opamp")
401
+ hier = to_hierarchical_spice(c, name="my_opamp")
402
+
403
+ # Use a specific topology by name
404
+ circuits = synthesize({"topology": "one_stage_opamp"})
405
+
406
+ # All 3-stage single-ended circuits using Reversed Nested Miller Compensation
407
+ circuits = synthesize({
408
+ "stages": 3,
409
+ "output_type": "single_ended",
410
+ "compensation_scheme": "reversed_nested_miller",
411
+ })
412
+
413
+ # Load custom module/topology definitions
414
+ from circuitgenome.synthesizer.loader import load_modules, load_topologies
415
+ from circuitgenome.synthesizer import enumerate_circuits
416
+
417
+ modules = load_modules("path/to/my_modules.yaml")
418
+ topologies = load_topologies("path/to/my_topologies.yaml")
419
+ for circuit in enumerate_circuits(topologies[0], modules):
420
+ print(to_flat_spice(circuit))
421
+ ```
422
+
423
+ ---
424
+
425
+ ## Extending with custom modules
426
+
427
+ Add new variants to `circuitgenome/synthesizer/config/opamp_modules.yaml`:
428
+
429
+ ```yaml
430
+ - name: my_custom_input_pair
431
+ category: input_pair
432
+ display_name: "My Custom Input Pair"
433
+ ports:
434
+ - {name: in1, role: input}
435
+ - {name: in2, role: input}
436
+ - {name: out1, role: output}
437
+ - {name: out2, role: output}
438
+ - {name: tail, role: supply_in}
439
+ - {name: vdd, role: supply}
440
+ - {name: gnd, role: supply}
441
+ devices:
442
+ - {ref: m1, type: pmos, d: out1, g: in1, s: tail, b: tail}
443
+ - {ref: m2, type: pmos, d: out2, g: in2, s: tail, b: tail}
444
+ - {ref: m3, type: nmos, d: out1, g: in1, s: gnd, b: gnd}
445
+ - {ref: m4, type: nmos, d: out2, g: in2, s: gnd, b: gnd}
446
+ ```
447
+
448
+ The new variant is picked up automatically — no code changes needed.
449
+
450
+ ---
451
+
452
+ ## Running tests
453
+
454
+ ```bash
455
+ python3 -m pytest tests/ -v
456
+ ```
457
+
458
+ ---
459
+
460
+ ## References
461
+
462
+ - *A Data-Driven Analog Circuit Synthesizer with Automatic Topology Selection and Sizing*
463
+ - *FUBOCO: Structure Synthesis of Basic Op-Amps by FUnctional BlOck COmposition*
464
+ - *A Functional Block Decomposition Method for Automatic Op-Amp Design*