.. role:: raw-html-m2r(raw) :format: html .. _memory_mapped_uart: Memory mapped UART ================== Introduction ------------ This example will take the ``UartCtrl`` component implemented in the previous :ref:`example ` to create a memory mapped UART controller. Specification ------------- The implementation will be based on the APB3 bus with a RX FIFO. Here is the register mapping table: .. list-table:: :header-rows: 1 :widths: 1 1 1 1 5 * - 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 Implementation -------------- 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 :ref:`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. .. code-block:: scala 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: .. image:: /asset/picture/memory_mapped_uart.svg :align: center .. code-block:: scala 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 <> uartCtrl.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 uartCtrl.io.config.clockDivider with this register busCtrl.driveAndRead(uartCtrl.io.config.clockDivider,address = 0) // Do the same thing than above but for uartCtrl.io.config.frame at the address 4 busCtrl.driveAndRead(uartCtrl.io.config.frame,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 uartCtrl.io.write by using an register stage (>->) busCtrl.createAndDriveFlow(Bits(uartCtrlConfig.dataWidthMax bits),address = 8).toStream >-> uartCtrl.io.write // To avoid losing writes commands between the Flow to Stream transformation just above, // make the occupancy of the uartCtrl.io.write readable at address 8 busCtrl.read(uartCtrl.io.write.valid,address = 8) // Take uartCtrl.io.read, 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
Bit 31 => read data valid ) busCtrl.readStreamNonBlocking(uartCtrl.io.read.toStream.queue(rxFifoDepth), address = 12, validBitOffset = 31, payloadBitOffset = 0) } .. important:: | Yes, that's all it takes. It's also synthesizable. | The Apb3SlaveFactory tool is not something hard-coded into the SpinalHDL compiler. It's something implemented with SpinalHDL regular hardware description syntax.