Lazy part parameters.
One of the tasks I hate most is switching out parts for a near replacement. Replacing a handful of like components to tweak a parameter on a reused module. These are all tasks that a program can do for us with less errors. I have had to get boards respun because a copied schematic page had too small of components and we missed changing a few of them, or a copy paste error made all the resistors 10K when we only wanted to change the size. This is largely an issue with graphical schematics, and can be clearly worked around, but some lower level support here could ease this task.
Let's start with the documentations resistor divider example, clearly contrived in this case as it's only two components!
@SubCircuit
def vdiv(inp, outp):
"""Divide inp voltage by 3 and place it on outp net."""
rup = Part('device', 'R', value='1K', footprint='Resistors_SMD:R_0805')
rlo = Part('device', 'R', value='500', footprint='Resistors_SMD:R_0805')
rup[1,2] += inp, outp
rlo[1,2] += outp, gnd
This implements two simple components but sets the footprint, so this sub circuit is not completely reusable across projects. If I go to use this subcircuit in a different design that is using R_0402 footprints then I have to go edit this module or monkey patch the the resistors parameters later. It also is missing information like the tolerance, temperature coefficient, power dissipation, and many more that might be required for a successful implementation.
There are many ways to address this, like passing in a footprint to the function. I want to make decisions when it is the proper time to make these decisions.
This issue is to propose a processing step that builds up the components that lack required attributes until they are fully defined for the BOM.
This step would:
- Process lazily bound or unset attributes.
- Compute optimal sizes
- Highlight conflicting requirements.
- Optionally choose from components that you commonly use and have on hand.
- Your nearly full reel of 10KΩ 1% 0603 resistors, but switching all new purchases to 0402.
Let's take that resistor divider and add some of these concepts.
from skidl.footprints import R_0402, R_0805
# Assuming we have footprint classes that have additional information.
`R_0402 < R_0805` would evaluate to `True`
@SubCircuit
def vdiv(inp, outp):
"""Divide inp voltage by 3 and place it on outp net.
Make it easy to change out the lower resistor for tuning"""
rup = Part('device', 'R', value='1K', tol='1%, temp_co='<=150 ppm')
rlo = Part('device', 'R', value='500', tol='1%', hand_solderable=True)
rup[1,2] += inp, outp
rlo[1,2] += outp, gnd
# Snip
resolve_schematic(min_footprint=R_0402, hand_solderable=R_0805, ...) # Like the linker step in compiling.
generate_netlist(tool='kicad', ...)
generate_bom(...)
Here we have the parts with additional parameters I care about, nothing more. The resolve step then sets things that matter when the design is going on to the next step, and we care about it.
Actions needed to implement this concept.
- [ ] Add a rule to the ERC that requires a valid footprint.
- [ ] Add a step to resolve lazy bound parameters.
- Add
_resolvefunction to Part class and run through a list of resolvable actions. - Call all functions with prefix
_resolve_*
- Add
- [ ] Add a step to output BOM related parameters (tolerance, temp co, footprint, value, and etc...).
- Propose: JSON list of dictionaries
- [ ] Determine better way to specify preferred lazy parameters.
- Minimum pin pitch
- Minimum square area
- Would like to avoid explicit ordered lists.
- [ ] Resolve issue #7 on normalizing footprint names based on tools.
Example _resolve_footprint function.
def _resolve_footprint(self, **cfg):
if self.footprint is not None:
return
if self.hand_solderable:
if 'hand_solderable' in cfg:
self.footprint = cfg['hand_solderable']
else:
self.footprint global_default_cfg['hand_solderable']
else:
self.footprint = max(global_default_cfg['min_footprint'], cfg['min_footprint'])
I've started working on a similar project, the details not fully worked out yet. For now describing circuits and simulating them works using gnucap as a simulation backend.
Can we turn skidl into a library? I'm not sure if I'm going to map Circuit->Part or Circuit->Footprint yet.
def EmitterAmplifier(**kwargs):
vcc, gnd, vin, vout = 4 * Pin()
q, [rc, re, r1, r2] = Transistor(), 4 * Passive()
vcc + rc[0] + r1[0]
rc[1] + q['c'] + vout
vin + r1[1] + q['b'] + r2[0]
q['e'] + re[0]
re[1] + gnd + r2[1]
return Circuit(locals(), **kwargs)
def EmitterAmplifierTestbench():
amp = EmitterAmplifier(q=BC549(), rc=R(2.2e3), re=R(1e3)//C(10e-6),
r1=R(5e3), r2=R(1e3))
# when a Circuit is assigned to a Circuit it is replaced within the parent Circuit
# when a Model is assigned to a Circuit it sets the model parameter
# this allows for system level models. The EmitterAmplifier can have a behavioral
# model attached to it that can be used as the amplifiers specification (to be checked
# in the testbench and for system level simulation
amp['q'] = NPN(bf=200) #BC549()
# 1 * V(5) converts V() a model to a circuit and is a shortcut for V(5).to_circuit()
# by convention Models have all uppercase names, while Circuits are CamlCase
# lower case names are used for Probes. Example: V is a voltage source that
# maps to a Poled circuit, v is a voltage probe
# A circuit is a collection of sub-Circuits and Pins
vcc, vin, gnd = 1 * V(5), 1 * V('generator'), Pin()
vcc['+'] + amp['vcc']
gnd + vcc['-'] + amp['gnd'] + vin['-']
vin['+'] + amp['vin']
return Circuit(locals())
gnucap = GnucapSession()
dut = EmitterAmplifierTestbench()
gnucap.build(dut)
vin = v(dut['amp']['vin'])
vout = v(dut['amp']['vout'])
ve = v(dut['amp']['q']['e'])
ic = i(dut['amp']['rc'])
dataset = gnucap.simulate('op', Sweep(-5, 50, 5), ic)
plot(dataset, ic,
title='Operating point',
xlabel='Temperature [°C]',
ylabel='Quiescent current [A]')
dataset = gnucap.simulate('dc', Sweep(0, 5, 0.1), vin, vout, ve)
plot(dataset, vin, vout, ve,
title='Transfer function',
xlabel='Vin [V]',
ylabel='Voltage [V]')
dataset = gnucap.simulate('ac', Sweep(20, 20e3, 2, log=True), vout)
plot(dataset, vout,
title='Frequency response',
xlabel='Frequency [Hz]',
xscale='log',
ylabel='Voltage [V]')
gnucap.exit()
Hi, dvc94ch. You asked if SKiDL could be a library. I was under the impression that it already is a library. So I need some further explanation of what you would like from SKiDL in order to further your project.
I'm not quite sure yet... Looking for good ideas to steal :)
My initial thought was that footprints could be generated automatically from a csg model of the package using something like pycsg. But looking at a version of IPC-SM-782 from 1999 I can not find an equation that relates the pad size to the pin geometry, so the footprint and the package model would still need to be described separately.
[0] https://pypi.python.org/pypi/pycsg