3 using Staf Verhaegen (Chips4Makers) wishbone TAP
5 Pinmux documented here https://libre-soc.org/docs/pinmux/
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
13 from nmigen
import (Module
, Signal
, Elaboratable
, Cat
)
14 from c4m
.nmigen
.jtag
.tap
import IOType
, TAP
16 # map from pinmux to c4m jtag iotypes
17 iotypes
= {'-': IOType
.In
,
24 # nmigen Resources has a different encoding for direction: "i", "o", "io", "oe"
25 resiotypes
= {'i': IOType
.In
,
28 'io': IOType
.InTriOut
,
31 # How many bits in each signal type
32 scanlens
= {IOType
.In
: 1,
40 # sigh this needs to come from pinmux.
43 gpios
.append("%d*" % i
)
44 return {'uart': ['tx+', 'rx-'],
46 'i2c': ['sda*', 'scl+']}
49 # TODO: move to suitable location
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
55 def __init__(self
, pindict
=None):
58 self
.io_names
= OrderedDict()
59 if isinstance(pindict
, OrderedDict
):
60 self
.io_names
.update(pindict
)
62 keys
= list(pindict
.keys())
65 self
.io_names
[k
] = pindict
[k
]
68 # start parsing io_names and enumerate them to return pin specs
70 for fn
, pins
in self
.io_names
.items():
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
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
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
))
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:
106 TAP
.__init
__(self
, ir_width
=4)
107 Pins
.__init
__(self
, pinset
)
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
113 self
.add_pins(list(self
))
115 # this is redundant. or maybe part of testing, i don't know.
116 self
.sr
= self
.add_shiftreg(ircode
=4, length
=3,
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
127 # create DMI2JTAG (goes through to dmi_sim())
128 self
.dmi
= self
.add_dmi(ircodes
=[8, 9, 10],
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
,
138 self
.sr_en
= self
.add_shiftreg(ircode
=11, length
=len(en_sigs
),
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
151 self
.pad_mgr
= ResourceManager([], [])
152 self
.core_mgr
= ResourceManager([], [])
153 self
.pad_mgr
.add_resources(resources
)
154 self
.core_mgr
.add_resources(resources
)
156 # record resource lookup between core IO names and pads
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
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]
171 for resource
in resources
:
172 print ("JTAG resource", resource
)
173 if resource
.name
in ['clk', 'rst']: # hack
175 self
.add_jtag_resource(resource
.name
, resource
.number
)
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
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?
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
)
197 # create a fake "stall"
199 #m.d.comb += wb.stall.eq(wb.cyc & ~wb.ack) # No burst support
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
= {}
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
)
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
)
225 # add the eq assignments connecting up JTAG boundary scan to core
229 def external_ports(self
):
230 """create a list of ports that goes into the top level il (or verilog)
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"
240 return list(self
.iter_ports())
242 def iter_ports(self
):
247 for pad
in self
.boundary_scan_pads
.values():
248 yield from pad
.values()
250 def request(self
, name
, number
=0, *, dir=None, xdr
=None):
251 """looks like ResourceManager.request but can be called multiple times.
253 return self
.resource_table
[(name
, number
)]
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.
259 this override will also - automatically - create a JTAG Boundary Scan
260 connection *without* any change to the actual Platform.request() API
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
)
273 # take a copy of the requests made
274 self
.requests_made
.append((name
, number
, dir, xdr
))
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
)
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"
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
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
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
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
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
)]
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
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
368 # and the *PAD* value so that it can be wired up externally as well
369 self
.resource_table_pads
[(name
, number
)] = pvalue
371 if __name__
== '__main__':
372 pinset
= dummy_pinset()
373 dut
= JTAG(pinset
, "sync")
375 vl
= rtlil
.convert(dut
)
376 with
open("test_jtag.il", "w") as f
: