Menu Close

MAC层发送 Verilog 工程(网络综合设计)

在网络综合设计中,IP层发送模块中将数据写入MAC层发送的FIFO中。MAC层发送模块需要将payload数据打成完整的以太网数据包后,以8-bit的数据形式伴随着数据使能,根据PHY层的网速发给PHY。本文将实现MAC发送模块。

1.MAC层发送需要实现的任务

MAC层发送模块需要完成的任务如下:

  • 从18 in/9 out FIFO中读出IP层传递过来的数据包(判断是否需要加上Padding)
  • 加上CRC校检
  • 将处理过的数据传递进8/8的乒乓buffer,为之后的数据处理提供更高的效率
  • 加上前导字(preamble),以及在数据包和数据包之间的GAP,将数据包按照检测出的网速(MII和RGMII/GMII分开)发送给PHY层

2.MAC层发送部分的框图结构

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

图1 网络综合实验框图

图2所示为MAC层的发送(eth_mac_tx.v)。

图2 eth_mac_tx.v

3.MAC层发送设计

MAC层发送的代码中主要有tx_ip_fifo_st和mac_tx_st两个状态机。tx_ip_fifo_st读从IP层发送过来的FIFO数据(判断是否需要加上padding),并将其加上CRC后,写入A/B Buffer。mac_tx_st读出A/B Buffer里的数据,根据MII的类型(MII/RGMII等)分开发送前导字和数据,最后加上数据包之间的gap。之所以需要分开MII的类型发送前导字和数据,是因为在一个时钟下MII无法发送1个字节,而RGMII可以在一个时钟下发送1个字节。

tx_ip_fifo_st:

图3所示为tx_ip_fifo_st状态机的简易流程图(注意省略了状态转移条件不满足时,停留在当前状态的箭头)。

  • 状态0和状态1指定了读A/B Buffer的地址,因为A/B Buffer是双端口RAM,需要等待两个时钟周期才能成功读出数据
  • 状态2判断读出的标志,指示当前A/B Buffer是否可写
  • 状态3和状态4将FIFO中存储的数据长度读出来
  • 状态5将FIFO中的数据读出,写入A/B Buffer
  • 状态6可根据上层接口调整(这里也可以省略)
  • 状态7检查不加CRC的数据包长度是否大于60,如果数据包长度小于60,填充数据0,直到数据包长度达到60
  • 状态8,状态9,状态10,状态11在数据包末尾加上CRC
  • 状态12和13将数据总长度,A/B Buffer可读的标志写进A/B Buffer
  • 状态14和状态15转到A/B Buffer另一侧,重新准备写入数据

 

图3 tx_ip_fifo_st流程图

 

因为FIFO是18 in/9 out,Xilinx FIFO IP不等宽的端口读写数据的顺序如图4和图5所示:

图4

图5

4.MAC层发送Verilog代码

MAC层发送,eth_mac_tx.v的代码如下(这里省略了CRC32生成代码,详情见文章互联网MAC层CRC32 生成器):

`timescale 1ns / 1ps


module eth_mac_tx #
(
    parameter MAC_TX_IFG     = 12,   //inter-frame gap
    parameter MAC_TX_CRC_EN  = 1,
    
    parameter MAC_TXBUF_SIZE = 11,
    parameter MAC_TXIDX_BIT  = 1
)
(
    input  sys_clk,

    input  [17:0] ip_fifo_din,
    input  ip_fifo_we,
    output [15:0] ip_fifo_cnt,
    
    input  mac_tx_clk,
    output reg [7:0] mac_tx_data = 0,
    output reg [1:0] mac_tx_en = 0,

    input  [2:0] eth_speed,
    input  reset
);
//=============== FIFO related signals ===============================
wire [8:0] ip_fifo_dout;
wire ip_fifo_valid;
reg  ip_fifo_rd = 0;
wire ip_fifo_full;
wire ip_fifo_empty;
TX_IP_FIFO   TX_IP_FIFO_inst
(
    .wr_clk (sys_clk),              // input wire wr_clk
    .din    (ip_fifo_din),          // input wire [17 : 0] din
    .wr_en  (ip_fifo_we),           // input wire wr_en
    .full   (ip_fifo_full),         // output wire full

    .rd_clk (sys_clk),              // input wire rd_clk
    .rd_en  (ip_fifo_rd),           // input wire rd_en
    .dout   (ip_fifo_dout),         // output wire [8 : 0] dout
    .empty  (ip_fifo_empty),        // output wire empty

    .valid  (ip_fifo_valid),        // output wire valid
    .wr_data_count(ip_fifo_cnt),    // output wire [10 : 0] wr_data_count

    .rst    (reset)                 // input wire rst
);
//=========================================================================
reg  ip_tx_wea = 0;
wire [MAC_TXBUF_SIZE + MAC_TXIDX_BIT - 1:0] ip_tx_addra;
reg  [7:0] ip_tx_dina;
wire [7:0] ip_tx_douta;

reg  [MAC_TXBUF_SIZE - 1:0] ip_tx_addra_sub = 0;
reg  [MAC_TXIDX_BIT - 1:0] ip_tx_ida = 0;
assign ip_tx_addra = {ip_tx_ida, ip_tx_addra_sub};
//=========================================================================
reg crc_init = 0;
reg crc_rd = 0;
reg crc_wr = 0;   //date enable
wire [7:0] crc_out;     //CRC result 
wire crc_end;
reg [15:0] fifo_cnt = 0;
reg [15:0] fifo_len = 0;
reg [3:0] tx_ip_fifo_st = 0;
always @ (posedge sys_clk)
if(reset)
begin
    ip_tx_wea <= 0;
    ip_tx_ida <= 0;
    ip_fifo_rd <= 0;
    tx_ip_fifo_st <= 0;
end
else case(tx_ip_fifo_st)
0:
begin
    crc_init <= 1;      //initialize the CRC
    crc_rd <= 0;
    crc_wr <= 0;
    fifo_cnt <= 0;
    fifo_len <= 0;
    
    ip_tx_addra_sub <= {MAC_TXBUF_SIZE{1'b1}};          //addr 2047
    ip_tx_wea <= 0;
    ip_fifo_rd <= 0;

    tx_ip_fifo_st <= 1;
end
1:
begin
    crc_init <= 0;
    ip_tx_addra_sub <= {MAC_TXBUF_SIZE{1'b1}};          //addr 2047
    tx_ip_fifo_st <= 2;
end
2:    //read from A/B Buffer, for address 2047, read after 2 clocks
begin
    if(ip_tx_douta[7] == 0)     //indicates A/B buffer is available to write
    begin
        ip_fifo_rd <= 1;        //read data from FIFO
        tx_ip_fifo_st <= 3;
    end
end
3:
begin
    if(ip_fifo_valid)
    begin
        if(ip_fifo_dout[8] == 0)      // normal data
        begin
            fifo_len[15:8] <= ip_fifo_dout[7:0];     //read packet length upper 8 bits from FIFO, big-endian
            tx_ip_fifo_st <= 4;
        end
    end
end
4:
begin   
    if(ip_fifo_valid)
    begin
        if(ip_fifo_dout[8] == 0)     // normal data
        begin
            fifo_len[7:0] <= ip_fifo_dout[7:0];     //read packet length lower 8 bits from FIFO
            tx_ip_fifo_st <= 5;
        end
    end
end
5:
begin
    ip_tx_wea <= 0;
    crc_wr <= 0;
    if(ip_fifo_valid)
    begin
        ip_tx_dina <= ip_fifo_dout[7:0]; //write into buffer, and calculate CRC
        
        if(ip_fifo_dout[8])      //end packet segment
        begin
            ip_tx_wea <= 0;
            crc_wr <= 0;
            ip_fifo_rd <= 0;
            tx_ip_fifo_st <= 6;
        end
        else
        begin
            if(fifo_len == fifo_cnt)     // finished reading current packet
            begin
                ip_tx_wea <= 0;
                crc_wr <= 0;
            end
            else          //write into A/B Buffer
            begin
                ip_tx_addra_sub <= ip_tx_addra_sub + 1;      //move up the address
                fifo_cnt <= fifo_cnt + 1;        //count data read from FIFO           
                ip_tx_wea <= 1;
                crc_wr <= 1;
            end
        end
    end
end
6:
begin
    tx_ip_fifo_st <= 7;   
end
7:
begin
    if(fifo_cnt < 60)         //if packet length(excluding CRC) < 60
    begin                     // padding with 0
        fifo_cnt <= fifo_cnt + 1;    
        ip_tx_addra_sub <= ip_tx_addra_sub + 1;
        ip_tx_dina <= 0;       //data 0
        ip_tx_wea <= 1;
        crc_wr <= 1;
    end
    else     //packet length okay
    begin
        ip_tx_wea <= 0;
        crc_wr <= 0;
        crc_rd <= 1;      //enable CRC
        tx_ip_fifo_st <= 8;
    end
end
8:     //state 8 - 11, fill 4 bytes CRC
begin
    fifo_cnt <= fifo_cnt + 1;
    ip_tx_addra_sub <= ip_tx_addra_sub + 1;
    ip_tx_dina <= crc_out;       // fill CRC
    ip_tx_wea <= 1;

    tx_ip_fifo_st <= 9;
end
9:
begin
    fifo_cnt <= fifo_cnt + 1;
    ip_tx_addra_sub <= ip_tx_addra_sub + 1;
    ip_tx_dina <= crc_out;      // fill CRC
    ip_tx_wea <= 1;

    tx_ip_fifo_st <= 10;
end
10:
begin
    fifo_cnt <= fifo_cnt + 1;
    ip_tx_addra_sub <= ip_tx_addra_sub + 1;
    ip_tx_dina <= crc_out;      // fill CRC
    ip_tx_wea <= 1;
    
    tx_ip_fifo_st <= 11;
end
11:
begin
    fifo_cnt <= fifo_cnt + 1;
    ip_tx_addra_sub <= ip_tx_addra_sub + 1;
    ip_tx_dina <= crc_out;      // fill CRC
    ip_tx_wea <= 1;

    crc_rd <= 0;            //disable CRC
    tx_ip_fifo_st <= 12;
end
12:
begin
    ip_tx_addra_sub <= {{(MAC_TXBUF_SIZE- 1){1'b1}}, 1'b0};      //addr 2046 {10'b1,1'b0}
    ip_tx_dina <= fifo_cnt[7:0];                  //packet size

    ip_tx_wea <= 1;
    tx_ip_fifo_st <= 13;
end
13:
begin
    ip_tx_addra_sub <= {MAC_TXBUF_SIZE{1'b1}};      // addr 2047
    ip_tx_dina <= {4'b1000, fifo_cnt[11:8]};        //flag and the packet size following bit

    ip_tx_wea <= 1;
    tx_ip_fifo_st <= 14;
end
14:
begin
    ip_tx_addra_sub <= {MAC_TXBUF_SIZE{1'b1}};          //addr 2047

    ip_tx_wea <= 0;
    tx_ip_fifo_st <= 15;
end
15:
begin
    ip_tx_addra_sub <= {MAC_TXBUF_SIZE{1'b1}};          //addr 2047
    ip_tx_wea <= 0;
    ip_tx_ida <= ip_tx_ida + 1;         //proceed to next buffer
    
    tx_ip_fifo_st <= 0;
end
default:     tx_ip_fifo_st <= 0;
endcase
//======================== CRC related signals ===========================
CRC_gen  CRC_gen_inst
(
    .clk        (sys_clk),
    .init       (crc_init),
    .frame_data (ip_tx_dina),
    .data_en    (crc_wr),
    .crc_rd     (crc_rd),

    .crc_out    (crc_out),
    .crc_end    (crc_end),

    .rst        (reset)
);
//===================== A/B Buffer related signals ====================

reg  ip_tx_web = 0;
wire [7:0] ip_tx_doutb;

reg  [MAC_TXBUF_SIZE - 1:0] ip_tx_addrb_sub = 0;
reg  [MAC_TXIDX_BIT - 1:0] ip_tx_idb = 0;
wire [MAC_TXBUF_SIZE + MAC_TXIDX_BIT - 1:0] ip_tx_addrb = {ip_tx_idb, ip_tx_addrb_sub};
//============================ A/B Buffer =============================  
MAC_TX_BRAM   MAC_TX_BRAM_inst
(
    .clka   (sys_clk),      // input wire clka
    .wea    (ip_tx_wea),    // input wire [0 : 0] wea
    .addra  (ip_tx_addra),  // input wire [11 : 0] addra
    .dina   (ip_tx_dina),   // input wire [7 : 0] dina
    .douta  (ip_tx_douta),  // output wire [7 : 0] douta
    
    .clkb   (mac_tx_clk),   // input wire clkb
    .web    (ip_tx_web),    // input wire [0 : 0] web
    .addrb  (ip_tx_addrb),  // input wire [11 : 0] addrb
    .dinb   (0),            // input wire [7 : 0] dinb
    .doutb  (ip_tx_doutb)   // output wire [7 : 0] doutb
);
//=======================================================================   
//decide MII or RGMII
reg eth_nibble = 0; 
always @ (posedge mac_tx_clk or posedge reset)
if(reset)
begin
    eth_nibble <= 0;
end 
else
begin
    if(eth_speed == 1 || eth_speed == 3) // 2.5m, 25m MII
        eth_nibble <= 1;
    else // RGMII or GMII
        eth_nibble <= 0;
end
//=========== prepare the preamble register ==================
reg [7:0] eth_preamble [0:7];
initial
begin
    eth_preamble [0] = 8'h55;
    eth_preamble [1] = 8'h55;
    eth_preamble [2] = 8'h55;
    eth_preamble [3] = 8'h55;
    eth_preamble [4] = 8'h55;
    eth_preamble [5] = 8'h55;
    eth_preamble [6] = 8'h55;
    eth_preamble [7] = 8'hd5;
end

//===============================================================
    
reg  [15:0] mac_tx_len = 0;
reg  [15:0] mac_tx_cnt = 0;
(* mark_debug = "true" *)reg  [3:0] mac_tx_st = 0;
always @ (posedge mac_tx_clk or posedge reset)
if(reset)
begin
    mac_tx_en <= 0;
    ip_tx_idb <= 0;
    ip_tx_web <= 0;
    mac_tx_st <= 0;
end 
else case (mac_tx_st)
0:
begin
    mac_tx_en <= 0;
    mac_tx_cnt <= 0;
    ip_tx_web <= 0;
    ip_tx_addrb_sub <= {MAC_TXBUF_SIZE{1'b1}};      // addr 2047
    
    mac_tx_st <= 1;
end
1:
begin
    ip_tx_addrb_sub <= {MAC_TXBUF_SIZE{1'b1}};      // addr 2047
    mac_tx_st <= 2;
end
2:     //read from A/B Buffer, for address 2047, read after 2 clocks
begin
    if(ip_tx_doutb[7] == 1)   //indicates A/B buffer is available to read
    begin
        ip_tx_addrb_sub <= {{(MAC_TXBUF_SIZE - 1){1'b1}}, 1'b0};    //addr 2046 {10'b1,1'b0}
        mac_tx_st <= 3;
    end
end
3:
begin
    mac_tx_len[15:8] <= {4'b0000, ip_tx_doutb[3:0]};          //read packet length upper bits from A/B Buffer
    mac_tx_st <= 4;
end
4:
begin
    mac_tx_len[7:0] <= ip_tx_doutb;          //read packet length lower bits from A/B Buffer
    ip_tx_addrb_sub <= 0;
    
    if(eth_nibble)     //MII
        mac_tx_st <= 5;
    else               //RGMII or GMII
        mac_tx_st <= 9;
end

// MII transmit
5: // state 5 - 6 MII transmit preamble
begin
    mac_tx_cnt <= mac_tx_cnt + 1;
    mac_tx_data <= eth_preamble[mac_tx_cnt];
    mac_tx_en <= 2'b11;
    mac_tx_st <= 6;
end
6: // state 5 - 6 MII transmit preamble
begin
    mac_tx_en <= 2'b10;
    
    if(mac_tx_cnt == 8)    //finished sending preamble
    begin    
        mac_tx_cnt <= 0;
        mac_tx_st <= 7;
    end
    else 
        mac_tx_st <= 5;
end
7: // state 7 - 8 MII transmit data
begin
    mac_tx_cnt <= mac_tx_cnt + 1;
    ip_tx_addrb_sub <= ip_tx_addrb_sub + 1;
    mac_tx_data <= ip_tx_doutb;
    mac_tx_en <= 2'b11;
    mac_tx_st <= 8;
end
8: // state 7 - 8 MII transmit data
begin
    mac_tx_en <= 2'b10;
    
    if(mac_tx_cnt == mac_tx_len)    //finished sending data
    begin
        mac_tx_cnt <= 1;
        mac_tx_st <= 11;
    end
    else mac_tx_st <= 7;
end

// RGMII transmit
9: // RGMII transmit preamble
begin
    mac_tx_en <= 2'b11;
    if(mac_tx_cnt == 6)
    begin
        mac_tx_data <= eth_preamble[7];      //finished sending preamble
        mac_tx_cnt <= 0;
        
        ip_tx_addrb_sub <= ip_tx_addrb_sub + 1;
        mac_tx_st <= 10;
    end
    else      //keep sending following preamble
    begin
        mac_tx_data <= eth_preamble[mac_tx_cnt];
        mac_tx_cnt <= mac_tx_cnt + 1;
    end
end

10: //RGMII transmit data
begin
    if(mac_tx_cnt == mac_tx_len)       //finished sending data
    begin
        mac_tx_cnt <= 1;
        mac_tx_en <= 0;
        mac_tx_st <= 11;
    end
    else
    begin
        mac_tx_cnt <= mac_tx_cnt + 1;
        ip_tx_addrb_sub <= ip_tx_addrb_sub + 1;
        mac_tx_data <= ip_tx_doutb;
        mac_tx_en <= 2'b11;
    end
end
11:    //gap 12 clock
begin
    mac_tx_en <= 0;
    mac_tx_cnt <= mac_tx_cnt + 1;

    ip_tx_addrb_sub <= {MAC_TXBUF_SIZE{1'b1}};    //addr 2047
    ip_tx_web <= 1;

    mac_tx_st <= 12;
end
12:
begin
    mac_tx_cnt <= mac_tx_cnt + 1;

    ip_tx_addrb_sub <= {MAC_TXBUF_SIZE{1'b1}};    //addr 2047
    ip_tx_web <= 0;

    if(mac_tx_cnt == (MAC_TX_IFG << nibble) )    // packet gap
    begin
        ip_tx_idb <= ip_tx_idb + 1;
        mac_tx_st <= 0;
    end
end

default: mac_tx_st <= 0;
endcase

//=======================================================================    

endmodule

 

mac_tx_st:

图6所示为mac_tx_st状态机的简易流程图(注意省略了状态转移条件不满足时,停留在当前状态的箭头)。

  • 状态0和状态1指定了读A/B Buffer的地址,因为A/B Buffer是双端口RAM,需要等待两个时钟周期才能成功读出数据
  • 状态2判断读出的标志,指示当前A/B Buffer是否可读
  • 状态3和状态4将A/B Buffer中存储的数据长度读出来,并且状态4将MII和RGMII/GMII分开
  • 状态5和状态6发送MII的前导字
  • 状态7和状态8发送MII的数据
  • 状态9发送RGMII/GMII的前导字
  • 状态10发送RGMII/GMII的数据
  • 状态11和状态12发送数据包之间的gap
  • 状态12转到A/B Buffer另一侧,重新准备读数据

 

图6 mac_tx_st流程图

关于模块的详细解释请参见对应的视频。

Posted in FPGA, FPGA 教材教案, Verilog, Verilog, Vivado, Vivado, 开发工具, 开发工具, 开发语言, 教材与教案, 文章, 编程语言

发表回复

相关链接