Basic Usage#

This is the minimum that you need to know to design electronic circuitry using SKiDL:

  • How to get access to SKiDL.

  • How to find and instantiate a component (or part).

  • How to connect pins of the parts to each other using nets.

  • How to run an ERC on the circuit.

  • How to generate a netlist for the circuit that serves as input to a PCB layout tool.

I’ll demonstrate these steps using SKiDL in an interactive Python session, but normally the statements that are shown would be entered into a file and executed as a Python script.

!pip install skidl
from skidl import *

# Create 0603 resistor and give it a value of 1k Ohms
resistor = Part('Device','R', footprint='digikey-footprints:0603')
resistor.value = '1K' 

# Generate the KiCAD netlist file
generate_netlist(file_="skidl_basics.netlist")

# Print out netlist file to console
with open('skidl_basics.netlist', 'r') as f:
    print(f.read())
f.close()
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: skidl in /home/shanemattner/.local/lib/python3.10/site-packages (1.1.0)
Requirement already satisfied: graphviz in /home/shanemattner/.local/lib/python3.10/site-packages (from skidl) (0.20)
Requirement already satisfied: kinet2pcb in /home/shanemattner/.local/lib/python3.10/site-packages (from skidl) (1.0.1)
Requirement already satisfied: kinparse>=0.1.0 in /home/shanemattner/.local/lib/python3.10/site-packages (from skidl) (1.1.0)
Requirement already satisfied: future>=0.15.0 in /home/shanemattner/.local/lib/python3.10/site-packages (from skidl) (0.18.2)
Requirement already satisfied: pyparsing>=2.1.1 in /usr/lib/python3.10/site-packages (from kinparse>=0.1.0->skidl) (2.4.7)
Requirement already satisfied: hierplace in /home/shanemattner/.local/lib/python3.10/site-packages (from kinet2pcb->skidl) (1.0.0)
(export (version D)
  (design
    (source "/home/shanemattner/.local/lib/python3.10/site-packages/ipykernel_launcher.py")
    (date "08/07/2022 06:56 PM")
    (tool "SKiDL (1.1.0)"))
  (components
    (comp (ref R1)
      (value 1K)
      (footprint digikey-footprints:0603)
      (fields
        (field (name F0) R)
        (field (name Reference) R)
        (field (name F1) R)
        (field (name Value) R)
        (field (name F3) ~)
        (field (name Datasheet) ~)
        (field (name F4) "R res resistor")
        (field (name ki_keywords) "R res resistor")
        (field (name F5) Resistor)
        (field (name ki_description) Resistor)
        (field (name F6) R_*)
        (field (name ki_fp_filters) R_*))
      (libsource (lib Device) (part R))
      (sheetpath (names /top/4485304031495430821) (tstamps /top/4485304031495430821))))
  (nets)
)
No errors or warnings found while generating netlist.

Accessing SKiDL#

To use skidl in a project, just place the following at the top of your file:

import skidl

But for this tutorial, I’ll just import everything:

from skidl import *

Finding Parts#

Command-line Searching#

SKiDL provides a convenience function for searching for parts called (naturally) search. For example, if you need an operational amplifier, then the following command would pull up a long list of likely candidates:

>>> search('opamp')
Amplifier_Audio.lib: OPA1622 (High-Fidelity, Bipolar-Input, Audio Operational Amplifier, VSON-10)
Amplifier_Audio.lib: LM386 (Low Voltage Audio Power Amplifier, DIP-8/SOIC-8/SSOP-8)
Amplifier_Difference.lib: LM733CH (Single Differential Amplifier, TO-5-10)
Amplifier_Difference.lib: LM733H (Single Differential Amplifier, TO-5-10)
Amplifier_Difference.lib: LM733CN (Single Differential Amplifier, DIP-14)
Amplifier_Instrumentation.lib: INA326 (Precision, Rail-to-Rail I/O Instrumentation Amplifier, MSOP-8 package)
Amplifier_Instrumentation.lib: INA327 (Precision, Rail-to-Rail I/O Instrumentation Amplifier, MSOP-10 package)
Amplifier_Instrumentation.lib: INA129 (Precision, Low Power Instrumentation Amplifier G = 1 + 49.4kOhm/Rg, DIP-8/SOIC-8)
Amplifier_Instrumentation.lib: INA128 (Precision, Low Power Instrumentation Amplifier G = 1 + 49.4kOhm/Rg, DIP-8/SOIC-8)
Amplifier_Operational.lib: OPA842xD (Single rail-to-rail input/output 8 MHz operational amplifiers, SOIC-8)
Amplifier_Operational.lib: OPA188xxD (Single rail-to-rail input/output 8 MHz operational amplifiers, SOIC-8)
Amplifier_Operational.lib: OPA855xDSG (1.8 GHz Unity-Gain Bandwidth FET Input Amplifier, WSON-8)
Amplifier_Operational.lib: SA5534 (Single Low-Noise Operational Amplifiers, DIP-8/SOIC-8)
...

search accepts keywords and scans for them anywhere within the name, description and keywords of all the parts in the library path. (You can read more about how SKiDL handles libraries here.) If you want search for an exact match, then use a regular expression like the following:

>>> search('^lm386$')
Amplifier_Audio.lib: LM386 (Low Voltage Audio Power Amplifier, DIP-8/SOIC-8/SSOP-8)

If you give search multiple terms, then it will find parts that contain all those terms:

>>> search('opamp low-noise dip-8')
Amplifier_Operational.lib: AD797 (Single Low-Noise Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: LM101 (Single Low-Noise Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: LM301 (Single Low-Noise Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: LT1012 (Single Low-Noise Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: SA5534 (Single Low-Noise Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: NE5534 (Single Low-Noise Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: LM201 (Single Low-Noise Operational Amplifiers, DIP-8/SOIC-8)

You can also use the | character to find parts that contain at least one of a set of choices:

>>> search('opamp (low-noise|dip-8)')

Amplifier_Audio.lib: LM386 (Low Voltage Audio Power Amplifier, DIP-8/SOIC-8/SSOP-8)
Amplifier_Operational.lib: TLV2371P (Rail-to-Rail Input/Output Operational Amplifier, PDIP-8)
Amplifier_Operational.lib: MAX4239ASA (Ultra-Low Offset/Drift, Low-Noise, Precision Amplifiers, SOIC-8)
Amplifier_Operational.lib: LM4250 (Programmable Operational Amplifier, DIP-8/SOIC-8)
Amplifier_Operational.lib: LF256 (Single JFET-Input Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: MAX4239AUT (Ultra-Low Offset/Drift, Low-Noise, Precision Amplifiers, SOT-23-6)
Amplifier_Operational.lib: LM6361 (Single High Speed Operational Amplifier, DIP-8/SOIC-8)
Amplifier_Operational.lib: LF257 (Single JFET-Input Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: AD8603 (Zero-Drift, Precision, Low-Noise, Rail-to-Rail Output, 36-V Operational Amplifier, TSOT-23-5)
...

If you need to search for a string containing spaces, just enclose it in quotes:

>>> search('opamp "high performance"')

Amplifier_Operational.lib: OP77 (Single SoundPlus High Performance Audio Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: LT1363 (Single SoundPlus High Performance Audio Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: OP07 (Single SoundPlus High Performance Audio Operational Amplifiers, DIP-8/SOIC-8)
Amplifier_Operational.lib: OPA134 (Single SoundPlus High Performance Audio Operational Amplifiers, DIP-8/SOIC-8)

Once you have the part name and library, you can see the part’s pin numbers, names and their functions using the show function:

>>> show('Amplifier_Audio', 'lm386')

 LM386 (): Low Voltage Audio Power Amplifier, DIP-8/SOIC-8/SSOP-8
    Pin None/1/GAIN/INPUT
    Pin None/2/-/INPUT
    Pin None/3/+/INPUT
    Pin None/4/GND/POWER-IN
    Pin None/5/~/OUTPUT
    Pin None/6/V+/POWER-IN
    Pin None/7/BYPASS/INPUT
    Pin None/8/GAIN/INPUT

show looks for exact matches of the part name in a library, so the following command raises an error:

>>> show('Amplifier_Audio', 'lm38')
ERROR: Unable to find part lm38 in library linear.

In addition to searching for parts, you can also search for footprints using the search_footprints command. It works similarly to the search command:

>>> search_footprints('QFN-48')

Package_DFN_QFN: QFN-48-1EP_5x5mm_P0.35mm_EP3.7x3.7mm ("QFN, 48 Pin (https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf#page=38), generated with kicad-footprint-generator ipc_noLead_generator.py" - "QFN NoLead")
Package_DFN_QFN: QFN-48-1EP_5x5mm_P0.35mm_EP3.7x3.7mm_ThermalVias ("QFN, 48 Pin (https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf#page=38), generated with kicad-footprint-generator ipc_noLead_generator.py" - "QFN NoLead")
Package_DFN_QFN: QFN-48-1EP_6x6mm_P0.4mm_EP4.2x4.2mm ("QFN, 48 Pin (https://static.dev.sifive.com/SiFive-FE310-G000-datasheet-v1p5.pdf#page=20), generated with kicad-footprint-generator ipc_noLead_generator.py" - "QFN NoLead")
Package_DFN_QFN: QFN-48-1EP_6x6mm_P0.4mm_EP4.2x4.2mm_ThermalVias ("QFN, 48 Pin (https://static.dev.sifive.com/SiFive-FE310-G000-datasheet-v1p5.pdf#page=20), generated with kicad-footprint-generator ipc_noLead_generator.py" - "QFN NoLead")
...

Zyc: A GUI Search Tool#

If you want to avoid using command-line tools, zyc lets you search for parts and footprints using a GUI. You can read more about it here.

Instantiating Parts#

You instantiate a part using its name and the library that contains it:

>>> resistor = Part('Device','R')

You can customize the resistor by setting its value attribute:

>>> resistor.value = '1K' 
>>> resistor.value        
'1K'          

It’s also possible to set attributes when creating a part:

>>> resistor = Part('Device', 'R', value='2K')
>>> resistor.value
'2K'

The ref attribute holds the part reference. It’s set automatically when you create the part:

>>> resistor.ref
'R1'

Since this was the first resistor we created, it has the honor of being named R1. But you can easily change that:

>>> resistor.ref = 'R5'
>>> resistor.ref
'R5'

Now what happens if we create another resistor?:

>>> another_res = Part('Device','R')   
>>> another_res.ref                        
'R1'

Since the R1 reference wasn’t being used, the new resistor got it. What if we tried renaming the first resistor back to R1:

>>> resistor.ref = 'R1'
>>> resistor.ref
'R1_1'

Since the R1 reference was already taken, SKiDL tried to give us something close to what we wanted. SKiDL won’t let different parts have the same reference because that would be confusing.

The ref, value, and footprint attributes are necessary when generating a final netlist for your circuit. Since a part is stored in a Python object, you can add any other attributes you want using setattr(). But if you want those attributes to be passed on within the netlist, then you should probably add them as part fields.

Connecting Pins#

Parts are great, but not very useful if they aren’t connected to anything. The connections between parts are called nets (think of them as wires) and every net has one or more part pins attached to it. SKiDL makes it easy to create nets and connect pins to them. To demonstrate, let’s build the voltage divider circuit shown in the introduction.

First, start by creating two resistors (note that I’ve also added the footprint attribute that describes the physical package for the resistors):

>>> rup = Part("Device", 'R', value='1K', footprint='Resistor_SMD.pretty:R_0805_2012Metric')                            
>>> rlow = Part("Device", 'R', value='500', footprint='Resistor_SMD.pretty:R_0805_2012Metric')                          
>>> rup.ref, rlow.ref                                                
('R1', 'R2')                                                         
>>> rup.value, rlow.value                                            
('1K', '500')  

To bring the voltage that will be divided into the circuit, let’s create a net:

>>> v_in = Net('VIN')
>>> v_in.name
'VIN'

Now attach the net to one of the pins of the rup resistor (resistors are bidirectional which means it doesn’t matter which pin, so pick pin 1):

>>> rup[1] += v_in

You can verify that the net is attached to pin 1 of the resistor like this:

>>> rup[1].net
VIN: Pin R1/1/~/PASSIVE

Next, create a ground reference net and attach it to rlow:

>>> gnd = Net('GND')
>>> rlow[1] += gnd
>>> rlow[1].net
GND: Pin R2/1/~/PASSIVE

Finally, the divided voltage has to come out of the circuit on a net. This can be done in several ways. The first way is to define the output net and then attach the unconnected pins of both resistors to it:

>>> v_out = Net('VO')
>>> v_out += rup[2], rlow[2]
>>> rup[2].net, rlow[2].net
(VO: Pin R1/2/~/PASSIVE, Pin R2/2/~/PASSIVE, VO: Pin R1/2/~/PASSIVE, Pin R2/2/~/PASSIVE)

An alternate method is to connect the resistors and then attach their junction to the output net:

>>> rup[2] += rlow[2]
>>> v_out = Net('VO')
>>> v_out += rlow[2]
>>> rup[2].net, rlow[2].net
(VO: Pin R1/2/~/PASSIVE, Pin R2/2/~/PASSIVE, VO: Pin R1/2/~/PASSIVE, Pin R2/2/~/PASSIVE)

Either way works Sometimes pin-to-pin connections are easier when you’re just wiring two devices together, while the pin-to-net connection method excels when three or more pins have a common connection.

With more complicated parts, the code is often clearer if you use pin names instead of numbers. Check out this section for how to do that.

Checking for Errors#

Once the parts are wired together, you can do simple electrical rules checking like this:

>>> ERC()                           

2 warnings found during ERC.        
0 errors found during ERC.  

Since this is an interactive session, the ERC warnings and errors are stored in the file skidl.erc. (Normally, your SKiDL circuit description is stored as a Python script such as my_circuit.py and the ERC() function will dump its messages to my_circuit.erc) The ERC messages are:

WARNING: Only one pin (PASSIVE pin 1/~ of R/R1) attached to net VIN.
WARNING: Only one pin (PASSIVE pin 1/~ of R/R2) attached to net GND.

These messages are generated because the VIN and GND nets each have only a single pin on them and this usually indicates a problem. But it’s OK for this simple example, so the ERC can be turned off for these two nets to prevent the spurious messages:

>>> v_in.do_erc = False
>>> gnd.do_erc = False
>>> ERC()

No ERC errors or warnings found.

Generating a Netlist or PCB#

The end goal of using SKiDL is to generate a netlist that can be used with a layout tool to generate a PCB. The netlist is output as follows:

>>> generate_netlist()

Like the ERC output, the netlist shown below is stored in the file skidl.net. But if your SKiDL circuit description is in the my_circuit.py file, then the netlist will be stored in my_circuit.net.

(export (version D)
  (design
    (source "/media/devb/Main/devbisme/KiCad/tools/skidl/skidl/circuit.py")
    (date "04/22/2021 01:50 PM")
    (tool "SKiDL (0.0.31dev)"))
  (components
    (comp (ref R1)
      (value 1K)
      (footprint Resistor_SMD.pretty:R_0805_2012Metric)
      (fields
        (field (name F0) R)
        (field (name F1) R))
      (libsource (lib Device) (part R))
      (sheetpath (names /top/16316864629425674383) (tstamps /top/16316864629425674383)))
    (comp (ref R2)
      (value 500)
      (footprint Resistor_SMD.pretty:R_0805_2012Metric)
      (fields
        (field (name F0) R)
        (field (name F1) R))
      (libsource (lib Device) (part R))
      (sheetpath (names /top/8136002053123588309) (tstamps /top/8136002053123588309))))
  (nets
    (net (code 1) (name GND)
      (node (ref R2) (pin 1)))
    (net (code 2) (name VIN)
      (node (ref R1) (pin 1)))
    (net (code 3) (name VO)
      (node (ref R1) (pin 2))
      (node (ref R2) (pin 2))))
)

You can also generate the netlist in XML format:

>>> generate_xml()

This is useful in a KiCad environment where the XML file is used as the input to BOM-generation tools.

If you’re designing with KiCad and want to skip some steps, you can go directly to a PCB like this:

>>> generate_pcb()

This outputs a .kicad_pcb file that you can open in PCBNEW without having to import the netlist. (Note that you will need to have KiCad installed since generate_pcb uses its pcbnew Python library to create the PCB.)