You're reading an pre-release version of this documentation.
For the latest stable release version, please have a look at master.

Slots

Introduction

Let’s say you have some hardware which has to keep track of multiple similar ongoing activities, you may want to implement an array of “slots” to do so. This example show how to do it using Area, OHMasking.first, onMask and reader.

Implementation

This implementation avoid the use of Vec. Instead, it use Area which allow to mix signal, registers and logic definitions in each slot.

Note that the reader API is for SpinalHDL version coming after 1.9.1

package spinaldoc.examples.advanced

import spinal.core._
import spinal.lib._
import scala.language.postfixOps

case class SlotsDemo(slotsCount : Int) extends Component {
  // ...
  
  
  // Create the hardware for each slot
  // Note each slot is an Area, not a Bundle
  val slots = for(i <- 0 until slotsCount) yield new Area {
    // Because the slot is an Area, we can define mix signal, registers, logic definitions
    // Here are the registers for each slots
    val valid = RegInit(False)
    val address = Reg(UInt(8 bits))
    val age = Reg(UInt(16 bits)) // Will count since how many cycles the slot is valid

    // Here is some hardware behavior for each slots
    // Implement the age logic
    when(valid) {
      age := age + 1
    }

    // removeIt will be used as a slot interface later on
    val removeIt = False
    when(removeIt) {
      valid := False
    }
  }

  // Logic to allocate a new slot
  val insert = new Area {
    val cmd = Stream(UInt(8 bits)) // interface to issue requests
    val free = slots.map(!_.valid)
    val freeOh = OHMasking.first(free) // Get the first free slot (on hot mask)
    cmd.ready := free.orR // Only allow cmd when there is a free slot
    when(cmd.fire) {
      // slots.onMask(freeOh)(code) will execute the code for each slot where the corresponding freeOh bit is set
      slots.onMask(freeOh){slot =>
        slot.valid := True
        slot.address := cmd.payload
        slot.age := 0
      }
    }
  }

  // Logic to remove the slots which match a given address (assuming there is not more than one match)
  val remove = new Area {
    val cmd = Flow(UInt(8 bits)) // interface to issue requests
    val oh = slots.map(s => s.valid && s.address === cmd.payload) // oh meaning "one hot"
    when(cmd.fire) {
      slots.onMask(oh){ slot =>
        slot.removeIt := True
      }
    }

    val reader = slots.reader(oh) // Create a facility to read the slots using "oh" as index
    val age = reader(_.age) // Age of the slot which is selected by "oh"
  }
  
  // ...
}

object SlotsDemo extends App {
  SpinalVerilog(SlotsDemo(4))
}

In practice

For instance, this kind of slot pattern is used in Tilelink coherency hub to keep track of all ongoing memory probes in flight:

https://github.com/SpinalHDL/SpinalHDL/blob/008c73f1ce18e294f137efe7a1442bd3f8fa2ee0/lib/src/main/scala/spinal/lib/bus/tilelink/coherent/Hub.scala#L376

As well in the DRAM / SDR / DDR memory controller to implement the handling of multiple memory transactions at once (having multiple precharge / active / read / write running at the same time to improve performances) :

https://github.com/SpinalHDL/SpinalHDL/blob/1edba1890b5f629b28e5171b3c449155337d2548/lib/src/main/scala/spinal/lib/memory/sdram/xdr/Tasker.scala#L202

As well in the NaxRiscv (out of order CPU) load-store-unit to handle the store-queue / load-queue hardware (a bit too scary to show here in the doc XD)