新聞中心

FPGA:Ethernet接口

作者: 時間:2024-01-10 來源:EEPW編譯 收藏

以太網(wǎng)全雙工協(xié)議易于在中實現(xiàn)。 這里的目標是將連接到10BASE-T連接。

本文引用地址:http://m.butianyuan.cn/article/202401/454644.htm

以太網(wǎng)數(shù)據(jù)包:發(fā)送和接收

10BASE-T 接口 0 - 發(fā)送以太網(wǎng)流量的方案

在這里,我們演示了如何將以太網(wǎng)流量直接從FPGA發(fā)送到PC。

對于此食譜,您需要:
  1. FPGA 開發(fā)板,具有 2 個空閑 IO 和一個 20MHz 時鐘。

  2. 一臺帶有以太網(wǎng)卡并安裝了 TCP-IP 堆棧的 PC(如果你能瀏覽 Internet,你就很好)。

  3. (可選)網(wǎng)絡(luò)集線器或交換機。

1. 將FPGA板連接到以太網(wǎng)

以下是使用以太網(wǎng)集線器或交換機的典型測試設(shè)置視圖。



使用集線器或交換機可讓電腦在執(zhí)行此實驗時保持與常規(guī)網(wǎng)絡(luò)(如果有)的連接。 但您也可以將FPGA直接連接到PC。
我們在這里使用帶有外部 20MHz 振蕩器的 Pluto 板。

將FPGA板上的兩個IO連接到以太網(wǎng)電纜。

  • 如果電纜的另一端連接到集線器或交換機(如上圖所示),請使用以太網(wǎng)電纜的引腳 1 和 2。

  • 如果電纜的另一端直接連接到 PC,請使用引腳 3 和 6。

有關(guān)引腳編號,請從這張圖片中獲取幫助:

請注意,極性通常無關(guān)緊要,因為信號是差分的,以太網(wǎng)設(shè)備可以從輸入信號中檢測極性。
此外,即使這在實踐中有效,我們也無法通過僅將FPGA連接到電纜(我們需要濾波器和變壓器)來滿足以太網(wǎng)電氣要求。 因此,讓我們將其視為“實驗室”實驗。

2.從PC獲取網(wǎng)絡(luò)信息

在命令行中鍵入“ipconfig /all”。

寫下您的“物理地址”和“IP 地址”。

3. 對FPGA進行編程

編譯以下 Verilog HDL 代碼。

確保:

  • 更新代碼中的數(shù)據(jù)值(“IP 源”、“IP 目標”和“物理地址”)。

  • 為您的電路板分配正確的引腳(只使用了 3 個引腳!

module TENBASET_TxD(clk20, Ethernet_TDp, Ethernet_TDm);

// a 20MHz clock (this code won't work with a different frequency)
input clk20;

// the two differential 10BASE-T outputs
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. 傳入數(shù)據(jù)包

在 PC 上運行此 UDP 接收器軟件(包括源代碼)。

你會得到這樣的東西:

發(fā)送有用的流量

上面的代碼在每個 UDP 數(shù)據(jù)包中發(fā)送 18 個數(shù)據(jù)字節(jié)。 這 18 個字節(jié)可以來自任何地方,因此,例如,您可以修改代碼以發(fā)送 FPGA 引腳的值。

玩得開心發(fā)送UDP數(shù)據(jù)包!

10BASE-T FPGA 接口 1 - 以太網(wǎng)的工作原理

這是對以太網(wǎng)技術(shù)的簡要介紹。
如果你是新手,你可以從Charles Spurgeon的以太網(wǎng)網(wǎng)站上獲得更多細節(jié)。

此頁面上的評論同樣適用于 10BASE-T 和 100BASE-T(后者快 10 倍)。

IEEE 802.3協(xié)議

IEEE 10.100 標準中描述了 802/3BASE-T 接口。
該標準可在 IEEE 802.3 標準協(xié)會頁面上免費獲得。 如果您想要副本,請選擇最新標準“IEEE 802.3-2002”。 相關(guān)章節(jié)包括第3章(MAC幀結(jié)構(gòu))和第14章(10BASE-T)。

RJ-45 連接器

10/100BASE-T 使用 RJ-45 8 針連接器。

它們看起來像這樣:


在 8 個引腳中,只使用了 4 個:
  1. TD+(傳輸+)

  2. TD-(傳輸-)

  3. RD+(接收+)



  4. RD-(接收-)



一對引腳用于發(fā)送(TD+/TD-),一對用于接收(RD+/RD-)。

注意:此引腳排列適用于從計算機輸出的 RJ-45。 集線器或交換機的引腳排列是反轉(zhuǎn)的(TD在引腳3和6上,RD在引腳1和2上)。

差分信號

每對都使用差分電信號(差分信號對外部干擾的免疫力更強)。 此外,每對線都絞合在電纜中,這進一步提高了抗擾度。

數(shù)據(jù)包編碼

要在以太網(wǎng)上發(fā)送數(shù)據(jù),您不能就這樣發(fā)送;您必須將其封裝到以太網(wǎng)數(shù)據(jù)包中。 數(shù)據(jù)包包含一個標頭,其中包含數(shù)據(jù)到達目的地所需的信息。

以太網(wǎng)基于共享介質(zhì)的理念 - 如果一個站點發(fā)送數(shù)據(jù)包,線路上的每個人都會收到它。 每個以太網(wǎng)卡都有一個唯一的ID(“MAC地址”),因此每個卡都可以自動丟棄發(fā)往另一個站點的數(shù)據(jù)包。 MAC 地址長度為 6 個字節(jié)(48 位),足以讓地球上的每個以太網(wǎng)卡都有一個唯一的編號!

半雙工與全雙工

以太網(wǎng)最初是使用真正的共享介質(zhì)(連接到多個站點的單根同軸電纜)構(gòu)建的。 傳輸和接收都使用同一根電纜。 所以當然,你不能同時發(fā)送和接收。通信是半雙工的。

半雙工使用稱為“CSMA/CD”(帶沖突檢測的載波偵聽多址)的協(xié)議: 在半雙工中:
  • 在發(fā)射之前,每個電臺都必須監(jiān)聽以確保線路是空閑的(“載波檢測”)。

  • 盡管如此,兩個電臺仍有可能同時傳輸。 因此,必須存在一個復(fù)雜的協(xié)議來中止(“沖突檢測”)并在以后恢復(fù)傳輸。

10/100BASE-T 使用“非屏蔽雙絞線”電纜(“UTP”電纜)代替同軸電纜。 UTP 電纜允許全雙工,因為它們包含用于傳輸和接收的單獨線對。

全雙工通信更好:
  • 您可以獲得兩倍的帶寬。

  • 每個站點都有一個專用的介質(zhì),可以隨時開始傳輸,而不會出現(xiàn)并發(fā)癥(CSMA/CD 不再適用)。

所以現(xiàn)在的問題是,如何讓您的 10/100BASE-T 網(wǎng)絡(luò)在全雙工模式下工作?

集線器和交換機

10/100BASE-T是一個“星形拓撲”網(wǎng)絡(luò)。 它需要使用集中器設(shè)備將多臺計算機連接在一起。

有兩種類型的集中器可用:“集線器”或“交換機”。

  • 集線器是一種簡單的電子設(shè)備,它在每個鏈路之間提供電氣隔離,但仍然將它們“邏輯”地連接在一起。 這迫使通信是半雙工的。 更糟糕的是:在任何給定時間,只有一臺計算機可以說話。 因此,網(wǎng)絡(luò)帶寬在所有計算機之間共享。

  • 交換機(或“交換機集線器”)是一種更復(fù)雜的電子設(shè)備,它在電氣和邏輯上隔離每個計算機鏈路。 它通過在重新傳輸之前在內(nèi)部存儲每個傳輸?shù)臄?shù)據(jù)來做到這一點。 因此,每臺計算機都可以隨時說話:這允許全雙工通信。 更好的是:每臺計算機都具有完整的鏈路帶寬。 而且由于介質(zhì)不共享,因此每個站點僅接收發(fā)給自己的數(shù)據(jù)包(隱私性得到改善)。

總之,對于 10BASE-T 網(wǎng)絡(luò)(將數(shù)字乘以 10,表示 100BASE-T):
  • 集線器:每個鏈路上的半雙工,所有計算機之間共享 10Mbps。慢。。。

  • 交換:每個鏈路上的全雙工,每臺計算機專用 20Mbps(單向 10Mbps)???!

開關(guān)可能會花費更多,但這是非常值得的!

此項目建議使用全雙工鏈路,因為它不實現(xiàn) CSMA/CD。 使用半雙工鏈路仍然有效,但代價是潛在的數(shù)據(jù)包丟失(尤其是在使用集線器共享介質(zhì)時)。

10BASE-T FPGA 接口 2 - 基于以太網(wǎng)的 IP/UDP

讓我們專注于以太網(wǎng)/IP/UDP 數(shù)據(jù)包。
這些數(shù)據(jù)包易于生產(chǎn);但功能強大,它們可以在互聯(lián)網(wǎng)上傳播(并被發(fā)送到世界任何地方!

下面是一個示例:

55 55 55 55 55 55 55 D5 00 10 A4 7B EA 80 00 12 34 56 78 90 08 00 45 00 00 2E B3 FE 00 00 80 11 05 40 C0 A8 00 2C C0 A8 00 04 04 00 04 00 00 1A 2D E8 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 B3 31 88 1B

繼續(xù)閱讀,它在下面解碼。

協(xié)議棧

數(shù)據(jù)包使用三種不同的協(xié)議:以太網(wǎng)、IP 和 UDP(從低級到高級協(xié)議)。
每個協(xié)議都添加了自己的功能,并嵌入到較低級別的協(xié)議中。
  • UDP 部分包含要發(fā)送的數(shù)據(jù)(“有效負載”)。

  • IP 部分允許數(shù)據(jù)包通過 Internet 路由。

  • 以太網(wǎng)部分允許數(shù)據(jù)包在以太網(wǎng)上本地發(fā)送。

UDP 嵌入到 IP 中,而 IP 本身也嵌入到以太網(wǎng)中。 這是如何一起使用網(wǎng)絡(luò)協(xié)議的一個很好的例子。
上面顯示的數(shù)據(jù)包在這里解碼:
  • 以太網(wǎng)前導(dǎo)碼/SFD(同步器):55 55 55 55 55 55 D55

  • 以太網(wǎng)目標地址:00 10 A4 7B EA 80

  • 以太網(wǎng)源地址:00 12 34 56 78 90

  • 以太網(wǎng)類型: 08 00 (=IP)

  • IP 標頭:45 00 00 2E B3 FE 00 00 80

  • IP 協(xié)議:11 (=UDP)

  • IP 校驗和: 05 40

  • IP 源 (192.168.0.44): C0 A8 00 2C

  • IP 目標 (192.168.0.4): C0 A8 00 04

  • UPD 源端口 (1024):04 00

  • UPD 目標端口 (1024):04 00

  • UDP 有效載荷長度 (18): 00 1A

  • UPD 校驗和: 2D E8

  • UDP 有效負載(18 字節(jié)):00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11

  • 以太網(wǎng)校驗和: B3 31 88 1B

您可以注意到使用的 IP:從“192.168.0.44”到“192.168.0.4”。 這些是本地IP,這個數(shù)據(jù)包不會走得太遠......

創(chuàng)建自己的數(shù)據(jù)包

這是一個簡單的軟件,允許創(chuàng)建自定義以太網(wǎng)/IP/UDP 數(shù)據(jù)包。
在此處下載。源代碼包括在內(nèi)(目前是 Delphi 6 - 在 Delphi XE2 中要小心,因為一個用戶報告了錯誤生成的數(shù)據(jù)包......



每次按下“發(fā)送”按鈕時,軟件都會構(gòu)建一個數(shù)據(jù)包,顯示它并將其發(fā)送到串行端口(這將在本項目的第 3 部分后面有用)。

10BASE-T FPGA 接口 3 - 發(fā)送數(shù)據(jù)包

現(xiàn)在我們知道需要傳輸什么,讓我們開始吧。
第 2 部分中的軟件將原始以太網(wǎng)數(shù)據(jù)包數(shù)據(jù)發(fā)送到 PC 的串行端口。
我們的FPGA板只需要從串行端口接收它(慢速...),然后通過10BASE-T線對發(fā)送(快!)。

讓 PC 計算原始數(shù)據(jù)包數(shù)據(jù)使我們能夠非常輕松地對所有數(shù)據(jù)包參數(shù)進行實驗。 這很好,但最終FPGA應(yīng)該在獨立模式下工作,并自行烹飪/發(fā)送數(shù)據(jù)包。

曼徹斯特編碼

10BASE-T 網(wǎng)絡(luò)以 10Mbps(每秒 10 兆比特)的速度工作。

但 10BASE-T 要求這些位是“曼徹斯特編碼”的。這需要將位數(shù)增加一倍!
  • 邏輯“0”作為高位發(fā)送,后跟低位。

  • 邏輯“1”作為低位發(fā)送,后跟高位。

因此,我們的 10Mbps 網(wǎng)絡(luò)實際上需要 20Mbps 的比特流......

FPGA 時鐘

簡單性決定了我們使用10MHz時鐘,并通過將時鐘與輸出數(shù)據(jù)位“xXing”來生成20Mbps曼徹斯特比特流。

那可能行得通。但我們正在把握時間。在FPGA設(shè)計中不建議這樣做。 在這種特殊情況下,這會降低20Mbps的信號質(zhì)量,因為每個位轉(zhuǎn)換都會出現(xiàn)毛刺(數(shù)據(jù)輸出永遠不會與時鐘完全重合)。

因此,讓我們使用20MHz時鐘。

保持鏈接處于活動狀態(tài)

即使 10BASE-T 電纜上沒有發(fā)送數(shù)據(jù)包,也必須定期發(fā)送脈沖(稱為“正常鏈路脈沖”或“NLP”)。 它用于保持連接“活動”。每 16 毫秒左右需要發(fā)送一次脈沖。

NLP也可以在稱為“自動協(xié)商”的過程中被“快速鏈接脈沖”(FLP)突發(fā)所取代。 FLP 攜帶有關(guān)發(fā)送方功能的信息,以便電纜兩端的硬件可以協(xié)商鏈路參數(shù),例如速度和半雙工/全雙工狀態(tài)。

HDL設(shè)計

假設(shè)要發(fā)送的數(shù)據(jù)包在FPGA的RAM中可用。
ram512 ram(
  .data(ram_input), .wraddress(wraddress), .clock(clk),
  .q(ram_output), .rdaddress(rdaddress)
);

我們假設(shè)我們也知道數(shù)據(jù)包的長度。我們將讀取數(shù)據(jù)包數(shù)據(jù)并將其發(fā)送到以太網(wǎng)上。
首先,我們需要一個開始信號。
wire StartSending; // pulse indicating when to start sending the packet

reg SendingPacket;
always @(posedge clk) if(StartSending) SendingPacket<=1; else if(DoneSending) SendingPacket<=0;

在 20MHz 時,我們要求每個位有 2 個時鐘周期。一個 16 位字節(jié)需要 8 個時鐘。
reg [3:0] ShiftCount; // count from 0 to 15, as 16 clocks are required per 8-bits bytes
always @(posedge clk) if(SendingPacket) ShiftCount <= ShiftCount+1; else ShiftCount <= 0;

當我們到達字節(jié)的最后一位時,我們從RAM中讀取一個新字節(jié)并將其放入移位寄存器中。 移位寄存器每隔一個時鐘就會給我們一個新位。
wire readram = (ShiftCount==15); // time to read a new byte from the RAM?
reg [7:0] ShiftData;
always @(posedge clk) if(ShiftCount[0]) ShiftData <= readram ? ram_output : {1'b0, ShiftData[7:1]};

always @(posedge clk) if(readram) rdaddress <= SendingPacket ? rdaddress+1 : 0;

每個數(shù)據(jù)包都需要以“TP_IDL”結(jié)尾(大約 3 位時間的正脈沖,然后是空閑期)。
reg [2:0] idlecount; // enough bits to count 3 bit-times

always @(posedge clk) if(SendingPacket) idlecount<=0; 

else if(~&idlecount) idlecount<=idlecount+1;


最后,曼徹斯特編碼器發(fā)送位,然后發(fā)送TP_IDL。
reg qo; always @(posedge clk) qo <= SendingPacket ? ~ShiftData[0]^ShiftCount[0] : 1;
reg qoe; always @(posedge clk) qoe <= SendingPacket | (idlecount<6);
reg q1; always @(posedge clk) q1 <= (qoe ? qo : 1'b0);
reg q2; always @(posedge clk) q2 <= (qoe ? ~qo : 1'b0);

此處顯示的代碼略有簡化。 例如,缺少保持鏈接活動所需的 NLP 脈沖。 完整的代碼可以在這里找到。

此代碼適用于集線器和交換機。 由于我們只使用了 NLP,因此鏈路是半雙工的,當發(fā)生沖突時可能會丟失數(shù)據(jù)包。
交換機顯示的丟棄很少 - 由于我們只在這里傳輸,除非另一個電臺發(fā)送廣播數(shù)據(jù)包,否則永遠不會發(fā)生沖突(可以通過使用 FLP 脈沖將鏈路帶入全雙工來修復(fù))。
由于發(fā)生沖突的可能性很高,集線器顯示大量丟包。

傳入數(shù)據(jù)包

FPGA 發(fā)送 UDP 數(shù)據(jù)包。 但是你如何檢測它們呢?

這是一個簡單的 UDP 測試軟件,可以在 PC 上發(fā)送和/或接收 UDP 數(shù)據(jù)包(使用 PC 的以太網(wǎng)網(wǎng)絡(luò)適配器)。
  • 軟件偵聽端口 1024。 如果它在此端口上收到數(shù)據(jù)包,則會顯示它獲得了多少字節(jié)。

  • 該軟件還可以在端口 1024 上發(fā)送。只需按下“發(fā)送 UDP”按鈕即可。



您可以向自己發(fā)送數(shù)據(jù)包 - 只需使用您機器自己的 IP。 但在這種情況下,數(shù)據(jù)包是在內(nèi)部路由的,永遠沒有機會進入網(wǎng)絡(luò),所以這不太有用。
要查找 PC 的 IP 或以太網(wǎng) MAC 地址,您可以在命令行中鍵入“ipconfig /all”。

請注意,端口值 1024 在軟件中是硬編碼的(在 GUI 中只能配置目標 IP 和有效負載長度)。 這在實踐中不是問題,因為您的計算機不太可能有另一個應(yīng)用程序已經(jīng)在偵聽此特定端口。

10BASE-T FPGA 接口 4 - 接收數(shù)據(jù)包

接收器可用于 2 件事:
  • 在網(wǎng)絡(luò)上創(chuàng)建合法端口。

  • “嗅探”網(wǎng)絡(luò)(監(jiān)視數(shù)據(jù)包) - 只需將接收器并行連接到另一個連接即可。

以下是我在本地網(wǎng)絡(luò)上嗅探的數(shù)據(jù)包示例:

55 55 55 55 55 55 55 D5 00 C0 02 37 57 28 00 10 A4 7B EA 80 08 00 45 00 00 3C 02 24 00 00 80 01 B7 47 C0 A8 00 04 C0 A8 00 01 08 00 42 5C 02 00 09 00 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 61 62 63 64 65 66 67 68 69 62 31 C5 4E

我會讓你剖析它(ping 從“192.168.0.4”到“192.168.0.1”)。

10BASE-T接收器

10BASE-T 接收器由以下部分組成:
  • 差分接收器電路(簡單)。

  • 時鐘提取電路(困難部分)。

  • 前導(dǎo)碼同步器、解串器和以太網(wǎng)校驗和檢查器(在FPGA中輕松完成)。

差分輸入

我們接收來自 RD+/RD- 的差分(2 線)信號。
信號通常是磁耦合的(使用一個小變壓器), 然后使用集成比較器或幾個晶體管轉(zhuǎn)換為共模信號(1 線加接地)。

我手上沒有變壓器,也沒有足夠快的集成比較器。 因此,我使用了電容耦合方案,然后是晶體管。

我的電路原理圖是:


第二個晶體管集電極上的2K電阻高于第一個晶體管(1K)上的值,以幫助在輸出端獲得50%的占空比信號。 否則,輸出端的信號不是方形的......

關(guān)于電容耦合:我相信它與電感耦合一樣好用,但有一個缺點: 當您將電纜插入接收器時,接收器和驅(qū)動器之間的電壓電位差可能會向接收器發(fā)送電流脈沖 (如果驅(qū)動器沒有被變壓器隔離)。 只是我的想法,我在任何地方都沒有找到任何相關(guān)信息。有人有更多信息嗎?

時鐘提取

共模數(shù)據(jù)仍采用曼徹斯特編碼。
曼徹斯特編碼方案的工作方式是始終在邏輯位的中間創(chuàng)建轉(zhuǎn)換。 請記?。?/span>
  • 邏輯“0”以高電平(50ns)后跟低電平(50ns)(故障沿)的形式發(fā)送。

  • 邏輯“1”以低電平(50ns)發(fā)送,后跟高電平(50ns)(上升沿)。

這意味著轉(zhuǎn)換也可能出現(xiàn)在 2 位之間(如果同一邏輯位連續(xù)發(fā)送兩次)。

我知道有三種基本方法可以進行時鐘提?。?/span>
  • 使用更快的時鐘對編碼信號進行過采樣,并使用常規(guī)FPGA邏輯(邊沿檢測器和狀態(tài)機)提取位。

  • 使用帶有改進的相位比較器電路的PLL來重新生成發(fā)送器使用的時鐘。

  • 使用固定延遲不可重新觸發(fā)的單穩(wěn)態(tài),其持續(xù)時間為 1.5 編碼位(75BASE-T 為 10ns)。 單穩(wěn)態(tài)由中間位轉(zhuǎn)換觸發(fā),并忽略位間距轉(zhuǎn)換。

我打算嘗試這三種技術(shù)?,F(xiàn)在,讓我們嘗試第一個。

對信號進行過采樣

這就是“蠻力”方法。 優(yōu)點是完整的解碼是在FPGA中完成的。 不方便的是你需要一個高頻時鐘。
位提取
我決定使用48MHz的采樣頻率。
進行曼徹斯特解碼需要 3 個步驟。
  1. 對傳入的數(shù)據(jù)進行采樣和同步。

  2. 檢測邊緣并啟動計數(shù)器。訣竅在于,計數(shù)器被制作成忽略任何后續(xù)邊沿,直到它翻轉(zhuǎn)(僅檢測曼徹斯特中位轉(zhuǎn)換)。

  3. 一旦檢測到邊沿,下一個位在 3 個計數(shù)后可用 (在48MHz時,周期約為21ns,因此三次計數(shù)為63ns,就在下一個位的中間)。 我們將每個位移入一個 8 位移位寄存器。

reg [2:0] in_data;
always @(posedge clk48) in_data <= {in_data[1:0], manchester_data_in};

reg [1:0] cnt;
always @(posedge clk48) if(|cnt || (in_data[2] ^ in_data[1])) cnt<=cnt+1;

reg [7:0] data;
reg new_bit_avail;
always @(posedge clk48) new_bit_avail <= (cnt==3);
always @(posedge clk48) if(cnt==3) data<={in_data[1],data[7:1]};

位進來了!
前導(dǎo)碼/SFD同步
到目前為止,我們只有比特同步(我們知道每個比特何時到來,以及它的值)。 我們知道一個字節(jié)每 8 位開始一次,但從哪位開始呢?

為了允許字節(jié)同步,以太網(wǎng)幀以以下 8 字節(jié)序列開頭:
55 55 55 55 55 55 55 D5

在二進制中,0x55 是01010101,而 0xD5 是11010101。 此外,以太網(wǎng)指定首先發(fā)送 LSB(例如,0xD5 以 1、0、1、0、1、0、1、1 的形式發(fā)送)。 因此,我們收到 1 和 0 的交替模式,一旦我們檢測到 2 個連續(xù)的 1,我們就知道接下來是真正的數(shù)據(jù)。

reg end_of_Ethernet_frame;

reg [4:0] sync1;
always @(posedge clk48)
if(end_of_Ethernet_frame)
  sync1 <= 0;
else
if(new_bit_avail)
begin
  if(!(data==8'h55 || data==8'hAA)) // not preamble?
    sync1 <= 0;
  else
  if(~&sync1) // if all bits of this "sync1" counter are one, we decide that enough of the preamble
                  // has been received, so stop counting and wait for "sync2" to detect the SFD
    sync1 <= sync1 + 1; // otherwise keep counting
end

reg [9:0] sync2;
always @(posedge clk48)
if(end_of_Ethernet_frame)
  sync2 <= 0;
else
if(new_bit_avail)
begin
  if(|sync2) // if the SFD has already been detected (Ethernet data is coming in)
    sync2 <= sync2 + 1; // then count the bits coming in
  else
  if(&sync1 && data==8'hD5) // otherwise, let's wait for the SFD (0xD5)
    sync2 <= sync2 + 1;
end

wire new_byte_available = new_bit_avail && (sync2[2:0]==3'h0) && (sync2[9:3]!=0);

終于,以太網(wǎng)數(shù)據(jù)正在涌入!
幀結(jié)束
如果在一段時間內(nèi)未檢測到時鐘轉(zhuǎn)換,則以太網(wǎng)幀結(jié)束。
reg [2:0] transition_timeout;

always @(posedge clk48)
if(in_data[2]^in_data[1]) // transition detected?
  transition_timeout <= 0;
else
if(~&cnt)
  transition_timeout <= transition_timeout + 1;

always @(posedge clk48) end_of_Ethernet_frame <= &transition_timeout;
結(jié)束語
獨立應(yīng)用程序需要檢查CRC(位于以太網(wǎng)數(shù)據(jù)包的末尾)是否存在錯誤的傳輸錯誤。
在這里,我只是將完整的數(shù)據(jù)發(fā)送到 PC - 它顯示它或做任何它喜歡的事情。

wire [7:0] q_fifo;
fifo myfifo(.data(data), .wrreq(new_byte_available), .wrclk(clk48),
                .q(q_fifo), .rdreq(rdreq), .rdclk(clk), .rdempty(rdempty));

wire TxD_busy;
wire TxD_start = ~TxD_busy & ~rdempty;
assign rdreq = TxD_start;

async_transmitter async_txd(.clk(clk), .TxD(TxD), .TxD_start(TxD_start), .TxD_busy(TxD_busy), .TxD_data(q_fifo));


關(guān)鍵詞: FPGA Ethernet接口

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉