a9e04b5ae6f0691879e4b781567203d8ae3e2f08
[c4m-jtag.git] / rtl / nmigen / jtag.py
1 #!/bin/env python3
2 import os
3
4 from nmigen import *
5 from nmigen.build import *
6 from nmigen.lib.io import *
7
8 from wishbone import Wishbone
9
10 __all__ = [
11 "PmodJTAGMasterResource",
12 "PmodJTAGMasterAResource",
13 "PmodJTAGSlaveResource",
14 "PmodJTAGSlaveAResource",
15 "JTAG",
16 ]
17
18 #TODO: Provide more documentation
19
20 def PmodJTAGMasterResource(name, number, *, pmod, attrs=Attrs(IOSTANDARD="LVCMOS33")):
21 return Resource(name, number,
22 Subsignal("TCK", Pins("1", dir="o", conn=("pmod", pmod))),
23 Subsignal("TMS", Pins("2", dir="o", conn=("pmod", pmod))),
24 Subsignal("TDO", Pins("3", dir="o", conn=("pmod", pmod))),
25 Subsignal("TDI", Pins("4", dir="i", conn=("pmod", pmod))),
26 attrs,
27 )
28
29 def PmodJTAGMasterAResource(name, number, *, pmod, attrs=Attrs(IOSTANDARD="LVCMOS33")):
30 return Resource(name, number,
31 Subsignal("TCK", Pins("1", dir="o", conn=("pmod", pmod))),
32 Subsignal("TMS", Pins("2", dir="o", conn=("pmod", pmod))),
33 Subsignal("TDO", Pins("3", dir="o", conn=("pmod", pmod))),
34 Subsignal("TDI", Pins("4", dir="i", conn=("pmod", pmod))),
35 Subsignal("TRST", PinsN("7", dir="o", conn=("pmod", pmod))),
36 attrs,
37 )
38
39 def PmodJTAGSlaveResource(name, number, *, pmod, attrs=Attrs(IOSTANDARD="LVCMOS33")):
40 return Resource(name, number,
41 Subsignal("TCK", Pins("1", dir="i", conn=("pmod", pmod))),
42 Subsignal("TMS", Pins("2", dir="i", conn=("pmod", pmod))),
43 Subsignal("TDI", Pins("3", dir="i", conn=("pmod", pmod))),
44 Subsignal("TDO", Pins("4", dir="o", conn=("pmod", pmod))),
45 attrs,
46 )
47
48 def PmodJTAGSlaveAResource(name, number, *, pmod, attrs=Attrs(IOSTANDARD="LVCMOS33")):
49 return Resource(name, number,
50 Subsignal("TCK", Pins("1", dir="i", conn=("pmod", pmod))),
51 Subsignal("TMS", Pins("2", dir="i", conn=("pmod", pmod))),
52 Subsignal("TDI", Pins("3", dir="i", conn=("pmod", pmod))),
53 Subsignal("TDO", Pins("4", dir="o", conn=("pmod", pmod))),
54 Subsignal("TRST", PinsN("7", dir="i", conn=("pmod", pmod))),
55 attrs,
56 )
57
58
59 def _add_files(platform, prefix):
60 d = os.path.realpath("{0}{1}{2}{1}vhdl".format(
61 os.path.dirname(__file__), os.path.sep, os.path.pardir
62 )) + os.path.sep
63 for fname in [
64 "c4m_jtag_pkg.vhdl",
65 "c4m_jtag_idblock.vhdl",
66 "c4m_jtag_iocell.vhdl",
67 "c4m_jtag_ioblock.vhdl",
68 "c4m_jtag_irblock.vhdl",
69 "c4m_jtag_tap_fsm.vhdl",
70 "c4m_jtag_tap_controller.vhdl",
71 ]:
72 f = open(d + fname, "r")
73 platform.add_file(prefix + fname, f)
74 f.close()
75
76
77 class ShiftReg(Elaboratable):
78 def __init__(self, ircodes, length, domain):
79 # The sr record will be returned to user code
80 self.sr = Record([("i", length), ("o", length), ("oe", len(ircodes)), ("ack", 1)])
81 # The next attributes are for JTAG class usage only
82 self.ir = None # made None as width is not known yet
83 self.tdi = Signal()
84 self.tdo = Signal()
85 self.tdo_en = Signal()
86 self.capture = Signal()
87 self.shift = Signal()
88 self.update = Signal()
89 self.jtag_cd = None # The JTAG clock domain
90
91 ##
92
93 self._ircodes = ircodes
94 self._domain = domain
95
96 def elaborate(self, platform):
97 length = len(self.sr.o)
98 domain = self._domain
99
100 m = Module()
101
102 m.domains.jtag = self.jtag_cd
103
104 sr_jtag = Signal(length)
105
106 assert isinstance(self.ir, Signal)
107 isir = Signal(len(self._ircodes))
108 capture = Signal()
109 shift = Signal()
110 update = Signal()
111 m.d.comb += [
112 isir.eq(Cat(self.ir == ircode for ircode in self._ircodes)),
113 capture.eq((isir != 0) & self.capture),
114 shift.eq((isir != 0) & self.shift),
115 update.eq((isir != 0) & self.update),
116 ]
117
118 # On update set o, oe and wait for ack
119 # update signal is on JTAG clockdomain, latch it
120 update_core = Signal()
121 m.d[domain] += update_core.eq(update) # This is CDC from JTAG domain to given domain
122 with m.FSM(domain=domain):
123 with m.State("IDLE"):
124 m.d.comb += self.sr.oe.eq(0)
125 with m.If(update_core):
126 # Latch sr_jtag cross domain but it should be stable due to latching of update_core
127 m.d[domain] += self.sr.o.eq(sr_jtag)
128 # Wait one cycle to raise oe so sr.o has one more cycle to stabilize
129 m.next = "WAIT4ACK"
130 with m.State("WAIT4ACK"):
131 m.d.comb += self.sr.oe.eq(isir)
132 with m.If(self.sr.ack):
133 m.next = "WAIT4END"
134 with m.State("WAIT4END"):
135 m.d.comb += self.sr.oe.eq(0)
136 with m.If(~update_core):
137 m.next = "IDLE"
138
139 m.d.comb += [
140 self.tdo.eq(sr_jtag[0]),
141 self.tdo_en.eq(shift),
142 ]
143
144 with m.If(shift):
145 m.d.jtag += sr_jtag.eq(Cat(sr_jtag[1:], self.tdi))
146 with m.If(capture):
147 m.d.jtag += sr_jtag.eq(self.sr.i)
148
149 return m
150
151 class JTAGWishbone(Elaboratable):
152 def __init__(self, sr_addr, sr_data, wb, domain):
153 self._sr_addr = sr_addr
154 self._sr_data = sr_data
155 self._wb = wb
156 self._domain = domain
157
158 # To be set by JTAG
159 self._ir = None
160
161 def elaborate(self, platform):
162 sr_addr = self._sr_addr
163 sr_data = self._sr_data
164 wb = self._wb
165 domain = self._domain
166 ir = self._ir
167
168 m = Module()
169
170 if hasattr(wb, "sel"):
171 # Always selected
172 m.d.comb += [s.eq(1) for s in wb.sel]
173
174 # Immediately ack oe
175 m.d[domain] += [
176 sr_addr.ack.eq(sr_addr.oe),
177 sr_data.ack.eq(sr_data.oe != 0),
178 ]
179
180 with m.FSM(domain=domain) as fsm:
181 with m.State("IDLE"):
182 m.d.comb += [
183 wb.cyc.eq(0),
184 wb.stb.eq(0),
185 wb.we.eq(0),
186 ]
187 with m.If(sr_addr.oe): # WBADDR code
188 m.d[domain] += wb.addr.eq(sr_addr.o)
189 m.next = "READ"
190 with m.If(sr_data.oe[0]): # WBREAD code
191 m.d[domain] += wb.addr.eq(wb.addr + 1)
192 m.next = "READ"
193 with m.If(sr_data.oe[1]): # WBWRITE code
194 m.d[domain] += wb.dat_w.eq(sr_data.o)
195 m.next = "WRITEREAD"
196 with m.State("READ"):
197 m.d.comb += [
198 wb.cyc.eq(1),
199 wb.stb.eq(1),
200 wb.we.eq(0),
201 ]
202 with m.If(~wb.stall):
203 m.next = "READACK"
204 with m.State("READACK"):
205 m.d.comb += [
206 wb.cyc.eq(1),
207 wb.stb.eq(0),
208 wb.we.eq(0),
209 ]
210 with m.If(wb.ack):
211 m.d[domain] += sr_data.i.eq(wb.dat_r)
212 m.next = "IDLE"
213 with m.State("WRITEREAD"):
214 m.d.comb += [
215 wb.cyc.eq(1),
216 wb.stb.eq(1),
217 wb.we.eq(1),
218 ]
219 with m.If(~wb.stall):
220 m.next = "WRITEREADACK"
221 with m.State("WRITEREADACK"):
222 m.d.comb += [
223 wb.cyc.eq(1),
224 wb.stb.eq(0),
225 wb.we.eq(0),
226 ]
227 with m.If(wb.ack):
228 m.d[domain] += wb.addr.eq(wb.addr + 1)
229 m.next = "READ"
230
231 return m
232
233
234 class JTAG(Elaboratable):
235 _files_added = []
236
237 def __init__(self, io_count, *, ir_width=None, manufacturer_id=Const(0b10001111111, 11),
238 part_number=Const(1, 16), version=Const(0, 4)
239 ):
240 assert(isinstance(io_count, int) and io_count > 0)
241 assert((ir_width is None) or (isinstance(ir_width, int) and ir_width >= 2))
242 assert(len(version) == 4)
243
244 # TODO: Handle IOs with different directions
245 self.tck = Signal()
246 self.tms = Signal()
247 self.tdo = Signal()
248 self.tdi = Signal()
249 self.core = Array(Pin(1, "io") for _ in range(io_count)) # Signals to use for core
250 self.pad = Array(Pin(1, "io") for _ in range(io_count)) # Signals going to IO pads
251
252 self.jtag_cd = ClockDomain(name="jtag", local=True) # Own clock domain using TCK as clock signal
253
254 ##
255
256 self._io_count = io_count
257 self._ir_width = ir_width
258 self._manufacturer_id = manufacturer_id
259 self._part_number = part_number
260 self._version = version
261
262 self._ircodes = [0, 1, 2] # Already taken codes, all ones added at the end
263 self._srs = []
264
265 self._wbs = []
266
267 def elaborate(self, platform):
268 _add_files(platform, "jtag" + os.path.sep)
269
270 m = Module()
271
272 tdo_jtag = Signal()
273 reset = Signal()
274 capture = Signal()
275 shift = Signal()
276 update = Signal()
277
278
279 ir_max = max(self._ircodes) + 1 # One extra code needed with all ones
280 ir_width = len("{:b}".format(ir_max))
281 if self._ir_width is not None:
282 assert self._ir_width >= ir_width, "Specified JTAG IR width not big enough for allocated shiift registers"
283 ir_width = self._ir_width
284 ir = Signal(ir_width)
285
286 core_i = Cat(pin.i for pin in self.core)
287 core_o = Cat(pin.o for pin in self.core)
288 core_oe = Cat(pin.oe for pin in self.core)
289 pad_i = Cat(pin.i for pin in self.pad)
290 pad_o = Cat(pin.o for pin in self.pad)
291 pad_oe = Cat(pin.oe for pin in self.pad)
292
293 params = {
294 "p_IOS": self._io_count,
295 "p_IR_WIDTH": ir_width,
296 "p_MANUFACTURER": self._manufacturer_id,
297 "p_PART_NUMBER": self._part_number,
298 "p_VERSION": self._version,
299 "i_TCK": self.tck,
300 "i_TMS": self.tms,
301 "i_TDI": self.tdi,
302 "o_TDO": tdo_jtag,
303 "i_TRST_N": Const(1),
304 "o_RESET": reset,
305 "o_DRCAPTURE": capture,
306 "o_DRSHIFT": shift,
307 "o_DRUPDATE": update,
308 "o_IR": ir,
309 "o_CORE_IN": core_i,
310 "i_CORE_OUT": core_o,
311 "i_CORE_EN": core_oe,
312 "i_PAD_IN": pad_i,
313 "o_PAD_OUT": pad_o,
314 "o_PAD_EN": pad_oe,
315 }
316 m.submodules.tap = Instance("c4m_jtag_tap_controller", **params)
317
318 m.d.comb += [
319 self.jtag_cd.clk.eq(self.tck),
320 self.jtag_cd.rst.eq(reset),
321 ]
322
323 for i, sr in enumerate(self._srs):
324 m.submodules["sr{}".format(i)] = sr
325 sr.ir = ir
326 m.d.comb += [
327 sr.tdi.eq(self.tdi),
328 sr.capture.eq(capture),
329 sr.shift.eq(shift),
330 sr.update.eq(update),
331 ]
332
333 if len(self._srs) > 0:
334 first = True
335 for sr in self._srs:
336 if first:
337 first = False
338 with m.If(sr.tdo_en):
339 m.d.comb += self.tdo.eq(sr.tdo)
340 else:
341 with m.Elif(sr.tdo_en):
342 m.d.comb += self.tdo.eq(sr.tdo)
343 with m.Else():
344 m.d.comb += self.tdo.eq(tdo_jtag)
345 else:
346 m.d.comb += self.tdo.eq(tdo_jtag)
347
348 for i, wb in enumerate(self._wbs):
349 m.submodules["wb{}".format(i)] = wb
350 wb._ir = ir
351
352 return m
353
354
355 def add_shiftreg(self, ircode, length, domain="sync"):
356 """Add a shift register to the JTAG interface
357
358 Parameters:
359 - ircode: code(s) for the IR; int or sequence of ints. In the latter case this
360 shiftreg is shared between different IR codes.
361 - length: the length of the shift register
362 - domain: the domain on which the signal will be used"""
363
364 try:
365 ir_it = iter(ircode)
366 ircodes = ircode
367 except TypeError:
368 ir_it = ircodes = (ircode,)
369 for _ircode in ir_it:
370 assert(isinstance(_ircode, int) and _ircode > 0 and _ircode not in self._ircodes)
371
372 sr = ShiftReg(ircodes, length, domain)
373 sr.jtag_cd = self.jtag_cd
374 self._ircodes.extend(ircodes)
375 self._srs.append(sr)
376
377 return sr.sr
378
379
380 def add_wishbone(self, ircodes, address_width, data_width, sel_width=None, domain="sync"):
381 """Add a wishbone interface
382
383 Parameters:
384 - ircodes: sequence of three integer for the JTAG IR codes;
385 they represent resp. WBADDR, WBREAD and WBREADWRITE. First code
386 has a shift register of length 'address_width', the two other codes
387 share a shift register of length data_width.
388 - address_width: width of the address
389 - data_width: width of the data"""
390
391 assert len(ircodes) == 3
392
393 sr_addr = self.add_shiftreg(ircodes[0], address_width, domain=domain)
394 sr_data = self.add_shiftreg(ircodes[1:], data_width, domain=domain)
395
396 wb = Wishbone(data_width=data_width, address_width=address_width, sel_width=sel_width, master=True)
397
398 self._wbs.append(JTAGWishbone(sr_addr, sr_data, wb, domain))
399
400 return wb