A buffered receiver
This example shows a receiver task that reads data off external I/O pins, and buffers that data. A client task can read the data out of the buffer.

Buffering data in this way decouples the client task from the receiver so that the client is not bound by the real time constraints of the I/O. Note that you do not always have to buffer between input and the client. In real-time streaming applications, it better to directly output to the client and design the client to keep up with the data rate. However, buffering is sometimes needed in the following cases:
- When he input rate is bursty, the max data rate is higher than the client can deal with but the average data rate is not
- When the behaviour of the client is bursty (this can be in the case where the client is dealing with several events on one logical core) so it will consume at irregular intervals.
In both cases, it is important to determine whether overflow of the buffer is possible and, if so, what the application will do in this case.
The receiver receives data on a simple clocked port, 32 bits of data at a time. When it receives data on the port, it will place the data in a FIFO buffer and notify the client. The client can then pull 32-bit words out of this FIFO.
The first thing to define in the program is the interface between the receiver task and its client.
interface receiver_if { // This *notification* function signals to the client when there is // data in the buffer. Since it is a notification function it is // special - instead of being called by the client it is used // by the client to event when data is ready. [[notification]] slave void data_ready(); // This function can be used by the client to pull data out of the fifo. // It clears the notification raised by the data_ready() function. [[clears_notification]] unsigned get_data(); // This function can be called to check if the receiver has any data in // the buffer. // Generally, you do not need to poll the receiver with this function // since you can use the data_ready() notification instead unsigned has_data(); };
The receiver task takes arguments to set it up. It takes the server end of an interface connection using the receiver_if interface - this will be connected to the client. It also takes the buffer size required and the ports and clock block variable for using the external I/O pins. The I/O interface requires a data pin, a clock block and a pin to provide a readyIn signal.
The I/O protocol is a simple protocol directly supported by the XMOS Hardware-Response port blocks. The clock block provides a clock for the data. When the externally driven p_ready_in signal is driven high, it signals the start of the data. After that data is clocked into the p_data port on the rising edge of the clock. Since the data port is a buffered port, the data is deserialize into 32-bit chunks, so the program will receive a 32-bit word at a time from the port.

This is the prototype of the receiver task:
void receiver(server interface receiver_if i, static const unsigned bufsize, in buffered port:32 p_data, clock clk, in port p_ready_in) {
Within definition of the task the first thing required is to define the local state. The buffer array provides the memory space for the FIFO. To implement a FIFO, the fifo_first_elem and fifo_last_elem hold the indices to the first and last element in the FIFO. All the array elements between these indices hold the data (the FIFO may wrap around the end of the array back to the beginning).
unsigned buffer[bufsize]; unsigned fifo_first_elem = 0, fifo_last_elem = 0;
The initial part of the task sets up the port block. The protocol on the pins is one supported by the Hardware-Response port blocks, so you can configure the port using a library function to set it to Strobed Slave mode (i.e. data input is governed by the readyIn signal). The port configuration functions are found in xs1.h.
configure_in_port_strobed_slave(p_data, p_ready_in, clk);
The main body of the task is a loop with a select inside. This select will either react to the port providing input, or to a request from the client over the interface connection:
while (1) { select { case p :> unsigned data: // handle port input .. case i.get_data() -> unsigned result: // request from client to get data, pop element off fifo // and place it in the return value 'result' .. case i.has_data() -> unsigned result: // request form client to determine if there is data in buffer // put 1 or 0 in the retrun value 'result' .. } }
When the port signals that it has data, the tasks reads it into the data variable.
case p_data :> unsigned data:
To handle the port input the program works out where it needs to be added to the FIFO by adding one to the last element index (and wrapping round in the buffer if needed).
unsigned new_last_elem = fifo_last_elem + 1; if (new_last_elem == bufsize) new_last_elem = 0;
If the last element index wraps all the way around to the beginning of the FIFO, there is buffer overflow. In this case the task just drops the data but different overflow handling code could be added here.
if (new_last_elem == fifo_first_elem) { //handle buffer overflow break; }
If there is room in the buffer, the data is inserted into the array and the last element index is updated.
buffer[fifo_last_elem] = data; fifo_last_elem = new_last_elem;
Finally, the server calls the data_ready notification. This signals to the client that there is some data in the buffer.
i.data_ready();
The following case responds to a client request for data. The return value back to the client is declared as a variable result. The body of this case can set this variable to pass a return value back to the client.
case i.get_data() -> unsigned result:
The data to extract from the FIFO is in the array at the position marked by the first element index variable. However, if this is the same as the last element then the buffer is empty. In this case the task returns the value 0 to the client, but different buffer underflow handling code could be added here.
if (fifo_first_elem == fifo_last_elem) { // handle buffer underflow result = 0; break; }
To pop an element from the FIFO, the result variable needs to be set and the first element index variable needs to be incremented (possibly wrapping around the buffer array).
result = buffer[fifo_first_elem]; fifo_first_elem++; if (fifo_first_elem == bufsize) fifo_first_elem = 0;
Finally, if the FIFO is not empty, the task re-notifies the client that data is available.
if (fifo_first_elem != fifo_last_elem) i.data_ready();
The final request the receiver task handles is from the client requesting whether data is available. This case is quite simple, just needing to return the current state based on the index variables for the FIFO.
case i.has_data() -> unsigned result: // request form client to determine if there is data in buffer // put 1 or 0 in the retrun value 'result' result = (fifo_first_elem != fifo_last_elem); break;
Tasks can attach to this receiver task and access data via the inteface connection. For example, the following consumer task takes a client end of interface connection to the receiver:
void consumer(client interface receiver_if i) { // This consumer task can wait for the data from the receiver task while (1) { select { case i.data_ready(): unsigned x = i.get_data(); // handle the data here break; } } }
The tasks can be run in parallel using a par statement and connected together via an interface:
int main() { interface receiver_if i; par { consumer(i); receiver(i, 1024, p_data, clk, p_ready_in); } return 0; }
Full example
#include <xs1.h> interface receiver_if { // This *notification* function signals to the client when there is // data in the buffer. Since it is a notification function it is // special - instead of being called by the client it is used // by the client to event when data is ready. [[notification]] slave void data_ready(); // This function can be used by the client to pull data out of the fifo. // It clears the notification raised by the data_ready() function. [[clears_notification]] unsigned get_data(); // This function can be called to check if the receiver has any data in // the buffer. // Generally, you do not need to poll the receiver with this function // since you can use the data_ready() notification instead unsigned has_data(); }; void receiver(server interface receiver_if i, static const unsigned bufsize, in buffered port:32 p_data, clock clk, in port p_ready_in) { unsigned buffer[bufsize]; unsigned fifo_first_elem = 0, fifo_last_elem = 0; configure_in_port_strobed_slave(p_data, p_ready_in, clk); while (1) { select { case p_data :> unsigned data: unsigned new_last_elem = fifo_last_elem + 1; if (new_last_elem == bufsize) new_last_elem = 0; if (new_last_elem == fifo_first_elem) { //handle buffer overflow break; } buffer[fifo_last_elem] = data; fifo_last_elem = new_last_elem; i.data_ready(); break; case i.get_data() -> unsigned result: if (fifo_first_elem == fifo_last_elem) { // handle buffer underflow result = 0; break; } result = buffer[fifo_first_elem]; fifo_first_elem++; if (fifo_first_elem == bufsize) fifo_first_elem = 0; if (fifo_first_elem != fifo_last_elem) i.data_ready(); break; case i.has_data() -> unsigned result: // request form client to determine if there is data in buffer // put 1 or 0 in the retrun value 'result' result = (fifo_first_elem != fifo_last_elem); break; } } } void consumer(client interface receiver_if i) { // This consumer task can wait for the data from the receiver task while (1) { select { case i.data_ready(): unsigned x = i.get_data(); // handle the data here break; } } } in buffered port:32 p_data = XS1_PORT_1A; clock clk = XS1_CLKBLK_1; in port p_ready_in = XS1_PORT_1B; int main() { interface receiver_if i; par { consumer(i); receiver(i, 1024, p_data, clk, p_ready_in); } return 0; }