Skip to content

Simple UDP Example

Julian Kemmerer edited this page Jul 31, 2018 · 21 revisions

Here's an FPGA in a drawer? Is that enough?

Or "Welcome to The Lab!"

THE LAB

I am working on a basic networking (ethernet/ip/udp) implementation.

The hardware I am using is a Digilent Arty Artix-35T board. https://store.digilentinc.com/arty-a7-artix-7-fpga-development-board-for-makers-and-hobbyists/

It uses a 32 bit AXIS (Advanced eXtensible Interface Streaming 4) interface from a Xilinx Tri-Mode Ethernet MAC. You might need to obtain an additional free license for the TEMAC - yeah, super annoying. My license might run out in a few weeks or so, so fuck me right.

Don't put too much weight behind the name 'AXIS' - its really simple:

Say I want to send you 7 bytes of data. I can send it in one chunk of 7 bytes, chunks of 2 bytes, or maybe I'll use 32b AXIS to do it in chunks of 4 bytes.

uint32_t data;

If you think of functions as being run in a loop then AXIS is how you receive a byte array over time. Sometimes in this loop you may have a valid set of 4 bytes to receive and sometimes not.

uint32_t data;
uint1_t valid; // The data is valid right now - do something with it

How will I know when I have received all the bytes? Tell me when I get the last chunk.

uint32_t data;
uint1_t valid; // The data is valid right now - do something with it
uint1_t last; // Last 4 bytes flag

But wait - two chunks of 4 bytes is 8 bytes total not 7! Well, only keep 3 bytes of the last chunk.

uint32_t data;
uint1_t valid; // The data is valid right now - do something with it
uint1_t last; // Last 4 bytes flag
uint4_t keep; // A bit for each byte in data (ex. keep = 7 (binary 0111) means keep only the first 3 bytes of data)

Ok so sending 7 bytes would look like this over time:

// 0+ iterations with valid=0
[... valid = 0; ...]
// The first chunk of 4 bytes
[data = (byte3, byte2, byte1, byte0); valid = 1; last = 0; keep=15]
// 0+ iterations with valid=0
[... valid = 0; ...]
// The last chunk of 3 bytes
[data = (<invalid>, byte6, byte5, byte4); valid = 1; last = 1; keep=7]

You can use AXIS to send any length byte array.

typedef struct axis32_t
{
	uint32_t data;
	uint1_t valid;
	uint1_t last;
	uint4_t keep;
} axis32_t;

So receiving a packet can look like:

... receive(axis32_t payload_axis)
{

}

And transmitting a packet can look like:

axis32_t transmit(...)
{
  axis32_t payload_axis;
  ...
  return payload_axis;
}

So what does this example project do?

Right now it receives two numbers in a UDP packet and sends another UDP packet with their sum. You should be able to replace 'two numbers' with anything and 'their sum' with some other work and make something at least mildly useful.

Here is the source code (main at bottom):

` // These are pipeline C modules I have already written #include "eth_32.c" #include "ip_32.c" #include "udp_32.c"

// I'm going to define some structs so swapping out // what is in the udp payload is a less annoying. // I should code generate much of this.

// The struct we intended to receive via UDP #define RX_SIZE 16 // No sizeof() right now #define RX_UDP_LENGTH 24 // 16 + 8 byte header #define RX_IP_LENGTH 44 // 24 + 20 byte header // (^ did not nest definitions since cant have // multiple add operations on a single line right now, dumb I know) typedef struct rx_data_t { uint64_t x0; uint64_t x1; } rx_data_t;

// Add a valid signal to the RX data so we know when its ready typedef struct udp_rx_payload_t { rx_data_t data; uint1_t valid; } udp_rx_payload_t;

// The struct we intended to send via UDP #define TX_SIZE 8 // No sizeof() right now #define TX_UDP_LENGTH 16 // 8 + 8 byte header #define TX_IP_LENGTH 36 // 16 + 20 byte header // (^ did not nest definitions since cant have // multiple add operations on a single line right now, dumb I know) typedef struct tx_data_t { uint64_t x0; } tx_data_t;

// Add a valid signal to the data so we know when its ready typedef struct udp_tx_payload_t { tx_data_t data; uint1_t valid; } udp_tx_payload_t;

// Logic to get data from AXIS UDP payload 4 bytes at a time // (TOTALY could benefit from code generation) typedef enum deserialize_state_t { X0_MSB, X0_LSB, X1_MSB, X1_LSB } deserialize_state_t; deserialize_state_t deserialize_state; udp_rx_payload_t deserialize_rv; udp_rx_payload_t deserialize(axis32_t axis) { // Dont have all the data to return yet deserialize_rv.valid = 0;

// Likely need to swap endianess
axis.data = bswap_32(axis.data);
axis.keep = uint4_0_3(axis.keep);

if (axis.valid == 1)
{
	if(deserialize_state==X0_MSB)
	{
		// x0[63:32] = axis.data
		deserialize_rv.data.x0 = uint64_uint32_32(deserialize_rv.data.x0, axis.data);
		deserialize_state = X0_LSB;
	}
	else if(deserialize_state==X0_LSB)
	{
		// x0[31:0] = axis.data
		deserialize_rv.data.x0 = uint64_uint32_0(deserialize_rv.data.x0, axis.data);
		deserialize_state = X1_MSB;
	}
	else if(deserialize_state==X1_MSB)
	{
		// x1[63:32] = axis.data
		deserialize_rv.data.x1 = uint64_uint32_32(deserialize_rv.data.x1, axis.data);
		deserialize_state = X1_LSB;
	}
	else // X1_LSB
	{
		// x1[31:0] = axis.data
		deserialize_rv.data.x1 = uint64_uint32_0(deserialize_rv.data.x1, axis.data);
		// Got all the data  now
		deserialize_rv.valid = 1;
		// Back to start
		deserialize_state = X0_MSB;
	}
	
	// Always reset if last
	if(axis.last == 1)
	{
		deserialize_state = X0_MSB;
	}
}

return deserialize_rv;

}

// Logic to put data into AXIS 4 bytes at a time for UDP payload // (TOTALY could benefit from code generation) typedef enum serialize_state_t { X0_MSB, X0_LSB } serialize_state_t; serialize_state_t serialize_state; tx_data_t serialize_data; axis32_t serialize_rv; axis32_t serialize(udp_tx_payload_t payload) { // No output data yet serialize_rv.valid = 0; serialize_rv.last = 0; serialize_rv.keep = 15;

if(serialize_state==X0_MSB)
{
	// Wait for valid to get started
	if(payload.valid)
	{
		// Save copy of input data
		serialize_data = payload.data;
		
		// data = x0[63:32]
		serialize_rv.data = uint64_63_32(serialize_data.x0);
		serialize_rv.valid = 1;
		serialize_state = X0_LSB;
	}
}
else //X0_LSB
{
	// data = x0[31:0]
	serialize_rv.data = uint64_31_0(serialize_data.x0);
	serialize_rv.valid = 1;
	serialize_rv.last = 1;
	serialize_state = X0_MSB;
}

// Likely need to swap endianess
serialize_rv.data = bswap_32(serialize_rv.data);
serialize_rv.keep = uint4_0_3(serialize_rv.keep);

return serialize_rv;

}

// Receive UDP payload data from the ethernet port udp_rx_payload_t receive(axis32_t mac_axis_rx) { // Receive the ETH frame eth32_frame_t eth_rx; eth_rx = eth_32_rx(mac_axis_rx);

// Receive IP packet
ip32_frame_t ip_rx;
ip_rx = ip_32_rx(eth_rx.payload);

// Receive UDP packet
udp32_frame_t udp_rx;
udp_rx = udp_32_rx(ip_rx.payload);

// Deserialize the rx data from the UDP AXIS
udp_rx_payload_t rv;
rv = deserialize(udp_rx.payload);
return rv;

}

// Transmit UDP payload data out the ethernet port axis32_t transmit(udp_tx_payload_t payload) { // Serialize the tx data into AXIS for UDP payload axis32_t tx_data; tx_data = serialize(payload);

// Send the data inside a udp packet
udp32_frame_t udp_tx;
udp_tx.header.src_port = 1234;
udp_tx.header.dst_port = 5678;
udp_tx.header.length = TX_UDP_LENGTH;
udp_tx.payload = tx_data;

// Form Tx IP packet with UDP packet as payload
ip32_frame_t ip_tx;
// MUST fully initialize local variables for now
ip_tx.header.ver = 4;
ip_tx.header.ihl = 0;
ip_tx.header.dscp = 0;
ip_tx.header.ecn = 0;
ip_tx.header.total_length = TX_IP_LENGTH;
ip_tx.header.iden = 0;
ip_tx.header.flags = 0;
ip_tx.header.frag = 0;
ip_tx.header.ttl = 0;
ip_tx.header.protocol = 17; // UDP
ip_tx.header.checksum = 0; // Calculated for you
ip_tx.header.src_ip = 16909060; //0x01020304
ip_tx.header.dst_ip = 84281096; //0x05060708
ip_tx.payload = udp_32_tx(udp_tx);

// Form Tx ETH frame with ip packet as payload
eth32_frame_t eth_tx;
eth_tx.header.src_mac = uint24_uint24(2748, 3807); // 0xABCEDF
eth_tx.header.dst_mac = uint24_uint24(291, 1110); // 0x123456
eth_tx.header.ethertype = 2048; // IP
eth_tx.payload = ip_32_tx(ip_tx); // Payload is IP tx packet

// Transmit the ETH frame
axis32_t mac_axis_tx;
mac_axis_tx = eth_32_tx(eth_tx);

return mac_axis_tx;

}

// Do something with RX data to form TX data tx_data_t foo(rx_data_t rx_data) { tx_data_t tx_data; // Like multiply two numbers! Rock on! tx_data.x0 = rx_data.x0 * rx_data.x1; return tx_data; }

// Input: AXIS from TEMAC // Output: AXIS to TEMAC axis32_t main(axis32_t mac_axis_rx) { // Receive data from the ethernet port udp_rx_payload_t rx_payload; rx_payload = receive(mac_axis_rx);

// Do some work to form transmit data
udp_tx_payload_t tx_payload;
tx_payload.data = foo(rx_payload.data);
tx_payload.valid = rx_payload.valid;

// Transmit data out the ethernet port
axis32_t mac_axis_tx;
mac_axis_tx = transmit(tx_payload);
return mac_axis_tx;

}

`

Clone this wiki locally