Skip to content

Commit 226e13c

Browse files
committed
jtag.tap: implement IEEE 1149.1 boundary scan TAP controller.
Co-authored-by: [email protected]
1 parent 618a13f commit 226e13c

File tree

3 files changed

+312
-0
lines changed

3 files changed

+312
-0
lines changed

amaranth_stdio/jtag/__init__.py

Whitespace-only changes.

amaranth_stdio/jtag/tap.py

+235
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
from typing import Iterable
2+
3+
from amaranth import *
4+
from amaranth.lib import enum, data, wiring, io, cdc
5+
from amaranth.lib.wiring import In, Out
6+
7+
8+
__all__ = ["State", "DataRegister", "Controller"]
9+
10+
11+
class State(enum.Enum, shape=unsigned(4)):
12+
Test_Logic_Reset = 0x0
13+
Run_Test_Idle = 0x8
14+
15+
Select_DR_Scan = 0x1
16+
Capture_DR = 0x2
17+
Shift_DR = 0x3
18+
Exit1_DR = 0x4
19+
Pause_DR = 0x5
20+
Exit2_DR = 0x6
21+
Update_DR = 0x7
22+
23+
Select_IR_Scan = 0x9
24+
Capture_IR = 0xA
25+
Shift_IR = 0xB
26+
Exit1_IR = 0xC
27+
Pause_IR = 0xD
28+
Exit2_IR = 0xE
29+
Update_IR = 0xF
30+
31+
32+
class DataRegister(wiring.PureInterface):
33+
def __init__(self, length):
34+
assert length >= 1, "DR must be at least 1 bit long"
35+
36+
self._length = length
37+
38+
super().__init__(wiring.Signature({
39+
"cap": In(length),
40+
"upd": Out(length),
41+
}))
42+
43+
@property
44+
def length(self):
45+
return self._length
46+
47+
48+
class Controller(wiring.Component):
49+
def __init__(self, *, ir_length, ir_idcode=None):
50+
assert ir_length >= 2, "IR must be at least 2 bits long"
51+
52+
self._ir_length = ir_length
53+
self._drs = dict()
54+
55+
if ir_idcode is not None:
56+
self._dr_idcode = self.add({ir_idcode}, length=32)
57+
else:
58+
self._dr_idcode = None
59+
60+
super().__init__({
61+
# TRST# is implicit in the (asynchronous) reset signal of the `jtag` clock domain.
62+
# TCK is implicit in the clock signal `jtag` clock domain.
63+
"tms": Out(io.Buffer.Signature("i", 1)),
64+
"tdi": Out(io.Buffer.Signature("i", 1)),
65+
"tdo": Out(io.Buffer.Signature("o", 1)),
66+
67+
# TAP state.
68+
"state": Out(State, init=State.Test_Logic_Reset),
69+
70+
# The high bits of the value loaded into the IR scan chain in the Capture-IR state.
71+
# The low bits are fixed at `01` (with 1 loaded into the least significant bit).
72+
"ir_cap": In(ir_length - 2),
73+
# The last value loaded into the IR scan chain in the Update-IR state; in other words,
74+
# the contents of the instruction register.
75+
"ir_upd": Out(ir_length, init=~0 if ir_idcode is None else ir_idcode),
76+
})
77+
78+
@property
79+
def ir_length(self) -> int:
80+
return self._ir_length
81+
82+
@property
83+
def dr_idcode(self) -> DataRegister:
84+
return self._dr_idcode
85+
86+
def add(self, ir_values: Iterable[int], *, length: int) -> DataRegister:
87+
ir_values = set(ir_values)
88+
89+
for ir_value in ir_values:
90+
assert ir_value in range(0, 1 << self._ir_length), "IR value must be within range"
91+
assert ir_value != ((1 << self._ir_length) - 1), "IR value must not be all-ones"
92+
for used_ir_values in self._drs.values():
93+
assert not (ir_values & used_ir_values), "IR values must be unused"
94+
95+
dr = DataRegister(length)
96+
self._drs[dr] = ir_values
97+
return dr
98+
99+
def elaborate(self, platform):
100+
m = Module()
101+
102+
with m.Switch(self.state):
103+
with m.Case(State.Test_Logic_Reset):
104+
with m.If(~self.tms.i):
105+
m.d.jtag += self.state.eq(State.Run_Test_Idle)
106+
107+
with m.Case(State.Run_Test_Idle):
108+
with m.If(self.tms.i):
109+
m.d.jtag += self.state.eq(State.Select_DR_Scan)
110+
111+
with m.Case(State.Select_DR_Scan):
112+
with m.If(~self.tms.i):
113+
m.d.jtag += self.state.eq(State.Capture_DR)
114+
with m.Else():
115+
m.d.jtag += self.state.eq(State.Select_IR_Scan)
116+
117+
with m.Case(State.Capture_DR):
118+
with m.If(self.tms.i):
119+
m.d.jtag += self.state.eq(State.Exit1_DR)
120+
with m.Else():
121+
m.d.jtag += self.state.eq(State.Shift_DR)
122+
123+
with m.Case(State.Shift_DR):
124+
with m.If(self.tms.i):
125+
m.d.jtag += self.state.eq(State.Exit1_DR)
126+
127+
with m.Case(State.Exit1_DR):
128+
with m.If(self.tms.i):
129+
m.d.jtag += self.state.eq(State.Update_DR)
130+
with m.Else():
131+
m.d.jtag += self.state.eq(State.Pause_DR)
132+
133+
with m.Case(State.Pause_DR):
134+
with m.If(self.tms.i):
135+
m.d.jtag += self.state.eq(State.Exit2_DR)
136+
137+
with m.Case(State.Exit2_DR):
138+
with m.If(self.tms.i):
139+
m.d.jtag += self.state.eq(State.Update_DR)
140+
with m.Else():
141+
m.d.jtag += self.state.eq(State.Shift_DR)
142+
143+
with m.Case(State.Update_DR):
144+
with m.If(self.tms.i):
145+
m.d.jtag += self.state.eq(State.Select_DR_Scan)
146+
with m.Else():
147+
m.d.jtag += self.state.eq(State.Run_Test_Idle)
148+
149+
with m.Case(State.Select_IR_Scan):
150+
with m.If(~self.tms.i):
151+
m.d.jtag += self.state.eq(State.Capture_IR)
152+
with m.Else():
153+
m.d.jtag += self.state.eq(State.Test_Logic_Reset)
154+
155+
with m.Case(State.Capture_IR):
156+
with m.If(~self.tms.i):
157+
m.d.jtag += self.state.eq(State.Shift_IR)
158+
with m.Else():
159+
m.d.jtag += self.state.eq(State.Exit1_IR)
160+
161+
with m.Case(State.Shift_IR):
162+
with m.If(self.tms.i):
163+
m.d.jtag += self.state.eq(State.Exit1_IR)
164+
165+
with m.Case(State.Exit1_IR):
166+
with m.If(self.tms.i):
167+
m.d.jtag += self.state.eq(State.Update_IR)
168+
with m.Else():
169+
m.d.jtag += self.state.eq(State.Pause_IR)
170+
171+
with m.Case(State.Pause_IR):
172+
with m.If(self.tms.i):
173+
m.d.jtag += self.state.eq(State.Exit2_IR)
174+
175+
with m.Case(State.Exit2_IR):
176+
with m.If(self.tms.i):
177+
m.d.jtag += self.state.eq(State.Update_IR)
178+
with m.Else():
179+
m.d.jtag += self.state.eq(State.Shift_IR)
180+
181+
with m.Case(State.Update_IR):
182+
with m.If(self.tms.i):
183+
m.d.jtag += self.state.eq(State.Select_DR_Scan)
184+
with m.Else():
185+
m.d.jtag += self.state.eq(State.Run_Test_Idle)
186+
187+
dr_chain = Signal(max([1, *(dr.length for dr in self._drs)]))
188+
ir_chain = Signal(self.ir_length)
189+
190+
with m.Switch(self.state):
191+
m.d.comb += self.tdo.oe.eq(0)
192+
193+
with m.Case(State.Test_Logic_Reset):
194+
m.d.jtag += self.ir_upd.eq(self.ir_upd.init)
195+
for dr, ir_values in self._drs.items():
196+
m.d.jtag += dr.upd.eq(dr.upd.init)
197+
198+
with m.Case(State.Capture_DR):
199+
with m.Switch(self.ir_upd):
200+
for dr, ir_values in self._drs.items():
201+
with m.Case(*ir_values):
202+
m.d.jtag += dr_chain[-dr.length:].eq(dr.cap)
203+
with m.Default(): # BYPASS
204+
m.d.jtag += dr_chain.eq(0)
205+
206+
with m.Case(State.Shift_DR):
207+
m.d.jtag += dr_chain.eq(Cat(dr_chain[1:], self.tdi.i))
208+
with m.Switch(self.ir_upd):
209+
for dr, ir_values in self._drs.items():
210+
with m.Case(*ir_values):
211+
m.d.comb += self.tdo.o.eq(dr_chain[-dr.length])
212+
with m.Default(): # BYPASS
213+
m.d.comb += self.tdo.o.eq(dr_chain[-1])
214+
m.d.comb += self.tdo.oe.eq(1)
215+
216+
with m.Case(State.Update_DR):
217+
with m.Switch(self.ir_upd):
218+
for dr, ir_values in self._drs.items():
219+
with m.Case(*ir_values):
220+
m.d.jtag += dr.upd.eq(dr_chain[-dr.length:])
221+
with m.Default(): # BYPASS
222+
pass
223+
224+
with m.Case(State.Capture_IR):
225+
m.d.jtag += ir_chain.eq(Cat(1, 0, self.ir_cap))
226+
227+
with m.Case(State.Shift_IR):
228+
m.d.jtag += ir_chain.eq(Cat(ir_chain[1:], self.tdi.i))
229+
m.d.comb += self.tdo.o.eq(ir_chain[0])
230+
m.d.comb += self.tdo.oe.eq(1)
231+
232+
with m.Case(State.Update_IR):
233+
m.d.jtag += self.ir_upd.eq(ir_chain)
234+
235+
return m

tests/test_tap.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import functools
2+
import unittest
3+
4+
from amaranth import *
5+
from amaranth.sim import Simulator
6+
from amaranth_stdio.jtag import tap
7+
8+
9+
async def shift_tms(ctx, dut, tms, state_after, *, expected={}):
10+
ctx.set(dut.tms.i, tms)
11+
12+
# HACK(bin): i'm so sorry?
13+
(_, _, *sampled) = await ctx.tick("jtag").sample(*[getattr(dut, s).o for s in expected.keys()])
14+
assert ctx.get(dut.state == state_after)
15+
16+
for (dut_value, (name, expected_value)) in zip(sampled, expected.items()):
17+
assert dut_value == expected_value, f"dut.{name} != {expected_value:#b}"
18+
19+
20+
class TAPTestCase(unittest.TestCase):
21+
def test_tap_controller(self):
22+
ir_idcode = 0b10101010
23+
dr_idcode = 0b0011_1111000011110000_00001010100_1
24+
25+
m = Module()
26+
m.submodules.dut = dut = tap.Controller(ir_length=8, ir_idcode=ir_idcode)
27+
m.d.comb += dut.dr_idcode.cap.eq(dr_idcode)
28+
29+
async def testbench(ctx):
30+
global shift_tms
31+
shift_tms = functools.partial(shift_tms, ctx, dut)
32+
33+
assert ctx.get(dut.state) == tap.State.Test_Logic_Reset
34+
35+
await shift_tms(0, tap.State.Run_Test_Idle)
36+
await shift_tms(1, tap.State.Select_DR_Scan)
37+
await shift_tms(0, tap.State.Capture_DR)
38+
await shift_tms(0, tap.State.Shift_DR)
39+
40+
for i in range(32):
41+
await shift_tms(0, tap.State.Shift_DR, expected={
42+
"tdo": (dr_idcode >> i) & 1
43+
})
44+
45+
await shift_tms(1, tap.State.Exit1_DR)
46+
await shift_tms(0, tap.State.Pause_DR)
47+
await shift_tms(1, tap.State.Exit2_DR)
48+
await shift_tms(1, tap.State.Update_DR)
49+
await shift_tms(1, tap.State.Select_DR_Scan)
50+
await shift_tms(1, tap.State.Select_IR_Scan)
51+
52+
ctx.set(dut.ir_cap, 0b111111)
53+
await shift_tms(0, tap.State.Capture_IR)
54+
await shift_tms(0, tap.State.Shift_IR)
55+
await shift_tms(0, tap.State.Shift_IR, expected={
56+
"tdo": 0b1
57+
})
58+
await shift_tms(1, tap.State.Exit1_IR, expected={
59+
"tdo": 0b0
60+
})
61+
await shift_tms(0, tap.State.Pause_IR)
62+
await shift_tms(1, tap.State.Exit2_IR)
63+
await shift_tms(1, tap.State.Update_IR)
64+
await shift_tms(1, tap.State.Select_DR_Scan)
65+
assert ctx.get(dut.ir_upd) == 0b111111
66+
67+
await shift_tms(1, tap.State.Select_IR_Scan)
68+
await shift_tms(1, tap.State.Test_Logic_Reset)
69+
70+
await shift_tms(1, tap.State.Test_Logic_Reset)
71+
assert ctx.get(dut.ir_upd) == ir_idcode
72+
73+
sim = Simulator(m)
74+
sim.add_clock(1e-3, domain="jtag")
75+
sim.add_testbench(testbench)
76+
with sim.write_vcd("test_tap.vcd"):
77+
sim.run()

0 commit comments

Comments
 (0)