sort out mess of trying to access the Pin resource instead of the pad
[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, defaultdict
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 = defaultdict(dict)
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 self.asic_resources = {}
207
208 # platform requested: make the exact same requests,
209 # then add JTAG afterwards
210 if platform is not None:
211 for (name, number, dir, xdr) in self.requests_made:
212 asicpad = platform.request(name, number, dir=dir, xdr=xdr)
213 self.asic_resources[(name, number)] = asicpad
214 jtagpad = self.resource_table_pads[(name, number)]
215 print ("jtagpad", jtagpad, jtagpad.layout)
216 m.d.comb += recurse_down(asicpad, jtagpad)
217
218 # wire up JTAG otherwise we are in trouble
219 jtag = platform.request('jtag')
220 m.d.comb += self.bus.tdi.eq(jtag.tdi)
221 m.d.comb += self.bus.tck.eq(jtag.tck)
222 m.d.comb += self.bus.tms.eq(jtag.tms)
223 m.d.comb += jtag.tdo.eq(self.bus.tdo)
224
225 # add the eq assignments connecting up JTAG boundary scan to core
226 m.d.comb += self.eqs
227 return m
228
229 def external_ports(self):
230 """create a list of ports that goes into the top level il (or verilog)
231 """
232 ports = super().external_ports() # gets JTAG signal names
233 ports += list(self.wb.fields.values()) # wishbone signals
234 for io in self.ios.values():
235 ports += list(io.core.fields.values()) # io "core" signals
236 ports += list(io.pad.fields.values()) # io "pad" signals"
237 return ports
238
239 def ports(self):
240 return list(self.iter_ports())
241
242 def iter_ports(self):
243 yield self.bus.tdi
244 yield self.bus.tdo
245 yield self.bus.tck
246 yield self.bus.tms
247 for pad in self.boundary_scan_pads.values():
248 yield from pad.values()
249
250 def request(self, name, number=0, *, dir=None, xdr=None):
251 """looks like ResourceManager.request but can be called multiple times.
252 """
253 return self.resource_table[(name, number)]
254
255 def add_jtag_resource(self, name, number=0, *, dir=None, xdr=None):
256 """request a Resource (e.g. name="uart", number=0) which will
257 return a data structure containing Records of all the pins.
258
259 this override will also - automatically - create a JTAG Boundary Scan
260 connection *without* any change to the actual Platform.request() API
261 """
262 pad_mgr = self.pad_mgr
263 core_mgr = self.core_mgr
264 padlookup = self.padlookup
265 # okaaaay, bit of shenanigens going on: the important data structure
266 # here is Resourcemanager._ports. requests add to _ports, which is
267 # what needs redirecting. therefore what has to happen is to
268 # capture the number of ports *before* the request. sigh.
269 start_ports = len(core_mgr._ports)
270 value = core_mgr.request(name, number, dir=dir, xdr=xdr)
271 end_ports = len(core_mgr._ports)
272
273 # take a copy of the requests made
274 self.requests_made.append((name, number, dir, xdr))
275
276 # now make a corresponding (duplicate) request to the pad manager
277 # BUT, if it doesn't exist, don't sweat it: all it means is, the
278 # application did not request Boundary Scan for that resource.
279 pad_start_ports = len(pad_mgr._ports)
280 pvalue = pad_mgr.request(name, number, dir=dir, xdr=xdr)
281 pad_end_ports = len(pad_mgr._ports)
282
283 # ok now we have the lengths: now create a lookup between the pad
284 # and the core, so that JTAG boundary scan can be inserted in between
285 core = core_mgr._ports[start_ports:end_ports]
286 pads = pad_mgr._ports[pad_start_ports:pad_end_ports]
287 # oops if not the same numbers added. it's a duplicate. shouldn't happen
288 assert len(core) == len(pads), "argh, resource manager error"
289 print ("core", core)
290 print ("pads", pads)
291
292 # pad/core each return a list of tuples of (res, pin, port, attrs)
293 for pad, core in zip(pads, core):
294 # create a lookup on pin name to get at the hidden pad instance
295 # this pin name will be handed to get_input, get_output etc.
296 # and without the padlookup you can't find the (duplicate) pad.
297 # note that self.padlookup and self.ios use the *exact* same
298 # pin.name per pin
299 padpin = pad[1]
300 corepin = core[1]
301 if padpin is None: continue # skip when pin is None
302 assert corepin is not None # if pad was None, core should be too
303 print ("iter", pad, padpin.name)
304 print ("existing pads", padlookup.keys())
305 assert padpin.name not in padlookup # no overwrites allowed!
306 assert padpin.name == corepin.name # has to be the same!
307 padlookup[padpin.name] = pad # store pad by pin name
308
309 # now add the IO Shift Register. first identify the type
310 # then request a JTAG IOConn. we can't wire it up (yet) because
311 # we don't have a Module() instance. doh. that comes in get_input
312 # and get_output etc. etc.
313 iotype = resiotypes[padpin.dir] # look up the C4M-JTAG IOType
314 io = self.add_io(iotype=iotype, name=padpin.name) # IOConn
315 self.ios[padpin.name] = io # store IOConn Record by pin name
316
317 # and connect up core to pads based on type. could create
318 # Modules here just like in Platform.get_input/output but
319 # in some ways it is clearer by being simpler to wire them globally
320
321 if padpin.dir == 'i':
322 print ("jtag_request add input pin", padpin)
323 print (" corepin", corepin)
324 print (" jtag io core", io.core)
325 print (" jtag io pad", io.pad)
326 # corepin is to be returned, here. so, connect jtag corein to it
327 self.eqs += [corepin.i.eq(io.core.i)]
328 # and padpin to JTAG pad
329 self.eqs += [io.pad.i.eq(padpin.i)]
330 self.boundary_scan_pads[padpin.name]['i'] = padpin.i
331 elif padpin.dir == 'o':
332 print ("jtag_request add output pin", padpin)
333 print (" corepin", corepin)
334 print (" jtag io core", io.core)
335 print (" jtag io pad", io.pad)
336 # corepin is to be returned, here. connect it to jtag core out
337 self.eqs += [io.core.o.eq(corepin.o)]
338 # and JTAG pad to padpin
339 self.eqs += [padpin.o.eq(io.pad.o)]
340 self.boundary_scan_pads[padpin.name]['o'] = padpin.o
341 elif padpin.dir == 'io':
342 print ("jtag_request add io pin", padpin)
343 print (" corepin", corepin)
344 print (" jtag io core", io.core)
345 print (" jtag io pad", io.pad)
346 # corepin is to be returned, here. so, connect jtag corein to it
347 self.eqs += [corepin.i.eq(io.core.i)]
348 # and padpin to JTAG pad
349 self.eqs += [io.pad.i.eq(padpin.i)]
350 # corepin is to be returned, here. connect it to jtag core out
351 self.eqs += [io.core.o.eq(corepin.o)]
352 # and JTAG pad to padpin
353 self.eqs += [padpin.o.eq(io.pad.o)]
354 # corepin is to be returned, here. connect it to jtag core out
355 self.eqs += [io.core.oe.eq(corepin.oe)]
356 # and JTAG pad to padpin
357 self.eqs += [padpin.oe.eq(io.pad.oe)]
358
359 self.boundary_scan_pads[padpin.name]['i'] = padpin.i
360 self.boundary_scan_pads[padpin.name]['o'] = padpin.o
361 self.boundary_scan_pads[padpin.name]['oe'] = padpin.oe
362
363 # finally record the *CORE* value just like ResourceManager.request()
364 # so that the module using this can connect to *CORE* i/o to the
365 # resource. pads are taken care of
366 self.resource_table[(name, number)] = value
367
368 # and the *PAD* value so that it can be wired up externally as well
369 self.resource_table_pads[(name, number)] = pvalue
370
371 if __name__ == '__main__':
372 pinset = dummy_pinset()
373 dut = JTAG(pinset, "sync")
374
375 vl = rtlil.convert(dut)
376 with open("test_jtag.il", "w") as f:
377 f.write(vl)
378