8beb7ed6c85ab6124af513f262e4ad8e32a6f0e7
[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__(self, *, with_reset=False, ir_width=None,
305 manufacturer_id=Const(0b10001111111, 11),
306 part_number=Const(1, 16),
307 version=Const(0, 4),
308 name=None, src_loc_at=0:
309 assert((ir_width is None) or (isinstance(ir_width, int) and
310 ir_width >= 2))
311 assert(len(version) == 4)
312
313 if name is None:
314 name = get_var_name(depth=src_loc_at+2, default="TAP")
315 self.name = name
316 self.bus = Interface(with_reset=with_reset, name=self.name+"_bus",
317 src_loc_at=src_loc_at+1)
318
319 ##
320
321 self._ir_width = ir_width
322 self._manufacturer_id = manufacturer_id
323 self._part_number = part_number
324 self._version = version
325
326 self._ircodes = [0, 1, 2] # Already taken codes, all ones added at end
327
328 self._ios = []
329 self._srs = []
330 self._wbs = []
331
332 def elaborate(self, platform):
333 m = Module()
334
335 # Determine ir_width if not fixed.
336 ir_max = max(self._ircodes) + 1 # One extra code needed with all ones
337 ir_width = len("{:b}".format(ir_max))
338 if self._ir_width is not None:
339 assert self._ir_width >= ir_width, "Specified JTAG IR width " \
340 "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,
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 (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
390 # (like for PRELOAD)
391 io_shift.eq(select_io & fsm.shift),
392 io_update.eq(select_io & fsm.update),
393 io_bd2io.eq(ir == cmd_extest),
394 io_bd2core.eq(ir == cmd_intest),
395 ]
396 io_tdo = self._elaborate_ios(
397 m=m,
398 capture=io_capture, shift=io_shift, update=io_update,
399 bd2io=io_bd2io, bd2core=io_bd2core,
400 )
401
402 # chain tdo: select as appropriate, to go into into shiftregs
403 tdo = Signal(name=self.name+"_tdo")
404 with m.If(select_ir):
405 m.d.comb += tdo.eq(irblock.tdo)
406 with m.Elif(select_id):
407 m.d.comb += tdo.eq(idblock.tdo)
408 with m.Elif(select_io):
409 m.d.comb += tdo.eq(io_tdo)
410
411 # shiftregs block
412 self._elaborate_shiftregs(
413 m, capture=fsm.capture, shift=fsm.shift, update=fsm.update,
414 ir=irblock.ir, tdo_jtag=tdo
415 )
416
417 # wishbone
418 self._elaborate_wishbones(m)
419
420 return m
421
422
423 def add_io(self, *, iotype, name=None, src_loc_at=0):
424 """Add a io cell to the boundary scan chain
425
426 Parameters:
427 - iotype: :class:`IOType` enum.
428
429 Returns:
430 - :class:`IOConn`
431 """
432 if name is None:
433 name = "ioconn" + str(len(self._ios))
434
435 ioconn = IOConn(iotype=iotype, name=name, src_loc_at=src_loc_at+1)
436 self._ios.append(ioconn)
437 return ioconn
438
439 def _elaborate_ios(self, *, m, capture, shift, update, bd2io, bd2core):
440 connlength = {
441 IOType.In: 1,
442 IOType.Out: 1,
443 IOType.TriOut: 2,
444 IOType.InTriOut: 3,
445 }
446 length = sum(connlength[conn._iotype] for conn in self._ios)
447
448 io_sr = Signal(length)
449 io_bd = Signal(length)
450
451 with m.If(capture):
452 idx = 0
453 for conn in self._ios:
454 if conn._iotype == IOType.In:
455 m.d.posjtag += io_sr[idx].eq(conn.pad.i)
456 idx += 1
457 elif conn._iotype == IOType.Out:
458 m.d.posjtag += io_sr[idx].eq(conn.core.o)
459 idx += 1
460 elif conn._iotype == IOType.TriOut:
461 m.d.posjtag += [
462 io_sr[idx].eq(conn.core.o),
463 io_sr[idx+1].eq(conn.core.oe),
464 ]
465 idx += 2
466 elif conn._iotype == IOType.InTriOut:
467 m.d.posjtag += [
468 io_sr[idx].eq(conn.pad.i),
469 io_sr[idx+1].eq(conn.core.o),
470 io_sr[idx+2].eq(conn.core.oe),
471 ]
472 idx += 3
473 else:
474 raise("Internal error")
475 assert idx == length, "Internal error"
476 with m.Elif(shift):
477 m.d.posjtag += io_sr.eq(Cat(self.bus.tdi, io_sr[:-1]))
478 with m.Elif(update):
479 m.d.negjtag += io_bd.eq(io_sr)
480
481 idx = 0
482 for conn in self._ios:
483 if conn._iotype == IOType.In:
484 m.d.comb += conn.core.i.eq(Mux(bd2core, io_bd[idx], conn.pad.i))
485 idx += 1
486 elif conn._iotype == IOType.Out:
487 m.d.comb += conn.pad.o.eq(Mux(bd2io, io_bd[idx], conn.core.o))
488 idx += 1
489 elif conn._iotype == IOType.TriOut:
490 m.d.comb += [
491 conn.pad.o.eq(Mux(bd2io, io_bd[idx], conn.core.o)),
492 conn.pad.oe.eq(Mux(bd2io, io_bd[idx+1], conn.core.oe)),
493 ]
494 idx += 2
495 elif conn._iotype == IOType.InTriOut:
496 m.d.comb += [
497 conn.core.i.eq(Mux(bd2core, io_bd[idx], conn.pad.i)),
498 conn.pad.o.eq(Mux(bd2io, io_bd[idx+1], conn.core.o)),
499 conn.pad.oe.eq(Mux(bd2io, io_bd[idx+2], conn.core.oe)),
500 ]
501 idx += 3
502 else:
503 raise("Internal error")
504 assert idx == length, "Internal error"
505
506 return io_sr[-1]
507
508
509 def add_shiftreg(self, *, ircode, length, domain="sync", name=None,
510 src_loc_at=0):
511 """Add a shift register to the JTAG interface
512
513 Parameters:
514 - ircode: code(s) for the IR; int or sequence of ints. In the latter
515 case this shiftreg is shared between different IR codes.
516 - length: the length of the shift register
517 - domain: the domain on which the signal will be used"""
518
519 try:
520 ir_it = iter(ircode)
521 ircodes = ircode
522 except TypeError:
523 ir_it = ircodes = (ircode,)
524 for _ircode in ir_it:
525 if not isinstance(_ircode, int) or _ircode <= 0:
526 raise ValueError("IR code '{}' is not an int "
527 "greater than 0".format(_ircode))
528 if _ircode in self._ircodes:
529 raise ValueError("IR code '{}' already taken".format(_ircode))
530
531 self._ircodes.extend(ircodes)
532
533 if name is None:
534 name = "sr{}".format(len(self._srs))
535 sr = ShiftReg(sr_length=length, cmds=len(ircodes), name=name,
536 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`
560 # clockdomain latch update in `domain` clockdomain and see when
561 # it has falling edge.
562 # At that edge put isir in sr.oe for one `domain` clockdomain
563 update_core = Signal(name=sr.name+"_update_core")
564 update_core_prev = Signal(name=sr.name+"_update_core_prev")
565 m.d[domain] += [
566 update_core.eq(sr_update), # This is CDC from JTAG domain
567 # to given domain
568 update_core_prev.eq(update_core)
569 ]
570 with m.If(update_core_prev & ~update_core):
571 # Falling edge of update
572 m.d[domain] += sr.oe.eq(isir)
573 with m.Else():
574 m.d[domain] += sr.oe.eq(0)
575
576 with m.If(sr_shift):
577 m.d.posjtag += reg.eq(Cat(reg[1:], self.bus.tdi))
578 with m.If(sr_capture):
579 m.d.posjtag += reg.eq(sr.i)
580
581 # tdo = reg[0], tdo_en = shift
582 tdos.append((reg[0], sr_shift))
583
584
585 # Assign the right tdo to the bus tdo
586 for i, (tdo, tdo_en) in enumerate(tdos):
587 if i == 0:
588 with m.If(tdo_en):
589 m.d.comb += self.bus.tdo.eq(tdo)
590 else:
591 with m.Elif(tdo_en):
592 m.d.comb += self.bus.tdo.eq(tdo)
593
594 if len(tdos) > 0:
595 with m.Else():
596 m.d.comb += self.bus.tdo.eq(tdo_jtag)
597 else:
598 # Always connect tdo_jtag to
599 m.d.comb += self.bus.tdo.eq(tdo_jtag)
600
601
602 def add_wishbone(self, *, ircodes, address_width, data_width,
603 granularity=None, domain="sync",
604 name=None, src_loc_at=0):
605 """Add a wishbone interface
606
607 In order to allow high JTAG clock speed, data will be cached.
608 This means that if data is output the value of the next address
609 will be read automatically.
610
611 Parameters:
612 -----------
613 ircodes: sequence of three integer for the JTAG IR codes;
614 they represent resp. WBADDR, WBREAD and WBREADWRITE. First code
615 has a shift register of length 'address_width', the two other codes
616 share a shift register of length data_width.
617 address_width: width of the address
618 data_width: width of the data
619
620 Returns:
621 wb: nmigen_soc.wishbone.bus.Interface
622 The Wishbone interface, is pipelined and has stall field.
623 """
624 if len(ircodes) != 3:
625 raise ValueError("3 IR Codes have to be provided")
626
627 if name is None:
628 name = "wb" + str(len(self._wbs))
629 sr_addr = self.add_shiftreg(
630 ircode=ircodes[0], length=address_width, domain=domain,
631 name=name+"_addrsr"
632 )
633 sr_data = self.add_shiftreg(
634 ircode=ircodes[1:], length=data_width, domain=domain,
635 name=name+"_datasr"
636 )
637
638 wb = WishboneInterface(data_width=data_width, addr_width=address_width,
639 granularity=granularity,
640 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
673 # and keep it there til next read
674 m.d[domain] += sr_data.i.eq(wb.dat_r)
675 m.next = "IDLE"
676 with m.State("WRITEREAD"):
677 with m.If(~wb.stall):
678 m.next = "WRITEREADACK"
679 with m.State("WRITEREADACK"):
680 with m.If(wb.ack):
681 m.d[domain] += wb.adr.eq(wb.adr + 1)
682 m.next = "READ"
683
684 m.d.comb += [
685 wb.cyc.eq(~fsm.ongoing("IDLE")),
686 wb.stb.eq(fsm.ongoing("READ") | fsm.ongoing("WRITEREAD")),
687 wb.we.eq(fsm.ongoing("WRITEREAD")),
688 ]