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