comments on JTAG IO
[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 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 class IOType(Enum):
156 In = auto()
157 Out = auto()
158 TriOut = auto()
159 InTriOut = auto()
160
161 class IOConn(Record):
162 """TAP subblock representing the interface for an JTAG IO cell.
163 It contains signal to connect to the core and to the pad
164
165 This object is normally only allocated and returned from ``TAP.add_io``
166 It is a Record subclass.
167
168 Attributes
169 ----------
170 core: subrecord with signals for the core
171 i: Signal(1), present only for IOType.In and IOType.InTriOut.
172 Signal input to core with pad input value.
173 o: Signal(1), present only for IOType.Out, IOType.TriOut and IOType.InTriOut.
174 Signal output from core with the pad output value.
175 oe: Signal(1), present only for IOType.TriOut and IOType.InTriOut.
176 Signal output from core with the pad output enable value.
177 pad: subrecord with for the pad
178 i: Signal(1), present only for IOType.In and IOType.InTriOut
179 Output from pad with pad input value for core.
180 o: Signal(1), present only for IOType.Out, IOType.TriOut and 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, src_loc_at=src_loc_at+1)
199
200 self._iotype = iotype
201
202 class _IDBypassBlock(Elaboratable):
203 """TAP subblock for the ID shift register"""
204 def __init__(self, *, manufacturer_id, part_number, version,
205 tdi, capture, shift, update, bypass,
206 name):
207 self.name = name
208 if not isinstance(manufacturer_id, Const) and len(manufacturer_id) != 11:
209 raise ValueError("manufacturer_id has to be Const of length 11")
210 if not isinstance(part_number, Const) and len(manufacturer_id) != 16:
211 raise ValueError("part_number has to be Const of length 16")
212 if not isinstance(version, Const) and len(version) != 4:
213 raise ValueError("version has to be Const of length 4")
214 self._id = Cat(Const(1,1), manufacturer_id, part_number, version)
215
216 self.tdo = Signal(name=name+"_tdo")
217
218 self._tdi = tdi
219 self._capture = capture
220 self._shift = shift
221 self._update = update
222 self._bypass = bypass
223
224 def elaborate(self, platform):
225 m = Module()
226
227 sr = Signal(32, reset_less=True, name=self.name+"_sr")
228
229 # Local signals for the module
230 _tdi = Signal()
231 _capture = Signal()
232 _shift = Signal()
233 _update = Signal()
234 _bypass = Signal()
235
236 m.d.comb += [
237 _tdi.eq(self._tdi),
238 _capture.eq(self._capture),
239 _shift.eq(self._shift),
240 _update.eq(self._update),
241 _bypass.eq(self._bypass),
242 self.tdo.eq(sr[0]),
243 ]
244
245 with m.If(_capture):
246 m.d.posjtag += sr.eq(self._id)
247 with m.Elif(_shift):
248 with m.If(_bypass):
249 m.d.posjtag += sr[0].eq(_tdi)
250 with m.Else():
251 m.d.posjtag += sr.eq(Cat(sr[1:], _tdi))
252
253 return m
254
255
256 class ShiftReg(Record):
257 """Object with interface for extra shift registers on a TAP.
258
259 Parameters
260 ----------
261 sr_length : int
262 cmds : int, default=1
263 The number of corresponding JTAG instructions
264
265 This object is normally only allocated and returned from ``TAP.add_shiftreg``
266 It is a Record subclass.
267
268 Attributes
269 ----------
270 i: length=sr_length, FANIN
271 The input data sampled during capture state of the TAP
272 ie: length=cmds, FANOUT
273 Indicates that data is to be sampled by the JTAG TAP and
274 should be held stable. The bit indicates the corresponding
275 instruction for which data is asked.
276 This signal is kept high for a whole JTAG TAP clock cycle
277 and may thus be kept higher for more than one clock cycle
278 on the domain where ShiftReg is used.
279 The JTAG protocol does not allow insertion of wait states
280 so data need to be provided before ie goes down. The speed
281 of the response will determine the max. frequency for the
282 JTAG interface.
283 o: length=sr_length, FANOUT
284 The value of the shift register.
285 oe: length=cmds, FANOUT
286 Indicates that output is stable and can be sampled downstream because
287 JTAG TAP is in the Update state. The bit indicates the corresponding
288 instruction. The bit is only kept high for one clock cycle.
289 """
290 def __init__(self, *, sr_length, cmds=1, name=None, src_loc_at=0):
291 layout = [
292 ("i", sr_length, Direction.FANIN),
293 ("ie", cmds, Direction.FANOUT),
294 ("o", sr_length, Direction.FANOUT),
295 ("oe", cmds, Direction.FANOUT),
296 ]
297 super().__init__(layout, name=name, src_loc_at=src_loc_at+1)
298
299
300 class TAP(Elaboratable):
301 #TODO: Document TAP
302 def __init__(
303 self, *, with_reset=False, ir_width=None,
304 manufacturer_id=Const(0b10001111111, 11), part_number=Const(1, 16),
305 version=Const(0, 4),
306 name=None, src_loc_at=0
307 ):
308 assert((ir_width is None) or (isinstance(ir_width, int) and ir_width >= 2))
309 assert(len(version) == 4)
310
311 if name is None:
312 name = get_var_name(depth=src_loc_at+2, default="TAP")
313 self.name = name
314 self.bus = Interface(with_reset=with_reset, name=self.name+"_bus",
315 src_loc_at=src_loc_at+1)
316
317 ##
318
319 self._ir_width = ir_width
320 self._manufacturer_id = manufacturer_id
321 self._part_number = part_number
322 self._version = version
323
324 self._ircodes = [0, 1, 2] # Already taken codes, all ones added at the end
325
326 self._ios = []
327 self._srs = []
328 self._wbs = []
329
330 def elaborate(self, platform):
331 m = Module()
332
333 # Determine ir_width if not fixed.
334 ir_max = max(self._ircodes) + 1 # One extra code needed with all ones
335 ir_width = len("{:b}".format(ir_max))
336 if self._ir_width is not None:
337 assert self._ir_width >= ir_width, "Specified JTAG IR width not big enough for allocated shiift registers"
338 ir_width = self._ir_width
339
340 # TODO: Make commands numbers configurable
341 cmd_extest = 0
342 cmd_intest = 0
343 cmd_idcode = 1
344 cmd_sample = 2
345 cmd_preload = 2
346 cmd_bypass = 2**ir_width - 1 # All ones
347
348 m.submodules._fsm = fsm = _FSM(bus=self.bus)
349 m.domains.posjtag = fsm.posjtag
350 m.domains.negjtag = fsm.negjtag
351
352 # IR block
353 select_ir = fsm.isir
354 m.submodules._irblock = irblock = _IRBlock(
355 ir_width=ir_width, cmd_idcode=cmd_idcode, tdi=self.bus.tdi,
356 capture=(fsm.isir & fsm.capture),
357 shift=(fsm.isir & fsm.shift),
358 update=(fsm.isir & fsm.update),
359 name=self.name+"_ir",
360 )
361 ir = irblock.ir
362
363 # ID block
364 select_id = fsm.isdr & ((ir == cmd_idcode) | (ir == cmd_bypass))
365 m.submodules._idblock = idblock = _IDBypassBlock(
366 manufacturer_id=self._manufacturer_id, part_number=self._part_number,
367 version=self._version, tdi=self.bus.tdi,
368 capture=(select_id & fsm.capture),
369 shift=(select_id & fsm.shift),
370 update=(select_id & fsm.update),
371 bypass=(ir == cmd_bypass),
372 name=self.name+"_id",
373 )
374
375 # IO (Boundary scan) block
376 io_capture = Signal()
377 io_shift = Signal()
378 io_update = Signal()
379 io_bd2io = Signal()
380 io_bd2core = Signal()
381 sample = (ir == cmd_extest) | (ir == cmd_sample)
382 preload = (ir == cmd_preload)
383 select_io = fsm.isdr & (sample | preload)
384 m.d.comb += [
385 io_capture.eq(sample & fsm.capture), # Don't capture if not sample (like for PRELOAD)
386 io_shift.eq(select_io & fsm.shift),
387 io_update.eq(select_io & fsm.update),
388 io_bd2io.eq(ir == cmd_extest),
389 io_bd2core.eq(ir == cmd_intest),
390 ]
391 io_tdo = self._elaborate_ios(
392 m=m,
393 capture=io_capture, shift=io_shift, update=io_update,
394 bd2io=io_bd2io, bd2core=io_bd2core,
395 )
396
397 # chain tdo: select as appropriate, to go into into shiftregs
398 tdo = Signal(name=self.name+"_tdo")
399 with m.If(select_ir):
400 m.d.comb += tdo.eq(irblock.tdo)
401 with m.Elif(select_id):
402 m.d.comb += tdo.eq(idblock.tdo)
403 with m.Elif(select_io):
404 m.d.comb += tdo.eq(io_tdo)
405
406 # shiftregs block
407 self._elaborate_shiftregs(
408 m, capture=fsm.capture, shift=fsm.shift, update=fsm.update,
409 ir=irblock.ir, tdo_jtag=tdo
410 )
411
412 # wishbone
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 if length == 0:
443 return self.bus.tdi
444
445 io_sr = Signal(length)
446 io_bd = Signal(length)
447
448 # Boundary scan "capture" mode. makes I/O status available via SR
449 with m.If(capture):
450 idx = 0
451 for conn in self._ios:
452 if conn._iotype == IOType.In:
453 m.d.posjtag += io_sr[idx].eq(conn.pad.i)
454 idx += 1
455 elif conn._iotype == IOType.Out:
456 m.d.posjtag += io_sr[idx].eq(conn.core.o)
457 idx += 1
458 elif conn._iotype == IOType.TriOut:
459 m.d.posjtag += [
460 io_sr[idx].eq(conn.core.o),
461 io_sr[idx+1].eq(conn.core.oe),
462 ]
463 idx += 2
464 elif conn._iotype == IOType.InTriOut:
465 m.d.posjtag += [
466 io_sr[idx].eq(conn.pad.i),
467 io_sr[idx+1].eq(conn.core.o),
468 io_sr[idx+2].eq(conn.core.oe),
469 ]
470 idx += 3
471 else:
472 raise("Internal error")
473 assert idx == length, "Internal error"
474
475 # "Shift" mode (sends out captured data on tdo, sets incoming from tdi)
476 with m.Elif(shift):
477 m.d.posjtag += io_sr.eq(Cat(self.bus.tdi, io_sr[:-1]))
478
479 # "Update" mode
480 with m.Elif(update):
481 m.d.negjtag += io_bd.eq(io_sr)
482
483 # sets up IO (pad<->core) or in testing mode depending on requested
484 # mode, via Muxes controlled by bd2core and bd2io
485 idx = 0
486 for conn in self._ios:
487 if conn._iotype == IOType.In:
488 m.d.comb += conn.core.i.eq(Mux(bd2core, io_bd[idx], conn.pad.i))
489 idx += 1
490 elif conn._iotype == IOType.Out:
491 m.d.comb += conn.pad.o.eq(Mux(bd2io, io_bd[idx], conn.core.o))
492 idx += 1
493 elif conn._iotype == IOType.TriOut:
494 m.d.comb += [
495 conn.pad.o.eq(Mux(bd2io, io_bd[idx], conn.core.o)),
496 conn.pad.oe.eq(Mux(bd2io, io_bd[idx+1], conn.core.oe)),
497 ]
498 idx += 2
499 elif conn._iotype == IOType.InTriOut:
500 m.d.comb += [
501 conn.core.i.eq(Mux(bd2core, io_bd[idx], conn.pad.i)),
502 conn.pad.o.eq(Mux(bd2io, io_bd[idx+1], conn.core.o)),
503 conn.pad.oe.eq(Mux(bd2io, io_bd[idx+2], conn.core.oe)),
504 ]
505 idx += 3
506 else:
507 raise("Internal error")
508 assert idx == length, "Internal error"
509
510 return io_sr[-1]
511
512 def add_shiftreg(self, *, ircode, length, domain="sync", name=None, src_loc_at=0):
513 """Add a shift register to the JTAG interface
514
515 Parameters:
516 - ircode: code(s) for the IR; int or sequence of ints. In the latter case this
517 shiftreg is shared between different IR codes.
518 - length: the length of the shift register
519 - domain: the domain on which the signal will be used"""
520
521 try:
522 ir_it = iter(ircode)
523 ircodes = ircode
524 except TypeError:
525 ir_it = ircodes = (ircode,)
526 for _ircode in ir_it:
527 if not isinstance(_ircode, int) or _ircode <= 0:
528 raise ValueError("IR code '{}' is not an int greater than 0".format(_ircode))
529 if _ircode in self._ircodes:
530 raise ValueError("IR code '{}' already taken".format(_ircode))
531
532 self._ircodes.extend(ircodes)
533
534 if name is None:
535 name = "sr{}".format(len(self._srs))
536 sr = ShiftReg(sr_length=length, cmds=len(ircodes), name=name, src_loc_at=src_loc_at+1)
537 self._srs.append((ircodes, domain, sr))
538
539 return sr
540
541 def _elaborate_shiftregs(self, m, capture, shift, update, ir, tdo_jtag):
542 # tdos is tuple of (tdo, tdo_en) for each shiftreg
543 tdos = []
544 for ircodes, domain, sr in self._srs:
545 reg = Signal(len(sr.o), name=sr.name+"_reg")
546 m.d.comb += sr.o.eq(reg)
547
548 isir = Signal(len(ircodes), name=sr.name+"_isir")
549 sr_capture = Signal(name=sr.name+"_capture")
550 sr_shift = Signal(name=sr.name+"_shift")
551 sr_update = Signal(name=sr.name+"_update")
552 m.d.comb += [
553 isir.eq(Cat(ir == ircode for ircode in ircodes)),
554 sr_capture.eq((isir != 0) & capture),
555 sr_shift.eq((isir != 0) & shift),
556 sr_update.eq((isir != 0) & update),
557 ]
558
559 # update signal is on the JTAG clockdomain, sr.oe is on `domain` clockdomain
560 # latch update in `domain` clockdomain and see when it has falling edge.
561 # At that edge put isir in sr.oe for one `domain` clockdomain
562 update_core = Signal(name=sr.name+"_update_core")
563 update_core_prev = Signal(name=sr.name+"_update_core_prev")
564 m.d[domain] += [
565 update_core.eq(sr_update), # This is CDC from JTAG domain to given domain
566 update_core_prev.eq(update_core)
567 ]
568 with m.If(update_core_prev & ~update_core):
569 # Falling edge of update
570 m.d[domain] += sr.oe.eq(isir)
571 with m.Else():
572 m.d[domain] += sr.oe.eq(0)
573
574 with m.If(sr_shift):
575 m.d.posjtag += reg.eq(Cat(reg[1:], self.bus.tdi))
576 with m.If(sr_capture):
577 m.d.posjtag += reg.eq(sr.i)
578
579 # tdo = reg[0], tdo_en = shift
580 tdos.append((reg[0], sr_shift))
581
582
583 # Assign the right tdo to the bus tdo
584 for i, (tdo, tdo_en) in enumerate(tdos):
585 if i == 0:
586 with m.If(tdo_en):
587 m.d.comb += self.bus.tdo.eq(tdo)
588 else:
589 with m.Elif(tdo_en):
590 m.d.comb += self.bus.tdo.eq(tdo)
591
592 if len(tdos) > 0:
593 with m.Else():
594 m.d.comb += self.bus.tdo.eq(tdo_jtag)
595 else:
596 # Always connect tdo_jtag to
597 m.d.comb += self.bus.tdo.eq(tdo_jtag)
598
599
600 def add_wishbone(self, *, ircodes, address_width, data_width, granularity=None, domain="sync",
601 name=None, src_loc_at=0):
602 """Add a wishbone interface
603
604 In order to allow high JTAG clock speed, data will be cached. This means that if data is
605 output the value of the next address will be read automatically.
606
607 Parameters:
608 -----------
609 ircodes: sequence of three integer for the JTAG IR codes;
610 they represent resp. WBADDR, WBREAD and WBREADWRITE. First code
611 has a shift register of length 'address_width', the two other codes
612 share a shift register of length data_width.
613 address_width: width of the address
614 data_width: width of the data
615
616 Returns:
617 wb: nmigen_soc.wishbone.bus.Interface
618 The Wishbone interface, is pipelined and has stall field.
619 """
620 if len(ircodes) != 3:
621 raise ValueError("3 IR Codes have to be provided")
622
623 if name is None:
624 name = "wb" + str(len(self._wbs))
625 sr_addr = self.add_shiftreg(
626 ircode=ircodes[0], length=address_width, domain=domain,
627 name=name+"_addrsr"
628 )
629 sr_data = self.add_shiftreg(
630 ircode=ircodes[1:], length=data_width, domain=domain,
631 name=name+"_datasr"
632 )
633
634 wb = WishboneInterface(data_width=data_width, addr_width=address_width,
635 granularity=granularity, 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 and keep it there til next read
668 m.d[domain] += sr_data.i.eq(wb.dat_r)
669 m.next = "IDLE"
670 with m.State("WRITEREAD"):
671 with m.If(~wb.stall):
672 m.next = "WRITEREADACK"
673 with m.State("WRITEREADACK"):
674 with m.If(wb.ack):
675 m.d[domain] += wb.adr.eq(wb.adr + 1)
676 m.next = "READ"
677
678 m.d.comb += [
679 wb.cyc.eq(~fsm.ongoing("IDLE")),
680 wb.stb.eq(fsm.ongoing("READ") | fsm.ongoing("WRITEREAD")),
681 wb.we.eq(fsm.ongoing("WRITEREAD")),
682 ]