图1所示为网络综合实验框图,其中红框部分是MAC层的接收(eth_mac_rx.v)。

图1 网络综合实验框图
图2所示为MAC层的接收(eth_mac_rx.v)。

图2 eth_mac_rx.v
MAC层的接收(eth_mac_rx.v)流程有以下的步骤:
- 接收PHY传递过来的数据包:
- 判断并拆除前导字(preamble)
- 判断数据包的大小是否满足IEEE 802.3的定义(数据包长度小于1518,且大于64个字节)
- 判断数据是否有效
- 判断数据包之间的GAP是否至少为9
- 将处理过的数据递进FIFO,注意FIFO里的数据是9-bit,低位8-bit是传输的数据,最高位bit的作用是隔断数据包和标记出错误的数据包。
- 计算数据包的CRC
- 将处理过的数据传递进8/16的AB buffer,为之后的数据处理提供更高的效率
注意,由于MAC层的接收比较复杂,所以专门创建了一个mac_error的寄存器,每一个bit的定义如下:
- mac_error[0] : 以太网数据包包头错误
- mac_error[1] : FIFO满
- mac_error[2] : 以太网数据包长度大于1518字节
- mac_error[3] : 以太网数据包长度小于64字节
- mac_error[4] : 以太网数据包 gap 错误
- mac_error[5] : 保留
- mac_error[6] : 保留
- mac_error[7] : 以太网数据包CRC错误
eth_mac_rx.v的代码如下(这里省略了CRC模块代码,详情可见文章互联网MAC层CRC32 Check Verilog 代码):
`timescale 1ns / 1ps
module eth_mac_rx #
(
parameter IP_BUF_SIZE = 10,
parameter IP_IDX_BIT = 1
)
(
input mac_rx_clk,
input [7:0] mac_rx_data,
input mac_rx_toggle,
input mac_rx_dv,
input sys_clk,
//used by A/B buffer
input ip_rx_web,
input [IP_BUF_SIZE + IP_IDX_BIT - 1:0] ip_rx_addrb,
output [15:0] ip_rx_doutb,
//mac_error register
output reg [7:0] mac_error = 0,
input reset
);
//====================FIFO SIGNALS=======================================
reg [8:0] eth_rx_fifo_din = 0;
reg eth_rx_fifo_we = 0;
wire eth_rx_fifo_full;
//=======================================================================
reg [7*8 - 1 : 0] chk_pream = 0; //used to check the preamble
reg [4:0] rx_mac_err = 0; //lower 5 bits of mac_error register
reg [2:0] rx_mac_st = 0; //state machine
reg [10:0] rx_cnt = 0; //count
always @ (posedge mac_rx_clk or posedge reset)
if(reset)
begin
rx_cnt <= 0;
rx_mac_st <= 0;
end
else case (rx_mac_st)
0:
begin
rx_mac_err <= 0;
chk_pream <= 0;
eth_rx_fifo_din <= 0;
eth_rx_fifo_we <= 0;
rx_cnt <= rx_cnt + 1; //hold for fifo reset
if(&rx_cnt)
begin
rx_mac_st <= 1;
end
end
1:
begin
rx_mac_err <= 0;
eth_rx_fifo_we <= 0;
rx_cnt <= 0;
if(mac_rx_dv)
begin
if(mac_rx_toggle)
begin
chk_pream <= {chk_pream[6*8 - 1: 0] , mac_rx_data}; //check the preamble
rx_mac_st <= 2;
end
end
else chk_pream <= 0;
end
2:
begin
if(mac_rx_dv)
begin
if(mac_rx_toggle)
begin
chk_pream <= {chk_pream[6*8 - 1: 0] , mac_rx_data}; //check the preamble
if({chk_pream[6*8 - 1: 0] , mac_rx_data} == {8'h55,8'h55,8'h55,8'h55,8'h55,8'h55,8'hd5})
rx_mac_st <= 3;
end
end
else //if the preamble is not correct, flag the mac_error[0] ===> eth packet header error
begin
rx_mac_err[0] <= 1;
rx_mac_st <= 7;
end
end
3:
begin
chk_pream <= 0; //reset the preamble check, so that the following packet header could be checked again
if(eth_rx_fifo_full) //if fifo is full, flag the mac_error[1] ===> fifo full error
begin
eth_rx_fifo_we <= 0;
rx_mac_err[1] <= 1;
rx_mac_st <= 7; // fifo full error
end
else //if fifo is not full, fill the fifo with eth packet
begin
if(mac_rx_dv)
begin
if(mac_rx_toggle)
begin
eth_rx_fifo_din <= {1'b0, mac_rx_data};
eth_rx_fifo_we <= 1;
rx_cnt <= rx_cnt + 1; //count the eth packet size
if(rx_cnt == 1518) //if excluding preamble, eth packet max length is 1518 bytes
begin
rx_mac_err[2] <= 1; // if packet > 1518, flag the mac_error[2] ===> eth packet > 1518
rx_mac_st <= 7; //handle the error
end
end
else eth_rx_fifo_we <= 0;
end
else //rx_err or packet end
begin
eth_rx_fifo_we <= 0;
rx_mac_st <= 4;
end
end
end
4:
begin
if(eth_rx_fifo_full || (rx_cnt < 64 ) ) //if fifo full or eth packet length smaller than the min length
begin
eth_rx_fifo_we <= 0;
rx_mac_err[1] <= eth_rx_fifo_full; //if fifo is full, flag the mac_error[1] ===> fifo full error
rx_mac_err[3] <= (rx_cnt < 64 ) ? 1'b1 : 1'b0; //if eth packet < 64, flag the mac_error[3] ===> eth packet < 64
rx_mac_st <= 7; //handle the error
end
else //error or packet end
begin
if(!mac_rx_dv)
begin
rx_cnt <= 2;
eth_rx_fifo_we <= 0;
rx_mac_st <= 5; //packet end
end
else
begin
rx_mac_err[4] <= 1; //flag the mac_error[4] ===> eth packet gap error
rx_mac_st <= 7; //handle the error
end
end
end
5: //packet end
begin
if(eth_rx_fifo_full) //if fifo is full, flag the mac_error[1] ===> fifo full error
begin
eth_rx_fifo_we <= 0;
rx_mac_err[1] <= 1;
rx_mac_st <= 7; // handle the error
end
else
begin
if(!mac_rx_dv)
begin
rx_cnt <= rx_cnt + 1; //count the gap between eth packets
if(rx_cnt == 9) // ifg(interframe gap) ifg >= 9
begin
eth_rx_fifo_din <= {1'b1, 8'hff}; // normal end flag
eth_rx_fifo_we <= 1;
rx_mac_st <= 1; //ready to receive new eth packet
end
end
else //if the eth packet gap is not enough
begin
rx_mac_err[4] <= 1; //flag the mac_error[4] ===> eth packet gap error
rx_mac_st <= 7; // handle the error
end
end
end
7: // handle the error flag
begin
chk_pream <= 0; //reset the registers
rx_cnt <= 0;
if(!eth_rx_fifo_full)
begin
eth_rx_fifo_din <= {1'b1, 8'h00}; // send error end flag
eth_rx_fifo_we <= 1;
rx_mac_st <= 1; //ready to receive new eth packet
end
else //if fifo is full, wait here
eth_rx_fifo_we <= 0;
end
default: rx_mac_st <= 1;
endcase
//======================= FIFO related signals ==========================
wire [11:0] eth_rx_fifo_cnt;
wire [8:0] eth_rx_fifo_dout;
reg eth_rx_fifo_rd = 0;
wire eth_rx_fifo_vld;
wire eth_rx_fifo_empty;
ETH_RX_FIFO ETH_RX_FIFO_inst
(
.wr_clk (mac_rx_clk), // input wire wr_clk
.din (eth_rx_fifo_din), // input wire [8 : 0] din
.wr_en (eth_rx_fifo_we), // input wire wr_en
.full (eth_rx_fifo_full), // output wire full
.rd_clk (sys_clk), // input wire rd_clk
.rd_en (eth_rx_fifo_rd), // input wire rd_en
.valid (eth_rx_fifo_vld), // output wire valid
.dout (eth_rx_fifo_dout), // output wire [8 : 0] dout
.empty (eth_rx_fifo_empty), // output wire empty
.rd_data_count(eth_rx_fifo_cnt),// output wire [11 : 0] wr_data_count
.rst (reset) // input wire rst
);
//=====================A/B Buffer related signals======================================
localparam MAC_RXBUF_SIZE = IP_BUF_SIZE + 1; //here 11, address can be up to 2048
localparam MAC_RXIDX_BIT = IP_IDX_BIT; //here 1
reg ip_rx_wea = 0;
reg [MAC_RXBUF_SIZE - 1:0] ip_rx_addra_sub = 0;
reg [MAC_RXIDX_BIT - 1:0] ip_rx_ida = 0;
// size is related to ip_rx_addrb
wire [MAC_RXBUF_SIZE + MAC_RXIDX_BIT - 1:0] ip_rx_addra = {ip_rx_ida, ip_rx_addra_sub};
reg [7:0] ip_rx_dina = 0;
wire [7:0] ip_rx_douta;
//=======================================================================
reg crc_err_flag = 0;
wire crc_err; //output from CRC module
reg crc_init = 0;
reg crc_en = 0; //used to enable CRC
reg [2:0] rx_fifo_st = 0; //state machine
reg [15:0] rx_fifo_cnt = 0;
always @ (posedge sys_clk)
if(reset)
begin
ip_rx_ida <= 0;
ip_rx_wea <= 0;
eth_rx_fifo_rd <= 0;
rx_fifo_st <= 0;
end
else case (rx_fifo_st)
0:
begin
crc_err_flag <= 0;
crc_en <= 0;
rx_fifo_cnt <= 0;
eth_rx_fifo_rd <= 0;
ip_rx_wea <= 0;
ip_rx_addra_sub <= {MAC_RXBUF_SIZE{1'b1}}; //addr 2047
rx_fifo_st <= 1;
end
1:
begin
crc_init <= 1; //initialize the CRC
ip_rx_wea <= 0;
ip_rx_addra_sub <= {MAC_RXBUF_SIZE{1'b1}};
rx_fifo_st <= 2;
end
2: //read from A/B Buffer, for address 2047, read after 2 clocks
begin
crc_init <= 0;
if(ip_rx_douta[7] == 0) //indicates A/B buffer is available to write
rx_fifo_st <= 3;
end
3:
begin
ip_rx_wea <= 0;
eth_rx_fifo_rd <= 1; //read data from FIFO
if(eth_rx_fifo_vld)
begin
if(eth_rx_fifo_dout[8] == 0) // normal data
begin
ip_rx_addra_sub <= ip_rx_addra_sub + 1; //move up the address, write 8 bits every time from port A
ip_rx_dina <= eth_rx_fifo_dout[7:0];
rx_fifo_cnt <= rx_fifo_cnt + 1; //count the data read from FIFO
crc_en <= 1;
ip_rx_wea <= 1;
end
else //eth_rx_fifo_dout[8] == 1, ===> end packet segment
begin
eth_rx_fifo_rd <= 0;
crc_en <= 0;
ip_rx_wea <= 0;
if(eth_rx_fifo_dout[7:0] == 8'hff) // packet end
rx_fifo_st <= 4; //proceed to next step
else // eth_rx_fifo_dout[7:0] == 8'h00, ===> mac packet error
begin
rx_fifo_st <= 0;
end
end
end
end
4:
begin
if(crc_err) //if CRC error, restart
begin
crc_err_flag <= 1;
rx_fifo_st <= 0;
end
else //proceed to next step
begin
rx_fifo_cnt <= rx_fifo_cnt - 4; // ip packet length = mac packet length - 4(crc) FCS
rx_fifo_st <= 5;
end
end
5:
begin
crc_err_flag <= 0;
ip_rx_addra_sub <= {{(MAC_RXBUF_SIZE-1){1'b1}}, 1'b0}; //addr 2046 {10'b1,1'b0}
ip_rx_dina <= rx_fifo_cnt[7:0]; //packet size
ip_rx_wea <= 1;
rx_fifo_st <= 6;
end
6:
begin
ip_rx_addra_sub <= {MAC_RXBUF_SIZE{1'b1}}; // addr 2047
ip_rx_dina <= {4'b1000, rx_fifo_cnt[11:8]}; //flag and the packet size following bit
ip_rx_wea <= 1;
rx_fifo_st <= 7;
end
7:
begin
ip_rx_ida <= ip_rx_ida + 1; //proceed to next buffer
ip_rx_wea <= 0;
rx_fifo_st <= 0;
end
default: rx_fifo_st <= 0;
endcase
//======================= CRC related signals =========================
wire crc_enable = crc_en & eth_rx_fifo_vld;
CRC_chk CRC_chk_inst
(
.clk (sys_clk),
// .crc_data (eth_rx_fifo_dout[7:0]),
.crc_data (ip_rx_dina),
.crc_init (crc_init),
.crc_en (crc_enable),
.crc_chk_en (1'b1),
.crc_err (crc_err),
.rst (reset)
);
//======================== A/B Buffer =================================
NET_DRAM IP_RX_DRAM_inst
(
.clka (sys_clk), // input wire clka
.wea (ip_rx_wea), // input wire [0 : 0] wea
.addra (ip_rx_addra), // input wire [11 : 0] addra
.dina (ip_rx_dina), // input wire [7 : 0] dina
.douta (ip_rx_douta), // output wire [7 : 0] douta
.clkb (sys_clk), // input wire clkb
.web (ip_rx_web), // input wire [0 : 0] web
.addrb (ip_rx_addrb), // input wire [10 : 0] addrb
.dinb (0), // input wire [15 : 0] dinb
.doutb (ip_rx_doutb) // output wire [15 : 0] doutb
);
//======================================================================
// save the mac_error register
always @ (posedge sys_clk)
mac_error <= {crc_err_flag, 2'b00, rx_mac_err};
endmodule
其中有rx_mac_st和rx_fifo_st两个状态机。rx_mac_st用来处理PHY层发过来的数据包,并将提取出来的数据送进FIFO。rx_fifo_st是将FIFO里的值写入A/B Buffer。接下来对这两个状态机进行分析如下:
rx_mac_st:
图3所示为rx_mac_st状态机的简易状态转移图(注意省略了状态转移条件不满足时,停留在当前状态的箭头)。
- 状态0是复位状态
- 状态1开始接收数据包
- 状态2负责检查数据包的前导字
- 状态3将提取出的数据写进FIFO
- 转换4和状态5负责处理数据包快结束的时候可能会出现的错误,比如FIFO已经满,数据包长度不符合IEEE 802.3的标准等
- 状态7用来处理在状态2,3,4,5中所有可能出现的错误,并在FIFO封包时将错误标志立起来

图3 rx_mac_st流程图
rx_fifo_st:
图4所示为rx_fifo_st状态机的简易状态转移图(注意省略了状态转移条件不满足时,停留在当前状态的箭头)。
- 状态0和状态1指定了读A/B Buffer的地址,因为A/B Buffer是双端口RAM,需要等待两个时钟周期才能成功读出数据
- 状态2判断读出的标志,指示当前A/B Buffer是否可写
- 状态3将FIFO的数据读出,写进A/B Buffer
- 状态4判断CRC
- 状态5和6将数据总长度,A/B Buffer可读的标志写进A/B Buffer
- 状态7转到A/B Buffer另一侧,重新准备写入数据

图4 rx_fifo_st流程图
老师,rx_mac_st状态机中,发生错误就转到状态7,但状态7没有马上把rx_mac_err清零,这里是不是清零好一点?
都是可以的, 关键时rx_mac_err = 1 持续几个rx_clk。 rx_mac_err 是用来提供错误信息的。 可以根据自己的需要,更改错误信号持续的时间