-
Notifications
You must be signed in to change notification settings - Fork 118
/
index.html
1281 lines (1007 loc) · 44 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
layout: home
title: SKiDL
description: Use Python to create circuits.
headline: SKiDL
tags: [Python, SKiDL, home]
---
{% markdown %}
# TL;DR
**Never use a lousy schematic editor again!**
SKiDL is a simple module that lets you describe electronic circuits using Python.
The resulting Python program outputs a netlist that a PCB layout tool uses to
create a finished circuit board.
### Contents
* [Introduction](#introduction)
* [Installation](#installation)
* [Basic Usage](#basic-usage)
* [Going Deeper](#going-deeper)
* [Converting Existing Designs to SKiDL](#converting-existing-designs-to-skidl)
# Introduction
SKiDL is a module that allows you to compactly describe the interconnection of
electronic circuits and components using Python.
The resulting Python program performs electrical rules checking
for common mistakes and outputs a netlist that serves as input to
a PCB layout tool.
## Features
* Has a powerful, flexible syntax (because it *is* Python).
* Permits compact descriptions of electronic circuits (think about *not* tracing
signals through a multi-page schematic).
* Allows textual descriptions of electronic circuits (think about using
`diff` and [git](https://en.wikipedia.org/wiki/Git) for circuits).
* Performs electrical rules checking (ERC) for common mistakes (e.g., unconnected device I/O pins).
* Supports linear / hierarchical / mixed descriptions of electronic designs.
* Fosters design reuse (think about using [PyPi](pypi.org) and [Github](github.com)
to distribute electronic designs).
* Makes possible the creation of *smart circuit modules* whose behavior / structure are changed parametrically
(think about filters whose component values are automatically adjusted based on your
desired cutoff frequency).
* Can work with any ECAD tool (only two methods are needed: one for reading the part libraries and another
for outputing the correct netlist format).
* Takes advantage of all the benefits of the Python ecosystem (because it *is* Python).
* Free software: MIT license.
* Open source: [https://github.com/xesscorp/skidl](https://github.com/xesscorp/skidl)
As a very simple example, the SKiDL program below describes a circuit that
takes an input voltage, divides it by three, and outputs it:
{% highlight python %}
from skidl import *
gnd = Net('GND') # Ground reference.
vin = Net('VI') # Input voltage to the divider.
vout = Net('VO') # Output voltage from the divider.
r1, r2 = 2 * Part("Device", 'R', TEMPLATE) # Create two resistors.
r1.value, r1.footprint = '1K', 'Resistors_SMD:R_0805' # Set resistor values
r2.value, r2.footprint = '500', 'Resistors_SMD:R_0805' # and footprints.
r1[1] += vin # Connect the input to the first resistor.
r2[2] += gnd # Connect the second resistor to ground.
vout += r1[2], r2[1] # Output comes from the connection of the two resistors.
generate_netlist()
{% endhighlight %}
And this is the netlist output that can be fed to a program like KiCad's `PCBNEW` to
create the physical PCB:
{% highlight text %}
(export (version D)
(design
(source "C:/Users/DEVB/PycharmProjects/test1\test.py")
(date "08/12/2016 11:13 AM")
(tool "SKiDL (0.0.1)"))
(components
(comp (ref R1)
(value 1K)
(footprint Resistors_SMD:R_0805))
(comp (ref R2)
(value 500)
(footprint Resistors_SMD:R_0805)))
(nets
(net (code 0) (name "VI")
(node (ref R1) (pin 1)))
(net (code 1) (name "GND")
(node (ref R2) (pin 2)))
(net (code 2) (name "VO")
(node (ref R1) (pin 2))
(node (ref R2) (pin 1))))
)
{% endhighlight %}
# Installation
SKiDL is pure Python so it's easy to install:
{% highlight bash %}
$ pip install skidl
{% endhighlight %}
or:
{% highlight bash %}
$ easy_install skidl
{% endhighlight %}
In order for SKiDL to access part libraries,
you'll also need to install [KiCad](http://kicad-pcb.org/).
# 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.
## Accessing SKiDL
To use skidl in a project, just place the following at the top of your file:
{% highlight python %}
import skidl
{% endhighlight %}
But for this tutorial, I'll just import everything:
{% highlight python %}
from skidl import *
{% endhighlight %}
## Finding Parts
SKiDL provides a convenience function for searching for parts called
(naturally) `search`.
For example, if you needed an operational amplifier, then the following command would
pull up some likely candidates:
{% highlight terminal %}
>>> search('opamp')
linear.lib: LT1492
linear.lib: MCP601R
linear.lib: LT1493
linear.lib: MCP603
linear.lib: LM4250
...
linear.lib: LM386
linear.lib: MCP603SN
linear.lib: INA128
linear.lib: LTC6082
linear.lib: MCP601SN
{% endhighlight %}
`search` accepts a regular expression and scans for it *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](#libraries).)
So the following search pulls up several candidates:
{% highlight terminal %}
>>> search('lm35')
dc-dc.lib: LM3578
linear.lib: LM358
regul.lib: LM350T
sensors.lib: LM35-NEB
sensors.lib: LM35-D
sensors.lib: LM35-LP
{% endhighlight %}
If you want to restrict the search to a specific part, then
use a regular expression like the following:
{% highlight terminal %}
>>> search('^lm358$')
linear.lib: LM358
{% endhighlight %}
Once you have the part name and library, you can see the part's pin numbers, names
and their functions using the `show` function:
{% highlight terminal %}
>>> show('linear', 'LM358')
LM358:
Pin 4/V-: POWER-IN
Pin 8/V+: POWER-IN
Pin 1/~: OUTPUT
Pin 2/-: INPUT
Pin 3/+: INPUT
Pin 5/+: INPUT
Pin 6/-: INPUT
Pin 7/~: OUTPUT
{% endhighlight %}
`show` looks for exact matches of the part name in a library, so the following
command raises an error:
{% highlight terminal %}
>>> show('linear', 'lm35')
ERROR: Unable to find part lm35 in library linear.
{% endhighlight %}
## Instantiating Parts
The part library and name are used to instantiate a part as follows:
{% highlight terminal %}
>>> resistor = Part("Device",'R')
{% endhighlight %}
You can customize the resistor by setting its attributes:
{% highlight terminal %}
>>> resistor.value = '1K'
>>> resistor.value
'1K'
{% endhighlight %}
You can also combine the setting of attributes with the creation of the part:
{% highlight terminal %}
>>> resistor = Part("Device", 'R', value='1K')
>>> resistor.value
'1K'
{% endhighlight %}
You can use any valid Python name for a part attribute, but `ref`, `value`,
and `footprint` are necessary in order to generate the final netlist
for your circuit. And the attribute can hold any type of Python object,
but simple strings are probably the most useful.
The `ref` attribute holds the *reference* for the part. It's set automatically
when you create the part:
{% highlight terminal %}
>>> resistor.ref
'R1'
{% endhighlight %}
Since this was the first resistor we created, it has the honor of being named `R1`.
But you can easily change it:
{% highlight terminal %}
>>> resistor.ref = 'R5'
>>> resistor.ref
'R5'
{% endhighlight %}
Now what happens if we create another resistor?:
{% highlight terminal %}
>>> another_res = Part("Device",'R')
>>> another_res.ref
'R1'
{% endhighlight %}
Since the `R1` reference was now available, the new resistor got it.
What if we tried renaming the first resistor back to `R1`:
{% highlight terminal %}
>>> resistor.ref = 'R1'
>>> resistor.ref
'R1_1'
{% endhighlight %}
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 confuse the hell out of everybody.
## Connecting Pins
Parts are great and all, 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* on 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):
{% highlight python %}
>>> rup = Part("Device", 'R', value='1K', footprint='Resistors_SMD:R_0805')
>>> rlow = Part("Device", 'R', value='500', footprint='Resistors_SMD:R_0805')
>>> rup.ref, rlow.ref
('R1', 'R2')
>>> rup.value, rlow.value
('1K', '500')
{% endhighlight %}
To bring the voltage that will be divided into the circuit, let's create a net:
{% highlight terminal %}
>>> v_in = Net('VIN')
>>> v_in.name
'VIN'
{% endhighlight %}
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):
{% highlight terminal %}
>>> rup[1] += v_in
{% endhighlight %}
You can verify that the net is attached to pin 1 of the resistor like this:
{% highlight terminal %}
>>> rup[1].net
VIN: Pin 1/~: PASSIVE
{% endhighlight %}
Next, create a ground reference net and attach it to `rlow`:
{% highlight terminal %}
>>> gnd = Net('GND')
>>> rlow[1] += gnd
>>> rlow[1].net
GND: Pin 1/~: PASSIVE
{% endhighlight %}
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:
{% highlight terminal %}
>>> v_out = Net('VO')
>>> v_out += rup[2], rlow[2]
>>> rup[2].net, rlow[2].net
(VO: Pin 2/~: PASSIVE, Pin 2/~: PASSIVE, VO: Pin 2/~: PASSIVE, Pin 2/~: PASSIVE)
{% endhighlight %}
An alternate method is to connect the resistors and then attach their
junction to the output net:
{% highlight terminal %}
>>> rup[2] += rlow[2]
>>> v_out = Net('VO')
>>> v_out += rlow[2]
>>> rup[2].net, rlow[2].net
(VO: Pin 2/~: PASSIVE, Pin 2/~: PASSIVE, VO: Pin 2/~: PASSIVE, Pin 2/~: PASSIVE)
{% endhighlight %}
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.
## Checking for Errors
Once the parts are wired together, you can do simple electrical rules checking
like this:
{% highlight terminal %}
>>> ERC()
2 warnings found during ERC.
0 errors found during ERC.
{% endhighlight %}
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:
{% highlight terminal %}
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.
{% endhighlight %}
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:
{% highlight terminal %}
>>> v_in.do_erc = False
>>> gnd.do_erc = False
>>> ERC()
No ERC errors or warnings found.
{% endhighlight %}
## Generating a Netlist
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:
{% highlight terminal %}
>>> generate_netlist()
{% endhighlight %}
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`.
{% highlight text %}
(export (version D)
(design
(source "C:\xesscorp\KiCad\tools\skidl\skidl\skidl.py")
(date "08/12/2016 10:05 PM")
(tool "SKiDL (0.0.1)"))
(components
(comp (ref R1)
(value 1K)
(footprint Resistors_SMD:R_0805))
(comp (ref R2)
(value 500)
(footprint Resistors_SMD:R_0805)))
(nets
(net (code 0) (name "VIN")
(node (ref R1) (pin 1)))
(net (code 1) (name "GND")
(node (ref R2) (pin 1)))
(net (code 2) (name "VO")
(node (ref R1) (pin 2))
(node (ref R2) (pin 2))))
)
{% endhighlight %}
You can also generate the netlist in XML format:
{% highlight terminal %}
>>> generate_xml()
{% endhighlight %}
This is useful in a KiCad environment where the XML file is used as the
input to BOM-generation tools.
# Going Deeper
The previous section showed the bare minimum you need to know to design
circuits with SKiDL, but doing a complicated circuit that way would suck donkeys.
This section will talk about some more advanced features.
## Basic SKiDL Objects: Parts, Pins, Nets, and Buses
SKiDL uses four types of objects to represent a circuit: `Part`, `Pin`,
`Net`, and `Bus`.
The `Part` object represents an electronic component, which SKiDL thinks of as simple
bags of `Pin` objects with a few other attributes attached
(like the part number, name, reference, value, footprint, etc.).
The `Pin` object represents a terminal that brings an electronic signal into
and out of the part. Each `Pin` object has two important attributes:
* `part` which stores the reference to the `Part` object to which the pin belongs.
* `net` which stores the the reference to the `Net` object that the pin is
connected to, or `None` if the pin is unconnected.
A `Net` object is kind of like a `Part`: it's a simple bag of pins.
The difference is, unlike a part, pins can be added to a net.
This happens when a pin on some part is connected to the net or when the
net is merged with another net.
Finally, a `Bus` is just a list of `Net` objects.
A bus of a certain width can be created from a number of existing nets,
newly-created nets, or both.
## Creating SKiDL Objects
Here's the most common way to create a part in your circuit:
{% highlight py %}
my_part = Part('some_library', 'some_part_name')
{% endhighlight %}
When this is processed, the current directory will be checked for a file
called `some_library.lib` which will be opened and scanned for a part with the
name `some_part_name`. If the file is not found or it doesn't contain
the requested part, then the process will be repeated using KiCad's default
library directory.
(You can change SKiDL's library search by changing the list of directories
stored in the `skidl.lib_search_paths_kicad` list.)
You're not restricted to using only the current directory or the KiCad default
directory to search for parts. You can also search any file for a part by
using a full file name:
{% highlight py %}
my_part = Part('C:/my_libs/my_great_parts.lib', 'my_super_regulator')
{% endhighlight %}
You're also not restricted to getting an exact match on the part name: you can
use a *regular expression* instead. For example, this will find a part
with "358" anywhere in a part name or alias:
{% highlight py %}
my_part = Part('some_library', '.*358.*')
{% endhighlight %}
If the regular expression matches more than one part, then you'll only get the
first match and a warning that multiple parts were found.
Once you have a part, you can set its attributes like you could for any Python
object. As was shown previously, the `ref` attribute will already be set
but you can override it:
{% highlight py %}
my_part.ref = 'U5'
{% endhighlight %}
The `value` and `footprint` attributes are also required for generating
a netlist. But you can also add any other attribute:
{% highlight py %}
my_part.manf = 'Atmel'
my_part.setattr('manf#', 'ATTINY4-TSHR'
{% endhighlight %}
It's also possible to set the attributes during the part creation:
{% highlight py %}
my_part = Part('some_lib', 'some_part', ref='U5', footprint='SMD:SOT23_6', manf='Atmel')
{% endhighlight %}
Creating nets is also simple:
{% highlight py %}
my_net = Net() # An unnamed net.
my_other_net = Net('Fred') # A named net.
{% endhighlight %}
As with parts, SKiDL will alter the name you assign to a net if it collides with another net
having the same name.
You can create a bus of a certain width like this:
{% highlight py %}
my_bus = Bus('bus_name', 8) # Create a byte-wide bus.
{% endhighlight %}
(All buses must be named, but SKiDL will look for and correct colliding
bus names.)
You can also create a bus from existing nets, or buses, or the pins of parts:
{% highlight py %}
my_part = Part('linear', 'LM358')
a_net = Net()
b_net = Net()
bus_nets = Bus('net_bus', a_net, b_net) # A 2-bit bus.
bus_pins = Bus('pin_bus', my_part[1], my_part[3]) # A 2-bit bus.
bus_buses = Bus('bus_bus', my_bus) # An 8-bit bus.
{% endhighlight %}
Finally, you can mix-and-match any combination of widths, nets, buses or part pins:
{% highlight py %}
bus_mixed = Bus('mongrel', 8, a_net, my_bus, my_part[2]) # 8+1+8+1 = 18-bit bus.
{% endhighlight %}
Finally, you can modify an existing bus by inserting or extending it with any combination
of widths, nets, buses or pins:
{% highlight py %}
bus = Bus('A', 8) # Eight-bit bus.
bus.insert(4, Bus('I', 3)) # Insert 3-bit bus before bus line bus[4].
bus.extend(5, Pin(), Net()) # Extend bus with another 5-bit bus, a pin, and a net.
{% endhighlight %}
The final object you can create is a `Pin`. You'll probably never do this
(except in interactive sessions), and it's probably a mistake if
you ever do do it, but here's how to do it:
{% highlight terminal %}
>>> p = Pin(num=1, name='my_pin', func=Pin.TRISTATE)
>>> p
Pin 1/my_pin: TRISTATE
{% endhighlight %}
## Copying SKiDL Objects
Instead of creating a SKiDL object from scratch, sometimes it's easier to just
copy an existing object. Here are some examples of creating a resistor and then making
some copies of it:
{% highlight terminal %}
>>> r1 = Part("Device", 'R', value=500)
>>> r2 = r1.copy() # Make a single copy of the resistor.
>>> r3 = r1.copy(value='1K') # Make a single copy, but give it a different value.
>>> r4 = r1(value='1K') # You can also call the object directly to make copies.
>>> r5, r6, r7 = r1(3) # Make 3 copies of the resistor.
>>> r8, r9, r10 = r1(value=[110,220,330]) # Make 3 copies, each with a different value.
>>> r11, r12 = 2 * r1 # Make copies using the '*' operator.
{% endhighlight %}
In some cases it's clearer to create parts by copying a *template part* that
doesn't actually get included in the netlist for the circuitry.
This is done like so:
{% highlight terminal %}
>>> r_template = Part("Device", 'R', dest=TEMPLATE) # Create a resistor just for copying.
>>> r1 = r_template(value='1K') # Make copy that becomes part of the actual circuitry.
{% endhighlight %}
## Accessing Part Pins and Bus Lines
You can access the pins on a part or the individual nets of a bus
using numbers, slices, strings, and regular expressions, either singly or in any combination.
Suppose you have a PIC10 processor in a six-pin package:
{% highlight terminal %}
>>> pic10 = Part('microchip_pic10mcu', 'pic10f220-i/ot')
>>> pic10
PIC10F220-I/OT:
Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL
Pin 2/VSS: POWER-IN
Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL
Pin 5/VDD: POWER-IN
Pin 6/Vpp/~MCLR~/GP3: INPUT
{% endhighlight %}
The most natural way to access one of its pins is to give the pin number
in brackets:
{% highlight terminal %}
>>> pic10[3]
Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
{% endhighlight %}
(If you have a part in a BGA package with pins numbers like `C11`, then
you'll have to enter the pin number as a quoted string like `'C11'`.)
You can also get several pins at once in a list:
{% highlight terminal %}
>>> pic10[3,1,6]
[Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL, Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL, Pin 6/Vpp/~MCLR~/GP3: INPUT]
{% endhighlight %}
You can even use Python slice notation:
{% highlight terminal %}
>>> pic10[2:4] # Get pins 2 through 4.
[Pin 2/VSS: POWER-IN, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL, Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL]
>>> pic10[4:2] # Get pins 4 through 2.
[Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL, Pin 2/VSS: POWER-IN]
>>> pic10[:] # Get all the pins.
[Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL, Pin 2/VSS: POWER-IN, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL, Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL, Pin 5/VDD: POWER-IN, Pin 6/Vpp/~MCLR~/GP3: INPUT]
{% endhighlight %}
(It's important to note that the slice notation used by SKiDL for parts is slightly
different than standard Python. In Python, a slice `n:m` would fetch indices
`n`, `n+1`, `...`, `m-1`. With SKiDL, it actually fetches all the
way up to the last number: `n`, `n+1`, `...`, `m-1`, `m`.
The reason for doing this is that most electronics designers are used to
the bounds on a slice including both endpoints. Perhaps it is a mistake to
do it this way. We'll see...)
Instead of pin numbers, sometimes it makes the design intent more clear to
access pins by their names.
For example, it's more obvious that a voltage supply net is being
attached to the power pin of the processor when it's expressed like this:
{% highlight py %}
pic10['VDD'] += supply_5V
{% endhighlight %}
You can use multiple names or regular expressions to get more than one pin:
{% highlight terminal %}
>>> pic10['VDD','VSS']
[Pin 5/VDD: POWER-IN, Pin 2/VSS: POWER-IN]
>>> pic10['.*GP[1-3]']
[Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL, Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL, Pin 6/Vpp/~MCLR~/GP3: INPUT]
{% endhighlight %}
It can be tedious and error prone entering all the quote marks if you're accessing
many pin names. SKiDL lets you enter a single, comma-delimited string of
pin names:
{% highlight terminal %}
>>> pic10['.*GP0, .*GP1, .*GP2']
[Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL, Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL]
{% endhighlight %}
`Part` objects also provide the `get_pins()` function which can select pins in even more ways.
For example, this would get every bidirectional pin of the processor:
{% highlight terminal %}
>>> pic10.get_pins(func=Pin.BIDIR)
[Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL, Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL]
{% endhighlight %}
However, slice notation doesn't work with pin names. You'll get an error if you try that.
Accessing the individual nets of a bus works similarly to accessing part pins:
{% highlight terminal %}
>>> a = Net('NET_A') # Create a named net.
>>> b = Bus('BUS_B', 8, a) # Create a nine-bit bus.
>>> b
BUS_B:
BUS_B0: # Note how the individual nets of the bus are named.
BUS_B1:
BUS_B2:
BUS_B3:
BUS_B4:
BUS_B5:
BUS_B6:
BUS_B7:
NET_A: # The last net retains its original name.
>>> b[0] # Get the first net of the bus.
BUS_B0:
>>> b[4,8] # Get the fifth and ninth bus lines.
[BUS_B4: , NET_A: ]
>>> b[3:0] # Get the first four bus lines in reverse order.
[BUS_B3: , BUS_B2: , BUS_B1: , BUS_B0: ]
>>> b['BUS_B.*'] # Get all the bus lines except the last one.
[BUS_B0: , BUS_B1: , BUS_B2: , BUS_B3: , BUS_B4: , BUS_B5: , BUS_B6: , BUS_B7: ]
>>> b['NET_A'] # Get the last bus line.
NET_A:
{% endhighlight %}
## Making Connections
Pins, nets, parts and buses can all be connected together in various ways, but
the primary rule of SKiDL connections is:
**The `+=` operator is the only way to make connections!**
At times you'll mistakenly try to make connections using the
assignment operator (`=`). In many cases, SKiDL warns you if you do that,
but there are situations where it can't (because
Python is a general-purpose programming language where
assignment is a necessary operation).
So remember the primary rule!
After the primary rule, the next thing to remember is that SKiDL's main
purpose is creating netlists. To that end, it handles four basic, connection operations:
**Pin-to-Net**:
A pin is connected to a net, adding it to the list of pins
connected to that net. If the pin is already attached to other nets,
then those nets are connected to this net as well.
**Net-to-Pin**:
This is the same as doing a pin-to-net connection.
**Pin-to-Pin**:
A net is created and both pins are attached to it. If one or
both pins are already connected to other nets, then those nets are connected
to the newly-created net as well.
**Net-to-Net**:
Connecting one net to another *merges* the pins on both nets
onto a single, larger net.
There are three variants of each connection operation:
**One-to-One**:
This is the most frequent type of connection, for example, connecting one
pin to another or connecting a pin to a net.
**One-to-Many**:
This mainly occurs when multiple pins are connected to the same net, like
when multiple ground pins of a chip are connected to the circuit ground net.
**Many-to-Many**:
This usually involves bus connections to a part, such as connecting
a bus to the data or address pins of a processor. But there must be the
same number of things to connect in each set, e.g. you can't connect
three pins to four nets.
As a first example, let's connect a net to a pin on a part:
{% highlight terminal %}
>>> pic10 = Part('microchip_pic10mcu','pic10f220-i/ot') # Get a part.
>>> io = Net('IO_NET') # Create a net.
>>> pic10['.*GP0'] += io # Connect the net to a part pin.
>>> io # Show the pins connected to the net.
IO_NET: Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL
{% endhighlight %}
You can do the same operation in reverse by connecting the part pin to the net
with the same result:
{% highlight terminal %}
>>> pic10 = Part('microchip_pic10mcu','pic10f220-i/ot')
>>> io = Net('IO_NET')
>>> io += pic10['.*GP0'] # Connect a part pin to the net.
>>> io
IO_NET: Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL
{% endhighlight %}
You can also connect a pin directly to another pin.
In this case, an *implicit net* will be created between the pins that can be
accessed using the `net` attribute of either part pin:
{% highlight terminal %}
>>> pic10['.*GP1'] += pic10['.*GP2'] # Connect two pins together.
>>> pic10['.*GP1'].net # Show the net connected to the pin.
N$1: Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
>>> pic10['.*GP2'].net # Show the net connected to the other pin. Same thing!
N$1: Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
{% endhighlight %}
You can connect multiple pins together, all at once:
{% highlight terminal %}
>>> pic10[1] += pic10[2,3,6]
>>> pic10[1].net
N$1: Pin 6/Vpp/~MCLR~/GP3: INPUT, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL, Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL, Pin 2/VSS: POWER-IN
{% endhighlight %}
Or you can do it incrementally:
{% highlight terminal %}
>>> pic10[1] += pic10[2]
>>> pic10[1] += pic10[3]
>>> pic10[1] += pic10[6]
>>> pic10[1].net
N$1: Pin 2/VSS: POWER-IN, Pin 6/Vpp/~MCLR~/GP3: INPUT, Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
{% endhighlight %}
If you connect pins on separate nets together, then all the pins are merged onto the same net:
{% highlight terminal %}
>>> pic10[1] += pic10[2] # Put pins 1 & 2 on one net.
>>> pic10[1].net
N$1: Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL, Pin 2/VSS: POWER-IN
>>> pic10[3] += pic10[4] # Put pins 3 & 4 on another net.
>>> pic10[3].net
N$2: Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
>>> pic10[1] += pic10[4] # Connect two pins from different nets.
>>> pic10[3].net # Now all the pins are on the same net!
N$2: Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL, Pin 2/VSS: POWER-IN, Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL, Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
{% endhighlight %}
Here's an example of connecting a three-bit bus to three pins on a part:
{% highlight terminal %}
>>> pic10 = Part('microchip_pic10mcu','pic10f220-i/ot')
>>> pic10
PIC10F220-I/OT:
Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL
Pin 2/VSS: POWER-IN
Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL
Pin 5/VDD: POWER-IN
Pin 6/Vpp/~MCLR~/GP3: INPUT
>>> b = Bus('GP', 3) # Create a 3-bit bus.
>>> pic10[4,3,1] += b[2:0] # Connect bus to part pins, one-to-one.
>>> b
GP:
GP0: Pin 1/ICSPDAT/AN0/GP0: BIDIRECTIONAL
GP1: Pin 3/ICSPCLK/AN1/GP1: BIDIRECTIONAL
GP2: Pin 4/T0CKI/FOSC4/GP2: BIDIRECTIONAL
{% endhighlight %}
But SKiDL will warn you if there aren't the same number of things to
connect on each side:
{% highlight terminal %}
>>> pic10[4,3,1] += b[1:0] # Too few bus lines for the pins!
ERROR: Connection mismatch 3 != 2!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "c:\xesscorp\kicad\tools\skidl\skidl\skidl.py", line 2630, in __iadd__
raise Exception
Exception
{% endhighlight %}
## Hierarchy
SKiDL supports the encapsulation of parts, nets and buses into modules
that can be replicated to reduce the design effort, and can be used in
other modules to create a functional hierarchy.
It does this using Python's built-in machinery for defining and calling functions
so there's almost nothing new to learn.
As an example, here's the voltage divider as a module:
{% highlight py %}
from skidl import *
import sys
# Define the voltage divider module. The @SubCircuit decorator
# handles some skidl housekeeping that needs to be done.
@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
gnd = Net('GND') # GLobal ground net.
input_net = Net('IN') # Net with the voltage to be divided.
output_net = Net('OUT') # Net with the divided voltage.
# Instantiate the voltage divider and connect it to the input & output nets.
vdiv(input_net, output_net)
generate_netlist(sys.stdout)
{% endhighlight %}
For the most part, `vdiv` is just a standard Python function:
it accepts inputs, it performs operations on them, and it could return
outputs (but in this case, it doesn't need to).
Other than the `@SubCircuit` decorator that appears before the function definition,
`vdiv` is just a Python function and it can do anything that a Python function can do.
Here's the netlist that's generated:
{% highlight text %}
(export (version D)
(design
(source "C:/Users/DEVB/PycharmProjects/test1\test.py")
(date "08/15/2016 03:35 PM")
(tool "SKiDL (0.0.1)"))
(components
(comp (ref R1)
(value 1K)
(footprint Resistors_SMD:R_0805))
(comp (ref R2)
(value 500)
(footprint Resistors_SMD:R_0805)))
(nets
(net (code 0) (name "IN")
(node (ref R1) (pin 1)))
(net (code 1) (name "OUT")
(node (ref R1) (pin 2))
(node (ref R2) (pin 1)))
(net (code 2) (name "GND")
(node (ref R2) (pin 2))))
)
{% endhighlight %}
For an example of a multi-level hierarchy, the `multi_vdiv` module shown below
can use the `vdiv` module to divide a voltage multiple times:
{% highlight py %}
from skidl import *
import sys
# Define the voltage divider module.
@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
@SubCircuit
def multi_vdiv(repeat, inp, outp):
"""Divide inp voltage by 3 ** repeat and place it on outp net."""
for _ in range(repeat):
out_net = Net() # Create an output net for the current stage.
vdiv(inp, out_net) # Instantiate a divider stage.
inp = out_net # The output net becomes the input net for the next stage.
outp += out_net # Connect the output from the last stage to the module output net.
gnd = Net('GND') # GLobal ground net.
input_net = Net('IN') # Net with the voltage to be divided.
output_net = Net('OUT') # Net with the divided voltage.
multi_vdiv(3, input_net, output_net) # Run the input through 3 voltage dividers.
generate_netlist(sys.stdout)
{% endhighlight %}
(For the EE's out there: *yes, I know cascading three simple voltage dividers
will not multiplicatively scale the input voltage because of the
input and output impedances of each stage!*
It's just the simplest example I could think of that shows the feature.)
And here's the resulting netlist: