首先先夸一下黑金的FPGA开发板和相关教程,真心不错,下面的内容也是学习黑金的开发板获得的。
一、 RGMII简单说明
RGMII实际上是是简化版的GMII,时钟频率依旧是125MHz,为了保持1000Mbps的传输速率不变,在时钟的上升沿和下降沿都采样数据,在参考时钟的上升沿处理GMII接口中的TXD[3:0]/RXD[3:0],在下降沿处理TXD[7:4]/RXD[7:4],在TX_EN信号线上传送TX_EN和TX_ER两种信息,在TX_CLK的上升沿发送TX_EN,下降沿发送TX_ER;同样,RX_DV信号线上传输RX_DV和RX_ER两种信息,在RX_CLK的上升沿发送RX_DV,下降沿发送RX_ER。采用RGMII的主要作用是为了降低电路成本。
RGMII还有个优势就是同时也兼容100Mbps和10Mbps两种速率,所以对于自适应的网络特别适用,此时的参考时钟分别为25MHz和2.5MHz。对于数据的处理在上升沿进行处理,与100Mbps的处理方式有所不同,所以为了达到自适应网络,FPGA程序部分需要做点处理,可能处理的方式有很多种,下面推荐一种处理方式。
二、 具体实现
下面方法以xilinx为模板做的。一般3种通信速率的PHY芯片都支持自检测通信速率,可以通过这个检测作为参考进行相应的处理。在数据的接口部分3种速率的处理方式都是一样的,在时钟的上升沿和下降沿都对数据进行处理,会用到原语IDDR和ODDR(这两个原语一个针对接收,一个针对发送,主要是在时钟的上升沿和下降沿都对数据进行处理)。为10Mbps/100Mbps时,接口传送的数据接口为4位,而数据处理时为8位处理,所以这可以当做是跨时钟域处理了,这就用到FIFO了,这里不管发送还是接收,都会用到2个FIFO,这2个FIFO的作用一个用来缓存数据,一个用来缓存数据长度,有人可能会觉得缓存数据长度的FIFO是多余的,非也,听我细细道来。在实际应用中,很多情况下发送的帧数据的长度并不是一个固定值,而是一个随机值,其次还有,这一帧数据还没处理结束,下一帧数据就过来了,所以也要对数据长度做一个缓存。其次,这个方式也不仅仅针对自适应以太网,在很多通信跨时钟域处理中都可以应用此方法。
下面还是看看程序的实现。
首先接口部分,可以看看上面的图,接收时钟由PHY部分提供,发送时钟由MAC部分提供,在写代码时,可以直接进行赋值就可以了,如果这个时钟不仅仅做一个转换的话,还是建议调用一下原语BUFG,这是提供全局时钟的。
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) rgmii_txc_out (
.Q (rgmii_txc),
.C (gmii_tx_clk_s),
.CE(1),
.D1(1),
.D2(0),
.R(tx_reset_sync),
.S(0));
generate
genvar i;
for (i = 0; i < 4; i = i + 1) begin : gen_tx_data
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) rgmii_td_out (
.Q (rgmii_td[i]),
.C (gmii_tx_clk_s),
.CE(1),
.D1(gmii_txd_r_d1[i]),
.D2(gmii_txd_low[i]),
.R(tx_reset_sync),
.S(0));
end
endgenerate
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE")
) rgmii_tx_ctl_out (
.Q (rgmii_tx_ctl),
.C (gmii_tx_clk_s),
.CE(1),
.D1(gmii_tx_en_r_d1),
.D2(rgmii_tx_ctl_r),
.R(tx_reset_sync),
.S(0));
generate
for (i = 0; i < 4; i = i + 1) begin
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED")
) rgmii_rx_iddr (
.Q1(gmii_rxd_s[i]),
.Q2(gmii_rxd_s[i+4]),
.C(gmii_rx_clk),
.CE(1),
.D(rgmii_rd[i]),
.R(0),
.S(0));
end
endgenerate
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED")
) rgmii_rx_ctl_iddr (
.Q1(gmii_rx_dv_s),
.Q2(rgmii_rx_ctl_s),
.C(gmii_rx_clk),
.CE(1),
.D(rgmii_rx_ctl),
.R(0),
.S(0));
上面是接口调用原语部分。
在接收数据时,要注意一下,在接口接收数据时接收了8位数据,其实在10Mbps/100Mbps时,高4位的数据就是低四位的数据;计数器计数的值为帧头的一半;还有就是缓存数据长度的FIFO写使能在数据有效信号的下降沿进行使能操作。具体实现主要分为2个状态,一个是检查缓存数据长度的FIFO是否有数据状态,另一个就是读FIFO数据状态了。具体相关代码如下:
`timescale 1ns / 1ps
//
// Module Name: ethernet_test
//
module gmii_rx_buffer
(
input clk,
input rst_n,
input eth_100m_en, //ethernet 100M enable
input eth_10m_en, //ethernet 100M enable
input link, //ethernet link signal
input gmii_rx_dv, //gmii rx dv
input [7:0] gmii_rxd, //gmii rxd
(* MARK_DEBUG="true" *)output reg e10_100_rx_dv, //ethernet 10/100 rx_dv
(* MARK_DEBUG="true" *)output [7:0] e10_100_rxd //ethernet 10/100 rxd
);
reg [15:0] rx_cnt ; //write fifo counter
reg rx_wren ; //write fifo wren
reg [7:0] rx_wdata ; //write fifo data
reg [15:0] rx_data_cnt ; //read fifo counter
reg rx_rden ; //read fifo rden
wire [7:0] rx_rdata ; //read fifo data
reg [3:0] rxd_high ; //rxd high 4 bit
reg [3:0] rxd_low ; //rxd low 4 bit
(* MARK_DEBUG="true" *)reg gmii_rx_dv_d0 ;
(* MARK_DEBUG="true" *)reg gmii_rx_dv_d1 ;
reg gmii_rx_dv_d2 ;
reg [15:0] pack_len ; //package length
reg [1:0] len_cnt ; //length latch counter
wire [4:0] pack_num ; //length fifo usedw
reg rx_len_wren ; //length wren
reg [15:0] rx_len_wdata ; //length write data
reg rx_len_rden ; //length rden
wire [15:0] rx_len ; //legnth read data
localparam IDLE = 4'd0 ;
localparam CHECK_FIFO = 4'd1 ;
localparam LEN_LATCH = 4'd2 ;
localparam REC_WAIT = 4'd3 ;
localparam READ_FIFO = 4'd4 ;
localparam REC_END = 4'd5 ;
reg [3:0] state ;
reg [3:0] next_state ;
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
state <= IDLE ;
else
state <= next_state ;
end
always @(*)
begin
case(state)
IDLE :
begin
next_state <= CHECK_FIFO ;
end
CHECK_FIFO :
begin
if (pack_num > 5'd0) // if length fifo usedw > 0, means there is package in data fifo
next_state <= LEN_LATCH ;
else
next_state <= CHECK_FIFO ;
end
LEN_LATCH:
begin
if (len_cnt == 2'd3) // delay some clock
next_state <= REC_WAIT ;
else
next_state <= LEN_LATCH ;
end
REC_WAIT :
next_state <= READ_FIFO ;
READ_FIFO :
begin
if (rx_data_cnt == pack_len - 1) // when reach package length read end
next_state <= REC_END ;
else
next_state <= READ_FIFO ;
end
REC_END :
next_state <= IDLE ;
default :
next_state <= IDLE ;
endcase
end
/*************************************************
write length to fifo
*************************************************/
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
gmii_rx_dv_d0 <= 1'b0 ;
gmii_rx_dv_d1 <= 1'b0 ;
gmii_rx_dv_d2 <= 1'b0 ;
end
else
begin
gmii_rx_dv_d0 <= gmii_rx_dv ;
gmii_rx_dv_d1 <= gmii_rx_dv_d0 ;
gmii_rx_dv_d2 <= gmii_rx_dv_d1 ;
end
end
//write rx length wren to fifo when gmii_rx_dv negedge
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
rx_len_wren <= 1'b0 ;
else if (gmii_rx_dv == 1'b0 && gmii_rx_dv_d0 == 1'b1)
rx_len_wren <= eth_100m_en | eth_10m_en ;
else
rx_len_wren <= 1'b0 ;
end
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
rx_cnt <= 16'd0 ;
//else if (eth_10m_en & (gmii_rx_dv_d0 | gmii_rx_dv_d1)) //when 10M mode, there is one unnecessary 4 bits data need to be take out
else if (eth_10m_en & (gmii_rx_dv | gmii_rx_dv_d0)) //when 10M mode, there is one unnecessary 4 bits data need to be take out
rx_cnt <= rx_cnt + 1'b1 ;
else if (eth_100m_en & (gmii_rx_dv | gmii_rx_dv_d1))
rx_cnt <= rx_cnt + 1'b1 ;
else if (state == REC_WAIT)
rx_cnt <= 16'd0 ;
end
//write length to fifo
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
rx_len_wdata <= 16'd0 ;
else
rx_len_wdata <= rx_cnt ;
end
/*************************************************
write data to fifo
*************************************************/
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
rxd_high <= 4'd0 ;
rxd_low <= 4'd0 ;
end
else if (gmii_rx_dv | gmii_rx_dv_d1)
begin
if (rx_cnt[0])
begin
rxd_high <= gmii_rxd[3:0] ;
end
else
begin
rxd_low <= gmii_rxd[3:0] ;
end
end
else
begin
rxd_high <= 4'd0 ;
rxd_low <= 4'd0 ;
end
end
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
rx_wren <= 1'b0 ;
rx_wdata <= 8'd0 ;
end
else if (gmii_rx_dv_d1)
begin
if (rx_cnt[0])
begin
rx_wren <= 1'b0 ;
end
else
begin
rx_wdata <= {rxd_high,rxd_low} ;
rx_wren <= eth_100m_en | eth_10m_en ;
end
end
else
begin
rx_wren <= 1'b0 ;
rx_wdata <= 8'd0 ;
end
end
/*************************************************
read length from fifo
*************************************************/
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
rx_len_rden <= 1'b0 ;
else if (state == LEN_LATCH && len_cnt == 2'd0)
rx_len_rden <= eth_100m_en | eth_10m_en ;
else
rx_len_rden <= 1'b0 ;
end
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
len_cnt <= 2'd0 ;
else if (state == LEN_LATCH)
len_cnt <= len_cnt + 1'b1 ;
else
len_cnt <= 2'd0 ;
end
//package total length
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
pack_len <= 16'd0 ;
else if (state == REC_WAIT)
pack_len <= rx_len/2 ;
end
/*************************************************
read data from fifo
*************************************************/
//read data counter
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
rx_data_cnt <= 16'd0 ;
else if (state == READ_FIFO)
rx_data_cnt <= rx_data_cnt + 1'b1 ;
else
rx_data_cnt <= 16'd0 ;
end
//read enable
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
rx_rden <= 1'b0 ;
else if (state == READ_FIFO)
rx_rden <= eth_100m_en | eth_10m_en ;
else
rx_rden <= 1'b0 ;
end
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
e10_100_rx_dv <= 1'b0 ;
else
e10_100_rx_dv <= rx_rden ;
end
assign e10_100_rxd = rx_rdata ;
eth_data_fifo rx_fifo
(
.clk (clk ), // input wire clk
.rst (~link ), // input wire rst
.din (rx_wdata ), // input wire [7 : 0] din
.wr_en (rx_wren ), // input wire wr_en
.rd_en (rx_rden ), // input wire rd_en
.dout (rx_rdata ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ), // output wire empty
.data_count ( ) // output wire [11 : 0] data_count
);
len_fifo rx_len_fifo
(
.clk (clk ), // input wire clk
.rst (~link ), // input wire rst
.din (rx_len_wdata ), // input wire [7 : 0] din
.wr_en (rx_len_wren ), // input wire wr_en
.rd_en (rx_len_rden ), // input wire rd_en
.dout (rx_len ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ), // output wire empty
.data_count (pack_num ) // output wire [11 : 0] data_count
);
endmodule
在发送时,也和接收数据时差不多,只不过把接收数据的处理方式反过来了。具体相关代码如下:
`timescale 1ns / 1ps
//
// Module Name: ethernet_test
//
module gmii_tx_buffer
(
input clk,
input rst_n,
input eth_10_100m_en, //ethernet 10M/100M enable
input link, //ethernet link signal
input gmii_tx_en, //gmii tx enable
input [7:0] gmii_txd, //gmii txd
output reg e10_100_tx_en, //ethernet 10/100M tx enable
output reg [7:0] e10_100_txd //ethernet 10/100M txd
);
(* MARK_DEBUG="true" *)reg [7:0] tx_wdata ; //tx data fifo write data
(* MARK_DEBUG="true" *)reg tx_wren ; //tx data fifo write enable
(* MARK_DEBUG="true" *)reg tx_rden ; //tx data fifo read enable
(* MARK_DEBUG="true" *)reg [15:0] tx_data_cnt ; //tx data counter
(* MARK_DEBUG="true" *)wire [7:0] tx_rdata ; //tx fifo read data
reg [16:0] pack_len ; //package length
reg tx_en ; //tx enable
reg [3:0] txd_high ; //high 4 bits
reg [3:0] txd_low ; //low 4 bits
reg tx_en_d0 ;
reg tx_en_d1 ;
reg [15:0] tx_len_cnt ; //tx length counter
reg gmii_tx_en_d0 ;
reg [1:0] len_cnt ; //length latch counter
wire [4:0] pack_num ; //length fifo usedw
reg tx_len_wren ; //length fifo wren
reg tx_len_rden ; //length fifo rden
wire [15:0] tx_len_wdata ; //length fifo write data
wire [15:0] tx_len ; //length fifo read data
localparam IDLE = 4'd0 ;
localparam CHECK_FIFO = 4'd1 ;
localparam LEN_LATCH = 4'd2 ;
localparam SEND_WAIT = 4'd3 ;
localparam SEND = 4'd4 ;
localparam SEND_WAIT_1 = 4'd5 ;
localparam SEND_END = 4'd6 ;
reg [3:0] state ;
reg [3:0] next_state ;
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
state <= IDLE ;
else
state <= next_state ;
end
always @(*)
begin
case(state)
IDLE :
begin
next_state <= CHECK_FIFO ;
end
CHECK_FIFO :
begin
if (pack_num > 5'd0) //check length fifo, if usedw > 0 ,there is a package in data fifo
next_state <= LEN_LATCH ;
else
next_state <= CHECK_FIFO ;
end
LEN_LATCH:
begin
if (len_cnt == 2'd3) //wait for read length fifo data
next_state <= SEND_WAIT ;
else
next_state <= LEN_LATCH ;
end
SEND_WAIT :
next_state <= SEND ;
SEND :
begin
if (tx_data_cnt == pack_len - 1) //read data fifo and send out
next_state <= SEND_WAIT_1 ;
else
next_state <= SEND ;
end
SEND_WAIT_1 :
begin
if (tx_data_cnt == pack_len + 1) //wait some clock for data latch
next_state <= SEND_END ;
else
next_state <= SEND_WAIT_1 ;
end
SEND_END :
next_state <= IDLE ;
default :
next_state <= IDLE ;
endcase
end
/*************************************************
write length to tx_len_fifo
*************************************************/
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
gmii_tx_en_d0 <= 1'b0 ;
else
gmii_tx_en_d0 <= gmii_tx_en ;
end
//write tx length to fifo when gmii_tx_en negedge
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
tx_len_wren <= 1'b0 ;
else if (gmii_tx_en == 1'b0 && gmii_tx_en_d0 == 1'b1)
tx_len_wren <= eth_10_100m_en ;
else
tx_len_wren <= 1'b0 ;
end
//calculate tx length
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
tx_len_cnt <= 16'd0 ;
else if (gmii_tx_en)
tx_len_cnt <= tx_len_cnt + 1'b1 ;
else if (tx_len_wren)
tx_len_cnt <= 16'd0 ;
end
assign tx_len_wdata = tx_len_cnt ; //write length data
/*************************************************
read length from tx_len_fifo
*************************************************/
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
tx_len_rden <= 1'b0 ;
else if (state == LEN_LATCH && len_cnt == 2'd0)
tx_len_rden <= eth_10_100m_en ;
else
tx_len_rden <= 1'b0 ;
end
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
len_cnt <= 2'd0 ;
else if (state == LEN_LATCH)
len_cnt <= len_cnt + 1'b1 ;
else
len_cnt <= 2'd0 ;
end
//package total length
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
pack_len <= 17'd0 ;
else if (state == SEND_WAIT)
pack_len <= 2*(tx_len) ;
end
//write data to tx_fifo
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
tx_wren <= 1'b0 ;
tx_wdata <= 8'd0 ;
end
else
begin
tx_wren <= gmii_tx_en & eth_10_100m_en ;
tx_wdata <= gmii_txd ;
end
end
/*************************************************
read tx_fifo
*************************************************/
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
tx_data_cnt <= 16'd0 ;
else if (state == SEND || state == SEND_WAIT_1)
tx_data_cnt <= tx_data_cnt + 1'b1 ;
else
tx_data_cnt <= 16'd0 ;
end
//read data enable
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
tx_rden <= 1'b0 ;
else if (state == SEND)
tx_rden <= ~tx_data_cnt[0] & eth_10_100m_en ;
else
tx_rden <= 1'b0 ;
end
//gmii tx enable
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
tx_en <= 1'b0 ;
else if (state == SEND)
tx_en <= 1'b1 ;
else
tx_en <= 1'b0 ;
end
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
tx_en_d0 <= 1'b0 ;
tx_en_d1 <= 1'b0 ;
end
else
begin
tx_en_d0 <= tx_en ;
tx_en_d1 <= tx_en_d0 ;
end
end
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
begin
txd_high <= 4'd0 ;
txd_low <= 4'd0 ;
end
else
begin
if (tx_data_cnt[0])
txd_high <= tx_rdata[7:4] ;
else
txd_low <= tx_rdata[3:0] ;
end
end
//ethernet 10/100M tx enable
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
e10_100_tx_en <= 1'b0 ;
else
e10_100_tx_en <= tx_en_d1 ;
end
always @(posedge clk or negedge rst_n)
begin
if (~rst_n)
e10_100_txd <= 8'd0 ;
else if (tx_data_cnt[0])
e10_100_txd <= {txd_low[3:0],txd_low[3:0]} ;
else
e10_100_txd <= {txd_high[3:0],txd_high[3:0]} ;
end
eth_data_fifo tx_fifo
(
.clk (clk ), // input wire clk
.rst (~link ), // input wire rst
.din (tx_wdata ), // input wire [7 : 0] din
.wr_en (tx_wren ), // input wire wr_en
.rd_en (tx_rden ), // input wire rd_en
.dout (tx_rdata ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ), // output wire empty
.data_count ( ) // output wire [11 : 0] data_count
);
len_fifo tx_len_fifo
(
.clk (clk ), // input wire clk
.rst (~link ), // input wire rst
.din (tx_len_wdata ), // input wire [7 : 0] din
.wr_en (tx_len_wren ), // input wire wr_en
.rd_en (tx_len_rden ), // input wire rd_en
.dout (tx_len ), // output wire [7 : 0] dout
.full ( ), // output wire full
.empty ( ), // output wire empty
.data_count (pack_num ) // output wire [11 : 0] data_count
);
endmodule