In Chapter 2 - Embedded software some basics on the embedded software has been given. A bit more general information is explained using the communicator as example.
As chip designer for the communicator, you are the one that knows how you wired up the Memory-Mapped IO. In this example the mapping below is assumed.
The image above shows the use of 4 registers. Slv_reg0 and slv_reg1 are outgoing registers while slv_reg2 and slv_reg3 are incoming registers. The data_out signal is mapped directly to slv_reg2 and, similarly, the data_in signal is driven by slv_reg0. Connecting up the data-portion is straightforward.
The control-portion of the design sometimes requires a bit more attention. Typically this is done through a command register (CR) (from the processor towards the hardware) and a status register (SR) (from the hardware to the processor). As the design is becoming more complex, a single 32-bit vector could not fit all the relevant signals and more registers can be required. In this example slv_reg1 acts as CR while slv_reg3 act as a SR.
According to the requirements, the I2C_slave component signals valid output data through a short pulse on the data_out_valid signals. As it is very likely that the software will miss this pulse, the example adds a set-reset-flipflop. This way there is an acknowledgement of the fact there is data present.
The result of the SRFF is always stored in slv_reg3.
In this example these defines are present in the xparameters.h.
...
/* Definitions for peripheral COMMUNICATOR_V1_0_0 */
#define XPAR_COMMUNICATOR_V1_0_0_BASEADDR 0x43C10000
#define XPAR_COMMUNICATOR_V1_0_0_HIGHADDR 0x43C1FFFF
...
To make the our lives easier we can extend these defines in our own code.
#define XMASCOMM_BASEADDRESS XPAR_COMMUNICATOR_V1_0_0_BASEADDR
#define XMASCOMM_REG0_ADDRESS (XMASCOMM_BASEADDRESS + 0*4)
#define XMASCOMM_REG1_ADDRESS (XMASCOMM_BASEADDRESS + 1*4)
#define XMASCOMM_REG2_ADDRESS (XMASCOMM_BASEADDRESS + 2*4)
#define XMASCOMM_REG3_ADDRESS (XMASCOMM_BASEADDRESS + 3*4)
With these defines installed, the software developer does not have to bother himself/herself with these increments of four (as long as the processor is a 32-bitter). A typical thing you see in drivers is also the mapping of certain bits. Applying this to the example could look like:
#define XMASCOMM_SR_RXAVAILABLE 0x00000001U
#define XMASCOMM_CR_TXSEND 0x00000001U
#define XMASCOMM_CR_RXCONFIRM 0x00000002U
In Chapter 2 the functions Xil_In32() and Xil_Out32() are explained. However, with a tiny bit of C-magic, the code can become more readable.
#define XMASCOMM_FPGA2EXT (*(volatile u32 *) XMASCOMM_REG0_ADDRESS)
#define XMASCOMM_CR (*(volatile u32 *) XMASCOMM_REG1_ADDRESS)
#define XMASCOMM_EXT2FPGA (*(volatile u32 *) XMASCOMM_REG2_ADDRESS)
#define XMASCOMM_SR (*(volatile u32 *) XMASCOMM_REG3_ADDRESS)
Let’s quickly break this down for those whose C-skills are a bit rusty. The define XMASCOMM_SR make sure that, everywhere in the code this define is substituted by (*(volatile u32 *) XMASCOMM_REG0_ADDRESS). XMASCOMM_REG0_ADDRESS contains the address of address 0. This value is type-cast to an unsigned 32-bit pointer. The keyword volatile states that the content of a variable can also be altered from another source. This is important !! Otherwise the optimisation of the C-compiler might optimise-out certain lines of C-code.
The attentive reader could now spot that (volatile u32 *) XMASCOMM_REG0_ADDRESS is explained. However, in the define is another (* … ) encapsulating all this. This is used to dereference the pointer that was created.
A TL;DR version: with these defines you can write x = XMASCOMM_SR;, or XMASCOMM_CR = 12;.
Earlier the following C-code was shown: Xil_Out32(XMAS_LIGHT_CR, 0x00000001); Note that this lines sets the LSB of the CR to one, but it also reset all other 31 bits to 0 !!
A better way of setting the LSB of the CR would be: XMASCOMM_CR |= 0x1;
Using the full power of the defines that were made, results in the more general: XMASCOMM_CR |= XMASCOMM_CR_TXSEND;
Let us assume the following two C-files:
/*
* xmascomm_driver.h
*/
#include "xparameters.h"
#include "xil_io.h"
#define XMASCOMM_BASEADDRESS XPAR_COMMUNICATOR_V1_0_0_BASEADDR
#define XMASCOMM_REG0_ADDRESS (XMASCOMM_BASEADDRESS + 0*4)
#define XMASCOMM_REG1_ADDRESS (XMASCOMM_BASEADDRESS + 1*4)
#define XMASCOMM_REG2_ADDRESS (XMASCOMM_BASEADDRESS + 2*4)
#define XMASCOMM_REG3_ADDRESS (XMASCOMM_BASEADDRESS + 3*4)
#define XMASCOMM_REG4_ADDRESS (XMASCOMM_BASEADDRESS + 4*4)
#define XMASCOMM_FPGA2EXT (*(volatile u32 *) XMASCOMM_REG0_ADDRESS)
#define XMASCOMM_CR (*(volatile u32 *) XMASCOMM_REG1_ADDRESS)
#define XMASCOMM_EXT2FPGA (*(volatile u32 *) XMASCOMM_REG2_ADDRESS)
#define XMASCOMM_SR (*(volatile u32 *) XMASCOMM_REG3_ADDRESS)
#define XMASCOMM_MSGCOUNTER (*(volatile u32 *) XMASCOMM_REG4_ADDRESS)
#define XMASCOMM_SR_RXAVAILABLE 0x00000001U
#define XMASCOMM_CR_TXSEND 0x00000001U
#define XMASCOMM_CR_RXCONFIRM 0x00000002U
void xmascomm_send_command(uint32_t data);
uint32_t xmascomm_wait_for_command(void);
uint32_t xmascomm_check_received(void);
uint32_t xmascomm_fetch_received(void);
void xmascomm_acknowledge_rx(void);
/*
* xmascomm_driver.c
*/
#include "xmascomm_driver.h"
void xmascomm_send_command(uint32_t data) {
XMASCOMM_FPGA2EXT = data;
XMASCOMM_CR |= XMASCOMM_CR_TXSEND;
XMASCOMM_CR &= ~(XMASCOMM_CR_TXSEND);
}
uint32_t xmascomm_wait_for_command(void) {
uint32_t rx;
while(! xmascomm_check_received());
rx = xmascomm_fetch_received();
xmascomm_acknowledge_rx();
return rx;
}
uint32_t xmascomm_check_received(void) {
return (XMASCOMM_SR & XMASCOMM_SR_RXAVAILABLE);
}
uint32_t xmascomm_fetch_received(void) {
return (XMASCOMM_EXT2FPGA);
}
void xmascomm_acknowledge_rx(void) {
XMASCOMM_CR |= XMASCOMM_CR_RXCONFIRM;
XMASCOMM_CR &= ~(XMASCOMM_CR_RXCONFIRM);
}
Although this is not yet optimal, the code above gives some idea on how a driver is constructed. For the sake of completeness, it is mentioned that these C-files can also be include in the IP core. When you make the IP core available, it then includes the hardware and the accompanying driver.
The API provides the user of the IP core with some nice function calls. However, this API should be documented or the user will not know which nice function calls he/she can use. Don’t forget !!