This example will take the UartCtrl component implemented in the previous example to create a memory mapped UART controller.


The implementation will be based on the APB3 bus with a RX FIFO.

Here is the register mapping table:

Name Type Access Address Description
clockDivider UInt RW 0 Set the UartCtrl clock divider
frame UartCtrlFrameConfig RW 4 Set the dataLength, the parity and the stop bit configuration
writeCmd Bits W 8 Send a write command to UartCtrl
writeBusy Bool R 8 Bit 0 => zero when a new writeCmd can be sent
read Bool / Bits R 12 Bits 7 downto 0 => rx payload
Bit 31 => rx payload valid


For this implementation, the Apb3SlaveFactory tool will be used. It allows you to define a APB3 slave with a nice syntax. You can find the documentation of this tool there.

First, we just need to define the Apb3Config that will be used for the controller. It is defined in a Scala object as a function to be able to get it from everywhere.

object Apb3UartCtrl{
  def getApb3Config = Apb3Config(
    addressWidth = 4,
    dataWidth    = 32

Then we can define a Apb3UartCtrl component which instantiates a UartCtrl and creates the memory mapping logic between it and the APB3 bus:

class Apb3UartCtrl(uartCtrlConfig : UartCtrlGenerics, rxFifoDepth : Int) extends Component{
  val io = new Bundle{
    val bus =  slave(Apb3(Apb3UartCtrl.getApb3Config))
    val uart = master(Uart())

  // Instanciate an simple uart controller
  val uartCtrl = new UartCtrl(uartCtrlConfig)
  io.uart <>

  // Create an instance of the Apb3SlaveFactory that will then be used as a slave factory drived by io.bus
  val busCtrl = Apb3SlaveFactory(io.bus)

  // Ask the busCtrl to create a readable/writable register at the address 0
  // and drive with this register
  busCtrl.driveAndRead(,address = 0)

  // Do the same thing than above but for at the address 4
  busCtrl.driveAndRead(,address = 4)

  // Ask the busCtrl to create a writable Flow[Bits] (valid/payload) at the address 8.
  // Then convert it into a stream and connect it to the by using an register stage (>->)
  busCtrl.createAndDriveFlow(Bits(uartCtrlConfig.dataWidthMax bits),address = 8).toStream >->

  // To avoid losing writes commands between the Flow to Stream transformation just above,
  // make the occupancy of the readable at address 8,address = 8)

  // Take, convert it into a Stream, then connect it to the input of a FIFO of 64 elements
  // Then make the output of the FIFO readable at the address 12 by using a non blocking protocol
  // (Bit 7 downto 0 => read data <br> Bit 31 => read data valid )
                                address = 12, validBitOffset = 31, payloadBitOffset = 0)