.. role:: raw-html-m2r(raw) :format: html .. _Timer: Timer ===== Introduction ------------ A timer module is probably one of the most basic pieces of hardware. But even for a timer, there are some interesting things that you can do with SpinalHDL. This example will define a simple timer component which integrates a bus bridging utile. Timer ----- So let's start with the ``Timer`` component. Specification ^^^^^^^^^^^^^ The ``Timer`` component will have a single construction parameter: .. list-table:: :header-rows: 1 :widths: 1 1 2 * - Parameter Name - Type - Description * - width - Int - Specify the bit width of the timer counter And also some inputs/outputs: .. list-table:: :header-rows: 1 :widths: 1 1 2 4 * - IO Name - Direction - Type - Description * - tick - in - Bool - When ``tick`` is True, the timer count up until ``limit``. * - clear - in - Bool - When ``tick`` is True, the timer is set to zero. ``clear`` has priority over ``tick``. * - limit - in - UInt(width bits) - When the timer value is equal to ``limit``\ , the ``tick`` input is inhibited. * - full - out - Bool - ``full`` is high when the timer value is equal to ``limit`` and ``tick`` is high. * - value - out - UInt(width bits) - Wire out the timer counter value. Implementation ^^^^^^^^^^^^^^ .. code-block:: scala 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 } Bridging function ----------------- Now we can start with the main purpose of this example: defining a bus bridging function. To do that we will use two techniques: * Using the ``BusSlaveFactory`` tool documented :ref:`here ` * Defining a function inside the ``Timer`` component which can be called from the parent component to drive the ``Timer``\ 's IO in an abstract way. Specification ^^^^^^^^^^^^^ This bridging function will take the following parameters: .. list-table:: :header-rows: 1 :widths: 1 1 10 * - Parameter Name - Type - Description * - busCtrl - BusSlaveFactory - The ``BusSlaveFactory`` instance that will be used by the function to create the bridging logic. * - baseAddress - BigInt - The base address where the bridging logic should be mapped. * - ticks - Seq[Bool] - A list of Bool sources that can be used as a tick signal. * - clears - Seq[Bool] - A list of Bool sources that can be used as a clear signal. The register mapping assumes that the bus system is 32 bits wide: .. list-table:: :header-rows: 1 :widths: 1 1 1 1 1 10 * - Name - Access - Width - Address offset - Bit offset - Description * - ticksEnable - RW - len(ticks) - 0 - 0 - Each ``ticks`` bool can be actived if the corresponding ``ticksEnable`` bit is high. * - clearsEnable - RW - len(clears) - 0 - 16 - Each ``clears`` bool can be actived if the corresponding ``clearsEnable`` bit is high. * - limit - RW - width - 4 - 0 - | Access the limit value of the timer component. | When this register is written to, the timer is cleared. * - value - R - width - 8 - 0 - Access the value of the timer. * - clear - W - - 8 - - When this register is written to, it clears the timer. Implementation ^^^^^^^^^^^^^^ Let's add this bridging function inside the ``Timer`` component. .. code-block:: scala 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) } // Logic previously defined // .... // 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 { //Address 0 => clear/tick masks + bus val ticksEnable = busCtrl.createReadWrite(Bits(ticks.length bits),baseAddress + 0,0) init(0) val clearsEnable = busCtrl.createReadWrite(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 //Address 4 => read/write limit (+ auto clear) busCtrl.driveAndRead(io.limit,baseAddress + 4) busClearing setWhen(busCtrl.isWriting(baseAddress + 4)) //Address 8 => read timer value / write => clear timer value busCtrl.read(io.value,baseAddress + 8) busClearing setWhen(busCtrl.isWriting(baseAddress + 8)) } } Usage ^^^^^ Here is some demonstration code which is very close to the one used in the Pinsec SoC timer module. Basically it instantiates following elements: * One 16 bit prescaler * One 32 bit timer * Three 16 bit timers Then by using an ``Apb3SlaveFactory`` and functions defined inside the ``Timer``\ s, it creates bridging logic between the APB3 bus and all instantiated components. .. code-block:: scala val io = new Bundle{ val apb = Apb3(ApbConfig(addressWidth = 8, dataWidth = 32)) val interrupt = in Bool() val external = new Bundle{ val tick = Bool() val clear = 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) val prescalerBridge = prescaler.driveFrom(busCtrl,0x00) val timerABridge = timerA.driveFrom(busCtrl,0x40)( // The first element is True, which allows you to have a mode where the timer is always counting up. ticks = List(True, prescaler.io.overflow), // By looping the timer full to the clears, it allows you to create an autoreload mode. clears = List(timerA.io.full) ) val timerBBridge = timerB.driveFrom(busCtrl,0x50)( //The external.tick could allow to create an impulsion counter mode ticks = List(True, prescaler.io.overflow, io.external.tick), //external.clear could allow to create an timeout mode. clears = List(timerB.io.full, io.external.clear) ) val timerCBridge = timerC.driveFrom(busCtrl,0x60)( ticks = List(True, prescaler.io.overflow, io.external.tick), clears = List(timerC.io.full, io.external.clear) ) val timerDBridge = 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) val interruptCtrlBridge = 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