fpga4fun.com - where FPGAs are fun.
Home
Welcome
Information


FPGA projects
Music box
Pong game
R/C servos
Text LCD module
Quadrature decoder
PWM and one-bit DAC
Debouncer
LED displays
Crossing clock domains
External contributions

FPGA interface projects
RS-232
JTAG
I2C
EPP
SPI
CNC steppers

FPGA advanced projects
Graphic LCD panel
Digital oscilloscope
10BASE-T interface
PCI interface
Spoc CPU core

Hands-on
A simple oscilloscope


FPGA introduction
What are FPGAs?
How FPGAs work
FPGA pins
Clocks and global lines
Download cables
Configuration
Learn more

FPGA software
Design software
Pin assignment
Design-entry/HDL
Simulation/HDL
Synthesis and P&R

FPGA electronic
SMD technology
Crystals and oscillators

HDL info
HDL tutorials
Verilog tips
VHDL tips

Quick-start guides
ISE
Quartus

Site
News
FPGA links
HDL tutorials
Forum


10BASE-T FPGA interface part 0 - A recipe to send Ethernet traffic

Here we demonstrate how to send Ethernet traffic directly from an FPGA to a PC.

For this recipe, you'll need:
  1. An FPGA development board, with 2 free IOs and a 20MHz clock.
  2. A PC with an Ethernet card, and the TCP-IP stack installed (if you can browse the net, you're good).
  3. Optionally, a network hub or switch.

1. Connect the FPGA board to the Ethernet network

Here's a view of a typical test setup, using an Ethernet hub or switch.



Using a hub or switch allows the PC to stay connected to your regular network (if you have one) while doing this experiment. But you can also connect the FPGA directly to the PC.
We are using a Pluto board here with an external 20MHz oscillator (we could also use a multiple frequency like 40MHz or 60MHz by dividing the clock internally in the FPGA).

Connect 2 IOs from the FPGA board to an Ethernet cable.

For the pin numbers, get help from this picture:

Note that the polarity usually doesn't matter, because the signal is differential and Ethernet equipment can detect the polarity from the incoming signal.
Also even if that works in practice, we are not compliant to the Ethernet electrical requirements by just connecting the FPGA to a cable (we would need a filter and a transformer). So let's just consider that a "lab" experiment.

2. Get the network info from the PC

Type "ipconfig /all" on the command-line.

Write down your "Physical Address" and your "IP Address".

3. Program the FPGA

Compile the following Verilog HDL code.

Make sure to:

module TENBASET_TxD(clk20, Ethernet_TDp, Ethernet_TDm);
input clk20;
output Ethernet_TDp, Ethernet_TDm;

// "IP source" - put an unused IP - if unsure, see comment below after the source code
parameter IPsource_1 = 192;
parameter IPsource_2 = 168;
parameter IPsource_3 = 0;
parameter IPsource_4 = 44;

// "IP destination" - put the IP of the PC you want to send to
parameter IPdestination_1 = 192;
parameter IPdestination_2 = 168;
parameter IPdestination_3 = 0;
parameter IPdestination_4 = 2;

// "Physical Address" - put the address of the PC you want to send to
parameter PhysicalAddress_1 = 8'h00;
parameter PhysicalAddress_2 = 8'h07;
parameter PhysicalAddress_3 = 8'h95;
parameter PhysicalAddress_4 = 8'h0B;
parameter PhysicalAddress_5 = 8'hFB;
parameter PhysicalAddress_6 = 8'hAF;

//////////////////////////////////////////////////////////////////////
// sends a packet roughly every second
reg [23:0] counter; always @(posedge clk20) counter<=counter+1;
reg StartSending; always @(posedge clk20) StartSending<=&counter;

//////////////////////////////////////////////////////////////////////
// we send a UDP packet, 18 bytes payload

// calculate the IP checksum, big-endian style
parameter IPchecksum1 = 32'h0000C53F + (IPsource_1<<8)+IPsource_2+(IPsource_3<<8)+IPsource_4+
                                                                (IPdestination_1<<8)+IPdestination_2+(IPdestination_3<<8)+(IPdestination_4);
parameter IPchecksum2 =  ((IPchecksum1&32'h0000FFFF)+(IPchecksum1>>16));
parameter IPchecksum3 = ~((IPchecksum2&32'h0000FFFF)+(IPchecksum2>>16));

reg [6:0] rdaddress;
reg [7:0] pkt_data;

always @(posedge clk20)
case(rdaddress)
// Ethernet preamble
  7'h00: pkt_data <= 8'h55;
  7'h01: pkt_data <= 8'h55;
  7'h02: pkt_data <= 8'h55;
  7'h03: pkt_data <= 8'h55;
  7'h04: pkt_data <= 8'h55;
  7'h05: pkt_data <= 8'h55;
  7'h06: pkt_data <= 8'h55;
  7'h07: pkt_data <= 8'hD5;
// Ethernet header
  7'h08: pkt_data <= PhysicalAddress_1;
  7'h09: pkt_data <= PhysicalAddress_2;
  7'h0A: pkt_data <= PhysicalAddress_3;
  7'h0B: pkt_data <= PhysicalAddress_4;
  7'h0C: pkt_data <= PhysicalAddress_5;
  7'h0D: pkt_data <= PhysicalAddress_6;
  7'h0E: pkt_data <= 8'h00;
  7'h0F: pkt_data <= 8'h12;
  7'h10: pkt_data <= 8'h34;
  7'h11: pkt_data <= 8'h56;
  7'h12: pkt_data <= 8'h78;
  7'h13: pkt_data <= 8'h90;
// IP header
  7'h14: pkt_data <= 8'h08;
  7'h15: pkt_data <= 8'h00;
  7'h16: pkt_data <= 8'h45;
  7'h17: pkt_data <= 8'h00;
  7'h18: pkt_data <= 8'h00;
  7'h19: pkt_data <= 8'h2E;
  7'h1A: pkt_data <= 8'h00;
  7'h1B: pkt_data <= 8'h00;
  7'h1C: pkt_data <= 8'h00;
  7'h1D: pkt_data <= 8'h00;
  7'h1E: pkt_data <= 8'h80;
  7'h1F: pkt_data <= 8'h11;
  7'h20: pkt_data <= IPchecksum3[15:8];
  7'h21: pkt_data <= IPchecksum3[ 7:0];
  7'h22: pkt_data <= IPsource_1;
  7'h23: pkt_data <= IPsource_2;
  7'h24: pkt_data <= IPsource_3;
  7'h25: pkt_data <= IPsource_4;
  7'h26: pkt_data <= IPdestination_1;
  7'h27: pkt_data <= IPdestination_2;
  7'h28: pkt_data <= IPdestination_3;
  7'h29: pkt_data <= IPdestination_4;
// UDP header
  7'h2A: pkt_data <= 8'h04;
  7'h2B: pkt_data <= 8'h00;
  7'h2C: pkt_data <= 8'h04;
  7'h2D: pkt_data <= 8'h00;
  7'h2E: pkt_data <= 8'h00;
  7'h2F: pkt_data <= 8'h1A;
  7'h30: pkt_data <= 8'h00;
  7'h31: pkt_data <= 8'h00;
// payload
  7'h32: pkt_data <= 8'h00; // put here the data that you want to send
  7'h33: pkt_data <= 8'h01; // put here the data that you want to send
  7'h34: pkt_data <= 8'h02; // put here the data that you want to send
  7'h35: pkt_data <= 8'h03; // put here the data that you want to send
  7'h36: pkt_data <= 8'h04; // put here the data that you want to send
  7'h37: pkt_data <= 8'h05; // put here the data that you want to send
  7'h38: pkt_data <= 8'h06; // put here the data that you want to send
  7'h39: pkt_data <= 8'h07; // put here the data that you want to send
  7'h3A: pkt_data <= 8'h08; // put here the data that you want to send
  7'h3B: pkt_data <= 8'h09; // put here the data that you want to send
  7'h3C: pkt_data <= 8'h0A; // put here the data that you want to send
  7'h3D: pkt_data <= 8'h0B; // put here the data that you want to send
  7'h3E: pkt_data <= 8'h0C; // put here the data that you want to send
  7'h3F: pkt_data <= 8'h0D; // put here the data that you want to send
  7'h40: pkt_data <= 8'h0E; // put here the data that you want to send
  7'h41: pkt_data <= 8'h0F; // put here the data that you want to send
  7'h42: pkt_data <= 8'h10; // put here the data that you want to send
  7'h43: pkt_data <= 8'h11; // put here the data that you want to send
  default: pkt_data <= 8'h00;
endcase

//////////////////////////////////////////////////////////////////////
// and finally the 10BASE-T's magic
reg [3:0] ShiftCount;
reg SendingPacket;
always @(posedge clk20) if(StartSending) SendingPacket<=1; else if(ShiftCount==14 && rdaddress==7'h48) SendingPacket<=0;
always @(posedge clk20) ShiftCount <= SendingPacket ? ShiftCount+1 : 15;
wire readram = (ShiftCount==15);
always @(posedge clk20) if(ShiftCount==15) rdaddress <= SendingPacket ? rdaddress+1 : 0;
reg [7:0] ShiftData; always @(posedge clk20) if(ShiftCount[0]) ShiftData <= readram ? pkt_data : {1'b0, ShiftData[7:1]};

// generate the CRC32
reg [31:0] CRC;
reg CRCflush; always @(posedge clk20) if(CRCflush) CRCflush <= SendingPacket; else if(readram) CRCflush <= (rdaddress==7'h44);
reg CRCinit; always @(posedge clk20) if(readram) CRCinit <= (rdaddress==7);
wire CRCinput = CRCflush ? 0 : (ShiftData[0] ^ CRC[31]);
always @(posedge clk20) if(ShiftCount[0]) CRC <= CRCinit ? ~0 : ({CRC[30:0],1'b0} ^ ({32{CRCinput}} & 32'h04C11DB7));

// generate the NLP
reg [17:0] LinkPulseCount; always @(posedge clk20) LinkPulseCount <= SendingPacket ? 0 : LinkPulseCount+1;
reg LinkPulse; always @(posedge clk20) LinkPulse <= &LinkPulseCount[17:1];

// TP_IDL, shift-register and manchester encoder
reg SendingPacketData; always @(posedge clk20) SendingPacketData <= SendingPacket;
reg [2:0] idlecount; always @(posedge clk20) if(SendingPacketData) idlecount<=0; else if(~&idlecount) idlecount<=idlecount+1;
wire dataout = CRCflush ? ~CRC[31] : ShiftData[0];
reg qo; always @(posedge clk20) qo <= SendingPacketData ? ~dataout^ShiftCount[0] : 1;
reg qoe; always @(posedge clk20) qoe <= SendingPacketData | LinkPulse | (idlecount<6);
reg Ethernet_TDp; always @(posedge clk20) Ethernet_TDp <= (qoe ? qo : 1'b0);
reg Ethernet_TDm; always @(posedge clk20) Ethernet_TDm <= (qoe ? ~qo : 1'b0);

endmodule

About the "IP source" that you have to choose in the code above, pick something that is compatible with your network, but still unused.
The example network shown above have IPs starting with "192.168.0" (the PC IP is 192.168.0.2 with a mask of 255.255.255.0). So here IPs like 192.168.0.x can be used. To check if an IP is used or not, "ping" it.

4. Incoming packets

Run this UDP receiver software on the PC (source code included).

You get something like this:

Send useful traffic

The code above sends 18 data bytes in each UDP packets. The 18 bytes can come from anywhere, so for example, you can modify the code to send the value of FPGA pins.

Have fun sending UDP packets!



>>> NEXT: 10BASE-T FPGA interface part 1 - How Ethernet works >>>



This page was last updated on February 05 2008.