Memory mapped UART
Introduction
This example will take the UartCtrl
component implemented in the previous 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:
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 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:
case class Apb3UartCtrl(uartCtrlConfig: UartCtrlGenerics, rxFifoDepth: Int) extends Component {
val io = new Bundle {
val bus = slave(Apb3(Apb3UartCtrl.getApb3Config))
val uart = master(Uart())
}
// Instantiate 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 <br> Bit 31 => read data valid )
busCtrl.readStreamNonBlocking(uartCtrl.io.read.queue(rxFifoDepth),
address = 12, validBitOffset = 31, payloadBitOffset = 0)
}
Important