├── tests
├── __init__.py
├── test_parser.py
├── test_component.py
├── test_device.py
├── test_pinassign.py
└── test_circuit.py
├── examples
├── __init__.py
├── .gitignore
├── print_library.py
├── sallen_key
│ ├── build.py
│ └── sallen_key.py
├── mcu
│ ├── build.py
│ └── mcu.py
├── common_emitter
│ ├── build.py
│ └── common_emitter.py
├── build.py
├── joule_thief
│ ├── joule_thief.py
│ └── build.py
└── voltage_divider
│ └── voltage_divider.py
├── pycircuit
├── __init__.py
├── library
│ ├── old
│ │ ├── sifive
│ │ │ ├── __init__.py
│ │ │ └── fe310g000.py
│ │ ├── ti
│ │ │ ├── __init__.py
│ │ │ ├── tps65982.py
│ │ │ ├── tps6229x.py
│ │ │ └── tps6213x.py
│ │ ├── connectors.py
│ │ ├── busses.py
│ │ ├── circuits.py
│ │ ├── ftdi
│ │ │ └── ftdi.py
│ │ └── lattice
│ │ │ └── lattice.py
│ ├── __init__.py
│ ├── devices.py
│ ├── packages.py
│ ├── outlines.py
│ ├── components.py
│ └── design_rules.py
├── formats
│ ├── json.py
│ ├── __init__.py
│ ├── yosys.py
│ ├── kicad.py
│ ├── spice.py
│ └── svg.py
├── testbench.py
├── optimize.py
├── device.py
├── pinassign.py
├── outline.py
├── build.py
├── component.py
├── layers.py
├── traces.py
├── pcb.py
└── package.py
├── MANIFEST.in
├── viewer
├── .gitignore
├── package.json
├── app.js
├── css
│ ├── pcb.svg.css
│ └── graphviz.svg.css
└── index.html
├── placer
├── __init__.py
├── grid.py
├── bin.py
├── place.py
└── box.py
├── router
├── __init__.py
└── router.py
├── .gitignore
├── Pipfile
├── .travis.yml
├── setup.py
├── README.md
└── Pipfile.lock
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pycircuit/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | *.sp
--------------------------------------------------------------------------------
/viewer/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /files/
3 |
--------------------------------------------------------------------------------
/placer/__init__.py:
--------------------------------------------------------------------------------
1 | from placer.place import Placer
2 |
--------------------------------------------------------------------------------
/router/__init__.py:
--------------------------------------------------------------------------------
1 | from router.router import Router
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | dist/
3 | MANIFEST
4 | .cache/
5 | *.pyc
--------------------------------------------------------------------------------
/pycircuit/library/old/sifive/__init__.py:
--------------------------------------------------------------------------------
1 | from pycircuit.library.sifive.fe310g000 import *
2 |
--------------------------------------------------------------------------------
/pycircuit/library/old/ti/__init__.py:
--------------------------------------------------------------------------------
1 | from pycircuit.library.ti.tps6213x import *
2 | from pycircuit.library.ti.tps6229x import *
3 |
--------------------------------------------------------------------------------
/pycircuit/library/old/ti/tps65982.py:
--------------------------------------------------------------------------------
1 | from pycircuit.device import *
2 |
3 | # Communication Controllers
4 | # USB C port controller
5 | Device('TPS65982', Pin('1'))
6 |
--------------------------------------------------------------------------------
/pycircuit/library/__init__.py:
--------------------------------------------------------------------------------
1 | from pycircuit.library.components import *
2 | from pycircuit.library.packages import *
3 | from pycircuit.library.devices import *
4 | from pycircuit.library.design_rules import *
5 | from pycircuit.library.outlines import *
6 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 |
3 | url = "https://pypi.python.org/simple"
4 | verify_ssl = true
5 | name = "pypi"
6 |
7 |
8 | [packages]
9 |
10 | numpy = "*"
11 | pykicad = "*"
12 | shapely = "*"
13 |
14 |
15 | [dev-packages]
16 |
17 | pytest = "*"
18 | twine = "*"
19 | "autopep8" = "*"
20 |
21 |
22 | [requires]
23 |
24 | python_version = "3.6"
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.6"
4 |
5 | addons:
6 | apt:
7 | packages:
8 | - libgeos-dev
9 |
10 | # command to install dependencies
11 | install:
12 | - pip install pipenv
13 | - pipenv install --dev
14 |
15 | # command to run the dependencies
16 | script:
17 | - pipenv run pytest
18 | - pipenv run autopep8 --in-place ./**/*.py
19 | - git diff --quiet
20 |
--------------------------------------------------------------------------------
/examples/print_library.py:
--------------------------------------------------------------------------------
1 | from pycircuit.library import *
2 |
3 |
4 | def print_components():
5 | for comp in Component.components:
6 | print(repr(comp))
7 |
8 |
9 | def print_packages():
10 | for package in Package.packages:
11 | print(repr(package))
12 |
13 |
14 | def print_devices():
15 | for device in Device.devices:
16 | print(repr(device))
17 |
18 |
19 | if __name__ == '__main__':
20 | print_components()
21 | print_packages()
22 | print_devices()
23 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name='pycircuit',
5 | packages=find_packages(exclude=['tests', 'examples']),
6 | version='0.0.2',
7 | description='Library for composing circuits and pcb layouts',
8 | long_description=open('README.md').read(),
9 | author='David Craven',
10 | author_email='david@craven.ch',
11 | url='https://github.com/dvc94ch/pycircuit',
12 | keywords=['eda', 'cad', 'hdl', 'kicad'],
13 | install_requires=[
14 | 'numpy', 'scipy', 'shapely', 'pykicad', 'z3-solver'
15 | ],
16 | tests_require=['pytest'],
17 | license='ISC'
18 | )
19 |
--------------------------------------------------------------------------------
/examples/sallen_key/build.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.signal as sig
3 | from pycircuit.build import Builder
4 | from pycircuit.testbench import testbench
5 | from pycircuit.optimize import Optimizer
6 | from pycircuit.library.design_rules import oshpark_4layer
7 |
8 | from sallen_key import lp_sallen_key, top
9 |
10 |
11 | def lp_optimize():
12 | spec = sig.butter(2, 2 * np.pi * 100, btype='low', analog=True)
13 | tb = Builder(testbench(lp_sallen_key())).compile()
14 | problem = Optimizer(tb, spec)
15 | cost = problem.optimize()
16 | print(cost)
17 | problem.plot_result()
18 | print(repr(problem.netlist))
19 |
20 |
21 | if __name__ == '__main__':
22 | lp_optimize()
23 | #Builder(top(), oshpark_4layer).build()
24 |
--------------------------------------------------------------------------------
/viewer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "viewer",
3 | "version": "0.0.1",
4 | "description": "Viewer for pycircuit generated files",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/dvc94ch/pycircuit.git"
12 | },
13 | "keywords": [
14 | "pycircuit",
15 | "svg",
16 | "graphviz"
17 | ],
18 | "author": "David Craven",
19 | "license": "ISC",
20 | "bugs": {
21 | "url": "https://github.com/dvc94ch/pycircuit/issues"
22 | },
23 | "homepage": "https://github.com/dvc94ch/pycircuit#readme",
24 | "dependencies": {
25 | "express": "^4.16.2",
26 | "normalize.css": "^7.0.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/examples/mcu/build.py:
--------------------------------------------------------------------------------
1 | from pycircuit.build import Builder
2 | from pycircuit.library.design_rules import oshpark_4layer
3 | from placer import Placer
4 | from pykicad.pcb import Zone
5 | from mcu import mcu
6 |
7 |
8 | def place(filein, fileout):
9 | placer = Placer()
10 | placer.place(filein, fileout)
11 |
12 |
13 | def post_process(pcb, kpcb):
14 | xmin, ymin, xmax, ymax = pcb.boundary()
15 | coords = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]
16 |
17 | zone = Zone(net_name='GND', layer='F.Cu',
18 | polygon=coords, clearance=0.3)
19 |
20 | kpcb.zones.append(zone)
21 | return kpcb
22 |
23 |
24 | if __name__ == '__main__':
25 | Builder(mcu(), oshpark_4layer,
26 | place=place, post_process=post_process).build()
27 |
--------------------------------------------------------------------------------
/examples/common_emitter/build.py:
--------------------------------------------------------------------------------
1 | import ngspyce as ng
2 | import numpy as np
3 | from matplotlib import pyplot as plt
4 | from pycircuit.formats import *
5 | from pycircuit.build import Builder
6 | from pycircuit.testbench import testbench
7 |
8 | from common_emitter import common_emitter_amplifer
9 |
10 |
11 | def sim():
12 | tb = testbench(common_emitter_amplifer())
13 | circuit = Builder(tb).compile()
14 | circuit.to_spice('common_emitter_amplifier.sp')
15 | ng.source('common_emitter_amplifier.sp')
16 | ng.cmd('tran 50us 10ms')
17 |
18 | print('\n'.join(ng.vector_names()))
19 | time, tp1 = map(ng.vector, ['time', 'V(VOUT)'])
20 | plt.plot(time, tp1, label='VOUT')
21 | plt.legend()
22 | plt.show()
23 |
24 |
25 | if __name__ == '__main__':
26 | sim()
27 |
--------------------------------------------------------------------------------
/placer/grid.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 |
4 | class Grid(object):
5 | def __init__(self, width, height):
6 | self.width = width
7 | self.height = height
8 | self.grid = [[' ' for i in range(width)] for i in range(height)]
9 |
10 | def add_box(self, box):
11 | h = int(box.height)
12 | w = int(box.width)
13 | b = int(box.bottom())
14 | l = int(box.left())
15 | for y in range(h):
16 | for x in range(w):
17 | self.grid[b + y][l + x] = box.symbol
18 |
19 | def __str__(self):
20 | result = '_' * (self.width + 2) + '\n'
21 | for line in reversed(self.grid):
22 | result += '|'
23 | for char in line:
24 | result += char
25 | result += '|\n'
26 | result += '_' * (self.width + 2) + '\n'
27 | return result
28 |
--------------------------------------------------------------------------------
/examples/build.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | examplesdir = os.path.dirname(os.path.realpath(__file__))
5 | examples = ['common_emitter', 'joule_thief', 'mcu', 'sallen_key']
6 |
7 |
8 | def build_dir_path(example):
9 | return os.path.join(examplesdir, example, 'build')
10 |
11 |
12 | def file_path(example, file):
13 | return os.path.join(examplesdir, example, file)
14 |
15 |
16 | def build_file_path(example, file):
17 | return os.path.join(build_dir_path(example), file)
18 |
19 |
20 | def build(path):
21 | os.system('python3 %s' % path)
22 |
23 |
24 | def clean(path):
25 | os.system('rm -rf %s' % path)
26 |
27 |
28 | def open_svg(path):
29 | os.system('chromium %s &' % path)
30 |
31 |
32 | if __name__ == '__main__':
33 | clean('build')
34 | for example in examples:
35 | print('Building', example)
36 | build(file_path(example, example + '.py'))
37 |
--------------------------------------------------------------------------------
/pycircuit/formats/json.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import Circuit, Netlist
2 | from pycircuit.pcb import Pcb
3 | from pycircuit.formats import extends, _to_file, _to_json, _from_file
4 |
5 |
6 | @extends(Circuit)
7 | def to_file(self, path):
8 | _to_file(path, _to_json(self.to_object()))
9 |
10 |
11 | @staticmethod
12 | @extends(Circuit)
13 | def from_file(path):
14 | return Circuit.from_object(_from_file(path))
15 |
16 |
17 | @extends(Netlist)
18 | def to_file(self, path):
19 | _to_file(path, _to_json(self.to_object()))
20 |
21 |
22 | @staticmethod
23 | @extends(Netlist)
24 | def from_file(path):
25 | return Netlist.from_object(_from_file(path))
26 |
27 |
28 | @extends(Pcb)
29 | def to_file(self, path):
30 | _to_file(path, _to_json(self.to_object()))
31 |
32 |
33 | @staticmethod
34 | @extends(Pcb)
35 | def from_file(path):
36 | return Pcb.from_object(_from_file(path))
37 |
--------------------------------------------------------------------------------
/pycircuit/library/devices.py:
--------------------------------------------------------------------------------
1 | import itertools
2 | from pycircuit.device import *
3 |
4 | for device in ['R', 'C']:
5 | for package in ['0805']:
6 | Device('%s%s' % (device, package), device, package,
7 | Map('1', 'A'),
8 | Map('2', 'B'))
9 |
10 | for package in ['0805']:
11 | Device('D%s' % package, 'D', package,
12 | Map('1', '+'),
13 | Map('2', '-'))
14 |
15 | for a, b, c in itertools.permutations('BCE', 3):
16 | Device('SOT23' + a + b + c, 'Q', 'SOT23',
17 | Map('1', a),
18 | Map('2', b),
19 | Map('3', c),
20 | Map(None, 'SUBSTRATE'))
21 |
22 | for a, b, c in itertools.permutations('GSD'):
23 | Device('SOT23' + a + b + c, 'M', 'SOT23',
24 | Map('1', a),
25 | Map('2', b),
26 | Map('3', c),
27 | Map(None, 'SUBSTRATE'))
28 |
29 | Device('TP', 'TP', 'Pins_1x1', Map('A1', 'TP'))
30 |
--------------------------------------------------------------------------------
/pycircuit/testbench.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import *
2 |
3 |
4 | def testbench(circuit):
5 | tb = Circuit(circuit.name + '_testbench')
6 | Circuit.active_circuit = tb
7 |
8 | dut = SubInst(circuit, _parent=tb)
9 | gnd = Net('gnd', _parent=tb)
10 | nets = []
11 |
12 | for port in dut.circuit.external_ports():
13 | if port.type == PortType.GND:
14 | dut[port.name] = gnd
15 | else:
16 | nets.append(Net(port.name, _parent=tb))
17 | dut[port.name] = nets[-1]
18 |
19 | if port.type == PortType.POWER:
20 | Inst('V', 'dc %d' % parse_power_net(port.name),
21 | _parent=tb)['+', '-'] = nets[-1], gnd
22 | elif port.type == PortType.IN:
23 | Inst('V', 'sin(0, 0.1, 1K) ac 1', _parent=tb)[
24 | '+', '-'] = nets[-1], gnd
25 | elif port.type == PortType.OUT:
26 | Inst('TP', _parent=tb)['TP'] = nets[-1]
27 |
28 | return tb
29 |
--------------------------------------------------------------------------------
/examples/common_emitter/common_emitter.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import *
2 | from pycircuit.library import *
3 |
4 |
5 | @circuit('Common Emitter', 'gnd', '12V', 'vin', 'vout')
6 | def common_emitter_amplifer(self, gnd, vcc, vin, vout):
7 | nb, ne = nets('nb ne')
8 | Inst('Q', 'npn sot23')['B', 'C', 'E'] = nb, vout, ne
9 |
10 | # Current limiting resistor
11 | Inst('R', '1.2k')['~', '~'] = vcc, vout
12 |
13 | # Thermal stabilization (leads to a gain reduction)
14 | Inst('R', '220')['~', '~'] = ne, gnd
15 | # Shorts Re for AC signal (increases gain)
16 | Inst('C', '10uF')['~', '~'] = ne, gnd
17 |
18 | # Biasing resistors
19 | Inst('R', '20k')['~', '~'] = vcc, nb
20 | Inst('R', '3.6k')['~', '~'] = nb, gnd
21 | # Decoupling capacitor
22 | Inst('C', '10uF')['~', '~'] = vin, nb
23 |
24 |
25 | if __name__ == '__main__':
26 | from pycircuit.formats import *
27 | from pycircuit.build import Builder
28 |
29 | Builder(common_emitter_amplifer()).compile()
30 |
--------------------------------------------------------------------------------
/pycircuit/library/old/connectors.py:
--------------------------------------------------------------------------------
1 | from pycircuit.device import *
2 |
3 |
4 | # Connectors
5 | # Power Connectors
6 | # DC Connector
7 | Device('DCCONN', pins=[
8 | Pwr('GND'),
9 | PwrOut('V')
10 | ])
11 |
12 |
13 | # Data Connectors
14 | # USB Connector
15 | Device('USBCONN', pins=[
16 | Pwr('GND'),
17 | PwrOut('VBUS'),
18 | Pin('D+', Fun('USB2', 'D+')),
19 | Pin('D-', Fun('USB2', 'D-'))
20 | ])
21 |
22 | # USB-C Connector
23 | Device('USBCCONN', pins=[
24 | Pwr('GND'),
25 | PwrOut('VBUS'),
26 | Pin('TX1+', Fun('USB3', 'TX1+')),
27 | Pin('TX1-', Fun('USB3', 'TX1-')),
28 | Pin('RX1+', Fun('USB3', 'RX1+')),
29 | Pin('RX1-', Fun('USB3', 'RX1-')),
30 | Pin('TX2+', Fun('USB3', 'TX2+')),
31 | Pin('TX2-', Fun('USB3', 'TX2-')),
32 | Pin('RX2+', Fun('USB3', 'RX2+')),
33 | Pin('RX2-', Fun('USB3', 'RX2-')),
34 | Pin('D+', Fun('USB2', 'D+')),
35 | Pin('D-', Fun('USB2', 'D-')),
36 | Pin('CC1'),
37 | Pin('CC2'),
38 | Pin('SBU1'),
39 | Pin('SBU2')
40 | ])
41 |
--------------------------------------------------------------------------------
/viewer/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 | var path = require('path');
4 |
5 | function random_port() {
6 | return Math.floor(Math.random() * 100) + 3000;
7 | }
8 |
9 | var index = path.join(__dirname, 'index.html');
10 | var port = process.argv[2] || random_port();
11 | var net = path.join(process.cwd(), process.argv[3] || path.join('build', 'net.dot.svg'));
12 | var pcb = path.join(process.cwd(), process.argv[4] || path.join('build', 'pcb.svg'));
13 |
14 | app.use('/css', express.static(path.join(__dirname, 'css')));
15 | app.use('/js', express.static(path.join(__dirname, 'js')));
16 |
17 | app.get('/', function(req, res) {
18 | res.sendFile(index);
19 | });
20 |
21 | app.get('/net', function(req, res) {
22 | res.sendFile(net);
23 | });
24 |
25 | app.get('/pcb', function(req, res) {
26 | res.sendFile(pcb);
27 | });
28 |
29 | app.listen(port, function () {
30 | console.log('Viewer listening on port', port);
31 | console.log('net file:', net);
32 | console.log('pcb file:', pcb);
33 | });
34 |
--------------------------------------------------------------------------------
/pycircuit/formats/__init__.py:
--------------------------------------------------------------------------------
1 | import json as j
2 | import os
3 | import sys
4 |
5 |
6 | def extends(klass):
7 | def decorator(func):
8 | setattr(klass, func.__name__, func)
9 | return func
10 | return decorator
11 |
12 |
13 | def _to_json(obj):
14 | return j.dumps(obj, sort_keys=True,
15 | indent=2, separators=(',', ': '))
16 |
17 |
18 | def _to_file(path, string):
19 | with open(path, 'w+') as f:
20 | f.write(string)
21 |
22 |
23 | def _from_file(path):
24 | with open(path) as f:
25 | return j.loads(f.read())
26 |
27 |
28 | def export(string, filename, extension):
29 | if filename is None:
30 | filename = os.path.basename(sys.argv[0])
31 | filename = os.path.splitext(filename)[0]
32 |
33 | if not filename.endswith(extension):
34 | filename += extension
35 |
36 | with open(filename, 'w+') as f:
37 | f.write(string)
38 |
39 |
40 | def polygon_to_lines(coords):
41 | coords.append(coords[0])
42 | for i in range(len(coords) - 1):
43 | yield coords[i], coords[i + 1]
44 |
45 |
46 | __all__ = ['json', 'svg', 'yosys', 'spice', 'kicad']
47 |
--------------------------------------------------------------------------------
/viewer/css/pcb.svg.css:
--------------------------------------------------------------------------------
1 | text {
2 | font-family: 'Verdana';
3 | }
4 |
5 | .outline {
6 | fill-opacity: 0;
7 | stroke: black;
8 | stroke-width: 0.05;
9 | }
10 |
11 | .crtyd {
12 | stroke: black;
13 | stroke-width: 0.05;
14 | fill-opacity: 0;
15 | }
16 |
17 | .pad {
18 | fill-opacity: 0.5;
19 | }
20 |
21 | .ref {
22 | font-size: 0.8px;
23 | }
24 |
25 | .pad-label {
26 | font-size: 0.4px;
27 | fill: white;
28 | }
29 |
30 | .via {
31 | fill-opacity: 1
32 | }
33 |
34 | .via .dia {
35 | fill: black;
36 | }
37 |
38 | .via .drill {
39 | fill: white;
40 | }
41 |
42 | .seg {
43 | stroke-linecap: round;
44 | stroke-opacity: 0.5;
45 | }
46 |
47 | /* Color segments and pads */
48 | .layer.top .pad {
49 | fill: red;
50 | }
51 |
52 | .layer.top .seg {
53 | stroke: red;
54 | }
55 |
56 | .layer.inner1 .pad {
57 | fill: blue;
58 | }
59 |
60 | .layer.inner1 .seg {
61 | stroke: blue;
62 | }
63 |
64 | .layer.inner2 .pad {
65 | fill: purple;
66 | }
67 |
68 | .layer.inner2 .seg {
69 | stroke: purple;
70 | }
71 |
72 | .layer.bottom .pad {
73 | fill: green;
74 | }
75 |
76 | .layer.bottom .seg {
77 | stroke: green;
78 | }
79 |
--------------------------------------------------------------------------------
/examples/joule_thief/joule_thief.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import *
2 | from pycircuit.library import *
3 |
4 |
5 | Device('V0805', 'V', '0805',
6 | Map('1', '+'),
7 | Map('2', '-'))
8 |
9 | Package('TDK ACT45B', RectCrtyd(5.9, 3.4), DualPads(4, 2.5, radius=2.275),
10 | package_size=(5.9, 3.4), pad_size=(0.9, 1.35))
11 |
12 | Device('TDK ACT45B', 'Transformer_1P_1S', 'TDK ACT45B',
13 | Map('1', 'L1.1'), Map('2', 'L2.1'), Map('3', 'L2.2'), Map('4', 'L1.2'))
14 |
15 |
16 | @circuit('Joule Thief', 'gnd', 'vcc', None, 'vout')
17 | def joule_thief(self, gnd, vin, vout):
18 | nb, nr = nets('nb nr')
19 |
20 | with Inst('Transformer_1P_1S', '1mH') as tr1:
21 | tr1['L1.1', 'L1.2'] = nr, nb
22 | tr1['L2.2', 'L2.1'] = vin, vout
23 |
24 | Inst('R', '1k 0805')['~', '~'] = vin, nr
25 | Inst('Q', 'npn sot23')['B', 'C', 'E'] = nb, vout, gnd
26 | Inst('D', 'led white 0805')['+', '-'] = vout, gnd
27 |
28 |
29 | @circuit('Top')
30 | def top(self):
31 | vcc, gnd, vout = nets('vcc gnd vout')
32 |
33 | SubInst(joule_thief())['vcc', 'vout', 'gnd'] = vcc, vout, gnd
34 | Inst('V', '1.5V')['+', '-'] = vcc, gnd
35 | Inst('TP')['TP'] = vout
36 |
37 |
38 | if __name__ == '__main__':
39 | from pycircuit.formats import *
40 | from pycircuit.build import Builder
41 |
42 | Builder(joule_thief()).compile()
43 | Builder(top()).compile()
44 |
--------------------------------------------------------------------------------
/examples/voltage_divider/voltage_divider.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import *
2 | from pycircuit.device import *
3 | from pycircuit.library import *
4 | from pycircuit.build import Builder
5 | from placer.place import Placer
6 | from router.router import Router
7 |
8 |
9 | Component('Connector', 'A connector', Pin('vin'), Pin('vout'), Pin('gnd'))
10 | Device('Connector', 'Connector', 'Pins_3x1',
11 | Map('A1', 'vin'),
12 | Map('A2', 'vout'),
13 | Map('A3', 'gnd')
14 | )
15 |
16 |
17 | @circuit('Voltage Divider', 'gnd', None, 'vin', 'vout')
18 | def voltage_divider(self, gnd, vin, vout):
19 | Inst('R', '10k 0805')['~', '~'] = vin, vout
20 | Inst('R', '10k 0805')['~', '~'] = vout, gnd
21 |
22 |
23 | @circuit('Top')
24 | def top(self):
25 | vin, vout, gnd = nets('vin vout gnd')
26 | SubInst(voltage_divider())['vin', 'vout', 'gnd'] = vin, vout, gnd
27 | Inst('Connector', 'Pins_3x1')['vin', 'vout', 'gnd'] = vin, vout, gnd
28 |
29 |
30 | def place(fin, fout):
31 | p = Placer()
32 | p.place(fin, fout)
33 |
34 |
35 | def route(fin, fout):
36 | r = Router(grid_size=.5, maxflow_enforcement_level=3)
37 | r.route(fin, fout)
38 |
39 |
40 | if __name__ == '__main__':
41 | outline = rectangle_with_mounting_holes(
42 | 20, 10, inset=1, hole_shift=2, hole_dia=1)
43 |
44 | Builder(top(), outline=outline,
45 | pcb_attributes=oshpark_2layer(),
46 | place=place, route=route).build()
47 |
--------------------------------------------------------------------------------
/tests/test_parser.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from pycircuit.circuit import *
3 |
4 |
5 | class ParserTests(unittest.TestCase):
6 | def setUp(self):
7 | self.circuit = Circuit('ParserTests')
8 | Circuit.active_circuit = self.circuit
9 |
10 | def test_parse_busses(self):
11 | ns = nets('')
12 | assert ns is None
13 |
14 | n0 = nets('n0')
15 | assert n0.name == 'n0'
16 |
17 | n1, n2, n3 = nets('n1 n2 n3')
18 | assert n1.name == 'n1'
19 | assert n2.name == 'n2'
20 | assert n3.name == 'n3'
21 | assert not n1.guid == n2.guid
22 | assert not n2.guid == n3.guid
23 | assert not n1.guid == n3.guid
24 |
25 | uart = nets('uart[2]')
26 | assert len(uart) == 2
27 | assert uart[0].name == 'uart[0]'
28 | assert uart[1].name == 'uart[1]'
29 | assert uart[0].guid == uart[1].guid
30 |
31 | gpio, uart = nets('gpio[3] usart[2]')
32 | assert len(gpio) == 3
33 | assert len(uart) == 2
34 | assert not gpio[0].guid == uart[0].guid
35 |
36 | def test_parse_power_net(self):
37 | assert parse_power_net('GND') == 0
38 | assert parse_power_net('gnd') == 0
39 |
40 | assert parse_power_net('0') == 0
41 | assert parse_power_net('0V') == 0
42 | assert parse_power_net('V0') == 0
43 |
44 | assert parse_power_net('3.3') == 3.3
45 | assert parse_power_net('3.3V') == 3.3
46 | assert parse_power_net('V3.3') == 3.3
47 | assert parse_power_net('3V3') == 3.3
48 |
--------------------------------------------------------------------------------
/examples/joule_thief/build.py:
--------------------------------------------------------------------------------
1 | import joule_thief
2 | from pycircuit.build import Builder
3 | from pycircuit.library.design_rules import oshpark_4layer
4 | from pycircuit.library.outlines import sick_of_beige
5 | from placer import Placer
6 | from router import Router
7 | from pykicad.pcb import Zone
8 |
9 |
10 | '''
11 | def sim():
12 | import ngspyce as ng
13 | import numpy as np
14 | from matplotlib import pyplot as plt
15 | netlist = Builder(top()).get_netlist()
16 | netlist.to_spice('joule_thief.sp')
17 | ng.source('joule_thief.sp')
18 | ng.cmd('tran 1us 500us')
19 |
20 | print('\n'.join(ng.vector_names()))
21 | time, tp1 = map(ng.vector, ['time', 'V(VOUT)'])
22 | plt.plot(time, tp1, label='VOUT')
23 | plt.legend()
24 | plt.show()
25 | '''
26 |
27 |
28 | def place(filein, fileout):
29 | placer = Placer()
30 | placer.place(filein, fileout)
31 |
32 |
33 | def route(filein, fileout):
34 | router = Router(maxflow_enforcement_level=2)
35 | router.route(filein, fileout)
36 |
37 |
38 | def post_process(pcb, kpcb):
39 | coords = list(pcb.outline.polygon.interiors[0].coords)
40 |
41 | zone = Zone(net_name='gnd', layer='F.Cu',
42 | polygon=coords, clearance=0.3)
43 |
44 | kpcb.zones.append(zone)
45 | return kpcb
46 |
47 |
48 | if __name__ == '__main__':
49 | pcb_attributes = oshpark_4layer()
50 | # Only allow placement on top layer
51 | pcb_attributes.layers.placement_layers.pop()
52 |
53 | Builder(joule_thief.top(), outline=sick_of_beige('DP5031'),
54 | pcb_attributes=pcb_attributes, place=place,
55 | # route=route,
56 | post_process=post_process).build()
57 |
--------------------------------------------------------------------------------
/placer/bin.py:
--------------------------------------------------------------------------------
1 | from z3 import *
2 |
3 |
4 | class Bin(object):
5 | counter = 0
6 |
7 | def __init__(self, width, height):
8 | self.id = Bin.counter
9 | Bin.counter += 1
10 |
11 | self.width = width
12 | self.height = height
13 |
14 | def dim(self):
15 | return (self.width, self.height)
16 |
17 | def center(self):
18 | return (self.width / 2, self.height / 2)
19 |
20 | def area(self):
21 | return self.width * self.height
22 |
23 | def __str__(self):
24 | return 'width=%s height=%s area=%s' % (self.width, self.height,
25 | self.area())
26 |
27 |
28 | class Z3Bin(Bin):
29 | def __init__(self):
30 | super().__init__(0, 0)
31 | self.var_width = Int('bin%s_w' % str(self.id))
32 | self.var_height = Int('bin%s_h' % str(self.id))
33 | self.var_area = Int('bin%s_a' % str(self.id))
34 | self.var_center_x = Int('bin%s_cx' % str(self.id))
35 | self.var_center_y = Int('bin%s_cy' % str(self.id))
36 |
37 | def var_center(self):
38 | return (self.var_center_x, self.var_center_y)
39 |
40 | def range_constraint(self):
41 | return And(self.var_width >= 0,
42 | self.var_height >= 0,
43 | self.var_width == 2 * self.var_center_x,
44 | self.var_height == 2 * self.var_center_y)
45 |
46 | def area_constraint(self, area):
47 | return And(self.var_area == self.var_width * self.var_height,
48 | self.var_area <= area)
49 |
50 | def eval(self, model):
51 | self.width = model[self.var_width].as_long()
52 | self.height = model[self.var_height].as_long()
53 |
--------------------------------------------------------------------------------
/pycircuit/library/packages.py:
--------------------------------------------------------------------------------
1 | from pycircuit.package import *
2 |
3 |
4 | # Basic
5 | for width in range(1, 3):
6 | for length in range(1, 21):
7 | t_length, t_width = 2.41 + (length - 1) * 2.54, 2.41
8 | Package('Pins_%dx%d' % (length, width), RectCrtyd(t_length, t_width),
9 | GridPads(width, length, pitch=2.54),
10 | package_size=(t_length, t_width),
11 | pad_size=(2.41, 2.41), pad_drill=.51, pad_shape='circle')
12 |
13 | Package('0805', IPCGrid(4, 8), TwoPads(1.9),
14 | package_size=(1.4, 2.15), pad_size=(1.5, 1.3))
15 |
16 | Package('SOT23', IPCGrid(8, 8), Sot23Pads(2.2, 0.95),
17 | package_size=(3, 1.4), pad_size=(1, 1.4))
18 |
19 |
20 | # DIP
21 | Package('DIP8', RectCrtyd(9.7, 12.55), DualPads(8, pitch=2.54, radius=3.81),
22 | package_size=(7.35, 12.21), pad_size=(1.6, 1.6),
23 | pad_shape='circle', pad_drill=0.8)
24 |
25 |
26 | # QFN
27 | Package('QFN16', RectCrtyd(5.3, 5.3), QuadPads(16, pitch=0.65, radius=2, thermal_pad=2.5),
28 | package_size=(5, 5), pad_size=(0.35, 0.8))
29 |
30 | Package('QFN48', RectCrtyd(7.5, 7.5), QuadPads(48, pitch=0.5, radius=3.5, thermal_pad=5.1),
31 | package_size=(7, 7), pad_size=(0.28, 0.724)) # FIXME: Radius is only approximate!!
32 |
33 | Package('QFN64', RectCrtyd(9, 9), QuadPads(64, 0.5, 4.5, thermal_pad=6),
34 | package_size=(9, 9), pad_size=(0.28, 0.724)) # FIXME: Radius is only approximate!!
35 |
36 |
37 | # TQFP
38 | # FIXME: Radius is only approximate!!
39 | Package('TQFP144', RectCrtyd(20, 20), QuadPads(144, pitch=0.5, radius=10))
40 |
41 |
42 | # BGA
43 | Package('PBGA16_8x8', RectCrtyd(8, 8), GridPads(4, 4, pitch=1.5),
44 | package_size=(8, 8), pad_size=(.6, .6), pad_shape='circle')
45 |
--------------------------------------------------------------------------------
/pycircuit/library/outlines.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from shapely.geometry.polygon import Polygon
3 | from pycircuit.outline import Hole, Outline
4 |
5 |
6 | def rectangle_with_mounting_holes(width, height, inset, hole_shift, hole_drill):
7 | shift_matrix = np.array([[1, 1], [-1, 1], [-1, -1], [1, -1]])
8 |
9 | exterior_coords = np.array(
10 | [[0, 0], [width, 0], [width, height], [0, height]])
11 | interior_coords = exterior_coords + shift_matrix * inset
12 | interior = Polygon(interior_coords)
13 |
14 | hole_coords = exterior_coords + shift_matrix * hole_shift
15 | holes = []
16 | for pos in hole_coords:
17 | holes.append(Hole(pos, hole_drill))
18 | hole_clearance = hole_drill / 2 + inset
19 | interior = interior.difference(
20 | Polygon(shift_matrix * hole_clearance + pos))
21 |
22 | outline = Polygon(exterior_coords, [interior.exterior.coords])
23 | return Outline(outline, *holes)
24 |
25 |
26 | def sick_of_beige(name):
27 | '''http://dangerousprototypes.com/docs/Sick_of_Beige_standard_PCB_sizes_v1.0'''
28 |
29 | lookup = {
30 | 'DP5031': (50, 31),
31 | 'DP6037': (60, 37),
32 | 'DP7043': (70, 43),
33 | 'DP8049': (80, 49),
34 | 'DP9056': (90, 56),
35 | 'DP10062': (100, 62),
36 | 'DP3030': (30, 30),
37 | 'DP4040': (40, 40),
38 | 'DP5050': (50, 50),
39 | 'DP6060': (60, 60),
40 | 'DP7070': (70, 70),
41 | 'DP8080': (80, 80),
42 | }
43 |
44 | width, height = lookup[name]
45 | inset = 1.7
46 | hole_shift = 4
47 | hole_dia = 3.2
48 |
49 | return rectangle_with_mounting_holes(width=width, height=height, inset=inset,
50 | hole_shift=hole_shift, hole_dia=hole_dia)
51 |
--------------------------------------------------------------------------------
/viewer/css/graphviz.svg.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 Mountainstorm
3 | *
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy
5 | * of this software and associated documentation files (the "Software"), to deal
6 | * in the Software without restriction, including without limitation the rights
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | * copies of the Software, and to permit persons to whom the Software is
9 | * furnished to do so, subject to the following conditions:
10 | *
11 | * The above copyright notice and this permission notice shall be included in all
12 | * copies or substantial portions of the Software.
13 | *
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | * SOFTWARE.
21 | */
22 |
23 | #graph {
24 | background-color: #2e3e56;
25 | }
26 |
27 | /* this element needs tooltip positioning to work */
28 | .graphviz-svg {
29 | position: relative;
30 | }
31 |
32 | /* stop tooltips wrapping */
33 | .graphviz-svg .tooltip-inner {
34 | white-space: nowrap;
35 | }
36 |
37 | /* stop people selecting text on nodes */
38 | .graphviz-svg text {
39 | -webkit-touch-callout: none;
40 | -webkit-user-select: none;
41 | -khtml-user-select: none;
42 | -moz-user-select: none;
43 | -ms-user-select: none;
44 | user-select: none;
45 | cursor: default;
46 | }
47 |
--------------------------------------------------------------------------------
/examples/sallen_key/sallen_key.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import *
2 | from pycircuit.library import *
3 |
4 |
5 | def sallen_key(z1, z2, z3, z4, vcc, vee):
6 |
7 | @circuit('Sallen Key', 'gnd', '%s %s' % (vcc, vee), 'vin', 'vout')
8 | def sallen_key_topology(self, gnd, vcc, vee, vin, vout):
9 | n1, n2 = nets('n1 n2')
10 |
11 | Inst(z1)['~', '~'] = vin, n1
12 | Inst(z2)['~', '~'] = n1, n2
13 | Inst(z3)['~', '~'] = n1, vout
14 | Inst(z4)['~', '~'] = n2, gnd
15 |
16 | with Inst('OP', 'OP') as op:
17 | op['+', '-', 'OUT'] = n2, vout, vout
18 | op['VCC', 'VEE'] = vcc, vee
19 |
20 | return sallen_key_topology()
21 |
22 |
23 | def lp_sallen_key(vcc='+12V', vee='-12V'):
24 | return sallen_key('R', 'R', 'C', 'C', vcc, vee)
25 |
26 |
27 | Device('V0805', 'V', '0805',
28 | Map('1', '+'),
29 | Map('2', '-'))
30 |
31 | Device('OPDIP', 'OP', 'DIP8',
32 | Map('1', 'VCC'),
33 | Map('2', 'VEE'),
34 | Map('3', '+'),
35 | Map('4', 'OUT'),
36 | Map('5', '-'),
37 | Map('6', None),
38 | Map('7', None),
39 | Map('8', None))
40 |
41 |
42 | @circuit('Sallen Key Top', 'gnd')
43 | def top(self, gnd):
44 | vcc, vee, vin, vout = nets('+12V -12V vin vout')
45 | # VCC
46 | Inst('V')['+', '-'] = vcc, gnd
47 | # VEE
48 | Inst('V')['+', '-'] = gnd, vee
49 | # Vin
50 | Inst('V')['+', '-'] = vin, gnd
51 |
52 | Inst('TP')['TP'] = vout
53 |
54 | with SubInst(lp_sallen_key()) as sk:
55 | sk['+12V', '-12V', 'gnd', 'vin', 'vout'] = vcc, vee, gnd, vin, vout
56 |
57 |
58 | if __name__ == '__main__':
59 | from pycircuit.formats import *
60 | from pycircuit.build import Builder
61 |
62 | Builder(lp_sallen_key()).compile()
63 | Builder(top()).compile()
64 |
--------------------------------------------------------------------------------
/pycircuit/library/old/busses.py:
--------------------------------------------------------------------------------
1 | from pycircuit.device import *
2 |
3 |
4 | Bus('OP',
5 | Fun('+', Fun.INPUT),
6 | Fun('-', Fun.INPUT),
7 | Fun('OUT', Fun.OUTPUT))
8 |
9 | Bus('PWM',
10 | Fun('0', Fun.OUTPUT),
11 | Fun('1', Fun.OUTPUT),
12 | Fun('2', Fun.OUTPUT),
13 | Fun('3', Fun.OUTPUT))
14 |
15 | Bus('UART',
16 | Fun('TX', Fun.OUTPUT),
17 | Fun('RX', Fun.INPUT))
18 |
19 | Bus('JTAG:M',
20 | Fun('TCK', Fun.OUTPUT),
21 | Fun('TDO', Fun.INPUT),
22 | Fun('TMS', Fun.OUTPUT),
23 | Fun('TDI', Fun.OUTPUT))
24 |
25 | Bus('JTAG:S',
26 | Fun('TCK', Fun.INPUT),
27 | Fun('TDO', Fun.OUTPUT),
28 | Fun('TMS', Fun.INPUT),
29 | Fun('TDI', Fun.INPUT))
30 |
31 | Bus('I2C:M',
32 | Fun('SDA', Fun.BIDIR),
33 | Fun('SCL', Fun.OUTPUT))
34 |
35 | Bus('I2C:S',
36 | Fun('SDA', Fun.BIDIR),
37 | Fun('SCL', Fun.INPUT))
38 |
39 | Bus('SPI:M',
40 | Fun('SCLK', Fun.OUTPUT),
41 | Fun('MOSI', Fun.OUTPUT),
42 | Fun('MISO', Fun.INPUT),
43 | Fun('SS0', Fun.OUTPUT),
44 | Fun('SS1', Fun.OUTPUT),
45 | Fun('SS2', Fun.OUTPUT),
46 | Fun('SS3', Fun.OUTPUT))
47 |
48 | Bus('SPI:S',
49 | Fun('SCLK', Fun.INPUT),
50 | Fun('MOSI', Fun.INPUT),
51 | Fun('MISO', Fun.OUTPUT),
52 | Fun('SS', Fun.INPUT))
53 |
54 | Bus('QSPI:M',
55 | Fun('DQ3', Fun.BIDIR),
56 | Fun('DQ2', Fun.BIDIR),
57 | Fun('DQ1', Fun.BIDIR),
58 | Fun('DQ0', Fun.BIDIR),
59 | Fun('SCLK', Fun.OUTPUT),
60 | Fun('SS0', Fun.OUTPUT),
61 | Fun('SS1', Fun.OUTPUT),
62 | Fun('SS2', Fun.OUTPUT),
63 | Fun('SS3', Fun.OUTPUT))
64 |
65 | Bus('QSPI:S',
66 | Fun('DQ3', Fun.BIDIR),
67 | Fun('DQ2', Fun.BIDIR),
68 | Fun('DQ1', Fun.BIDIR),
69 | Fun('DQ0', Fun.BIDIR),
70 | Fun('SCLK', Fun.INPUT),
71 | Fun('SS', Fun.INPUT))
72 |
73 | Bus('USB2',
74 | Fun('D+', Fun.BIDIR),
75 | Fun('D-', Fun.BIDIR))
76 |
77 | Bus('USB3',
78 | Fun('TX1+', Fun.OUTPUT),
79 | Fun('TX1-', Fun.OUTPUT),
80 | Fun('RX1+', Fun.INPUT),
81 | Fun('RX1-', Fun.INPUT),
82 | Fun('TX2+', Fun.OUTPUT),
83 | Fun('TX2-', Fun.OUTPUT),
84 | Fun('RX2+', Fun.INPUT),
85 | Fun('RX2-', Fun.INPUT))
86 |
--------------------------------------------------------------------------------
/pycircuit/library/old/circuits.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import *
2 |
3 |
4 | @circuit('Pierce oscillator')
5 | def pierce_oscillator(freq, cap, res=False):
6 | Node('Y', 'XTAL', freq)
7 | Node('C1', 'C', cap)
8 | Node('C2', 'C', cap)
9 |
10 | xi = Net('XTAL_XI') + Ref('Y')['~'] + Ref('C1')['~']
11 | xo = Net('XTAL_XO') + Ref('Y')['~'] + Ref('C2')['~']
12 | Net('GND') + Ref('C1')['~'] + Ref('C2')['~']
13 |
14 | if res:
15 | Node('R', 'R', res)
16 | xi + Ref('R')['~']
17 | xo + Ref('R')['~']
18 |
19 |
20 | @circuit('Level shifter')
21 | def level_shifter(r_low=False, r_high='10K'):
22 | Node('R_HIGH', 'R', r_high)
23 | Node('Q', 'M')
24 |
25 | vdd_low = Net('VDD_LOW') + Ref('Q')['G']
26 | low = Net('LOW') + Ref('Q')['S']
27 |
28 | if r_low:
29 | Node('R_LOW', 'R', r_low)
30 | vdd_low + Ref('R_LOW')['~']
31 | low + Ref('R_LOW')['~']
32 |
33 | Net('VDD_HIGH') + Ref('R_HIGH')['~']
34 | Net('HIGH') + Ref('Q')['D'] + Ref('R_HIGH')['~']
35 |
36 |
37 | @circuit('Button')
38 | def button(pullup='100K', esd_protection=True):
39 | Node('BTN', 'BTN')
40 | Node('PULLUP', 'R', pullup)
41 | Net('VDD') + Ref('PULLUP')['~']
42 | inp = Net('IN') + Ref('PULLUP')['~'] + Ref('BTN')['~']
43 | gnd = Net('GND') + Ref('BTN')['~']
44 |
45 | if esd_protection:
46 | Node('TVS', 'DD')
47 | inp + Ref('TVS')['~']
48 | gnd + Ref('TVS')['~']
49 |
50 |
51 | @circuit('Decoupling')
52 | def decoupling_capacitors(*args):
53 | vdd, vss = Net('VDD'), Net('VSS')
54 | for i, c in enumerate(args):
55 | name = 'C' + str(i + 1)
56 | Node(name, 'C', c)
57 | vdd + Ref(name)['~']
58 | vss + Ref(name)['~']
59 |
60 | #@circuit('Debouncer')
61 | # def debouncer(r1='470', r2='10K', c='10nF', diode=True):
62 | # Node('R1', 'R', r1)
63 | # Node('R2', 'R', r2)
64 | # Node('C', 'C', c)
65 |
66 |
67 | @circuit('RGB')
68 | def rgb(r='330'):
69 | Node('RGB', 'RGB_A')
70 |
71 | def led(color):
72 | Node('R' + color[0], 'R', r)
73 | Net(color) + Ref('R' + color[0])['~']
74 | Net() + Ref('R' + color[0])['~'] + Ref('RGB')[color[0]]
75 |
76 | led('RED')
77 | led('GREEN')
78 | led('BLUE')
79 | Net('VDD') + Ref('RGB')['A']
80 |
--------------------------------------------------------------------------------
/viewer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
18 |
19 | Click node to highlight; Shift-scroll to zoom; Esc to unhighlight
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/examples/mcu/mcu.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import *
2 | from pycircuit.library import *
3 |
4 |
5 | Component('MCU', 'Microcontroller',
6 | Gnd('GND'),
7 | Pwr('5V'),
8 | In('XTAL_XI'),
9 | Out('XTAL_XO'),
10 | In('JTAG_TCK'),
11 | In('JTAG_TMS'),
12 | In('JTAG_TDI'),
13 | Out('JTAG_TDO'),
14 | Io('GPIO_1', BusFun('UART0', 'UART_TX')),
15 | Io('GPIO_2', BusFun('UART0', 'UART_RX')),
16 | Io('GPIO_3'),
17 | Io('GPIO_4'),
18 | Io('GPIO_5', BusFun('UART1', 'UART_TX')),
19 | Io('GPIO_6', BusFun('UART1', 'UART_RX')),
20 | Io('GPIO_7'))
21 |
22 | Device('MCUQFN16', 'MCU', 'QFN16',
23 | Map('1', 'GPIO_1'),
24 | Map('2', 'GPIO_2'),
25 | Map('3', 'GPIO_3'),
26 | Map('4', 'GPIO_4'),
27 | Map('5', '5V'),
28 | Map('6', 'GND'),
29 | Map('7', 'GPIO_5'),
30 | Map('8', 'GPIO_6'),
31 | Map('9', 'XTAL_XI'),
32 | Map('10', 'XTAL_XO'),
33 | Map('11', 'GPIO_7'),
34 | Map('12', 'JTAG_TCK'),
35 | Map('13', 'JTAG_TDO'),
36 | Map('14', 'JTAG_TMS'),
37 | Map('15', 'JTAG_TDI'),
38 | Map('16', None),
39 | Map('17', 'GND'))
40 |
41 | Device('V0805', 'V', '0805',
42 | Map('1', '+'),
43 | Map('2', '-'))
44 |
45 | Device('OSC0805', 'XTAL', '0805',
46 | Map('1', 'A'),
47 | Map('2', 'B'))
48 |
49 |
50 | @circuit('LED', 'gnd', None, 'vin')
51 | def led(self, gnd, vin):
52 | n1 = nets('n1')
53 |
54 | Inst('R')['~', '~'] = vin, n1
55 | Inst('D', 'led')['+', '-'] = n1, gnd
56 |
57 |
58 | @circuit('RGB', 'gnd', None, 'red green blue')
59 | def rgb(self, gnd, *inputs):
60 | for port in inputs:
61 | SubInst(led())['vin', 'gnd'] = port, gnd
62 |
63 |
64 | @circuit('MCU')
65 | def mcu(self):
66 | vcc, gnd, clk, gpio, uart = nets('5V GND clk[2] gpio[3] uart[2]')
67 |
68 | Inst('V')['+', '-'] = vcc, gnd
69 | Inst('XTAL')['~', '~'] = clk
70 | SubInst(rgb())['red', 'green', 'blue', 'gnd'] = *gpio, gnd
71 |
72 | with Inst('MCU') as mcu:
73 | mcu['5V', 'GND'] = vcc, gnd
74 | mcu['XTAL_XI', 'XTAL_XO'] = clk
75 | mcu['GPIO', 'GPIO', 'GPIO'] = gpio
76 | mcu['UART_TX', 'UART_RX'] = uart
77 | mcu['UART_RX', 'UART_TX'] = uart
78 |
79 |
80 | if __name__ == '__main__':
81 | from pycircuit.formats import *
82 | from pycircuit.build import Builder
83 |
84 | Builder(led()).compile()
85 | Builder(rgb()).compile()
86 | Builder(mcu()).compile()
87 |
--------------------------------------------------------------------------------
/pycircuit/library/old/ti/tps6229x.py:
--------------------------------------------------------------------------------
1 | from pycircuit.device import *
2 | from pycircuit.footprint import *
3 | from pycircuit.circuit import *
4 |
5 |
6 | Device('TPS6229x', descr='''1A Step-Down Converter''', pins=[
7 | PwrIn('VIN', descr='''VIN power supply pin.'''),
8 |
9 | In('EN', descr='''This is the enable pin of the device. Pulling this pin to
10 | low forces the device into shutdown mode. Pulling this pin to high enables
11 | the device. This pin must be terminated.'''),
12 |
13 | Pwr('GND', descr='''GND supply pin.'''),
14 |
15 | In('MODE', descr='''High forces the device to operate in fixed-frequency PWM
16 | mode. When Low enables the power save mode with automatic transition from
17 | PFM mode to fixed-frequency PWM mode.'''),
18 |
19 | PwrOut('SW', descr='''This is the switch pin and is connected to the
20 | internal MOSFET switches. Connect the external inductor between this
21 | terminal and the output capacitor.'''),
22 |
23 | In('FB', descr='''Feedback pin for the internal regulation loop. Connect
24 | the external resistor divider to this pin. In case of fixed output voltage
25 | option, connect this pin directly to the output capacitor.'''),
26 |
27 | Pwr('EP', descr='''Connect the exposed thermal pad to GND.''')
28 | ])
29 |
30 | #Device('TPS62290', adjustable=True)
31 | #Device('TPS62291', vout=3.3)
32 | #Device('TPS62293', vout=1.8)
33 |
34 | Footprint('TPS6229x', 'TPS6229x', 'QFN16', # 'SON',
35 | Map(1, 'SW'),
36 | Map(2, 'MODE'),
37 | Map(3, 'FB'),
38 | Map(4, 'EN'),
39 | Map(5, 'VIN'),
40 | Map(6, 'GND'),
41 | Map(7, 'EP'))
42 |
43 |
44 | @circuit('TPS6229x')
45 | def tps6229x(dev='TPS6229x', en=True, mode=True, fb=False):
46 |
47 | Node('U', dev)
48 |
49 | Net('GND') + Ref('U')['GND', 'EP']
50 | Net('VIN') + Ref('U')['VIN']
51 |
52 | Node('CIN', 'C', '10uF')
53 | Nets('VIN', 'GND') + Ref('CIN')['~', '~']
54 |
55 | Node('L', 'L', '2.2mH')
56 | Net('SW') + Ref('U')['SW'] + Ref('L')['~']
57 | Net('VOUT') + Ref('L')['~']
58 |
59 | Node('COUT', 'C', '10uF')
60 | Nets('VOUT', 'GND') + Ref('COUT')['~', '~']
61 |
62 | if en:
63 | Net('VIN') + Ref('U')['EN']
64 |
65 | if mode:
66 | Net('GND') + Ref('U')['MODE']
67 |
68 | if fb:
69 | Node('FB_R1', 'R', fb[0])
70 | Node('FB_R2', 'R', fb[1])
71 | Node('FB_C', 'C', '22pF')
72 | Net('VOUT') + Ref('FB_R1')['~'] + Ref('FB_C')['~']
73 | Net('FB') + Ref('U')['FB'] + Ref('FB_R1')['~'] + \
74 | Ref('FB_C')['~'] + Ref('FB_R2')['~']
75 | else:
76 | Net('VOUT') + Ref('U')['FB']
77 |
--------------------------------------------------------------------------------
/placer/place.py:
--------------------------------------------------------------------------------
1 | from pycircuit.pcb import Pcb
2 | from pycircuit.package import Courtyard
3 | from pycircuit.formats import json
4 |
5 | from z3 import *
6 | from placer.box import Z3Box
7 | from placer.bin import Z3Bin
8 | from placer.grid import Grid
9 |
10 |
11 | class Placer(object):
12 | def __init__(self, grid_size=Courtyard.IPC_GRID_SCALE, density=1.5):
13 | assert density > 1
14 | self.grid_size = grid_size
15 | self.density = density
16 |
17 | def place(self, filein, fileout):
18 | self.pcb = Pcb.from_file(filein)
19 | left, bottom, right, top = self.pcb.outline.polygon.interiors[0].bounds
20 | width, height = (right - left) / \
21 | self.grid_size, (top - bottom) / self.grid_size
22 | print('width', width, 'height', height)
23 |
24 | boxes = []
25 | min_area = 0
26 |
27 | for inst in self.pcb.netlist.insts:
28 | box = Z3Box.from_inst(inst)
29 | min_area += box.area()
30 | boxes.append(box)
31 |
32 | pcb = Z3Bin()
33 |
34 | s = Solver()
35 |
36 | s.add(pcb.range_constraint())
37 |
38 | for i, box in enumerate(boxes):
39 | # Add rotation constraints
40 | s.add(box.rotation_constraint())
41 | # Constrain position to be on the pcb
42 | s.add(box.range_constraint(pcb))
43 | for j in range(i):
44 | # Add non overlapping constraint
45 | s.add(box.overlap_constraint(boxes[j]))
46 |
47 | # Project constraints:
48 | s.add(pcb.var_width <= width)
49 | s.add(pcb.var_height <= height)
50 | s.add(pcb.area_constraint(min_area * self.density))
51 |
52 | # s.add(boxes[0].fix_position_constraint(*pcb.var_center()))
53 |
54 | if s.check() == sat:
55 | model = s.model()
56 |
57 | pcb.eval(model)
58 | print(str(pcb))
59 |
60 | grid = Grid(*pcb.dim())
61 |
62 | for box in boxes:
63 | box.eval(model)
64 | print(str(box))
65 | grid.add_box(box)
66 | box.place_inst((left, top), self.grid_size)
67 |
68 | print(str(grid))
69 |
70 | self.pcb.to_file(fileout)
71 | else:
72 | print('unsat')
73 |
74 |
75 | if __name__ == '__main__':
76 | import argparse
77 |
78 | parser = argparse.ArgumentParser(
79 | description='SMT-based, constrained placement')
80 |
81 | parser.add_argument('filein', type=str)
82 | parser.add_argument('fileout', type=str)
83 |
84 | args, unknown = parser.parse_known_args()
85 |
86 | placer = Placer()
87 | placer.place(args.filein, args.fileout)
88 |
--------------------------------------------------------------------------------
/tests/test_component.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from pycircuit.component import *
3 |
4 |
5 | class FunTests(unittest.TestCase):
6 |
7 | def test_name_only(self):
8 | f = Fun('GPIO')
9 | assert f.function == 'GPIO'
10 |
11 | def test_name_and_dir(self):
12 | f = Fun('VCC')
13 | assert f.function == 'VCC'
14 |
15 | def test_bus(self):
16 | f = BusFun('UART0', 'UART_TX')
17 | assert f.bus == 'UART0'
18 | assert f.function == 'UART_TX'
19 |
20 |
21 | class PinTests(unittest.TestCase):
22 |
23 | def test_simple_pin(self):
24 | pin = Pin('GPIO')
25 | assert pin.name == 'GPIO'
26 | assert pin.description == ''
27 | assert len(pin.funs) == 1
28 |
29 | def test_pin_with_descr(self):
30 | pin = Pin('GPIO', description='A GPIO Pin')
31 | assert pin.description == 'A GPIO Pin'
32 |
33 | def test_pin_with_fun(self):
34 | pin = Pin('GPIO_1', Fun('GPIO'), BusFun(
35 | 'UART0', 'UART_TX'), description='A GPIO Pin')
36 | assert pin.name == 'GPIO_1'
37 | assert pin.description == 'A GPIO Pin'
38 | assert len(pin.funs) == 2
39 | assert pin.funs[0].function == 'GPIO'
40 | assert pin.funs[1].function == 'UART_TX'
41 |
42 | def test_function_by_name(self):
43 | pin = Pin('GPIO_1', Fun('GPIO'))
44 | assert pin.has_function('GPIO') == True
45 |
46 |
47 | class ComponentTests(unittest.TestCase):
48 |
49 | def test_component(self):
50 | cmp = Component('MCU', 'Microcontroller',
51 | Pin('GND'),
52 | Pin('VCC'),
53 | Pin('XTAL_XI'),
54 | Pin('XTAL_XO'),
55 | Pin('JTAG_TCK'),
56 | Pin('JTAG_TDO'),
57 | Pin('JTAG_TMS'),
58 | Pin('JTAG_TDI'),
59 | Pin('GPIO_1', Fun('GPIO'), BusFun('UART0', 'UART_TX')),
60 | Pin('GPIO_2', Fun('GPIO'), BusFun('UART0', 'UART_RX')),
61 | Pin('GPIO_3', Fun('GPIO')),
62 | Pin('GPIO_4', Fun('GPIO')),
63 | Pin('GPIO_5', Fun('GPIO'), BusFun('UART1', 'UART_TX')),
64 | Pin('GPIO_6', Fun('GPIO'), BusFun('UART1', 'UART_RX')))
65 |
66 | assert cmp.name == 'MCU'
67 | assert cmp.description == 'Microcontroller'
68 | assert len(cmp.pins) == 14
69 | assert len(cmp.busses) == 2
70 |
71 | gnd = cmp.pin_by_name('GND')
72 | assert len(gnd.funs) == 1
73 |
74 | gpio_1 = cmp.pin_by_name('GPIO_1')
75 | assert gpio_1.name == 'GPIO_1'
76 | assert len(gpio_1.funs) == 2
77 |
--------------------------------------------------------------------------------
/tests/test_device.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import unittest
3 | from pycircuit.device import Device, Map
4 | from pycircuit.library import *
5 |
6 |
7 | class DeviceTests(unittest.TestCase):
8 | def setUp(self):
9 | Device.devices = []
10 |
11 | def test_works(self):
12 | Device('R0805', 'R', '0805',
13 | Map('1', 'A'),
14 | Map('2', 'B'))
15 |
16 | def test_none_none_map(self):
17 | with pytest.raises(Exception):
18 | Map(None, None)
19 |
20 | def test_component_doesnt_exist(self):
21 | with pytest.raises(Exception):
22 | Device('R0805', 'R1', '0805',
23 | Map('1', 'A'),
24 | Map('2', 'B'))
25 |
26 | def test_package_doesnt_exist(self):
27 | with pytest.raises(Exception):
28 | Device('R0805', 'R', 'R0805',
29 | Map('1', 'A'),
30 | Map('2', 'B'))
31 |
32 | def test_pin_doesnt_exist(self):
33 | with pytest.raises(Exception):
34 | Device('R0805', 'R', '0805',
35 | Map('1', 'A'),
36 | Map('2', 'C'))
37 |
38 | def test_pad_doesnt_exist(self):
39 | with pytest.raises(Exception):
40 | Device('R0805', 'R', '0805',
41 | Map('1', 'A'),
42 | Map('3', 'B'))
43 |
44 | def test_works2(self):
45 | Device('SOT23BCE', 'Q', 'SOT23',
46 | Map('1', 'B'),
47 | Map('2', 'C'),
48 | Map('3', 'E'),
49 | Map(None, 'SUBSTRATE'))
50 |
51 | def test_pin_unmapped(self):
52 | with pytest.raises(Exception):
53 | Device('SOT23BCE', 'Q', 'SOT23',
54 | Map('1', 'B'),
55 | Map('2', 'C'),
56 | Map('3', 'E'))
57 |
58 | def test_pin_required(self):
59 | with pytest.raises(Exception):
60 | Device('SOT23BCE', 'Q', 'SOT23',
61 | Map('1', 'B'),
62 | Map('2', 'C'),
63 | Map('3', 'SUBSTRATE'),
64 | Map(None, 'E'))
65 |
66 | def pad_mapped_multiple(self):
67 | with pytest.raises(Exception):
68 | Device('SOT23BCE', 'Q', 'SOT23',
69 | Map('1', 'B'),
70 | Map('2', 'C'),
71 | Map('3', 'E'),
72 | Map('3', 'SUBSTRATE'))
73 |
74 | def test_works3(self):
75 | Device('RSOT23', 'R', 'SOT23',
76 | Map('1', 'A'),
77 | Map('2', 'B'),
78 | Map('3', None))
79 |
80 | def test_pad_unmapped(self):
81 | with pytest.raises(Exception):
82 | Device('RSOT23', 'R', 'SOT23',
83 | Map('1', 'A'),
84 | Map('2', 'B'))
85 |
--------------------------------------------------------------------------------
/tests/test_pinassign.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from pycircuit.component import *
3 | from pycircuit.pinassign import *
4 |
5 | Component('R', 'Resistor',
6 | Pin('1', Fun('~')),
7 | Pin('2', Fun('~'))
8 | )
9 |
10 | Component('MCU', 'Microcontroller',
11 | Pin('VCC'),
12 | Pin('GPIO0', Fun('GPIO'), BusFun('UART0', 'UART_TX')),
13 | Pin('GPIO1', Fun('GPIO'), BusFun('UART0', 'UART_RX')),
14 | Pin('GPIO2', Fun('GPIO'), BusFun('UART1', 'UART_TX')),
15 | Pin('GPIO3', Fun('GPIO'), BusFun('UART1', 'UART_RX'))
16 | )
17 |
18 | Component('3x0806', '2 Resistors and one Capacitor',
19 | Pin('P1.1', BusFun('P1', '~'), BusFun('P1', '+'), BusFun('P1', '-')),
20 | Pin('P1.2', BusFun('P1', '~'), BusFun('P1', '+'), BusFun('P1', '-')),
21 | Pin('P2.1', BusFun('P2', '~'), BusFun('P2', '+'), BusFun('P2', '-')),
22 | Pin('P2.2', BusFun('P2', '~'), BusFun('P2', '+'), BusFun('P2', '-')),
23 | Pin('P3.1', BusFun('P3', '~'), BusFun('P3', '+'), BusFun('P3', '-')),
24 | Pin('P3.2', BusFun('P3', '~'), BusFun('P3', '+'), BusFun('P3', '-')),
25 | )
26 |
27 |
28 | class PinAssignTests(unittest.TestCase):
29 | def test_obvious_assign(self):
30 | component = Component.component_by_name('R')
31 | problem = AssignmentProblem(component, (
32 | Z3Assign('~', 0),
33 | Z3Assign('~', 1),
34 | ))
35 |
36 | problem.solve()
37 | problem.print_solution()
38 | assert problem.check_solution() is None
39 |
40 | def test_simple_assign(self):
41 | component = Component.component_by_name('MCU')
42 | problem = AssignmentProblem(component, (
43 | Z3Assign('GPIO', 0),
44 | Z3Assign('GPIO', 1),
45 | Z3Assign('UART_TX', 2),
46 | Z3Assign('UART_RX', 3),
47 | Z3Assign('VCC', 4)
48 | ))
49 |
50 | problem.solve()
51 | problem.print_solution()
52 | assert problem.check_solution() is None
53 |
54 | def test_bus_assign(self):
55 | component = Component.component_by_name('MCU')
56 | problem = AssignmentProblem(component, (
57 | Z3Assign('GPIO', 0),
58 | Z3Assign('GPIO', 1),
59 | Z3BusAssign(
60 | Z3Assign('UART_TX', 2),
61 | Z3Assign('UART_RX', 3)
62 | ),
63 | Z3Assign('VCC', 4)
64 | ))
65 |
66 | problem.solve()
67 | problem.print_solution()
68 | assert problem.check_solution() is None
69 |
70 | def test_multiple_bus_assign(self):
71 | component = Component.component_by_name('3x0806')
72 | problem = AssignmentProblem(component, (
73 | Z3BusAssign(
74 | Z3Assign('~', 0),
75 | Z3Assign('~', 1)
76 | ),
77 | Z3BusAssign(
78 | Z3Assign('~', 2),
79 | Z3Assign('~', 3)
80 | ),
81 | Z3BusAssign(
82 | Z3Assign('+', 4),
83 | Z3Assign('-', 5)
84 | )
85 | ))
86 |
87 | problem.solve()
88 | problem.print_solution()
89 | assert problem.check_solution() is None
90 |
--------------------------------------------------------------------------------
/pycircuit/library/components.py:
--------------------------------------------------------------------------------
1 | from pycircuit.component import *
2 |
3 |
4 | # Passive Devices
5 | Component('Z', 'Impedance',
6 | Pin('A', Fun('~'), optional=False),
7 | Pin('B', Fun('~'), optional=False))
8 |
9 | Component('R', 'Resistor',
10 | Pin('A', Fun('~'), optional=False),
11 | Pin('B', Fun('~'), optional=False))
12 |
13 | Component('C', 'Capacitor',
14 | Pin('A', BusFun('Ceramic', '~'),
15 | BusFun('Electrolytic', '+'), optional=False),
16 | Pin('B', BusFun('Ceramic', '~'),
17 | BusFun('Electrolytic', '-'), optional=False))
18 |
19 | Component('L', 'Inductor',
20 | Pin('A', Fun('~'), optional=False),
21 | Pin('B', Fun('~'), optional=False))
22 |
23 | Component('V', 'Voltage source',
24 | Pin('+', optional=False),
25 | Pin('-', optional=False))
26 |
27 | Component('S', 'Switch',
28 | Pin('A', Fun('~'), optional=False),
29 | Pin('B', Fun('~'), optional=False))
30 |
31 | Component('XTAL', 'Crystal',
32 | Pin('A', Fun('~'), optional=False),
33 | Pin('B', Fun('~'), optional=False))
34 |
35 | Component('TP', 'Test point',
36 | In('TP', optional=False))
37 |
38 | Component('J2P', 'Jumper 2-pins',
39 | Pin('A', Fun('~'), optional=False),
40 | Pin('B', Fun('~'), optional=False))
41 |
42 | Component('J3P', 'Jumper 3-pins',
43 | Pin('A', Fun('~'), optional=False),
44 | Pin('B', Fun('~'), optional=False),
45 | Pin('C', optional=False))
46 |
47 | Component('ANT', 'Antenna', Pin('ANT', optional=False))
48 |
49 | Component('Transformer_1P_1S', 'Transformer with one primary and one secondary winding',
50 | Pin('L1.1', optional=False),
51 | Pin('L1.2', optional=False),
52 | Pin('L2.1', optional=False),
53 | Pin('L2.2', optional=False))
54 |
55 |
56 | # Active Devices
57 | Component('D', 'Diode',
58 | Pin('+', optional=False),
59 | Pin('-', optional=False))
60 |
61 | Component('Q', 'Bipolar transistor',
62 | Pin('B', optional=False),
63 | Pin('C', optional=False),
64 | Pin('E', optional=False),
65 | Pin('SUBSTRATE'))
66 |
67 | Component('M', 'Mosfet',
68 | Pin('G', optional=False),
69 | Pin('D', optional=False),
70 | Pin('S', optional=False),
71 | Pin('SUBSTRATE'))
72 |
73 | Component('OP', 'Opamp',
74 | Pwr('VCC', optional=False),
75 | Pwr('VEE', optional=False),
76 | In('+', optional=False),
77 | In('-', optional=False),
78 | Out('OUT', optional=False))
79 |
80 | Component('RGB_A', 'RGB LED (Common Anode)',
81 | In('+', optional=False),
82 | Out('R', optional=False),
83 | Out('G', optional=False),
84 | Out('B', optional=False))
85 |
86 | Component('RGB_C', 'RGB LED (Common Cathode)',
87 | In('R', optional=False),
88 | In('G', optional=False),
89 | In('B', optional=False),
90 | Out('-', optional=False))
91 |
92 | Component('CLK', 'Clock',
93 | Pwr('VDD', optional=False),
94 | Gnd('GND', optional=False),
95 | Out('CLK', optional=False))
96 |
97 | Component('QSPI:S', 'Quad SPI Slave',
98 | Pwr('VDD', optional=False),
99 | Gnd('GND', optional=False),
100 | In('SCLK', optional=False),
101 | Io('DQ0'),
102 | Io('DQ1'),
103 | Io('DQ2'),
104 | Io('DQ3'),
105 | In('SS', optional=False))
106 |
107 | Component('I2C:S', 'I2C Slave',
108 | Pwr('VDD', optional=False),
109 | Gnd('GND', optional=False),
110 | Io('SDA', optional=False),
111 | In('SCL', optional=False))
112 |
--------------------------------------------------------------------------------
/pycircuit/optimize.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.signal
3 | import ngspyce
4 | from pycircuit.testbench import testbench
5 | from pycircuit.build import Builder
6 | from pycircuit.diffev import DiffEvolver
7 |
8 |
9 | class Optimizer(object):
10 | def __init__(self, netlist, spec, nsamples=20, npop=50):
11 | self.netlist = netlist
12 | self.spec = spec
13 |
14 | # Find simulation parameters from specification
15 | self.nsamples = nsamples
16 | self.sim_params = self.find_sim_params()
17 |
18 | # Find all resistors and capacitors in netlist
19 | # to determine problem size
20 | self.resistors = []
21 | self.capacitors = []
22 |
23 | for inst in netlist.iter_insts():
24 | if inst.component.name == 'R':
25 | self.resistors.append(inst)
26 | elif inst.component.name == 'C':
27 | self.capacitors.append(inst)
28 |
29 | self.nparams = len(self.resistors) + len(self.capacitors)
30 |
31 | # Run simulation to find freqs
32 | self.freqs, _ = self.simulate()
33 | # Compute target values from specification
34 | _, self.target = scipy.signal.freqs(
35 | *self.spec, worN=2 * np.pi * self.freqs)
36 |
37 | # Initialize DiffEvolver
38 | lbound = scipy.zeros(self.nparams)
39 | ubound = scipy.ones(self.nparams)
40 | self.de = DiffEvolver.frombounds(self.cost, lbound, ubound, npop)
41 | self.de.set_boundaries(lbound, ubound, mode='mirror')
42 |
43 | def find_sim_params(self):
44 | w = scipy.signal.findfreqs(*self.spec, self.nsamples)
45 | fstart = w[0] / 2 / np.pi
46 | fstop = w[-1] / 2 / np.pi
47 | decades = np.log10(fstop) - np.log10(fstart)
48 | npoints = self.nsamples / decades
49 | return {
50 | 'mode': 'dec',
51 | 'npoints': npoints,
52 | 'fstart': fstart,
53 | 'fstop': fstop
54 | }
55 |
56 | def transform(self, vector):
57 | for ir, r in enumerate(self.resistors):
58 | value = 10 ** (vector[ir] * 3 + 3) # 1K to 1M
59 | r.set_value(value)
60 | for ic, c in enumerate(self.capacitors):
61 | value = 10 ** (vector[ir + ic] * 6 - 9) # 1nF to 1mF
62 | c.set_value(value)
63 |
64 | def simulate(self):
65 | self.netlist.to_spice('optimize.sp')
66 | ngspyce.source('optimize.sp')
67 | ngspyce.ac(**self.sim_params)
68 | fs = np.abs(ngspyce.vector('frequency'))
69 | vs = ngspyce.vector('vout')
70 | return fs, vs
71 |
72 | def _vector_to_values(self, vector):
73 | self.transform(vector)
74 | _, values = self.simulate()
75 | return values
76 |
77 | def cost(self, vector):
78 | values = self._vector_to_values(vector)
79 | return sum(abs(self.target - values) ** 2)
80 |
81 | def plot_result(self):
82 | values = self._vector_to_values(self.de.best_vector)
83 | self.plot_freq_resp(values)
84 |
85 | def plot_freq_resp(self, values):
86 | from matplotlib import pyplot as plt
87 |
88 | fig, (ax1, ax2) = plt.subplots(2)
89 | fig.suptitle('Frequency response')
90 |
91 | ax1.semilogx(self.freqs, 20 * np.log10(abs(self.target)))
92 | ax1.semilogx(self.freqs, 20 * np.log10(abs(values)))
93 | ax1.set_xlabel('Frequency [Hz]')
94 | ax1.set_ylabel('Amplitude [dB]')
95 | ax1.grid(which='both', axis='both')
96 |
97 | ax2.semilogx(self.freqs, np.angle(self.target, True))
98 | ax2.semilogx(self.freqs, np.angle(values, True))
99 | ax2.set_xlabel('Frequency [Hz]')
100 | ax2.set_ylabel('Phase [degrees]')
101 | ax2.grid(which='both', axis='both')
102 |
103 | plt.show()
104 |
105 | def optimize(self):
106 | self.de.solve(10)
107 | min_cost = self.cost(self.de.best_vector)
108 | return min_cost
109 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/dvc94ch/pycircuit)
2 | # Circuit Description Library
3 |
4 | ## Getting started
5 |
6 | `common_emitter.py`
7 | ```python
8 | from pycircuit.circuit import *
9 | from pycircuit.library import *
10 |
11 |
12 | @circuit('Common Emitter', 'gnd', '12V', 'vin', 'vout')
13 | def common_emitter_amplifer(self, gnd, vcc, vin, vout):
14 | nb, ne = nets('nb ne')
15 | Inst('Q', 'npn sot23')['B', 'C', 'E'] = nb, vout, ne
16 |
17 | # Current limiting resistor
18 | Inst('R', '1.2k')['~', '~'] = vcc, vout
19 |
20 | # Thermal stabilization (leads to a gain reduction)
21 | Inst('R', '220')['~', '~'] = ne, gnd
22 | # Shorts Re for AC signal (increases gain)
23 | Inst('C', '10uF')['~', '~'] = ne, gnd
24 |
25 | # Biasing resistors
26 | Inst('R', '20k')['~', '~'] = vcc, nb
27 | Inst('R', '3.6k')['~', '~'] = nb, gnd
28 | # Decoupling capacitor
29 | Inst('C', '10uF')['~', '~'] = vin, nb
30 |
31 |
32 | if __name__ == '__main__':
33 | from pycircuit.formats import *
34 | from pycircuit.build import Builder
35 |
36 | Builder(common_emitter_amplifer()).compile()
37 | ```
38 |
39 |
40 | 
41 |
42 | ## Optimization
43 | `sallen_key/build.py`
44 | ```python
45 | import numpy as np
46 | import scipy.signal as sig
47 | from pycircuit.build import Builder
48 | from pycircuit.circuit import testbench
49 | from pycircuit.optimize import Optimizer
50 |
51 | from sallen_key import lp_sallen_key
52 |
53 | def lp_optimize():
54 | spec = sig.butter(2, 2 * np.pi * 100, btype='low', analog=True)
55 | tb = Builder(testbench(lp_sallen_key())).compile()
56 | problem = Optimizer(tb, spec)
57 | cost = problem.optimize()
58 | print(cost)
59 | problem.plot_result()
60 | print(repr(problem.netlist))
61 |
62 |
63 | if __name__ == '__main__':
64 | lp_optimize()
65 | ```
66 |
67 | After 10s runtime. If it's not good enough run it again...
68 | 
69 |
70 | ## Physical design
71 | `joule_thief/build.py`
72 | ```python
73 | import joule_thief
74 | from pycircuit.build import Builder
75 | from pycircuit.library.design_rules import oshpark_4layer
76 | from placer import Placer
77 | from router import Router
78 | from pykicad.pcb import Zone
79 |
80 | def place(filein, fileout):
81 | placer = Placer()
82 | placer.place(filein, fileout)
83 |
84 |
85 | def route(filein, fileout):
86 | router = Router()
87 | router.route(filein, fileout)
88 |
89 |
90 | def post_process(pcb, kpcb):
91 | xmin, ymin, xmax, ymax = pcb.boundary()
92 | coords = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]
93 |
94 | zone = Zone(net_name='GND', layer='F.Cu',
95 | polygon=coords, clearance=0.3)
96 |
97 | kpcb.zones.append(zone)
98 | return kpcb
99 |
100 |
101 | if __name__ == '__main__':
102 | Builder(joule_thief.top(), oshpark_4layer,
103 | place=place, route=route, post_process=post_process).build()
104 | ```
105 |
106 | 
107 |
108 |
109 | # License
110 | ISC License
111 |
112 | Copyright (c) 2017, David Craven
113 |
114 | Permission to use, copy, modify, and/or distribute this software for any
115 | purpose with or without fee is hereby granted, provided that the above
116 | copyright notice and this permission notice appear in all copies.
117 |
118 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
119 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
120 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
121 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
122 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
123 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
124 | PERFORMANCE OF THIS SOFTWARE.
125 |
--------------------------------------------------------------------------------
/pycircuit/library/old/ftdi/ftdi.py:
--------------------------------------------------------------------------------
1 | from pycircuit.component import *
2 | from pycircuit.footprint import *
3 |
4 |
5 | # FT232
6 | Component('FT2232H', 'FTDI chip', (
7 | # Power and ground
8 | Pin('VCORE'), # 1V8
9 | Pin('VCCIO'), # 3V3
10 | Pin('VPLL'), # 3V3
11 | Pin('VPHY'), # 3V3
12 | Pin('VREGIN'),
13 | Pin('VREGOUT'),
14 | Pin('AGND'),
15 | Pin('GND'),
16 | # Common pins
17 | Pin('OSCI'), # 12MHz
18 | Pin('OSCO'), # 12MHz
19 | Pin('REF'),
20 | Pin('DN', BusFun('USB0', 'USB', 'D-')),
21 | Pin('DP', BusFun('USB0', 'USB', 'D+')),
22 | Pin('TEST'),
23 | Pin('RESET'),
24 | Pin('PWREN'),
25 | Pin('SUSPEND'),
26 | # EEPROM
27 | Pin('EECS'),
28 | Pin('EECLK'),
29 | Pin('EEDATA'),
30 | # Channel A
31 | Pin('ADBUS0',
32 | BusFun('UART0', 'UART', 'TX'),
33 | BusFun('JTAG0', 'JTAG', 'TCK'),
34 | BusFun('SPI0', 'SPI', 'SCK')),
35 | Pin('ADBUS1',
36 | BusFun('UART0', 'UART', 'RX'),
37 | BusFun('JTAG0', 'JTAG', 'TDI'),
38 | BusFun('SPI0', 'SPI', 'MISO')),
39 | Pin('ADBUS2',
40 | BusFun('JTAG0', 'JTAG', 'TDO'),
41 | BusFun('SPI0', 'SPI', 'MOSI')),
42 | Pin('ADBUS3',
43 | BusFun('JTAG0', 'JTAG', 'TMS'),
44 | BusFun('SPI0', 'SPI', 'SS0')),
45 | Pin('ADBUS4'),
46 | Pin('ADBUS5'),
47 | Pin('ADBUS6'),
48 | Pin('ADBUS7'),
49 | Pin('ACBUS0'),
50 | Pin('ACBUS1'),
51 | Pin('ACBUS2'),
52 | Pin('ACBUS3'),
53 | Pin('ACBUS4'),
54 | Pin('ACBUS5'),
55 | Pin('ACBUS6'),
56 | Pin('ACBUS7'),
57 | # Channel B
58 | Pin('BDBUS0',
59 | BusFun('UART1', 'UART', 'TX'),
60 | BusFun('JTAG1', 'JTAG', 'TCK'),
61 | BusFun('SPI1', 'SPI', 'SCK')),
62 | Pin('BDBUS1',
63 | BusFun('UART1', 'UART', 'RX'),
64 | BusFun('JTAG1', 'JTAG', 'TDI'),
65 | BusFun('SPI1', 'SPI', 'MISO')),
66 | Pin('BDBUS2',
67 | BusFun('JTAG1', 'JTAG', 'TDO'),
68 | BusFun('SPI1', 'SPI', 'MOSI')),
69 | Pin('BDBUS3',
70 | BusFun('JTAG1', 'JTAG', 'TMS'),
71 | BusFun('SPI1', 'SPI', 'SS0')),
72 | Pin('BDBUS4'),
73 | Pin('BDBUS5'),
74 | Pin('BDBUS6'),
75 | Pin('BDBUS7'),
76 | Pin('BCBUS0'),
77 | Pin('BCBUS1'),
78 | Pin('BCBUS2'),
79 | Pin('BCBUS3'),
80 | Pin('BCBUS4'),
81 | Pin('BCBUS5'),
82 | Pin('BCBUS6'),
83 | Pin('BCBUS7', Fun('PWRSAV'))
84 | ))
85 |
86 |
87 | Footprint('FT2232HQ', 'FT2232H', 'QFN64',
88 | Map(1, 'GND'),
89 | Map(2, 'OSCI'),
90 | Map(3, 'OSCO'),
91 | Map(4, 'VPHY'),
92 | Map(5, 'GND'),
93 | Map(6, 'REF'),
94 | Map(7, 'DM'),
95 | Map(8, 'DP'),
96 | Map(9, 'VPLL'),
97 | Map(10, 'AGND'),
98 | Map(11, 'GND'),
99 | Map(12, 'VCORE'),
100 | Map(13, 'TEST'),
101 | Map(14, 'RESET'),
102 | Map(15, 'GND'),
103 | Map(16, 'ADBUS0'),
104 | Map(17, 'ADBUS1'),
105 | Map(18, 'ADBUS2'),
106 | Map(19, 'ADBUS3'),
107 | Map(20, 'VCCIO'),
108 | Map(21, 'ADBUS4'),
109 | Map(22, 'ADBUS5'),
110 | Map(23, 'ADBUS6'),
111 | Map(24, 'ADBUS7'),
112 | Map(25, 'GND'),
113 | Map(26, 'ACBUS0'),
114 | Map(27, 'ACBUS1'),
115 | Map(28, 'ACBUS2'),
116 | Map(29, 'ACBUS3'),
117 | Map(30, 'ACBUS4'),
118 | Map(31, 'VCCIO'),
119 | Map(32, 'ACBUS5'),
120 | Map(33, 'ACBUS6'),
121 | Map(34, 'ACBUS7'),
122 | Map(35, 'GND'),
123 | Map(36, 'SUSPEND'),
124 | Map(37, 'VCORE'),
125 | Map(38, 'BDBUS0'),
126 | Map(39, 'BDBUS1'),
127 | Map(40, 'BDBUS2'),
128 | Map(41, 'BDBUS3'),
129 | Map(42, 'VCCIO'),
130 | Map(43, 'BDBUS4'),
131 | Map(44, 'BDBUS5'),
132 | Map(45, 'BDBUS6'),
133 | Map(46, 'BDBUS7'),
134 | Map(47, 'GND'),
135 | Map(48, 'BCBUS0'),
136 | Map(49, 'VREGOUT'),
137 | Map(50, 'VREGIN'),
138 | Map(51, 'GND'),
139 | Map(52, 'BCBUS1'),
140 | Map(53, 'BCBUS2'),
141 | Map(54, 'BCBUS3'),
142 | Map(55, 'BCBUS4'),
143 | Map(56, 'VCCIO'),
144 | Map(57, 'BCBUS5'),
145 | Map(58, 'BCBUS6'),
146 | Map(59, 'BCBUS7'),
147 | Map(60, 'PWREN'),
148 | Map(61, 'EEDATA'),
149 | Map(62, 'EECLK'),
150 | Map(63, 'EECS'),
151 | Map(64, 'VCORE'))
152 |
--------------------------------------------------------------------------------
/pycircuit/device.py:
--------------------------------------------------------------------------------
1 | from pycircuit.component import *
2 | from pycircuit.package import *
3 |
4 |
5 | class Map(object):
6 | '''Represents the a map from a Pad to a Pin.'''
7 |
8 | def __init__(self, pad, pin):
9 | '''A Map has a pad name, a pin name and a Footprint.'''
10 | assert not (pad is None and pin is None)
11 | self.pad = pad
12 | self.pin = pin
13 | self.device = None
14 |
15 | def __str__(self):
16 | '''Returns the name of the pad.'''
17 |
18 | return self.pad
19 |
20 | def __repr__(self):
21 | '''Returns a string "pad <> pin".'''
22 |
23 | return '%4s <> %s' % (self.pad, self.pin)
24 |
25 |
26 | class Device(object):
27 | '''Represents a mapping from a Component to a Package.'''
28 |
29 | devices = []
30 |
31 | def __init__(self, name, component, package, *maps):
32 | '''A Device with name that maps the pads of a Package to the pins
33 | of a Component.'''
34 |
35 | self.name = name
36 | self.component = Component.component_by_name(component)
37 | self.package = Package.package_by_name(package)
38 | self.maps = []
39 |
40 | for map in maps:
41 | self.add_map(map)
42 |
43 | self.check_device()
44 | self.register_device(self)
45 |
46 | def add_map(self, map):
47 | '''Adds a map to Device.'''
48 |
49 | pin = map.pin
50 | pad = map.pad
51 |
52 | if pin is not None:
53 | pin = self.component.pin_by_name(pin)
54 | if pin is None:
55 | print('Warn: Device %s: Pin %s not in component %s'
56 | % (self.name, map.pin, self.component.name))
57 | if pad is not None:
58 | pad = self.package.pad_by_name(pad)
59 | if pad is None:
60 | print('Warn: Device %s: Pad %s not in package %s'
61 | % (self.name, map.pad, self.package.name))
62 |
63 | if pad is None:
64 | assert pin.optional
65 |
66 | map.pin = pin
67 | map.pad = pad
68 | map.device = self
69 | self.maps.append(map)
70 |
71 | def check_device(self):
72 | '''Checks that every Pin and every Pad has a Map.'''
73 |
74 | for pin in self.component.pins:
75 | for map in self.maps:
76 | if map.pin == pin:
77 | break
78 | else:
79 | raise AssertionError('No map for component %s pin %s in device %s'
80 | % (self.component.name, pin.name, self.name))
81 | for pad in self.package.pads:
82 | for map in self.maps:
83 | if map.pad == pad:
84 | break
85 | else:
86 | raise AssertionError('No map for package %s pad %s in device %s'
87 | % (self.package.name, pad.name, self.name))
88 |
89 | def pin_by_pad(self, pad):
90 | '''Returns the pin mapped to pad.'''
91 |
92 | for map in self.maps:
93 | if map.pad == pad:
94 | return map.pin
95 |
96 | def pads_by_pin(self, pin):
97 | '''Returns a list of pads mapped to pin.'''
98 |
99 | for map in self.maps:
100 | if map.pin == pin:
101 | yield map.pad
102 |
103 | def __str__(self):
104 | '''Returns the name of the Device.'''
105 |
106 | return self.name
107 |
108 | def __repr__(self):
109 | '''Returns a string representation of a Device.'''
110 |
111 | return '%s (%s <> %s)\n%s\n' % (self.name, self.component, self.package,
112 | '\n'.join([repr(map) for map in self.maps]))
113 |
114 | @classmethod
115 | def device_by_name(cls, name):
116 | '''Returns the Device with name from registered devices.'''
117 |
118 | for dev in cls.devices:
119 | if dev.name == name:
120 | return dev
121 |
122 | raise IndexError('No Device with name ' + name)
123 |
124 | @classmethod
125 | def devices_by_component(cls, component):
126 | '''Returns the available Devices for Component.'''
127 |
128 | for dev in cls.devices:
129 | if dev.component == component:
130 | yield dev
131 |
132 | @classmethod
133 | def register_device(cls, device):
134 | '''Register a Device.'''
135 |
136 | try:
137 | cls.device_by_name(device.name)
138 | raise Exception('Device with name %s already exists' % device.name)
139 | except IndexError:
140 | cls.devices.append(device)
141 |
--------------------------------------------------------------------------------
/pycircuit/library/design_rules.py:
--------------------------------------------------------------------------------
1 | from pycircuit.layers import Layers, Layer, Materials
2 | from pycircuit.outline import OutlineDesignRules
3 | from pycircuit.traces import TraceDesignRules
4 | from pycircuit.pcb import PcbAttributes
5 |
6 |
7 | def oshpark_4layer():
8 | '''Design rules for a OSHPark 4-layer board
9 |
10 | layer stackup:
11 | 1.0 mil (0.0254mm) solder resist +/-0.2mil (0.0051mm)
12 | 1.4 mil (0.0356mm) 1 oz copper
13 | 6.7 mil (0.1702mm) FR408 prepreg +/-.67mil (0.017mm)
14 | 0.7 mil (0.0178mm) 0.5 oz copper
15 | 47 mil (1.1938mm) FR408 core +/-4.7mil (0.1194mm)
16 | 0.7 mil (0.0178mm) 0.5 oz copper
17 | 6.7 mil (0.1702mm) FR408 prepreg +/-.67mil (0.017mm)
18 | 1.4 mil (0.0356mm) 1 oz copper
19 | 1.0 mil (0.0254mm) solder resist +/-0.2mil (0.0051mm)
20 |
21 | outline design rules:
22 | min_drill_size = 10mil (0.254mm)
23 | min_slot_width = 100" (1.7272mm)
24 | min_cutout_size = 100" (1.7272mm)
25 |
26 | net design rules:
27 | min_trace_width = 5mil (0.127mm)
28 | min_clearance = 5mil (0.127mm)
29 | min_via_drill = 10mil (0.254mm)
30 | min_annular_ring = 4mil (0.1016mm)
31 | min_edge_clearance = 15mil (0.381mm)
32 | '''
33 |
34 | layers = Layers([
35 | Layer('silk_top', None, Materials.SilkScreen),
36 | Layer('finish_top', None, Materials.ENIG),
37 | Layer('mask_top', 0.0254, Materials.PurpleSolderMask),
38 |
39 | Layer('top', 0.0356, Materials.Cu),
40 | Layer('sub_outer1', 0.1702, Materials.FR408),
41 |
42 | Layer('inner1', 0.0178, Materials.Cu),
43 | Layer('sub_inner', 1.1938, Materials.FR408),
44 | Layer('inner2', 0.0178, Materials.Cu),
45 |
46 | Layer('sub_outer2', 0.1702, Materials.FR408),
47 | Layer('bottom', 0.0356, Materials.Cu),
48 |
49 | Layer('mask_bottom', 0.0254, Materials.PurpleSolderMask),
50 | Layer('finish_bottom', None, Materials.ENIG),
51 | Layer('silk_bottom', None, Materials.SilkScreen),
52 | ])
53 |
54 | outline_design_rules = OutlineDesignRules(
55 | min_drill_size=0.254,
56 | min_slot_width=1.7272,
57 | min_cutout_size=1.7272
58 | )
59 |
60 | trace_design_rules = TraceDesignRules(
61 | min_width=0.127,
62 | min_clearance=0.127,
63 | min_drill=0.254,
64 | min_annular_ring=0.1016,
65 | min_edge_clearance=0.381,
66 | blind_vias_allowed=False,
67 | burried_vias_allowed=False
68 | )
69 |
70 | cost_cm2 = 1.5
71 | return PcbAttributes(layers, outline_design_rules,
72 | trace_design_rules, cost_cm2)
73 |
74 |
75 | def oshpark_2layer():
76 | '''Design rules for a OSHPark 2-layer board
77 |
78 | layer stackup:
79 | 1.0 mil (0.0254mm) solder resist +/-0.2mil (0.00508mm)
80 | 1.4 mil (0.0356mm) 1 oz copper
81 | 60 mil (1.5240mm) core +/-6.0mil (0.1524mm)
82 | 1.4 mil (0.0356mm) 1 oz copper
83 | 1.0 mil (0.0254mm) solder resist +/-0.2mil (0.00508mm)
84 |
85 | outline design rules:
86 | min_drill_size = 10mil (0.254mm)
87 | min_slot_width = 100" (1.7272mm)
88 | min_cutout_size = 100" (1.7272mm)
89 |
90 | net design rules:
91 | min_trace_width = 6mil (0.1524mm)
92 | min_clearance = 6mil (0.1524mm)
93 | min_via_drill = 10mil (0.254mm)
94 | min_annular_ring = 5mil (0.127mm)
95 | min_edge_clearance = 15mil (0.381mm)
96 | '''
97 |
98 | layers = Layers([
99 | Layer('silk_top', None, Materials.SilkScreen),
100 | Layer('finish_top', None, Materials.ENIG),
101 | Layer('mask_top', 0.0254, Materials.PurpleSolderMask),
102 |
103 | Layer('top', 0.0356, Materials.Cu),
104 | Layer('substrate', 1.5240, Materials.FR4),
105 | Layer('bottom', 0.0356, Materials.Cu),
106 |
107 | Layer('mask_bottom', 0.0254, Materials.PurpleSolderMask),
108 | Layer('finish_bottom', None, Materials.ENIG),
109 | Layer('silk_bottom', None, Materials.SilkScreen),
110 | ])
111 |
112 | outline_design_rules = OutlineDesignRules(
113 | min_drill_size=0.254,
114 | min_slot_width=1.7272,
115 | min_cutout_size=1.7272
116 | )
117 |
118 | trace_design_rules = TraceDesignRules(
119 | min_width=0.1524,
120 | min_clearance=0.1524,
121 | min_drill=0.254,
122 | min_annular_ring=0.127,
123 | min_edge_clearance=0.381,
124 | blind_vias_allowed=False,
125 | burried_vias_allowed=False
126 | )
127 |
128 | cost_cm2 = 0.75
129 | return PcbAttributes(layers, outline_design_rules,
130 | trace_design_rules, cost_cm2)
131 |
--------------------------------------------------------------------------------
/pycircuit/formats/yosys.py:
--------------------------------------------------------------------------------
1 | import json
2 | from pycircuit.component import Pin
3 | from pycircuit.circuit import *
4 | from pycircuit.formats import extends
5 |
6 |
7 | def match_skin(inst):
8 | cname = inst.component.name
9 | value = '' if inst.value is None else inst.value
10 | suffix = '_h' if inst.horizontal else '_v'
11 |
12 | if cname == 'R' or cname == 'C' or cname == 'L':
13 | return cname.lower() + suffix
14 |
15 | if cname == 'D':
16 | if 'led' in value:
17 | return 'd_led' + suffix
18 | if 'sk' in value:
19 | return 'd_sk' + suffix
20 | return 'd' + suffix
21 |
22 | if cname == 'Q':
23 | if 'pnp' in value:
24 | return 'q_pnp'
25 | return 'q_npn'
26 |
27 | mapping = {
28 | 'OP': 'op',
29 | 'XTAL': 'xtal',
30 | 'V': 'v',
31 | 'I': 'i',
32 | 'Transformer_1P_1S': 'transformer_1p_1s'
33 | }
34 |
35 | if cname in mapping:
36 | return mapping[cname]
37 |
38 | return cname
39 |
40 |
41 | def match_port_direction(skin, assign):
42 | if skin.startswith('r_') or skin.startswith('c_') or skin.startswith('l_'):
43 | return {'A': 'input', 'B': 'output'}[assign.pin.name]
44 | if skin.startswith('d_'):
45 | return {'+': 'input', '-': 'output'}[assign.pin.name]
46 | if skin.startswith('q_'):
47 | return {'B': 'input', 'C': 'input', 'E': 'output'}[assign.pin.name]
48 | if skin == 'v' or skin == 'i':
49 | return {'+': 'input', '-': 'output'}[assign.pin.name]
50 | if skin == 'op':
51 | return {'VCC': 'input', 'VEE': 'output', '+': 'input', '-': 'input', 'OUT': 'output'}[assign.pin.name]
52 | if skin == 'xtal':
53 | return {'A': 'input', 'B': 'output'}[assign.pin.name]
54 | if skin == 'transformer_1p_1s':
55 | return {'L1.1': 'input', 'L1.2': 'input', 'L2.1': 'output', 'L2.2': 'output'}[assign.pin.name]
56 | if assign.erc_type == ERCType.INPUT:
57 | return 'input'
58 | elif assign.erc_type == ERCType.OUTPUT:
59 | return 'output'
60 | else:
61 | print('Warn: Pin %s has no direction!' % assign.pin.name)
62 |
63 |
64 | def gnd(conn):
65 | return {
66 | 'type': 'gnd',
67 | 'port_directions': {
68 | 'A': 'input'
69 | },
70 | 'connections': {
71 | 'A': [conn]
72 | }
73 | }
74 |
75 |
76 | def vcc(conn):
77 | return {
78 | 'type': 'vcc',
79 | 'port_directions': {
80 | 'A': 'output'
81 | },
82 | 'connections': {
83 | 'A': [conn]
84 | }
85 | }
86 |
87 |
88 | def vee(conn):
89 | return {
90 | 'type': 'vee',
91 | 'port_directions': {
92 | 'A': 'input'
93 | },
94 | 'connections': {
95 | 'A': [conn]
96 | }
97 | }
98 |
99 |
100 | @extends(Circuit)
101 | def to_yosys(self):
102 | cells = {}
103 | for inst in self.insts:
104 | connections = {}
105 | port_directions = {}
106 | for assign in inst.assigns:
107 | uid = UID.uid()
108 | net_type = assign.net.type
109 | if net_type == NetType.GND:
110 | cells['gnd' + str(uid)] = gnd(uid)
111 | connections[assign.pin.name] = [uid]
112 | elif net_type == NetType.VCC:
113 | cells['vcc' + str(uid)] = vcc(uid)
114 | connections[assign.pin.name] = [uid]
115 | elif net_type == NetType.VEE:
116 | cells['vee' + str(uid)] = vee(uid)
117 | connections[assign.pin.name] = [uid]
118 | else:
119 | connections[assign.pin.name] = [assign.net.uid]
120 |
121 | skin = match_skin(inst)
122 | port_directions[assign.pin.name] = match_port_direction(
123 | skin, assign)
124 |
125 | cells[inst.name] = {
126 | 'type': match_skin(inst),
127 | 'port_directions': port_directions,
128 | 'connections': connections
129 | }
130 |
131 | ports = {}
132 | for port in self.external_ports():
133 | if port.type == PortType.IN:
134 | direction = 'input'
135 | elif port.type == PortType.OUT:
136 | direction = 'output'
137 | else:
138 | continue
139 |
140 | ports[port.name] = {
141 | 'direction': direction,
142 | 'bits': [port.internal_net().uid]
143 | }
144 |
145 | return {
146 | 'modules': {
147 | self.name: {
148 | 'ports': ports,
149 | 'cells': cells
150 | }
151 | }
152 | }
153 |
154 |
155 | @extends(Circuit)
156 | def to_yosys_file(self, path):
157 | with open(path, 'w+') as f:
158 | f.write(json.dumps(self.to_yosys(), sort_keys=True,
159 | indent=2, separators=(',', ': ')))
160 |
--------------------------------------------------------------------------------
/placer/box.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from z3 import *
3 | import math
4 | import uuid
5 |
6 |
7 | class Anchor(Enum):
8 | Min = 0
9 | Center = 1
10 | Max = 2
11 |
12 |
13 | class Coord(object):
14 | def __init__(self, anchor):
15 | self.anchor = anchor
16 | self.value = 0
17 |
18 |
19 | class Box(object):
20 | counter = 0
21 | symbols = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+[]{};:\'",./<>?|\\'
22 |
23 | def __init__(self, inst, width, height, x_anchor=Anchor.Min, y_anchor=Anchor.Min):
24 | self.symbol = Box.symbols[Box.counter % len(Box.symbols)]
25 | Box.counter += 1
26 |
27 | self.inst = inst
28 | self.x = Coord(x_anchor)
29 | self.y = Coord(y_anchor)
30 | self.width = width
31 | self.height = height
32 |
33 | def set_position(self, x, y):
34 | self.x.value = x
35 | self.y.value = y
36 | return self
37 |
38 | def top(self):
39 | if self.y.anchor == Anchor.Max:
40 | return self.y.value
41 | if self.y.anchor == Anchor.Min:
42 | return self.y.value + self.height
43 | if self.y.anchor == Anchor.Center:
44 | return self.y.value + self.height / 2
45 |
46 | def bottom(self):
47 | if self.y.anchor == Anchor.Min:
48 | return self.y.value
49 | if self.y.anchor == Anchor.Max:
50 | return self.y.value - self.height
51 | if self.y.anchor == Anchor.Center:
52 | return self.y.value - self.height / 2
53 |
54 | def right(self):
55 | if self.x.anchor == Anchor.Max:
56 | return self.x.value
57 | if self.x.anchor == Anchor.Min:
58 | return self.x.value + self.width
59 | if self.x.anchor == Anchor.Center:
60 | return self.x.value + self.width / 2
61 |
62 | def left(self):
63 | if self.x.anchor == Anchor.Min:
64 | return self.x.value
65 | if self.x.anchor == Anchor.Max:
66 | return self.x.value - self.width
67 | if self.x.anchor == Anchor.Center:
68 | return self.x.value - self.width / 2
69 |
70 | def area(self):
71 | return self.height * self.width
72 |
73 | def place_inst(self, offset, grid_size):
74 | inst_attributes = self.inst.attributes
75 | x = (self.x.value + offset[0]) * grid_size
76 | y = (self.y.value + offset[1]) * grid_size
77 | inst_attributes.place(inst_attributes.layer, x, y)
78 |
79 | def __str__(self):
80 | return 'x=%s y=%s w=%s h=%s' % (self.x.value, self.y.value,
81 | self.height, self.width)
82 |
83 | @classmethod
84 | def from_inst(cls, inst):
85 | return cls(inst, inst.device.package.courtyard.ipc_width,
86 | inst.device.package.courtyard.ipc_height)
87 |
88 |
89 | class Z3Box(Box):
90 |
91 | def __init__(self, inst, width, height):
92 | super().__init__(inst, width, height, Anchor.Center, Anchor.Center)
93 |
94 | self.const_rx = int(math.ceil(width / 2))
95 | self.const_ry = int(math.ceil(height / 2))
96 |
97 | self.var_x = Int('%s_x' % str(inst.uid))
98 | self.var_y = Int('%s_y' % str(inst.uid))
99 | self.var_rot = Bool('%s_rot' % str(inst.uid))
100 | self.var_rx = Int('%s_rx' % str(inst.uid))
101 | self.var_ry = Int('%s_ry' % str(inst.uid))
102 |
103 | def range_constraint(self, bin):
104 | return And(self.var_x >= self.var_rx,
105 | self.var_x <= bin.var_width - self.var_rx,
106 | self.var_y >= self.var_ry,
107 | self.var_y <= bin.var_height - self.var_ry)
108 |
109 | def rotation_constraint(self, allow_rotate=False):
110 | # Enabling rotation makes things much slower
111 | return And(
112 | BoolVal(True) if allow_rotate else self.var_rot == False,
113 | Implies(Not(self.var_rot), And(self.var_rx == self.const_rx,
114 | self.var_ry == self.const_ry)),
115 | Implies(self.var_rot, And(self.var_rx == self.const_ry,
116 | self.var_ry == self.const_rx))
117 | )
118 |
119 | def fix_position_constraint(self, x, y):
120 | return And(self.var_x == x, self.var_y == y)
121 |
122 | def overlap_constraint(self, other):
123 | return Or(
124 | self.var_x - other.var_x >= self.var_rx + other.var_rx,
125 | other.var_x - self.var_x >= self.var_rx + other.var_rx,
126 | self.var_y - other.var_y >= self.var_ry + other.var_ry,
127 | other.var_y - self.var_y >= self.var_ry + other.var_ry
128 | )
129 |
130 | def set_rotation(self, rotation):
131 | width, height = self.width, self.height
132 | if rotation:
133 | self.width = height
134 | self.height = width
135 |
136 | def eval(self, model):
137 | self.set_position(model[self.var_x].as_long(),
138 | model[self.var_y].as_long())
139 | self.set_rotation(is_true(model[self.var_rot]))
140 |
--------------------------------------------------------------------------------
/pycircuit/pinassign.py:
--------------------------------------------------------------------------------
1 | import uuid
2 | from z3 import And, Or, Implies, Int, Distinct, Solver, sat
3 |
4 |
5 | class Z3Assign(object):
6 | def __init__(self, function, meta):
7 | self.function = function
8 | self.meta = meta
9 |
10 | self.constraints = []
11 |
12 | _uuid = uuid.uuid4()
13 | self.z3_fun = Int(str(_uuid) + '_fun')
14 | self.z3_pin = Int(str(_uuid) + '_pin')
15 | self.z3_bus = Int(str(_uuid) + '_bus')
16 |
17 | def component_constraint(self, component):
18 | self.component = component
19 |
20 | fun_constraints = []
21 | for fun in component.funs_by_function(self.function):
22 | assert(fun.id >= 0)
23 | assert(fun.bus_id is not None)
24 |
25 | # Constraint z3_fun to pin functions
26 | fun_constraints.append(self.z3_fun == fun.id)
27 |
28 | # z3_fun implies z3_pin and z3_bus
29 | self.constraints.append(Implies(self.z3_fun == fun.id,
30 | And(self.z3_bus == fun.bus_id,
31 | self.z3_pin == fun.pin.id)))
32 | self.constraints.append(Or(fun_constraints))
33 |
34 | def eval(self, model):
35 | self.fun = self.component.funs[model[self.z3_fun].as_long()]
36 | self.pin = self.component.pins[model[self.z3_pin].as_long()]
37 | self.bus = model[self.z3_bus].as_long()
38 |
39 | def __iter__(self):
40 | yield self
41 |
42 | def __repr__(self):
43 | return self.function
44 |
45 |
46 | class Z3BusAssign(object):
47 | def __init__(self, *assigns):
48 | _uuid = uuid.uuid4()
49 | self.z3_bus = Int(str(_uuid) + '_bus')
50 |
51 | self.assigns = []
52 | self.constraints = []
53 |
54 | for assign in assigns:
55 | self.add_assign(assign)
56 |
57 | def add_assign(self, assign):
58 | # All assignments in a BusAssign need to have a the same bus
59 | self.assigns.append(assign)
60 | self.constraints.append(self.z3_bus == assign.z3_bus)
61 |
62 | def eval(self, model):
63 | self.bus = model[self.z3_bus].as_long()
64 |
65 | def __iter__(self):
66 | for assig in self.assigns:
67 | yield assig
68 |
69 |
70 | class AssignmentProblem(object):
71 | def __init__(self, component, assigns):
72 | self.component = component
73 | self.assigns = []
74 | self.bus_assigns = []
75 | self.constraints = []
76 |
77 | for bus_assign in assigns:
78 | if isinstance(bus_assign, Z3Assign):
79 | bus_assign = Z3BusAssign(bus_assign)
80 |
81 | self.constraints += bus_assign.constraints
82 | self.bus_assigns.append(bus_assign)
83 |
84 | for assign in bus_assign:
85 | assign.component_constraint(component)
86 | self.constraints += assign.constraints
87 | self.assigns.append(assign)
88 |
89 | # Each assignment needs a different Fun
90 | self.constraints.append(
91 | Distinct([assign.z3_fun for assign in self.assigns]))
92 |
93 | # Each assignment needs a different Pin
94 | self.constraints.append(
95 | Distinct([assign.z3_pin for assign in self.assigns]))
96 |
97 | # Each BusFun needs a different bus
98 | self.constraints.append(
99 | Distinct([bus_assign.z3_bus for bus_assign in self.bus_assigns]))
100 |
101 | def solve(self):
102 | s = Solver()
103 |
104 | s.add(And(self.constraints))
105 |
106 | if not s.check() == sat:
107 | print('Problem:')
108 | self.print_problem()
109 | print('Constraints:')
110 | for constraint in self.constraints:
111 | print(constraint)
112 | raise Exception('unsat')
113 |
114 | model = s.model()
115 |
116 | for assign in self.assigns:
117 | assign.eval(model)
118 |
119 | for bus_assign in self.bus_assigns:
120 | bus_assign.eval(model)
121 |
122 | def check_solution(self):
123 | pins = set()
124 | funs = set()
125 | busses = set()
126 |
127 | for assign in self.assigns:
128 | if assign.fun in funs:
129 | return 'Fun assigned multiple times'
130 | else:
131 | funs.add(assign.fun)
132 |
133 | if assign.pin in pins:
134 | return 'Pin assigned multiple times'
135 | else:
136 | pins.add(assign.pin)
137 |
138 | for bus_assign in self.bus_assigns:
139 | if bus_assign.bus in busses:
140 | return 'BusAssign needs unique bus'
141 | busses.add(bus_assign.bus)
142 |
143 | for assign in bus_assign:
144 | if not assign.bus == bus_assign.bus:
145 | return 'BusAssign Assigns need the same bus'
146 |
147 | def print_problem(self):
148 | print(self.assigns)
149 |
150 | def print_solution(self):
151 | for i, assign in enumerate(self.assigns):
152 | print('pin: %s func: %s assign: %s' %
153 | (assign.fun.pin.id, str(assign.fun), str(i)))
154 |
--------------------------------------------------------------------------------
/pycircuit/formats/kicad.py:
--------------------------------------------------------------------------------
1 | import pykicad as ki
2 | from shapely.ops import polygonize
3 | from pycircuit.formats import extends, polygon_to_lines
4 | from pycircuit.outline import Outline
5 | from pycircuit.package import Package, Courtyard, Pad
6 | from pycircuit.pcb import Pcb, InstAttributes, NetAttributes
7 | from pycircuit.traces import Via, Segment
8 |
9 |
10 | @staticmethod
11 | @extends(Package)
12 | def from_kicad(kmodule):
13 | lines = []
14 | for elem in kmodule.courtyard():
15 | # Only Lines are supported in the courtyard at the moment
16 | assert isinstance(elem, ki.module.Line)
17 | start, end = (elem.start[0], elem.start[1]), (elem.end[0], elem.end[1])
18 | lines.append((start, end))
19 |
20 | polygons = list(polygonize(lines))
21 | assert len(polygons) == 1
22 | coords = list(polygons[0].exterior.coords)
23 |
24 | package = Package(kmodule.name, Courtyard(coords), [])
25 | for kpad in kmodule.pads:
26 | drill = None if kpad.drill is None else kpad.drill.size
27 | package.add_pad(Pad(kpad.name, kpad.at[0], kpad.at[1],
28 | size=(kpad.size[0], kpad.size[1]),
29 | drill=drill,
30 | shape=kpad.shape))
31 | return package
32 |
33 |
34 | @extends(Package)
35 | def to_kicad(package):
36 | kmod = ki.module.Module(package.name, layer='F.Cu', attr='smd')
37 |
38 | for start, end in polygon_to_lines(package.courtyard.coords):
39 | kline = ki.module.Line(list(start), list(end), layer='F.CrtYd',
40 | width=0.05)
41 | kmod.lines.append(kline)
42 |
43 | for pad in package.pads:
44 | pad_at = list(pad.location)
45 | pad_at[2] = pad.angle
46 |
47 | if pad.drill is None:
48 | pad_type = 'smd'
49 | pad_layers = ['F.Cu', 'F.Paste', 'F.Mask']
50 | drill = None
51 | else:
52 | pad_type = 'thru_hole'
53 | pad_layers = ['*.Cu', 'F.Mask', 'B.Mask']
54 | drill = ki.module.Drill(pad.drill)
55 |
56 | kpad = ki.module.Pad(pad.name, type=pad_type, shape=pad.shape,
57 | size=list(pad.size), at=pad_at, drill=drill,
58 | layers=pad_layers)
59 | kmod.pads.append(kpad)
60 |
61 | text_radius = package.size()[1] / 2 + 0.5
62 | ref = ki.module.Text(type='reference', layer='F.SilkS', thickness=0.15,
63 | size=[1, 1], text=package.name,
64 | at=[0, -text_radius - 0.35])
65 | value = ki.module.Text(type='value', layer='F.Fab', thickness=0.15,
66 | size=[1, 1], text=package.name,
67 | at=[0, text_radius + 0.25])
68 | kmod.texts = [ref, value]
69 |
70 | return kmod
71 |
72 |
73 | @extends(InstAttributes)
74 | def to_kicad(self):
75 | kmodule = self.inst.device.package.to_kicad()
76 | if self.layer.flip:
77 | kmodule.flip()
78 | if self.angle is not None:
79 | kmodule.rotate(self.angle)
80 | kmodule.place(self.x, self.y)
81 | kmodule.set_reference(self.inst.name)
82 | kmodule.set_value(self.inst.component.name)
83 | return kmodule
84 |
85 |
86 | @extends(Outline)
87 | def to_kicad(self, kpcb):
88 | for start, end in polygon_to_lines(list(self.polygon.exterior.coords)):
89 | kline = ki.pcb.GrLine(list(start), list(
90 | end), layer='Edge.Cuts', width=0.15)
91 | kpcb.lines.append(kline)
92 | for feature in self.features:
93 | center = [float(n) for n in feature.position]
94 | end = [float(n)
95 | for n in feature.position + [feature.drill_size / 2, 0]]
96 | kcircle = ki.pcb.GrCircle(list(center), list(
97 | end), layer='Edge.Cuts', width=0.15)
98 | kpcb.circles.append(kcircle)
99 |
100 |
101 | @extends(Pcb)
102 | def to_kicad(self):
103 | kpcb = ki.pcb.Pcb(title=self.netlist.name)
104 |
105 | # Add kicad modules
106 | for inst in self.netlist.insts:
107 | kpcb.modules.append(inst.attributes.to_kicad())
108 |
109 | # Add kicad nets, segments and vias and connect modules
110 | for i, net in enumerate(self.netlist.nets):
111 | # Kicad nets need to have an id > 0
112 | knet_code = i + 1
113 | knet = ki.module.Net(net.name, knet_code)
114 | kpcb.nets.append(knet)
115 |
116 | for abspad in net.attributes.iter_pads():
117 | kpcb.module_by_reference(abspad.inst.name) \
118 | .connect(abspad.pad.name, knet)
119 |
120 | for seg in net.attributes.segments:
121 | layer = 'F.Cu' if seg.layer.layer.name == 'top' else 'B.Cu'
122 | kseg = ki.pcb.Segment(start=list(seg.start)[0:2],
123 | end=list(seg.end)[0:2],
124 | net=knet_code, width=seg.width,
125 | layer=layer)
126 | kpcb.segments.append(kseg)
127 |
128 | for via in net.attributes.vias:
129 | kvia = ki.pcb.Via(at=list(via.coord)[0:2],
130 | net=knet_code,
131 | size=self.diameter(),
132 | drill=self.drill())
133 | kpcb.vias.append(kvia)
134 |
135 | self.outline.to_kicad(kpcb)
136 | return kpcb
137 |
--------------------------------------------------------------------------------
/pycircuit/formats/spice.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import Inst, Netlist, UID
2 | from pycircuit.formats import extends
3 |
4 |
5 | class SpiceInst(object):
6 | def __init__(self, ty, ref, nodes, value=None, model=None):
7 | self.ty = ty
8 | self.ref = ref
9 | self.nodes = nodes
10 | self.value = value
11 | self.model = model
12 |
13 | if not ref.startswith(self.ty):
14 | self.ref = self.ty + self.ref
15 |
16 | def __str__(self):
17 | nodes = ' '.join([str(n) for n in self.nodes])
18 | value = '' if self.value is None else str(self.value)
19 | model = '' if self.model is None else self.model.mname
20 | return '%s %s %s %s' % (self.ref, nodes, value, model)
21 |
22 |
23 | class SpiceModel(object):
24 | def __init__(self, ty, params):
25 | self.mname = 'mod' + str(UID.uid())
26 | self.ty = ty
27 | self.params = params
28 |
29 | def __str__(self):
30 | params = ' '.join(['%s=%s' % (k, str(v))
31 | for k, v in self.params.items()])
32 | return '.model %s %s (%s)' % (self.mname, self.ty, params)
33 |
34 |
35 | class SpiceProbe(object):
36 | def __init__(self, ty, ref, node):
37 | self.ty = ty
38 | self.ref = ref
39 | self.node = node
40 |
41 | def __str__(self):
42 | return '.probe %s(%s) %s' % (self.ty, self.ref, str(self.node))
43 |
44 |
45 | @extends(Inst)
46 | def to_spice(self):
47 | def nodes_from_assigns(*pin_names):
48 | nodes = []
49 | for name in pin_names:
50 | if name == '0':
51 | nodes.append('0')
52 | else:
53 | assign = self.assign_by_pin_name(name)
54 | if assign is None:
55 | continue
56 | if assign.net.name.lower() == 'gnd':
57 | nodes.append('0')
58 | else:
59 | nodes.append(assign.net.name)
60 | return nodes
61 |
62 | models = []
63 |
64 | if self.component.name == 'R':
65 | nodes = nodes_from_assigns('A', 'B')
66 | models.append(SpiceInst('R', self.name, nodes, self.value))
67 | elif self.component.name == 'C':
68 | nodes = nodes_from_assigns('A', 'B')
69 | models.append(SpiceInst('C', self.name, nodes, self.value))
70 | elif self.component.name == 'L':
71 | nodes = nodes_from_assigns('A', 'B')
72 | models.append(SpiceInst('L', self.name, nodes, self.value))
73 | elif self.component.name == 'V':
74 | nodes = nodes_from_assigns('+', '-')
75 | net = 'n' + str(UID.uid())
76 | models.append(SpiceInst('V', self.name, (net, nodes[1]), self.value))
77 | models.append(SpiceInst('R', self.name, (net, nodes[0]), 30))
78 | elif self.component.name == 'OP':
79 | nodes = nodes_from_assigns('OUT', '0', '+', '-')
80 | models.append(SpiceInst('E', self.name, nodes, 100000))
81 | elif self.component.name == 'D':
82 | nodes = nodes_from_assigns('A', 'C')
83 | if 'led' in self.value.split(' '):
84 | model = SpiceModel('D', {'is': '1a', 'rs': 3.3, 'n': 1.8})
85 | else:
86 | raise Exception('D value needs to contain led')
87 | models.append(model)
88 | models.append(SpiceInst('D', self.name, nodes, model=model))
89 | elif self.component.name == 'Q':
90 | nodes = nodes_from_assigns('C', 'B', 'E', 'SUBSTRATE')
91 | if 'npn' in self.value.split(' '):
92 | model = SpiceModel('npn', {'is': 1e-15})
93 | elif 'pnp' in self.value.split(' '):
94 | model = SpiceModel('pnp', {'is': 1e-15})
95 | else:
96 | raise Exception('Q value needs to contain npn or pnp')
97 | models.append(model)
98 | models.append(SpiceInst('Q', self.name, nodes, model=model))
99 | elif self.component.name == 'M':
100 | nodes = nodes_from_assigns('D', 'G', 'S', 'SUBSTRATE')
101 | if 'nmos' in self.value.split(' '):
102 | model = SpiceModel('nmos')
103 | elif 'pmos' in self.value.split(' '):
104 | model = SpiceModel('pmos')
105 | else:
106 | raise Exception('M value needs to contain nmos or pmos')
107 | models.append(model)
108 | models.append(SpiceModel('M', self.name, nodes, model=model))
109 | elif self.component.name == 'Transformer_1P_1S':
110 | inductors = 'L1' + self.name, 'L2' + self.name
111 | nodes = nodes_from_assigns('L1.1', 'L1.2')
112 | models.append(SpiceInst('L', inductors[0], nodes, self.value))
113 | nodes = nodes_from_assigns('L2.1', 'L2.2')
114 | models.append(SpiceInst('L', inductors[1], nodes, self.value))
115 | models.append(SpiceInst('K', self.name, inductors, 1))
116 | elif self.component.name == 'TP':
117 | node = nodes_from_assigns('TP')
118 | models.append(SpiceProbe('v', self.name, *node))
119 | else:
120 | raise Exception("Component %s doesn't have a spice model"
121 | % self.component.name)
122 |
123 | return models
124 |
125 |
126 | @extends(Netlist)
127 | def to_spice(self, filename):
128 | with open(filename, 'w+') as f:
129 | # Emit title
130 | print('.title', filename, file=f)
131 |
132 | for inst in self.insts:
133 | for model in inst.to_spice():
134 | print(str(model), file=f)
135 |
136 | # Emit end
137 | print('.end', file=f)
138 |
--------------------------------------------------------------------------------
/pycircuit/outline.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from shapely.geometry import mapping, shape
3 | from shapely.geometry.polygon import Polygon
4 |
5 |
6 | class OutlineDesignRules(object):
7 | def __init__(self, min_drill_size, min_slot_width, min_cutout_size):
8 | self.min_drill_size = min_drill_size
9 | self.min_slot_width = min_slot_width
10 | self.min_cutout_size = min_cutout_size
11 |
12 | def to_object(self):
13 | return {
14 | 'min_drill_size': self.min_drill_size,
15 | 'min_slot_width': self.min_slot_width,
16 | 'min_cutout_size': self.min_cutout_size,
17 | }
18 |
19 | @classmethod
20 | def from_object(cls, obj):
21 | return cls(obj['min_drill_size'], obj['min_slot_width'],
22 | obj['min_cutout_size'])
23 |
24 |
25 | class OutlineDesignRuleError(Exception):
26 | pass
27 |
28 |
29 | class OutlineElement(object):
30 | def check(self, design_rules):
31 | assert isinstance(design_rules, OutlineDesignRules)
32 | raise NotImplementedError()
33 |
34 |
35 | class Hole(OutlineElement):
36 | def __init__(self, position, drill_size):
37 | self.position = position
38 | self.drill_size = drill_size
39 |
40 | def check(self, design_rules):
41 | if self.drill_size < design_rules.min_drill_size:
42 | raise OutlineDesignRuleError('Hole drill size needs to be larger than %d',
43 | design_rules.min_drill_size)
44 |
45 | def to_object(self):
46 | return {
47 | 'type': 'hole',
48 | 'drill_size': self.drill_size,
49 | 'x': float(self.position[0]),
50 | 'y': float(self.position[1]),
51 | }
52 |
53 | @classmethod
54 | def from_object(cls, obj):
55 | assert obj['type'] == 'hole'
56 | return cls(np.array([obj['x'], obj['y']]), obj['drill_size'])
57 |
58 |
59 | class Slot(OutlineElement):
60 | def __init__(self, position, drill_size, width, angle=0):
61 | self.position = position
62 | self.drill_size = drill_size
63 | self.width = width # hole to hole
64 | self.angle = angle
65 |
66 | def check(self, design_rules):
67 | if self.drill_size < design_rules.min_drill_size:
68 | raise OutlineDesignRuleError('Slot drill size needs to be larger than %d',
69 | design_rules.min_drill_size)
70 | if self.width < design_rules.min_slot_width + self.drill_size:
71 | raise OutlineDesignRuleError('Slot width needs to be larger than %d',
72 | design_rules.min_slot_width + self.drill_size)
73 |
74 | def to_object(self):
75 | return {
76 | 'type': 'slot',
77 | 'drill_size': self.drill_size,
78 | 'width': self.width,
79 | 'x': float(self.position[0]),
80 | 'y': float(self.position[1]),
81 | 'r': self.angle,
82 | }
83 |
84 | @classmethod
85 | def from_object(cls, obj):
86 | assert obj['type'] == 'slot'
87 | return cls(np.array(obj['x'], obj['y']), obj['drill_size'],
88 | obj['width'], obj['r'])
89 |
90 |
91 | class Cutout(OutlineElement):
92 | def __init__(self, polygon):
93 | assert isinstance(polygon, Polygon)
94 | self.polygon = polygon
95 | self.width = polygon.bounds[2] - polygon.bounds[0]
96 | self.height = polygon.bounds[3] - polygon.bounds[1]
97 |
98 | def check(self, design_rules):
99 | if self.height < design_rules.min_cutout_size:
100 | raise OutlineDesignRuleError('Cutout height must be larger than %d',
101 | design_rules.min_cutout_size)
102 | if self.width < design_rules.min_cutout_size:
103 | raise OutlineDesignRuleError('Cutout width must be larger than %d',
104 | design_rules.min_cutout_size)
105 |
106 | def to_object(self):
107 | return {
108 | 'type': 'cutout',
109 | 'polygon': mapping(self.polygon)
110 | }
111 |
112 | @classmethod
113 | def from_object(cls, obj):
114 | assert obj['type'] == 'cutout'
115 | return cls(shape(obj['polygon']))
116 |
117 |
118 | class Outline(object):
119 | def __init__(self, polygon, *features):
120 | assert isinstance(polygon, Polygon)
121 | self.polygon = polygon
122 | self.features = []
123 |
124 | for f in features:
125 | assert isinstance(f, OutlineElement)
126 | self.features.append(f)
127 |
128 | def __iter__(self):
129 | return iter(self.features)
130 |
131 | def check(self, design_rules):
132 | for f in self.features:
133 | f.check(design_rules)
134 |
135 | def to_object(self):
136 | return {
137 | 'polygon': mapping(self.polygon),
138 | 'features': [f.to_object() for f in self.features]
139 | }
140 |
141 | @classmethod
142 | def from_object(cls, obj):
143 | features = []
144 | for f in obj['features']:
145 | if f['type'] == 'hole':
146 | features.append(Hole.from_object(f))
147 | elif f['type'] == 'slot':
148 | features.append(Slot.from_object(f))
149 | elif f['type'] == 'cutout':
150 | features.append(Cutout.from_object(f))
151 |
152 | return cls(shape(obj['polygon']), *features)
153 |
--------------------------------------------------------------------------------
/pycircuit/library/old/ti/tps6213x.py:
--------------------------------------------------------------------------------
1 | from pycircuit.device import *
2 | from pycircuit.footprint import *
3 | from pycircuit.circuit import *
4 |
5 |
6 | Device('TPS6213x', descr='''3V to 17V, 3A Step-Down Converter''', pins=[
7 | PwrOut('SW', descr='''Switch node, which is connected to the internal
8 | MOSFET switches. Connect inductor between SW and output capacitor.'''),
9 |
10 | Out('PG', descr='''Output power good (High = Vout ready, Low = Vout below
11 | nominal regulation) ; open drain (requires pull-up resistor).'''),
12 |
13 | Pwr('AGND', descr='''Analog Ground. Must be connected directly to the
14 | Exposed Thermal Pad and common ground plane.'''),
15 |
16 | In('FB', descr='''Voltage feedback of adjustable version. Connect
17 | resistive voltage divider to this pin. It is recommended to connect FB
18 | to AGND on fixed output voltage versions for improved thermal
19 | performance.'''),
20 |
21 | In('FSW', descr='''Switching Frequency Slect (Low ~2.5MHz, High ~1.25MHz
22 | [0] for typical operation) [0] Connect FSW to Vout or PG in this case.'''),
23 |
24 | In('DEF', descr='''Output Voltage Scaling (Low = nominal,
25 | High = nominal +5%)'''),
26 |
27 | In('SS/TR', descr='''Soft-Start / Tracking Pin. An external capacitor
28 | connected to this pin sets the internal voltage reference rise time. It
29 | can be used for tracking and sequencing.'''),
30 |
31 | PwrIn('AVIN', descr='''Supply voltage for control circuitry. Connect to
32 | same source as PVIN.'''),
33 |
34 | PwrIn('PVIN', descr='''Supply voltage for power stage. Connect to same
35 | source as AVIN.'''),
36 |
37 | In('EN', descr='''Enable input (High = enabled, Low = disabled)'''),
38 |
39 | In('VOS', descr='''Output voltage sense pin and connection for the
40 | control loop circuitry.'''),
41 |
42 | Pwr('PGND', descr='''Power Ground. Must be connected directly to the
43 | Exposed Thermal Pad and common ground plane.'''),
44 |
45 | Pwr('EP', descr='''Must be connected to AGND, PGND and common ground
46 | plane. Must be soldered to achieve appropriate power dissipation and
47 | mechanical reliablility.''')
48 | ])
49 |
50 |
51 | #Device('TPS62130', 'TPS62130x', adjustable=True)
52 | # Device('TPS62130A', 'TPS62130x', adjustable=True) # Power Good logic level low
53 | #Device('TPS62131', 'TPS62130x', vout=1.8, adjustable=False)
54 | #Device('TPS62132', 'TPS62130x', vout=3.3, adjustable=False)
55 | #Device('TPS62133', 'TPS62130x', vout=5, adjustable=False)
56 |
57 |
58 | Footprint('TPS6213x', 'TPS6213x', 'QFN16', # VQFN16
59 | Map(1, 'SW'),
60 | Map(2, 'SW'),
61 | Map(3, 'SW'),
62 | Map(4, 'PG'),
63 | Map(5, 'FB'),
64 | Map(6, 'AGND'),
65 | Map(7, 'FSW'),
66 | Map(8, 'DEF'),
67 | Map(9, 'SS/TR'),
68 | Map(10, 'AVIN'),
69 | Map(11, 'PVIN'),
70 | Map(12, 'PVIN'),
71 | Map(13, 'EN'),
72 | Map(14, 'VOS'),
73 | Map(15, 'PGND'),
74 | Map(16, 'PGND'),
75 | Map(17, 'EP'))
76 |
77 |
78 | @circuit('TPS6213x')
79 | def tps6213x(dev='TPS6213x', en=True, pg=False, fb=False):
80 | '''Returns a circuit for a TPS6213x Voltage Regulator.
81 |
82 | Keyword arguments:
83 | dev -- Device name
84 | en -- Connects EN to VIN when True (default True)
85 | pg -- Adds a pullup to PG or connects to GND when unused (default False)
86 | fb -- Feedback resistive voltage divider takes a tuple of resistor
87 | values (default False)
88 | '''
89 |
90 | Node('U', dev)
91 |
92 | # Input circuitry.
93 | # PVIN and AVIN must be connected to same source
94 | Net('VIN') + Ref('U')['PVIN', 'AVIN']
95 | # AGND, PGND and EP must be connected
96 | Net('GND') + Ref('U')['AGND', 'PGND', 'EP']
97 |
98 | # Input decoupling capacitors
99 | Node('CIN1', 'C', '10uF')
100 | Node('CIN2', 'C', '0.1uF')
101 | Net('VIN') + Ref('CIN1')['~'] + Ref('CIN2')['~']
102 | Net('GND') + Ref('CIN1')['~'] + Ref('CIN2')['~']
103 |
104 | # Output circuitry.
105 | # Recommended inductor values are 1/2.2uH.
106 | Node('L', 'L', '2.2uH')
107 | Net('SW') + Ref('U')['SW'] + Ref('L')['~']
108 | Net('VOUT') + Ref('L')['~'] + Ref('U')['VOS']
109 |
110 | # Output decoupling capacitor.
111 | Node('COUT', 'C', '22uF')
112 | Nets('VOUT', 'GND') + Ref('COUT')['~', '~']
113 |
114 | # Feedback voltage divider
115 | if fb:
116 | Node('FB_R1', 'R', fb[0])
117 | Node('FB_R2', 'R', fb[1])
118 | Net('VOUT') + Ref('FB_R1')['~']
119 | Net('FB') + Ref('FB_R1')['~'] + Ref('U')['FB'] + Ref('FB_R2')['~']
120 | Net('GND') + Ref('FB_R2')['~']
121 | else:
122 | # Connect FB to GND for fixed output voltage regulators.
123 | Net('GND') + Ref('U')['FB']
124 |
125 | # Misc configuration pins
126 | # EN pin
127 | if en:
128 | # Connect EN to VIN to enable device when power
129 | # is applied
130 | Net('VIN') + Ref('U')['EN']
131 |
132 | # Power good pin
133 | if pg:
134 | # PG requires a pullup when used
135 | Node('PG_PULLUP', 'R', '100K')
136 | Net('PG') + Ref('U')['PG'] + Ref('PG_PULLUP')['~']
137 | Net('VOUT') + Ref('PG_PULLUP')['~']
138 | else:
139 | # PG should be connected to GND when unused
140 | Net('GND') + Ref('U')['PG']
141 |
142 | # DEF and FSW pins are connected to GND by default
143 | Net('GND') + Ref('U')['DEF', 'FSW']
144 |
145 | # SS/TR controls the startup time after EN goes high
146 | Node('C_START', 'C', '3.3nF')
147 | Net('SS/TR') + Ref('C_START')['~'] + Ref('U')['SS/TR']
148 | Net('GND') + Ref('C_START')['~']
149 |
--------------------------------------------------------------------------------
/pycircuit/build.py:
--------------------------------------------------------------------------------
1 | import os
2 | import hashlib
3 | import shutil
4 | from pycircuit.circuit import Netlist, Circuit
5 | from pycircuit.compiler import Compiler
6 | from pycircuit.formats import *
7 | from pycircuit.pcb import Pcb
8 |
9 |
10 | def netlistsvg(filein, fileout):
11 | skin = '~/repos/netlistsvg/lib/analog.svg'
12 | os.system('netlistsvg --skin %s %s -o %s' % (skin, filein, fileout))
13 |
14 |
15 | def default_compile(filein, fileout):
16 | compiler = Compiler()
17 | return compiler.compile(filein, fileout)
18 |
19 |
20 | def default_place(filein, fileout):
21 | Pcb.from_file(filein).to_file(fileout)
22 |
23 |
24 | def default_route(filein, fileout):
25 | Pcb.from_file(filein).to_file(fileout)
26 |
27 |
28 | def default_post_process(pcb, kpcb):
29 | return kpcb
30 |
31 |
32 | def string_to_filename(string):
33 | return string.lower().replace(' ', '_')
34 |
35 |
36 | class Builder(object):
37 | def __init__(self, circuit,
38 | outline=None,
39 | pcb_attributes=None,
40 | builddir='build',
41 | compile=default_compile,
42 | place=default_place,
43 | route=default_route,
44 | post_process=default_post_process):
45 | self.base_file_name = string_to_filename(circuit.name)
46 | self.builddir = builddir
47 | self.files = {
48 | 'hash': self.base_file_name + '.hash',
49 | 'net_in': self.base_file_name + '.net',
50 | 'net_out': self.base_file_name + '.out.net',
51 | 'place_in': self.base_file_name + '.place.pcb',
52 | 'place_out': self.base_file_name + '.place.out.pcb',
53 | 'route_in': self.base_file_name + '.place.out.pcb',
54 | 'route_out': self.base_file_name + '.route.out.pcb',
55 | 'spice': self.base_file_name + '.sp',
56 | 'net_yosys': self.base_file_name + '.json',
57 | 'net_svg': self.base_file_name + '.net.svg',
58 | 'pcb_svg': self.base_file_name + '.pcb.svg',
59 | 'kicad': self.base_file_name + '.kicad_pcb'
60 | }
61 | for tag, filename in self.files.items():
62 | self.files[tag] = os.path.join(self.builddir, filename)
63 |
64 | self.hashs = {}
65 | self.circuit = circuit
66 | self.outline = outline
67 | self.pcb_attributes = pcb_attributes
68 |
69 | self.compile_hook = compile
70 | self.place_hook = place
71 | self.route_hook = route
72 | self.post_process_hook = post_process
73 |
74 | def file_hash(self, path):
75 | try:
76 | with open(path) as f:
77 | return hashlib.sha256(f.read().encode('utf-8')).hexdigest()
78 | except FileNotFoundError:
79 | return None
80 |
81 | def write_hashfile(self):
82 | with open(self.files['hash'], 'w+') as f:
83 | for name, path in self.files.items():
84 | if name == 'hash':
85 | continue
86 | print(path, self.file_hash(path), file=f)
87 |
88 | def read_hashfile(self):
89 | try:
90 | with open(self.files['hash']) as f:
91 | for line in f.read().split('\n'):
92 | if line == '':
93 | continue
94 | path, digest = line.split(' ')
95 | if digest == 'None':
96 | digest = None
97 | self.hashs[path] = digest
98 | except FileNotFoundError:
99 | if not os.path.exists(self.builddir):
100 | os.makedirs(self.builddir)
101 | self.write_hashfile()
102 | self.read_hashfile()
103 |
104 | def stored_hash(self, name):
105 | return self.hashs[self.files[name]]
106 |
107 | def current_hash(self, name):
108 | return self.file_hash(self.files[name])
109 |
110 | def load_pcb(self, place=False, route=False):
111 | file_name = self.files['place_in']
112 | if place:
113 | file_name = self.files['place_out']
114 | if route:
115 | file_name = self.files['route_out']
116 | return Pcb.from_file(file_name)
117 |
118 | def step(self, input, output, call):
119 | if not self.stored_hash(input) == self.current_hash(input) \
120 | or self.current_hash(output) is None:
121 | result = call(self.files[input], self.files[output])
122 | self.write_hashfile()
123 | return True, result
124 | else:
125 | return False, None
126 |
127 | def build(self):
128 | self.compile()
129 | self.place()
130 | self.route()
131 | self.post_process()
132 |
133 | def compile(self):
134 | self.read_hashfile()
135 | self.circuit.to_file(self.files['net_in'])
136 |
137 | run, circuit = self.step('net_in', 'net_out', self.compile_hook)
138 | if not run:
139 | circuit = Circuit.from_file(self.files['net_out'])
140 |
141 | self.step('net_out', 'net_yosys', lambda _,
142 | x: circuit.to_yosys_file(x))
143 | self.step('net_yosys', 'net_svg', netlistsvg)
144 | return circuit
145 |
146 | def place(self):
147 | netlist = Netlist.from_file(self.files['net_out'])
148 | pcb = Pcb(netlist, self.outline, self.pcb_attributes)
149 |
150 | self.step('net_out', 'place_in', lambda _, x: pcb.to_file(x))
151 | self.step('place_in', 'place_out', self.place_hook)
152 | pcb = self.load_pcb(place=True)
153 | self.step('place_out', 'pcb_svg', lambda _, x: pcb.to_svg(x))
154 |
155 | def route(self):
156 | pcb = self.load_pcb(place=True)
157 | self.step('place_out', 'route_in', lambda _, x: pcb.to_file(x))
158 | self.step('route_in', 'route_out', self.route_hook)
159 | pcb = self.load_pcb(place=True, route=True)
160 | self.step('route_out', 'pcb_svg', lambda _, x: pcb.to_svg(x))
161 |
162 | def post_process(self):
163 | pcb = self.load_pcb(place=True, route=True)
164 | kpcb = self.post_process_hook(pcb, pcb.to_kicad())
165 | kpcb.to_file(self.files['kicad'])
166 |
167 | def clean(self):
168 | shutil.rmtree(self.builddir)
169 |
--------------------------------------------------------------------------------
/pycircuit/component.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class PinType(Enum):
5 | POWER, GND, IN, OUT, INOUT, UNKNOWN = range(6)
6 |
7 | def __str__(self):
8 | return self.name.lower()
9 |
10 | @classmethod
11 | def from_string(cls, string):
12 | if string == 'power':
13 | return PinType.POWER
14 | if string == 'gnd':
15 | return PinType.GND
16 | if string == 'in':
17 | return PinType.IN
18 | if string == 'out':
19 | return PinType.OUT
20 | if string == 'inout':
21 | return PinType.INOUT
22 | if string == 'unknown':
23 | return PinType.UNKNOWN
24 |
25 |
26 | class Fun(object):
27 | def __init__(self, function, **kwargs):
28 | self.id = None
29 | self.bus_id = None
30 | self.pin = None
31 |
32 | self.function = function
33 |
34 | def __str__(self):
35 | return self.function
36 |
37 | def __repr__(self):
38 | return 'fn %s' % self.function
39 |
40 |
41 | class BusFun(Fun):
42 | def __init__(self, bus, function):
43 | super().__init__(function)
44 |
45 | self.bus = bus
46 |
47 | def __str__(self):
48 | return '%s %s' % (self.bus, self.function)
49 |
50 | def __repr__(self):
51 | return 'busfn %s %s' % (self.bus, self.function)
52 |
53 |
54 | class Pin(object):
55 | def __init__(self, name, *funs, **kwargs):
56 | self.id = None
57 | self.device = None
58 | self.name = name
59 | self.funs = []
60 | self.type = PinType.UNKNOWN
61 |
62 | self.optional = True
63 | if 'optional' in kwargs:
64 | self.optional = kwargs['optional']
65 |
66 | self.description = ''
67 | if 'description' in kwargs:
68 | self.description = kwargs['description']
69 |
70 | if 'type' in kwargs:
71 | self.type = kwargs['type']
72 |
73 | if len(funs) == 0:
74 | self.add_fun(Fun(self.name))
75 | else:
76 | for fun in funs:
77 | self.add_fun(fun)
78 |
79 | def has_function(self, function):
80 | for fun in self.funs:
81 | if fun.function == function:
82 | return True
83 | return False
84 |
85 | def add_fun(self, fun):
86 | assert not self.has_function(fun.function)
87 | self.funs.append(fun)
88 |
89 | def __str__(self):
90 | return '%s %s' % (str(self.type), self.name)
91 |
92 | def __repr__(self):
93 | '''Return a string "Pin (Functions)".'''
94 |
95 | return '%-15s (%s)' % (str(self), ' | '.join([str(fun) for fun in self.funs]))
96 |
97 |
98 | class Component(object):
99 | components = []
100 |
101 | def __init__(self, name, description, *pins):
102 | Component.components.append(self)
103 |
104 | self.name = name
105 | self.description = description
106 |
107 | self.pins = []
108 | self.funs = []
109 | self.busses = []
110 | self._functions = set()
111 |
112 | for pin in pins:
113 | self.add_pin(pin)
114 |
115 | # Check that there is no Fun named like a BusFun
116 | # and no BusFun named like a Fun
117 | for function in self._functions:
118 | iterator = self.funs_by_function(function)
119 | ty = type(next(iterator))
120 | for fun in iterator:
121 | assert type(fun) == ty
122 |
123 | def add_pin(self, pin):
124 | assert self.pin_by_name(pin.name) is None
125 |
126 | pin.id = len(self.pins)
127 | pin.component = self
128 | self.pins.append(pin)
129 |
130 | for fun in pin.funs:
131 | fun.id = len(self.funs)
132 | fun.pin = pin
133 | self.funs.append(fun)
134 | self._functions.add(fun.function)
135 |
136 | # Assign bus_id
137 | if isinstance(fun, BusFun):
138 | for i, bus in enumerate(self.busses):
139 | if fun.bus == bus:
140 | fun.bus_id = i
141 | break
142 | else:
143 | fun.bus_id = len(self.busses)
144 | self.busses.append(fun.bus)
145 | else:
146 | # make sure bus_id is unique and smaller zero
147 | fun.bus_id = -fun.id - 1
148 |
149 | def has_function(self, function):
150 | return function in self._functions
151 |
152 | def is_busfun(self, function):
153 | return isinstance(next(self.funs_by_function(function)), BusFun)
154 |
155 | def funs_by_function(self, function):
156 | for fun in self.funs:
157 | if fun.function == function:
158 | yield fun
159 |
160 | def pin_by_name(self, name):
161 | for pin in self.pins:
162 | if pin.name == name:
163 | return pin
164 |
165 | def __str__(self):
166 | '''Return the name of the component.'''
167 |
168 | return self.name
169 |
170 | def __repr__(self):
171 | component = '%s\n' % self.name
172 | for pin in self.pins:
173 | component += ' ' + repr(pin) + '\n'
174 | return component
175 |
176 | @classmethod
177 | def component_by_name(cls, name):
178 | '''Returns the Component called `name` from registered components.'''
179 |
180 | for c in cls.components:
181 | if c.name == name:
182 | return c
183 |
184 | raise IndexError('No Component with name ' + name)
185 |
186 | @classmethod
187 | def register_component(cls, component):
188 | '''Register a Component.'''
189 |
190 | try:
191 | cls.component_by_name(component.name)
192 | raise Exception('Component with name %s exists' % component.name)
193 | except IndexError:
194 | cls.components.append(component)
195 |
196 |
197 | class Io(Pin):
198 | def __init__(self, name, *funs, **kwargs):
199 | kwargs['type'] = PinType.INOUT
200 | super().__init__(name, Fun('GPIO'), *funs, **kwargs)
201 |
202 |
203 | class In(Pin):
204 | def __init__(self, name, *funs, **kwargs):
205 | kwargs['type'] = PinType.IN
206 | super().__init__(name, *funs, **kwargs)
207 |
208 |
209 | class Out(Pin):
210 | def __init__(self, name, *funs, **kwargs):
211 | kwargs['type'] = PinType.OUT
212 | super().__init__(name, *funs, **kwargs)
213 |
214 |
215 | class Pwr(Pin):
216 | def __init__(self, name, **kwargs):
217 | kwargs['type'] = PinType.POWER
218 | super().__init__(name, **kwargs)
219 |
220 |
221 | class Gnd(Pin):
222 | def __init__(self, name, **kwargs):
223 | kwargs['type'] = PinType.GND
224 | super().__init__(name, **kwargs)
225 |
--------------------------------------------------------------------------------
/pycircuit/layers.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class BaseMaterial(Enum):
5 | def properties(self):
6 | return {}
7 |
8 |
9 | class Conductor(BaseMaterial):
10 | Cu, Ag, Au = list(range(3))
11 |
12 | def properties(self):
13 | prop = {}
14 |
15 | _conductivity = {
16 | 'Cu': 5.96 * 10**7, # S/m @ 20C
17 | 'Ag': 6.30 * 10**7, # S/m @ 20C
18 | 'Au': 4.10 * 10**7, # S/m @ 20C
19 | }
20 |
21 | prop['conductivity'] = _conductivity[self.name]
22 | return prop
23 |
24 |
25 | class Dielectric(BaseMaterial):
26 | FR4, FR408, PVP = list(range(3))
27 |
28 | def properties(self):
29 | prop = {}
30 |
31 | _permittivity = {
32 | 'PVP': 3, # Er ref: Guess
33 | 'FR4': 4.6, # Er @1MHz ref: OSHPark
34 | 'FR408': 3.66, # Er @1GHz ref: OSHPark
35 | }
36 |
37 | prop['permittivity'] = _permittivity[self.name]
38 | return prop
39 |
40 |
41 | class SolderMask(BaseMaterial):
42 | Purple, Red, Green, Blue, Black, White = list(range(6))
43 |
44 | def properties(self):
45 | prop = {}
46 |
47 | _color = {
48 | 'Purple': 'purple',
49 | 'Red': 'red',
50 | 'Green': 'green',
51 | 'Blue': 'blue',
52 | 'Black': 'black',
53 | 'White': 'white',
54 | }
55 |
56 | prop['color'] = _color[self.name]
57 | return prop
58 |
59 |
60 | class SolderPaste(BaseMaterial):
61 | Any = list(range(1))
62 |
63 |
64 | class Adhesive(BaseMaterial):
65 | Any = list(range(1))
66 |
67 |
68 | class SilkScreen(BaseMaterial):
69 | Any = list(range(1))
70 |
71 |
72 | class Finish(BaseMaterial):
73 | ENIG = list(range(1))
74 |
75 |
76 | class MaterialType(Enum):
77 | Conductor, Dielectric, SolderPaste, Adhesive, SolderMask, SilkScreen, Finish \
78 | = list(range(7))
79 |
80 | def get_class(self):
81 | _class = {
82 | MaterialType.Conductor: Conductor,
83 | MaterialType.Dielectric: Dielectric,
84 | MaterialType.SolderMask: SolderMask,
85 | MaterialType.SolderPaste: SolderPaste,
86 | MaterialType.Adhesive: Adhesive,
87 | MaterialType.SilkScreen: SilkScreen,
88 | MaterialType.Finish: Finish,
89 | }
90 |
91 | return _class[self]
92 |
93 |
94 | class Material(object):
95 | def __init__(self, material_type, material=None):
96 | assert isinstance(material_type, MaterialType)
97 | self.material_type = material_type
98 |
99 | _class = material_type.get_class()
100 | if _class is not None:
101 | assert isinstance(material, _class)
102 | self.material = material
103 |
104 | self.is_conductor = False
105 | if material_type is MaterialType.Conductor:
106 | self.is_conductor = True
107 |
108 | def to_object(self):
109 | obj = {
110 | 'material_type': self.material_type.name
111 | }
112 |
113 | if self.material is not None:
114 | obj['material'] = self.material.name
115 | obj['properties'] = self.material.properties()
116 |
117 | return obj
118 |
119 | @classmethod
120 | def from_object(cls, obj):
121 | material_type = MaterialType[obj['material_type']]
122 | material = material_type.get_class()[obj['material']]
123 | return cls(material_type, material)
124 |
125 |
126 | class Materials(object):
127 | # Conductors
128 | Cu = Material(MaterialType.Conductor, Conductor.Cu)
129 | Ag = Material(MaterialType.Conductor, Conductor.Ag)
130 | Au = Material(MaterialType.Conductor, Conductor.Au)
131 | # Dielectrics
132 | FR4 = Material(MaterialType.Dielectric, Dielectric.FR4)
133 | FR408 = Material(MaterialType.Dielectric, Dielectric.FR408)
134 | PVP = Material(MaterialType.Dielectric, Dielectric.PVP)
135 | # Soldermasks
136 | PurpleSolderMask = Material(MaterialType.SolderMask, SolderMask.Purple)
137 | # Finishes
138 | ENIG = Material(MaterialType.Finish, Finish.ENIG)
139 | # Misc
140 | SolderPaste = Material(MaterialType.SolderPaste, SolderPaste.Any)
141 | SilkScreen = Material(MaterialType.SilkScreen, SilkScreen.Any)
142 | Adhesive = Material(MaterialType.Adhesive, Adhesive.Any)
143 |
144 |
145 | class Layer(object):
146 | def __init__(self, name, thickness, material):
147 | self.name = name
148 | self.thickness = thickness
149 | self.material = material
150 |
151 | def __str__(self):
152 | return self.name
153 |
154 | def __repr__(self):
155 | return self.name
156 |
157 | def to_object(self):
158 | return {
159 | 'name': self.name,
160 | 'thickness': self.thickness,
161 | 'material': self.material.to_object()
162 | }
163 |
164 | @classmethod
165 | def from_object(cls, obj):
166 | return cls(obj['name'], obj['thickness'], Material.from_object(obj['material']))
167 |
168 |
169 | class RoutingLayer(object):
170 | def __init__(self, layer):
171 | self.layer = layer
172 | self.segments = []
173 | self.vias = []
174 |
175 |
176 | class PlacementLayer(object):
177 | def __init__(self, layer, flip=False):
178 | self.layer = layer
179 | self.flip = flip
180 | self.insts = []
181 |
182 |
183 | class Layers(object):
184 | def __init__(self, layers):
185 | self.layers = layers
186 |
187 | self.placement_layers = []
188 | self.routing_layers = []
189 |
190 | # Add routing layers
191 | for layer in self.layers:
192 | if layer.material.is_conductor:
193 | self.routing_layers.append(RoutingLayer(layer))
194 |
195 | # Add placement layers
196 | top = PlacementLayer(self.routing_layers[0].layer)
197 | bottom = PlacementLayer(self.routing_layers[-1].layer, flip=True)
198 | self.placement_layers += [top, bottom]
199 |
200 | def __getitem__(self, index):
201 | return self.layers[index]
202 |
203 | def __iter__(self):
204 | return iter(self.layers)
205 |
206 | def layer_by_name(self, name):
207 | for layer in self.layers:
208 | if layer.name == name:
209 | return layer
210 |
211 | def player_by_name(self, name):
212 | for player in self.placement_layers:
213 | if player.layer.name == name:
214 | return player
215 |
216 | def rlayer_by_name(self, name):
217 | for rlayer in self.routing_layers:
218 | if rlayer.layer.name == name:
219 | return rlayer
220 |
221 | def iter_conductive(self):
222 | for layer in self.layers:
223 | if layer.material.is_conductor:
224 | yield layer
225 |
226 | def __str__(self):
227 | return str([str(layer) for layer in self.layers])
228 |
229 | def to_object(self):
230 | return [layer.to_object() for layer in self.layers]
231 |
232 | @classmethod
233 | def from_object(cls, obj):
234 | return cls([Layer.from_object(layer) for layer in obj])
235 |
--------------------------------------------------------------------------------
/pycircuit/traces.py:
--------------------------------------------------------------------------------
1 | from pycircuit.circuit import UID
2 |
3 |
4 | class TraceDesignRules(object):
5 | def __init__(self, min_width, min_clearance,
6 | min_annular_ring, min_drill,
7 | blind_vias_allowed, burried_vias_allowed,
8 | min_edge_clearance):
9 | self.min_width = min_width
10 | self.min_clearance = min_clearance
11 | self.min_annular_ring = min_annular_ring
12 | self.min_drill = min_drill
13 | self.blind_vias_allowed = blind_vias_allowed
14 | self.burried_vias_allowed = burried_vias_allowed
15 | self.min_edge_clearance = min_edge_clearance
16 |
17 | def to_netclass(self):
18 | return NetClass(segment_width=self.min_width,
19 | segment_clearance=self.min_clearance,
20 | via_drill=self.min_drill,
21 | via_diameter=self.min_annular_ring * 2 + self.min_drill,
22 | via_clearance=self.min_clearance,
23 | blind_vias=self.blind_vias_allowed,
24 | burried_vias=self.burried_vias_allowed)
25 |
26 | def to_object(self):
27 | return {
28 | 'min_width': self.min_width,
29 | 'min_clearance': self.min_clearance,
30 | 'min_annular_ring': self.min_annular_ring,
31 | 'min_drill': self.min_drill,
32 | 'blind_vias_allowed': self.blind_vias_allowed,
33 | 'burried_vias_allowed': self.burried_vias_allowed,
34 | 'min_edge_clearance': self.min_edge_clearance,
35 | }
36 |
37 | @classmethod
38 | def from_object(cls, obj):
39 | return cls(obj['min_width'], obj['min_clearance'], obj['min_annular_ring'],
40 | obj['min_drill'], obj['blind_vias_allowed'],
41 | obj['burried_vias_allowed'], obj['min_edge_clearance'])
42 |
43 |
44 | class TraceDesignRuleError(Exception):
45 | pass
46 |
47 |
48 | class NetClass(object):
49 | def __init__(self, net_class=None, segment_width=None,
50 | segment_clearance=None, via_diameter=None,
51 | via_drill=None, via_clearance=None,
52 | blind_vias=None, burried_vias=None,
53 | length_match=None, uid=None):
54 | self.uid = uid
55 | if uid is None:
56 | self.uid = UID.uid()
57 | self.parent = net_class
58 | self._segment_width = segment_width
59 | self._segment_clearance = segment_clearance
60 | self._via_diameter = via_diameter
61 | self._via_drill = via_drill
62 | self._via_clearance = via_clearance
63 | self._blind_vias = blind_vias
64 | self._burried_vias = burried_vias
65 |
66 | def __getattr__(self, attr):
67 | value = getattr(self, '_' + attr)
68 | if value is None:
69 | return getattr(self.parent, attr)
70 | return value
71 |
72 | def to_object(self):
73 | obj = {'uid': self.uid}
74 | if self.parent is not None:
75 | obj['net_class'] = self.parent.uid
76 | if self._segment_width is not None:
77 | obj['segment_width'] = self._segment_width
78 | if self._segment_clearance is not None:
79 | obj['segment_clearance'] = self._segment_clearance
80 | if self._via_diameter is not None:
81 | obj['via_diameter'] = self._via_diameter
82 | if self._via_drill is not None:
83 | obj['via_drill'] = self._via_drill
84 | if self._via_clearance is not None:
85 | obj['via_clearance'] = self._via_clearance
86 | if self._blind_vias is not None:
87 | obj['blind_vias'] = self._blind_vias
88 | if self._burried_vias is not None:
89 | obj['burried_vias'] = self._burried_vias
90 | return obj
91 |
92 | @classmethod
93 | def from_object(cls, obj, pcb):
94 | if 'net_class' in obj:
95 | obj['net_class'] = pcb.net_class_by_uid(obj['net_class'])
96 | return cls(**obj)
97 |
98 |
99 | class Segment(object):
100 | def __init__(self, net, start, end, routable_layer):
101 | self.net = net
102 |
103 | self.start = start
104 | self.end = end
105 | self.layer = routable_layer
106 |
107 | self.net.attributes.segments.append(self)
108 | self.layer.segments.append(self)
109 |
110 | def __getattr__(self, attr):
111 | if attr == 'width':
112 | return self.net.attributes.net_class.segment_width
113 | elif attr == 'length':
114 | return Point(self.start[0:2]).distance(Point(self.end[0:2]))
115 |
116 | def __str__(self):
117 | return '%s %s' % (str(self.start), str(self.end))
118 |
119 | def check(self, design_rules):
120 | # TODO: check clearance and edge_clearance
121 | if self.width < design_rules.min_width:
122 | raise TraceDesignRuleError('Trace width needs to be larger than %d'
123 | % design_rules.min_width)
124 |
125 | def to_object(self):
126 | return {
127 | 'net': self.net.uid,
128 | 'start': self.start,
129 | 'end': self.end,
130 | 'layer': self.layer.layer.name,
131 | }
132 |
133 | @classmethod
134 | def from_object(cls, obj, pcb):
135 | net = pcb.netlist.net_by_uid(obj['net'])
136 | rlayer = pcb.attributes.layers.rlayer_by_name(obj['layer'])
137 | return cls(net, obj['start'], obj['end'], rlayer)
138 |
139 |
140 | class Via(object):
141 | def __init__(self, net, position, routable_layers):
142 | self.net = net
143 |
144 | self.position = position
145 |
146 | self.is_blind = False
147 | self.is_burried = False
148 |
149 | self.net.attributes.vias.append(self)
150 | for layer in layers:
151 | layer.vias.append(self)
152 |
153 | def __getattr__(self, attr):
154 | if attr == 'drill':
155 | return self.net.attributes.net_class.via_drill
156 | elif attr == 'diameter':
157 | return self.net.attributes.net_class.via_diameter
158 |
159 | def iter_layers(self):
160 | for layer in self.layers:
161 | yield layer
162 |
163 | def check(self, design_rules):
164 | # TODO: check clearance and edge_clearance
165 | if self.drill < design_rules.min_drill:
166 | raise TraceDesignRuleError('Via drill needs to be larger than %d'
167 | % design_rules.min_drill)
168 | min_diameter = design_rules.min_drill + 2 * design_rules.min_annular_ring
169 | if self.diameter < min_diameter:
170 | raise TraceDesignRuleError('Via diameter needs to be larger than %d'
171 | % min_diameter)
172 | if self.is_blind and not design_rules.blind_vias_allowed:
173 | raise TraceDesignRuleError('No blind vias allowed')
174 | if self.is_burried and not design_rules.burried_vias_allowed:
175 | raise TraceDesignRuleError('No burried vias allowed')
176 |
177 | def to_object(self):
178 | return {
179 | 'net': self.net.uid,
180 | 'x': float(self.position[0]),
181 | 'y': float(self.position[1]),
182 | 'layers': [layer.layer.name for layer in self.layers],
183 | }
184 |
185 | @classmethod
186 | def from_object(cls, obj, pcb):
187 | net = pcb.netlist.net_by_uid(obj['net'])
188 | net_class = pcb.net_class_by_uid(obj['net_class'])
189 | layers = [pcb.attributes.layers.rlayer_by_name(name)
190 | for layer in obj['layers']]
191 | return cls(net, net_class, np.array(obj['x'], obj['y']), rlayers)
192 |
--------------------------------------------------------------------------------
/tests/test_circuit.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import unittest
3 | from pycircuit.circuit import *
4 |
5 |
6 | class CircuitTests(unittest.TestCase):
7 | def setUp(self):
8 | self.circuit = Circuit('CircuitTests')
9 | Circuit.active_circuit = self.circuit
10 |
11 | def test_port(self):
12 | p1 = Port('p1', PortType.IN)
13 | assert self.circuit.port_by_name('p1') == p1
14 | assert self.circuit.port_by_uid(p1.uid) == p1
15 | assert str(p1) == 'p1'
16 | assert repr(p1) == 'in p1'
17 |
18 | def test_net(self):
19 | n1 = Net('n1')
20 | assert self.circuit.net_by_uid(n1.uid) == n1
21 | assert str(n1) == 'n1'
22 | assert repr(n1) == 'signal n1'
23 |
24 | def test_inst(self):
25 | r1 = Inst('R')
26 | assert self.circuit.inst_by_uid(r1.uid) == r1
27 | assert str(r1) == 'R'
28 | assert repr(r1) == 'inst R of R {\n}\n'
29 |
30 | def test_subinst(self):
31 | s1 = SubInst(Circuit('SubCircuit'))
32 | assert self.circuit.subinst_by_uid(s1.uid) == s1
33 | assert str(s1) == 'SubCircuit'
34 | assert repr(s1) == 'subinst SubCircuit of SubCircuit {\n}\n'
35 |
36 | def test_assign_inst_to_net(self):
37 | n1 = Net('n1')
38 | r1 = Inst('R')
39 | r1['~'] = n1
40 | assert len(r1.assigns) == 1
41 | assert len(n1.assigns) == 1
42 | assert repr(r1) == 'inst R of R {\n ~ = n1\n}\n'
43 |
44 | def test_assign_inst_to_port(self):
45 | p1 = Port('p1', PortType.IN)
46 | r1 = Inst('R')
47 | r1['~'] = p1
48 | assert len(r1.assigns) == 1
49 | assert len(p1.assigns) == 1
50 | assert repr(r1) == 'inst R of R {\n ~ = p1\n}\n'
51 |
52 | def test_assign_subinst_to_net(self):
53 | net = Net('n1')
54 | circuit = Circuit('SubCircuit')
55 | port_inner = Port('p', PortType.IN, _parent=circuit)
56 | subinst = SubInst(circuit)
57 | subinst['p'] = net
58 | assert len(subinst.assigns) == 1
59 | assert len(net.assigns) == 1
60 | assert repr(
61 | subinst) == 'subinst SubCircuit of SubCircuit {\n p = n1\n}\n'
62 |
63 | def test_assign_subinst_to_port(self):
64 | port = Port('p1', PortType.IN)
65 | circuit = Circuit('SubCircuit')
66 | port_inner = Port('p', PortType.IN, _parent=circuit)
67 | subinst = SubInst(circuit)
68 | subinst['p'] = port
69 | assert len(subinst.assigns) == 1
70 | assert len(port.assigns) == 1
71 | assert len(port_inner.assigns) == 1
72 | assert port.assigns[0].net == port_inner.assigns[0].net
73 | assert repr(
74 | subinst) == 'subinst SubCircuit of SubCircuit {\n p = p1\n}\n'
75 |
76 | def test_port_to_object(self):
77 | p1 = Port('p1', PortType.IN)
78 | obj = p1.to_object()
79 | assert isinstance(obj['uid'], int)
80 | assert isinstance(obj['guid'], int)
81 | assert obj['name'] == 'p1'
82 |
83 | def test_port_from_object(self):
84 | p1 = Port.from_object(
85 | {'uid': 0, 'guid': 1, 'name': 'p1', 'type': 0}, self.circuit)
86 | assert p1.uid == 0
87 | assert p1.name == 'p1'
88 |
89 | def test_net_to_object(self):
90 | n1 = Net('n1')
91 | obj = n1.to_object()
92 | assert isinstance(obj['uid'], int)
93 | assert isinstance(obj['guid'], int)
94 | assert obj['name'] == 'n1'
95 |
96 | def test_net_from_object(self):
97 | n1 = Port.from_object(
98 | {'uid': 0, 'guid': 1, 'name': 'n1', 'type': 0}, self.circuit)
99 | assert n1.uid == 0
100 | assert n1.name == 'n1'
101 |
102 | def test_inst_to_object(self):
103 | r1 = Inst('R')
104 | obj = r1.to_object()
105 | assert isinstance(obj['uid'], int)
106 | assert isinstance(obj['guid'], int)
107 | assert obj['name'] == 'R'
108 | assert obj['component'] == 'R'
109 | assert obj['value'] == None
110 | assert obj['assigns'] == []
111 |
112 | def test_inst_from_object(self):
113 | r1 = Inst.from_object({
114 | 'uid': 0,
115 | 'guid': 1,
116 | 'name': 'R1',
117 | 'component': 'R',
118 | 'value': None,
119 | 'assigns': []
120 | }, self.circuit)
121 | assert r1.uid == 0
122 | assert r1.name == 'R1'
123 | assert isinstance(r1.component, Component)
124 | assert r1.component.name == 'R'
125 | assert r1.device is None
126 | assert r1.value is None
127 |
128 | def test_circuit_to_object(self):
129 | p1, n1, r1 = Port('p1', PortType.IN), Net('n1'), Inst('R')
130 | obj = self.circuit.to_object()
131 | assert obj['name'] == 'CircuitTests'
132 | assert len(obj['ports']) == 1
133 | assert len(obj['nets']) == 1
134 | assert len(obj['insts']) == 1
135 |
136 | def test_circuit_from_object(self):
137 | c1 = Circuit.from_object({
138 | 'name': 'SubCircuit',
139 | 'ports': [{'uid': 0, 'guid': 2, 'name': 'p1', 'type': 0}],
140 | 'nets': [{'uid': 1, 'guid': 3, 'name': 'n1'}],
141 | 'insts': [{
142 | 'uid': 4,
143 | 'guid': 5,
144 | 'name': 'R1',
145 | 'component': 'R',
146 | 'value': None,
147 | 'assigns': []
148 | }],
149 | }, None)
150 | assert c1.name == 'SubCircuit'
151 | assert len(c1.ports) == 1
152 | assert isinstance(c1.ports[0], Port)
153 | assert len(c1.nets) == 1
154 | assert isinstance(c1.nets[0], Net)
155 | assert len(c1.insts) == 1
156 | assert isinstance(c1.insts[0], Inst)
157 |
158 | def test_inst_assign_to_object(self):
159 | inst = Inst('R')
160 | function = '~'
161 | net = Net('n1')
162 | assign = InstAssign(inst, function, net)
163 | obj = assign.to_object()
164 | assert isinstance(obj['uid'], int)
165 | assert isinstance(obj['guid'], int)
166 | assert obj['function'] == function
167 | assert obj['net'] == net.uid
168 |
169 | def test_inst_assign_from_object(self):
170 | uid = UID.uid()
171 | guid = UID.uid()
172 | inst = Inst('R')
173 | function = '~'
174 | net = Net('n1')
175 | assign = InstAssign.from_object({
176 | 'uid': uid,
177 | 'guid': guid,
178 | 'function': function,
179 | 'pin': None,
180 | 'type': None,
181 | 'net': net.uid,
182 | }, self.circuit, inst)
183 | assert assign.uid == uid
184 | assert assign.guid == guid
185 | assert assign.inst == inst
186 | assert assign.function == function
187 | assert assign.pin is None
188 | assert assign.type is None
189 | assert assign.net == net
190 |
191 | def test_port_assign_to_object(self):
192 | circuit = Circuit('sub')
193 | port = Port('p1', PortType.IN, _parent=circuit)
194 | subinst = SubInst(circuit)
195 | net = Net('n1')
196 | assign = PortAssign(port, net)
197 | obj = assign.to_object()
198 | assert isinstance(obj['uid'], int)
199 | assert isinstance(obj['guid'], int)
200 | assert obj['net'] == net.uid
201 |
202 | def test_port_assign_from_object(self):
203 | uid = UID.uid()
204 | guid = UID.uid()
205 | circuit = Circuit('sub')
206 | port = Port('p1', PortType.IN, _parent=circuit)
207 | subinst = SubInst(circuit)
208 | net = Net('n1')
209 | assign = PortAssign.from_object({
210 | 'uid': uid,
211 | 'guid': guid,
212 | 'net': net.uid,
213 | }, self.circuit, port)
214 | assert assign.uid == uid
215 | assert assign.guid == guid
216 | assert assign.port == port
217 | assert assign.net == net
218 |
--------------------------------------------------------------------------------
/router/router.py:
--------------------------------------------------------------------------------
1 | import itertools
2 | from shapely.geometry import Point, LineString, MultiLineString
3 | from shapely.ops import linemerge
4 | from pycircuit.pcb import Pcb
5 | from pycircuit.traces import Segment
6 | from router.monosat import route_multi
7 |
8 |
9 | class Router(object):
10 | def __init__(self, grid_size=0.05, allow_45=False,
11 | monosat_args=[], amo_builtin_size=20, heuristic_edge_weights=0,
12 | graph_separation_enforcement=2, maxflow_enforcement_level=0,
13 | flowgraph_separation_enforcement=0):
14 | '''Initialize Router with settings.
15 |
16 | Keyword arguments:
17 | amo-builtin-size -- (default 20)
18 | The largest at-most-one constraint size to manually build instead of
19 | using builtin AMO solver.
20 |
21 | heuristic-edge-weights -- (default 0) (choices range(0, 2))
22 | This enables a heuristic which sets assigned edges to unit weight,
23 | to encourage edge-reuse in solutions in the solver.
24 |
25 | graph-separation-enforcement -- (default 2) (choices range(1, 4))
26 | This controls the type of constraint that prevents nets from
27 | intersecting. All three are reasonable choices.
28 |
29 | maxflow-enforcement-level -- (default 0) (choices range(0, 5))
30 | Set to >= 1 to enable redundant, over-approximative maximum flow
31 | constraints, which can help the solver prune bad solutions early.
32 | Options 2, 3, 4 control heuristic interactions between the flow
33 | constraints and the routing constraints in the solver.
34 |
35 | flowgraph-separation-enforcement -- (default 0) (choices range(0, 4))
36 | This controls the type of constraint used to prevent nets from
37 | intersecting with each other in the maximum flow constraint, IF
38 | maxflow constraints are used.
39 | '''
40 |
41 | self.grid_size = grid_size
42 | self.allow_45 = allow_45
43 |
44 | # default argument for MonoSAT; enables the heuristics described in
45 | # "Routing Under Constraints", FMCAD 2016, A. Nadel
46 | monosat_args.append('-ruc')
47 |
48 | assert heuristic_edge_weights in range(0, 2)
49 | assert graph_separation_enforcement in range(1, 4)
50 | assert maxflow_enforcement_level in range(0, 5)
51 | assert flowgraph_separation_enforcement in range(0, 4)
52 |
53 | self.monosat_args = monosat_args
54 | self.amo_builtin_size = amo_builtin_size
55 | self.heuristic_edge_weights = heuristic_edge_weights
56 | self.graph_separation_enforcement = graph_separation_enforcement
57 | self.maxflow_enforcement_level = maxflow_enforcement_level
58 | self.flowgraph_separation_enforcement = flowgraph_separation_enforcement
59 |
60 | def route(self, filein, fileout):
61 | pcb = Pcb.from_file(filein)
62 |
63 | width, height, bounds = *pcb.size(), pcb.outline.polygon.exterior.bounds
64 | grid_width, grid_height = int(width / self.grid_size + 1), \
65 | int(height / self.grid_size + 1)
66 |
67 | def pos_to_grid(x, y):
68 | return (
69 | int((x - bounds[0]) / self.grid_size),
70 | int((y - bounds[1]) / self.grid_size)
71 | )
72 |
73 | def grid_to_pos(x, y):
74 | return (
75 | x * self.grid_size + bounds[0],
76 | y * self.grid_size + bounds[1]
77 | )
78 |
79 | nets = []
80 | for net in pcb.netlist.nets:
81 | nets.append([])
82 | for pad in net.attributes.iter_pads():
83 | nets[-1].append(pos_to_grid(*pad.location[0:2]))
84 | # for inst in self.netlist.insts:
85 | # for pad in inst.attributes.iter_pads():
86 | # loc = grid(pad.location[0] - pad.size[0] / 2,
87 | # pad.location[1] - pad.size[1] / 2)
88 | # width = int(math.ceil(pad.size[0] / grid_size))
89 | # height = int(math.ceil(pad.size[1] / grid_size))
90 | # vids = []
91 | # for x in range(width):
92 | # for y in range(height):
93 | # vids.append(vid(x + loc[0], y + loc[1]))
94 | #print('C', ' '.join(vids), file=f)
95 |
96 | ## ROUTE ##
97 | nets = route_multi(self, grid_width, grid_height, nets)
98 |
99 | def reduce_linestring(linestring):
100 | if len(linestring.coords) < 3:
101 | return linestring
102 | coords = [linestring.coords[0]]
103 | for i, end in enumerate(linestring.coords[2:]):
104 | start = linestring.coords[i]
105 | test = linestring.coords[i + 1]
106 | if not LineString([start, end]).contains(Point(test)):
107 | coords.append(test)
108 | coords.append(linestring.coords[-1])
109 | return LineString(coords)
110 |
111 | net_segments = []
112 | for i, coords in enumerate(nets):
113 | net_segments.append([])
114 |
115 | lines = []
116 | for a, b in itertools.combinations(coords, 2):
117 | if Point(a).distance(Point(b)) == 1:
118 | lines.append(LineString([a, b]))
119 | mls = linemerge(MultiLineString(lines))
120 | if not isinstance(mls, MultiLineString):
121 | line = reduce_linestring(mls).coords
122 | net_segments[-1].append(line)
123 | else:
124 | for line in mls:
125 | line = reduce_linestring(line).coords
126 | net_segments[-1].append(line)
127 |
128 | for segments, net in zip(net_segments, pcb.netlist.nets):
129 | for seg in segments:
130 | for i, coord in enumerate(seg[1:]):
131 | start = grid_to_pos(*seg[i])
132 | end = grid_to_pos(*coord)
133 | Segment(net, start, end,
134 | pcb.attributes.layers.routing_layers[0])
135 |
136 | pcb.to_file(fileout)
137 |
138 |
139 | if __name__ == '__main__':
140 | import sys
141 |
142 | monosat_args = ['-ruc']
143 |
144 | parser = argparse.ArgumentParser(
145 | description='SAT-based, constrained multi-terminal routing')
146 |
147 | parser.add_argument('--amo-builtin-size', default=20, type=int, help='''The largest at-most-one constraint size to manually
148 | build instead of using builtin AMO solver''')
149 | parser.add_argument('--heuristic-edge-weights', default=0, type=int,
150 | choices=range(0, 2), help='''This enables a heuristic
151 | which sets assigned edges to unit weight, to encourage
152 | edge-reuse in solutions in the solver.''')
153 | parser.add_argument('--graph-separation-enforcement', default=2, type=int,
154 | choices=range(1, 4), help='''This controls the type of
155 | constraint that prevents nets from intersecting. All
156 | three are reasonable choices.''')
157 | parser.add_argument('--maxflow-enforcement-level', default=0, type=int,
158 | choices=range(0, 5), help='''Set to >= 1 to enable
159 | redundant, over-approximative maximum flow constraints,
160 | which can help the solver prune bad solutions early.
161 | Options 2,3,4 control heuristic interactions between the
162 | flow constraints and the routing constraints in the
163 | solver.''')
164 | parser.add_argument('--flowgraph-separation-enforcement', default=0, type=int,
165 | choices=range(0, 4), help='''This controls the type of
166 | constraint used to prevent nets from intersecting with
167 | each other in the maximum flow constraint, IF maxflow
168 | constraints are used.''')
169 |
170 | parser.add_argument('filein', type=str)
171 | parser.add_argument('fileout', type=str)
172 |
173 | args, unknown = parser.parse_known_args()
174 |
175 | if len(unknown) > 0:
176 | print("Passing unrecognized arguments to monosat: " + str(unknown))
177 | monosat_args = unknown
178 |
179 | router = Router(monosat_args,
180 | amo_builtin_size=amo_builtin_size,
181 | heuristic_edge_weights=heuristic_edge_weights,
182 | graph_separation_enforcement=graph_separation_enforcement,
183 | maxflow_enforcement_level=maxflow_enforcement_level,
184 | flowgraph_separation_enforcement=flowgraph_separation_enforcement)
185 |
186 | router.route(args.filein, args.fileout)
187 |
--------------------------------------------------------------------------------
/pycircuit/library/old/sifive/fe310g000.py:
--------------------------------------------------------------------------------
1 | from pycircuit.device import *
2 | from pycircuit.footprint import *
3 | from pycircuit.circuit import *
4 | from pycircuit.library import *
5 |
6 |
7 | Device('FE310-G000', pins=[
8 | Pwr('GND', descr='0V Ground input'),
9 | PwrIn('VDD', descr='+1.8V Core voltage supply input'),
10 | PwrIn('IVDD', descr='+3.3V I/O voltage supply input'),
11 | PwrIn('AON_VDD', descr='+1.8V Always-On core voltage supply input'),
12 | PwrIn('AON_IVDD', descr='+1.8V Always-On I/O voltage supply input'),
13 | PwrIn('PLL_AVDD', descr='+1.8V PLL Supply input'),
14 | Pwr('PLL_AVSS', descr='''PLL VSS input. Connect through a capacitor to
15 | PLL_AVDD, not to GND'''),
16 | PwrIn('OTP_AIVDD', descr='+3.3V OTP Supply Input'),
17 |
18 | Pin('XTAL_XI', descr='16MHz Crystal Oscillator Input'),
19 | Pin('XTAL_XO', descr='16MHz Crystal Oscillator Output'),
20 |
21 | Pin('JTAG_TCK', Fun('JTAG:S', 'TCK')),
22 | Pin('JTAG_TDO', Fun('JTAG:S', 'TDO')),
23 | Pin('JTAG_TMS', Fun('JTAG:S', 'TMS')),
24 | Pin('JTAG_TDI', Fun('JTAG:S', 'TDI')),
25 |
26 | Pin('QSPI_DQ_3', Fun('QSPI:M', 'DQ3')),
27 | Pin('QSPI_DQ_2', Fun('QSPI:M', 'DQ2')),
28 | Pin('QSPI_DQ_1', Fun('QSPI:M', 'DQ1')),
29 | Pin('QSPI_DQ_0', Fun('QSPI:M', 'DQ0')),
30 | Pin('QSPI_CS', Fun('QSPI:M', 'SS0')),
31 | Pin('QSPI_SCK', Fun('QSPI:M', 'SCLK')),
32 |
33 | Pin('GPIO_0', 'GPIO', Fun('PWM0', 'PWM', '0')),
34 | Pin('GPIO_1', 'GPIO', Fun('PWM0', 'PWM', '1')),
35 | Pin('GPIO_2', 'GPIO', Fun('PWM0', 'PWM', '2'), Fun('SPI1', 'SPI:M', 'SS0')),
36 | Pin('GPIO_3', 'GPIO', Fun('PWM0', 'PWM', '3'), Fun('SPI1', 'SPI:M', 'MOSI')),
37 | Pin('GPIO_4', 'GPIO', Fun('SPI1', 'SPI:M', 'MISO')),
38 | Pin('GPIO_5', 'GPIO', Fun('SPI1', 'SPI:M', 'SCLK')),
39 | Pin('GPIO_9', 'GPIO', Fun('SPI1', 'SPI:M', 'SS1')),
40 | Pin('GPIO_10', 'GPIO', Fun('PWM2', 'PWM', '0'), Fun('SPI1', 'SPI:M', 'SS2')),
41 | Pin('GPIO_11', 'GPIO', Fun('PWM2', 'PWM', '1')),
42 | Pin('GPIO_12', 'GPIO', Fun('PWM2', 'PWM', '2')),
43 | Pin('GPIO_13', 'GPIO', Fun('PWM2', 'PWM', '3')),
44 | Pin('GPIO_16', 'GPIO', Fun('UART0', 'UART', 'RX')),
45 | Pin('GPIO_17', 'GPIO', Fun('UART0', 'UART', 'TX')),
46 | Pin('GPIO_18', 'GPIO'),
47 | Pin('GPIO_19', 'GPIO', Fun('PWM1', 'PWM', '1')),
48 | Pin('GPIO_20', 'GPIO', Fun('PWM1', 'PWM', '0')),
49 | Pin('GPIO_21', 'GPIO', Fun('PWM1', 'PWM', '2')),
50 | Pin('GPIO_22', 'GPIO', Fun('PWM1', 'PWM', '3')),
51 | Pin('GPIO_23', 'GPIO'),
52 |
53 | Out('AON_PMU_OUT_1', # domain='aon',
54 | descr='Programmable SLEEP control'),
55 |
56 | Out('AON_PMU_OUT_0', # domain='aon'
57 | descr='Programmable SLEEP control'),
58 |
59 | In('AON_PMU_DWAKEUP_N', # domain='aon',
60 | descr='Digital Wake-from-sleep. Active low.'),
61 |
62 | In('AON_ERST_N', # domain='aon',
63 | descr='External System Reset. Active low.'),
64 |
65 | In('AON_PSD_LFALTCLK', # domain='aon',
66 | descr='Optional 32kHz Clock input'),
67 |
68 | In('AON_PSD_LFCLKSEL', # domain='aon',
69 | descr='''32kHz clock source selector. When
70 | driven low, AON_PSD_LFALTCLK input is used as the 32kHz low-frequency
71 | clock source. When left unconnected or driven high, the internal LFROSC
72 | source is used.''')
73 | ])
74 |
75 |
76 | Footprint('FE310-G000', 'FE310-G000', 'QFN48',
77 | Map(1, 'QSPI_DQ_3'),
78 | Map(2, 'QSPI_DQ_2'),
79 | Map(3, 'QSPI_DQ_1'),
80 | Map(4, 'QSPI_DQ_0'),
81 | Map(5, 'QSPI_CS'),
82 | Map(6, 'VDD'),
83 | Map(7, 'PLL_AVDD'),
84 | Map(8, 'PLL_AVSS'),
85 | Map(9, 'XTAL_XI'),
86 | Map(10, 'XTAL_XO'),
87 | Map(11, 'IVDD'),
88 | Map(12, 'OTP_AIVDD'),
89 | Map(13, 'JTAG_TCK'),
90 | Map(14, 'JTAG_TDO'),
91 | Map(15, 'JTAG_TMS'),
92 | Map(16, 'JTAG_TDI'),
93 | Map(17, 'AON_PMU_OUT_1'),
94 | Map(18, 'AON_PMU_OUT_0'),
95 | Map(19, 'AON_IVDD'),
96 | Map(20, 'AON_PSD_LFALTCLK'),
97 | Map(21, 'AON_PSD_LFCLKSEL'),
98 | Map(22, 'AON_PMU_DWAKEUP_N'),
99 | Map(23, 'AON_VDD'),
100 | Map(24, 'AON_ERST_N'),
101 | Map(25, 'GPIO_0'),
102 | Map(26, 'GPIO_1'),
103 | Map(27, 'GPIO_2'),
104 | Map(28, 'GPIO_3'),
105 | Map(29, 'GPIO_4'),
106 | Map(30, 'VDD'),
107 | Map(31, 'GPIO_5'),
108 | Map(32, 'IVDD'),
109 | Map(33, 'GPIO_9'),
110 | Map(34, 'GPIO_10'),
111 | Map(35, 'GPIO_11'),
112 | Map(36, 'GPIO_12'),
113 | Map(37, 'GPIO_13'),
114 | Map(38, 'GPIO_16'),
115 | Map(39, 'GPIO_17'),
116 | Map(40, 'GPIO_18'),
117 | Map(41, 'GPIO_19'),
118 | Map(42, 'GPIO_20'),
119 | Map(43, 'GPIO_21'),
120 | Map(44, 'GPIO_22'),
121 | Map(45, 'GPIO_23'),
122 | Map(46, 'VDD'),
123 | Map(47, 'IVDD'),
124 | Map(48, 'QSPI_SCK'),
125 | Map(49, 'GND'))
126 |
127 |
128 | @circuit('AON-BTN')
129 | def aon_btn(name, debounce=False):
130 | Sub(name + '_BTN', button())
131 | Node(name + '_D', 'D')
132 | Node(name + '_R', 'R', '100K')
133 |
134 | Net('VDD_1V8') + Ref(name + '_R')['~']
135 | Net('IN') + Ref(name + '_D')['A'] + Ref(name + '_R')['~']
136 | Net(name) + Ref(name + '_D')['K'] + Ref(name + '_BTN')['IN']
137 | Net('VDD_3V3') + Ref(name + '_BTN')['VDD']
138 | Net('GND') + Ref(name + '_BTN')['GND']
139 |
140 | if debounce:
141 | Node(name + '_C', 'C', '10nF')
142 | # Shorts RESET_R on power glitch to instantaneously
143 | # discharge RESET_C
144 | Node(name + '_C_D', 'D')
145 | Net('VDD_1V8') + Ref(name + '_C_D')['K']
146 | Net('IN') + Ref(name + '_C')['~'] + Ref(name + '_C_D')['A']
147 | Net('GND') + Ref(name + '_C')['~']
148 |
149 |
150 | @circuit('FE310-G000')
151 | def fe310g000(qspi_flash=True, lfaltclk=True):
152 | '''Returns a circuit for a FE310-G000 Microcontroller.'''
153 |
154 | Node('U', 'FE310-G000')
155 |
156 | Net('VDD_1V8') + Ref('U')['VDD', 'AON_VDD', 'AON_IVDD']
157 | Net('VDD_3V3') + Ref('U')['IVDD', 'OTP_AIVDD']
158 | Net('GND') + Ref('U')['GND']
159 |
160 | # VDD decoupling caps
161 | Sub('VDD_C', decoupling_capacitors('10uF', '0.1uF', '0.1uF'))
162 | Net('VDD_1V8') + Ref('VDD_C')['VDD']
163 | Net('GND') + Ref('VDD_C')['VSS']
164 |
165 | # AON VDD decoupling caps
166 | Sub('AON_C', decoupling_capacitors('0.1uF'))
167 | Net('VDD_1V8') + Ref('AON_C')['VDD']
168 | Net('GND') + Ref('AON_C')['VSS']
169 |
170 | # IVDD decoupling caps
171 | Sub('IVDD_C', decoupling_capacitors('0.1uF', '0.1uF', '0.1uF'))
172 | Net('VDD_3V3') + Ref('IVDD_C')['VDD']
173 | Net('GND') + Ref('IVDD_C')['VSS']
174 |
175 | # PLL_AVDD decoupling caps
176 | Sub('PLL_C', decoupling_capacitors('0.1uF', '0.1uF'))
177 | Net('PLL_AVDD') + Ref('U')['PLL_AVDD'] + Ref('PLL_C')['VDD']
178 | Net('PLL_AVSS') + Ref('U')['PLL_AVSS'] + Ref('PLL_C')['VSS']
179 |
180 | # PLL_AVDD
181 | Node('PLL_R', 'R', '100')
182 | Net('VDD_1V8') + Ref('PLL_R')['~']
183 | Net('PLL_AVDD') + Ref('PLL_R')['~']
184 |
185 | # Crystal oscillator
186 | Sub('XTAL', pierce_oscillator('16MHz', '12pF'))
187 | Nets('XTAL_XI', 'XTAL_XO') + Ref('U')['XTAL_XI', 'XTAL_XO'] + \
188 | Ref('XTAL')['XTAL_XI', 'XTAL_XO']
189 | Net('GND') + Ref('XTAL')['GND']
190 |
191 | if qspi_flash:
192 | Node('FLASH', 'QSPI:S')
193 | Sub('FLASH_C', decoupling_capacitors('0.1uF'))
194 | Node('SS_PULLUP', 'R', '100K')
195 |
196 | for net in ['DQ0', 'DQ1', 'DQ2', 'DQ3', 'SCLK']:
197 | Net(net) + Ref('U')['QSPI:M'][net] + Ref('FLASH')['QSPI:S'][net]
198 | Net('SS') + Ref('U')['QSPI:M']['SS0'] + Ref('FLASH')['QSPI:S']['SS']
199 |
200 | Net('VDD_3V3') + Ref('SS_PULLUP')['~']
201 | Ref('SS_PULLUP')['~'] + Ref('FLASH')['SS']
202 | Net('VDD_3V3') + Refs('FLASH', 'FLASH_C')['VDD']
203 | Net('GND') + Ref('FLASH')['GND'] + Ref('FLASH_C')['VSS']
204 |
205 | if lfaltclk:
206 | Node('LFALTCLK', 'CLK')
207 | Sub('LFALTCLK_C', decoupling_capacitors('0.1uF'))
208 | Net() + Ref('U')['AON_PSD_LFALTCLK'] + Ref('LFALTCLK')['CLK']
209 | Net('VDD_1V8') + Refs('LFALTCLK', 'LFALTCLK_C')['VDD']
210 | Net('GND') + Ref('LFALTCLK')['GND'] + Ref('LFALTCLK_C')['VSS']
211 |
212 | # Reset and Wake BTN
213 | Sub('RESET_BLK', aon_btn('RESET', debounce=True))
214 | Net('AON_ERST_N') + Ref('U')['AON_ERST_N'] + Ref('RESET_BLK')['IN']
215 |
216 | Sub('WAKE_BLK', aon_btn('WAKE'))
217 | Net('AON_PMU_DWAKEUP_N') + \
218 | Ref('U')['AON_PMU_DWAKEUP_N'] + Ref('WAKE_BLK')['IN']
219 |
220 | Net('VDD_3V3') + Refs('RESET_BLK', 'WAKE_BLK')['VDD_3V3']
221 | Net('VDD_1V8') + Refs('RESET_BLK', 'WAKE_BLK')['VDD_1V8']
222 | Net('GND') + Refs('RESET_BLK', 'WAKE_BLK')['GND']
223 |
224 | # PMU
225 | Net('PMU_OUT_0') + Ref('U')['AON_PMU_OUT_0']
226 | Net('PMU_OUT_1') + Ref('U')['AON_PMU_OUT_1']
227 |
--------------------------------------------------------------------------------
/pycircuit/formats/svg.py:
--------------------------------------------------------------------------------
1 | import xml.etree.cElementTree as xml
2 | from pycircuit.formats import extends
3 | from pycircuit.layers import RoutingLayer, PlacementLayer
4 | from pycircuit.package import Package
5 | from pycircuit.outline import *
6 | from pycircuit.pcb import Pcb
7 |
8 |
9 | class SvgElement(object):
10 | tag = ''
11 |
12 | def __init__(self, attrs={}):
13 | self.element = xml.Element(self.tag)
14 | self.set_attrs(attrs)
15 | self.children = []
16 |
17 | def set_attrs(self, attrs):
18 | for k, v in attrs.items():
19 | if not isinstance(v, str):
20 | v = str(v)
21 | self.set_attr(k, v)
22 | return self
23 |
24 | def set_attr(self, key, value):
25 | self.element.attrib[key] = value
26 |
27 | def get_attr(self, key):
28 | return self.element.attrib[key]
29 |
30 | def add_class(self, klass):
31 | klasses = self.get_attr('class')
32 | klasses += " " + klass
33 | self.set_attr('class', klasses)
34 |
35 | def append(self, *children):
36 | for child in children:
37 | self.element.append(child.element)
38 | self.children.append(child)
39 | return self
40 |
41 | def transform(self, transform):
42 | if 'transform' in self.element.attrib:
43 | transform = self.element.attrib['transform'] + transform
44 |
45 | self.element.attrib['transform'] = transform
46 | return self
47 |
48 | def translate(self, x, y):
49 | return self.transform('translate(%f %f)' % (x, y))
50 |
51 | def rotate(self, angle):
52 | return self.transform('rotate(%f)' % angle)
53 |
54 | def scale(self, x, y):
55 | return self.transform('scale(%f %f)' % (x, y))
56 |
57 | def find(self, tag):
58 | def find_rec(node, tag):
59 | for child in node:
60 | if child.tag == tag:
61 | yield child
62 | for found in find_rec(child, tag):
63 | yield found
64 | return find_rec(self.element, tag)
65 |
66 |
67 | class SvgRoot(SvgElement):
68 | tag = 'svg'
69 |
70 | def __init__(self, viewbox, attrs={}, style=''):
71 | attrs['xmlns'] = 'http://www.w3.org/2000/svg'
72 | attrs['width'] = '100%'
73 | attrs['height'] = '100%'
74 | super().__init__(attrs)
75 | self.viewbox(*viewbox)
76 |
77 | style_node = xml.Element('style')
78 | style_node.text = style
79 | self.element.append(style_node)
80 |
81 | def viewbox(self, left, top, right, bottom):
82 | return self.set_attrs({
83 | 'viewBox': '%f %f %f %f' % (left, top, right - left, bottom - top)
84 | })
85 |
86 | def save(self, filename):
87 | tree = xml.ElementTree(self.element)
88 | tree.write(filename)
89 |
90 |
91 | class SvgGroup(SvgElement):
92 | tag = 'g'
93 |
94 | def __init__(self, klass, *children):
95 | super().__init__({'class': klass})
96 | self.append(*children)
97 |
98 |
99 | class SvgLine(SvgElement):
100 | tag = 'line'
101 |
102 | def __init__(self, start, end, attrs={}):
103 | attrs['x1'] = start[0]
104 | attrs['y1'] = start[1]
105 | attrs['x2'] = end[0]
106 | attrs['y2'] = end[1]
107 | super().__init__(attrs)
108 |
109 |
110 | class SvgCircle(SvgElement):
111 | tag = 'circle'
112 |
113 | def __init__(self, radius, attrs={}):
114 | if not 'cx' in attrs:
115 | attrs['cx'] = 0
116 | if not 'cy' in attrs:
117 | attrs['cy'] = 0
118 | attrs['r'] = radius
119 | super().__init__(attrs)
120 |
121 |
122 | class SvgRect(SvgElement):
123 | tag = 'rect'
124 |
125 | def __init__(self, size, attrs={}):
126 | attrs['x'] = -size[0] / 2
127 | attrs['y'] = -size[1] / 2
128 | attrs['width'] = size[0]
129 | attrs['height'] = size[1]
130 | super().__init__(attrs)
131 |
132 |
133 | class SvgText(SvgElement):
134 | tag = 'text'
135 |
136 | def __init__(self, text, attrs={}):
137 | if not 'x' in attrs:
138 | attrs['x'] = 0
139 | if not 'y' in attrs:
140 | attrs['y'] = 0
141 | if not 'text-anchor' in attrs:
142 | attrs['text-anchor'] = 'middle'
143 | super().__init__(attrs)
144 | self.element.text = text
145 |
146 | def set_text(self, text):
147 | self.element.text = text
148 | return self
149 |
150 |
151 | class SvgPath(SvgElement):
152 | tag = 'path'
153 |
154 | def set_coords(self, coords):
155 | coords = ['{0},{1}'.format(*c) for c in coords]
156 |
157 | path = 'M {0} L {1} z'.format(coords[0], ' L '.join(coords[1:]))
158 | return self.set_attrs({'d': path})
159 |
160 |
161 | @extends(Package)
162 | def to_svg(self):
163 | # Courtyard
164 | svg_crtyd = SvgGroup('crtyd', SvgPath().set_coords(self.courtyard.coords))
165 |
166 | # Pads
167 | svg_pads = SvgGroup('pads')
168 | svg_pad_labels = SvgGroup('pad-labels')
169 |
170 | for pad in self.pads:
171 | if pad.shape == 'rect':
172 | svg_pad = SvgRect(pad.size)
173 | elif pad.shape == 'circle':
174 | svg_pad = SvgCircle(pad.size[0] / 2)
175 | else:
176 | raise Exception('Unknown pad shape')
177 |
178 | svg_pad.set_attrs({'class': 'pad'})
179 | svg_pad.translate(*pad.location[0:2]).rotate(-pad.angle)
180 | svg_pads.append(svg_pad)
181 |
182 | svg_label = SvgText(pad.name, {'dy': '.4em'})
183 | svg_label.set_attrs({'class': 'pad-label'})
184 | svg_label.translate(*pad.location[0:2])
185 | svg_pad_labels.append(svg_label)
186 |
187 | # Reference
188 | text_radius = self.size()[1] / 2
189 | svg_ref = SvgText(self.name, {'class': 'ref', 'y': -text_radius - 0.2})
190 |
191 | return SvgRoot(self.courtyard.bounds) \
192 | .append(svg_crtyd, svg_pads, svg_pad_labels, svg_ref)
193 |
194 |
195 | @extends(PlacementLayer)
196 | def to_svg(self):
197 | def transform(elem, x, y, angle, no_flip=False):
198 | elem.translate(x, y).rotate(-angle)
199 | if not no_flip and self.flip:
200 | elem.scale(1, -1)
201 | return elem
202 |
203 | pads_layer = SvgGroup('pads_layer')
204 | pad_labels_layer = SvgGroup('pad_labels_layer')
205 | crtyd_layer = SvgGroup('crtyd_layer')
206 | ref_layer = SvgGroup('ref_layer')
207 |
208 | for inst in self.insts:
209 | package = inst.device.package.to_svg()
210 | crtyd, pads, pad_labels, ref = package.children
211 | ref.set_text(inst.name)
212 |
213 | x, y, angle = inst.attributes.x, inst.attributes.y, inst.attributes.angle
214 |
215 | pads_layer.append(transform(pads, x, y, angle))
216 | crtyd_layer.append(transform(crtyd, x, y, angle))
217 | ref_layer.append(transform(ref, x, y, angle, no_flip=True))
218 | pad_labels_layer.append(
219 | transform(pad_labels, x, y, angle, no_flip=True))
220 |
221 | layer = SvgGroup('layer')
222 | layer.add_class(self.layer.name)
223 | layer.append(pads_layer)
224 | layer.append(pad_labels_layer)
225 | layer.append(crtyd_layer)
226 | layer.append(ref_layer)
227 |
228 | return layer
229 |
230 |
231 | @extends(RoutingLayer)
232 | def to_svg(self):
233 | layer = SvgGroup('layer')
234 | layer.add_class(self.layer.name)
235 |
236 | for via in self.vias:
237 | svia = SvgGroup('via',
238 | SvgCircle(via.diameter / 2, {
239 | 'class': 'dia'
240 | }),
241 | SvgCircle(via.drill / 2, {
242 | 'class': 'drill'
243 | })
244 | ).translate(via.coord[0], via.coord[1])
245 | layer.append(svia)
246 |
247 | for seg in self.segments:
248 | sseg = SvgLine(seg.start, seg.end, {
249 | 'class': 'seg',
250 | 'stroke-width': seg.width
251 | })
252 | layer.append(sseg)
253 |
254 | return layer
255 |
256 |
257 | @extends(Outline)
258 | def to_svg(self):
259 | layer = SvgGroup('outline')
260 | layer.append(SvgPath().set_coords(self.polygon.exterior.coords))
261 | for f in self.features:
262 | if isinstance(f, Hole):
263 | f = SvgCircle(f.drill_size / 2, {
264 | 'cx': f.position[0],
265 | 'cy': f.position[1]
266 | })
267 | layer.append(f)
268 | return layer
269 |
270 |
271 | @extends(Pcb)
272 | def to_svg(self, path):
273 | bounds = self.outline.polygon.exterior.bounds
274 | with open('../../viewer/css/pcb.svg.css') as f:
275 | style = f.read()
276 | svg = SvgRoot(bounds, style=style)
277 |
278 | top = self.attributes.layers.placement_layers[0]
279 | bottom = self.attributes.layers.placement_layers[-1]
280 |
281 | svg.append(bottom.to_svg())
282 |
283 | for layer in reversed(self.attributes.layers.routing_layers):
284 | svg.append(layer.to_svg())
285 |
286 | svg.append(top.to_svg())
287 |
288 | svg.append(self.outline.to_svg())
289 |
290 | svg.save(path)
291 |
--------------------------------------------------------------------------------
/pycircuit/library/old/lattice/lattice.py:
--------------------------------------------------------------------------------
1 | from pycircuit.device import *
2 | from pycircuit.footprint import *
3 |
4 |
5 | # iCE40 FPGA Family
6 | Device('HX1K',
7 | # IO Left
8 | Pin('IOL_1A', Bus('DPIO1', 'DPIO', '-')),
9 | Pin('IOL_1B', Bus('DPIO1', 'DPIO', '+')),
10 | Pin('IOL_2A', Bus('DPIO2', 'DPIO', '-')),
11 | Pin('IOL_2B', Bus('DPIO2', 'DPIO', '+')),
12 | Pin('IOL_3A', Bus('DPIO3', 'DPIO', '-')),
13 | Pin('IOL_3B', Bus('DPIO3', 'DPIO', '+')),
14 | Pin('IOL_4A', Bus('DPIO4', 'DPIO', '-')),
15 | Pin('IOL_4B', Bus('DPIO4', 'DPIO', '+')),
16 | Pin('IOL_5A', Bus('DPIO5', 'DPIO', '-')),
17 | Pin('IOL_5B', Bus('DPIO5', 'DPIO', '+')),
18 | Pin('IOL_6A', Bus('DPIO6', 'DPIO', '-')),
19 | Pin('IOL_6B_GBIN7', Bus('DPIO6', 'DPIO', '+'), Bus('GBIN', '7')),
20 | Pin('IOL_7A_GBIN6', Bus('DPIO7', 'DPIO', '-'), Bus('GBIN', '6')),
21 | Pin('IOL_7B', Bus('DPIO7', 'DPIO', '+')),
22 | Pin('IOL_8A', Bus('DPIO8', 'DPIO', '-')),
23 | Pin('IOL_8B', Bus('DPIO8', 'DPIO', '+')),
24 | Pin('IOL_9A', Bus('DPIO9', 'DPIO', '-')),
25 | Pin('IOL_9B', Bus('DPIO9', 'DPIO', '+')),
26 | Pin('IOL_10A', Bus('DPIO10', 'DPIO', '-')),
27 | Pin('IOL_10B', Bus('DPIO10', 'DPIO', '+')),
28 | Pin('IOL_11A', Bus('DPIO11', 'DPIO', '-')),
29 | Pin('IOL_11B', Bus('DPIO11', 'DPIO', '+')),
30 | Pin('IOL_12A', Bus('DPIO12', 'DPIO', '-')),
31 | Pin('IOL_12B', Bus('DPIO12', 'DPIO', '+')),
32 | # PLL_VCC
33 | Pin('GNDPLL'),
34 | Pin('VCCPLL'), # 1V2
35 |
36 | # IO Bottom
37 | Pin('IOB_24', 'PIO'),
38 | Pin('IOB_25', 'PIO'),
39 | Pin('IOB_26', 'PIO'),
40 | Pin('IOB_27', 'PIO'),
41 | Pin('IOB_28', 'PIO'),
42 | Pin('IOB_29', 'PIO'),
43 | Pin('IOB_30', 'PIO'),
44 | Pin('IOB_31', 'PIO'),
45 | Pin('IOB_32', 'PIO'),
46 | Pin('IOB_33', 'PIO'),
47 | Pin('IOB_35_GBIN5', 'PIO', Bus('GBIN', '5')),
48 | Pin('IOB_36_GBIN4', 'PIO', Bus('GBIN', '4')),
49 | Pin('IOB_34', 'PIO'),
50 | Pin('IOB_37', 'PIO'),
51 | Pin('IOB_38', 'PIO'),
52 | Pin('IOB_39', 'PIO'),
53 | Pin('IOB_40', 'PIO'),
54 | Pin('IOB_41', 'PIO'),
55 | Pin('IOB_42_CBSEL0', 'PIO', Bus('CBSEL', '0')),
56 | Pin('IOB_43_CBSEL1', 'PIO', Bus('CBSEL', '1')),
57 | # CONFIG
58 | Pin('CDONE'),
59 | Pin('CRESET_B'),
60 | # SPI
61 | Pin('IOB_44_SDO', 'PIO', Bus('SPI', 'MISO')),
62 | Pin('IOB_45_SDI', 'PIO', Bus('SPI', 'MOSI')),
63 | Pin('IOB_46_SCK', 'PIO', Bus('SPI', 'SCK')),
64 | Pin('IOB_47_SS', 'PIO', Bus('SPI', 'SS0')),
65 | Pin('VCC_SPI'), # 3V3
66 |
67 | # IO Right
68 | Pin('IOR_48', 'PIO'),
69 | Pin('IOR_49', 'PIO'),
70 | Pin('IOR_50', 'PIO'),
71 | Pin('IOR_51', 'PIO'),
72 | Pin('IOR_52', 'PIO'),
73 | Pin('IOR_53', 'PIO'),
74 | Pin('IOR_54', 'PIO'),
75 | Pin('IOR_55', 'PIO'),
76 | Pin('IOR_56', 'PIO'),
77 | Pin('IOR_57', 'PIO'),
78 | Pin('IOR_58', 'PIO'),
79 | Pin('IOR_59', 'PIO'),
80 | Pin('IOR_60_GBIN3', 'PIO', Bus('GBIN', '3')),
81 | Pin('IOR_61_GBIN2', 'PIO', Bus('GBIN', '2')),
82 | Pin('IOR_62', 'PIO'),
83 | Pin('IOR_63', 'PIO'),
84 | Pin('IOR_64', 'PIO'),
85 | Pin('IOR_65', 'PIO'),
86 | Pin('IOR_66', 'PIO'),
87 | Pin('IOR_67', 'PIO'),
88 | Pin('IOR_68', 'PIO'),
89 | Pin('IOR_69', 'PIO'),
90 | Pin('IOR_70', 'PIO'),
91 | Pin('IOR_71', 'PIO'),
92 | Pin('IOR_72', 'PIO'),
93 | Pin('VPP_2V5'), # 2V5
94 | Pin('VPP_FAST'),
95 |
96 | # IO Top
97 | Pin('IOT_73', 'PIO'),
98 | Pin('IOT_74', 'PIO'),
99 | Pin('IOT_75', 'PIO'),
100 | Pin('IOT_76', 'PIO'),
101 | Pin('IOT_77', 'PIO'),
102 | Pin('IOT_78', 'PIO'),
103 | Pin('IOT_79', 'PIO'),
104 | Pin('IOT_80', 'PIO'),
105 | Pin('IOT_81', 'PIO'),
106 | Pin('IOT_82', 'PIO'),
107 | Pin('IOT_83', 'PIO'),
108 | Pin('IOT_84_GBIN1', 'PIO', Bus('GBIN', '1')),
109 | Pin('IOT_85_GBIN0', 'PIO', Bus('GBIN', '0')),
110 | Pin('IOT_86', 'PIO'),
111 | Pin('IOT_87', 'PIO'),
112 | Pin('IOT_88', 'PIO'),
113 | Pin('IOT_89', 'PIO'),
114 | Pin('IOT_90', 'PIO'),
115 | Pin('IOT_91', 'PIO'),
116 | Pin('IOT_92', 'PIO'),
117 | Pin('IOT_93', 'PIO'),
118 | Pin('IOT_94', 'PIO'),
119 | Pin('IOT_95', 'PIO'),
120 | Pin('IOT_96', 'PIO'),
121 | Pin('GND'),
122 | Pin('VCC'), # 1V2
123 | Pin('VCCIO_0'), # 3V3
124 | Pin('VCCIO_1'), # 3V3
125 | Pin('VCCIO_2'), # 3V3
126 | Pin('VCCIO_3')) # 3V3
127 |
128 |
129 | Footprint('HX1K-TQFP144', 'HX1K', 'TQFP144',
130 | Map(1, 'IOL_1A'),
131 | Map(2, 'IOL_1B'),
132 | Map(3, 'IOL_2A'),
133 | Map(4, 'IOL_2B'),
134 | Map(5, 'GND'),
135 | Map(6, 'VCCIO_3'),
136 | Map(7, 'IOL_3A'),
137 | Map(8, 'IOL_3B'),
138 | Map(9, 'IOL_4A'),
139 | Map(10, 'IOL_4B'),
140 | Map(11, 'IOL_5A'),
141 | Map(12, 'IOL_5B'),
142 | Map(13, 'GND'),
143 | Map(14, 'GND'),
144 | Map(15, None),
145 | Map(16, None),
146 | Map(17, None),
147 | Map(18, None),
148 | Map(19, 'IOL_6A'),
149 | Map(20, 'IOL_6B_GBIN7'),
150 | Map(21, 'IOL_7A_GBIN6'),
151 | Map(22, 'IOL_7B'),
152 | Map(23, 'IOL_8A'),
153 | Map(24, 'IOL_8B'),
154 | Map(25, 'IOL_9A'),
155 | Map(26, 'IOL_9B'),
156 | Map(27, 'VCC'),
157 | Map(28, 'IOL_10A'),
158 | Map(29, 'IOL_10B'),
159 | Map(30, 'VCCIO_3'),
160 | Map(31, 'IOL_11A'),
161 | Map(32, 'IOL_11B'),
162 | Map(33, 'IOL_12A'),
163 | Map(34, 'IOL_12B'),
164 | Map(35, 'GNDPLL'),
165 | Map(36, 'VCCPLL'),
166 | Map(37, 'IOB_24'),
167 | Map(38, 'IOB_25'),
168 | Map(39, 'IOB_26'),
169 | Map(40, None),
170 | Map(41, 'IOB_27'),
171 | Map(42, 'IOB_28'),
172 | Map(43, 'IOB_29'),
173 | Map(44, 'IOB_30'),
174 | Map(45, 'IOB_31'),
175 | Map(46, 'VCCIO_3'),
176 | Map(47, 'IOB_32'),
177 | Map(48, 'IOB_33'),
178 | Map(49, 'IOB_35_GBIN5'),
179 | Map(50, 'IOB_36_GBIN4'),
180 | Map(51, 'VCC'),
181 | Map(52, 'IOB_34'),
182 | Map(53, None),
183 | Map(54, None),
184 | Map(55, None),
185 | Map(56, 'IOB_37'),
186 | Map(57, 'VCCIO_2'),
187 | Map(58, 'IOB_38'),
188 | Map(59, 'GND'),
189 | Map(60, 'IOB_39'),
190 | Map(61, 'IOB_40'),
191 | Map(62, 'IOB_41'),
192 | Map(63, 'IOB_42_CBSEL0'),
193 | Map(64, 'IOB_43_CBSEL1'),
194 | Map(65, 'CDONE'),
195 | Map(66, 'CRESET_B'),
196 | Map(67, 'IOB_44_SDO'),
197 | Map(68, 'IOB_45_SDI'),
198 | Map(69, 'GND'),
199 | Map(70, 'IOB_46_SCK'),
200 | Map(71, 'IOB_47_SS'),
201 | Map(72, 'VCC_SPI'),
202 | Map(73, 'IOR_48'),
203 | Map(74, 'IOR_49'),
204 | Map(75, 'IOR_50'),
205 | Map(76, 'IOR_51'),
206 | Map(77, None),
207 | Map(78, 'IOR_52'),
208 | Map(79, 'IOR_53'),
209 | Map(80, 'IOR_54'),
210 | Map(81, 'IOR_55'),
211 | Map(82, None),
212 | Map(83, None),
213 | Map(84, None),
214 | Map(85, None),
215 | Map(86, 'GND'),
216 | Map(87, 'IOR_56'),
217 | Map(88, 'IOR_57'),
218 | Map(89, 'VCCIO_1'),
219 | Map(90, 'IOR_58'),
220 | Map(91, 'IOR_59'),
221 | Map(92, 'VCC'),
222 | Map(93, 'IOR_60_GBIN3'),
223 | Map(94, 'IOR_61_GBIN2'),
224 | Map(95, 'IOR_62'),
225 | Map(96, 'IOR_63'),
226 | Map(97, 'IOR_64'),
227 | Map(98, 'IOR_65'),
228 | Map(99, 'IOR_66'),
229 | Map(100, 'VCCIO_1'),
230 | Map(101, 'IOR_67'),
231 | Map(102, 'IOR_68'),
232 | Map(103, 'GND'),
233 | Map(104, 'IOR_69'),
234 | Map(105, 'IOR_70'),
235 | Map(106, 'IOR_71'),
236 | Map(107, 'IOR_72'),
237 | Map(108, 'VPP_2V5'),
238 | Map(109, 'VPP_FAST'),
239 | Map(110, None),
240 | Map(111, 'VCC'),
241 | Map(112, 'IOT_73'),
242 | Map(113, 'IOT_74'),
243 | Map(114, 'IOT_75'),
244 | Map(115, 'IOT_76'),
245 | Map(116, 'IOT_77'),
246 | Map(117, 'IOT_78'),
247 | Map(118, 'IOT_79'),
248 | Map(119, 'IOT_80'),
249 | Map(120, 'IOT_81'),
250 | Map(121, 'IOT_82'),
251 | Map(122, 'IOT_83'),
252 | Map(123, 'VCCIO_0'),
253 | Map(124, None),
254 | Map(125, None),
255 | Map(126, None),
256 | Map(127, None),
257 | Map(128, 'IOT_84_GBIN1'),
258 | Map(129, 'IOT_85_GBIN0'),
259 | Map(None, 'IOT_86'),
260 | Map(130, None),
261 | Map(131, None),
262 | Map(132, 'GND'),
263 | Map(133, 'VCCIO_0'),
264 | Map(134, 'IOT_87'),
265 | Map(135, 'IOT_88'),
266 | Map(136, 'IOT_89'),
267 | Map(137, 'IOT_90'),
268 | Map(138, 'IOT_91'),
269 | Map(139, 'IOT_92'),
270 | Map(140, 'GND'),
271 | Map(141, 'IOT_93'),
272 | Map(142, 'IOT_94'),
273 | Map(143, 'IOT_95'),
274 | Map(144, 'IOT_96'))
275 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "08aa94e0e4afd72a99e6bf94d89960f1018b9a274b3db03c1fd391fca1a6f173"
5 | },
6 | "host-environment-markers": {
7 | "implementation_name": "cpython",
8 | "implementation_version": "3.6.3",
9 | "os_name": "posix",
10 | "platform_machine": "x86_64",
11 | "platform_python_implementation": "CPython",
12 | "platform_release": "4.12.14",
13 | "platform_system": "Linux",
14 | "platform_version": "#1 SMP 1",
15 | "python_full_version": "3.6.3",
16 | "python_version": "3.6",
17 | "sys_platform": "linux"
18 | },
19 | "pipfile-spec": 6,
20 | "requires": {
21 | "python_version": "3.6"
22 | },
23 | "sources": [
24 | {
25 | "name": "pypi",
26 | "url": "https://pypi.python.org/simple",
27 | "verify_ssl": true
28 | }
29 | ]
30 | },
31 | "default": {
32 | "numpy": {
33 | "hashes": [
34 | "sha256:428cd3c0b197cf857671353d8c85833193921af9fafcc169a1f29c7185833d50",
35 | "sha256:a476e437d73e5754aa66e1e75840d0163119c3911b7361f4cd06985212a3c3fb",
36 | "sha256:289ff717138cd9aa133adcbd3c3e284458b9c8230db4d42b39083a3407370317",
37 | "sha256:c5eccb4bf96dbb2436c61bb3c2658139e779679b6ae0d04c5e268e6608b58053",
38 | "sha256:75471acf298d455b035226cc609a92aee42c4bb6aa71def85f77fa2c2b646b61",
39 | "sha256:5c54fb98ecf42da59ed93736d1c071842482b18657eb16ba6e466bd873e1b923",
40 | "sha256:9ddf384ac3aacb72e122a8207775cc29727cbd9c531ee1a4b95754f24f42f7f3",
41 | "sha256:781d3197da49c421a07f250750de70a52c42af08ca02a2f7bdb571c0625ae7eb",
42 | "sha256:93b26d6c06a22e64d56aaca32aaaffd27a4143db0ac2f21a048f0b571f2bfc55",
43 | "sha256:b2547f57d05ba59df4289493254f29f4c9082d255f1f97b7e286f40f453e33a1",
44 | "sha256:eef6af1c752eef538a96018ef9bdf8e37bbf28aab50a1436501a4aa47a6467df",
45 | "sha256:ff8a4b2c3ac831964f529a2da506c28d002562b230261ae5c16885f5f53d2e75",
46 | "sha256:194074058c22a4066e1b6a4ea432486ee468d24ab16f13630c1030409e6b8666",
47 | "sha256:4e13f1a848fde960dea33702770265837c72b796a6a3eaac7528cfe75ddefadd",
48 | "sha256:91101216d72749df63968d86611b549438fb18af2c63849c01f9a897516133c7",
49 | "sha256:97507349abb7d1f6b76b877258defe8720833881dc7e7fd052bac90c88587387",
50 | "sha256:1479b46b6040b5c689831496354c8859c456b152d37315673a0c18720b41223b",
51 | "sha256:98b1ac79c160e36093d7914244e40ee1e7164223e795aa2c71dcce367554e646",
52 | "sha256:24bbec9a199f938eab75de8390f410969bc33c218e5430fa1ae9401b00865255",
53 | "sha256:7880f412543e96548374a4bb1d75e4cdb8cad80f3a101ed0f8d0e0428f719c1c",
54 | "sha256:6112f152b76a28c450bbf665da11757078a724a90330112f5b7ea2d6b6cefd67",
55 | "sha256:7c5276763646480143d5f3a6c2acb2885460c765051a1baf4d5070f63d05010f",
56 | "sha256:3de643935b212307b420248018323a44ec51987a336d1d747c1322afc3c099fb"
57 | ],
58 | "version": "==1.14.0"
59 | },
60 | "pykicad": {
61 | "hashes": [
62 | "sha256:ded058f3af116845d8a0495f3a8ff4b6a7e707e534c5b71ee61b37fb23553923"
63 | ],
64 | "version": "==0.1.1"
65 | },
66 | "pyparsing": {
67 | "hashes": [
68 | "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010",
69 | "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
70 | "sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e",
71 | "sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07",
72 | "sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5",
73 | "sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18",
74 | "sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58"
75 | ],
76 | "version": "==2.2.0"
77 | },
78 | "shapely": {
79 | "hashes": [
80 | "sha256:13c82c9110e630039163bdcdb9969db4fadd145bcf633fc678fa762b01686e6d",
81 | "sha256:cf02668c8633a41782b5244e04d5d0689847ac535cdf2fea66e4cac7dd772895",
82 | "sha256:049e7732c58f8ee143899f9efbf714d8e7e3578443883c95614cfd4891d36502",
83 | "sha256:c1f4d74e4c4a5681e77fbee5a7b0599a91ca39a8e260f2839a9a764382994f7f",
84 | "sha256:41850cbfc79de117e3ccbc025b2eb7ee9170c29e65910003828e560ff076fc0d",
85 | "sha256:a0db70e3384f1227bc40e22e9d77db3a54e02b54b48cf6b0c22ddfc0b688542d",
86 | "sha256:592a7821c00ef4e0ae07e952366ad537313ccb7488665dd54b87e6f8eb31ef80",
87 | "sha256:2222eba7f461f4b3f406b569727561f300ecd57e31e01b49ffd327dc4cbda632",
88 | "sha256:e848bf533f0d6cbc336b4cf01dd1aa7873174ba7304a620baa7d663d1d0ce879",
89 | "sha256:30df7572d311514802df8dc0e229d1660bc4cbdcf027a8281e79c5fc2fcf02f2"
90 | ],
91 | "version": "==1.6.4.post1"
92 | }
93 | },
94 | "develop": {
95 | "attrs": {
96 | "hashes": [
97 | "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450",
98 | "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9"
99 | ],
100 | "version": "==17.4.0"
101 | },
102 | "autopep8": {
103 | "hashes": [
104 | "sha256:c7be71ab0cb2f50c9c22c82f0c9acaafc6f57492c3fbfee9790c415005c2b9a5"
105 | ],
106 | "version": "==1.3.4"
107 | },
108 | "certifi": {
109 | "hashes": [
110 | "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
111 | "sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d"
112 | ],
113 | "version": "==2018.1.18"
114 | },
115 | "chardet": {
116 | "hashes": [
117 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
118 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
119 | ],
120 | "version": "==3.0.4"
121 | },
122 | "idna": {
123 | "hashes": [
124 | "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
125 | "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
126 | ],
127 | "version": "==2.6"
128 | },
129 | "pkginfo": {
130 | "hashes": [
131 | "sha256:31a49103180ae1518b65d3f4ce09c784e2bc54e338197668b4fb7dc539521024",
132 | "sha256:bb1a6aeabfc898f5df124e7e00303a5b3ec9a489535f346bfbddb081af93f89e"
133 | ],
134 | "version": "==1.4.1"
135 | },
136 | "pluggy": {
137 | "hashes": [
138 | "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff"
139 | ],
140 | "version": "==0.6.0"
141 | },
142 | "py": {
143 | "hashes": [
144 | "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f",
145 | "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d"
146 | ],
147 | "version": "==1.5.2"
148 | },
149 | "pycodestyle": {
150 | "hashes": [
151 | "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9",
152 | "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766"
153 | ],
154 | "version": "==2.3.1"
155 | },
156 | "pytest": {
157 | "hashes": [
158 | "sha256:95fa025cd6deb5d937e04e368a00552332b58cae23f63b76c8c540ff1733ab6d",
159 | "sha256:6074ea3b9c999bd6d0df5fa9d12dd95ccd23550df2a582f5f5b848331d2e82ca"
160 | ],
161 | "version": "==3.4.0"
162 | },
163 | "requests": {
164 | "hashes": [
165 | "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
166 | "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
167 | ],
168 | "version": "==2.18.4"
169 | },
170 | "requests-toolbelt": {
171 | "hashes": [
172 | "sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237",
173 | "sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"
174 | ],
175 | "version": "==0.8.0"
176 | },
177 | "six": {
178 | "hashes": [
179 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb",
180 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9"
181 | ],
182 | "version": "==1.11.0"
183 | },
184 | "tqdm": {
185 | "hashes": [
186 | "sha256:4c041f8019f7be65b8028ddde9a836f7ccc51c4637f1ff2ba9b5813d38d19d5a",
187 | "sha256:df32e6f127dc0ccbc675eadb33f749abbcb8f174c5cb9ec49c0cdb73aa737377"
188 | ],
189 | "version": "==4.19.5"
190 | },
191 | "twine": {
192 | "hashes": [
193 | "sha256:d3ce5c480c22ccfb761cd358526e862b32546d2fe4bc93d46b5cf04ea3cc46ca",
194 | "sha256:caa45b7987fc96321258cd7668e3be2ff34064f5c66d2d975b641adca659c1ab"
195 | ],
196 | "version": "==1.9.1"
197 | },
198 | "urllib3": {
199 | "hashes": [
200 | "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
201 | "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
202 | ],
203 | "version": "==1.22"
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/pycircuit/pcb.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import shapely.ops as ops
3 | from shapely.geometry import Point, Polygon
4 | from pycircuit.circuit import Netlist
5 | from pycircuit.device import Device
6 | from pycircuit.outline import Outline, OutlineDesignRules
7 | from pycircuit.layers import Layers
8 | from pycircuit.traces import NetClass, TraceDesignRules, Segment, Via
9 |
10 |
11 | class Matrix(object):
12 | '''A collection of functions for generating transformation
13 | matrices with numpy and applying them to shapely geometry.'''
14 |
15 | @staticmethod
16 | def translation(x, y):
17 | '''Returns a translation matrix.'''
18 |
19 | return np.array([[1, 0, x],
20 | [0, 1, y],
21 | [0, 0, 1]])
22 |
23 | @staticmethod
24 | def rotation(angle):
25 | '''Returns a rotation matrix for a coordinate system with an inverted
26 | y-axis [oo, -oo]. The unit of angle is degrees.'''
27 |
28 | theta = (-angle / 180) * np.pi
29 | return np.array([[np.cos(theta), -np.sin(theta), 0],
30 | [np.sin(theta), np.cos(theta), 0],
31 | [0, 0, 1]])
32 |
33 | @staticmethod
34 | def flip():
35 | '''Returns a matrix that mirrors accross the y-axis.'''
36 |
37 | return np.array([[1, 0, 0],
38 | [0, -1, 0],
39 | [0, 0, 1]])
40 |
41 | @staticmethod
42 | def transform(geometry, matrix):
43 | '''Returns a new shapely geometry transformed with matrix.'''
44 |
45 | def apply_matrix(xs, ys):
46 | res_xs, res_ys = [], []
47 | for x, y in zip(xs, ys):
48 | vec = np.array([x, y, 1])
49 | res = matrix.dot(np.array([x, y, 1]))
50 | res_xs.append(res[0])
51 | res_ys.append(res[1])
52 | return (res_xs, res_ys)
53 |
54 | return ops.transform(apply_matrix, geometry)
55 |
56 | @staticmethod
57 | def inst_matrix(x, y, angle, flip):
58 | if flip:
59 | flip = Matrix.flip()
60 | else:
61 | flip = np.identity(3)
62 |
63 | return Matrix.translation(x, y).dot(Matrix.rotation(angle)).dot(flip)
64 |
65 |
66 | class AbsolutePad(object):
67 | def __init__(self, inst, pad, matrix):
68 | self.inst = inst
69 | self.pad = pad
70 | self.location = matrix.dot(pad.location)
71 | self.size = pad.size
72 |
73 | def __repr__(self):
74 | return 'pad %s %s' % (self.inst.name, self.pad.name)
75 |
76 |
77 | class NetAttributes(object):
78 | def __init__(self, net, net_class, pcb):
79 | self.net = net
80 | self.net.attributes = self
81 | self.pcb = pcb
82 |
83 | self.net_class = net_class
84 | self.segments = []
85 | self.vias = []
86 |
87 | def iter_pads(self):
88 | '''Iterator over all coordinates belonging to the net.'''
89 |
90 | for assign in self.net.assigns:
91 | for pad in assign.inst.attributes.pads_by_pin(assign.pin):
92 | yield pad
93 |
94 | def bounds(self):
95 | '''Returns a tuple (min_x, min_y, max_x, max_y) of all the coordinates
96 | connected by the net. Useful for placement algorithms.'''
97 |
98 | return asMultiPoint(list(self.pad_locations())).bounds
99 |
100 | def size(self):
101 | '''Returns a tuple (width, height) of the net. Useful for placement
102 | algorithms.'''
103 |
104 | min_x, min_y, max_x, max_y = self.bounds()
105 | return max_x - min_x, max_y - min_y
106 |
107 | def half_perimeter_length(self):
108 | '''Returns an estimation of the wire length from the boundary
109 | of it's coordinates. Useful for placement algorithms.'''
110 |
111 | return sum(self.size())
112 |
113 | def length(self):
114 | '''Returns the geometric length of the net by summing
115 | the length of it's segments.'''
116 |
117 | length = 0
118 | for segment in self.segments:
119 | length += segment.length()
120 | return length
121 |
122 | def to_object(self):
123 | return {
124 | self.net.name: {
125 | 'net_class': self.net_class.uid,
126 | 'segments': [seg.to_object() for seg in self.segments],
127 | 'vias': [via.to_object() for via in self.vias],
128 | }
129 | }
130 |
131 | @classmethod
132 | def from_object(cls, obj, net, pcb):
133 | net_class = pcb.net_class_by_uid(obj['net_class'])
134 | attrs = cls(net, net_class, pcb)
135 | for seg in obj['segments']:
136 | Segment.from_object(seg, pcb)
137 | for via in obj['vias']:
138 | Via.from_object(via, pcb)
139 | return attrs
140 |
141 |
142 | class InstAttributes(object):
143 | def __init__(self, inst, pcb):
144 | assert isinstance(inst.device, Device)
145 |
146 | self.inst = inst
147 | self.inst.attributes = self
148 | self.pcb = pcb
149 |
150 | self.layer = pcb.attributes.layers.placement_layers[0]
151 | self.x = 0
152 | self.y = 0
153 | self.angle = 0
154 | self.matrix = None
155 |
156 | def iter_pads(self):
157 | for pad in self.inst.device.package.pads:
158 | yield AbsolutePad(self.inst, pad, self.matrix)
159 |
160 | def pad_by_name(self, name):
161 | pad = self.inst.device.package.pad_by_name(name)
162 | return AbsolutePad(self.inst, pad, self.matrix)
163 |
164 | def pads_by_pin(self, pin):
165 | for pad in self.inst.device.pads_by_pin(pin):
166 | yield AbsolutePad(self.inst, pad, self.matrix)
167 |
168 | def courtyard(self):
169 | crtyd = self.inst.device.package.courtyard.polygon
170 | return Matrix.transform(crtyd, self.matrix)
171 |
172 | def place(self, layer, x, y, angle=0):
173 | '''Places the node.'''
174 |
175 | self.layer = layer
176 | self.angle = angle
177 | self.x = x
178 | self.y = y
179 | self.matrix = Matrix.inst_matrix(x, y, angle, layer.flip)
180 | layer.insts.append(self.inst)
181 |
182 | def to_object(self):
183 | return {
184 | self.inst.name: {
185 | 'x': self.x,
186 | 'y': self.y,
187 | 'angle': self.angle,
188 | 'flip': self.layer.flip,
189 | 'layer': self.layer.layer.name,
190 | 'package': self.inst.device.package.name,
191 | }
192 | }
193 |
194 | @classmethod
195 | def from_object(cls, obj, inst, pcb):
196 | attrs = cls(inst, pcb)
197 | player = pcb.attributes.layers.player_by_name(obj['layer'])
198 | attrs.place(player, obj['x'], obj['y'], obj['angle'])
199 | return attrs
200 |
201 |
202 | class PcbAttributes(object):
203 | def __init__(self, layers, outline_design_rules, trace_design_rules,
204 | cost_cm2):
205 | self.layers = layers
206 | self.outline_design_rules = outline_design_rules
207 | self.trace_design_rules = trace_design_rules
208 | self.cost_cm2 = cost_cm2
209 |
210 | def to_object(self):
211 | return {
212 | 'layers': self.layers.to_object(),
213 | 'outline_design_rules': self.outline_design_rules.to_object(),
214 | 'trace_design_rules': self.trace_design_rules.to_object(),
215 | 'cost_cm2': self.cost_cm2,
216 | }
217 |
218 | @classmethod
219 | def from_object(cls, obj):
220 | return cls(Layers.from_object(obj['layers']),
221 | OutlineDesignRules.from_object(obj['outline_design_rules']),
222 | TraceDesignRules.from_object(obj['trace_design_rules']),
223 | obj['cost_cm2'])
224 |
225 |
226 | class Pcb(object):
227 | def __init__(self, netlist, outline, attributes, _init=True):
228 | assert isinstance(netlist, Netlist)
229 | assert isinstance(outline, Outline)
230 | assert isinstance(attributes, PcbAttributes)
231 |
232 | self.netlist = netlist
233 | self.outline = outline
234 | self.attributes = attributes
235 | self.net_classes = [attributes.trace_design_rules.to_netclass()]
236 |
237 | if _init:
238 | for inst in self.netlist.insts:
239 | InstAttributes(inst, self)
240 |
241 | for net in self.netlist.nets:
242 | nc = NetClass(self.net_classes[0])
243 | self.net_classes.append(nc)
244 | NetAttributes(net, nc, self)
245 |
246 | def net_class_by_uid(self, uid):
247 | for nc in self.net_classes:
248 | if nc.uid == uid:
249 | return nc
250 |
251 | def size(self):
252 | bounds = self.outline.polygon.exterior.bounds
253 | width = bounds[2] - bounds[0]
254 | height = bounds[3] - bounds[1]
255 | return width, height
256 |
257 | def area(self):
258 | return self.outline.polygon.exterior.area
259 |
260 | def cost(self):
261 | return self.outline.polygon.exterior.area / 100 * self.attributes.cost_cm2
262 |
263 | def to_object(self):
264 | packages = {}
265 | insts = {}
266 | nets = {}
267 | for inst in self.netlist.insts:
268 | pck = inst.device.package
269 | packages.update(pck.to_object())
270 | insts.update(inst.attributes.to_object())
271 | for net in self.netlist.nets:
272 | nets.update(net.attributes.to_object())
273 |
274 | return {
275 | 'netlist': self.netlist.to_object(),
276 | 'outline': self.outline.to_object(),
277 | 'attributes': self.attributes.to_object(),
278 | 'packages': packages,
279 | 'insts': insts,
280 | 'nets': nets,
281 | 'net_classes': [nc.to_object() for nc in self.net_classes],
282 | }
283 |
284 | @classmethod
285 | def from_object(cls, obj):
286 | pcb = cls(Netlist.from_object(obj['netlist']),
287 | Outline.from_object(obj['outline']),
288 | PcbAttributes.from_object(obj['attributes']),
289 | _init=False)
290 |
291 | for nc in obj['net_classes']:
292 | pcb.net_classes.append(NetClass.from_object(nc, pcb))
293 |
294 | for inst in pcb.netlist.insts:
295 | inst_obj = obj['insts'][inst.name]
296 | InstAttributes.from_object(inst_obj, inst, pcb)
297 |
298 | for net in pcb.netlist.nets:
299 | net_obj = obj['nets'][net.name]
300 | NetAttributes.from_object(net_obj, net, pcb)
301 |
302 | return pcb
303 |
--------------------------------------------------------------------------------
/pycircuit/package.py:
--------------------------------------------------------------------------------
1 | import math
2 | import numpy as np
3 | from shapely import affinity
4 | from shapely.geometry import Point, Polygon
5 |
6 |
7 | class Pad(object):
8 |
9 | def __init__(self, name, x, y, angle=0, size=None, shape=None, drill=None):
10 | self.name = name
11 | self.angle = angle
12 |
13 | point = affinity.rotate(Point(x, y), angle, origin=Point(0, 0))
14 | self.location = np.array([point.coords[0][0], point.coords[0][1], 1])
15 |
16 | # Optional properties for pcb export
17 | self.size = size
18 | self.shape = shape
19 | self.drill = drill
20 |
21 | def __repr__(self):
22 | return '%s (%s, %s)' % (self.name, self.location[0], self.location[1])
23 |
24 | def to_object(self):
25 | return {
26 | self.name: {
27 | 'x': self.location[0],
28 | 'y': self.location[1],
29 | 'r': self.angle,
30 | }
31 | }
32 |
33 |
34 | class PadArray(object):
35 | def __init__(self, length, pitch=0, radius=0, angle=0, prefix='', offset=0):
36 | self.length = length
37 | self.pitch = pitch
38 | self.angle = angle
39 | self.array_radius = (length - 1) * pitch / 2
40 | self.radius = radius
41 | # Pad name is %s%s % prefix, (index + offset)
42 | self.prefix = prefix
43 | self.offset = offset
44 |
45 | def __getitem__(self, index):
46 | pad_name = self.prefix + str(index + self.offset)
47 | pad_x = (index - 1) * self.pitch - self.array_radius
48 | return Pad(pad_name, pad_x, self.radius, self.angle)
49 |
50 | def __len__(self):
51 | return self.length
52 |
53 | def __iter__(self):
54 | self.counter = 0
55 | return self
56 |
57 | def __next__(self):
58 | if self.counter < self.length:
59 | self.counter += 1
60 | return self[self.counter]
61 | raise StopIteration()
62 |
63 |
64 | class TwoPads(object):
65 | def __init__(self, distance):
66 | self.distance = distance
67 |
68 | def __iter__(self):
69 | for i, angle in enumerate([90, -90]):
70 | for pad in PadArray(1, 0, self.distance / 2, angle, offset=i):
71 | yield pad
72 |
73 |
74 | class Sot23Pads(object):
75 | def __init__(self, c, e):
76 | self.c = c
77 | self.e = e
78 |
79 | def __iter__(self):
80 | for pad in PadArray(2, self.e * 2, self.c / 2, 0):
81 | yield pad
82 | for pad in PadArray(1, 0, self.c / 2, 180, offset=2):
83 | yield pad
84 |
85 |
86 | class DualPads(object):
87 | def __init__(self, num, pitch, radius):
88 | assert num % 2 == 0
89 | self.num = num
90 | self.pitch = pitch
91 | self.radius = radius
92 |
93 | def __iter__(self):
94 | pad_array_length = int(self.num / 2)
95 | for i, angle in enumerate([90, -90]):
96 | for pad in PadArray(pad_array_length, self.pitch, self.radius, angle,
97 | offset=pad_array_length * i):
98 | yield pad
99 |
100 |
101 | class QuadPads(object):
102 | def __init__(self, num, pitch, radius, thermal_pad=False):
103 | assert num % 4 == 0
104 | self.num = num
105 | self.pitch = pitch
106 | self.radius = radius
107 | self.thermal_pad = thermal_pad
108 |
109 | def __iter__(self):
110 | pad_array_length = int(self.num / 4)
111 | for i, angle in enumerate([90, 0, -90, 180]):
112 | for pad in PadArray(pad_array_length, self.pitch, self.radius, angle,
113 | offset=pad_array_length * i):
114 | yield pad
115 | if self.thermal_pad:
116 | yield Pad(str(self.num + 1), 0, 0,
117 | size=(self.thermal_pad, self.thermal_pad))
118 |
119 |
120 | class GridPads(object):
121 | def __init__(self, width, length, pitch):
122 | self.width = width
123 | self.length = length
124 | self.pitch = pitch
125 |
126 | def __iter__(self):
127 | grid_radius = (self.width - 1) * self.pitch / 2
128 | prefix = GridPads.default_prefix_generator()
129 | for col in range(self.width):
130 | radius = col * self.pitch - grid_radius
131 | for pad in PadArray(self.length, self.pitch, radius, 0,
132 | prefix=next(prefix)):
133 | yield pad
134 |
135 | @staticmethod
136 | def default_prefix_generator():
137 | for i in range(27):
138 | for j in range(27):
139 | for k in range(26):
140 | label = chr(k + ord('A'))
141 | if j > 0:
142 | label = chr(j + ord('A') - 1) + label
143 | if i > 0:
144 | label = chr(i + ord('A') - 1) + label
145 | yield label
146 |
147 |
148 | class StaggeredGridPads(object):
149 | def __init__(self, width, length, pitch):
150 | assert num % 2 == 1
151 | self.width = width
152 | self.length = length
153 | self.pitch = pitch
154 | self.grid = GridPads(width, length, pitch)
155 |
156 | def __iter__(self):
157 | for i, pad in enumerate(self.grid):
158 | if i % 2 == 1:
159 | continue
160 | yield pad
161 |
162 |
163 | class PerimeterPads(object):
164 | def __init__(self, width, length, pitch, perimeter, inner=0):
165 | self.width = width
166 | self.length = length
167 | self.pitch = pitch
168 | self.perimeter = perimeter
169 | self.inner = inner
170 | self.grid = GridPads(width, length, pitch)
171 |
172 | def __iter__(self):
173 | pad_iter = iter(self.grid)
174 | perimeter_end = self.perimeter - 1
175 | perimeter_start = self.num - self.perimeter
176 | inner_start = int((self.num - self.inner) / 2 - 1)
177 | inner_end = inner_start + self.inner + 1
178 | for col in range(self.num):
179 | for row in range(self.num):
180 | pad = next(pad_iter)
181 | if row > inner_start and row < inner_end and \
182 | col > inner_start and col < inner_end:
183 | yield pad
184 | else:
185 | if row > perimeter_end and row < perimeter_start and \
186 | col > perimeter_end and col < perimeter_start:
187 | continue
188 | yield pad
189 |
190 |
191 | class Courtyard(object):
192 | # IPC grid element is 0.5mm * 0.5mm
193 | IPC_GRID_SCALE = 0.5
194 |
195 | def __init__(self, coords):
196 | self.coords = coords
197 | self.polygon = Polygon(coords)
198 | self.bounds = self.polygon.bounds
199 | self.width = self.bounds[2] - self.bounds[0]
200 | self.height = self.bounds[3] - self.bounds[1]
201 | self.ipc_width = int(math.ceil(self.width / self.IPC_GRID_SCALE))
202 | self.ipc_height = int(math.ceil(self.height / self.IPC_GRID_SCALE))
203 |
204 | def __repr__(self):
205 | return repr(self.coords)
206 |
207 | def __str__(self):
208 | return 'width=%s (0.5mm), height=%s (0.5mm)' % \
209 | (self.ipc_width, self.ipc_height)
210 |
211 |
212 | class RectCrtyd(Courtyard):
213 | def __init__(self, xlen, ylen):
214 | self.xlen = xlen
215 | self.ylen = ylen
216 |
217 | x, y = xlen / 2, ylen / 2
218 | super().__init__([(-x, y), (x, y), (x, -y), (-x, -y)])
219 |
220 |
221 | class IPCGrid(RectCrtyd):
222 |
223 | def __init__(self, ipc_x, ipc_z):
224 | super().__init__(ipc_z * self.IPC_GRID_SCALE,
225 | ipc_x * self.IPC_GRID_SCALE)
226 |
227 | # X is the ylen axis
228 | self.ipc_x = ipc_x
229 | # Z is the xlen axis
230 | self.ipc_z = ipc_z
231 |
232 |
233 | class Package(object):
234 | '''Package represents a IC package. Packages are registered when imported
235 | from library.'''
236 |
237 | packages = []
238 |
239 | def __init__(self, name, courtyard, pads,
240 | package_size=(5, 5), pad_size=(1, 1),
241 | pad_shape='rect', pad_drill=None):
242 | self.name = name
243 | self.courtyard = courtyard
244 | self.pads = []
245 | self.package_size = package_size
246 | self.pad_size = pad_size
247 | self.pad_shape = pad_shape
248 | self.pad_drill = pad_drill
249 |
250 | for pad in pads:
251 | self.add_pad(pad)
252 |
253 | self.register_package(self)
254 |
255 | def add_pad(self, pad):
256 | pad.package = self
257 | if pad.size is None:
258 | pad.size = self.pad_size
259 | if pad.shape is None:
260 | pad.shape = self.pad_shape
261 | if pad.drill is None:
262 | pad.drill = self.pad_drill
263 |
264 | self.pads.append(pad)
265 |
266 | def pad_by_name(self, name):
267 | for pad in self.pads:
268 | if pad.name == name:
269 | return pad
270 |
271 | def size(self):
272 | bounds = self.courtyard.polygon.bounds
273 | return (bounds[2] - bounds[0], bounds[3] - bounds[1])
274 |
275 | def to_object(self):
276 | pads = {}
277 | for pad in self.pads:
278 | pads.update(pad.to_object())
279 |
280 | return {
281 | self.name: {
282 | 'width': self.courtyard.ipc_width,
283 | 'height': self.courtyard.ipc_height,
284 | 'pads': pads,
285 | }
286 | }
287 |
288 | def __str__(self):
289 | '''Returns the name of the Package.'''
290 |
291 | return self.name
292 |
293 | def __repr__(self):
294 | '''Returns the representation of a Package.'''
295 |
296 | pad_string = '\n'.join([2 * ' ' + repr(pad) for pad in self.pads])
297 | return '%s %s\n%s\n' \
298 | % (self.name, str(self.courtyard), pad_string)
299 |
300 | @classmethod
301 | def package_by_name(cls, name):
302 | '''Returns the Package with name from registered packages.'''
303 |
304 | for pkg in cls.packages:
305 | if pkg.name == name:
306 | return pkg
307 |
308 | raise IndexError('No Package with name ' + name)
309 |
310 | @classmethod
311 | def register_package(cls, package):
312 | '''Register a Package.'''
313 |
314 | try:
315 | cls.package_by_name(package.name)
316 | raise Exception(
317 | 'Package with name %s already exists' % package.name)
318 | except IndexError:
319 | cls.packages.append(package)
320 |
--------------------------------------------------------------------------------