计时器

简介

计时器模块可能是最基本的硬件模块之一。但即使对于计时器,您也可以使用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

Each ticks bool can be activated if the corresponding ticksEnable bit is high.

clearsEnable

RW

len(clears)

0

16

Each clears bool can be activated if the corresponding clearsEnable bit is high.

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位计时器

然后,通过使用 Apb3SlaveFactoryTimer 内定义的函数,它在 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
}