FPGA乙太網學習-RGMII與GMII

祈愿树下發表於2024-07-07

乙太網口都叫RJ45介面,從功能角度說,網口只是訊號連線,本身沒有通訊能力。

FPGA乙太網學習-RGMII與GMII

PHY(物理層),這邊需要一個晶片,將並行的乙太網資料到符合乙太網物理層鏈路資料傳輸格式的電平訊號轉換。

上圖PHY右邊是經過編碼後的序列資料訊號,左側是提供多種並行訊號。網路變壓器連線序列訊號和網口。

MII介面(百兆/十兆介面)

FPGA乙太網學習-RGMII與GMII

FPGA乙太網學習-RGMII與GMII

MAC向PHY側傳輸資料的時序圖如下


MII 介質無關介面

MAC 介質訪問介面

FPGA乙太網學習-RGMII與GMII

RTL8211F-CG是一款支援 RGMII 介面的乙太網物理層收發器,能夠工作在 10M、100M 或 1000M Base模式,對 MAC 層可提供 RGMII 介面模式,並提供了標準的 MDIO 管理介面與處理器相連。

乙太網物理層晶片都有一個器件地址,該器件地址就是在介紹 PHY 管理介面 MDIO 時所說的 PHY 器件地址,該器件地址用來由 MDIO 主機(如 MAC 或處理器)定址 MDIO 匯流排上連線的指定的 PHY 晶片。

對於 RTL8211F-CG 晶片,該地址分為固定部分和硬體可設定兩部分。器件地址共有 5 位,其中高 2 位為固定的 00,低三位透過三個地址設定腳在晶片上電或復位時候設定, 這三位分別對應 RTL8211F 晶片的 22、27、26 腳。下表為這三個腳功能說明。

FPGA乙太網學習-RGMII與GMII

FPGA乙太網學習-RGMII與GMII

FPGA乙太網學習-RGMII與GMII

注意:手冊裡下面這個話好像寫錯了,PHY_ADDR1透過電阻下拉到低電平,其他兩個透過電阻上拉到高電平。

所以器件地址為00101.

FPGA乙太網學習-RGMII與GMII


乙太網MAC幀協議介紹

乙太網技術的正式標準是 IEEE802.3 標準,它規定了在乙太網中傳輸的資料幀結構,如下圖所示。

FPGA乙太網學習-RGMII與GMII

從目的地址開始到資料和填充欄位的最後一個為止,這些跟CRC進行計算。不包括前同步碼和分隔符。

FPGA乙太網學習-RGMII與GMII

前導碼的作用是為了標記一幀乙太網幀即將開始傳輸,分隔符存在的意義是指前導碼傳輸完畢。

MC地址對應,一對一,多播,廣播。MAC地址,每個裝置都唯一,需要向美國IEEE申請。48位地址,前24位IEEE指定,後24位裝置廠商自行定義。

乙太網中的資料部分,一般都在另一個上層協議,如TCP/IP協議。使用者資料都是包含在該上層協議中。


RGMII 介面訊號與時序

FPGA乙太網學習-RGMII與GMII

這個RGMII和GMII的互轉,是因為需要使用RGMII並行傳輸協議,RGMII是上下沿取樣,所以是雙倍於GMII的速度

在PHY和MAC層之間傳輸並行資料的方法。所以從PHY到MAC這邊是RGMII轉GMII,從MAC到PHY這邊是GMII轉RGMII

用下面這個迴環測試的示意圖能看出來。

1、網口連線電腦以太,從電腦這邊發一個資料,經過PHY串轉並,透過RGMII轉GMII進入,然後輸出來之後,給GMII轉RGMII就行了

FPGA乙太網學習-RGMII與GMII

FPGA乙太網學習-RGMII與GMIIFPGA乙太網學習-RGMII與GMII

RGMII資料在時鐘的上升沿和下降沿均進行取樣。

資料邊沿對齊的形式使得 PCB 設計變得更加的複雜,所以,後來的 PHY 晶片都針對 RGMII介面提供了可選的內部延遲電路。(暫時不理解)

RGMII呼叫FPGA的ODDR原語,實現輸出DDR暫存器,例項化原語就會自動訪問該功能。

FPGA乙太網學習-RGMII與GMII

埠訊號FPGA乙太網學習-RGMII與GMII

ODDR屬性FPGA乙太網學習-RGMII與GMII

直接看程式碼(寫註釋裡面)

這個跟TB檔案中的程式碼對應,

rgmii_to_gmii.v


module rgmii_to_gmii(
  reset,            //復位訊號

  rgmii_rx_clk,     //接收資料參考時鐘,由PHY輸出過來的
  rgmii_rxd,        //PHY傳向MAC的資料[3:0]
                    
  rgmii_rxdv, //在RX_CTL裡面的,傳輸GMII中的RX_DV和RX_ER訊號

  gmii_rx_clk,  //gmii的參考時鐘,跟RGMII的對應就行了
  gmii_rxdv,    //Reveive Data Valid,接收資料有效訊號,作用類似於傳送通道的TX_EN
  gmii_rxd,    //接收資料
  gmii_rxer     //接收錯誤error,高電平有效
);



  input         reset;

  input         rgmii_rx_clk;
  input  [3:0]  rgmii_rxd;
  input         rgmii_rxdv;

  output        gmii_rx_clk;
  output [7:0]  gmii_rxd;
  output        gmii_rxdv;
  output        gmii_rxer;
  
  wire          gmii_rxer_r;
  

  assign gmii_rx_clk = rgmii_rx_clk;   //時鐘狠狠同步

  genvar i;
  generate
    for(i=0;i<4;i=i+1)
    begin: rgmii_rxd_i
      IDDR #(
        .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE" 
                                              // or "SAME_EDGE_PIPELINED" Q1Q2變化發生在同一個上升沿,都是下一個
        .INIT_Q1(1'b0   ),  // Initial value of Q1: 1'b0 or 1'b1,預設0
        .INIT_Q2(1'b0   ),  // Initial value of Q2: 1'b0 or 1'b1
        .SRTYPE ("SYNC" )   // Set/Reset type: "SYNC" or "ASYNC" ,這是預設
      ) IDDR_rxd (
        .Q1   (gmii_rxd[i]   ), // 1-bit output for positive edge of clock,低4位
        .Q2   (gmii_rxd[i+4] ), // 1-bit output for negative edge of clock,高4位
        .C    (rgmii_rx_clk  ), // 1-bit clock input,rgmii參考時鐘
        .CE   (1'b1          ), // 1-breset_nit clock enable input
        .D    (rgmii_rxd[i]  ), // 上升沿傳GMII的RXD的低4位,下降沿傳高4位
        .R    (reset         ), // 1-bit reset
        .S    (1'b0          )  // 1-bit set,復位,高電平有效
      );
    end
  endgenerate

  IDDR #(
    .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE" 
                                          // or "SAME_EDGE_PIPELINED" 
                                          //Q1Q2變化發生在同一個上升沿,但是都是下一個上升沿
    .INIT_Q1(1'b0   ),  // Initial value of Q1: 1'b0 or 1'b1
    .INIT_Q2(1'b0   ),  // Initial value of Q2: 1'b0 or 1'b1
    .SRTYPE ("SYNC" )   // Set/Reset type: "SYNC" or "ASYNC" 
  ) IDDR_rxdv (
    .Q1   (gmii_rxdv), // 1-bit output for positive edge of clock
    .Q2   (gmii_rxer_r), // 1-bit output for negative edge of clock
    .C    (rgmii_rx_clk ), // 1-bit clock input
    .CE   (1'b1         ), // 1-breset_nit clock enable input
    .D    (rgmii_rxdv   ), // 1-bit DDR data input,這個就對應RX_CTL的,因為上升沿傳DV,下降沿傳ER
                            //
    .R    (reset        ), // 1-bit reset
    .S    (1'b0         )  // 1-bit set
  );

    assign gmii_rxer = gmii_rxer_r^gmii_rxdv; //異或,相異為1.
endmodule
rgmii_to_gmii_tb.v
 `timescale 1ns / 1ps
`define CLK_PERIOD 8

module rgmii_to_gmii_tb();
    reg  reset;
    wire gmii_rx_clk;
    
    reg [3:0] rx_byte_cnt;//接收位元計數
    
    wire [7:0] gmii_rxd;
    wire gmii_rxdv;
    wire gmii_rxer;
    wire rgmii_rx_clk;
    reg [3:0] rgmii_rxd;
    reg rgmii_rxdv;
    reg rx_clk;
    wire locked;
    
    rgmii_to_gmii rgmii_to_gmii(
        .reset(reset),
        .gmii_rx_clk(gmii_rx_clk),  
        .gmii_rxdv(gmii_rxdv),
        .gmii_rxd(gmii_rxd),
        .gmii_rxer(gmii_rxer),
    
        .rgmii_rx_clk(rgmii_rx_clk),
        .rgmii_rxd(rgmii_rxd),
        .rgmii_rxdv(rgmii_rxdv)  //RX_CTL
    );
    
    //對rgmii_rx_clk時鐘進行90度相位調製,以保證該時鐘到達IDDR時候
    //與rgmii_rxd和rgmii_rxdv訊號成中心對齊關係。
    //其實就是為了能夠在對rgmii_rx_clk上升沿和下降沿的時候,正好處在rgmii_rxd的值的中心位置取樣。
      rx_pll rx_pll
   (
    // Clock out ports
    .clk_out1(rgmii_rx_clk),     // output clk_out1,90度相位調製
    // Status and control signals
    .reset(reset), // input reset
    .locked(locked),       // output locked,是時鐘穩定的訊號
   // Clock in ports
    .clk_in1(rx_clk)      // input clk_in1
    );

    //clock generate
    initial rx_clk = 1'b1;
    always #(`CLK_PERIOD/2)rx_clk = ~rx_clk;  //每4ns轉,週期8ns
    
    always@(rx_clk or posedge reset)
    if(reset)
        rx_byte_cnt <= 4'd0;
    else if(rgmii_rxdv && locked)   //RX_CTL的
        rx_byte_cnt <= rx_byte_cnt + 1'b1;   //16個資料
    else
        rx_byte_cnt <= 4'd0;

    always@(*)
    begin
        case(rx_byte_cnt)
        16'd0  : rgmii_rxd = 12;
        16'd1  : rgmii_rxd = 7;
        16'd2  : rgmii_rxd = 9;
        16'd3  : rgmii_rxd = 6;
        16'd4  : rgmii_rxd = 11;
        16'd5  : rgmii_rxd = 15;
        16'd6  : rgmii_rxd = 0;
        16'd7  : rgmii_rxd = 8;
        16'd8  : rgmii_rxd = 4;
        16'd9  : rgmii_rxd = 2;
        16'd10 : rgmii_rxd = 5;
        16'd11 : rgmii_rxd = 1;
        16'd12 : rgmii_rxd = 3;
        16'd13 : rgmii_rxd = 10;
        16'd14 : rgmii_rxd = 14;
        16'd15 : rgmii_rxd = 13;
        endcase
    end

  initial
    begin
    reset = 1;
    rgmii_rxdv = 0;
    #201;
    reset = 0;   //因為PLL時鐘的RESET訊號,高電平有效
    rgmii_rxdv = 1;
    #2000; 
    $stop;
  end

endmodule

1、這邊看到計數0的時候,RGMII_RXD的值是12(C),在RGMII_RX_CLK的上升沿傳輸低4位,下降沿傳輸高四位

2、以第一個GMII的資料為例,上升沿C,下降沿7,因為是SAME_EDGE_PIPELINED,所以Q1Q2在下一個上升沿發生變化,在GMII_RXD這邊就是7C的呈現。

3、時鐘偏90度相位,我的理解就是方便取樣取值

FPGA乙太網學習-RGMII與GMII

下面是GMII轉RGMII介面。

gmii_to_rgmii.v
 /*例化6個ODDR就可以實現,其中4個用來傳送4個TXD訊號
一個用來傳送TXEN和TXER訊號
一個用來輸出TX_CLK訊號

*/
module gmii_to_rgmii(
  reset_n,

  gmii_tx_clk,   //gmii傳送參考時鐘,mac提供
  gmii_txd,      //gmii_txd[7:0]
  gmii_txen,    //傳送使能
  gmii_txer,    //傳送錯誤資訊

  rgmii_tx_clk,
  rgmii_txd,     
  rgmii_txen     //tx_ctl,上升沿傳tx_en,下降沿傳tx_er
);
  input        reset_n;

  input        gmii_tx_clk;
  input  [7:0] gmii_txd;
  input        gmii_txen;
  input        gmii_txer;
  
  output       rgmii_tx_clk;
  output [3:0] rgmii_txd;
  output       rgmii_txen;

  genvar i;  //generate必須是用這個genvar定義引數
  generate
    for(i=0;i<4;i=i+1)
    begin: rgmii_txd_o
      ODDR #(
        .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
        .INIT  (1'b0   ),          // Initial value of Q: 1'b0 or 1'b1
        .SRTYPE("SYNC" )          // Set/Reset type: "SYNC" or "ASYNC" 
      ) ODDR_rgmii_txd (
        .Q   (rgmii_txd[i]     ), // 輸出RGMII的TXD
        .C   (gmii_tx_clk      ), // gmii的傳送時鐘
        .CE  (1'b1             ), // 使能
        .D1  (gmii_txd[i]      ), // GMII_TXD的低4位
        .D2  (gmii_txd[i+4]    ), // GMII_TXD的高4位
        .R   (~reset_n         ), // 復位,因為復位reset跟set,高電平有效,所以reset=0的時候,這邊才復位
        .S   (1'b0             )  // 高電平有效,不設定
      );
    end
  endgenerate

  ODDR #(
    .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
    .INIT  (1'b0   ),           // Initial value of Q: 1'b0 or 1'b1
    .SRTYPE("SYNC" )            // Set/Reset type: "SYNC" or "ASYNC" 
  ) ODDR_rgmii_txd (
    .Q   (rgmii_txen          ), // 1-bit DDR output
    .C   (gmii_tx_clk         ), // 1-bit clock input
    .CE  (1'b1                ), // 1-bit clock enable input
    .D1  (gmii_txen           ), // 上升沿採集txen訊號
    .D2  (gmii_txen^gmii_txer ), // RGMII的TX_CTL傳輸GMII的TX_EN 和 TX_ER 兩種資訊,TX_CLK 的上升沿傳送 TX_EN,下降沿傳送 TX_ER
    //.D2  (gmii_txer ),
    //所以這邊D2應該GMII_TXER也可以,等會模擬看看
    .R   (~reset_n            ), // 1-bit reset
    .S   (1'b0                )  // 1-bit set
  );

//這邊對於RGMII_TX_CLK的輸出,也是用ODDR輸出,而不是PLL產生。
//優勢在於最大程度保證輸出的RGMII_TX_CLK與RGMII_TXD[3:0]保持邊緣對齊
//前面的TXD和CLK都能ODDR輸出,可以保證傳輸路徑的時序模型完全一致
  ODDR #(
    .DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" 
    .INIT  (1'b0   ),           // Initial value of Q: 1'b0 or 1'b1
    .SRTYPE("SYNC" )            // Set/Reset type: "SYNC" or "ASYNC" 
  ) ODDR_rgmii_clk (
    .Q   (rgmii_tx_clk  ), // 1-bit DDR output
    .C   (gmii_tx_clk   ), // 1-bit clock input
    .CE  (1'b1          ), // 1-bit clock enable input
    .D1  (1'b1          ), // 1-bit data input (positive edge)
    .D2  (1'b0          ), // 1-bit data input (negative edge)
    .R   (~reset_n      ), // 1-bit reset
    .S   (1'b0          )  // 1-bit set
  );

endmodule
gmii_to_rgmii_tb.v
 `timescale 1ns / 1ps
`define CLK_PERIOD 8

module gmii_to_rgmii_tb();
    reg  reset_n;
    reg gmii_tx_clk;
    reg [3:0] tx_byte_cnt;  //傳送資料計數
    reg [7:0] gmii_txd;    //傳送資料
    reg gmii_txen;
    reg gmii_txer;
    wire rgmii_tx_clk;
    wire [3:0] rgmii_txd;
    wire rgmii_txen;
    
    gmii_to_rgmii gmii_to_rgmii(
      .reset_n(reset_n),
      .gmii_tx_clk(gmii_tx_clk),
      .gmii_txd(gmii_txd),
      .gmii_txen(gmii_txen),
      .gmii_txer(gmii_txer),
    
      .rgmii_tx_clk(rgmii_tx_clk),
      .rgmii_txd(rgmii_txd),
      .rgmii_txen(rgmii_txen)
    );

        //clock generate
        //每4ns翻轉一次,週期8ns
    initial gmii_tx_clk = 1'b1;
    always #(`CLK_PERIOD/2)gmii_tx_clk = ~gmii_tx_clk;
    
    always@(posedge gmii_tx_clk or negedge reset_n)
    if(!reset_n)
        tx_byte_cnt <= 4'd0;
    else if(gmii_txen) //如果gmii的傳送使能,計數開始
        tx_byte_cnt <= tx_byte_cnt + 1'b1;
    else
        tx_byte_cnt <= 4'd0;

    always@(*)
    begin
        case(tx_byte_cnt)  //計數16次,分別賦值的傳送資料
        16'd0  : gmii_txd = 25;
        16'd1  : gmii_txd = 34;
        16'd2  : gmii_txd = 18;
        16'd3  : gmii_txd = 96;
        16'd4  : gmii_txd = 78;
        16'd5  : gmii_txd = 29;
        16'd6  : gmii_txd = 63;
        16'd7  : gmii_txd = 42;
        16'd8  : gmii_txd = 51;
        16'd9  : gmii_txd = 74;
        16'd10 : gmii_txd = 39;
        16'd11 : gmii_txd = 60;
        16'd12 : gmii_txd = 27;
        16'd13 : gmii_txd = 36;
        16'd14 : gmii_txd = 145;
        16'd15 : gmii_txd = 231;
        endcase
    end

  initial
    begin
    reset_n = 0;
    gmii_txen = 0;
    gmii_txer = 0;
    #201;
    reset_n = 1;
    gmii_txen = 1;
    #2000; 
    $stop;
  end

endmodule

1、gmii_txd的第一個資料是19(其實是0001 1001)換算十進位制就是25,用的SAME_EDGE,所以是時鐘上升沿取D1D2(低四位高四位),然後依次在一個時鐘週期內賦值給Q

2、可以看到RGMII_TXD是先9再1,而9是上面的低4位,1是上面的高4位,且是在同一個時鐘週期。

FPGA乙太網學習-RGMII與GMII

相關文章