[米联客-安路飞龙DR1-FPSOC] UDP通信篇连载-02 MAC层程序设计

news2024/9/21 16:38:49

软件版本:Anlogic -TD5.9.1-DR1_ES1.1

操作系统:WIN10 64bit

硬件平台:适用安路(Anlogic)FPGA

实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板

板卡获取平台:https://milianke.tmall.com/

登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

目录

​3.1 MAC层

3.1.1 MAC接收模块

3.1.2 MAC发送模块

3.1.3 CRC校验模块

3.1.4 PAUSE帧流控制模块


前面我们介绍了以太网的基本概念,及涉及的各层协议格式,接下来我们通过设计Verilog程序来实现以太网各个子层的功能。程序整体架构图如下:

3.1 MAC层

MAC层一边连接GMII接口,一边连接上层协议传来的数据。该层将上层传来的数据包组帧发送出去,或者将接收到的数据帧解析,将信息和拆解出来的数据包传给上层协议。

3.1.1 MAC接收模块

MAC接收模块主要实现以下几个功能:

(1)对接收到的MAC帧进行解析,过滤前导码、帧起始定界符、MAC地址、类型、CRC校验位,将上层数据包提取出来,并缓存数据包类型,传给上层做判断。

(2)对每帧数据进行CRC校验,与帧末尾的4字节校验位做比较,判断数据的正确性

(3)识别接收到的流控帧,将有效信息发送给子层解析,把解析到的暂停时间和源MAC地址输出至MAC接收模块做进一步处理。

(4)通过FIFO完成PHY接收时钟和用户接口时钟之间的时钟域的转换,并将数据包输出至上层。

 /*******************************uimac_rx模块*********************
--以下是米联客设计的uimac_rx模块
--本模块主要有以下几个功能
--1. 从外部PHY芯片接收mac帧,解析mac帧首部,进行mac地址过滤和帧类型过滤。
--2. 内部子模块crc32_check对每帧数据进行crc32值的计算,判断数据的正确性。
--3. 识别接收的mac流控帧,子模块mac_frame_ctrl提取流控帧中的暂停时间和源mac地址输出至uimac_tx模块。
--4. mac_rx_data_fifo完成phy接收时钟和用户接口时钟之间的时钟域转换,将数据输出。
*********************************************************************/
`timescale  1ns/1ps
module  uimac_rx
(
    input   wire    [47:0]      I_mac_local_addr    ,   //本地MAC地址
    input   wire                I_crc32_en          ,   //使能CRC校验
    input   wire                I_reset             ,   //系统复位
    //MAC接收数据发送给上层协议
    input   wire                I_mac_rclk          ,   //接收时钟
    output  wire                O_mac_rvalid        ,   //MAC帧数据有效
    output  wire    [7:0]       O_mac_rdata         ,   //MAC有效数据
    output  wire    [15:0]      O_mac_rdata_type    ,   //MAC类型
    output  wire                O_mac_rdata_error   ,
    //发送PAUSE控制到mac_send
    output  wire                O_mac_pause_en      ,
    output  wire    [21:0]      O_mac_pause_time    ,
    output  wire    [47:0]      O_mac_pause_addr    ,
    //从硬件层获取的裸MAC数据
    input   wire                I_gmii_rclk         ,   //rgmii接收时钟
    input   wire                I_gmii_rvalid       ,   //gmii接收数据有效使能信号
    input   wire    [7:0]       I_gmii_rdata            //gmii接收数据
);

wire    [7:0]       mac_rdata;
reg                 mac_rdata_valid;
reg     [15:0]      mac_rdata_type;
reg                 mac_rdata_error;

assign  O_mac_rdata         =   mac_rdata;
assign  O_mac_rvalid        =   mac_rdata_valid;
assign  O_mac_rdata_type    =   mac_rdata_type;
assign  O_mac_rdata_error   =   mac_rdata_error;

reg     [10:0]      mac_rdata_cnt;
reg                 mac_wfifo_en;   //FIFO写入数据有效使能
reg                 mac_rfifo_en;   //FIFO读数据使能

reg                 crc_en;         //crc校验使能
reg     [2:0]       crc_cnt;        //移位计数器
wire    [31:0]      crc_data_out;   //crc校验结果输出
reg     [2:0]       STATE;          //写FIFO状态机
reg     [1:0]       S_RFIFO;        //读FIFO状态机

reg     [47:0]      dst_mac_addr;   //MAC帧解析出的目的MAC地址(接收方的MAC地址)
reg     [47:0]      src_mac_addr;   //MAC帧解析出的源MAC地址(发送方的MAC地址)
reg     [15:0]      mac_frame_type; //上层数据包类型(0x0800 ip;0x0806 arp;0x8808 mac_ctrl)
reg                 mac_pause_en;   //PAUSE帧有效使能
reg     [3:0]       cnt;            //对MAC帧头部的字节数计数

reg     [10:0]      mac_wfifo_data_cnt_info;//写帧信息字节数到信息FIFO
reg                 mac_wfifo_en_info;      //写帧信息FIFO有效使能
reg                 mac_rfifo_en_info;      //读帧信息FIFO有效使能
wire    [26:0]      mac_rfifo_data_info;    //从信息FIFO读出帧信息
reg     [10:0]      mac_rdata_len;          //mac帧长度
wire                mac_rfifo_empty_info;   //信息FIFO读空信号

reg     [7:0]       mac_rdata_r1, mac_rdata_r2, mac_rdata_r3, mac_rdata_r4;//打拍
reg                 mac_rvalid_r1, mac_rvalid_r2, mac_rvalid_r3, mac_rvalid_r4;//打拍

localparam  WAIT_SFD            =   3'd0;
localparam  CHECK_MAC_HEADER    =   3'd1;
localparam  WRITE_FIFO          =   3'd2;
localparam  RECORD_FRAME_LENGTH =   3'd3;
localparam  WAIT_FRAME_END      =   3'd4;//STATE

localparam  WAIT_MAC_FRAME          =   2'd0;
localparam  READ_MAC_FRAME_DATA_LENGTH  =   2'd1;
localparam  READ_MAC_FRAME_DATA     =   2'd2;//S_RFIFO

localparam  ARP_TYPE    =   16'h0806;
localparam  IP_TYPE     =   16'h0800;
localparam  MAC_CONTROL_TYPE    =   16'h8808;

由于用户接口时钟和PHY芯片接收时钟不同源,因此需要对输入的数据进行跨时钟域处理,再将数据传至上层。由于需要传输的数据量比较大,且用FIFO做跨时钟域比较简单,所以程序中使用异步FIFO做跨时钟域处理。 在uimac_rx和uimac_tx发送模块中,都使用了两个FIFO做跨时钟域处理,一个FIFO用来缓存数据,另一个FIFO用来缓存需要传递的信息,在一帧输入接收完成后,将信息写入帧信息FIFO,通过FIFO空标志信号来控制一帧数据读出。

信号打4拍再写入数据FIFO是为了过滤掉4字节CRC校验位。

assign  O_mac_pause_addr    =   src_mac_addr;

always@(posedge I_gmii_rclk) begin
    mac_rdata_r1    <=  I_gmii_rdata;
    mac_rdata_r2    <=  mac_rdata_r1;   
    mac_rdata_r3    <=  mac_rdata_r2;
    mac_rdata_r4    <=  mac_rdata_r3;
end

always@(posedge I_gmii_rclk) begin
    mac_rvalid_r1   <=  I_gmii_rvalid;
    mac_rvalid_r2   <=  mac_rvalid_r1;  
    mac_rvalid_r3   <=  mac_rvalid_r2;
    mac_rvalid_r4   <=  mac_rvalid_r3;
end//打4拍,方便fifo只写入有效数据,而不写入crc校验位

mac_rx_data_fifo mac_rx_data_fifo (
    .rst            (I_reset),
    .wr_clk         (I_gmii_rclk),
    .din            (mac_rdata_r4),
    .wr_en          (mac_wfifo_en & I_gmii_rvalid),//mac_wfifo_en控制只写入有效数据部分,I_gmii_rvalid控制最后的CRC部分不写入

    .rd_clk         (I_mac_rclk),
    .rd_en          (mac_rfifo_en),
    .dout           (mac_rdata),
    .full           (),
    .empty          (),
    .rd_data_count  (),
    .wr_data_count  ()
);

mac_rx_frame_fifo mac_rx_frame_fifo (
    .rst            (I_reset),
    .wr_clk         (I_gmii_rclk),
    .din            ({mac_wfifo_data_cnt_info,mac_frame_type}),
    .wr_en          (mac_wfifo_en_info),

    .rd_clk         (I_mac_rclk),
    .rd_en          (mac_rfifo_en_info),
    .dout           (mac_rfifo_data_info),
    .full           (),
    .empty          (mac_rfifo_empty_info)
);

crc32_check crc32_check
(
    .reset          (I_reset),
    .clk            (I_gmii_rclk),
    .CRC32_en       (crc_en & I_crc32_en),
    .CRC32_init     (~mac_rvalid_r4),
    .data           (mac_rdata_r4),
    .CRC_data       (crc_data_out)
);

//mac帧控制,当接收方来不及处理接收数据,需要进行帧控制,通知发送模块。
uimac_tx_frame_ctrl mac_tx_frame_ctrl
(
    .I_clk              (I_gmii_rclk),
    .I_reset            (I_reset),
    .I_mac_pause_en     (mac_rvalid_r4 & mac_pause_en),
    .I_mac_data         (mac_rdata_r4),
    .O_mac_pause_en     (O_mac_pause_en),
    .O_mac_pause_time   (O_mac_pause_time)
);

通过状态机分别对FIFO写入数据和FIFO读出数据的时序进行控制。FIFO写入数据的状态机转换图如图所示。

WAIT_SFD:初始状态为WAIT_SFD,等待接收到帧起始定界符8’hd5时,跳转到CHECK_MAC_HEADER帧头接收状态。

CHECK_MAC_HEADER:通过计数器将每一字节的帧头信息缓存,帧头信息接收完成后,判断接收到数据包的类型。如果接收到的MAC帧类型为IP包或者ARP包,跳转至WRITE_FIFO状态,并将数据FIFO写使能拉高,将有效数据写入数据FIFO,如果接收到的MAC帧类型为流量控制帧,则将有效数据传入帧流控制模块,对信息进行解析,跳转至FRAME_END状态,状态如果以上情况都不是,丢弃该帧,跳转至FRAME_END状态。

WRITE_FIFO:当数据有效信号为高时,将有效数据写入写数据FIFO,使用I_gmii_rvalid做为计数器计数的有效信号,是为了统计到有效数据的准确长度。CRC校验完成后,将帧长度和帧类型写入帧信息FIFO中,进入RECORD_FRAME_LENGTH状态。

RECORD_FRAME_LENGTH:帧信息写入帧信息FIFO完成后,回到WAIT_SFD状态,等待接收下一帧。

always@(posedge I_gmii_rclk or posedge I_reset) begin
    if(I_reset) begin
        dst_mac_addr        <=  48'd0;
        src_mac_addr        <=  48'd0;
        mac_frame_type      <=  16'd0;
        mac_wfifo_en        <=  1'b0;
        mac_wfifo_en_info   <=  1'b0;
        mac_wfifo_data_cnt_info <=  11'd0;
        cnt                 <=  4'd0;
        crc_en              <=  1'b0;
        crc_cnt             <=  3'd4;
        mac_rdata_error     <=  1'b1;
        mac_pause_en        <=  1'b0;
        STATE               <=  WAIT_SFD;
    end
    else begin
        case(STATE)
            WAIT_SFD:begin
                if(mac_rvalid_r4 & (mac_rdata_r4 == 8'hd5)) begin//以太网帧开始同步,一个字节为mac字段
                    crc_en  <=  1'b1;//使能crc
                    STATE   <=  CHECK_MAC_HEADER;//进入帧头接收
                end
                else
                    STATE   <=  WAIT_SFD;
            end
            CHECK_MAC_HEADER:begin
                case(cnt)
                    4'd0:begin  dst_mac_addr[47:40]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd1:begin  dst_mac_addr[39:32]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd2:begin  dst_mac_addr[31:24]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd3:begin  dst_mac_addr[23:16]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd4:begin  dst_mac_addr[15: 8]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd5:begin  dst_mac_addr[ 7: 0]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end//目的mac
                    4'd6:begin  src_mac_addr[47:40]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd7:begin  src_mac_addr[39:32]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd8:begin  src_mac_addr[31:24]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd9:begin  src_mac_addr[23:16]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd10:begin src_mac_addr[15: 8]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd11:begin src_mac_addr[ 7: 0]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
                    4'd12:begin mac_frame_type[15: 8]   <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end//源mac
                    4'd13:begin
                        mac_frame_type[7:0] <=  mac_rdata_r4;
                        cnt <=  4'd0;
                        if(dst_mac_addr == I_mac_local_addr) begin//判断mac是否一致
                            if({mac_frame_type[15:8], mac_rdata_r4} == IP_TYPE || {mac_frame_type[15:8], mac_rdata_r4} == ARP_TYPE) begin
                                mac_wfifo_en    <=  1'b1;//写fifo使能,只写入数据有效部分
                                STATE           <=  WRITE_FIFO;
                            end
                            else begin//需要过滤的帧
                                mac_wfifo_en    <=  1'b0;//禁止写fifo
                                STATE           <=  WAIT_FRAME_END;//过滤该帧,等待帧结束
                            end
                        end
                        else if(dst_mac_addr == 48'h01_80_c2_00_00_01) begin//如果目的地址为48'h0180c2000001(固定值),mac控制帧,需要进行PAUSE流控制
                            mac_wfifo_en    <=  1'b0;
                            STATE           <=  WAIT_FRAME_END;
                            if({mac_frame_type[15:8], mac_rdata_r4} == MAC_CONTROL_TYPE)//报文类型字段,需要进行pause流控制
                                mac_pause_en    <=  1'b1;//mac控制帧有效
                            else
                                mac_pause_en    <=  1'b0;
                        end
                        else if(dst_mac_addr == 48'hff_ff_ff_ff_ff_ff) begin//对于广播地址,只接收arp包,其余类型的广播包全部过滤
                            if({mac_frame_type[15:8], mac_rdata_r4} == ARP_TYPE) begin
                                mac_wfifo_en    <=  1'b1;//写帧数据fifo使能,只写入有效数据部分
                                STATE           <=  WRITE_FIFO;
                            end
                            else begin//需要过滤的帧
                                mac_wfifo_en    <=  1'b0;
                                STATE           <=  WAIT_FRAME_END;
                            end
                        end
                        else begin//需要过滤的帧
                            mac_wfifo_en    <=  1'b0;
                            STATE           <=  WAIT_FRAME_END;
                        end
                    end
                endcase
            end 
            WRITE_FIFO:begin//将去除首部后的ip数据包或者arp帧存入mac_rx_frame_fifo中,同时对当前数据包的长度进行统计
                if(I_gmii_rvalid) begin//写帧信息fifo
                    mac_wfifo_data_cnt_info     <=  mac_wfifo_data_cnt_info + 1'b1;//有效数据计数器
                    STATE                       <=  WRITE_FIFO;
                end
                else begin
                    if(crc_cnt == 3'd0) begin//crc校验
                        if(crc_data_out != 32'hc704dd7b)
                            mac_rdata_error <=  1'b1;//校验正确
                        else
                            mac_rdata_error <=  1'b0;//校验错误

                        mac_wfifo_en        <=  1'b0;
                        mac_wfifo_en_info   <=  1'b1;//写帧信息fifo使能
                        crc_en              <=  1'b0;
                        crc_cnt             <=  3'd4;
                        STATE               <=  RECORD_FRAME_LENGTH;//写入帧信息到帧信息fifo
                    end
                    else
                        crc_cnt <=  crc_cnt - 1'b1;//crc计算计数器
                end
            end
            RECORD_FRAME_LENGTH:begin//写帧信息完成后,回到状态机WAIT_SFD
                mac_wfifo_en_info       <=  1'b0;
                mac_wfifo_data_cnt_info <=  11'd0;
                STATE                   <=  WAIT_SFD;//回到帧探测状态机
            end
            WAIT_FRAME_END:begin//等待帧结束 
                if(mac_rvalid_r4)
                    STATE               <=  WAIT_FRAME_END;
                else begin
                    crc_en              <=  1'b0;
                    mac_pause_en        <=  1'b0;
                    STATE               <=  WAIT_SFD;
                end
            end
        endcase
    end
end

WAIT_FRAME_END:等待不传入上层的帧结束,回到WAIT_SFD状态,等待接收下一帧。FIFO读出数据的状态机转换图如图所示。

WAIT_MAC_FRAME:当帧信息FIFO的空信号拉低,表明一帧数据已经接收完毕,将帧信息FIFO缓存的帧信息读出,并跳转至READ_MAC_FRAME_DATA_LENGTH状态。

READ_MAC_FRAME_DATA_LENGTH:该状态下开始读出数据FIFO缓存的有效数据,并将数据有效信号拉高,开始向上层传输数据包,随即进入READ_MAC_FRAME_DATA状态。

READN_MAC_FRAME_DATA:对读出的数据数量计数,当读出的数据量为帧信息FIFO中读出的帧长度时,停止读出数据,返回WAIT_MAC_FRAME状态。

always@(posedge I_mac_rclk or posedge I_reset) begin
    if(I_reset) begin
        mac_rfifo_en_info   <=  1'b0;
        mac_rdata_len       <=  11'd0;
        mac_rdata_cnt       <=  11'd0;
        mac_rfifo_en        <=  1'b0;
        mac_rdata_type      <=  16'd0;
        mac_rdata_valid     <=  1'b0;
        S_RFIFO             <=  WAIT_MAC_FRAME;
    end
    else begin
        case(S_RFIFO)
            WAIT_MAC_FRAME:begin
                if(!mac_rfifo_empty_info) begin//接收mac帧信息fifo非空
                    mac_rfifo_en_info   <=  1'b1;
                    S_RFIFO             <=  READ_MAC_FRAME_DATA_LENGTH;
                end
                else
                    S_RFIFO             <=  WAIT_MAC_FRAME;
            end
            READ_MAC_FRAME_DATA_LENGTH:begin
                mac_rdata_len       <=  mac_rfifo_data_info[26:16];//mac帧长度
                mac_rdata_type      <=  mac_rfifo_data_info[15:0];//mac类型
                mac_rfifo_en_info   <=  1'b0;
                mac_rfifo_en        <=  1'b1;//读数据fifo
                mac_rdata_valid     <=  1'b1;//数据有效
                S_RFIFO             <=  READ_MAC_FRAME_DATA;
            end
            READ_MAC_FRAME_DATA:begin
                if(mac_rdata_cnt < (mac_rdata_len - 1'b1)) begin//读完一帧数据
                    mac_rdata_cnt   <=  mac_rdata_cnt + 1'b1;
                    S_RFIFO         <=  READ_MAC_FRAME_DATA;
                end
                else begin
                    mac_rfifo_en    <=  1'b0;
                    mac_rdata_valid <=  1'b0;
                    mac_rdata_cnt   <=  11'd0;
                    mac_rdata_len   <=  11'd0;
                    mac_rdata_type  <=  16'd0;
                    S_RFIFO         <=  WAIT_MAC_FRAME;
                end
            end
        endcase
    end
end

3.1.2 MAC发送模块

MAC发送模块主要实现以下功能:

(1)接收IP或ARP数据包,添加MAC帧首部,并对长度不足64字节的包进行补0。

(2)通过CRC校验模块,生成CRC校验值添加在帧的末尾。

(3)通过流控模块,接收MAC接收模块发送的暂停信号,进行流量控制。

(4)通过FIFO完成PHY发送时钟和用户接口时钟之间的时钟域的转换,并将数据输出至外部PHY芯片。

该模块数据跨时钟域转换的方式和uimac_rx模块类似,使用两个FIFO对数据进行处理。通过DR1_LOGIC_SHIFTER原语将数据延时,将帧头插入,该方法在uiip_tx和uiudp_tx模块中也有所体现,该原语可在TD安装路径下的arch文件夹的dr1_macro.v文件中找到。

控制FIFO写入数据的状态机跳转图如所示。

WAIT_DATA_PACKET:当数据发送过来后,开始接收数据并缓存进FIFO中,并通过寄存器缓存帧信息,进入WIRTE_FIFO状态。如果数据FIFO写计数器大于阈值(本工程设置为2500,具体数值根据FIFO深度而定),则停止接收该帧,保持原状态不变。

WRITE_FIFO:写数据到数据FIFO中,并且对写入的数据长度进行计数,如果数据长度小于46,对数据末尾进行补0,一帧数据写入完成后,将长度、类型和地址信息写入帧信息FIFO,进入RECORD_DATA_PACKET_INFO状态。

RECORD_DATA_PACKET_INFO:信息写入帧信息FIFO完成后,回到WAIT_DATA_PACKET状态,等待接收下一帧数据。

always@(posedge I_mac_tclk or posedge rst) begin
    if(rst) begin
        mac_wfifo_en_info           <=  1'b0;   //MAC消息FIFO,把MAC的信息包括,目的MAC地址、有效数据长度、帧类型写入到info fifo暂存
        mac_wfifo_data_addr_info    <=  48'd0;  //MAC目的地址,暂存info fifo
        mac_wfifo_data_type_info    <=  16'd0;  //MAC帧类型,暂存info fifo
        mac_wfifo_data_cnt_info     <=  11'd0;  //MAC数据部分发送字节计数器
        mac_wfifo_en                <=  1'b0;   //将帧数据写入到mac_tx_data_fifo缓存
        mac_wfifo_data              <=  8'd0;   //将帧数据写入到mac_tx_data_fifo缓存
        O_mac_tbusy                 <=  1'b1;   //通知外部模块,非忙
        S_WFIFO                     <=  WAIT_DATA_PACKET;
    end
    else begin
        case(S_WFIFO)
            WAIT_DATA_PACKET:begin
                if(mac_wfifo_data_cnt > SEND_PAUSE_THRESHOLD) begin//当FIFO写通道数据计数器大于SEND_PAUSE_THRESHOLD,不进行新的一帧传输,O_mac_tbusy为握手信号,不进行握手(拉高)
                    O_mac_tbusy                 <=  1'b0;
                    S_WFIFO                     <=  WAIT_DATA_PACKET;
                end
                else begin
                    if(I_mac_tvalid) begin//当有效数据发送过来后开始接收数据并且缓存到FIFO
                        O_mac_tbusy             <=  1'b1;               //uimac_tx 忙
                        mac_wfifo_en            <=  1'b1;               //将数据写入FIFO
                        mac_wfifo_data          <=  I_mac_tdata;        //写入FIFO的数据
                        mac_wfifo_data_addr_info<=  I_mac_tdest_addr;   //目的MAC地址
                        mac_wfifo_data_type_info<=  {14'd0, I_mac_tdata_type};//数据类型
                        mac_wfifo_data_cnt_info <=  mac_wfifo_data_cnt_info + 1'b1;//一帧数据的长度,以BYTE为单位
                        S_WFIFO                 <=  WRITE_FIFO;//进入下一个状态等待写FIFO
                    end
                    else begin
                        O_mac_tbusy             <=  1'b0;           //uimac_tx 非忙
                        S_WFIFO                 <=  WAIT_DATA_PACKET;
                    end
                end
            end
            WRITE_FIFO:begin//写数据到FIFO该FIFO用于缓存udp协议发送过来的数据
                if(I_mac_tvalid) begin//一帧数据接收过程中O_gmii_tdata_valid始终为高电平
                    mac_wfifo_en                <=  1'b1;//继续写FIFO
                    mac_wfifo_data              <=  I_mac_tdata;//写入FIFO的数据
                    mac_wfifo_data_cnt_info     <=  mac_wfifo_data_cnt_info + 1'b1;//帧字节计数器累加
                    S_WFIFO                     <=  WRITE_FIFO;
                end
                else begin
                    if(mac_wfifo_data_cnt_info < 11'd46) begin//当一包/帧数据的长度小于46字节,自动补0(一帧数据最小64bytes,其中数据部分最小46bytes)
                        mac_wfifo_en            <=  1'b1;
                        mac_wfifo_data          <=  8'd0;
                        mac_wfifo_en_info       <=  1'b0;
                        mac_wfifo_data_cnt_info <=  mac_wfifo_data_cnt_info + 1'b1;
                        S_WFIFO                 <=  WRITE_FIFO;
                    end
                    else begin//当一包/帧数据接收完,写包/帧信息 到包/帧信息FIFO
                        mac_wfifo_en            <=  1'b0;
                        mac_wfifo_data          <=  8'd0;
                        mac_wfifo_en_info       <=  1'b1;
                        S_WFIFO                 <=  RECORD_DATA_PACKET_INFO;
                    end
                end
            end
            RECORD_DATA_PACKET_INFO:begin//时序中,该周期完成写包/帧信息 到包/帧信息FIFO
                mac_wfifo_en_info               <=  1'b0;
                mac_wfifo_data_addr_info        <=  48'd0;
                mac_wfifo_data_type_info        <=  16'd0;
                mac_wfifo_data_cnt_info         <=  11'd0;
                S_WFIFO                         <=  WAIT_DATA_PACKET;
            end
        endcase
    end
end

控制FIFO读出数据的状态机转换图如图所示。

WAIT_DATA_PACKET:帧信息FIFO非空,说明有帧需要发送,将帧信息FIFO读使能拉高一个时钟周期,跳转至READ_DATA_PACKET_INFO状态。

READ_DATA_PACKET_INFO:对读出的帧信息进行解析,通过帧类型得到目地MAC地址,并拉高数据FIFO读使能,开始读出有效数据,输入进移位寄存器中,进入RAED_DATA_PACKET状态。如果PAUSE标志信号为高,且PAUSE帧的地址和目的MAC地址相同,说明对方请求暂停发送,则进入WAIT_PAUSE_END状态。

READ_DATA_PACKET:当一帧数据读完后,进入WAIT_CRC_TRANS_DONE状态。

WAIT_CRC_TRANS_DONE:等待CRC计数器置零,代表一帧数据发送完成,进入ADD_IFG状态。

ADD_IFG:等待最小帧间隔结束,回到WAIT_DATA_PACKET状态,等待接收下一帧。

WAIT_PAUSE_END:等待PAUSE标志信号拉低,暂停结束,此时将数据FIFO读使能拉高,继续读该帧的有效数据,进入READ_DATA_PACKET状态。

READ_DATA_PACKET:将shift_ram移位后的数据进行组帧,通过计数器添加前导码、SFD、目的MAC、源MAC和类型字段,并在帧末尾填充4字节CRC校验数据。

//完成MAC帧的发送,用到了前面的帧缓存FIFO,信息缓存FIFO,以及SHIFT寄存器(实现MAC帧头信息插入)
always@(posedge I_gmii_tclk or posedge I_reset) begin
    if(I_reset) begin
        mac_rfifo_data_en           <=  1'b0;   
        mac_rfifo_en_info           <=  1'b0;   
        mac_rfifo_data_cnt          <=  11'd0;  
        mac_rfifo_data_length       <=  11'd0;  
        mac_rfifo_data_addr         <=  48'd0;  
        ether_type                  <=  16'd0;  
        inter_gap_cnt               <=  4'd0;   
        S_RFIFO                     <=  WAIT_DATA_PACKET;
    end
    else begin
        case(S_RFIFO)
            WAIT_DATA_PACKET:begin
                if(!mac_rfifo_empty_info) begin//帧信息FIFO非空代表有帧需要发送
                    mac_rfifo_en_info       <=  1'b1;//FIFO是设置的FWT模式,如果只有1帧数据,那么FIFO被读空,否则FIFO输出更新到下一帧
                    S_RFIFO                 <=  READ_DATA_PACKET_INFO;
                end
                else
                    S_RFIFO                 <=  WAIT_DATA_PACKET;
            end
            READ_DATA_PACKET_INFO:begin
                if(mac_rfifo_data_info[15:0] == 16'h0002) begin//发送的ARP包类型为应答包
                    ether_type              <=  ARP_PACKET;
                    mac_rfifo_data_addr     <=  mac_rfifo_data_info[74:27];//MAC地址
                end
                else if(mac_rfifo_data_info[15:0] == 16'h0003) begin
                    ether_type              <=  ARP_PACKET;
                    mac_rfifo_data_addr     <=  48'hff_ff_ff_ff_ff_ff;//广播地址
                end
                else begin
                    ether_type              <=  IP_PACKET;//IP 包
                    mac_rfifo_data_addr     <=  mac_rfifo_data_info[74:27];//MAC地址
                end

                mac_rfifo_data_length       <=  mac_rfifo_data_info[26:16];//数据长度
                mac_rfifo_en_info           <=  1'b0;

                if(pause_flag && mac_rfifo_data_info[74:27] == pause_dst_mac_addr) begin//如果存在PAUSE帧需要发送,并且目的地址和当前目的地址一致
                    mac_rfifo_data_en       <=  1'b0;//PAUSE 帧阶段不从FIFO读数据   
                    S_RFIFO                 <=  WAIT_PAUSE_END;//等待PAUSE流控制结束
                end
                else begin
                    mac_rfifo_data_en       <=  1'b1;
                    S_RFIFO                 <=  READ_DATA_PACKET;
                end
            end
            READ_DATA_PACKET:begin
                if(mac_rfifo_data_cnt == (mac_rfifo_data_length - 1'b1)) begin//一帧数据从FIFO读完(上一个状态已经读出了一个周期,所以这里少计一个数)
                    mac_rfifo_data_en       <=  1'b0;
                    mac_rfifo_data_length   <=  11'd0;
                    mac_rfifo_data_cnt      <=  11'd0;
                    mac_rfifo_data_addr     <=  48'd0;
                    ether_type              <=  16'd0;
                    S_RFIFO                 <=  WAIT_CRC_TRANS_DONE;    
                end
                else begin
                    mac_rfifo_data_en       <=  1'b1;
                    mac_rfifo_data_cnt      <=  mac_rfifo_data_cnt + 1'b1;
                    S_RFIFO                 <=  READ_DATA_PACKET;
                end
            end
            WAIT_CRC_TRANS_DONE:begin//等待正在发送的MAC数据包CRC发送完成
                if(crc_cnt)
                    S_RFIFO                 <=  WAIT_CRC_TRANS_DONE;
                else
                    S_RFIFO                 <=  ADD_IFG;
            end
            ADD_IFG:begin//数据包发送后,插入帧间隔,2帧之间最少需要IFGmini=96bit/speed,比如1000M 96ns 100M 960ns 10M 9600ns
                if(inter_gap_cnt == (IFG - 4'd4)) begin//插入最小帧间隔周期,在此状态机,MAC_SEND_FLOW_CONTROL 流控制模可以发送PAUSE帧,减去4'd4是本计数器结束后,距离下一帧发送实际需要还要经过4个时钟周期
                    inter_gap_cnt           <=  4'd0;
                    S_RFIFO                 <=  WAIT_DATA_PACKET;//进入WAIT_DATA_PACKET
                end
                else begin
                    inter_gap_cnt           <=  inter_gap_cnt + 1'b1;
                    S_RFIFO                 <=  ADD_IFG;
                end
            end
            WAIT_PAUSE_END:begin//等待暂停结束后重新传输数据
                if(pause_flag) begin//pause 控制
                    mac_rfifo_data_en       <=  1'b0;
                    S_RFIFO                 <=  WAIT_PAUSE_END;
                end
                else begin
                    mac_rfifo_data_en       <=  1'b1;//暂停结束后,继续读帧FIFO中数据
                    S_RFIFO                 <=  READ_DATA_PACKET;
                end
            end
        endcase
    end
end

always@(posedge I_gmii_tclk or posedge I_reset) begin
    if(I_reset) begin
        O_gmii_tvalid       <=  1'b0;
        mac_tdata           <=  8'd0;
        mac_tdata_crc_en    <=  1'b0;
        data22_cnt          <=  5'd0;
        data22_shift_cnt    <=  5'd22;
        crc_cnt             <=  3'd4;
        crc_read            <=  1'b0;
    end
    else if(mac_rfifo_data_en) begin
        case(data22_cnt)//这个阶段移位寄存器进行数据的填充
            0   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; O_gmii_tvalid    <=  1'b1; data22_shift_cnt  <=  5'd22;end
            1   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
            2   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end  
            3   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
            4   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
            5   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
            6   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
            7   :begin  mac_tdata   <=  8'hd5;    data22_cnt  <=  data22_cnt + 1'b1; end
            
            8   :begin  mac_tdata   <=  mac_rfifo_data_addr[47:40]; data22_cnt  <=  data22_cnt + 1'b1;
 mac_tdata_crc_en <=  1'b1; end
            9   :begin  mac_tdata   <=  mac_rfifo_data_addr[39:32]; data22_cnt  <=  data22_cnt + 1'b1; end
            10  :begin  mac_tdata   <=  mac_rfifo_data_addr[31:24]; data22_cnt  <=  data22_cnt + 1'b1; end
            11  :begin  mac_tdata   <=  mac_rfifo_data_addr[23:16]; data22_cnt  <=  data22_cnt + 1'b1; end
            12  :begin  mac_tdata   <=  mac_rfifo_data_addr[15:8];  data22_cnt  <=  data22_cnt + 1'b1; end
            13  :begin  mac_tdata   <=  mac_rfifo_data_addr[7:0];   data22_cnt  <=  data22_cnt + 1'b1; end
            14  :begin  mac_tdata   <=  I_mac_local_addr[47:40];    data22_cnt  <=  data22_cnt + 1'b1; end
            15  :begin  mac_tdata   <=  I_mac_local_addr[39:32];    data22_cnt  <=  data22_cnt + 1'b1; end
            16  :begin  mac_tdata   <=  I_mac_local_addr[31:24];    data22_cnt  <=  data22_cnt + 1'b1; end
            17  :begin  mac_tdata   <=  I_mac_local_addr[23:16];    data22_cnt  <=  data22_cnt + 1'b1; end
            18  :begin  mac_tdata   <=  I_mac_local_addr[15:8];     data22_cnt  <=  data22_cnt + 1'b1; end
            19  :begin  mac_tdata   <=  I_mac_local_addr[7:0];      data22_cnt  <=  data22_cnt + 1'b1; end
            
            20  :begin  mac_tdata   <=  ether_type[15:8];           data22_cnt  <=  data22_cnt + 1'b1; end
            21  :begin  mac_tdata   <=  ether_type[7:0];            data22_cnt  <=  data22_cnt + 1'b1; end
            22  :begin  mac_tdata   <=  mac_tdata_shift_out; end
            default:    data22_cnt  <=  5'd0;
        endcase
    end
    else if(!mac_rfifo_data_en) begin//tmac_en=1阶段会读取mac_tx_frame_info_fifo中所有的数据写到移位寄存器,当tmac_en=0,移位寄存器剩余22个有效数据需要移除
        if(data22_shift_cnt != 5'd0) begin//将移位寄存器组中的剩余22个数据读出 
            mac_tdata           <=  mac_tdata_shift_out;
            data22_shift_cnt    <=  data22_shift_cnt - 1'b1;
        end
        else begin
            if(I_crc32_en && O_gmii_tvalid) begin //开始传送帧的CRC32校验值
                O_gmii_tvalid       <=  1'b1;
                data22_cnt          <=  5'd0;
                mac_tdata_crc_en    <=  1'b0;//停止CRC计算
                crc_read            <=  1'b1;//开始传输CRC32校验值
                if(crc_cnt != 3'd0)
                    crc_cnt         <=  crc_cnt - 1'b1;
                else begin
                    O_gmii_tvalid   <=  1'b0;
                    crc_read        <=  1'b0;//4字节的CRC校验值传输完毕
                    crc_cnt         <=  3'd4;
                end
            end
            else begin//不进行CRC32校验,无需传输校验值
                O_gmii_tvalid       <=  1'b0;
                data22_shift_cnt    <=  5'd0;
            end
        end 
    end
end

3.1.3 CRC校验模块

通过网页生成CRC校验代码,将代码稍作修改,得到CRC校验计算模块。注意uimac_rx模块将接收到的CRC校验位也进行CRC校验,若校验计算结果为0则校验正确。

`timescale 1ns / 1ps
module crc32_gen(
input   reset,
input   clk,
input   CRC32_en,         //CRC校验使能信号
input   CRC32_init,       //CRC校验值初始化信号
input   CRC_read, 
     //input   CRC32_valid,      //CRC校验值维持有效
input  [7:0]  data,  
output [7:0]  CRC_out 
);
reg [31:0]   CRC_temp;
assign CRC_out = CRC_read ? ~{CRC_temp[24], CRC_temp[25], CRC_temp[26], CRC_temp[27],
                               CRC_temp[28], CRC_temp[29], CRC_temp[30], CRC_temp[31]} : 8'h00;           
    
always@(posedge clk or posedge reset)         
   if(reset)
        CRC_temp <= 32'hffffffff;         
   else if(CRC32_init)
        CRC_temp <= 32'hffffffff;
   else if(CRC32_en)
      begin
CRC_temp[0]<=CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
CRC_temp[1]<=CRC_temp[25]^CRC_temp[31]^data[0]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
CRC_temp[2]<=CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]
^data[7];
CRC_temp[3]<=CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
CRC_temp[4]<=CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[24]^CRC_temp[30]^data[1]
^data[7];
CRC_temp[5]<=CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
CRC_temp[6]<=CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
CRC_temp[7]<=CRC_temp[31]^data[0]^CRC_temp[29]^data[2]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[24]^data[7];
CRC_temp[8]<=CRC_temp[0]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[25]^data[6]^CRC_temp[24]^data[7];
CRC_temp[9]<=CRC_temp[1]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[26]^data[5]^CRC_temp[25]^data[6];
CRC_temp[10]<=CRC_temp[2]^CRC_temp[29]^data[2]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[24]^data[7];
CRC_temp[11]<=CRC_temp[3]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[25]^data[6]^CRC_temp[24]^data[7];
CRC_temp[12]<=CRC_temp[4]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[26]^data[5]^CRC_temp[25]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
CRC_temp[13]<=CRC_temp[5]^CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
CRC_temp[14]<=CRC_temp[6]^CRC_temp[31]^data[0]^CRC_temp[30]^data[1]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5];
CRC_temp[15]<=CRC_temp[7]^CRC_temp[31]^data[0]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4];
CRC_temp[16]<=CRC_temp[8]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[24]^data[7];
CRC_temp[17]<=CRC_temp[9]^CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[25]^data[6];
CRC_temp[18]<=CRC_temp[10]^CRC_temp[31]^data[0]^CRC_temp[30]^data[1]^CRC_temp[26]^data[5];
CRC_temp[19]<=CRC_temp[11]^CRC_temp[31]^data[0]^CRC_temp[27]^data[4];
CRC_temp[20]<=CRC_temp[12]^CRC_temp[28]^data[3];
CRC_temp[21]<=CRC_temp[13]^CRC_temp[29]^data[2];
CRC_temp[22]<=CRC_temp[14]^CRC_temp[24]^data[7];
CRC_temp[23]<=CRC_temp[15]^CRC_temp[25]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
CRC_temp[24]<=CRC_temp[16]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
CRC_temp[25]<=CRC_temp[17]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5];
CRC_temp[26]<=CRC_temp[18]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
CRC_temp[27]<=CRC_temp[19]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
CRC_temp[28]<=CRC_temp[20]^CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[26]^data[5];
CRC_temp[29]<=CRC_temp[21]^CRC_temp[31]^data[0]^CRC_temp[30]^data[1]^CRC_temp[27]^data[4];
CRC_temp[30]<=CRC_temp[22]^CRC_temp[31]^data[0]^CRC_temp[28]^data[3];
CRC_temp[31]<=CRC_temp[23]^CRC_temp[29]^data[2];
      end
    else if(CRC_read)
        CRC_temp <= {CRC_temp[23:0], 8'hff};
         
endmodule

3.1.4 PAUSE帧流控制模块

uimac_rx模块接收到PAUSE帧后,会将有效数据段送入mac_tx_frame_ctrl模块进行解析,得到暂停时间和PAUSE帧发送方的MAC地址,将其发送给uimac_tx模块的mac_tx_pause_ctrl子模块。

/*******************************mac_tx_frame_ctrl模块*********************
--以下是米联客设计的mac_tx_frame_ctrl模块,用于产生MAC发送模块的PAUSE暂停发送
1.
*********************************************************************/
`timescale 1ns/1ps
module  uimac_tx_frame_ctrl
(
    input   wire                I_clk,
    input   wire                I_reset,
    input   wire                I_mac_pause_en,
    input   wire    [7:0]       I_mac_data,
    output  reg                 O_mac_pause_en,
    output  reg     [21:0]      O_mac_pause_time//发送MAC停止发送数据的时间
);

reg     [15:0]              opcode;
reg     [15:0]              pause_time;//pause_time字段为发送MAC停止发送数据的时间,每单位为512bit传输时间,比如数值为16’d1024表示暂停时间为MAC传输1024*512bit数据所需要的时间
reg     [2:0]               cnt;
reg                         STATE;

localparam  READ_FRAME      =   0;
localparam  WAIT_FRAME_END  =   1;

localparam  PAUSE_FRAME     =   16'h0001;//操作码,固定值为0x0001

always@(posedge I_clk or posedge I_reset) begin
    if(I_reset) begin
        cnt                 <=  3'd0;
        opcode              <=  16'd0;
        pause_time          <=  16'd0;
        O_mac_pause_en      <=  1'b0;
        O_mac_pause_time    <=  22'd0;
        STATE               <=  READ_FRAME;
    end
    else begin
        case(STATE)
            READ_FRAME:begin
                if(I_mac_pause_en)//帧流控制有效
                    case(cnt)
                        0:begin opcode[15: 8]   <=  I_mac_data; cnt <=  cnt + 1'b1;end
                        1:begin 
                            opcode[ 7: 0]   <=  I_mac_data;
                            if({opcode[15: 8], I_mac_data} == PAUSE_FRAME) begin//判断是PAUSE帧
                                STATE   <=  READ_FRAME;
                                cnt     <=  cnt + 1'b1;
                            end
                            else begin
                                STATE   <=  WAIT_FRAME_END;
                                cnt     <=  3'd0;
                            end
                        end
                        2:begin pause_time[15: 8]   <=  I_mac_data; cnt <=  cnt + 1'b1;end
                        3:begin pause_time[ 7: 0]   <=  I_mac_data; cnt <=  cnt + 1'b1;end//需要暂停发送的时间
                        4:begin
                            cnt                 <=  3'd0;
                            opcode              <=  16'd0;
                            pause_time          <=  16'd0;
                            O_mac_pause_en      <=  1'b1;//通知MAC发送控制器,接收到了PAUSE帧
                            O_mac_pause_time    <=  {pause_time, 6'd0};//*512/8 = *64 = *(2^6)
                            STATE               <=  WAIT_FRAME_END;//等待帧结束
                        end
                    endcase
                else
                    STATE   <=  READ_FRAME;
            end
            WAIT_FRAME_END:begin//等待帧结束
                O_mac_pause_time    <=  22'd0;
                O_mac_pause_en      <=  1'b0;
                if(I_mac_pause_en)
                    STATE   <=  WAIT_FRAME_END;
                else
                    STATE   <=  READ_FRAME;
            end
        endcase
    end
end

endmodule

mac_tx_pause_ctrl接收到mac_pasue_en信号为高时,将接收的信息寄存,状态机跳转,等待MAC发送端发送完一帧数据,进入帧间隔等待。当MAC发送模块进入帧间隔状态后,流控模块拉高pause_flag信号,等待暂停时间结束后将信号拉低。

/*******************************mac_tx_pause_ctrl模块*********************
--以下是米联客设计的mac_tx_pause_ctrl MAC发送端,流控制器模块
1.
*********************************************************************/
`timescale 1ns/1ps
module  uimac_tx_pause_ctrl
(
    input   wire                I_clk,
    input   wire                I_reset,
    input   wire    [2:0]       I_mac_state,
    input   wire                I_mac_pause_en,
    input   wire    [21:0]      I_mac_pause_time,
    input   wire    [47:0]      I_mac_pause_addr,
    output  reg     [47:0]      O_pause_dst_mac_addr,
    output  reg                 O_pause_flag
);

reg     [21:0]          pause_clk_num;
reg     [21:0]          pause_clk_cnt;
reg     [1:0]           STATE;

localparam  WAIT_PAUSE_FRAME        =   2'd0;
localparam  WAIT_CURRENT_SEND_DONE  =   2'd1;
localparam  MAC_SEND_PAUSE          =   2'd2;

localparam  ADD_IFG     =   3'd4;

always@(posedge I_clk or posedge I_reset) begin
    if(I_reset) begin
        pause_clk_num           <=  22'd0;
        pause_clk_cnt           <=  22'd0;
        O_pause_flag            <=  1'b0;
        O_pause_dst_mac_addr    <=  48'd0;
        STATE                   <=  WAIT_PAUSE_FRAME;
    end
    else begin
        case(STATE)
            WAIT_PAUSE_FRAME:begin//等待PAUSE帧
                O_pause_flag    <=  1'b0;
                if(I_mac_pause_en) begin    //MAC接收模块接收到PAUSE帧
                    O_pause_dst_mac_addr    <=  I_mac_pause_addr;//MAC发送模块需要发送PAUSE的目的MAC地址        
                    pause_clk_num           <=  I_mac_pause_time;//PAUSE时间,在MAC接收端已经换算好需要PAUSE的时钟周期个数
                    STATE                   <=  WAIT_CURRENT_SEND_DONE;
                end
                else begin
                    O_pause_dst_mac_addr    <=  48'd0;
                    pause_clk_num           <=  22'd0;
                    STATE                   <=  WAIT_PAUSE_FRAME;                   
                end
            end
            WAIT_CURRENT_SEND_DONE:begin//等待当MAC发送状态机在I_mac_state == ADD_IFG状态的时候,设置O_pause_flag标志
                if(I_mac_state == ADD_IFG) begin
                    O_pause_flag            <=  1'b1;//设置O_pause_flag,通知MAC 帧发送模块,暂停数据发送
                    STATE                   <=  MAC_SEND_PAUSE;
                end
                else begin
                    O_pause_flag            <=  1'b0;
                    STATE                   <=  WAIT_CURRENT_SEND_DONE;
                end
            end
            MAC_SEND_PAUSE:begin//暂停数据发送,等待(pause_clk_num - 3)个时钟周期
                if(pause_clk_cnt == (pause_clk_num - 3)) begin
                    O_pause_flag            <=  1'b0;
                    O_pause_dst_mac_addr    <=  48'd0;
                    pause_clk_cnt           <=  22'd0;
                    pause_clk_num           <=  22'd0;
                    STATE                   <=  WAIT_PAUSE_FRAME;
                end
                else begin
                    O_pause_flag            <=  1'b1;//设置O_pause_flag,通知MAC 帧发送模块,暂停数据发送
                    pause_clk_cnt           <=  pause_clk_cnt + 1'b1;
                    STATE                   <=  MAC_SEND_PAUSE;
                end
            end
        endcase
    end
end

endmodule

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1982858.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SX_c语言字符带空格字符串读写_9

改项目的时候又发现c语言读字符串的一些细节这里做补充其中%[^\n]可以读取带空格的字符串 #include <stdio.h>int main(){char cmd;scanf("%c", &cmd);char time[32] {0};scanf("%s", time);printf("%c\n", cmd);printf("%s&quo…

QT使用腾讯云对象存储SDK报错:`undefined reference to qcloud_cos`

QT使用腾讯云对象存储SDK编译时报错&#xff1a;undefined reference to qcloud_cos 文章目录 QT使用腾讯云对象存储SDK编译时报错&#xff1a;undefined reference to qcloud_cos问题1&#xff1a;在VS中编译cossdk报错LNK1104 无法打开文件“PocoFoundationd.lib”问题2&…

海外媒体发稿:意大利-展新社/意大利金融报的影响力

一.IT WDPP&#xff1a;世界发展新闻社意大利站 世界发展新闻社&#xff08;展新社&#xff09;是全球顶级通讯社之一&#xff0c;其在全球多国设立分社&#xff0c;热门资讯覆盖全球18种主流语言。作为世界发展新闻社的意大利站&#xff0c;IT WDPP以其独特的视角和深度报道&…

工具学习_CONAN_Consuming Packages

1. Build a simple CMake project using Conan 首先创建一个使用最流行的 C 库之一 Zlib 的字符串压缩器应用程序&#xff0c;教程将使用 CMake 构建系统&#xff0c;但需要注意 Conan 适用于任何系统构建方式。 为了完成教程&#xff0c;首先下载对应的项目&#xff1a; git…

大学生助学贷款

大学生助学贷款 文章目录 大学生助学贷款生源地贷款1. 网上申请2. 线下申请 生源地贷款 助学贷款分为生源地贷款和高校助学贷款, 一般情况下都是申请的生源地贷款.第一次贷款, 需要现在网上申请, 申请结束之后, 会有两张表, 贷款申请表和困难认定申请表.注意: 贷款申请表和困难…

pdf转图片要怎么转?6个软件帮助你轻松转换图片格式

pdf转图片要怎么转&#xff1f;6个软件帮助你轻松转换图片格式 将PDF文件转换为图片格式是许多用户的常见需求&#xff0c;尤其是在需要展示、编辑或分享PDF内容时。以下是六款能够帮助你快速、轻松地将PDF转换为图片格式的软件&#xff1a; 迅捷PDF转换器 这是一款非常简单好…

Redis01——Redis简介

目录 NOSQL与SQL的差异 数据结构&#xff1a;结构化 VS 非结构化 数据关联&#xff1a;关系型 VS 非关系型 数据查询&#xff1a;SQL查询 VS 非SQL查询 事务特性&#xff1a;满足事 VS 没有完全满足 Redis 简介 Redis 安装 Redis 数据结构简介 Redis 常用命令&#xff…

论文被Turnitin查出AI率,会受影响吗?

无论是作业或是毕业论文的提交&#xff0c;原创性已成为了硬性标准。因此提交前都会使用Turnitin&#xff08;www.checktoo.com&#xff09;进行论文检测&#xff0c;用来确保其原创性。可当Turnitin报告能显示出论文的AI率时&#xff0c;越来越多学者开始担忧“这会对我的论文…

c++单例实践

C单例实践 在日常开发中&#xff0c;虽然太多的单例调用会让代码的耦合度变高&#xff0c;但是例如日志类这种&#xff0c;单例模式就变得非常有。所以这篇文章为大家介绍static 关键字相关知识以及如何实现自己的C单例类。 static关键字 首先让我们请出今天的主角: static。…

Brave浏览器:开启隐私保护新时代

今天电脑天空要向大家介绍一款专注隐私安全的浏览器——BraveBrowser&#xff0c;它不仅仅是一个浏览工具&#xff0c;更是你在线隐私的守护者&#xff01; BraveBrowser是一款免费且开源的网络浏览器&#xff0c;它的核心优势在于其强大的广告和跟踪器阻止功能。想象一下&…

数据要素流通交易的场景概述

00前言 数据要素作为国家重点发展方向&#xff0c;如何发挥其数据要素价值是目前研究的重点。核心的观点是流动的数据才能产生价值&#xff0c;如果数据并没有开放、共享那么价值一定是有限的。目前&#xff0c;数据流通类型主要包括数据开放、数据共享和数据交易三种。 数据…

【Python学习手册(第四版)】学习笔记16-函数基础

个人总结难免疏漏&#xff0c;请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。 本文主要介绍Python中函数的基本概念&#xff0c;作用域以及参数传递&#xff0c;函数语法以及def和return语句的操作&#xff0c;函数调用表达式的行为&#xff…

AWS DMS遇到 Error : no handler found for uri

问题描述&#xff1a; 当我按照文档[1]配置AWS DMS 目标端为OpenSearch, 并进行数据迁移的时候&#xff0c;我遇到了如下报错&#xff1a; 00015696: 2024-07-31T03:26:57 [TARGET_LOAD ]E: Elasticsearch:FAILED SourceTable:test TargetIndex:test Operation:INSERT_E…

Mysql80主从复制搭建;遇到问题 Slave_IO_Running: Connecting和Slave_SQL_Running以及解决过程

总结主要步骤 1.配置一个提供复制的账号&#xff1b; 创建用户 CREATE USER replication% IDENTIFIED BY your_password; GRANT REPLICATION SLAVE ON *.* TO replication%; FLUSH PRIVILEGES;2.修改配置 选择模式 主库配置&#xff1b; windows的得话是my.ini文件 默认这个目…

Vue项目学习(1)

1、进入cmd命令行——> vue ui ——>等等操作 2、 3、src目录下 4、vue项目的启动 &#xff08;1&#xff09; &#xff08;2&#xff09; 5、如何更改前端vue项目的端口号&#xff1f;——>去vue.config.js里配置应一个对象

云端医疗解决方案:互联网医院系统的云计算架构与实现

随着云计算技术的成熟和普及&#xff0c;医疗行业开始探索云端解决方案&#xff0c;以应对数据存储、计算能力和系统扩展性等方面的挑战。互联网医院系统作为医疗信息化的重要组成部分&#xff0c;通过云计算架构实现了高效、灵活和可扩展的医疗服务。本文将深入探讨互联网医院…

【vulnhub】DC-2靶机

信息收集 靶机扫描 nmap 192.168.93.1/24 端口扫描 网页访问 发现访问不到&#xff0c;根据显示考虑IP未遵循重定向到域名 在本机的C:\Windows\System32\drivers\etc 修改hosts⽂件&#xff0c;添加192.168.93.136 dc-2 再次进行访问&#xff0c;可以访问到 点击flag&#x…

牛客JS题(十七)总成绩排名

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 引用传值深拷贝合理封装 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charset"utf-8" /></head><body><script type"text/javascript&quo…

ResNet原理

一、ResNet基础架构 残差块 批量归一化层 二、ResNet网络参数详解 三、ResNet总结 残差网络的出现使人们摆脱了深度的束缚&#xff0c;大幅改善了深度神经网络中的模型退化问题&#xff0c;使网络层数从数十层跃升至几百上千层&#xff0c;大幅提高了模型精度&#xff0c;通用…

Linux 下 perf 的使用

目录 一、概述1、perf 作用2、常用的工具集 二、perf 工具的使用1、perf list2、perf stat3、perf top4、perf record/report4.1 perf record4.2 perf report 5、perf annotate 一、概述 1、perf 作用 perf 是一个性能分析工具&#xff08;基于 Linux 内核提供的性能事件 per…