Document why FFSynchronizer is not used.
[c4m-jtag.git] / c4m / nmigen / jtag / tap.py
1 #!/usr/bin/env python3
2 import os, textwrap
3 from enum import Enum, auto
4
5 from nmigen import *
6 from nmigen.build import *
7 from nmigen.lib.io import *
8 from nmigen.hdl.rec import Direction, Layout
9 from nmigen.tracer import get_var_name
10
11 from nmigen_soc.wishbone import Interface as WishboneInterface
12
13 from .bus import Interface
14
15 __all__ = [
16 "TAP", "ShiftReg", "IOType", "IOConn",
17 ]
18
19
20 class _FSM(Elaboratable):
21 """TAP subblock for the FSM"""
22 def __init__(self, *, bus):
23 self.isir = Signal()
24 self.isdr = Signal()
25 self.capture = Signal()
26 self.shift = Signal()
27 self.update = Signal()
28
29 # JTAG uses both edges of the incoming clock (TCK). set them up here
30 self.posjtag = ClockDomain("posjtag", local=True)
31 self.negjtag = ClockDomain("negjtag", local=True, clk_edge="neg")
32
33 self._bus = bus
34
35 def elaborate(self, platform):
36 m = Module()
37
38 rst = Signal()
39 m.d.comb += [
40 self.posjtag.clk.eq(self._bus.tck),
41 self.posjtag.rst.eq(rst),
42 self.negjtag.clk.eq(self._bus.tck),
43 self.negjtag.rst.eq(rst),
44 ]
45
46 # Make local clock domain optionally using trst of JTAG bus as reset
47 if hasattr(self._bus, "trst"):
48 m.domains.local = local = ClockDomain(local=True)
49 m.d.comb += local.rst.eq(self._bus.trst)
50 else:
51 m.domains.local = local = ClockDomain(local=True, reset_less=True)
52 m.d.comb += local.clk.eq(self._bus.tck)
53
54 with m.FSM(domain="local") as fsm:
55 with m.State("TestLogicReset"):
56 # Be sure to reset isir, isdr
57 m.d.local += [
58 self.isir.eq(0),
59 self.isdr.eq(0),
60 ]
61 with m.If(self._bus.tms == 0):
62 m.next = "RunTestIdle"
63 with m.State("RunTestIdle"):
64 # Be sure to reset isir, isdr
65 m.d.local += [
66 self.isir.eq(0),
67 self.isdr.eq(0),
68 ]
69 with m.If(self._bus.tms == 1):
70 m.next = "SelectDRScan"
71 with m.State("SelectDRScan"):
72 with m.If(self._bus.tms == 0):
73 m.d.local += self.isdr.eq(1)
74 m.next = "CaptureState"
75 with m.Else():
76 m.next = "SelectIRScan"
77 with m.State("SelectIRScan"):
78 with m.If(self._bus.tms == 0):
79 m.d.local += self.isir.eq(1)
80 m.next = "CaptureState"
81 with m.Else():
82 m.next = "TestLogicReset"
83 with m.State("CaptureState"):
84 with m.If(self._bus.tms == 0):
85 m.next = "ShiftState"
86 with m.Else():
87 m.next = "Exit1"
88 with m.State("ShiftState"):
89 with m.If(self._bus.tms == 1):
90 m.next = "Exit1"
91 with m.State("Exit1"):
92 with m.If(self._bus.tms == 0):
93 m.next = "Pause"
94 with m.Else():
95 m.next = "UpdateState"
96 with m.State("Pause"):
97 with m.If(self._bus.tms == 1):
98 m.next = "Exit2"
99 with m.State("Exit2"):
100 with m.If(self._bus.tms == 0):
101 m.next = "ShiftState"
102 with m.Else():
103 m.next = "UpdateState"
104 with m.State("UpdateState"):
105 m.d.local += [
106 self.isir.eq(0),
107 self.isdr.eq(0),
108 ]
109 with m.If(self._bus.tms == 0):
110 m.next = "RunTestIdle"
111 with m.Else():
112 m.next = "SelectDRScan"
113
114 m.d.comb += [
115 rst.eq(fsm.ongoing("TestLogicReset")),
116 self.capture.eq(fsm.ongoing("CaptureState")),
117 self.shift.eq(fsm.ongoing("ShiftState")),
118 self.update.eq(fsm.ongoing("UpdateState")),
119 ]
120
121 return m
122
123
124 class _IRBlock(Elaboratable):
125 """TAP subblock for handling the IR shift register"""
126 def __init__(self, *, ir_width, cmd_idcode,
127 tdi, capture, shift, update,
128 name):
129 self.name = name
130 self.ir = Signal(ir_width, reset=cmd_idcode)
131 self.tdo = Signal()
132
133 self._tdi = tdi
134 self._capture = capture
135 self._shift = shift
136 self._update = update
137
138 def elaborate(self, platform):
139 m = Module()
140
141 shift_ir = Signal(len(self.ir), reset_less=True)
142
143 m.d.comb += self.tdo.eq(self.ir[0])
144 with m.If(self._capture):
145 m.d.posjtag += shift_ir.eq(self.ir)
146 with m.Elif(self._shift):
147 m.d.posjtag += shift_ir.eq(Cat(shift_ir[1:], self._tdi))
148 with m.Elif(self._update):
149 # For ir we only update it on the rising edge of clock
150 # to avoid that we already have the new ir value when still in
151 # Update state
152 m.d.posjtag += self.ir.eq(shift_ir)
153
154 return m
155
156
157 class IOType(Enum):
158 In = auto()
159 Out = auto()
160 TriOut = auto()
161 InTriOut = auto()
162
163
164 class IOConn(Record):
165 """TAP subblock representing the interface for an JTAG IO cell.
166 It contains signal to connect to the core and to the pad
167
168 This object is normally only allocated and returned from ``TAP.add_io``
169 It is a Record subclass.
170
171 Attributes
172 ----------
173 core: subrecord with signals for the core
174 i: Signal(1), present only for IOType.In and IOType.InTriOut.
175 Signal input to core with pad input value.
176 o: Signal(1), present only for IOType.Out, IOType.TriOut and IOType.InTriOut.
177 Signal output from core with the pad output value.
178 oe: Signal(1), present only for IOType.TriOut and IOType.InTriOut.
179 Signal output from core with the pad output enable value.
180 pad: subrecord with for the pad
181 i: Signal(1), present only for IOType.In and IOType.InTriOut
182 Output from pad with pad input value for core.
183 o: Signal(1), present only for IOType.Out, IOType.TriOut and IOType.InTriOut.
184 Input to pad with pad output value.
185 oe: Signal(1), present only for IOType.TriOut and IOType.InTriOut.
186 Input to pad with pad output enable value.
187 """
188 @staticmethod
189 def layout(iotype):
190 sigs = []
191 if iotype in (IOType.In, IOType.InTriOut):
192 sigs.append(("i", 1))
193 if iotype in (IOType.Out, IOType.TriOut, IOType.InTriOut):
194 sigs.append(("o", 1))
195 if iotype in (IOType.TriOut, IOType.InTriOut):
196 sigs.append(("oe", 1))
197
198 return Layout((("core", sigs), ("pad", sigs)))
199
200 def __init__(self, *, iotype, name=None, src_loc_at=0):
201 super().__init__(self.__class__.layout(iotype), name=name, src_loc_at=src_loc_at+1)
202
203 self._iotype = iotype
204
205
206 class _IDBypassBlock(Elaboratable):
207 """TAP subblock for the ID shift register"""
208 def __init__(self, *, manufacturer_id, part_number, version,
209 tdi, capture, shift, update, bypass,
210 name):
211 self.name = name
212 if not isinstance(manufacturer_id, Const) and len(manufacturer_id) != 11:
213 raise ValueError("manufacturer_id has to be Const of length 11")
214 if not isinstance(part_number, Const) and len(manufacturer_id) != 16:
215 raise ValueError("part_number has to be Const of length 16")
216 if not isinstance(version, Const) and len(version) != 4:
217 raise ValueError("version has to be Const of length 4")
218 self._id = Cat(Const(1,1), manufacturer_id, part_number, version)
219
220 self.tdo = Signal(name=name+"_tdo")
221
222 self._tdi = tdi
223 self._capture = capture
224 self._shift = shift
225 self._update = update
226 self._bypass = bypass
227
228 def elaborate(self, platform):
229 m = Module()
230
231 sr = Signal(32, reset_less=True, name=self.name+"_sr")
232
233 # Local signals for the module
234 _tdi = Signal()
235 _capture = Signal()
236 _shift = Signal()
237 _update = Signal()
238 _bypass = Signal()
239
240 m.d.comb += [
241 _tdi.eq(self._tdi),
242 _capture.eq(self._capture),
243 _shift.eq(self._shift),
244 _update.eq(self._update),
245 _bypass.eq(self._bypass),
246 self.tdo.eq(sr[0]),
247 ]
248
249 with m.If(_capture):
250 m.d.posjtag += sr.eq(self._id)
251 with m.Elif(_shift):
252 with m.If(_bypass):
253 m.d.posjtag += sr[0].eq(_tdi)
254 with m.Else():
255 m.d.posjtag += sr.eq(Cat(sr[1:], _tdi))
256
257 return m
258
259
260 class ShiftReg(Record):
261 """Object with interface for extra shift registers on a TAP.
262
263 Parameters
264 ----------
265 sr_length : int
266 cmds : int, default=1
267 The number of corresponding JTAG instructions
268
269 This object is normally only allocated and returned from ``TAP.add_shiftreg``
270 It is a Record subclass.
271
272 Attributes
273 ----------
274 i: length=sr_length, FANIN
275 The input data sampled during capture state of the TAP
276 ie: length=cmds, FANOUT
277 Indicates that data is to be sampled by the JTAG TAP and
278 should be held stable. The bit indicates the corresponding
279 instruction for which data is asked.
280 This signal is kept high for a whole JTAG TAP clock cycle
281 and may thus be kept higher for more than one clock cycle
282 on the domain where ShiftReg is used.
283 The JTAG protocol does not allow insertion of wait states
284 so data need to be provided before ie goes down. The speed
285 of the response will determine the max. frequency for the
286 JTAG interface.
287 o: length=sr_length, FANOUT
288 The value of the shift register.
289 oe: length=cmds, FANOUT
290 Indicates that output is stable and can be sampled downstream because
291 JTAG TAP is in the Update state. The bit indicates the corresponding
292 instruction. The bit is only kept high for one clock cycle.
293 """
294 def __init__(self, *, sr_length, cmds=1, name=None, src_loc_at=0):
295 layout = [
296 ("i", sr_length, Direction.FANIN),
297 ("ie", cmds, Direction.FANOUT),
298 ("o", sr_length, Direction.FANOUT),
299 ("oe", cmds, Direction.FANOUT),
300 ]
301 super().__init__(layout, name=name, src_loc_at=src_loc_at+1)
302
303
304 class TAP(Elaboratable):
305 #TODO: Document TAP
306 def __init__(
307 self, *, with_reset=False, ir_width=None,
308 manufacturer_id=Const(0b10001111111, 11), part_number=Const(1, 16),
309 version=Const(0, 4),
310 name=None, src_loc_at=0
311 ):
312 assert((ir_width is None) or (isinstance(ir_width, int) and ir_width >= 2))
313 assert(len(version) == 4)
314
315 if name is None:
316 name = get_var_name(depth=src_loc_at+2, default="TAP")
317 self.name = name
318 self.bus = Interface(with_reset=with_reset, name=self.name+"_bus",
319 src_loc_at=src_loc_at+1)
320
321 ##
322
323 self._ir_width = ir_width
324 self._manufacturer_id = manufacturer_id
325 self._part_number = part_number
326 self._version = version
327
328 self._ircodes = [0, 1, 2] # Already taken codes, all ones added at the end
329
330 self._ios = []
331 self._srs = []
332 self._wbs = []
333
334 def elaborate(self, platform):
335 m = Module()
336
337 # Determine ir_width if not fixed.
338 ir_max = max(self._ircodes) + 1 # One extra code needed with all ones
339 ir_width = len("{:b}".format(ir_max))
340 if self._ir_width is not None:
341 assert self._ir_width >= ir_width, "Specified JTAG IR width not big enough for allocated shiift registers"
342 ir_width = self._ir_width
343
344 # TODO: Make commands numbers configurable
345 cmd_extest = 0
346 cmd_intest = 0
347 cmd_idcode = 1
348 cmd_sample = 2
349 cmd_preload = 2
350 cmd_bypass = 2**ir_width - 1 # All ones
351
352 m.submodules._fsm = fsm = _FSM(bus=self.bus)
353 m.domains.posjtag = fsm.posjtag
354 m.domains.negjtag = fsm.negjtag
355
356 # IR block
357 select_ir = fsm.isir
358 m.submodules._irblock = irblock = _IRBlock(
359 ir_width=ir_width, cmd_idcode=cmd_idcode, tdi=self.bus.tdi,
360 capture=(fsm.isir & fsm.capture),
361 shift=(fsm.isir & fsm.shift),
362 update=(fsm.isir & fsm.update),
363 name=self.name+"_ir",
364 )
365 ir = irblock.ir
366
367 # ID block
368 select_id = fsm.isdr & ((ir == cmd_idcode) | (ir == cmd_bypass))
369 m.submodules._idblock = idblock = _IDBypassBlock(
370 manufacturer_id=self._manufacturer_id, part_number=self._part_number,
371 version=self._version, tdi=self.bus.tdi,
372 capture=(select_id & fsm.capture),
373 shift=(select_id & fsm.shift),
374 update=(select_id & fsm.update),
375 bypass=(ir == cmd_bypass),
376 name=self.name+"_id",
377 )
378
379 # IO (Boundary scan) block
380 io_capture = Signal()
381 io_shift = Signal()
382 io_update = Signal()
383 io_bd2io = Signal()
384 io_bd2core = Signal()
385 sample = (ir == cmd_extest) | (ir == cmd_sample)
386 preload = (ir == cmd_preload)
387 select_io = fsm.isdr & (sample | preload)
388 m.d.comb += [
389 io_capture.eq(sample & fsm.capture), # Don't capture if not sample (like for PRELOAD)
390 io_shift.eq(select_io & fsm.shift),
391 io_update.eq(select_io & fsm.update),
392 io_bd2io.eq(ir == cmd_extest),
393 io_bd2core.eq(ir == cmd_intest),
394 ]
395 io_tdo = self._elaborate_ios(
396 m=m,
397 capture=io_capture, shift=io_shift, update=io_update,
398 bd2io=io_bd2io, bd2core=io_bd2core,
399 )
400
401 # chain tdo: select as appropriate, to go into into shiftregs
402 tdo = Signal(name=self.name+"_tdo")
403 with m.If(select_ir):
404 m.d.comb += tdo.eq(irblock.tdo)
405 with m.Elif(select_id):
406 m.d.comb += tdo.eq(idblock.tdo)
407 with m.Elif(select_io):
408 m.d.comb += tdo.eq(io_tdo)
409
410 # shiftregs block
411 self._elaborate_shiftregs(
412 m, capture=fsm.capture, shift=fsm.shift, update=fsm.update,
413 ir=irblock.ir, tdo_jtag=tdo
414 )
415
416 # wishbone
417 self._elaborate_wishbones(m)
418
419 return m
420
421
422 def add_io(self, *, iotype, name=None, src_loc_at=0):
423 """Add a io cell to the boundary scan chain
424
425 Parameters:
426 - iotype: :class:`IOType` enum.
427
428 Returns:
429 - :class:`IOConn`
430 """
431 if name is None:
432 name = "ioconn" + str(len(self._ios))
433
434 ioconn = IOConn(iotype=iotype, name=name, src_loc_at=src_loc_at+1)
435 self._ios.append(ioconn)
436 return ioconn
437
438 def _elaborate_ios(self, *, m, capture, shift, update, bd2io, bd2core):
439 connlength = {
440 IOType.In: 1,
441 IOType.Out: 1,
442 IOType.TriOut: 2,
443 IOType.InTriOut: 3,
444 }
445 length = sum(connlength[conn._iotype] for conn in self._ios)
446 if length == 0:
447 return self.bus.tdi
448
449 io_sr = Signal(length)
450 io_bd = Signal(length)
451
452 # Boundary scan "capture" mode. makes I/O status available via SR
453 with m.If(capture):
454 idx = 0
455 for conn in self._ios:
456 if conn._iotype == IOType.In:
457 m.d.posjtag += io_sr[idx].eq(conn.pad.i)
458 idx += 1
459 elif conn._iotype == IOType.Out:
460 m.d.posjtag += io_sr[idx].eq(conn.core.o)
461 idx += 1
462 elif conn._iotype == IOType.TriOut:
463 m.d.posjtag += [
464 io_sr[idx].eq(conn.core.o),
465 io_sr[idx+1].eq(conn.core.oe),
466 ]
467 idx += 2
468 elif conn._iotype == IOType.InTriOut:
469 m.d.posjtag += [
470 io_sr[idx].eq(conn.pad.i),
471 io_sr[idx+1].eq(conn.core.o),
472 io_sr[idx+2].eq(conn.core.oe),
473 ]
474 idx += 3
475 else:
476 raise("Internal error")
477 assert idx == length, "Internal error"
478
479 # "Shift" mode (sends out captured data on tdo, sets incoming from tdi)
480 with m.Elif(shift):
481 m.d.posjtag += io_sr.eq(Cat(self.bus.tdi, io_sr[:-1]))
482
483 # "Update" mode
484 with m.Elif(update):
485 m.d.negjtag += io_bd.eq(io_sr)
486
487 # sets up IO (pad<->core) or in testing mode depending on requested
488 # mode, via Muxes controlled by bd2core and bd2io
489 idx = 0
490 for conn in self._ios:
491 if conn._iotype == IOType.In:
492 m.d.comb += conn.core.i.eq(Mux(bd2core, io_bd[idx], conn.pad.i))
493 idx += 1
494 elif conn._iotype == IOType.Out:
495 m.d.comb += conn.pad.o.eq(Mux(bd2io, io_bd[idx], conn.core.o))
496 idx += 1
497 elif conn._iotype == IOType.TriOut:
498 m.d.comb += [
499 conn.pad.o.eq(Mux(bd2io, io_bd[idx], conn.core.o)),
500 conn.pad.oe.eq(Mux(bd2io, io_bd[idx+1], conn.core.oe)),
501 ]
502 idx += 2
503 elif conn._iotype == IOType.InTriOut:
504 m.d.comb += [
505 conn.core.i.eq(Mux(bd2core, io_bd[idx], conn.pad.i)),
506 conn.pad.o.eq(Mux(bd2io, io_bd[idx+1], conn.core.o)),
507 conn.pad.oe.eq(Mux(bd2io, io_bd[idx+2], conn.core.oe)),
508 ]
509 idx += 3
510 else:
511 raise("Internal error")
512 assert idx == length, "Internal error"
513
514 return io_sr[-1]
515
516 def add_shiftreg(self, *, ircode, length, domain="sync", name=None, src_loc_at=0):
517 """Add a shift register to the JTAG interface
518
519 Parameters:
520 - ircode: code(s) for the IR; int or sequence of ints. In the latter case this
521 shiftreg is shared between different IR codes.
522 - length: the length of the shift register
523 - domain: the domain on which the signal will be used"""
524
525 try:
526 ir_it = iter(ircode)
527 ircodes = ircode
528 except TypeError:
529 ir_it = ircodes = (ircode,)
530 for _ircode in ir_it:
531 if not isinstance(_ircode, int) or _ircode <= 0:
532 raise ValueError("IR code '{}' is not an int greater than 0".format(_ircode))
533 if _ircode in self._ircodes:
534 raise ValueError("IR code '{}' already taken".format(_ircode))
535
536 self._ircodes.extend(ircodes)
537
538 if name is None:
539 name = "sr{}".format(len(self._srs))
540 sr = ShiftReg(sr_length=length, cmds=len(ircodes), name=name, src_loc_at=src_loc_at+1)
541 self._srs.append((ircodes, domain, sr))
542
543 return sr
544
545 def _elaborate_shiftregs(self, m, capture, shift, update, ir, tdo_jtag):
546 # tdos is tuple of (tdo, tdo_en) for each shiftreg
547 tdos = []
548 for ircodes, domain, sr in self._srs:
549 reg = Signal(len(sr.o), name=sr.name+"_reg")
550 m.d.comb += sr.o.eq(reg)
551
552 isir = Signal(len(ircodes), name=sr.name+"_isir")
553 sr_capture = Signal(name=sr.name+"_capture")
554 sr_shift = Signal(name=sr.name+"_shift")
555 sr_update = Signal(name=sr.name+"_update")
556 m.d.comb += [
557 isir.eq(Cat(ir == ircode for ircode in ircodes)),
558 sr_capture.eq((isir != 0) & capture),
559 sr_shift.eq((isir != 0) & shift),
560 sr_update.eq((isir != 0) & update),
561 ]
562
563 # update signal is on the JTAG clockdomain, sr.oe is on `domain` clockdomain
564 # latch update in `domain` clockdomain and see when it has falling edge.
565 # At that edge put isir in sr.oe for one `domain` clockdomain
566 # Using this custom sync <> JTAG domain synchronization avoids the use of
567 # more generic but also higher latency CDC solutions like FFSynchronizer.
568 update_core = Signal(name=sr.name+"_update_core")
569 update_core_prev = Signal(name=sr.name+"_update_core_prev")
570 m.d[domain] += [
571 update_core.eq(sr_update), # This is CDC from JTAG domain to given domain
572 update_core_prev.eq(update_core)
573 ]
574 with m.If(update_core_prev & ~update_core):
575 # Falling edge of update
576 m.d[domain] += sr.oe.eq(isir)
577 with m.Else():
578 m.d[domain] += sr.oe.eq(0)
579
580 with m.If(sr_shift):
581 m.d.posjtag += reg.eq(Cat(reg[1:], self.bus.tdi))
582 with m.If(sr_capture):
583 m.d.posjtag += reg.eq(sr.i)
584
585 # tdo = reg[0], tdo_en = shift
586 tdos.append((reg[0], sr_shift))
587
588
589 # Assign the right tdo to the bus tdo
590 for i, (tdo, tdo_en) in enumerate(tdos):
591 if i == 0:
592 with m.If(tdo_en):
593 m.d.comb += self.bus.tdo.eq(tdo)
594 else:
595 with m.Elif(tdo_en):
596 m.d.comb += self.bus.tdo.eq(tdo)
597
598 if len(tdos) > 0:
599 with m.Else():
600 m.d.comb += self.bus.tdo.eq(tdo_jtag)
601 else:
602 # Always connect tdo_jtag to
603 m.d.comb += self.bus.tdo.eq(tdo_jtag)
604
605
606 def add_wishbone(self, *, ircodes, address_width, data_width, granularity=None, domain="sync",
607 name=None, src_loc_at=0):
608 """Add a wishbone interface
609
610 In order to allow high JTAG clock speed, data will be cached. This means that if data is
611 output the value of the next address will be read automatically.
612
613 Parameters:
614 -----------
615 ircodes: sequence of three integer for the JTAG IR codes;
616 they represent resp. WBADDR, WBREAD and WBREADWRITE. First code
617 has a shift register of length 'address_width', the two other codes
618 share a shift register of length data_width.
619 address_width: width of the address
620 data_width: width of the data
621
622 Returns:
623 wb: nmigen_soc.wishbone.bus.Interface
624 The Wishbone interface, is pipelined and has stall field.
625 """
626 if len(ircodes) != 3:
627 raise ValueError("3 IR Codes have to be provided")
628
629 if name is None:
630 name = "wb" + str(len(self._wbs))
631 sr_addr = self.add_shiftreg(
632 ircode=ircodes[0], length=address_width, domain=domain,
633 name=name+"_addrsr"
634 )
635 sr_data = self.add_shiftreg(
636 ircode=ircodes[1:], length=data_width, domain=domain,
637 name=name+"_datasr"
638 )
639
640 wb = WishboneInterface(data_width=data_width, addr_width=address_width,
641 granularity=granularity, features={"stall", "lock", "err", "rty"},
642 name=name, src_loc_at=src_loc_at+1)
643
644 self._wbs.append((sr_addr, sr_data, wb, domain))
645
646 return wb
647
648 def _elaborate_wishbones(self, m):
649 for sr_addr, sr_data, wb, domain in self._wbs:
650 m.d.comb += sr_addr.i.eq(wb.adr)
651
652 if hasattr(wb, "sel"):
653 # Always selected
654 m.d.comb += [s.eq(1) for s in wb.sel]
655
656 with m.FSM(domain=domain) as fsm:
657 with m.State("IDLE"):
658 with m.If(sr_addr.oe): # WBADDR code
659 m.d[domain] += wb.adr.eq(sr_addr.o)
660 m.next = "READ"
661 with m.Elif(sr_data.oe[0]): # WBREAD code
662 # If data is
663 m.d[domain] += wb.adr.eq(wb.adr + 1)
664 m.next = "READ"
665 with m.Elif(sr_data.oe[1]): # WBWRITE code
666 m.d[domain] += wb.dat_w.eq(sr_data.o)
667 m.next = "WRITEREAD"
668 with m.State("READ"):
669 with m.If(~wb.stall):
670 m.next = "READACK"
671 with m.State("READACK"):
672 with m.If(wb.ack):
673 # Store read data in sr_data.i and keep it there til next read
674 # This is enough to synchronize between sync and JTAG clock domain
675 # and no higher latency solutions like FFSynchronizer is needed.
676 m.d[domain] += sr_data.i.eq(wb.dat_r)
677 m.next = "IDLE"
678 with m.State("WRITEREAD"):
679 with m.If(~wb.stall):
680 m.next = "WRITEREADACK"
681 with m.State("WRITEREADACK"):
682 with m.If(wb.ack):
683 m.d[domain] += wb.adr.eq(wb.adr + 1)
684 m.next = "READ"
685
686 m.d.comb += [
687 wb.cyc.eq(~fsm.ongoing("IDLE")),
688 wb.stb.eq(fsm.ongoing("READ") | fsm.ongoing("WRITEREAD")),
689 wb.we.eq(fsm.ongoing("WRITEREAD")),
690 ]