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
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
= []
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
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
)
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
)
223 # add the eq assignments connecting up JTAG boundary scan to core
227 def external_ports(self
):
228 """create a list of ports that goes into the top level il (or verilog)
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"
238 return list(self
.iter_ports())
240 def iter_ports(self
):
245 yield from self
.boundary_scan_pads
247 def request(self
, name
, number
=0, *, dir=None, xdr
=None):
248 """looks like ResourceManager.request but can be called multiple times.
250 return self
.resource_table
[(name
, number
)]
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.
256 this override will also - automatically - create a JTAG Boundary Scan
257 connection *without* any change to the actual Platform.request() API
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
)
270 # take a copy of the requests made
271 self
.requests_made
.append((name
, number
, dir, xdr
))
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
)
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"
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
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
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
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
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
)]
356 self
.boundary_scan_pads
.append(padpin
.i
)
357 self
.boundary_scan_pads
.append(padpin
.o
)
358 self
.boundary_scan_pads
.append(padpin
.oe
)
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
365 # and the *PAD* value so that it can be wired up externally as well
366 self
.resource_table_pads
[(name
, number
)] = pvalue
368 if __name__
== '__main__':
369 pinset
= dummy_pinset()
370 dut
= JTAG(pinset
, "sync")
372 vl
= rtlil
.convert(dut
)
373 with
open("test_jtag.il", "w") as f
: