Skip to content

Simple UDP Example

Julian Kemmerer edited this page Jun 9, 2020 · 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.

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.

Right now the most annoying part of changing the code is rewriting the de/serialization functions.

The PipelineC source code for this is here (main function at bottom)

The Vivado files are checked in as a zip file.

Want to give it a try? Run the tool.

Once you have the output from the tool (a bitstream) you can load the FPGA and try some of the minimal test code.

HW.py has some simple functions that send raw Ethernet frames. This doesn't use higher level sockets because there is no logic on the FPGA side to do ARPs and stuff. Run HW.py and Wireshark at the same time to see the sent packet and response back. PS: Wireshark shows extra bytes on the Ethernet frame from the FPGA, but thats a problem for future me.

Clone this wiki locally