Fix CE amp coupling cap routing and Colpitts test variable selection

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.
This commit is contained in:
Ryan Malloy 2026-02-11 06:01:30 -07:00
parent 9b418a06c5
commit b16c20c2ca
2 changed files with 39 additions and 53 deletions

View File

@ -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)

View File

@ -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])