"):
261 | self._context_ref_value = None
262 | raise Exception("Can't get context from stdin")
263 |
264 |
265 | tohash = repr((
266 | grab_nearby_lines(self.instance.defined_at, 3),
267 | ))
268 |
269 | h = hashlib.md5(tohash.encode("utf8")).hexdigest()
270 |
271 | ret = "c" + h[:8]
272 | self._context_ref_value = ret
273 | return ret
274 |
275 | @property
276 | def _anchor_code(self):
277 | try:
278 | return self._anchor_code_value
279 | except AttributeError:
280 | pass
281 |
282 | self._anchor_code_value = self._generate_anchor_code()
283 | return self._anchor_code_value
284 |
285 | def _generate_anchor_nets(self):
286 | tohash = repr((
287 | sorted(pin.net.name for pin in self.instance.pins if (pin._net is not None and "ANON_NET" not in pin.net.name)),
288 | ))
289 |
290 | h = hashlib.md5(tohash.encode("utf8")).hexdigest()
291 |
292 | ret = "n" + h[:8]
293 | self._context_ref_value = ret
294 | return ret
295 |
296 | @property
297 | def _anchor_nets(self):
298 | try:
299 | return self._anchor_nets_value
300 | except AttributeError:
301 | pass
302 |
303 | self._anchor_nets_value = self._generate_anchor_nets()
304 | return self._anchor_nets_value
305 |
306 | global_context = Context()
307 | nets = global_context.named_nets
308 |
--------------------------------------------------------------------------------
/pcbdl/html.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from .base import Part, PartInstancePin, Net, Plugin
16 | from .context import *
17 | from .netlistsvg import generate_svg
18 | import pcbdl.defined_at
19 |
20 | import collections
21 | from datetime import datetime
22 | import html
23 | import inspect
24 | import itertools
25 | import os
26 | import textwrap
27 |
28 | import pygments
29 | import pygments.lexers
30 | import pygments.formatters
31 |
32 | """HTML output format"""
33 | __all__ = ["generate_html"]
34 |
35 | _PCBDL_BUILTINS_PATH = os.path.dirname(pcbdl.__file__)
36 |
37 | @Plugin.register((Net, Part))
38 | class HTMLDefinedAt(Plugin):
39 | def register(self):
40 | self.defined_at = self.instance.defined_at
41 |
42 | self.filename, self.line = self.defined_at.rsplit(":", 1)
43 | self.line = int(self.line)
44 |
45 | self.code_manager.instanced_here(self.instance, self.filename, self.line)
46 |
47 | @property
48 | def href_line(self):
49 | return "Defined at: %s
" % (self.filename, self.line, self.defined_at)
50 |
51 | @Plugin.register(Part)
52 | class HTMLPart(Plugin):
53 | def class_list_generator(self):
54 | l = self.instance.__class__.__mro__
55 | l = l[:l.index(Part) + 1]
56 | for cls in l:
57 | filename, line = inspect.getsourcelines(cls)
58 | filename = os.path.relpath(inspect.getsourcefile(cls), pcbdl.defined_at.cwd)
59 | if filename in self.code_manager.file_database:
60 | yield "%s" % (filename, line, html.escape(repr(cls)))
61 | else:
62 | yield "%s" % html.escape(repr(cls))
63 |
64 | @property
65 | def part_li(self):
66 | part = self.instance
67 |
68 | class_str = ""
69 | if not part.populated:
70 | class_str = " class=\"not-populated\""
71 |
72 | yield "%s
" % (class_str, part.refdes, part.refdes)
73 |
74 | yield part.plugins[HTMLDefinedAt].href_line
75 | yield "%s
" % ", ".join(self.class_list_generator())
76 | try:
77 | yield "Variable Name: %s
" % part.variable_name
78 | except AttributeError:
79 | pass
80 |
81 | if part.__doc__:
82 | yield "%s
" % textwrap.dedent(part.__doc__.rstrip())
83 |
84 | yield "See in SVG
" % part.refdes
85 |
86 | yield "Value: %s
" % part.value
87 | yield "Part Number: %s
" % part.part_number
88 | if not part.populated:
89 | yield "Do Not Populate!
"
90 | try:
91 | yield "Package: %s
" % part.package
92 | except AttributeError:
93 | yield "Package not defined"
94 |
95 | real_pin_count = len({number for pin in part.pins for number in pin.numbers})
96 | yield "%d logical pins (%d real pins):
" % (len(part.pins), real_pin_count)
97 | for pin in part.pins:
98 | yield "- %s (%s)" % (pin.part.refdes, pin.name, " / ".join(pin.names), ', '.join(pin.numbers))
99 |
100 | try:
101 | net_name = pin._net.name
102 | yield "net: %s" % (net_name, net_name)
103 | except AttributeError:
104 | pass
105 |
106 | try:
107 | yield "well: %s" % (pin.well.plugins[HTMLPin].short_anchor)
108 | except AttributeError:
109 | pass
110 |
111 | yield "
"
112 | yield "
"
113 | yield ""
114 |
115 | @Plugin.register(Net)
116 | class HTMLNet(Plugin):
117 | @property
118 | def net_li(self):
119 | net = self.instance
120 | name = net.name
121 |
122 | yield "%s
" % (name, name)
123 | yield net.plugins[HTMLDefinedAt].href_line
124 |
125 | try:
126 | yield "Variable Name: %s
" % net.variable_name
127 | except AttributeError:
128 | pass
129 |
130 | yield "%d connections:
" % len(net.connections)
131 | for pin in net.connections:
132 | yield "- %s
" % (pin.plugins[HTMLPin].full_anchor)
133 | yield "
"
134 |
135 | yield ""
136 |
137 | @Plugin.register(PartInstancePin)
138 | class HTMLPin(Plugin):
139 | @property
140 | def short_anchor(self):
141 | pin = self.instance
142 | return "%s" % (pin.part.refdes, pin.name, pin.name)
143 |
144 | @property
145 | def full_anchor(self):
146 | pin = self.instance
147 | part_anchor = "%s." % (pin.part.refdes, pin.part.refdes)
148 | return part_anchor + self.short_anchor
149 |
150 | class Code:
151 | class CodeHtmlFormatter(pygments.formatters.HtmlFormatter):
152 | def _wrap_linespans(self, inner):
153 | s = self.linespans
154 | line_no = self.linenostart - 1
155 | for t, line in inner:
156 | if t:
157 | line_no += 1
158 | variables = self.fill_variables_for_line(line_no)
159 | line = line.rstrip("\n")
160 | yield 1, '%s%s\n' % (s, line_no, line, variables)
161 | else:
162 | yield 0, line
163 |
164 | def set_source_file(self, filename, fileinstances):
165 | self.linespans = filename
166 | self.lineanchors = filename
167 | self.fileinstances = fileinstances
168 |
169 | def fill_variables_for_line(self, line_no):
170 | variables_on_this_line = self.fileinstances[line_no]
171 |
172 | if not variables_on_this_line:
173 | return ""
174 |
175 | links = []
176 | for variable in variables_on_this_line:
177 | if isinstance(variable, Net):
178 | net_name = variable.name
179 | links.append("%s" % (net_name, net_name))
180 | continue
181 |
182 | if isinstance(variable, Part):
183 | part = variable
184 | links.append("%s" % (part.refdes, part.refdes))
185 | continue
186 |
187 | raise Exception("No idea how to make link for %r of type %r" % (variable, type(variable)))
188 |
189 | return "# %s" % ", ".join(links)
190 |
191 | def __init__(self):
192 | # {filename: {line: [instance]}}
193 | self.file_database = collections.defaultdict(lambda: collections.defaultdict(set))
194 | self._instances = set()
195 |
196 | self.lexer = pygments.lexers.PythonLexer()
197 | self.formatter = self.CodeHtmlFormatter(
198 | linenos=True,
199 | linespans="undefined",
200 |
201 | anchorlinenos = True,
202 | lineanchors="undefined",
203 |
204 | cssclass="code",
205 | )
206 | self.formatter.file_database = self.file_database
207 |
208 | def instanced_here(self, instance, filename, line):
209 | self.file_database[filename][line].add(instance)
210 | self._instances.add(instance)
211 |
212 | def css_generator(self):
213 | yield self.formatter.get_style_defs()
214 |
215 | def code_generator(self):
216 | file_list = self.file_database.keys()
217 | for filename in file_list:
218 | yield "%s
" % (filename, filename)
219 |
220 | with open(filename) as f:
221 | source_code = f.read()
222 |
223 | self.formatter.set_source_file(filename, self.file_database[filename])
224 | result = pygments.highlight(source_code, self.lexer, self.formatter)
225 |
226 | for instance in self._instances:
227 | try:
228 | variable_name = instance.variable_name
229 | except AttributeError:
230 | continue
231 |
232 | if isinstance(instance, Net):
233 | net = instance
234 | href = "#net-%s" % net.name
235 | title = "Net %s" % net
236 |
237 | if isinstance(instance, Part):
238 | part = instance
239 | href = "#part-%s" % part.refdes
240 | title = "Part %s" % part
241 |
242 | original_span = "%s" % variable_name
243 | modified_span = "%s" % (href, title, variable_name)
244 |
245 | if isinstance(instance, Part):
246 | # Linkify all the pins too
247 | for pin in part.pins:
248 | for name in pin.names:
249 | prepend = original_span + "."
250 | original_pin_span = prepend + "%s" % name
251 |
252 | title = repr(pin)
253 | href = "#pin-%s.%s" % (pin.part.refdes, pin.name)
254 | modified_pin_span = prepend + "%s" % (href, title, name)
255 | result = result.replace(original_pin_span, modified_pin_span)
256 |
257 | result = result.replace(original_span, modified_span)
258 |
259 | yield result
260 |
261 |
262 | def html_generator(context=global_context, include_svg=False):
263 | code_manager = Code()
264 |
265 | HTMLDefinedAt.code_manager = code_manager
266 | HTMLPart.code_manager = code_manager
267 |
268 | # Make sure the code_manager knows about everything already
269 | for instance in context.parts_list + context.net_list:
270 | instance.plugins[HTMLDefinedAt].register()
271 | for part in context.parts_list:
272 | l = part.__class__.__mro__
273 | l = l[:l.index(Part) + 1]
274 | for cls in l:
275 | filename = inspect.getsourcefile(cls)
276 | if _PCBDL_BUILTINS_PATH not in filename: # we don't want pcbdl builtin files in the list
277 | filename = os.path.relpath(filename, pcbdl.defined_at.cwd)
278 | _, line = inspect.getsourcelines(cls)
279 | code_manager.instanced_here(part, filename, line)
280 |
281 | yield ""
282 | yield ""
283 | yield ""
284 | yield "PCBDL %s" % (list(code_manager.file_database.keys())[0])
285 | yield ""
286 |
287 | yield ""
311 | yield ""
312 | yield ""
313 |
314 | yield "PCBDL HTML Output
"
315 | yield "Contents
"
316 | yield "- Parts
"
317 | yield "- Nets
"
318 | yield "- Code"
319 | yield "
"
320 | for filename in code_manager.file_database.keys():
321 | yield "- %s
" % (filename, filename)
322 | yield "
"
323 | yield " "
324 | if include_svg:
325 | yield "- SVG
"
326 | yield "
"
327 |
328 | yield "Parts
"
329 | for part in context.parts_list:
330 | yield from part.plugins[HTMLPart].part_li
331 | yield "
"
332 |
333 | yield "Nets
"
334 | for net in context.net_list:
335 | yield from net.plugins[HTMLNet].net_li
336 | yield "
"
337 |
338 | yield "Code
"
339 | yield from code_manager.code_generator()
340 |
341 | if include_svg:
342 | yield "SVG
"
343 | yield from generate_svg(context=context, max_pin_count=50)
344 |
345 | yield ""
346 | yield ""
347 |
348 | def generate_html(*args, **kwargs):
349 | return "\n".join(html_generator(*args, **kwargs))
350 |
--------------------------------------------------------------------------------
/pcbdl/netlistsvg.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from .base import Part, PartInstancePin, Net
16 | from .context import *
17 | import pcbdl.small_parts as small_parts
18 |
19 | import collections
20 | import json
21 | import os
22 | import re
23 | import subprocess
24 | import tempfile
25 |
26 | """Renders our circuit into svg with the help of netlistsvg."""
27 | __all__ = ["generate_svg", "SVGPage"]
28 |
29 | NETLISTSVG_LOCATION = os.path.expanduser(
30 | os.environ.get("NETLISTSVG_LOCATION", "~/netlistsvg"))
31 |
32 | NET_REGEX_ALL = ".*"
33 |
34 | class SVGNet(object):
35 | def __init__(self, instance, schematic_page):
36 | self.instance = instance
37 | self.schematic_page = schematic_page
38 |
39 | current_node_number = -1
40 | @classmethod
41 | def get_next_node_number(cls):
42 | cls.current_node_number += 1
43 | return cls.current_node_number
44 |
45 | def categorize_groups(self):
46 | self.grouped_connections = []
47 |
48 | for original_group in self.instance.grouped_connections:
49 | group = list(original_group) # make a copy so we can fragment it
50 | group_pin_count = sum(len(pin.part.pins) for pin in group)
51 | self.grouped_connections.append(group)
52 |
53 | if self.schematic_page.airwires < 2:
54 | continue
55 |
56 | #if group_pin_count < 40:
57 | #continue
58 |
59 | first_big_part = None
60 | for pin in original_group:
61 | if len(pin.part.pins) <= 3:
62 | # part is too small, probably should stay here
63 | continue
64 |
65 | if first_big_part is None:
66 | first_big_part = pin.part
67 | continue
68 |
69 | if pin.part is not first_big_part:
70 | # too many big parts here, move this one out
71 | #print("Too many big parts in %s group %r, moving %r out" % (self.instance.name, group, pin))
72 | group.remove(pin)
73 | self.grouped_connections.append((pin,))
74 |
75 |
76 | self.node_numbers = [self.get_next_node_number()
77 | for group in self.grouped_connections]
78 |
79 | def _find_group(self, pin):
80 | if not hasattr(self, "node_numbers"):
81 | self.categorize_groups()
82 |
83 | for i, group in enumerate(self.grouped_connections):
84 | if pin in group:
85 | return i, group
86 |
87 | raise ValueError("Can't find pin %s on %s" % (pin, self.instance))
88 |
89 | def get_other_pins_in_group(self, pin):
90 | _, group = self._find_group(pin)
91 | return group
92 |
93 | def get_node_number(self, pin):
94 | group_idx, _ = self._find_group(pin)
95 |
96 | if self.schematic_page.airwires == 0:
97 | return self.node_numbers[0]
98 | return self.node_numbers[group_idx]
99 |
100 | class SVGPart(object):
101 | SKIN_MAPPING = { # pcbdl_class: (skin_alias_name, pin_names, is_symmetric, is_rotatable)
102 | # more specific comes first
103 | small_parts.R: ("r_", "AB", True, True),
104 | small_parts.C: ("c_", "AB", True, True),
105 | small_parts.L: ("l_", "AB", True, True),
106 | small_parts.LED: ("d_led_", "+-", False, True),
107 | small_parts.D: ("d_", "+-", False, True),
108 | }
109 | SKIN_MAPPING_INSTANCES = tuple(SKIN_MAPPING.keys())
110 |
111 | def __init__(self, part, schematic_page):
112 | self.part = part
113 | self.schematic_page = schematic_page
114 |
115 | self.svg_type = part.refdes
116 | self.is_skinned = False
117 | self.skin_pin_names = ()
118 | self.is_symmetric = False
119 | self.is_rotatable = False
120 |
121 | for possible_class, skin_properties in self.SKIN_MAPPING.items():
122 | if isinstance(part, possible_class):
123 | self.is_skinned = True
124 | self.svg_type, self.skin_pin_names, self.is_symmetric, self.is_rotatable = skin_properties
125 | break
126 |
127 | def attach_net_name_port(self, net, net_node_number, direction):
128 | self.schematic_page.ports_dict["%s_node%s" % (net.name, str(net_node_number))] = {
129 | "bits": [net_node_number],
130 | "direction": direction
131 | }
132 |
133 | def attach_net_name(self, net, net_node_number, display=True):
134 | netname_entry = self.schematic_page.netnames_dict[net.name]
135 | if net_node_number not in netname_entry["bits"]: # avoid duplicates
136 | netname_entry["bits"].append(net_node_number)
137 | if display:
138 | netname_entry["hide_name"] = 0
139 |
140 | def attach_power_symbol(self, net, net_node_number):
141 | name = net.name
142 | if len(name) > 10:
143 | name = name.replace("PP","")
144 | name = name.replace("_VREF","")
145 |
146 | power_symbol = {
147 | "connections": {"A": [net_node_number]},
148 | "attributes": {"name": name},
149 | "type": "gnd" if net.is_gnd else "vcc",
150 | }
151 |
152 | if name == "GND":
153 | # redundant
154 | del power_symbol["attributes"]["name"]
155 |
156 | cell_name = "power_symbol_%d" % (net_node_number)
157 | self.schematic_page.cells_dict[cell_name] = power_symbol
158 |
159 | def should_draw_pin(self, pin):
160 | if not pin._net:
161 | # if we don't have a pin name, do it conditionally based on if a regex is set
162 | return self.schematic_page.net_regex.pattern == NET_REGEX_ALL
163 | else:
164 | return self.schematic_page.net_regex.match(str(pin.net.name))
165 |
166 | def add_parts(self, indent_depth=""):
167 | # Every real part might yield multiple smaller parts (eg: airwires, gnd/vcc connections)
168 | part = self.part
169 | self.schematic_page.parts_to_draw.remove(part)
170 |
171 | connections = {}
172 | port_directions = {}
173 |
174 | parts_to_bring_on_page = []
175 |
176 | pin_count = len(part.pins)
177 | for i, pin in enumerate(part.pins):
178 | name = "%s (%s)" % (pin.name, ", ".join(pin.numbers))
179 | if self.skin_pin_names:
180 | # if possible we need to match pin names with the skin file in netlistsvg
181 | name = self.skin_pin_names[i]
182 |
183 | DIRECTIONS = ["output", "input"] # aka right, left
184 | port_directions[name] = DIRECTIONS[i < pin_count/2]
185 |
186 | # TODO: Make this depend on pin type, instead of such a rough heuristic
187 | if "OUT" in pin.name:
188 | port_directions[name] = "output"
189 | if "IN" in pin.name:
190 | port_directions[name] = "input"
191 | if "EN" in pin.name:
192 | port_directions[name] = "input"
193 |
194 | is_connector = part.refdes.startswith("J") or part.refdes.startswith("CN")
195 | if is_connector:
196 | try:
197 | pin_number = int(pin.number)
198 | except ValueError:
199 | pass
200 | else:
201 | port_directions[name] = DIRECTIONS[pin_number % 2]
202 |
203 | pin_net = pin._net
204 | if pin_net:
205 | if hasattr(pin_net, "parent"):
206 | pin_net = pin_net.parent
207 |
208 | pin_net_helper = self.schematic_page.net_helpers[pin_net]
209 |
210 | net_node_number = pin_net_helper.get_node_number(pin)
211 | connections[name] = [net_node_number]
212 |
213 | for other_pin in pin_net_helper.get_other_pins_in_group(pin):
214 | other_part = other_pin.part
215 | parts_to_bring_on_page.append(other_part)
216 | else:
217 | # Make up a new disposable connection
218 | connections[name] = [SVGNet.get_next_node_number()]
219 |
220 | skip_drawing_pin = not self.should_draw_pin(pin)
221 |
222 | if self.is_skinned or part.refdes.startswith("Q"):
223 | # if any other pins good on a small part, we should draw the whole part (all pins)
224 | for other_pin in set(part.pins) - set((pin,)):
225 | if self.should_draw_pin(other_pin):
226 | skip_drawing_pin = False
227 |
228 | if pin in self.schematic_page.pins_to_skip:
229 | skip_drawing_pin = True
230 |
231 | if skip_drawing_pin:
232 | del connections[name]
233 | continue
234 |
235 | self.schematic_page.pins_drawn.append(pin)
236 | self.schematic_page.pin_count += 1
237 |
238 | if pin_net:
239 | display_net_name = pin.net.has_name
240 | if pin.net.is_gnd or pin.net.is_power:
241 | self.attach_power_symbol(pin.net, net_node_number)
242 | display_net_name = False
243 | self.attach_net_name(pin.net, net_node_number, display=display_net_name)
244 |
245 | if not connections:
246 | return
247 |
248 | if self.is_rotatable:
249 | suffix = "h"
250 | swap_pins = False
251 | for i, pin in enumerate(part.pins):
252 | if pin.net.is_power:
253 | suffix = "v"
254 | if i != 0:
255 | swap_pins = True
256 | if pin.net.is_gnd:
257 | suffix = "v"
258 | if i != 1:
259 | swap_pins = True
260 | #TODO maybe keep it horizontal if both sides .is_power
261 |
262 | if swap_pins and self.is_symmetric:
263 | mapping = {"A": "B", "B": "A"}
264 | connections = {mapping[name]:v
265 | for name, v in connections.items()}
266 |
267 | if self.is_rotatable:
268 | self.svg_type += suffix
269 |
270 | self.schematic_page.cells_dict[self.part.refdes] = {
271 | "connections": connections,
272 | "port_directions": port_directions,
273 | "attributes": {"value": part.value},
274 | "type": self.svg_type
275 | }
276 |
277 | print(indent_depth + str(part))
278 |
279 | # Make sure the other related parts are squeezed on this page
280 | for other_part in parts_to_bring_on_page:
281 | if other_part not in self.schematic_page.parts_to_draw:
282 | # we already drew it earlier
283 | continue
284 |
285 | self.schematic_page.part_helpers[other_part].add_parts(indent_depth + " ")
286 |
287 | class SVGPage(object):
288 | """Represents single .svg page"""
289 |
290 | def __init__(self, net_regex=NET_REGEX_ALL, airwires=2, pins_to_skip=[], max_pin_count=None, context=global_context):
291 | self.net_regex = re.compile(net_regex)
292 | self.airwires = airwires
293 | self.context = context
294 |
295 | self.max_pin_count = max_pin_count
296 | self.pin_count = 0
297 |
298 | self.pins_to_skip = pins_to_skip
299 | self.pins_drawn = []
300 |
301 | self.cells_dict = {}
302 | self.netnames_dict = collections.defaultdict(lambda: {"bits": [], "hide_name": 1})
303 | self.ports_dict = {}
304 |
305 | # start helper classes
306 | self.net_helpers = {}
307 | for net in self.context.net_list:
308 | self.net_helpers[net] = SVGNet(net, self)
309 |
310 | self.part_helpers = {}
311 | for part in self.context.parts_list:
312 | self.part_helpers[part] = SVGPart(part, self)
313 |
314 | class PageEmpty(Exception):
315 | pass
316 |
317 | def write_json(self, fp):
318 | """Generate the json input required for netlistsvg and dumps it to a file."""
319 | self.parts_to_draw = collections.deque(self.context.parts_list)
320 | while self.parts_to_draw:
321 |
322 | if self.max_pin_count and self.pin_count > self.max_pin_count:
323 | # stop drawing, this page is too cluttered
324 | break
325 |
326 | part = self.parts_to_draw[0]
327 | self.part_helpers[part].add_parts()
328 |
329 | if not self.pins_drawn:
330 | raise self.PageEmpty
331 |
332 | big_dict = {"modules": {"SVG Output": {
333 | "cells": self.cells_dict,
334 | "netnames": self.netnames_dict,
335 | "ports": self.ports_dict,
336 | }}}
337 |
338 | json.dump(big_dict, fp, indent=4)
339 | fp.flush()
340 |
341 | def generate(self):
342 | """Calls netlistsvg to generate the page and returns the svg contents as a string."""
343 | with tempfile.NamedTemporaryFile("w", prefix="netlistsvg_input_", suffix=".json", delete=False) as json_file, \
344 | tempfile.NamedTemporaryFile("r", prefix="netlistsvg_output_", suffix=".svg", delete=False) as netlistsvg_output:
345 | self.write_json(json_file)
346 | netlistsvg_command = [
347 | "/usr/bin/env", "node",
348 | os.path.join(NETLISTSVG_LOCATION, "bin", "netlistsvg.js"),
349 |
350 | "--skin",
351 | os.path.join(NETLISTSVG_LOCATION, "lib", "analog.svg"),
352 |
353 | json_file.name,
354 |
355 | "-o",
356 | netlistsvg_output.name
357 | ]
358 | print(netlistsvg_command)
359 | subprocess.call(netlistsvg_command)
360 |
361 | svg_contents = netlistsvg_output.read()
362 |
363 | # When a net appears in a few places (when we have airwires), we need to disambiguage the parts of the net
364 | # so netlistsvg doesn't think they're actually the same net and should connect them together.
365 | # Remove the extra decoration:
366 | svg_contents = re.sub("_node\d+", "", svg_contents)
367 |
368 | return svg_contents
369 |
370 |
371 | def generate_svg(*args, **kwargs):
372 | pins_to_skip = []
373 | while True:
374 | n = SVGPage(*args, **kwargs, pins_to_skip=pins_to_skip)
375 | try:
376 | svg_contents = n.generate()
377 | except SVGPage.PageEmpty:
378 | break
379 | pins_to_skip += n.pins_drawn
380 |
381 | yield svg_contents
382 |
--------------------------------------------------------------------------------
/pcbdl/base.py:
--------------------------------------------------------------------------------
1 | # Copyright 2019 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # https://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import collections
16 | import copy
17 | import enum
18 | import itertools
19 | __all__ = [
20 | "PinType", "ConnectDirection",
21 | "Net", "Part", "Pin"
22 | ]
23 |
24 | class Plugin(object):
25 | def __new__(cls, instance):
26 | self = super(Plugin,cls).__new__(cls)
27 | self.instance = instance
28 | return self
29 |
30 | @staticmethod
31 | def register(plugin_targets):
32 | if not isinstance(plugin_targets, collections.abc.Iterable):
33 | plugin_targets = (plugin_targets,)
34 |
35 | def wrapper(plugin):
36 | for target_cls in plugin_targets:
37 | try:
38 | target_cls.plugins
39 | except AttributeError:
40 | target_cls.plugins = set()
41 | target_cls.plugins.add(plugin)
42 | return plugin
43 |
44 | return wrapper
45 |
46 | @staticmethod
47 | def init(instance):
48 | """Init plugins associated with this instance"""
49 | try:
50 | factories = instance.plugins
51 | except AttributeError:
52 | return
53 | assert type(instance.plugins) is not dict
54 | instance.plugins = {plugin: plugin(instance) for plugin in factories}
55 |
56 | class ConnectDirection(enum.Enum):
57 | UNKNOWN = 0
58 | IN = 1
59 | OUT = 2
60 |
61 | class PinType(enum.Enum):
62 | UNKNOWN = 0
63 | PRIMARY = 1
64 | SECONDARY = 2
65 | POWER_INPUT = 3
66 | POWER_OUTPUT = 4
67 | GROUND = 5
68 | INPUT = 6
69 | OUTPUT = 7
70 |
71 | def _maybe_single(o):
72 | if isinstance(o, collections.abc.Iterable):
73 | yield from o
74 | else:
75 | yield o
76 |
77 | class _PinList(collections.OrderedDict):
78 | def __getitem__(self, pin_name):
79 | if isinstance(pin_name, int):
80 | return tuple(self.values())[pin_name]
81 | pin_name = pin_name.upper()
82 | try:
83 | return super().__getitem__(pin_name)
84 | except KeyError:
85 | # try looking slowly through the other names
86 | for pin in self.values():
87 | if pin_name.upper() in pin.names:
88 | return pin
89 | else:
90 | raise
91 |
92 | def __iter__(self):
93 | yield from self.values()
94 |
95 | def __repr__(self):
96 | return repr(tuple(self.values()))
97 |
98 | class Net(object):
99 | _name = None
100 | has_name = False
101 |
102 | def __init__(self, name=None):
103 | if name is not None:
104 | self.name = name.upper()
105 | self._connections = []
106 |
107 | Plugin.init(self)
108 |
109 | def connect(self, others, direction=ConnectDirection.UNKNOWN, pin_type=PinType.PRIMARY):
110 | try:
111 | connection_group = self.group
112 | except AttributeError:
113 | connection_group = collections.OrderedDict()
114 | self._connections.append(connection_group)
115 |
116 | for other in _maybe_single(others):
117 | pin = None
118 |
119 | if isinstance(other, Part):
120 | pin = other.get_pin_to_connect(pin_type, self)
121 |
122 | if isinstance(other, PartInstancePin):
123 | pin = other
124 |
125 | if isinstance(other, Net):
126 | raise NotImplementedError("Can't connect nets together yet.")
127 |
128 | if pin is None:
129 | raise TypeError("Don't know how to get %s pin from %r." % (pin_type.name, other))
130 |
131 | connection_group[pin] = direction
132 | pin.net = self
133 |
134 | self._last_connection_group = connection_group
135 |
136 | def _shift(self, direction, others):
137 | self.connect(others, direction, PinType.PRIMARY)
138 |
139 | if hasattr(self, "group"):
140 | return self
141 |
142 | # Return a copy that acts just like us, but already knows the group
143 | grouped_net = copy.copy(self)
144 | grouped_net.parent = self
145 | grouped_net.group = self._last_connection_group
146 | return grouped_net
147 |
148 | def __lshift__(self, others):
149 | return self._shift(ConnectDirection.IN, others)
150 |
151 | def __rshift__(self, others):
152 | return self._shift(ConnectDirection.OUT, others)
153 |
154 | _MAX_REPR_CONNECTIONS = 10
155 | def __repr__(self):
156 | connected = self.connections
157 | if len(connected) >= self._MAX_REPR_CONNECTIONS:
158 | inside_str = "%d connections" % (len(connected))
159 | elif len(connected) == 0:
160 | inside_str = "unconnected"
161 | elif len(connected) == 1:
162 | inside_str = "connected to " + repr(connected[0])
163 | else:
164 | inside_str = "connected to " + repr(connected)[1:-1]
165 | return "%s(%s)" % (self, inside_str)
166 |
167 | def __str__(self):
168 | return self.name
169 |
170 | @property
171 | def name(self):
172 | if hasattr(self, "parent"):
173 | return self.parent.name
174 |
175 | if not self.has_name:
176 | # This path should be rare, only if the user really wants trouble
177 | return "ANON_NET?m%05x" % (id(self) // 32 & 0xfffff)
178 |
179 | return self._name
180 |
181 | @name.setter
182 | def name(self, new_name):
183 | self._name = new_name.upper()
184 | self.has_name = True
185 |
186 | @property
187 | def connections(self):
188 | """
189 | A :class:`tuple` of pins connected to this net.
190 |
191 | Useful in the interpreter and/or when you want to inspect your schematic::
192 |
193 | >>> gnd.connections
194 | (U1.GND, VREG1.GND, U2.GND, VREG2.GND)
195 |
196 | """
197 | return sum(self.grouped_connections, ())
198 |
199 | @property
200 | def grouped_connections(self):
201 | """
202 | Similar to :attr:`connections`, but this time pins that were connected together stay in groups::
203 |
204 | >>> pp1800.grouped_connections
205 | ((U1.GND, VREG1.GND), (U2.GND, VREG2.GND))
206 | """
207 | return tuple(tuple(group.keys()) for group in self._connections)
208 |
209 | def is_net_of_class(self, keywords):
210 | for keyword in keywords:
211 | if keyword in self.name:
212 | return True
213 |
214 | @property
215 | def is_power(self):
216 | return self.is_net_of_class(("VCC", "PP", "VBUS"))
217 |
218 | @property
219 | def is_gnd(self):
220 | return self.is_net_of_class(("GND",))
221 |
222 | class PinFragment(object):
223 | """
224 | This is the fully featured (as opposed to just a tuple of parameters)
225 | element of :attr:`PINS` at the time of writing
226 | a :class:`Part`. Saves all parameters it's given,
227 | merges later once the Part is fully defined.
228 |
229 | .. warning:: Just like the name implies this is just a fragment of the
230 | information we need for the pin. It's possible the Part needs to be
231 | overlayed on top of its parents before we can have a complete picture.
232 | Ex: this could be the pin labeled "PA2" of a microcontroller, but until
233 | the part is told what package it is, we don't really know the pin
234 | number.
235 | """
236 | def __init__(self, names_or_numbers=(), names_if_numbers=None, *args, **kwargs):
237 | # Check if short form for the positional arguments
238 | if names_if_numbers is None:
239 | names, numbers = names_or_numbers, ()
240 | else:
241 | names, numbers = names_if_numbers, names_or_numbers
242 |
243 | if isinstance(names, str):
244 | names = (names,)
245 | names += kwargs.pop("names", ())
246 | try:
247 | names += (kwargs.pop("name"),)
248 | except KeyError:
249 | pass
250 | names = tuple(name.upper() for name in names)
251 | self.names = names
252 |
253 | if isinstance(numbers, (str, int)):
254 | numbers = (numbers,)
255 | numbers += kwargs.pop("numbers", ())
256 | try:
257 | numbers += (kwargs.pop("number"),)
258 | except KeyError:
259 | pass
260 | numbers = tuple(str(maybe_int) for maybe_int in numbers)
261 | self.numbers = numbers
262 |
263 | self.args = args
264 | self.kwargs = kwargs
265 |
266 | Plugin.init(self)
267 |
268 | def __repr__(self):
269 | def arguments():
270 | yield repr(self.names)
271 | if self.numbers:
272 | yield "numbers=" + repr(self.numbers)
273 | for arg in self.args:
274 | yield repr(arg)
275 | for name, value in self.kwargs.items():
276 | yield "%s=%r" % (name, value)
277 | return "PinFragment(%s)" % (", ".join(arguments()))
278 |
279 | def __eq__(self, other):
280 | """If any names match between two fragments, we're talking about the same pin. This is associative, so it chains through other fragments."""
281 | for my_name in self.names:
282 | if my_name in other.names:
283 | return True
284 | return False
285 |
286 | @staticmethod
287 | def part_superclasses(part):
288 | for cls in type(part).__mro__:
289 | if cls is Part:
290 | return
291 | yield cls
292 |
293 | @staticmethod
294 | def gather_fragments(cls_list):
295 | all_fragments = [pin for cls in cls_list for pin in cls.PINS]
296 | while len(all_fragments) > 0:
297 | same_pin_fragments = []
298 | same_pin_fragments.append(all_fragments.pop(0))
299 | pin_index = 0
300 | while True:
301 | try:
302 | i = all_fragments.index(same_pin_fragments[pin_index])
303 | same_pin_fragments.append(all_fragments.pop(i))
304 | except ValueError:
305 | pin_index += 1 # try following the chain of names, maybe there's another one we need to search by
306 | except IndexError:
307 | break # probably no more fragments for this pin
308 | yield same_pin_fragments
309 |
310 | @staticmethod
311 | def resolve(fragments):
312 | # union the names, keep order
313 | name_generator = (n for f in fragments for n in f.names)
314 | seen_names = set()
315 | deduplicated_names = [n for n in name_generator if not (n in seen_names or seen_names.add(n))]
316 |
317 | pin_numbers = [number for fragment in fragments for number in fragment.numbers]
318 |
319 | # union the args and kwargs, stuff near the front has priority to override
320 | args = []
321 | kwargs = {}
322 | for fragment in reversed(fragments):
323 | args[:len(fragment.args)] = fragment.args
324 | kwargs.update(fragment.kwargs)
325 |
326 | return PartClassPin(deduplicated_names, pin_numbers, *args, **kwargs)
327 |
328 | @staticmethod
329 | def second_name_important(pin):
330 | """
331 | Swap the order of the pin names so the functional (second) name is first.
332 |
333 | Used as a :func:`Part._postprocess_pin filter`.
334 | """
335 | pin.names = pin.names[1:] + (pin.names[0],)
336 | Pin = PinFragment
337 |
338 | class PartClassPin(object):
339 | """
340 | Pin of a Part, but no particular Part instance.
341 | Contains general information about the pin (but it could be for any
342 | part of that type), nothing related to a specific part instance.
343 | """
344 | well_name = None
345 |
346 | def __init__(self, names, numbers, type=PinType.UNKNOWN, well=None):
347 | self.names = names
348 | self.numbers = numbers
349 | self.type = type
350 | self.well_name = well
351 |
352 | Plugin.init(self)
353 |
354 | @property
355 | def name(self):
356 | return self.names[0]
357 |
358 | @property
359 | def number(self):
360 | return self.numbers[0]
361 |
362 | def __str__(self):
363 | return "Pin %s" % (self.name)
364 | __repr__ = __str__
365 |
366 | class PartInstancePin(PartClassPin):
367 | """Particular pin of a particular part instance. Can connect to nets. Knows the refdes of its part."""
368 | _net = None
369 |
370 | def __init__(self, part_instance, part_class_pin, inject_number=None):
371 | # copy state of the Pin to be inherited, then continue as if the parent class always existed that way
372 | self.__dict__.update(part_class_pin.__dict__.copy())
373 | # no need to call PartClassPin.__init__
374 |
375 | self._part_class_pin = part_class_pin
376 |
377 | # save arguments
378 | self.part = part_instance
379 |
380 | if inject_number is not None:
381 | self.numbers = (inject_number,)
382 | assert self.numbers is not None, "this Pin really should have had real pin numbers assigned by now"
383 |
384 | well_name = self.well_name
385 | if well_name is not None:
386 | try:
387 | self.well = self.part.pins[well_name]
388 | except KeyError:
389 | raise KeyError("Couldn't find voltage well pin %s on part %r" % (well_name, part_instance))
390 | if self.well.type not in (PinType.POWER_INPUT, PinType.POWER_OUTPUT):
391 | raise ValueError("The chosen well pin %s is not a power pin (but is %s)" % (self.well, self.well.type))
392 |
393 | Plugin.init(self)
394 |
395 | @property
396 | def net(self):
397 | """
398 | The :class:`Net` that this pin is connected to.
399 |
400 | If it's not connected to anything yet, we'll get a fresh net.
401 | """
402 | if self._net is None:
403 | fresh_net = Net() #defined_at: not here
404 | return fresh_net << self
405 | #fresh_net.connect(self, direction=ConnectDirection.UNKNOWN) # This indirectly sets self.netf
406 | return self._net
407 | @net.setter
408 | def net(self, new_net):
409 | if self._net is not None:
410 | # TODO: Maybe just unify the existing net and the new
411 | # net and allow this.
412 | raise ValueError("%s pin is already connected to a net (%s). Can't connect to %s too." %
413 | (self, self._net, new_net))
414 |
415 | self._net = new_net
416 |
417 | def connect(self, *args, **kwargs):
418 | self.net.connect(*args, **kwargs)
419 |
420 | def __lshift__(self, others):
421 | net = self._net
422 | if net is None:
423 | # don't let the net property create a new one,
424 | # we want to dictate the direction to that Net
425 | net = Net() #defined_at: not here
426 | net >>= self
427 | return net << others
428 |
429 | def __rshift__(self, others):
430 | net = self._net
431 | if net is None:
432 | # don't let the net property create a new one,
433 | # we want to dictate the direction to that Net
434 | net = Net() #defined_at: not here
435 | net <<= self
436 | return net >> others
437 |
438 | def __str__(self):
439 | return "%r.%s" % (self.part, self.name)
440 | __repr__ = __str__
441 |
442 | class PinFragmentList(list):
443 | """Used as a marker that we have visited Part.PINS and converted all the elements to PinFragment."""
444 | def __init__(self, part_cls):
445 | self.part_cls = part_cls
446 | list.__init__(self, part_cls.PINS)
447 | for i, maybenames in enumerate(self):
448 | # syntactic sugar, .PIN list might have only names instead of the long form Pin instances
449 | if not isinstance(maybenames, Pin):
450 | self[i] = PinFragment(maybenames)
451 |
452 | if part_cls._postprocess_pin.__code__ == Part._postprocess_pin.__code__:
453 | # Let's not waste our time with a noop
454 | return
455 | for i, _ in enumerate(self):
456 | # do user's postprocessing
457 | part_cls._postprocess_pin(self[i])
458 |
459 | class Part(object):
460 | """
461 | This is the :ref:`base class` for any new Part the writer of a schematic or a part librarian has to make. ::
462 |
463 | class Transistor(Part):
464 | REFDES_PREFIX = "Q"
465 | PINS = ["B", "C", "E"]
466 | """
467 |
468 | PINS = []
469 | """
470 | This is how the pins of a part are defined, as a :class:`list` of pins.
471 |
472 | Each pin entry can be one of:
473 |
474 | * :class:`Pin`
475 | * :class:`tuple` of names which will automatically be turned into a :class:`Pin`
476 | * just one :class:`string`, representing a pin name, if one cares about nothing else.
477 |
478 | So these are all valid ways to define a pin (in decreasing order of detail), and mean about the same thing::
479 |
480 | PINS = [
481 | Pin("1", ("GND", "GROUND"), type=PinType.POWER_INPUT),
482 | ("GND", "GROUND"),
483 | "GND",
484 | ]
485 |
486 | See the :class:`Pins Section` for the types of properties that can be
487 | defined on each Pin entry.
488 | """
489 |
490 | pins = _PinList()
491 | """
492 | Once the Part is instanced (aka populated on the schematic), our pins become real too (they turn into :class:`PartInstancePins`).
493 | This is a :class:`dict` like object where the pins are stored. One can look up pins by any of its names::
494 |
495 | somechip.pins["VCC"]
496 |
497 | Though most pins are also directly populated as a attributes to the part, so this is equivalent::
498 |
499 | somechip.VCC
500 |
501 | The pins list can still be used to view all of the pins at once, like on the console:
502 |
503 | >>> diode.pins
504 | (D1.VCC, D1.NC, D1.P1, D1.GND, D1.P2)
505 | """
506 |
507 | REFDES_PREFIX = "UNK"
508 | """
509 | The prefix that every reference designator of this part will have.
510 |
511 | Example: :attr:`"R"` for resistors,
512 | :attr:`"C"` for capacitors.
513 |
514 | The auto namer system will eventually put numbers after the prefix to get the complete :attr:`refdes`.
515 | """
516 |
517 | pin_names_match_nets = False
518 | """
519 | Sometimes when connecting nets to a part, the pin names become very redundant::
520 |
521 | Net("GND") >> somepart.GND
522 | Net("VCC") >> somepart.VCC
523 | Net("RESET") >> somepart.RESET
524 |
525 | We can use this variable tells the part to pick the right pin depending on
526 | the variable name, at that point the part itself can be used in lieu of
527 | the pin::
528 |
529 | Net("GND") >> somepart
530 | Net("VCC") >> somepart
531 | Net("RESET") >> somepart
532 | """
533 |
534 | pin_names_match_nets_prefix = ""
535 | """
536 | When :attr:`pin_names_match_nets` is active, it strips a
537 | little bit of the net name in case it's part of a bigger net group::
538 |
539 | class SPIFlash(Part):
540 | pin_names_match_nets = True
541 | pin_names_match_nets_prefix = "SPI1"
542 | PINS = ["MOSI", "MISO", "SCK", "CS", ...]
543 | ...
544 | Net("SPI1_MOSI") >> spi_flash # autoconnects to the pin called only "MOSI"
545 | Net("SPI1_MISO") << spi_flash # "MISO"
546 | Net("SPI1_SCK") >> spi_flash # "SCK"
547 | Net("SPI1_CS") >> spi_flash # "CS"
548 | """
549 |
550 | def __init__(self, value=None, refdes=None, package=None, part_number=None, populated=True):
551 | if part_number is not None:
552 | self.part_number = part_number
553 | if value is not None:
554 | self.value = value
555 |
556 | # if we don't have a value xor a package, use one of them for both
557 | if not hasattr(self, "value") and hasattr(self, "part_number"):
558 | self.value = self.part_number
559 | if not hasattr(self, "part_number") and hasattr(self, "value"):
560 | self.part_number = self.value
561 | # if we don't have either, then there's not much we can do
562 | if not hasattr(self, "value") and not hasattr(self, "part_number"):
563 | self.value = ""
564 | self.part_number = ""
565 |
566 | self._refdes = refdes
567 | if package is not None:
568 | self.package = package
569 | self.populated = populated
570 |
571 | self._generate_pin_instances()
572 |
573 | Plugin.init(self)
574 |
575 | def _generate_pin_instances(self):
576 | cls_list = list(PinFragment.part_superclasses(self))
577 |
578 | # process the pin lists a little bit
579 | for cls in cls_list:
580 | # but only if we didn't already do it
581 | if isinstance(cls.PINS, PinFragmentList):
582 | continue
583 | cls.PINS = PinFragmentList(cls)
584 |
585 | self.__class__.pins = [PinFragment.resolve(f) for f in PinFragment.gather_fragments(cls_list)]
586 |
587 | self.pins = _PinList()
588 | for i, part_class_pin in enumerate(self.__class__.pins):
589 | # if we don't have an assigned pin number, generate one
590 | inject_pin_number = str(i + 1) if not part_class_pin.numbers else None
591 |
592 | pin = PartInstancePin(self, part_class_pin, inject_pin_number)
593 | self.pins[pin.name] = pin
594 |
595 | # save the pin as an attr for this part too
596 | for name in pin.names:
597 | self.__dict__[name] = pin
598 |
599 | @property
600 | def _refdes_from_memory_address(self):
601 | return "%s?m%05x" % (self.REFDES_PREFIX, id(self) // 32 & 0xfffff)
602 |
603 | @property
604 | def refdes(self):
605 | """
606 | Reference designator of the part. Example: R1, R2.
607 |
608 | It's essentially the unique id for the part that will be used to
609 | refer to it in most output methods.
610 | """
611 | if self._refdes is not None:
612 | return self._refdes
613 |
614 | # make up a refdes based on memory address
615 | return self._refdes_from_memory_address
616 |
617 | @refdes.setter
618 | def refdes(self, new_value):
619 | self._refdes = new_value.upper()
620 |
621 | def __repr__(self):
622 | return self.refdes
623 |
624 | def __str__(self):
625 | return "%s - %s%s" % (self.refdes, self.value, " DNS" if not self.populated else "")
626 |
627 | def get_pin_to_connect(self, pin_type, net=None): # pragma: no cover
628 | assert isinstance(pin_type, PinType)
629 |
630 | if self.pin_names_match_nets and net is not None:
631 | prefix = self.pin_names_match_nets_prefix
632 | net_name = net.name
633 | for pin in self.pins:
634 | for pin_name in pin.names:
635 | if pin_name == net_name:
636 | return pin
637 | if prefix + pin_name == net_name:
638 | return pin
639 | raise ValueError("Couldn't find a matching named pin on %r to connect the net %s" % (self, net_name))
640 |
641 | raise NotImplementedError("Don't know how to get %s pin from %r" % (pin_type.name, self))
642 |
643 | @classmethod
644 | def _postprocess_pin(cls, pin):
645 | """
646 | It's sometimes useful to process the pins from the source code before the part gets placed down.
647 | This method will be called for each pin by each subclass of a Part.
648 |
649 | Good uses for this:
650 |
651 | * :func:`Raise the importance of the second name` in a connector, so the more semantic name is the primary name, not the pin number::
652 |
653 | PINS = [
654 | ("P1", "Nicer name"),
655 | ]
656 | _postprocess_pin = Pin.second_name_important
657 |
658 | * Populate alternate functions of a pin if they follow an easy pattern.
659 | * A simple programmatic alias on pin names without subclassing the part itself.
660 | """
661 | raise TypeError("This particular implementation of _postprocess_pin should be skipped by PinFragmentList()")
662 |
--------------------------------------------------------------------------------
/examples/servo_micro.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Copyright 2019 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | """
18 | Full reimplementation of servo micro in pcbdl.
19 |
20 | Servo Micro's information page (including pdf schematics made in orthodox tools) can be found at:
21 | https://chromium.googlesource.com/chromiumos/third_party/hdctools/+/refs/heads/master/docs/servo_micro.md
22 | """
23 |
24 | from pcbdl import *
25 |
26 | # Start of things that should really be in a generic library
27 | # It's a TODO to make a library. Until then, 300 lines to start a new schematic from scratch with no library is probably not bad.
28 | def make_connector(pin_count):
29 | class Connector(Part):
30 | REFDES_PREFIX = "CN"
31 |
32 | PINS = []
33 |
34 | for i in range(pin_count):
35 | i += 1 # 1 indexed
36 | pin = Pin(i, "P%d" % i)
37 | Connector.PINS.append(pin)
38 |
39 | return Connector
40 |
41 | class UsbConnector(Part):
42 | REFDES_PREFIX = "CN"
43 | part_number = "1981568-1"
44 | package = "TE_1981568-1"
45 | PINS = [
46 | "VBUS",
47 | ("DM", "D-"),
48 | ("DP", "D+"),
49 | "ID",
50 | "GND",
51 | Pin("G", numbers=("G1", "G2", "G3", "G4")),
52 | ]
53 |
54 | class FET(Part):
55 | """FET Transistor"""
56 | REFDES_PREFIX = "Q"
57 |
58 | PINS = [
59 | Pin("D", "D"),
60 | Pin("G", "G"),
61 | Pin("S", "S"),
62 | ]
63 |
64 | class Regulator(Part):
65 | REFDES_PREFIX = "U"
66 |
67 | PINS = [
68 | Pin("IN", type=PinType.POWER_INPUT),
69 | Pin("OUT", type=PinType.POWER_OUTPUT),
70 | Pin("GND", type=PinType.POWER_INPUT),
71 | ]
72 |
73 | class MIC5504(Regulator):
74 | part_number = "MIC5504-3.3YMT"
75 | package = "SON65P100X100X40-5T48X48N"
76 |
77 | PINS = [
78 | Pin("4", "IN"),
79 | Pin("1", "OUT"),
80 | Pin("3", "EN"),
81 | Pin(("2", "G1"), ("GND", "PAD")),
82 | ]
83 |
84 | class TLV70018DSER(Regulator):
85 | part_number = "TLV70018DSER"
86 | package = "SON50P150X150X80-6L"
87 |
88 | PINS = [
89 | Pin("1", "IN"),
90 | Pin("3", "OUT"),
91 | Pin("6", "EN"),
92 | Pin("4", "NC1"),
93 | Pin("5", "NC2"),
94 | Pin("2", "GND"),
95 | ]
96 |
97 | class UsbEsdDiode(Part):
98 | REFDES_PREFIX = "D"
99 | part_number = "TPD2E001DRLR"
100 | package = "SOP50P170X60-5N"
101 | PINS = [
102 | Pin("1", "VCC", type=PinType.POWER_INPUT),
103 | Pin("4", "GND", type=PinType.POWER_INPUT),
104 | Pin("3", "P1"),
105 | Pin("5", "P2"),
106 | Pin("2", "NC"),
107 | ]
108 |
109 | class DoubleDiode(Part):
110 | REFDES_PREFIX = "D"
111 | part_number = "240-800MV"
112 | package = "SOT95P247X115-3L"
113 | PINS = ["A1", "A2", "K"]
114 |
115 | class STM32F072(Part):
116 | REFDES_PREFIX = "U"
117 |
118 | part_number = "STM32F072CBU6TR"
119 | package = "QFN05P_7-1X7-1_0-6_49N"
120 |
121 | PINS = [
122 | Pin(("24", "48"), "VDD", type=PinType.POWER_INPUT),
123 | Pin("1", "VBAT", type=PinType.POWER_INPUT),
124 | Pin("9", "VDDA", type=PinType.POWER_INPUT),
125 | Pin("36", "VDDIO2", type=PinType.POWER_INPUT),
126 |
127 | Pin(("23", "35", "47"), "VSS"),
128 | Pin("8", "VSSA"),
129 | Pin("49", "PAD"),
130 |
131 | Pin("44", "BOOT0"),
132 | Pin("7", "NRST"),
133 | ]
134 |
135 | for i in range(8):
136 | PINS.append(Pin(i + 10, "PA%d" % i))
137 |
138 | PINS += [
139 | Pin("29", "PA8"),
140 | Pin("30", "PA9"),
141 | Pin("31", "PA10"),
142 | Pin("32", "PA11"),
143 | Pin("33", "PA12"),
144 | Pin("34", "PA13"),
145 | Pin("37", "PA14"),
146 | Pin("38", "PA15"),
147 |
148 | Pin("18", "PB0"),
149 | Pin("19", "PB1"),
150 | Pin("20", "PB2"),
151 | Pin("39", "PB3"),
152 | Pin("40", "PB4"),
153 | Pin("41", "PB5"),
154 | Pin("42", "PB6"),
155 | Pin("43", "PB7"),
156 |
157 | Pin("45", "PB8"),
158 | Pin("46", "PB9"),
159 | Pin("21", "PB10"),
160 | Pin("22", "PB11"),
161 | Pin("25", "PB12"),
162 | Pin("26", "PB13"),
163 | Pin("27", "PB14"),
164 | Pin("28", "PB15"),
165 |
166 | Pin("2", "PC13"),
167 | Pin("3", ("PC14", "OSC32_IN")),
168 | Pin("4", ("PC15", "OSC32_OUT")),
169 |
170 | Pin("5", ("PF0", "OSC_IN")),
171 | Pin("6", ("PF1", "OSC_OUT")),
172 | ]
173 |
174 | for pin in PINS:
175 | if pin.names[0].startswith("PA"):
176 | pin.well_name = "VDD"
177 |
178 | if pin.names[0].startswith("PB"):
179 | pin.well_name = "VDDA"
180 |
181 | if pin.names[0].startswith("PC"):
182 | pin.well_name = "VDDA"
183 |
184 | if pin.names[0].startswith("PF"):
185 | pin.well_name = "VDDA"
186 |
187 | class I2cIoExpander(Part):
188 | REFDES_PREFIX = "U"
189 |
190 | part_number = "TCA6416ARTWR"
191 | package = "QFN50P400X400X080-25N"
192 |
193 | PINS = [
194 | Pin("23", "VCCI"),
195 | Pin("21", "VCCP"),
196 | Pin("9", "GND"),
197 | Pin("25", "PAD"),
198 |
199 | Pin("19", "SCL"),
200 | Pin("20", "SDA"),
201 | Pin("22", "INT_L"),
202 | Pin("24", "RESET_L"),
203 |
204 | Pin("18", "A0"),
205 | ]
206 |
207 | for i in range(8):
208 | PINS.append(Pin(i + 1, "P0%d" % i))
209 |
210 | for i in range(8):
211 | PINS.append(Pin(i + 10, "P1%d" % i))
212 |
213 | class Mux(Part):
214 | REFDES_PREFIX = "U"
215 |
216 | part_number = "313-00929-00"
217 | package = "SOT65P210X110-6L"
218 |
219 | PINS = [
220 | Pin("5", "VCC"),
221 | Pin("2", "GND"),
222 |
223 | Pin("3", ("0", "IN0")),
224 | Pin("1", ("1", "IN1")),
225 |
226 | Pin("6", ("S0", "SEL")),
227 | Pin("4", ("Y", "OUT")),
228 | ]
229 |
230 | class OutputBuffer(Part):
231 | REFDES_PREFIX = "U"
232 |
233 | part_number = "SN74LVC1G126YZPR"
234 | package = "BGA5C50P3X2_141X91X50L"
235 |
236 | PINS = [
237 | Pin("A2", "VCC"),
238 | Pin("C1", "GND"),
239 |
240 | Pin("B1", ("A", "IN")),
241 | Pin("A1", ("OE", "SEL")),
242 | Pin("C2", ("Y", "OUT")),
243 | ]
244 |
245 | class LevelShifter(Part):
246 | """
247 | Bidirectional Level Shifter
248 |
249 | DIR=0 : B->A
250 | DIR=1 : A->B
251 | """
252 | REFDES_PREFIX = "U"
253 |
254 | PINS = [
255 | "VCCA",
256 | "VCCB",
257 | "GND",
258 | ]
259 |
260 | @property
261 | def direction_AB(self):
262 | return self.VCCA.net
263 |
264 | @property
265 | def direction_BA(self):
266 | return self.GND.net
267 |
268 | class LevelShifter1(LevelShifter):
269 | __doc__ = LevelShifter.__doc__
270 | part_number = "SN74AVC1T45DRLR"
271 | package = "SOP50P170X60-6N"
272 |
273 | PINS = [
274 | Pin("1", "VCCA", type=PinType.POWER_INPUT),
275 | Pin("6", "VCCB", type=PinType.POWER_INPUT),
276 | Pin("3", "A"),
277 | Pin("4", "B"),
278 | Pin("5", "DIR"),
279 | Pin("2", "GND", type=PinType.POWER_INPUT),
280 | ]
281 |
282 | class LevelShifter2(LevelShifter):
283 | __doc__ = LevelShifter.__doc__
284 | part_number = "SN74AVC2T245RSWR"
285 | package = "QFN40P145X185X55-10N"
286 |
287 | PINS = [
288 | Pin("7", "VCCA", type=PinType.POWER_INPUT),
289 | Pin("6", "VCCB", type=PinType.POWER_INPUT),
290 | Pin("2", "OE_L"),
291 | Pin("8", "A1"),
292 | Pin("9", "A2"),
293 | Pin("5", "B1"),
294 | Pin("4", "B2"),
295 | Pin("10", "DIR1"),
296 | Pin("1", "DIR2"),
297 | Pin("3", "GND", type=PinType.POWER_INPUT),
298 | ]
299 |
300 | class LevelShifter4(LevelShifter):
301 | __doc__ = LevelShifter.__doc__
302 | part_number = "SN74AVC4T774RSVR"
303 | package = "QFN40P265X185X55-16N"
304 |
305 | PINS = [
306 | Pin("14", "VCCA", type=PinType.POWER_INPUT),
307 | Pin("13", "VCCB", type=PinType.POWER_INPUT),
308 | Pin("7", "OE_L"),
309 | Pin("1", "A1"),
310 | Pin("2", "A2"),
311 | Pin("3", "A3"),
312 | Pin("4", "A4"),
313 | Pin("12", "B1"),
314 | Pin("11", "B2"),
315 | Pin("10", "B3"),
316 | Pin("9", "B4"),
317 | Pin("15", "DIR1"),
318 | Pin("16", "DIR2"),
319 | Pin("5", "DIR3"),
320 | Pin("6", "DIR4"),
321 | Pin("8", "GND", type=PinType.POWER_INPUT),
322 | ]
323 |
324 | class AnalogSwitch(Part):
325 | """
326 | Dual Analog Switch
327 |
328 | IN DIRECTION
329 | L NC -> COM
330 | H NO -> COM
331 | """
332 | REFDES_PREFIX = "U"
333 |
334 | part_number = "TS3A24159"
335 | package = "BGA10C50P4X3_186X136X50L"
336 |
337 | PINS = [
338 | Pin("D2", ("V+", "VCC")),
339 | Pin("A2", "GND"),
340 |
341 | Pin("B1", ("IN1", "SEL1")),
342 | Pin("C1", "COM1"),
343 | Pin("A1", "NC1"),
344 | Pin("D1", "NO1"),
345 |
346 | Pin("B3", ("IN2", "SEL2")),
347 | Pin("C3", "COM2"),
348 | Pin("A3", "NC2"),
349 | Pin("D3", "NO2"),
350 | ]
351 |
352 | class PowerSwitch(Part):
353 | REFDES_PREFIX = "U"
354 |
355 | part_number = "ADP194ACBZ-R7"
356 | package = "BGA4C40P2X2_80X80X56"
357 |
358 | PINS = [
359 | Pin("A1", ("IN", "IN1")),
360 | Pin("A2", ("OUT", "OUT1")),
361 | Pin("B1", "EN"),
362 | Pin("B2", "GND"),
363 | ]
364 | # End of things that should be in a generic library
365 |
366 | # Maybe this connector could be in a library too, since it's not too specific to this servo schematic
367 | class ServoConnector(make_connector(pin_count=50)):
368 | part_number = "AXK850145WG"
369 | package = "AXK850145WG"
370 |
371 | pin_names_match_nets = True
372 | pin_names_match_nets_prefix = "DUT_"
373 | PINS = [
374 | ("P1", "GND"),
375 | ("P2", "SPI2_CLK", "SPI2_SK"),
376 | ("P3", "SPI2_CS"),
377 | ("P4", "SPI2_MOSI", "SPI2_DI"),
378 | ("P5", "SPI2_MISO", "SPI2_DO"),
379 | ("P6", "SPI2_VREF"),
380 | ("P7", "SPI2_HOLD_L"),
381 | ("P8", "GND"),
382 | ("P9", "SPI1_CLK", "SPI1_SK"),
383 | ("P10", "SPI1_CS"),
384 | ("P11", "SPI1_MOSI", "SPI1_DI"),
385 | ("P12", "SPI1_MISO", "SPI1_DO"),
386 | ("P13", "SPI1_VREF"),
387 | ("P14", "EC_RESET_L", "COLD_RESET_L"),
388 | ("P15", "GND"),
389 | ("P16", "UART2_SERVO_DUT_TX", "UART2_RXD"),
390 | ("P17", "UART2_DUT_SERVO_TX", "UART2_TXD"),
391 | ("P18", "UART2_VREF"),
392 | ("P19", "SD_DETECT_L"),
393 | ("P20", "GND"),
394 | ("P21", "JTAG_TCK"),
395 | ("P22", "PWR_BUTTON"),
396 | ("P23", "JTAG_TMS"),
397 | ("P24", "JTAG_TDI"),
398 | ("P25", "JTAG_TDO"),
399 | ("P26", "JTAG_RTCK"),
400 | ("P27", "JTAG_TRST_L"),
401 | ("P28", "JTAG_SRST_L", "WARM_RESET_L"),
402 | ("P29", "JTAG_VREF"),
403 | ("P30", "REC_MODE_L", "GOOG_REC_MODE_L"),
404 | ("P31", "GND"),
405 | ("P32", "UART1_SERVO_DUT_TX", "UART1_RXD"),
406 | ("P33", "UART1_DUT_SERVO_TX", "UART1_TXD"),
407 | ("P34", "UART1_VREF"),
408 | ("P35", "I2C_3.3V"),
409 | ("P36", "GND"),
410 | ("P37", "I2C_SDA"),
411 | ("P38", "I2C_SCL"),
412 | ("P39", "HPD"),
413 | ("P40", "FW_WP", "MFG_MODE"),
414 | ("P41", "PROC_HOT_L", "FW_UPDATE_L", "FW_UP_L"),
415 | ("P42", "GND"),
416 | ("P43", "DEV_MODE"),
417 | ("P44", "LID_OPEN"),
418 | ("P45", "PCH_DISABLE_L", "CPU_NMI"),
419 | ("P46", "KBD_COL1"),
420 | ("P47", "KBD_COL2"),
421 | ("P48", "KBD_ROW1"),
422 | ("P49", "KBD_ROW2"),
423 | ("P50", "KBD_ROW3"),
424 | ]
425 | _postprocess_pin = Pin.second_name_important
426 |
427 | # The following part definitions are only related to this circuit
428 | class ProgrammingConnector(make_connector(8)):
429 | part_number = "FH34SRJ-8S-0.5SH(50)"
430 | package = "HRS_FH34SRJ-8S-0-5SH"
431 |
432 | PINS = [
433 | ("P1", "GND"),
434 | ("P2", "UART_TX"),
435 | ("P3", "UART_RX"),
436 | ("P6", "NRST"),
437 | ("P8", "BOOT0"),
438 | Pin("G", numbers=("G1", "G2")),
439 | ]
440 | _postprocess_pin = Pin.second_name_important
441 |
442 | class JtagConnector(make_connector(10)):
443 | part_number = "HDR_2X5_50MIL-210-00939-00-SAMTEC_FTSH-105-01"
444 | package = "SAMTEC_FTSH-105-01-L-DV-K"
445 |
446 |
447 | pin_names_match_nets = True
448 | pin_names_match_nets_prefix = "DUT_JTAG_"
449 | PINS = [
450 | ("P1", "VCC"),
451 | ("P2", "TMS", "SWDIO"),
452 | ("P3", "GND"),
453 | ("P4", "TCK", "SWDCLK"),
454 | ("P5", "GND"),
455 | ("P6", "TDO", "SWO"),
456 | ("P7", "KEY"),
457 | ("P8", "TDI"),
458 | ("P9", "GNDDetect"),
459 | ("P10", "RESET"),
460 | ]
461 | _postprocess_pin = Pin.second_name_important
462 |
463 | class ServoEC(STM32F072):
464 | pin_names_match_nets = True
465 | PINS = [
466 | Pin(("PA0", "UART3_TX")),
467 | Pin(("PA1", "UART3_RX")),
468 | Pin(("PA2", "UART1_TX")),
469 | Pin(("PA3", "UART1_RX")),
470 | Pin(("PA4", "SERVO_JTAG_TMS")),
471 | Pin(("PA5", "SPI1_MUX_SEL")),
472 | Pin(("PA6", "SERVO_JTAG_TDO_BUFFER_EN")),
473 | Pin(("PA7", "SERVO_JTAG_TDI")),
474 |
475 | Pin(("PA8", "UART1_EN_L")),
476 | Pin(("PA9", "EC_UART_TX")),
477 | Pin(("PA10", "EC_UART_RX")),
478 | Pin(("PA11", "USB_DM")),
479 | Pin(("PA12", "USB_DP")),
480 | Pin(("PA13", "SERVO_JTAG_TRST_L")),
481 | Pin(("PA14", "SPI1_BUF_EN_L")),
482 | Pin(("PA15", "SPI2_BUF_EN_L")),
483 |
484 | Pin(("PB0", "UART2_EN_L")),
485 | Pin(("PB1", "SERVO_JTAG_RTCK")),
486 | Pin(("PB2", "SPI1_VREF_33")),
487 | Pin(("PB3", "SPI1_VREF_18")),
488 | Pin(("PB4", "SPI2_VREF_33")),
489 | Pin(("PB5", "SPI2_VREF_18")),
490 | Pin(("PB6", "SERVO_JTAG_TRST_DIR")),
491 | Pin(("PB7", "SERVO_JTAG_TDI_DIR")),
492 |
493 | Pin(("PB8", "MASTER_I2C_SCL")),
494 | Pin(("PB9", "MASTER_I2C_SDA")),
495 | Pin(("PB10", "UART2_TX")),
496 | Pin(("PB11", "UART2_RX")),
497 | Pin(("PB12", "SERVO_SPI_CS")),
498 | Pin(("PB13", "SERVO_TO_SPI1_MUX_CLK")),
499 | Pin(("PB14", "SERVO_TO_SPI1_MUX_MISO")),
500 | Pin(("PB15", "SERVO_SPI_MOSI")),
501 |
502 | Pin(("PC13", "RESET_L")),
503 | Pin(("PC14", "SERVO_JTAG_TMS_DIR")),
504 | Pin(("PC15", "SERVO_JTAG_TDO_SEL")),
505 |
506 | Pin(("PF0", "JTAG_BUFOUT_EN_L")),
507 | Pin(("PF1", "JTAG_BUFIN_EN_L")),
508 | ]
509 | _postprocess_pin = Pin.second_name_important
510 |
511 | # Start of actual schematic
512 | vbus_in = Net("VBUS_IN")
513 | gnd = Net("GND")
514 | def decoupling(value="100n", package=None):
515 | if package is None:
516 | package = "CAPC0603X33L"
517 |
518 | if "u" in value:
519 | package = "CAPC1005X71L"
520 |
521 | if "0u" in value:
522 | package = "CAPC1608X80L"
523 |
524 | return C(value, to=gnd, package=package, part_number="CY" + value) #defined_at: not here
525 | old_R = R
526 | def R(value, to):
527 | return old_R(value, package="RESC0603X23L", part_number="R" + value, to=to) #defined_at: not here
528 |
529 | # usb stuff
530 | usb = UsbConnector()
531 | usb_esd = UsbEsdDiode()
532 | Net("USB_DP") << usb.DP << usb_esd.P1
533 | Net("USB_DM") << usb.DM << usb_esd.P2 >> usb_esd.NC
534 | vbus_in << usb.VBUS << usb_esd.VCC
535 | gnd << usb.GND << usb.G << usb_esd.GND
536 | # We could make this type-c instead!
537 |
538 | # 3300 regulator
539 | pp3300 = Net("PP3300")
540 | reg3300 = MIC5504()
541 | vbus_in << (
542 | reg3300.IN, decoupling("2.2u"),
543 | reg3300.EN,
544 | )
545 | gnd << reg3300.GND
546 | pp3300 << (
547 | reg3300.OUT,
548 | decoupling("10u"),
549 | decoupling(),
550 | decoupling("1000p"),
551 | )
552 |
553 | # 1800 regulator
554 | pp1800 = Net("PP1800")
555 | reg1800 = TLV70018DSER()
556 | drop_diode = DoubleDiode()
557 | pp3300 << drop_diode.A1 << drop_diode.A2
558 | Net("PP1800_VIN") << (
559 | drop_diode.K,
560 | reg1800.IN, decoupling(),
561 | reg1800.EN
562 | )
563 | gnd << reg1800.GND
564 | pp1800 << reg1800.OUT << decoupling("1u", "CAPC0603X33L")
565 |
566 | ec = ServoEC()
567 | usb.DP << ec
568 | usb.DM << ec
569 |
570 | # ec power
571 | pp3300 << (
572 | ec.VBAT, decoupling(),
573 | ec.VDD, decoupling(),
574 | decoupling("4.7u"),
575 | )
576 | Net("PP3300_PD_VDDA") << (
577 | ec.VDDA,
578 | L("600@100MHz", to=pp3300, package="INDC1005L", part_number="FERRITE_BEAD-185-00019-00"),
579 | decoupling("1u"),
580 | decoupling("100p"),
581 | )
582 | pp3300 << (
583 | ec.VDDIO2, decoupling(),
584 | decoupling("4.7u"),
585 | )
586 | gnd << ec.VSS << ec.VSSA << ec.PAD
587 |
588 | # ec programming/debug
589 | prog = ProgrammingConnector()
590 | gnd << prog.GND << prog.G
591 | Net("PD_NRST_L") << (
592 | ec.NRST,
593 | prog.NRST,
594 | decoupling(),
595 | )
596 | boot0 = Net("PD_BOOT0")
597 | boot0_q = FET("CSD13381F4", package="DFN100X60X35-3L")
598 | # Use OTG + A-TO-A cable to go to bootloader mode
599 | Net("USB_ID") >> boot0_q.G >> R("51.1k", to=vbus_in) << usb.ID
600 | boot0 << boot0_q.D << R("51.1k", to=vbus_in) << ec.BOOT0 << prog.BOOT0
601 | gnd << boot0_q.S
602 | Net("EC_UART_TX") << ec << prog.UART_TX
603 | Net("EC_UART_RX") << ec << prog.UART_RX
604 |
605 | ppdut_spi_vrefs = {
606 | 1: Net("PPDUT_SPI1_VREF"),
607 | 2: Net("PPDUT_SPI2_VREF"),
608 | }
609 |
610 | jtag_buffer_to_servo_tdo = Net("JTAG_BUFFER_TO_SERVO_TDO") >> ec.UART3_RX # also Net("UART3_TX")
611 | servo_jtag_tck = Net("SERVO_JTAG_TCK") << ec.UART3_TX # also Net("UART3_TX")
612 |
613 | dut = ServoConnector()
614 | gnd << dut.GND
615 | pp3300 >> dut.pins["I2C_3.3V"]
616 |
617 | io = I2cIoExpander()
618 | pp3300 << io.VCCI << decoupling()
619 | gnd << io.GND << io.PAD
620 | gnd << io.A0 # i2c addr 7'H=0x20
621 | Net("I2C_REMOTE_ADC_SDA") << R("4.7k", to=pp3300) << ec.MASTER_I2C_SDA << io.SDA << dut.I2C_SDA
622 | Net("I2C_REMOTE_ADC_SCL") << R("4.7k", to=pp3300) << ec.MASTER_I2C_SCL << io.SCL << dut.I2C_SCL
623 | Net("RESET_L") << io.RESET_L << ec
624 | pp1800 << io.VCCP << decoupling()
625 |
626 | dut_mfg_mode = Net("DUT_MFG_MODE") << dut
627 | mfg_mode_shifter = LevelShifter1()
628 | gnd << mfg_mode_shifter.GND
629 |
630 | Net("FW_WP_EN") << mfg_mode_shifter.VCCA << io.P00 << decoupling() << R("4.7k", to=gnd)
631 | Net("FTDI_MFG_MODE") << io.P01 << mfg_mode_shifter.A
632 | dut_mfg_mode << io.P02
633 | io.P03 << TP(package="TP075") # spare
634 | Net("SPI_HOLD_L") << io.P04 >> dut.SPI2_HOLD_L
635 | Net("DUT_COLD_RESET_L") << io.P05 >> dut
636 | Net("DUT_PWR_BUTTON") << io.P06 >> dut
637 |
638 | Net("DUT_GOOG_REC_MODE_L") << io.P10 >> dut
639 | dut_mfg_mode << io.P11
640 | Net("HPD") << io.P12 >> dut
641 | Net("FW_UP_L") << io.P13 >> dut
642 | Net("DUT_LID_OPEN") << io.P14 >> dut
643 | Net("DUT_DEV_MODE") << io.P15 >> dut
644 | Net("PCH_DISABLE_L") << io.P16 >> dut
645 | io.P17 << TP(package="TP075") # spare
646 |
647 | mfg_mode_shifter.direction_AB << mfg_mode_shifter.DIR
648 | ppdut_spi_vrefs[2] >> mfg_mode_shifter.VCCB << decoupling()
649 | Net("DUT_MFG_MODE_BUF") << R("0", to=dut_mfg_mode) >> mfg_mode_shifter.B
650 |
651 | # JTAG
652 | jtag_connector = JtagConnector()
653 | gnd >> jtag_connector.GND
654 | Net("DUT_WARM_RESET_L") << io.P07 >> dut << jtag_connector.RESET
655 |
656 | jtag_vref = Net("PPDUT_JTAG_VREF")
657 | jtag_vref << dut.JTAG_VREF >> jtag_connector.VCC
658 |
659 | shifter1 = LevelShifter4()
660 | pp3300 >> shifter1.VCCA << decoupling()
661 | jtag_vref >> shifter1.VCCB << decoupling()
662 | gnd >> shifter1.GND
663 |
664 | shifter2 = LevelShifter4()
665 | pp3300 >> shifter2.VCCA << decoupling()
666 | jtag_vref >> shifter2.VCCB << decoupling()
667 | gnd >> shifter2.GND
668 |
669 | jtag_mux = Mux()
670 | pp3300 >> jtag_mux.VCC << decoupling()
671 | gnd >> jtag_mux.GND
672 | Net("SERVO_JTAG_TDO_SEL") << ec >> jtag_mux.SEL
673 |
674 | jtag_output_buffer = OutputBuffer()
675 | pp3300 >> jtag_output_buffer.VCC << decoupling()
676 | gnd >> jtag_output_buffer.GND
677 | Net("SERVO_JTAG_TDO_BUFFER_EN") << ec >> jtag_output_buffer.OE
678 | Net("SERVO_JTAG_MUX_TDO") << jtag_mux.OUT >> jtag_output_buffer.IN
679 | jtag_buffer_to_servo_tdo << jtag_output_buffer.OUT
680 |
681 | Net("JTAG_BUFOUT_EN_L") << ec >> shifter1.OE_L
682 | Net("JTAG_BUFIN_EN_L") << ec >> shifter2.OE_L
683 |
684 | pp3300 >> shifter1.A1 # spare
685 | Net("SERVO_JTAG_TRST_L") << ec << shifter1.A2
686 | Net("SERVO_JTAG_TMS") << ec << shifter1.A3
687 | Net("SERVO_JTAG_TDI") << ec << shifter1.A4
688 |
689 | shifter1.direction_AB >> shifter1.DIR1 # spare
690 | Net("SERVO_JTAG_TRST_DIR") << ec >> shifter1.DIR2
691 | Net("SERVO_JTAG_TMS_DIR") << ec >> shifter1.DIR3
692 | Net("SERVO_JTAG_TDI_DIR") << ec >> shifter1.DIR4
693 |
694 | shifter1.B1 # spare
695 | Net("DUT_JTAG_TRST_L") << dut << shifter1.B2
696 | Net("DUT_JTAG_TMS") >> dut << shifter1.B3 << jtag_connector
697 | Net("DUT_JTAG_TDI") << dut << shifter1.B4 >> shifter2.B3 >> jtag_connector
698 |
699 | Net("DUT_JTAG_TDO") << dut >> shifter2.B1 >> jtag_connector
700 | Net("DUT_JTAG_RTCK") << dut >> shifter2.B2
701 | Net("DUT_JTAG_TCK") << dut >> shifter2.B4 >> jtag_connector
702 |
703 | shifter2.direction_BA >> shifter2.DIR1
704 | shifter2.direction_BA >> shifter2.DIR2
705 | shifter2.direction_BA >> shifter2.DIR3
706 | shifter2.direction_AB >> shifter2.DIR4
707 |
708 | Net("SERVO_JTAG_TDO") << shifter2.A1 >> jtag_mux.IN0
709 | Net("SERVO_JTAG_RTCK") >> ec << shifter2.A2
710 | Net("SERVO_JTAG_SWDIO") << shifter2.A3 >> jtag_mux.IN1
711 | servo_jtag_tck << shifter2.A4
712 |
713 | # SPI1 & 2
714 | # TODO SERVO_TO_SPI1_MUX_CLK
715 | servo_spi_mosi = Net("SERVO_SPI_MOSI") << ec
716 | servo_spi_cs = Net("SERVO_SPI_CS") << ec
717 |
718 | # Since the circuits look so similar, we'll just have a loop
719 | spi_shifters = {
720 | 1: LevelShifter4(),
721 | 2: LevelShifter4(),
722 | }
723 | for i, s in spi_shifters.items():
724 | # Power supply
725 | vref = ppdut_spi_vrefs[i]
726 | vref << dut.pins["SPI%d_VREF" % i]
727 |
728 | power_switches = [
729 | ("18", pp1800, PowerSwitch()),
730 | ("33", pp3300, PowerSwitch()),
731 | ]
732 | for voltage, input_rail, power_switch in power_switches:
733 | gnd << power_switch.GND
734 | Net("SPI%d_VREF_%s" % (i, voltage)) << ec >> power_switch.EN << R("4.7k", to=gnd)
735 | input_rail << power_switch.IN
736 | vref << power_switch.OUT
737 |
738 | # Level shifter setup
739 | pp3300 >> s.VCCA << decoupling()
740 | vref >> s.VCCB << decoupling()
741 | gnd >> s.GND
742 | Net("SPI%d_BUF_EN_L" % i) << ec >> s.OE_L
743 |
744 | # MISO
745 | Net("DUT_SPI%d_MISO" % i) << dut >> s.B1
746 | s.direction_BA >> s.DIR1
747 | # A side connected after this loop
748 |
749 | # MOSI
750 | servo_spi_mosi >> s.A2
751 | s.direction_AB >> s.DIR2
752 | Net("DUT_SPI%d_MOSI" % i) << dut >> s.B2
753 |
754 | # CS
755 | servo_spi_cs >> s.A3
756 | s.direction_AB >> s.DIR3
757 | Net("DUT_SPI%d_CS" % i) << dut >> s.B3
758 |
759 | # CLK
760 | # A side connected after this loop
761 | s.direction_AB >> s.DIR4
762 | Net("DUT_SPI%d_CLK" % i) << dut >> s.B4
763 |
764 | spi1_mux = AnalogSwitch()
765 | pp3300 >> spi1_mux.VCC >> decoupling()
766 | gnd >> spi1_mux.GND
767 | Net("SPI1_MUX_SEL") << ec >> spi1_mux.SEL1 >> spi1_mux.SEL2
768 |
769 | Net("SPI_MUX_TODUT_SPI1_MISO") >> spi1_mux.COM1 << spi_shifters[1].A1
770 | Net("SPI_MUX_TO_DUT_SPI1_CLK") << spi1_mux.COM2 >> spi_shifters[1].A4
771 |
772 | Net("SERVO_TO_SPI1_MUX_MISO") << spi1_mux.NO1 << spi_shifters[2].A1 >> ec
773 | Net("SERVO_TO_SPI1_MUX_CLK") >> spi1_mux.NO2 >> spi_shifters[2].A4 << ec
774 |
775 | jtag_buffer_to_servo_tdo << spi1_mux.NC1
776 | servo_jtag_tck >> spi1_mux.NC2
777 |
778 | # UART 1 & 2
779 | uart_shifters = {
780 | 1: LevelShifter2(),
781 | 2: LevelShifter2(),
782 | }
783 | for i, s in uart_shifters.items():
784 | vref = Net("PPDUT_UART%d_VREF" % i)
785 | vref << dut.pins["UART%d_VREF" % i]
786 |
787 | # Power off to VCCA or VCCB provides isolation
788 | pp3300 >> s.VCCA << decoupling()
789 | vref >> s.VCCB << decoupling()
790 | gnd >> s.GND
791 | Net("UART%d_EN_L" % i) << ec >> s.OE_L
792 |
793 | Net("UART%d_TX" % i) << ec >> s.A1
794 | s.direction_AB >> s.DIR1
795 | Net("UART%d_SERVO_DUT_TX" % i) >> dut << s.B1
796 |
797 | Net("UART%d_DUT_SERVO_TX" % i) << dut >> s.B2
798 | s.direction_BA >> s.DIR2
799 | Net("UART%d_RX" % i) >> ec << s.A2
800 |
801 | global_context.autoname("servo_micro.refdes_mapping")
802 |
--------------------------------------------------------------------------------