Skip to content
This repository was archived by the owner on Mar 7, 2026. It is now read-only.

Commit 47eceae

Browse files
dioderobothexdae
andauthored
[SIM] inline support (#135)
* inline sim setup * sim file relative * simpler just file * enusre build * add f-string * add spice model for all generics * add tests for all generics * implement better conventions * fix warnings * fix fmt * add non ideal models * add inductor schematics * move generic spice lib * add provisional modifiers * format * better docs * better docs * add esl * add help * address comments * improve regression * add esl and cp --------- Co-authored-by: d-asnaghi <30296575+hexdae@users.noreply.github.com>
1 parent 04093aa commit 47eceae

93 files changed

Lines changed: 3109 additions & 695 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Justfile

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,2 @@
1-
2-
sim: sim_VoltageDivider sim_InvertingSum sim_LevelShifter sim_SallenKey sim_TwinTNotch sim_Sum sim_Inductor sim_NetTie sim_Diode sim_Led sim_Crystal
3-
4-
sim_VoltageDivider:
5-
pcb sim simulation/test/test_VoltageDivider.zen --setup simulation/test/test_VoltageDivider.sp -o - | ngspice
6-
7-
sim_InvertingSum:
8-
pcb sim simulation/test/test_InvertingSum.zen --setup simulation/test/test_InvertingSum.sp -o - | ngspice
9-
10-
sim_LevelShifter:
11-
pcb sim simulation/test/test_LevelShifter.zen --setup simulation/test/test_LevelShifter.sp -o - | ngspice
12-
13-
sim_SallenKey:
14-
pcb sim simulation/test/test_SallenKey.zen --setup simulation/test/test_SallenKey.sp -o - | ngspice
15-
16-
sim_TwinTNotch:
17-
pcb sim simulation/test/test_TwinTNotch.zen --setup simulation/test/test_TwinTNotch.sp -o - | ngspice
18-
19-
sim_Sum:
20-
pcb sim simulation/test/test_Sum.zen --setup simulation/test/test_Sum.sp -o - | ngspice
21-
22-
sim_Inductor:
23-
pcb sim simulation/test/test_Inductor.zen --setup simulation/test/test_Inductor.sp -o - | ngspice
24-
25-
sim_NetTie:
26-
pcb sim simulation/test/test_NetTie.zen --setup simulation/test/test_NetTie.sp -o - | ngspice
27-
28-
sim_Diode:
29-
pcb sim simulation/test/test_Diode.zen --setup simulation/test/test_Diode.sp -o - | ngspice
30-
31-
sim_Led:
32-
pcb sim simulation/test/test_Led.zen --setup simulation/test/test_Led.sp -o - | ngspice
33-
34-
sim_Crystal:
35-
pcb sim simulation/test/test_Crystal.zen --setup simulation/test/test_Crystal.sp -o - | ngspice
1+
_default:
2+
@just --list

generics/Bjt.zen

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ Mount = enum("SMD")
1414
# -----------------------------------------------------------------------------
1515

1616
# Required
17-
mpn = config("mpn", str, optional=True)
18-
manufacturer = config("manufacturer", str, optional=True)
17+
mpn = config("mpn", str, optional=True, help="Manufacturer Part Number")
18+
manufacturer = config("manufacturer", str, optional=True, help="Manufacturer")
1919
package = config("package", Package)
2020
bjt_type = config("bjt_type", BjtType)
2121

2222
# Optional
23-
mount = config("mount", Mount, default=Mount("SMD"), optional=True)
24-
hfe = config("hfe", float, optional=True)
25-
vceo = config("vceo", Voltage, optional=True)
26-
vebo = config("vebo", Voltage, optional=True)
27-
vcbo = config("vcbo", Voltage, optional=True)
28-
ic_max = config("ic_max", Current, optional=True)
29-
ft = config("ft", Frequency, optional=True)
23+
mount = config("mount", Mount, default=Mount("SMD"), optional=True, help="Mounting type")
24+
hfe = config("hfe", float, optional=True, help="DC current gain")
25+
vceo = config("vceo", Voltage, optional=True, help="Maximum collector-emitter voltage (base open)")
26+
vebo = config("vebo", Voltage, optional=True, help="Maximum emitter-base voltage (collector open)")
27+
vcbo = config("vcbo", Voltage, optional=True, help="Maximum collector-base voltage (emitter open)")
28+
ic_max = config("ic_max", Current, optional=True, help="Maximum collector current")
29+
ft = config("ft", Frequency, optional=True, help="Transition frequency")
3030

3131
# Properties – combined and normalized
3232
properties = {
@@ -89,6 +89,42 @@ def _symbol(bjt_type: BjtType):
8989
return symbols[bjt_type]
9090

9191

92+
def _spice_subcircuit_name(bjt_type: BjtType):
93+
subcircuits = {
94+
BjtType("NPN"): "NPN",
95+
BjtType("PNP"): "PNP",
96+
}
97+
return subcircuits[bjt_type]
98+
99+
100+
def _spice_args(bjt_type: BjtType, hfe):
101+
if bjt_type == BjtType("PNP"):
102+
args = {
103+
"BF": str(hfe) if hfe else "100",
104+
"IS": "1e-14",
105+
"NF": "1.0",
106+
"VAF": "50",
107+
"RB": "10",
108+
"RC": "1",
109+
"RE": "0.5",
110+
"CJE": "2p",
111+
"CJC": "1p",
112+
}
113+
else:
114+
args = {
115+
"BF": str(hfe) if hfe else "100",
116+
"IS": "1e-14",
117+
"NF": "1.0",
118+
"VAF": "100",
119+
"RB": "10",
120+
"RC": "1",
121+
"RE": "0.5",
122+
"CJE": "2p",
123+
"CJC": "1p",
124+
}
125+
return args
126+
127+
92128
# -----------------------------------------------------------------------------
93129
# Component definition
94130
# -----------------------------------------------------------------------------
@@ -105,6 +141,12 @@ Component(
105141
"C": C,
106142
"E": E,
107143
},
144+
spice_model=SpiceModel(
145+
"spice/Bjt.lib",
146+
_spice_subcircuit_name(bjt_type),
147+
nets=[B, C, E],
148+
args=_spice_args(bjt_type, hfe),
149+
),
108150
properties=properties,
109151
type="bjt",
110152
)

generics/Capacitor.zen

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("../units.zen", "Capacitance", "Resistance", "Temperature", "Voltage")
1+
load("../units.zen", "Capacitance", "Inductance", "Resistance", "Temperature", "Voltage")
22
load("../utils.zen", "format_value")
33

44
# -----------------------------------------------------------------------------
@@ -18,18 +18,19 @@ package = config("package", Package, default=Package("0603"))
1818
value = config("value", Capacitance)
1919

2020
# Optional
21-
mpn = config("mpn", str, optional=True)
22-
manufacturer = config("manufacturer", str, optional=True)
23-
mount = config("mount", Mount, default=Mount("SMD"), optional=True)
24-
voltage = config("voltage", Voltage, optional=True)
25-
dielectric = config("dielectric", Dielectric, optional=True)
26-
esr = config("esr", Resistance, optional=True)
27-
t_max = config("t_max", Temperature, optional=True)
28-
t_min = config("t_min", Temperature, optional=True)
29-
30-
do_not_populate = config("do_not_populate", bool, default=False)
31-
exclude_from_bom = config("exclude_from_bom", bool, default=False)
32-
skip_bom = config("skip_bom", bool, default=False)
21+
mpn = config("mpn", str, optional=True, help="Manufacturer Part Number")
22+
manufacturer = config("manufacturer", str, optional=True, help="Manufacturer")
23+
mount = config("mount", Mount, default=Mount("SMD"), optional=True, help="Mounting type")
24+
voltage = config("voltage", Voltage, optional=True, help="Maximum operating voltage")
25+
dielectric = config("dielectric", Dielectric, optional=True, help="Dielectric material type")
26+
esr = config("esr", Resistance, optional=True, help="Equivalent series resistance")
27+
esl = config("esl", Inductance, optional=True, help="Equivalent series inductance")
28+
t_max = config("t_max", Temperature, optional=True, help="Maximum operating temperature")
29+
t_min = config("t_min", Temperature, optional=True, help="Minimum operating temperature")
30+
31+
do_not_populate = config("do_not_populate", bool, default=False, help="Do not populate the component in the BOM")
32+
exclude_from_bom = config("exclude_from_bom", bool, default=False, help="Exclude the component from the BOM")
33+
skip_bom = config("skip_bom", bool, default=False, help="Skip the BOM generation")
3334

3435
if do_not_populate:
3536
warn("do_not_populate is deprecated. Use dnp instead.", kind="deprecated.do_not_populate")
@@ -94,6 +95,53 @@ def _footprint(mount: Mount, package: Package):
9495
return kicad_footprints[(mount, package)]
9596

9697

98+
def _spice_args(value, package, esr=None, esl=None):
99+
"""Derive series RLC model parameters for a realistic capacitor.
100+
101+
ESL is estimated from package size (pad geometry determines loop inductance).
102+
ESR uses the user-supplied value if available, otherwise a reasonable
103+
default that scales with package size.
104+
"""
105+
106+
# Typical ESL by package -- based on published MLCC measurements (nH)
107+
esl_table = {
108+
Package("01005"): Inductance("0.08nH"),
109+
Package("0201"): Inductance("0.15nH"),
110+
Package("0402"): Inductance("0.35nH"),
111+
Package("0603"): Inductance("0.5nH"),
112+
Package("0805"): Inductance("0.7nH"),
113+
Package("1206"): Inductance("1.0nH"),
114+
Package("1210"): Inductance("1.0nH"),
115+
Package("1812"): Inductance("1.5nH"),
116+
Package("1825"): Inductance("1.5nH"),
117+
Package("2220"): Inductance("2.0nH"),
118+
Package("2225"): Inductance("2.0nH"),
119+
Package("3640"): Inductance("2.5nH"),
120+
}
121+
122+
# Typical ESR by package -- larger bodies dissipate better
123+
esr_table = {
124+
Package("01005"): Resistance("50mOhm"),
125+
Package("0201"): Resistance("30mOhm"),
126+
Package("0402"): Resistance("10mOhm"),
127+
Package("0603"): Resistance("5mOhm"),
128+
Package("0805"): Resistance("3mOhm"),
129+
Package("1206"): Resistance("2mOhm"),
130+
Package("1210"): Resistance("2mOhm"),
131+
Package("1812"): Resistance("1.5mOhm"),
132+
Package("1825"): Resistance("1.5mOhm"),
133+
Package("2220"): Resistance("1mOhm"),
134+
Package("2225"): Resistance("1mOhm"),
135+
Package("3640"): Resistance("1mOhm"),
136+
}
137+
138+
return {
139+
"CVAL": str(value),
140+
"ESR": str(esr or esr_table.get(package, Resistance("50mOhm"))),
141+
"ESL": str(esl or esl_table.get(package, Inductance("0.5nH"))),
142+
}
143+
144+
97145
# -----------------------------------------------------------------------------
98146
# Component definition
99147
# -----------------------------------------------------------------------------
@@ -116,10 +164,10 @@ Component(
116164
"P2": P2,
117165
},
118166
spice_model=SpiceModel(
119-
"../simulation/Capacitor.lib",
167+
"spice/Capacitor.lib",
120168
"C",
121169
nets=[P1, P2],
122-
args={"CVAL": str(value.value)},
170+
args=_spice_args(value, package, esr, esl),
123171
),
124172
dnp=do_not_populate,
125173
skip_bom=skip_bom or exclude_from_bom,

generics/Crystal.zen

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ package = config("package", Package)
3030
frequency = config("frequency", Frequency)
3131

3232
# Optional
33-
mpn = config("mpn", str, optional=True)
34-
manufacturer = config("manufacturer", str, optional=True)
35-
mount = config("mount", Mount, default=Mount("SMD"), optional=True)
36-
load_capacitance = config("load_capacitance", Capacitance, optional=True)
33+
mpn = config("mpn", str, optional=True, help="Manufacturer Part Number")
34+
manufacturer = config("manufacturer", str, optional=True, help="Manufacturer")
35+
mount = config("mount", Mount, default=Mount("SMD"), optional=True, help="Mounting type")
36+
load_capacitance = config(
37+
"load_capacitance", Capacitance, optional=True, help="Load capacitance for oscillator circuit"
38+
)
3739

3840
# Properties – combined and normalized
3941
properties = {
@@ -163,7 +165,7 @@ Component(
163165
symbol=Symbol(**_symbol(package)),
164166
footprint=File(_footprint(package)),
165167
spice_model=SpiceModel(
166-
"../simulation/Crystal.lib",
168+
"spice/Crystal.lib",
167169
_spice_subcircuit_name(package),
168170
nets=_spice_nets(package, XIN, XOUT, GND),
169171
args=_spice_args(frequency, load_capacitance, package),

generics/Diode.zen

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ Mount = enum("SMD")
1717
package = config("package", Package)
1818

1919
# Optional
20-
mpn = config("mpn", str, optional=True)
21-
manufacturer = config("manufacturer", str, optional=True)
22-
mount = config("mount", Mount, default=Mount("SMD"), optional=True)
23-
variant = config("variant", Variant, default=Variant("Standard"))
24-
v_f = config("v_f", Voltage, optional=True) # Forward voltage
25-
i_f = config("i_f", Current, optional=True) # Forward current
26-
v_r = config("v_r", Voltage, optional=True) # Reverse voltage
27-
i_r = config("i_r", Current, optional=True) # Reverse current
20+
mpn = config("mpn", str, optional=True, help="Manufacturer Part Number")
21+
manufacturer = config("manufacturer", str, optional=True, help="Manufacturer")
22+
mount = config("mount", Mount, default=Mount("SMD"), optional=True, help="Mounting type")
23+
variant = config("variant", Variant, default=Variant("Standard"), help="Diode type (Standard, Schottky, Zener)")
24+
v_f = config("v_f", Voltage, optional=True, help="Forward voltage drop")
25+
i_f = config("i_f", Current, optional=True, help="Forward current rating")
26+
v_r = config("v_r", Voltage, optional=True, help="Maximum reverse voltage")
27+
i_r = config("i_r", Current, optional=True, help="Reverse leakage current")
2828

2929
# Properties
3030
properties = {
@@ -144,7 +144,7 @@ Component(
144144
symbol=Symbol(**_symbol(variant)),
145145
footprint=File(_footprint(mount, package)),
146146
spice_model=SpiceModel(
147-
"../simulation/Diode.lib",
147+
"spice/Diode.lib",
148148
_spice_subcircuit_name(variant),
149149
nets=[A, K],
150150
args=_spice_args(variant, v_f, i_f, v_r, i_r),

generics/FerriteBead.zen

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@ Mount = enum("SMD")
1616
package = config("package", Package)
1717

1818
# Deprecated
19-
resistance = config("resistance", Resistance, optional=True)
19+
resistance = config(
20+
"resistance", Resistance, optional=True, help="Impedance at rated frequency (deprecated, use value)"
21+
)
2022

2123
# Optional
22-
dcr = config("dcr", Resistance, optional=True)
23-
impedance = config("value", Impedance, default=Impedance("220Ohm"), optional=True)
24-
frequency = config("frequency", Frequency, default=Frequency("100MHz"), optional=True)
25-
mpn = config("mpn", str, optional=True)
26-
manufacturer = config("manufacturer", str, optional=True)
27-
mount = config("mount", Mount, default=Mount("SMD"), optional=True)
28-
current = config("current", Current, optional=True)
24+
dcr = config("dcr", Resistance, optional=True, help="DC resistance")
25+
impedance = config("value", Impedance, default=Impedance("220Ohm"), optional=True, help="Impedance at rated frequency")
26+
frequency = config(
27+
"frequency", Frequency, default=Frequency("100MHz"), optional=True, help="Rated frequency for impedance spec"
28+
)
29+
mpn = config("mpn", str, optional=True, help="Manufacturer Part Number")
30+
manufacturer = config("manufacturer", str, optional=True, help="Manufacturer")
31+
mount = config("mount", Mount, default=Mount("SMD"), optional=True, help="Mounting type")
32+
current = config("current", Current, optional=True, help="Maximum rated current")
2933

3034
# Backwards compatibility: if resistance is specified, use it as impedance
3135
if resistance:
@@ -88,6 +92,36 @@ def _footprint(package: Package) -> str:
8892
return resistor_footprints[package]
8993

9094

95+
def _spice_args(impedance, frequency, dcr):
96+
"""Derive RLC model parameters from rated impedance and frequency.
97+
98+
At resonance of L and Cp, only Rac remains, giving the rated impedance.
99+
L is sized so its reactance equals Rac at the rated frequency (Q ≈ 1).
100+
Cp is set to resonate with L at the rated frequency.
101+
"""
102+
rac = impedance
103+
freq = frequency
104+
rdc = dcr or Resistance("0.3Ohm")
105+
pi = 3.14159265358979
106+
107+
if rac <= 0 or freq <= 0:
108+
return {
109+
"RDC": str(rdc),
110+
"L": "0",
111+
"RAC": str(rac),
112+
"CP": "0",
113+
}
114+
115+
l_val = rac / (2 * pi * freq)
116+
cp_val = 1.0 / (2 * pi * freq * rac)
117+
return {
118+
"RDC": str(rdc),
119+
"L": str(l_val),
120+
"RAC": str(rac),
121+
"CP": str(cp_val),
122+
}
123+
124+
91125
# -----------------------------------------------------------------------------
92126
# Component definition
93127
# -----------------------------------------------------------------------------
@@ -107,6 +141,12 @@ Component(
107141
"P1": P1,
108142
"P2": P2,
109143
},
144+
spice_model=SpiceModel(
145+
"spice/FerriteBead.lib",
146+
"FB",
147+
nets=[P1, P2],
148+
args=_spice_args(impedance, frequency, dcr),
149+
),
110150
properties=properties,
111151
type="ferrite_bead",
112152
)

generics/Fiducial.zen

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Variant = enum(
1818
# Component parameters
1919
# -----------------------------------------------------------------------------
2020

21-
variant = config("variant", Variant, default=Variant("1mm_Mask2mm"), optional=True)
21+
variant = config("variant", Variant, default=Variant("1mm_Mask2mm"), optional=True, help="Fiducial pad and mask size")
2222

2323
# Properties – combined and normalized
2424
properties = {

0 commit comments

Comments
 (0)