计时器
简介
计时器模块可能是最基本的硬件模块之一。但即使对于计时器,您也可以使用SpinalHDL做一些有趣的事情。这个示例将定义一个简单的计时器组件,其中集成了一个总线桥接实用工具。
计时器
那么让我们从 Timer
组件开始。
规范
Timer
组件将具有一个构造参数:
参数名称 |
类型 |
描述 |
---|---|---|
width |
Int |
指定计时器计数器的位宽 |
还有一些输入/输出:
IO名称 |
方向 |
类型 |
描述 |
---|---|---|---|
tick |
in |
Bool |
当 |
clear |
in |
Bool |
当 |
limit |
in |
UInt(width bits) |
当计时器值等于 |
full |
out |
Bool |
当计时器值等于 |
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 |
函数将使用 |
baseAddress |
BigInt |
桥接逻辑应映射到的基地址。 |
ticks |
Seq[Bool] |
可用作tick信号的Bool源序列。 |
clears |
Seq[Bool] |
可用作clear信号的Bool源序列。 |
假设寄存器映射的总线系统位宽是32位:
名称 |
访问 |
位宽 |
地址偏移 |
位偏移 |
描述 |
---|---|---|---|---|---|
ticksEnable |
RW |
len(ticks) |
0 |
0 |
Each |
clearsEnable |
RW |
len(clears) |
0 |
16 |
Each |
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
}