计时器

简介

计时器模块可能是最基本的硬件模块之一。但即使对于计时器,您也可以使用SpinalHDL做一些有趣的事情。这个示例将定义一个简单的计时器组件,其中集成了一个总线桥接实用工具。

计时器

那么让我们从 Timer 组件开始。

规范

Timer 组件将具有一个构造参数:

参数名称

类型

描述

width

Int

指定计时器计数器的位宽

还有一些输入/输出:

IO名称

方向

类型

描述

tick

in

Bool

当 tick 为 True 时,计时器计数到 limit 。

clear

in

Bool

当 tick 为 True 时,计时器设为零。 clear 优先于 tick 。

limit

in

UInt(width bits)

当计时器值等于 limit 时,禁止 tick 输入。

full

out

Bool

当计时器值等于 limit 并且 tick 为高时,full 为高。

value

out

UInt(width bits)

引出计时器的计数器值。

实现

case class Timer(width : Int) extends Component {
  val io = new Bundle {
    val tick  = in Bool()
    val clear = in Bool()
    val limit = in UInt(width bits)

    val full  = out Bool()
    val value = out UInt(width bits)
  }

  val counter = Reg(UInt(width bits))
  when(io.tick && !io.full) {
    counter := counter + 1
  }
  when(io.clear) {
    counter := 0
  }

  io.full := counter === io.limit && io.tick
  io.value := counter

}

桥接函数

现在我们可以从这个例子的主要目的开始:定义总线桥接功能。为此,我们将使用两种技术:

  • 使用在文档 此处 的 BusSlaveFactory 工具

  • 在 Timer 组件内定义一个函数,这个函数可以从父组件调用该函数,并以抽象方式驱动 Timer 的 IO。

规范

该桥接函数将使用以下参数:

参数名称

类型

描述

busCtrl

BusSlaveFactory

函数将使用 BusSlaveFactory 实例来创建桥接逻辑。

baseAddress

BigInt

桥接逻辑应映射到的基地址。

ticks

Seq[Bool]

可用作tick信号的Bool源序列。

clears

Seq[Bool]

可用作clear信号的Bool源序列。

假设寄存器映射的总线系统位宽是32位:

名称

访问

位宽

地址偏移

位偏移

描述

ticksEnable

RW

len(ticks)

0

0

每个 ticks 逻辑值都可以被相应的 ticksEnable 位取高激活。

clearsEnable

RW

len(clears)

0

16

每个 clears 逻辑值都可以被相应的 clearsEnable 位取高激活。

limit

RW

width

4

0

访问计时器组件的limit值。
当写入该寄存器时,计时器清零。

value

R

width

8

0

访问计时器的值。

clear

W

8

当写入该寄存器时,计时器清零。

实现

让我们在 Timer 组件中添加这个桥接函数。

case class Timer(width : Int) extends Component {
...
  // The function prototype uses Scala currying funcName(arg1,arg2)(arg3,arg3)
  // which allow to call the function with a nice syntax later
  // This function also returns an area, which allows to keep names of inner signals in the generated VHDL/Verilog.
  def driveFrom(busCtrl: BusSlaveFactory, baseAddress: BigInt)(ticks: Seq[Bool], clears: Seq[Bool]) = new Area {
    // Offset 0 => clear/tick masks + bus
    val ticksEnable = busCtrl.createReadAndWrite(Bits(ticks.length bits), baseAddress + 0,0) init(0)
    val clearsEnable = busCtrl.createReadAndWrite(Bits(clears.length bits), baseAddress + 0,16) init(0)
    val busClearing = False

    io.clear := (clearsEnable & clears.asBits).orR | busClearing
    io.tick := (ticksEnable  & ticks.asBits ).orR

    // Offset 4 => read/write limit (+ auto clear)
    busCtrl.driveAndRead(io.limit, baseAddress + 4)
    busClearing.setWhen(busCtrl.isWriting(baseAddress + 4))

    // Offset 8 => read timer value / write => clear timer value
    busCtrl.read(io.value, baseAddress + 8)
    busClearing.setWhen(busCtrl.isWriting(baseAddress + 8))
  }
}

用法

下面是一些演示代码,它与Pinsec SoC计时器模块中使用的代码非常接近。基本上,它实例化了以下元素:

  • 1个16位预分频器

  • 1个32位计时器

  • 3个16位计时器

然后,通过使用 Apb3SlaveFactory 和 Timer 内定义的函数,它在 APB3 总线和所有实例化组件之间创建桥接逻辑。

  val io = new Bundle {
    val apb = slave(Apb3(Apb3Config(addressWidth=8, dataWidth=32)))
    val interrupt = out Bool()
    val external = new Bundle {
      val tick = in Bool()
      val clear = in Bool()
    }
  }

  //Prescaler is very similar to the timer, it mainly integrates a piece of auto reload logic.
  val prescaler = Prescaler(width = 16)

  val timerA = Timer(width = 32)
  val timerB,timerC,timerD = Timer(width = 16)

  val busCtrl = Apb3SlaveFactory(io.apb)
  
  prescaler.driveFrom(busCtrl, 0x00)

  timerA.driveFrom(busCtrl, 0x40)(
    ticks=List(True, prescaler.io.overflow),
    clears=List(timerA.io.full)
  )
  timerB.driveFrom(busCtrl, 0x50)(
    ticks=List(True, prescaler.io.overflow, io.external.tick),
    clears=List(timerB.io.full, io.external.clear)
  )
  timerC.driveFrom(busCtrl, 0x60)(
    ticks=List(True, prescaler.io.overflow, io.external.tick),
    clears=List(timerC.io.full, io.external.clear)
  )
  timerD.driveFrom(busCtrl, 0x70)(
    ticks=List(True, prescaler.io.overflow, io.external.tick),
    clears=List(timerD.io.full, io.external.clear)
  )

  val interruptCtrl = InterruptCtrl(4)
  interruptCtrl.driveFrom(busCtrl, 0x10)
  interruptCtrl.io.inputs(0) := timerA.io.full
  interruptCtrl.io.inputs(1) := timerB.io.full
  interruptCtrl.io.inputs(2) := timerC.io.full
  interruptCtrl.io.inputs(3) := timerD.io.full
  io.interrupt := interruptCtrl.io.pendings.orR
}