From b16c20c2caadb9b81f7510c619e27386894c0afa Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Wed, 11 Feb 2026 06:01:30 -0700 Subject: [PATCH] Fix CE amp coupling cap routing and Colpitts test variable selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CE amplifier schematic: the input coupling cap CC_in was placed horizontally (R90) at y=336 — the same y as the RB1-to-base bias wire. Both cap pins sat on the wire, shorting the cap and allowing Vin's 0V DC to override the bias divider, putting Q1 in cutoff. Fix: move CC_in to vertical orientation (R0) above the base wire. Now pinA=(400,256) and pinB=(400,320) are off the y=336 bias path. The cap properly blocks DC while passing the 1kHz input signal. Result: V(out) swings 2.2Vpp (gain ≈ 110) instead of stuck at Vcc. Colpitts oscillator test: the schematic was actually working (V(out) pp=2.05V) but the test's fallback variable selection picked V(n001) (the Vcc rail, constant 12V) instead of V(out). Fix: look for V(out) first since the schematic labels the collector with "out". Integration tests: 4/4 pass, unit tests: 360/360 pass. --- src/mcp_ltspice/asc_generator.py | 75 +++++++++++++++----------------- tests/test_integration.py | 17 ++------ 2 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/mcp_ltspice/asc_generator.py b/src/mcp_ltspice/asc_generator.py index 1cfcca7..588655b 100644 --- a/src/mcp_ltspice/asc_generator.py +++ b/src/mcp_ltspice/asc_generator.py @@ -581,76 +581,70 @@ def generate_common_emitter_amp( qe = pin_position("npn", 2, 400, 288) # emitter # RC from Vcc rail to collector. - # Want pinB at collector (464, 288). res at (448, 192): pinB=(464, 288). Good. + # res at (448, 192): pinA=(464, 208)=vcc rail, pinB=(464, 288)=collector rc_a = pin_position("res", 0, 448, 192) # (464, 208) = vcc rail # RE from emitter to GND. - # Want pinA at emitter (464, 384). res at (448, 368): pinA=(464, 384). + # res at (448, 368): pinA=(464, 384)=emitter, pinB=(464, 464)=GND re_b = pin_position("res", 1, 448, 368) # (464, 464) = GND - # RB1 from Vcc rail to base. Vertical, placed to the left of Q1. - # Want pinB at base Y (336). res at (240, 240): pinA=(256, 256), pinB=(256, 336) + # RB1 from Vcc rail to base. res at (240, 240): pinA=(256, 256), pinB=(256, 336) rb1_a = pin_position("res", 0, 240, 240) # (256, 256) rb1_b = pin_position("res", 1, 240, 240) # (256, 336) - # RB2 from base to GND. - # Want pinA at base Y (336). res at (240, 320): pinA=(256, 336), pinB=(256, 416) + # RB2 from base to GND. res at (240, 320): pinA=(256, 336), pinB=(256, 416) rb2_b = pin_position("res", 1, 240, 320) # (256, 416) - # CC_in (input coupling cap) horizontal, connecting input to base. - # cap R90: pinA=origin+(-16,0)→(origin_x, origin_y+16)... wait let me recalculate. - # cap R90: pinA offset (16,0) → rotate90 → (0, 16), pinB offset (16,64) → (-64, 16) - # At origin (368, 320): pinA = (368, 336), pinB = (304, 336) - # pinA at (368, 336) close to base (400, 336). pinB at (304, 336). - # Wire from pinA (368, 336) to base (400, 336). - ccin_a = pin_position("cap", 0, 368, 320, 90) # (368, 336) - ccin_b = pin_position("cap", 1, 368, 320, 90) # (304, 336) + # CC_in (input coupling cap) — VERTICAL (R0) above the base wire. + # cap R0 at (384, 256): pinA=(400, 256)=input side, pinB=(400, 320)=base side. + # Critically, neither pin sits on the horizontal RB1-to-base wire at y=336, + # so the coupling cap is NOT shorted by the bias wire. + ccin_a = pin_position("cap", 0, 384, 256) # (400, 256) = input + ccin_b = pin_position("cap", 1, 384, 256) # (400, 320) = base side - # CC_out (output coupling cap) to the right of collector. - # cap R90 at origin (560, 272): pinA = (560, 288), pinB = (496, 288) - # pinB near collector (464, 288), pinA extends right to output - # Actually pinB = (560-64, 288) = (496, 288). Wire from collector (464,288) to (496,288). + # CC_out (output coupling cap) horizontal R90 to the right of collector. + # cap R90 at (560, 272): pinA=(560, 288)=output, pinB=(496, 288)=collector side ccout_a = pin_position("cap", 0, 560, 272, 90) # (560, 288) ccout_b = pin_position("cap", 1, 560, 272, 90) # (496, 288) # CE (emitter bypass cap) parallel to RE. - # Place to the right of RE. cap at (528, 384): pinA=(544, 384), pinB=(544, 448) + # cap at (528, 384): pinA=(544, 384)=emitter, pinB=(544, 448)=GND ce_a = pin_position("cap", 0, 528, 384) # (544, 384) ce_b = pin_position("cap", 1, 528, 384) # (544, 448) - # Vcc source at (80, 96): pin+=(80, 112), pin-=(80, 192) + # Vcc source at (80, 96): pin+=(80, 112), pin-=(80, 192) vcc_p = pin_position("voltage", 0, 80, 96) # (80, 112) vcc_n = pin_position("voltage", 1, 80, 96) # (80, 192) - # Vin source at (80, 288): pin+=(80, 304), pin-=(80, 384) + # Vin source at (80, 288): pin+=(80, 304), pin-=(80, 384) vin_p = pin_position("voltage", 0, 80, 288) # (80, 304) vin_n = pin_position("voltage", 1, 80, 288) # (80, 384) # === WIRING === - # Vcc rail at y=208 — route RIGHT from Vcc+ first (avoid crossing Vcc- at 80,192) + # Vcc rail at y=208 — route RIGHT from Vcc+ (avoid crossing Vcc- at 80,192) vcc_y = rc_a[1] # 208 - sch.add_wire(vcc_p[0], vcc_p[1], 160, vcc_p[1]) # (80,112) → (160,112) - sch.add_wire(160, vcc_p[1], 160, vcc_y) # (160,112) → (160,208) - sch.add_wire(160, vcc_y, rc_a[0], vcc_y) # (160,208) → (464,208) = RC top - sch.add_wire(rb1_a[0], rb1_a[1], rb1_a[0], vcc_y) # RB1 top up to rail - sch.add_wire(rb1_a[0], vcc_y, rc_a[0], vcc_y) # connect to rail + sch.add_wire(vcc_p[0], vcc_p[1], 160, vcc_p[1]) # (80,112)→(160,112) + sch.add_wire(160, vcc_p[1], 160, vcc_y) # (160,112)→(160,208) + sch.add_wire(160, vcc_y, rc_a[0], vcc_y) # (160,208)→(464,208) = RC top + sch.add_wire(rb1_a[0], rb1_a[1], rb1_a[0], vcc_y) # RB1 top up to rail + sch.add_wire(rb1_a[0], vcc_y, rc_a[0], vcc_y) # connect to rail - # RB1 bottom to base, RB2 top at same junction - sch.add_wire(rb1_b[0], rb1_b[1], qb[0], qb[1]) # RB1 bottom → base + # RB1 bottom → base (horizontal at y=336) + sch.add_wire(rb1_b[0], rb1_b[1], qb[0], qb[1]) - # CC_in to base - sch.add_wire(ccin_a[0], ccin_a[1], qb[0], qb[1]) # CC_in right → base + # CC_in base side → base junction (short vertical drop) + sch.add_wire(ccin_b[0], ccin_b[1], qb[0], qb[1]) # (400,320)→(400,336) - # Input: Vin+ to CC_in left - sch.add_wire(vin_p[0], vin_p[1], ccin_b[0], ccin_b[1]) # Vin+ → CC_in left + # Vin+ → CC_in input side via manhattan L-route + sch.add_wire(vin_p[0], vin_p[1], ccin_a[0], vin_p[1]) # (80,304)→(400,304) + sch.add_wire(ccin_a[0], vin_p[1], ccin_a[0], ccin_a[1]) # (400,304)→(400,256) - # Collector to CC_out - sch.add_wire(qc[0], qc[1], ccout_b[0], ccout_b[1]) # collector → CC_out left + # Collector → CC_out left pin + sch.add_wire(qc[0], qc[1], ccout_b[0], ccout_b[1]) - # Emitter to CE (bypass cap in parallel with RE) - sch.add_wire(qe[0], qe[1], ce_a[0], ce_a[1]) # emitter → CE top - # CE bottom to RE bottom (shared GND rail) - sch.add_wire(ce_b[0], ce_b[1], re_b[0], re_b[1]) # CE bot → RE bot + # Emitter → CE top (bypass cap in parallel with RE) + sch.add_wire(qe[0], qe[1], ce_a[0], ce_a[1]) + # CE bottom → GND (separate ground flag, no diagonal wire to RE) # === COMPONENTS === sch.add_component("npn", "Q1", bjt_model, 400, 288) @@ -658,7 +652,7 @@ def generate_common_emitter_amp( sch.add_component("res", "RE", re, 448, 368) sch.add_component("res", "RB1", rb1, 240, 240) sch.add_component("res", "RB2", rb2, 240, 320) - sch.add_component("cap", "CC1", cc_in, 368, 320, rotation=90) + sch.add_component("cap", "CC1", cc_in, 384, 256) sch.add_component("cap", "CC2", cc_out, 560, 272, rotation=90) sch.add_component("cap", "CE", ce, 528, 384) sch.add_component("voltage", "Vcc", vcc, 80, 96) @@ -669,6 +663,7 @@ def generate_common_emitter_amp( sch.add_ground(*vin_n) sch.add_ground(*rb2_b) sch.add_ground(*re_b) + sch.add_ground(*ce_b) # CE bottom to GND directly (no diagonal wire) sch.add_net_label("out", *ccout_a) sch.add_directive(".tran 5m", 80, 528) diff --git a/tests/test_integration.py b/tests/test_integration.py index 46737fa..fd5486a 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -180,28 +180,19 @@ class TestColpittsOscillator: raw = result.raw_data time_idx = None - # Look for collector voltage vcol_idx = None for var in raw.variables: if var.name.lower() == "time": time_idx = var.index - elif "collector" in var.name.lower() or var.name.lower() == "v(collector)": + elif var.name.lower() == "v(out)": + vcol_idx = var.index + elif "collector" in var.name.lower() and vcol_idx is None: vcol_idx = var.index assert time_idx is not None - if vcol_idx is None: - # Fall back to any voltage signal that isn't supply - for var in raw.variables: - if var.name.lower().startswith("v(") and var.name.lower() not in ( - "v(vcc)", - "v(time)", - ): - vcol_idx = var.index - break - assert vcol_idx is not None, ( - f"No suitable signal found. Variables: {[v.name for v in raw.variables]}" + f"No V(out) or collector signal. Variables: {[v.name for v in raw.variables]}" ) sig = np.real(raw.data[vcol_idx])