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