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