JTAG TAP
简介
重要
本页的目的是展示一个JTAG TAP(从设备)的非常规方式实现方法。
重要
重要
本页不会解释 JTAG 的工作原理。可以在 这里 找到一个很好的教程。
常用的HDL与Spinal之间的一个重要区别在于,SpinalHDL允许您定义硬件生成器/构建器。这与描述硬件的方式非常不同。让我们看看下面的例子,因为在生成/构建/描述之间的区别可能看起来像是在“玩文字游戏”,或者可以用不同的方式解释。
下面的示例是一个JTAG TAP,它允许JTAG主设备读取 switchs
/keys
的输入并写入 leds
的输出。主设备也可以通过使用UID 0x87654321来识别此 TAP。
class SimpleJtagTap extends Component {
val io = new Bundle {
val jtag = slave(Jtag())
val switchs = in Bits(8 bits)
val keys = in Bits(4 bits)
val leds = out Bits(8 bits)
}
val tap = new JtagTap(io.jtag, 8)
val idcodeArea = tap.idcode(B"x87654321") (instructionId=4)
val switchsArea = tap.read(io.switchs) (instructionId=5)
val keysArea = tap.read(io.keys) (instructionId=6)
val ledsArea = tap.write(io.leds) (instructionId=7)
}
正如您所看到的,上例创建了一个 JtagTap,不过又调用了一些生成(Generator)/构建(Builder)函数(idcode、read、write)来创建每个JTAG指令。这就是我所说的“硬件生成器/构建器”,然后用户使用这些生成器/构建器来描述硬件。还有一点是,在通常的HDL中,你只能描述你的硬件,这意味着很多繁琐的工作。
本JTAG TAP教程基于 该库 实现。
JTAG总线
首先我们需要定义一个JTAG总线束。
case class Jtag() extends Bundle with IMasterSlave {
val tms = Bool()
val tdi = Bool()
val tdo = Bool()
override def asMaster() : Unit = {
out(tdi, tms)
in(tdo)
}
}
正如您所看到的,该总线不包含TCK引脚,因为它将由时钟域提供。
JTAG状态机
让我们定义JTAG状态机,依据 此处 所述。
object JtagState extends SpinalEnum {
val RESET, IDLE,
IR_SELECT, IR_CAPTURE, IR_SHIFT, IR_EXIT1, IR_PAUSE, IR_EXIT2, IR_UPDATE,
DR_SELECT, DR_CAPTURE, DR_SHIFT, DR_EXIT1, DR_PAUSE, DR_EXIT2, DR_UPDATE = newElement()
}
class JtagFsm(jtag: Jtag) extends Area {
import JtagState._
val stateNext = JtagState()
val state = RegNext(stateNext) randBoot()
stateNext := state.mux(
default -> (jtag.tms ? RESET | IDLE), // RESET
IDLE -> (jtag.tms ? DR_SELECT | IDLE),
IR_SELECT -> (jtag.tms ? RESET | IR_CAPTURE),
IR_CAPTURE -> (jtag.tms ? IR_EXIT1 | IR_SHIFT),
IR_SHIFT -> (jtag.tms ? IR_EXIT1 | IR_SHIFT),
IR_EXIT1 -> (jtag.tms ? IR_UPDATE | IR_PAUSE),
IR_PAUSE -> (jtag.tms ? IR_EXIT2 | IR_PAUSE),
IR_EXIT2 -> (jtag.tms ? IR_UPDATE | IR_SHIFT),
IR_UPDATE -> (jtag.tms ? DR_SELECT | IDLE),
DR_SELECT -> (jtag.tms ? IR_SELECT | DR_CAPTURE),
DR_CAPTURE -> (jtag.tms ? DR_EXIT1 | DR_SHIFT),
DR_SHIFT -> (jtag.tms ? DR_EXIT1 | DR_SHIFT),
DR_EXIT1 -> (jtag.tms ? DR_UPDATE | DR_PAUSE),
DR_PAUSE -> (jtag.tms ? DR_EXIT2 | DR_PAUSE),
DR_EXIT2 -> (jtag.tms ? DR_UPDATE | DR_SHIFT),
DR_UPDATE -> (jtag.tms ? DR_SELECT | IDLE)
)
}
备注
state
中的 randBoot()
会使其以随机状态初始化。这仅用于仿真目的。
JTAG TAP
让我们不使用任何指令,只用最基本的指令寄存器(IR)控制和旁路控制实现JTAG TAP的核心部分。
class JtagTap(val jtag: Jtag, instructionWidth: Int) extends Area with JtagTapAccess {
val fsm = new JtagFsm(jtag)
val instruction = Reg(Bits(instructionWidth bits))
val instructionShift = Reg(Bits(instructionWidth bits))
val bypass = Reg(Bool())
jtag.tdo := bypass
switch(fsm.state) {
is(JtagState.IR_CAPTURE) {
instructionShift := instruction
}
is(JtagState.IR_SHIFT) {
instructionShift := (jtag.tdi ## instructionShift) >> 1
jtag.tdo := instructionShift.lsb
}
is(JtagState.IR_UPDATE) {
instruction := instructionShift
}
is(JtagState.DR_SHIFT) {
bypass := jtag.tdi
}
}
}
备注
暂时忽略 with JTagTapAccess 的引用,下面将会进一步解释。
Jtag指令
现在JTAG TAP核心部分已经完成了,我们可以考虑如何通过可重用的方式来实现JTAG指令。
JTAG TAP类接口
首先,我们需要定义指令如何与JTAG TAP内核交互。我们当然可以直接使用JtagTap区域(area)。但这不是很好,因为在某些情况下JTAG TAP内核是由另一个IP(例如Altera的虚拟JTAG)提供的。
因此,让我们在JTAG TAP内核和指令之间定义一个简单抽象接口:
trait JtagTapAccess {
def getTdi: Bool
def getTms: Bool
def setTdo(value: Bool): Unit
def getState: JtagState.C
def getInstruction(): Bits
def setInstruction(value: Bits) : Unit
}
然后让 JtagTap
实现这个抽象接口:
class JtagTap(val jtag: Jtag, ...) extends Area with JtagTapAccess {
...
override def getTdi: Bool = jtag.tdi
override def setTdo(value: Bool): Unit = jtag.tdo := value
override def getTms: Bool = jtag.tms
override def getState: JtagState.C = fsm.state
override def getInstruction(): Bits = instruction
override def setInstruction(value: Bits): Unit = instruction := value
}
基类
让我们为JTAG指令定义一个有用的基类,它根据所选指令和JTAG TAP的状态提供一些回调(doCapture/doShift/doUpdate/doReset):
class JtagInstruction(tap: JtagTapAccess,val instructionId: Bits) extends Area {
def doCapture(): Unit = {}
def doShift(): Unit = {}
def doUpdate(): Unit = {}
def doReset(): Unit = {}
val instructionHit = tap.getInstruction === instructionId
Component.current.addPrePopTask(() => {
when(instructionHit) {
when(tap.getState === JtagState.DR_CAPTURE) {
doCapture()
}
when(tap.getState === JtagState.DR_SHIFT) {
doShift()
}
when(tap.getState === JtagState.DR_UPDATE) {
doUpdate()
}
}
when(tap.getState === JtagState.RESET) {
doReset()
}
})
}
备注
读指令
让我们实现一条允许JTAG读取信号的指令。
class JtagInstructionRead[T <: Data](data: T)(tap: JtagTapAccess, instructionId: Bits) extends JtagInstruction(tap, instructionId) {
val shifter = Reg(Bits(data.getBitsWidth bits))
override def doCapture(): Unit = {
shifter := data.asBits
}
override def doShift(): Unit = {
shifter := (tap.getTdi ## shifter) >> 1
tap.setTdo(shifter.lsb)
}
}
写指令
让我们实现一条允许JTAG写入寄存器(并读取其当前值)的指令。
class JtagInstructionWrite[T <: Data](data: T, cleanUpdate: Boolean, readable: Boolean)(tap: JtagTapAccess, instructionId: Bits) extends JtagInstruction(tap, instructionId) {
val shifter, store = Reg(Bits(data.getBitsWidth bit))
override def doCapture(): Unit = {
shifter := store
}
override def doShift(): Unit = {
shifter := (tap.getTdi ## shifter) >> 1
tap.setTdo(shifter.lsb)
}
override def doUpdate(): Unit = {
store := shifter
}
data.assignFromBits(store)
}
Idcode指令
让我们实现向JTAG返回idcode的指令,并且当发生复位时,将指令寄存器 (IR) 设置为它自己的instructionId。
class JtagInstructionIdcode[T <: Data](value: Bits)(tap: JtagTapAccess, instructionId: Bits) extends JtagInstruction(tap, instructionId) {
val shifter = Reg(Bits(32 bit))
override def doShift(): Unit = {
shifter := (tap.getTdi ## shifter) >> 1
tap.setTdo(shifter.lsb)
}
override def doReset(): Unit = {
shifter := value
tap.setInstruction(instructionId)
}
}
用户友好型包装
让我们向JtagTapAccess添加一些对用户友好的功能,使指令实例化更容易。
trait JtagTapAccess {
...
def idcode(value: Bits)(instructionId: Bits) =
new JtagInstructionIdcode(value)(this, instructionId)
def read[T <: Data](data: T)(instructionId: Bits) =
new JtagInstructionRead(data)(this, instructionId)
def write[T <: Data](data: T, cleanUpdate: Boolean = true, readable: Boolean = true)(instructionId: Bits) =
new JtagInstructionWrite[T](data, cleanUpdate, readable)(this, instructionId)
}
使用演示
现在,我们可以非常容易地创建应用特定的JTAG TAP,而无需编写任何逻辑或任何互连。
class SimpleJtagTap extends Component {
val io = new Bundle {
val jtag = slave(Jtag())
val switchs = in Bits(8 bits)
val keys = in Bits(4 bits)
val leds = out Bits(8 bits)
}
val tap = new JtagTap(io.jtag, 8)
val idcodeArea = tap.idcode(B"x87654321") (instructionId=4)
val switchsArea = tap.read(io.switchs) (instructionId=5)
val keysArea = tap.read(io.keys) (instructionId=6)
val ledsArea = tap.write(io.leds) (instructionId=7)
}
// end SimpleJtagTap
这种处理方式(生成硬件)也可以应用于生成APB/AHB/AXI的从端总线等。