FPGA:Ethernet接口
以太網全雙工協議易于在FPGA中實現。 這里的目標是將FPGA連接到10BASE-T連接。
本文引用地址:http://www.j9360.com/article/202401/454644.htm以太網數據包:發送和接收
10BASE-T FPGA 接口 0 - 發送以太網流量的方案
在這里,我們演示了如何將以太網流量直接從FPGA發送到PC。
對于此食譜,您需要:FPGA 開發板,具有 2 個空閑 IO 和一個 20MHz 時鐘。
一臺帶有以太網卡并安裝了 TCP-IP 堆棧的 PC(如果你能瀏覽 Internet,你就很好)。
(可選)網絡集線器或交換機。
1. 將FPGA板連接到以太網
以下是使用以太網集線器或交換機的典型測試設置視圖。

使用集線器或交換機可讓電腦在執行此實驗時保持與常規網絡(如果有)的連接。 但您也可以將FPGA直接連接到PC。
我們在這里使用帶有外部 20MHz 振蕩器的 Pluto 板。
將FPGA板上的兩個IO連接到以太網電纜。
如果電纜的另一端連接到集線器或交換機(如上圖所示),請使用以太網電纜的引腳 1 和 2。
如果電纜的另一端直接連接到 PC,請使用引腳 3 和 6。
有關引腳編號,請從這張圖片中獲取幫助:
請注意,極性通常無關緊要,因為信號是差分的,以太網設備可以從輸入信號中檢測極性。
此外,即使這在實踐中有效,我們也無法通過僅將FPGA連接到電纜(我們需要濾波器和變壓器)來滿足以太網電氣要求。 因此,讓我們將其視為“實驗室”實驗。
2.從PC獲取網絡信息
在命令行中鍵入“ipconfig /all”。
寫下您的“物理地址”和“IP 地址”。
3. 對FPGA進行編程
編譯以下 Verilog HDL 代碼。
確保:
更新代碼中的數據值(“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. 傳入數據包
在 PC 上運行此 UDP 接收器軟件(包括源代碼)。
你會得到這樣的東西:
發送有用的流量
上面的代碼在每個 UDP 數據包中發送 18 個數據字節。 這 18 個字節可以來自任何地方,因此,例如,您可以修改代碼以發送 FPGA 引腳的值。
玩得開心發送UDP數據包!
10BASE-T FPGA 接口 1 - 以太網的工作原理
如果你是新手,你可以從Charles Spurgeon的以太網網站上獲得更多細節。
此頁面上的評論同樣適用于 10BASE-T 和 100BASE-T(后者快 10 倍)。
IEEE 802.3協議
IEEE 10.100 標準中描述了 802/3BASE-T 接口。該標準可在 IEEE 802.3 標準協會頁面上免費獲得。 如果您想要副本,請選擇最新標準“IEEE 802.3-2002”。 相關章節包括第3章(MAC幀結構)和第14章(10BASE-T)。
RJ-45 連接器
10/100BASE-T 使用 RJ-45 8 針連接器。它們看起來像這樣:

在 8 個引腳中,只使用了 4 個:
TD+(傳輸+)
TD-(傳輸-)
RD+(接收+)
RD-(接收-)
注意:此引腳排列適用于從計算機輸出的 RJ-45。 集線器或交換機的引腳排列是反轉的(TD在引腳3和6上,RD在引腳1和2上)。
差分信號
每對都使用差分電信號(差分信號對外部干擾的免疫力更強)。 此外,每對線都絞合在電纜中,這進一步提高了抗擾度。數據包編碼
要在以太網上發送數據,您不能就這樣發送;您必須將其封裝到以太網數據包中。 數據包包含一個標頭,其中包含數據到達目的地所需的信息。以太網基于共享介質的理念 - 如果一個站點發送數據包,線路上的每個人都會收到它。 每個以太網卡都有一個唯一的ID(“MAC地址”),因此每個卡都可以自動丟棄發往另一個站點的數據包。 MAC 地址長度為 6 個字節(48 位),足以讓地球上的每個以太網卡都有一個唯一的編號!
半雙工與全雙工
以太網最初是使用真正的共享介質(連接到多個站點的單根同軸電纜)構建的。 傳輸和接收都使用同一根電纜。 所以當然,你不能同時發送和接收。通信是半雙工的。半雙工使用稱為“CSMA/CD”(帶沖突檢測的載波偵聽多址)的協議: 在半雙工中:
在發射之前,每個電臺都必須監聽以確保線路是空閑的(“載波檢測”)。
盡管如此,兩個電臺仍有可能同時傳輸。 因此,必須存在一個復雜的協議來中止(“沖突檢測”)并在以后恢復傳輸。
全雙工通信更好:
您可以獲得兩倍的帶寬。
每個站點都有一個專用的介質,可以隨時開始傳輸,而不會出現并發癥(CSMA/CD 不再適用)。
集線器和交換機
10/100BASE-T是一個“星形拓撲”網絡。 它需要使用集中器設備將多臺計算機連接在一起。有兩種類型的集中器可用:“集線器”或“交換機”。
集線器是一種簡單的電子設備,它在每個鏈路之間提供電氣隔離,但仍然將它們“邏輯”地連接在一起。 這迫使通信是半雙工的。 更糟糕的是:在任何給定時間,只有一臺計算機可以說話。 因此,網絡帶寬在所有計算機之間共享。
交換機(或“交換機集線器”)是一種更復雜的電子設備,它在電氣和邏輯上隔離每個計算機鏈路。 它通過在重新傳輸之前在內部存儲每個傳輸的數據來做到這一點。 因此,每臺計算機都可以隨時說話:這允許全雙工通信。 更好的是:每臺計算機都具有完整的鏈路帶寬。 而且由于介質不共享,因此每個站點僅接收發給自己的數據包(隱私性得到改善)。
集線器:每個鏈路上的半雙工,所有計算機之間共享 10Mbps。慢。。。
交換:每個鏈路上的全雙工,每臺計算機專用 20Mbps(單向 10Mbps)。快!
此項目建議使用全雙工鏈路,因為它不實現 CSMA/CD。 使用半雙工鏈路仍然有效,但代價是潛在的數據包丟失(尤其是在使用集線器共享介質時)。
10BASE-T FPGA 接口 2 - 基于以太網的 IP/UDP
這些數據包易于生產;但功能強大,它們可以在互聯網上傳播(并被發送到世界任何地方!
下面是一個示例:
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
繼續閱讀,它在下面解碼。
協議棧
數據包使用三種不同的協議:以太網、IP 和 UDP(從低級到高級協議)。每個協議都添加了自己的功能,并嵌入到較低級別的協議中。
UDP 部分包含要發送的數據(“有效負載”)。
IP 部分允許數據包通過 Internet 路由。
以太網部分允許數據包在以太網上本地發送。
上面顯示的數據包在這里解碼:
以太網前導碼/SFD(同步器):55 55 55 55 55 55 D55
以太網目標地址:00 10 A4 7B EA 80
以太網源地址:00 12 34 56 78 90
以太網類型: 08 00 (=IP)
IP 標頭:45 00 00 2E B3 FE 00 00 80
IP 協議: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 字節):00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11
以太網校驗和: B3 31 88 1B
創建自己的數據包
這是一個簡單的軟件,允許創建自定義以太網/IP/UDP 數據包。在此處下載。源代碼包括在內(目前是 Delphi 6 - 在 Delphi XE2 中要小心,因為一個用戶報告了錯誤生成的數據包......

每次按下“發送”按鈕時,軟件都會構建一個數據包,顯示它并將其發送到串行端口(這將在本項目的第 3 部分后面有用)。
10BASE-T FPGA 接口 3 - 發送數據包
第 2 部分中的軟件將原始以太網數據包數據發送到 PC 的串行端口。
我們的FPGA板只需要從串行端口接收它(慢速...),然后通過10BASE-T線對發送(快!)。
讓 PC 計算原始數據包數據使我們能夠非常輕松地對所有數據包參數進行實驗。 這很好,但最終FPGA應該在獨立模式下工作,并自行烹飪/發送數據包。
曼徹斯特編碼
10BASE-T 網絡以 10Mbps(每秒 10 兆比特)的速度工作。但 10BASE-T 要求這些位是“曼徹斯特編碼”的。這需要將位數增加一倍!
邏輯“0”作為高位發送,后跟低位。
邏輯“1”作為低位發送,后跟高位。
FPGA 時鐘
簡單性決定了我們使用10MHz時鐘,并通過將時鐘與輸出數據位“xXing”來生成20Mbps曼徹斯特比特流。那可能行得通。但我們正在把握時間。在FPGA設計中不建議這樣做。 在這種特殊情況下,這會降低20Mbps的信號質量,因為每個位轉換都會出現毛刺(數據輸出永遠不會與時鐘完全重合)。
因此,讓我們使用20MHz時鐘。
保持鏈接處于活動狀態
即使 10BASE-T 電纜上沒有發送數據包,也必須定期發送脈沖(稱為“正常鏈路脈沖”或“NLP”)。 它用于保持連接“活動”。每 16 毫秒左右需要發送一次脈沖。NLP也可以在稱為“自動協商”的過程中被“快速鏈接脈沖”(FLP)突發所取代。 FLP 攜帶有關發送方功能的信息,以便電纜兩端的硬件可以協商鏈路參數,例如速度和半雙工/全雙工狀態。
HDL設計
假設要發送的數據包在FPGA的RAM中可用。ram512 ram( .data(ram_input), .wraddress(wraddress), .clock(clk), .q(ram_output), .rdaddress(rdaddress) ); |
我們假設我們也知道數據包的長度。我們將讀取數據包數據并將其發送到以太網上。
首先,我們需要一個開始信號。
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 位字節需要 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; |
當我們到達字節的最后一位時,我們從RAM中讀取一個新字節并將其放入移位寄存器中。 移位寄存器每隔一個時鐘就會給我們一個新位。
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; |
每個數據包都需要以“TP_IDL”結尾(大約 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; |
最后,曼徹斯特編碼器發送位,然后發送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,因此鏈路是半雙工的,當發生沖突時可能會丟失數據包。
交換機顯示的丟棄很少 - 由于我們只在這里傳輸,除非另一個電臺發送廣播數據包,否則永遠不會發生沖突(可以通過使用 FLP 脈沖將鏈路帶入全雙工來修復)。
由于發生沖突的可能性很高,集線器顯示大量丟包。
傳入數據包
FPGA 發送 UDP 數據包。 但是你如何檢測它們呢?這是一個簡單的 UDP 測試軟件,可以在 PC 上發送和/或接收 UDP 數據包(使用 PC 的以太網網絡適配器)。
軟件偵聽端口 1024。 如果它在此端口上收到數據包,則會顯示它獲得了多少字節。
該軟件還可以在端口 1024 上發送。只需按下“發送 UDP”按鈕即可。

您可以向自己發送數據包 - 只需使用您機器自己的 IP。 但在這種情況下,數據包是在內部路由的,永遠沒有機會進入網絡,所以這不太有用。
要查找 PC 的 IP 或以太網 MAC 地址,您可以在命令行中鍵入“ipconfig /all”。
請注意,端口值 1024 在軟件中是硬編碼的(在 GUI 中只能配置目標 IP 和有效負載長度)。 這在實踐中不是問題,因為您的計算機不太可能有另一個應用程序已經在偵聽此特定端口。
10BASE-T FPGA 接口 4 - 接收數據包
在網絡上創建合法端口。
“嗅探”網絡(監視數據包) - 只需將接收器并行連接到另一個連接即可。
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 接收器由以下部分組成:差分接收器電路(簡單)。
時鐘提取電路(困難部分)。
前導碼同步器、解串器和以太網校驗和檢查器(在FPGA中輕松完成)。
差分輸入
我們接收來自 RD+/RD- 的差分(2 線)信號。信號通常是磁耦合的(使用一個小變壓器), 然后使用集成比較器或幾個晶體管轉換為共模信號(1 線加接地)。
我手上沒有變壓器,也沒有足夠快的集成比較器。 因此,我使用了電容耦合方案,然后是晶體管。
我的電路原理圖是:

第二個晶體管集電極上的2K電阻高于第一個晶體管(1K)上的值,以幫助在輸出端獲得50%的占空比信號。 否則,輸出端的信號不是方形的......
關于電容耦合:我相信它與電感耦合一樣好用,但有一個缺點: 當您將電纜插入接收器時,接收器和驅動器之間的電壓電位差可能會向接收器發送電流脈沖 (如果驅動器沒有被變壓器隔離)。 只是我的想法,我在任何地方都沒有找到任何相關信息。有人有更多信息嗎?
時鐘提取
共模數據仍采用曼徹斯特編碼。曼徹斯特編碼方案的工作方式是始終在邏輯位的中間創建轉換。 請記住:
邏輯“0”以高電平(50ns)后跟低電平(50ns)(故障沿)的形式發送。
邏輯“1”以低電平(50ns)發送,后跟高電平(50ns)(上升沿)。
我知道有三種基本方法可以進行時鐘提取:
使用更快的時鐘對編碼信號進行過采樣,并使用常規FPGA邏輯(邊沿檢測器和狀態機)提取位。
使用帶有改進的相位比較器電路的PLL來重新生成發送器使用的時鐘。
使用固定延遲不可重新觸發的單穩態,其持續時間為 1.5 編碼位(75BASE-T 為 10ns)。 單穩態由中間位轉換觸發,并忽略位間距轉換。
對信號進行過采樣
這就是“蠻力”方法。 優點是完整的解碼是在FPGA中完成的。 不方便的是你需要一個高頻時鐘。位提取
我決定使用48MHz的采樣頻率。進行曼徹斯特解碼需要 3 個步驟。
對傳入的數據進行采樣和同步。
檢測邊緣并啟動計數器。訣竅在于,計數器被制作成忽略任何后續邊沿,直到它翻轉(僅檢測曼徹斯特中位轉換)。
一旦檢測到邊沿,下一個位在 3 個計數后可用 (在48MHz時,周期約為21ns,因此三次計數為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]}; |
位進來了!
前導碼/SFD同步
到目前為止,我們只有比特同步(我們知道每個比特何時到來,以及它的值)。 我們知道一個字節每 8 位開始一次,但從哪位開始呢?為了允許字節同步,以太網幀以以下 8 字節序列開頭:
55 55 55 55 55 55 55 D5
在二進制中,0x55 是01010101,而 0xD5 是11010101。 此外,以太網指定首先發送 LSB(例如,0xD5 以 1、0、1、0、1、0、1、1 的形式發送)。 因此,我們收到 1 和 0 的交替模式,一旦我們檢測到 2 個連續的 1,我們就知道接下來是真正的數據。
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); |
終于,以太網數據正在涌入!
幀結束
如果在一段時間內未檢測到時鐘轉換,則以太網幀結束。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; |
結束語
獨立應用程序需要檢查CRC(位于以太網數據包的末尾)是否存在錯誤的傳輸錯誤。在這里,我只是將完整的數據發送到 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)); |
評論