JTAG TAP

简介

重要

本页的目的是展示一个JTAG TAP(从设备)的非常规方式实现方法。

重要

这个实现并不简单,它混合了面向对象编程、抽象接口解耦、硬件生成和硬件描述。
当然,简单的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
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()
    }
  })
}

备注

关于Component.current.addPrePopTask(…) :
这允许您在当前组件构造结束时调用给定的代码。由于JtagInstruction面向对象的性质,doCapture、doShift、doUpdate 和 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
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的从端总线等。