.. role:: raw-html-m2r(raw) :format: html .. _bus_slave_factory_implementation: Bus Slave Factory Implementation ================================ Introduction ------------ This page will document the implementation of the BusSlaveFactory tool and one of those variant. You can get more information about the functionality of that tool :ref:`there `. Specification ------------- The class diagram is the following : .. image:: /asset/picture/bus_slave_factory_classes.svg :align: center The ``BusSlaveFactory`` abstract class define minimum requirements that each implementation of it should provide : .. list-table:: :header-rows: 1 :widths: 1 5 * - Name - Description * - busDataWidth - Return the data width of the bus * - read(that,address,bitOffset) - When the bus read the ``address``\ , fill the response with ``that`` at ``bitOffset`` * - write(that,address,bitOffset) - When the bus write the ``address``\ , assign ``that`` with bus's data from ``bitOffset`` * - onWrite(address)(doThat) - Call ``doThat`` when a write transaction occur on ``address`` * - onRead(address)(doThat) - Call ``doThat`` when a read transaction occur on ``address`` * - nonStopWrite(that,bitOffset) - Permanently assign ``that`` by the bus write data from ``bitOffset`` By using them the ``BusSlaveFactory`` should also be able to provide many utilities : .. list-table:: :header-rows: 1 :widths: 2 1 10 * - Name - Return - Description * - readAndWrite(that,address,bitOffset) - - Make ``that`` readable and writable at ``address`` and placed at ``bitOffset`` in the word * - readMultiWord(that,address) - - | Create the memory mapping to read ``that`` from 'address'. : | If ``that`` is bigger than one word it extends the register on followings addresses * - writeMultiWord(that,address) - - | Create the memory mapping to write ``that`` at 'address'. : | If ``that`` is bigger than one word it extends the register on followings addresses * - createWriteOnly(dataType,address,bitOffset) - T - Create a write only register of type ``dataType`` at ``address`` and placed at ``bitOffset`` in the word * - createReadWrite(dataType,address,bitOffset) - T - Create a read write register of type ``dataType`` at ``address`` and placed at ``bitOffset`` in the word * - createAndDriveFlow(dataType,address,bitOffset) - Flow[T] - Create a writable Flow register of type ``dataType`` at ``address`` and placed at ``bitOffset`` in the word * - drive(that,address,bitOffset) - - Drive ``that`` with a register writable at ``address`` placed at ``bitOffset`` in the word * - driveAndRead(that,address,bitOffset) - - Drive ``that`` with a register writable and readable at ``address`` placed at ``bitOffset`` in the word * - driveFlow(that,address,bitOffset) - - Emit on ``that`` a transaction when a write happen at ``address`` by using data placed at ``bitOffset`` in the word * - | readStreamNonBlocking(that,address, | validBitOffset,payloadBitOffset) - - | Read ``that`` and consume the transaction when a read happen at ``address``. | valid <= validBitOffset bit | payload <= payloadBitOffset+widthOf(payload) downto ``payloadBitOffset`` * - | doBitsAccumulationAndClearOnRead | (that,address,bitOffset) - - | Instanciate an internal register which at each cycle do : | reg := reg | that | Then when a read occur, the register is cleared. This register is readable at ``address`` and placed at ``bitOffset`` in the word About ``BusSlaveFactoryDelayed``, it's still an abstract class, but it capture each primitives (BusSlaveFactoryElement) calls into a data-model. This datamodel is one list that contain all primitives, but also a HashMap that link each address used to a list of primitives that are using it. Then when they all are collected (at the end of the current component), it do a callback that should be implemented by classes that extends it. The implementation of this callback should implement the hardware corresponding to all primitives collected. Implementation -------------- BusSlaveFactory ^^^^^^^^^^^^^^^ Let's describe primitives abstract function : .. code-block:: scala trait BusSlaveFactory extends Area{ def busDataWidth : Int def read(that : Data, address : BigInt, bitOffset : Int = 0) : Unit def write(that : Data, address : BigInt, bitOffset : Int = 0) : Unit def onWrite(address : BigInt)(doThat : => Unit) : Unit def onRead (address : BigInt)(doThat : => Unit) : Unit def nonStopWrite( that : Data, bitOffset : Int = 0) : Unit //... } Then let's operate the magic to implement all utile based on them : .. code-block:: scala trait BusSlaveFactory extends Area{ //... def readAndWrite(that : Data, address: BigInt, bitOffset : Int = 0): Unit = { write(that,address,bitOffset) read(that,address,bitOffset) } def drive(that : Data, address : BigInt, bitOffset : Int = 0) : Unit = { val reg = Reg(that) write(reg,address,bitOffset) that := reg } def driveAndRead(that : Data, address : BigInt, bitOffset : Int = 0) : Unit = { val reg = Reg(that) write(reg,address,bitOffset) read(reg,address,bitOffset) that := reg } def driveFlow[T <: Data](that : Flow[T], address: BigInt, bitOffset : Int = 0) : Unit = { that.valid := False onWrite(address){ that.valid := True } nonStopWrite(that.payload,bitOffset) } def createReadWrite[T <: Data](dataType: T, address: BigInt, bitOffset : Int = 0): T = { val reg = Reg(dataType) write(reg,address,bitOffset) read(reg,address,bitOffset) reg } def createAndDriveFlow[T <: Data](dataType : T, address: BigInt, bitOffset : Int = 0) : Flow[T] = { val flow = Flow(dataType) driveFlow(flow,address,bitOffset) flow } def doBitsAccumulationAndClearOnRead( that : Bits, address : BigInt, bitOffset : Int = 0): Unit = { assert(that.getWidth <= busDataWidth) val reg = Reg(that) reg := reg | that read(reg,address,bitOffset) onRead(address){ reg := that } } def readStreamNonBlocking[T <: Data] (that : Stream[T], address: BigInt, validBitOffset : Int, payloadBitOffset : Int) : Unit = { that.ready := False onRead(address){ that.ready := True } read(that.valid ,address,validBitOffset) read(that.payload,address,payloadBitOffset) } def readMultiWord(that : Data, address : BigInt) : Unit = { val wordCount = (widthOf(that) - 1) / busDataWidth + 1 val valueBits = that.asBits.resize(wordCount*busDataWidth) val words = (0 until wordCount).map(id => valueBits(id * busDataWidth , busDataWidth bits)) for (wordId <- (0 until wordCount)) { read(words(wordId), address + wordId*busDataWidth/8) } } def writeMultiWord(that : Data, address : BigInt) : Unit = { val wordCount = (widthOf(that) - 1) / busDataWidth + 1 for (wordId <- (0 until wordCount)) { write( that = new DataWrapper{ override def getBitsWidth: Int = Math.min(busDataWidth, widthOf(that) - wordId * busDataWidth) override def assignFromBits(value : Bits): Unit = { that.assignFromBits( bits = value.resized, offset = wordId * busDataWidth, bitCount = getBitsWidth bits) } },address = address + wordId * busDataWidth / 8,0 ) } } } BusSlaveFactoryDelayed ^^^^^^^^^^^^^^^^^^^^^^ Let's implement classes that will be used to store primitives : .. code-block:: scala trait BusSlaveFactoryElement // Ask to make `that` readable when a access is done on `address`. // bitOffset specify where `that` is placed on the answer case class BusSlaveFactoryRead(that : Data, address : BigInt, bitOffset : Int) extends BusSlaveFactoryElement // Ask to make `that` writable when a access is done on `address`. // bitOffset specify where `that` get bits from the request case class BusSlaveFactoryWrite(that : Data, address : BigInt, bitOffset : Int) extends BusSlaveFactoryElement // Ask to execute `doThat` when a write access is done on `address` case class BusSlaveFactoryOnWrite(address : BigInt, doThat : () => Unit) extends BusSlaveFactoryElement // Ask to execute `doThat` when a read access is done on `address` case class BusSlaveFactoryOnRead( address : BigInt, doThat : () => Unit) extends BusSlaveFactoryElement // Ask to constantly drive `that` with the data bus // bitOffset specify where `that` get bits from the request case class BusSlaveFactoryNonStopWrite(that : Data, bitOffset : Int) extends BusSlaveFactoryElement Then let's implement the ``BusSlaveFactoryDelayed`` itself : .. code-block:: scala trait BusSlaveFactoryDelayed extends BusSlaveFactory{ // elements is an array of all BusSlaveFactoryElement requested val elements = ArrayBuffer[BusSlaveFactoryElement]() // elementsPerAddress is more structured than elements, it group all BusSlaveFactoryElement per requested addresses val elementsPerAddress = collection.mutable.HashMap[BigInt,ArrayBuffer[BusSlaveFactoryElement]]() private def addAddressableElement(e : BusSlaveFactoryElement,address : BigInt) = { elements += e elementsPerAddress.getOrElseUpdate(address, ArrayBuffer[BusSlaveFactoryElement]()) += e } override def read(that : Data, address : BigInt, bitOffset : Int = 0) : Unit = { assert(bitOffset + that.getBitsWidth <= busDataWidth) addAddressableElement(BusSlaveFactoryRead(that,address,bitOffset),address) } override def write(that : Data, address : BigInt, bitOffset : Int = 0) : Unit = { assert(bitOffset + that.getBitsWidth <= busDataWidth) addAddressableElement(BusSlaveFactoryWrite(that,address,bitOffset),address) } def onWrite(address : BigInt)(doThat : => Unit) : Unit = { addAddressableElement(BusSlaveFactoryOnWrite(address,() => doThat),address) } def onRead (address : BigInt)(doThat : => Unit) : Unit = { addAddressableElement(BusSlaveFactoryOnRead(address,() => doThat),address) } def nonStopWrite( that : Data, bitOffset : Int = 0) : Unit = { assert(bitOffset + that.getBitsWidth <= busDataWidth) elements += BusSlaveFactoryNonStopWrite(that,bitOffset) } //This is the only thing that should be implement by class that extends BusSlaveFactoryDelayed def build() : Unit component.addPrePopTask(() => build()) } AvalonMMSlaveFactory ^^^^^^^^^^^^^^^^^^^^ First let's implement the companion object that provide the compatible AvalonMM configuration object that correspond to the following table : .. list-table:: :header-rows: 1 :widths: 2 3 4 * - Pin name - Type - Description * - read - Bool - High one cycle to produce a read request * - write - Bool - High one cycle to produce a write request * - address - UInt(addressWidth bits) - Byte granularity but word aligned * - writeData - Bits(dataWidth bits) - * - readDataValid - Bool - High to respond a read command * - readData - Bool(dataWidth bits) - Valid when readDataValid is high .. code-block:: scala object AvalonMMSlaveFactory{ def getAvalonConfig( addressWidth : Int, dataWidth : Int) = { AvalonMMConfig.pipelined( //Create a simple pipelined configuration of the Avalon Bus addressWidth = addressWidth, dataWidth = dataWidth ).copy( //Change some parameters of the configuration useByteEnable = false, useWaitRequestn = false ) } def apply(bus : AvalonMM) = new AvalonMMSlaveFactory(bus) } Then, let's implement the AvalonMMSlaveFactory itself. .. code-block:: scala class AvalonMMSlaveFactory(bus : AvalonMM) extends BusSlaveFactoryDelayed{ assert(bus.c == AvalonMMSlaveFactory.getAvalonConfig(bus.c.addressWidth,bus.c.dataWidth)) val readAtCmd = Flow(Bits(bus.c.dataWidth bits)) val readAtRsp = readAtCmd.stage() bus.readDataValid := readAtRsp.valid bus.readData := readAtRsp.payload readAtCmd.valid := bus.read readAtCmd.payload := 0 override def build(): Unit = { for(element <- elements) element match { case element : BusSlaveFactoryNonStopWrite => element.that.assignFromBits(bus.writeData(element.bitOffset, element.that.getBitsWidth bits)) case _ => } for((address,jobs) <- elementsPerAddress){ when(bus.address === address){ when(bus.write){ for(element <- jobs) element match{ case element : BusSlaveFactoryWrite => { element.that.assignFromBits(bus.writeData(element.bitOffset, element.that.getBitsWidth bits)) } case element : BusSlaveFactoryOnWrite => element.doThat() case _ => } } when(bus.read){ for(element <- jobs) element match{ case element : BusSlaveFactoryRead => { readAtCmd.payload(element.bitOffset, element.that.getBitsWidth bits) := element.that.asBits } case element : BusSlaveFactoryOnRead => element.doThat() case _ => } } } } } override def busDataWidth: Int = bus.c.dataWidth } Conclusion ---------- That's all, you can check one example that use this ``Apb3SlaveFactory`` to create an Apb3UartCtrl :ref:`there `. If you want to add the support of a new memory bus, it's very simple you just need to implement another variation of the ``BusSlaveFactoryDelayed`` trait. The ``Apb3SlaveFactory`` is probably a good starting point :D