add pinmux docs
[pinmux.git] / src / spec / jtag.py
1 """JTAG interface
2
3 using Staf Verhaegen (Chips4Makers) wishbone TAP
4
5 Pinmux documented here https://libre-soc.org/docs/pinmux/
6 """
7
8 from nmigen.build.res import ResourceManager
9 from nmigen.hdl.rec import Layout
10 from collections import OrderedDict
11 from nmigen.cli import rtlil
12
13 from nmigen import (Module, Signal, Elaboratable, Cat)
14 from c4m.nmigen.jtag.tap import IOType, TAP
15
16 # map from pinmux to c4m jtag iotypes
17 iotypes = {'-': IOType.In,
18 '+': IOType.Out,
19 '>': IOType.TriOut,
20 '*': IOType.InTriOut,
21 }
22
23 # Resources
24 # nmigen Resources has a different encoding for direction: "i", "o", "io", "oe"
25 resiotypes = {'i': IOType.In,
26 'o': IOType.Out,
27 'oe': IOType.TriOut,
28 'io': IOType.InTriOut,
29 }
30
31 # How many bits in each signal type
32 scanlens = {IOType.In: 1,
33 IOType.Out: 1,
34 IOType.TriOut: 2,
35 IOType.InTriOut: 3,
36 }
37
38
39 def dummy_pinset():
40 # sigh this needs to come from pinmux.
41 gpios = []
42 for i in range(16):
43 gpios.append("%d*" % i)
44 return {'uart': ['tx+', 'rx-'],
45 'gpio': gpios,
46 'i2c': ['sda*', 'scl+']}
47
48
49 # TODO: move to suitable location
50 class Pins:
51 """declare a list of pins, including name and direction. grouped by fn
52 the pin dictionary needs to be in a reliable order so that the JTAG
53 Boundary Scan is also in a reliable order
54 """
55 def __init__(self, pindict=None):
56 if pindict is None:
57 pindict = {}
58 self.io_names = OrderedDict()
59 if isinstance(pindict, OrderedDict):
60 self.io_names.update(pindict)
61 else:
62 keys = list(pindict.keys())
63 keys.sort()
64 for k in keys:
65 self.io_names[k] = pindict[k]
66
67 def __iter__(self):
68 # start parsing io_names and enumerate them to return pin specs
69 scan_idx = 0
70 for fn, pins in self.io_names.items():
71 for pin in pins:
72 # decode the pin name and determine the c4m jtag io type
73 name, pin_type = pin[:-1], pin[-1]
74 iotype = iotypes[pin_type]
75 pin_name = "%s_%s" % (fn, name)
76 yield (fn, name, iotype, pin_name, scan_idx)
77 scan_idx += scanlens[iotype] # inc boundary reg scan offset
78
79
80 def recurse_down(asicpad, jtagpad):
81 """recurse_down: messy ASIC-to-JTAG pad matcher which expects
82 at some point some Records named i, o and oe, and wires them
83 up in the right direction according to those names. "i" for
84 input must come *from* the ASIC pad and connect *to* the JTAG pad
85 """
86 eqs = []
87 for asiclayout, jtaglayout in zip(asicpad.layout, jtagpad.layout):
88 apad = getattr(asicpad, asiclayout[0])
89 jpad = getattr(jtagpad, jtaglayout[0])
90 print ("recurse_down", asiclayout, jtaglayout, apad, jpad)
91 if isinstance(asiclayout[1], Layout):
92 eqs += recurse_down(apad, jpad)
93 elif asiclayout[0] == 'i':
94 eqs.append(jpad.eq(apad))
95 elif asiclayout[0] in ['o', 'oe']:
96 eqs.append(apad.eq(jpad))
97 return eqs
98
99
100 class JTAG(TAP, Pins):
101 # 32-bit data width here. use None to not add a wishbone interface
102 def __init__(self, pinset, domain, wb_data_wid=32, resources=None):
103 if resources is None:
104 resources = []
105 self.domain = domain
106 TAP.__init__(self, ir_width=4)
107 Pins.__init__(self, pinset)
108
109 # enumerate pin specs and create IOConn Records.
110 # we store the boundary scan register offset in the IOConn record
111 self.ios = {} # these are enumerated in external_ports
112 self.scan_len = 0
113 self.add_pins(list(self))
114
115 # this is redundant. or maybe part of testing, i don't know.
116 self.sr = self.add_shiftreg(ircode=4, length=3,
117 domain=domain)
118
119 # create and connect wishbone
120 if wb_data_wid is not None:
121 self.wb = self.add_wishbone(ircodes=[5, 6, 7], features={'err'},
122 address_width=30, data_width=wb_data_wid,
123 granularity=8, # 8-bit wide
124 name="jtag_wb",
125 domain=domain)
126
127 # create DMI2JTAG (goes through to dmi_sim())
128 self.dmi = self.add_dmi(ircodes=[8, 9, 10],
129 domain=domain)
130
131 # use this for enable/disable of parts of the ASIC.
132 # XXX make sure to add the _en sig to en_sigs list
133 self.wb_icache_en = Signal(reset=1)
134 self.wb_dcache_en = Signal(reset=1)
135 self.wb_sram_en = Signal(reset=1)
136 self.en_sigs = en_sigs = Cat(self.wb_icache_en, self.wb_dcache_en,
137 self.wb_sram_en)
138 self.sr_en = self.add_shiftreg(ircode=11, length=len(en_sigs),
139 domain=domain)
140
141 # Platform Resource Mirror: enumerated by boundary_elaborate()
142 # in order to make a transparent/auto wire-up of what would
143 # normally be directly connected to IO Pads, to go instead
144 # first through a JTAG Boundary Scan... *and then* get auto-
145 # connected on ultimately to the IO Pads. to do that, the best
146 # API is one that reflects that of Platforms... and that means
147 # using duplicate ResourceManagers so that the user may use
148 # the exact same resource-requesting function, "request", and
149 # may also use the exact same Resource list
150
151 self.pad_mgr = ResourceManager([], [])
152 self.core_mgr = ResourceManager([], [])
153 self.pad_mgr.add_resources(resources)
154 self.core_mgr.add_resources(resources)
155
156 # record resource lookup between core IO names and pads
157 self.padlookup = {}
158 self.requests_made = []
159 self.boundary_scan_pads = []
160 self.resource_table = {}
161 self.resource_table_pads = {}
162 self.eqs = [] # list of BS to core/pad connections
163
164 # allocate all resources in advance in pad/core ResourceManagers
165 # this is because whilst a completely new (different) platform is
166 # passed in to elaborate()s every time, that cannot happen with
167 # JTAG Boundary scanning: the resources are allocated *prior*
168 # to elaborate() being called [from Simulation(), Platform.build(),
169 # and many other sources, multiple times]
170
171 for resource in resources:
172 print ("JTAG resource", resource)
173 if resource.name in ['clk', 'rst']: # hack
174 continue
175 self.add_jtag_resource(resource.name, resource.number)
176
177 def add_pins(self, pinlist):
178 for fn, pin, iotype, pin_name, scan_idx in pinlist:
179 io = self.add_io(iotype=iotype, name=pin_name)
180 io._scan_idx = scan_idx # hmm shouldn't really do this
181 self.scan_len += scan_idx # record full length of boundary scan
182 self.ios[pin_name] = io
183
184 def elaborate(self, platform):
185 m = super().elaborate(platform)
186 m.d.comb += self.sr.i.eq(self.sr.o) # loopback as part of test?
187
188 # provide way to enable/disable wishbone caches and SRAM
189 # just in case of issues
190 # see https://bugs.libre-soc.org/show_bug.cgi?id=520
191 with m.If(self.sr_en.oe):
192 m.d.sync += self.en_sigs.eq(self.sr_en.o)
193 # also make it possible to read the enable/disable current state
194 with m.If(self.sr_en.ie):
195 m.d.comb += self.sr_en.i.eq(self.en_sigs)
196
197 # create a fake "stall"
198 #wb = self.wb
199 #m.d.comb += wb.stall.eq(wb.cyc & ~wb.ack) # No burst support
200
201 return m
202
203 def boundary_elaborate(self, m, platform):
204 jtag_resources = self.pad_mgr.resources
205 core_resources = self.core_mgr.resources
206
207 # platform requested: make the exact same requests,
208 # then add JTAG afterwards
209 if platform is not None:
210 for (name, number, dir, xdr) in self.requests_made:
211 asicpad = platform.request(name, number, dir=dir, xdr=xdr)
212 jtagpad = self.resource_table_pads[(name, number)]
213 print ("jtagpad", jtagpad, jtagpad.layout)
214 m.d.comb += recurse_down(asicpad, jtagpad)
215
216 # wire up JTAG otherwise we are in trouble
217 jtag = platform.request('jtag')
218 m.d.comb += self.bus.tdi.eq(jtag.tdi)
219 m.d.comb += self.bus.tck.eq(jtag.tck)
220 m.d.comb += self.bus.tms.eq(jtag.tms)
221 m.d.comb += jtag.tdo.eq(self.bus.tdo)
222
223 # add the eq assignments connecting up JTAG boundary scan to core
224 m.d.comb += self.eqs
225 return m
226
227 def external_ports(self):
228 """create a list of ports that goes into the top level il (or verilog)
229 """
230 ports = super().external_ports() # gets JTAG signal names
231 ports += list(self.wb.fields.values()) # wishbone signals
232 for io in self.ios.values():
233 ports += list(io.core.fields.values()) # io "core" signals
234 ports += list(io.pad.fields.values()) # io "pad" signals"
235 return ports
236
237 def ports(self):
238 return list(self.iter_ports())
239
240 def iter_ports(self):
241 yield self.bus.tdi
242 yield self.bus.tdo
243 yield self.bus.tck
244 yield self.bus.tms
245 yield from self.boundary_scan_pads
246
247 def request(self, name, number=0, *, dir=None, xdr=None):
248 """looks like ResourceManager.request but can be called multiple times.
249 """
250 return self.resource_table[(name, number)]
251
252 def add_jtag_resource(self, name, number=0, *, dir=None, xdr=None):
253 """request a Resource (e.g. name="uart", number=0) which will
254 return a data structure containing Records of all the pins.
255
256 this override will also - automatically - create a JTAG Boundary Scan
257 connection *without* any change to the actual Platform.request() API
258 """
259 pad_mgr = self.pad_mgr
260 core_mgr = self.core_mgr
261 padlookup = self.padlookup
262 # okaaaay, bit of shenanigens going on: the important data structure
263 # here is Resourcemanager._ports. requests add to _ports, which is
264 # what needs redirecting. therefore what has to happen is to
265 # capture the number of ports *before* the request. sigh.
266 start_ports = len(core_mgr._ports)
267 value = core_mgr.request(name, number, dir=dir, xdr=xdr)
268 end_ports = len(core_mgr._ports)
269
270 # take a copy of the requests made
271 self.requests_made.append((name, number, dir, xdr))
272
273 # now make a corresponding (duplicate) request to the pad manager
274 # BUT, if it doesn't exist, don't sweat it: all it means is, the
275 # application did not request Boundary Scan for that resource.
276 pad_start_ports = len(pad_mgr._ports)
277 pvalue = pad_mgr.request(name, number, dir=dir, xdr=xdr)
278 pad_end_ports = len(pad_mgr._ports)
279
280 # ok now we have the lengths: now create a lookup between the pad
281 # and the core, so that JTAG boundary scan can be inserted in between
282 core = core_mgr._ports[start_ports:end_ports]
283 pads = pad_mgr._ports[pad_start_ports:pad_end_ports]
284 # oops if not the same numbers added. it's a duplicate. shouldn't happen
285 assert len(core) == len(pads), "argh, resource manager error"
286 print ("core", core)
287 print ("pads", pads)
288
289 # pad/core each return a list of tuples of (res, pin, port, attrs)
290 for pad, core in zip(pads, core):
291 # create a lookup on pin name to get at the hidden pad instance
292 # this pin name will be handed to get_input, get_output etc.
293 # and without the padlookup you can't find the (duplicate) pad.
294 # note that self.padlookup and self.ios use the *exact* same
295 # pin.name per pin
296 padpin = pad[1]
297 corepin = core[1]
298 if padpin is None: continue # skip when pin is None
299 assert corepin is not None # if pad was None, core should be too
300 print ("iter", pad, padpin.name)
301 print ("existing pads", padlookup.keys())
302 assert padpin.name not in padlookup # no overwrites allowed!
303 assert padpin.name == corepin.name # has to be the same!
304 padlookup[padpin.name] = pad # store pad by pin name
305
306 # now add the IO Shift Register. first identify the type
307 # then request a JTAG IOConn. we can't wire it up (yet) because
308 # we don't have a Module() instance. doh. that comes in get_input
309 # and get_output etc. etc.
310 iotype = resiotypes[padpin.dir] # look up the C4M-JTAG IOType
311 io = self.add_io(iotype=iotype, name=padpin.name) # IOConn
312 self.ios[padpin.name] = io # store IOConn Record by pin name
313
314 # and connect up core to pads based on type. could create
315 # Modules here just like in Platform.get_input/output but
316 # in some ways it is clearer by being simpler to wire them globally
317
318 if padpin.dir == 'i':
319 print ("jtag_request add input pin", padpin)
320 print (" corepin", corepin)
321 print (" jtag io core", io.core)
322 print (" jtag io pad", io.pad)
323 # corepin is to be returned, here. so, connect jtag corein to it
324 self.eqs += [corepin.i.eq(io.core.i)]
325 # and padpin to JTAG pad
326 self.eqs += [io.pad.i.eq(padpin.i)]
327 self.boundary_scan_pads.append(padpin.i)
328 elif padpin.dir == 'o':
329 print ("jtag_request add output pin", padpin)
330 print (" corepin", corepin)
331 print (" jtag io core", io.core)
332 print (" jtag io pad", io.pad)
333 # corepin is to be returned, here. connect it to jtag core out
334 self.eqs += [io.core.o.eq(corepin.o)]
335 # and JTAG pad to padpin
336 self.eqs += [padpin.o.eq(io.pad.o)]
337 self.boundary_scan_pads.append(padpin.o)
338 elif padpin.dir == 'io':
339 print ("jtag_request add io pin", padpin)
340 print (" corepin", corepin)
341 print (" jtag io core", io.core)
342 print (" jtag io pad", io.pad)
343 # corepin is to be returned, here. so, connect jtag corein to it
344 self.eqs += [corepin.i.eq(io.core.i)]
345 # and padpin to JTAG pad
346 self.eqs += [io.pad.i.eq(padpin.i)]
347 # corepin is to be returned, here. connect it to jtag core out
348 self.eqs += [io.core.o.eq(corepin.o)]
349 # and JTAG pad to padpin
350 self.eqs += [padpin.o.eq(io.pad.o)]
351 # corepin is to be returned, here. connect it to jtag core out
352 self.eqs += [io.core.oe.eq(corepin.oe)]
353 # and JTAG pad to padpin
354 self.eqs += [padpin.oe.eq(io.pad.oe)]
355
356 self.boundary_scan_pads.append(padpin.i)
357 self.boundary_scan_pads.append(padpin.o)
358 self.boundary_scan_pads.append(padpin.oe)
359
360 # finally record the *CORE* value just like ResourceManager.request()
361 # so that the module using this can connect to *CORE* i/o to the
362 # resource. pads are taken care of
363 self.resource_table[(name, number)] = value
364
365 # and the *PAD* value so that it can be wired up externally as well
366 self.resource_table_pads[(name, number)] = pvalue
367
368 if __name__ == '__main__':
369 pinset = dummy_pinset()
370 dut = JTAG(pinset, "sync")
371
372 vl = rtlil.convert(dut)
373 with open("test_jtag.il", "w") as f:
374 f.write(vl)
375