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