从零开始 verilog 以太网交换机(三)MAC发送控制器的设计与实现

从零开始 verilog 以太网交换机(三)MAC发送控制器的设计与实现


🔈声明:

😃博主主页:王_嘻嘻的CSDN主页

🧨 从零开始 verilog 以太网交换机系列专栏:点击这里

🔑未经作者允许,禁止转载,侵权必删

🚩关注本专题的朋友们可以收获一个经典交换机设计的全流程,包括设计与验证(FPGA);以太网MAC的基础知识。新手朋友们还将获得一个具有竞争力的项目经历,后续整个工程和代码下载链接也都会放在csdn和公众号内

  本章将继续进行MAC发送端控制器的设计与实现,交换机完整的架构可以参考:从零开始 verilog 以太网交换机(一)架构分析。



1、MAC发送控制器功能

  MAC发送控制器的功能和接收控制器对称,负责以太网的MAC -> PHY的数据交换,将数据帧转换为MII接口形式的数据流,并根据长度进行补零,计算CRC校验等。

  其功能包括以下5点:

  • 根据以太网规范,先发送前导码,再发送帧起始符(关于前导符和帧起始符的基础知识在第二章中有介绍:MAC接收控制器的设计与实现);
  • 之后将Payload(有效荷载)从数据缓冲区读出,并发送;
  • 若数据过短,需要填充数据‘0’,使MAC帧长度符合要求;
  • 将同步计算的CRC-32校验值发送;
  • 当MAC全部发送结束后,需要按照以太网规范插入帧间等待时间,本工程中为24个cycle(时钟周期);

    2、MAC发送控制器接口

      与接收控制器正好相反,MAC发送控制器一端连接数据缓冲区和状态缓冲区,另一端连接标准MII接口,具体接口如下。

      需要注意的是,系统时钟clk和MII发送端时钟tx_clk不同,tx_dv和tx_d需要在tx_clk下控制。

    3、MAC发送控制器实现细节

      发送控制器需要根据数据帧生成CRC-32校验值,通常CRC的多项式公式可以直接通过生成器来生成。(CRC生成器将放在本号的资源栏中,有需要的可以下载,或者关注博主的公众号,也有下载链接)

      AFIFO的格式和结构都与接受控制器的相同,不了解的同学可以回看上一章内容:从零开始 verilog 以太网交换机(一)架构分析。

    3.1、功能细节分析

    1. 检查state_fifo是否为空,若非空,则从state_fifo中取出一个数据,得到需要发送的MAC帧长度信息;
    2. 发送前导码’0101’(本工程内重复6次)和帧起始符4‘b1011;
    3. 将data_fifo数据取出发送到MII接口,并同步进行CRC-32的计算,当长度过小时进行填充处理;
    4. Payload发送完毕后,将CRC-32的计算值一并发送;
    5. MAC发送结束后,等待帧间隔时间后,继续发送下一帧;

    3.2、MAC发送器核心电路设计

      由于MAC发送控制器需要制造前导码,在帧尾填充数据,将交换机内部的8-bits数据转换成MII接口的4-bits数据,且系统时钟sys_clk和MII时钟tx_clk异步,所以在该模块中设置了一级用AFIFO实现的中间队列,既便于控制数据通路,又能处理异步时钟域。

      所以发送控制器的总体架构如下:


      前级队列控制状态机负责接收交换机处理完的数据帧,并写入对应的中间队列internal_data_fifo和internal_state_fifo,在设计中前级状态机分为5个状态,R_IDLE、R_PRE、R_SEND、R_PAD和R_CRC,后四个状态分别负责向internal_data fifo存储前导码、Payload、填充‘0’和CRC校验值。

      根据每个状态需要的字节数cnt_r控制状态的跳转,每当前级state fifo非空时,发起一次接收数据帧的处理,把封装好的完整帧存储在internal data fifo中,并在最后一拍更新internal state fifo内容。

      而当internal data fifo不足以存放一个最大帧时(此最大帧需要包括前导码和CRC校验的字节数),前级队列状态机会反压输入,停止接收任何数据。

      后级队列控制器负责将接收到的完整数据帧连续不断的发送到MII接口,同样也是通过状态机的形式控制internal fifo的输出逻辑。

      后级队列控制相对容易,当internal state fifo非空时,取出实际长度数据,并按长度读取data fifo,每拍发送一个4-bits data到MII接口,发送完一个完整帧后,停止一段时间后继续开始检测下一数据帧是否准备完成。

    3.3、MAC发送器代码

      控制器的设计并不复杂,Verilog代码将放在下面,Testbench就不展示了,有需要的可以等专题结束后在资源中下载,或者去我的公众号获得链接。

    module mac_t(
    //system interface
    input           clk,
    input           rst_n,
    //MII interface
    input           tx_clk,
    output  reg     tx_dv,
    output  [3:0]   tx_d,
    //mac-r - interface mux
    output  reg     data_fifo_rd,
    input   [7:0]   data_fifo_dout,
    output  reg     state_fifo_rd,
    input   [15:0]  state_fifo_dout,
    input           state_fifo_empty
        );
        
    parameter   BCNT_MAX        = 1518;
    parameter   BCNT_MIN        = 64;
    parameter   PRE_CNT         = 7;    //前导码数量
    parameter   CRC_CNT         = 4;    //4 byte crc code
    parameter   FRAME_WAIT_CNT  = 24;
    //前级接收数据帧的状态机
    localparam  R_IDLE    =   5'b00001;
    localparam  R_PRE     =   5'b00010;
    localparam  R_SEND    =   5'b00100;
    localparam  R_PAD     =   5'b01000;
    localparam  R_CRC     =   5'b10000;
        
    //缓存向MII发送数据帧的状态机
    localparam  T_IDLE    =   3'b001;
    localparam  T_SEND    =   3'b010;
    localparam  T_WAIT    =   3'b100;
    reg     [4:0]   cur_sta_r;
    reg     [4:0]   next_sta_r;
    reg     [10:0]  cnt_r;   //内部计数器  记录还有多少byte需要发送   最大2k
    //internal data fifo变量
    reg     [7:0]   in_data_fifo_din;
    reg             in_data_fifo_wr;
    reg             in_data_fifo_rd;
    wire    [7:0]   in_data_fifo_dout;
    wire    [11:0]  in_data_fifo_wr_cnt;
    //internal state fifo变量
    reg     [15:0]  in_state_fifo_din;
    reg             in_state_fifo_wr;
    wire            in_state_fifo_rd;
    wire    [15:0]  in_state_fifo_dout;
    wire            in_state_fifo_full;
    wire            in_state_fifo_empty;
    wire    bp;  //反压信号,当internal fifo空间不够一个最大帧时,反压上级,拒绝接收一切数据
    //crc 变量
    reg             crc_init;
    wire            crc_cal;
    wire            crc_vld;
    wire    [7:0]   crc_dout;
    wire    [7:0]   crc_din;
    assign bp = (in_data_fifo_wr_cnt[11:0] > 4096-BCNT_MAX-PRE_CNT-1) | in_state_fifo_full;
    assign crc_din[7:0] = in_data_fifo_din[7:0];
    //后级状态机变量
    reg     [2:0]   cur_sta_t;
    reg     [2:0]   next_sta_t;
    reg     [11:0]  cnt_t;           //以半字节为单位
    reg             in_state_fifo_empty_dly;
    //============================前级状态机=========================
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            cur_sta_r[4:0] <= R_IDLE;
        else
            cur_sta_r[4:0] <= next_sta_r[4:0];
    end
    always @(*)begin
        case(cur_sta_r[4:0])
            R_IDLE: next_sta_r[4:0] = (!bp & !state_fifo_empty) ? R_PRE : R_IDLE;  //IDLE态时 只有internal fifo空间足够 且 前级已有处理完的数据帧才能进入下一状态
            
            R_PRE:  next_sta_r[4:0] = (cnt_r[10:0]==11'b1) ? R_SEND : R_PRE;        //发送完所有前导码和起始符后 进入下一状态
            
            R_SEND: next_sta_r[4:0] = (cnt_r[10:0]==11'b1) ? ( state_fifo_dout[10:0]< BCNT_MIN-CRC_CNT ) ? R_PAD : R_CRC : R_SEND;        //发送完所有数据帧  进入下一状态 cnt每一状态都会更新
            
            R_PAD:  next_sta_r[4:0] = (cnt_r[10:0]==11'b1) ? R_CRC : R_PAD;         //完成所有填充 进入下一状态
            
            R_CRC:  next_sta_r[4:0] = (cnt_r[10:0]==11'b1) ? R_IDLE: R_CRC;         //CRC校验需要4 cycle 结束后,将校验值填入 返回IDLE态
            
            default: next_sta_r[4:0] = R_IDLE;       
        endcase
    end
    //cnt采样
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            cnt_r[10:0] <= 11'b0;
        else begin
            case(cur_sta_r[4:0])
                R_IDLE: cnt_r[10:0] <= PRE_CNT ;
                R_PRE:  cnt_r[10:0] <= cnt_r[10:0] > 11'b1 ? cnt_r[10:0] - 11'b1 : state_fifo_dout[10:0];
                R_SEND: cnt_r[10:0] <= cnt_r[10:0] > 11'b1 ? cnt_r[10:0] - 11'b1 : (state_fifo_dout[10:0] < (BCNT_MIN-CRC_CNT)) ? BCNT_MIN-CRC_CNT-state_fifo_dout[10:0] : CRC_CNT;
                R_PAD:  cnt_r[10:0] <= cnt_r[10:0] > 11'b1 ? cnt_r[10:0] - 11'b1 : CRC_CNT;
                R_CRC:  cnt_r[10:0] <= cnt_r[10:0] - 11'b1;
                default: cnt_r[10:0] <= 11'b0;
            endcase
        end
    end
    //==============crc var 控制================
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            crc_init <= 1'b0;
        else if(cur_sta_r[4:0]==R_IDLE && next_sta_r[4:0]==R_PRE)
            crc_init <= 1'b1;
        else
            crc_init <= 1'b0;
    end
    assign crc_cal = cur_sta_r[4:0]==R_SEND | cur_sta_r[4:0]==R_PAD;
    assign crc_vld = cur_sta_r[4:0]==R_SEND | cur_sta_r[4:0]==R_PAD | cur_sta_r[4:0]==R_CRC;
    //=========================================
    //out data fifo控制
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            data_fifo_rd <= 1'b0;
        else if(cur_sta_r[4:0]==R_PRE && cnt_r[10:0]==11'b1)
            data_fifo_rd <= 1'b1;
        else if(cur_sta_r[4:0]==R_SEND && cnt_r[10:0]==11'b1)
            data_fifo_rd <= 1'b0;
    end
    //out state fifo控制
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            state_fifo_rd <= 1'b0;
        else if(cur_sta_r[4:0]==R_CRC && cnt_r[10:0]==11'b1)
            state_fifo_rd <= 1'b1;
        else
            state_fifo_rd <= 1'b0;
    end
    //internal data fifo控制
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            in_data_fifo_wr <= 1'b0;
        else if(cur_sta_r[4:0]==R_IDLE)
            in_data_fifo_wr <= 1'b0;
        else if(next_sta_r[4:0]==R_PRE)
            in_data_fifo_wr <= 1'b1;
    end
    always @(posedge clk or negedge rst_n)begin   //TODO
        if(!rst_n)
            in_data_fifo_din[7:0] <= 8'h0;
        else if(cur_sta_r[4:0]==R_IDLE)
            in_data_fifo_din[7:0] <= 8'h55;
        else if(cur_sta_r[4:0]==R_PRE && cnt_r[10:0]==11'd1)
            in_data_fifo_din[7:0] <= 8'hd5;
        else if(cur_sta_r[4:0]==R_SEND)
            in_data_fifo_din[7:0] <= data_fifo_dout[7:0];
        else if(cur_sta_r[4:0]==R_PAD)
            in_data_fifo_din[7:0] <= 8'h0;
        else if(cur_sta_r[4:0]==R_CRC)
            in_data_fifo_din[7:0] <= crc_dout[7:0];
    end
    //internal state fifo控制
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            in_state_fifo_wr <= 1'b0;
        else if(cur_sta_r[4:0]==R_CRC && cnt_r[10:0]==11'b1)
            in_state_fifo_wr <= 1'b1;
        else
            in_state_fifo_wr <= 1'b0;
    end
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            in_state_fifo_din[15:0] <= 16'b0;
        else if(cur_sta_r[4:0]==R_CRC && cnt_r[10:0]==11'b1)
            in_state_fifo_din[15:0] <= state_fifo_dout[15:0];
        else
            in_state_fifo_din[15:0] <= 16'b0; 
    end
    //==========================================================================
    //=================================后级状态机===============================
    always @(posedge tx_clk or negedge rst_n)begin
        if(!rst_n)
            cur_sta_t[2:0] <= T_IDLE;
        else
            cur_sta_t[2:0] <= next_sta_t[2:0];
    end
    always @(posedge tx_clk or negedge rst_n)begin  //打一拍 为了in state fifo read over, fsm enter next state
        if(!rst_n)
            in_state_fifo_empty_dly <= 1'b1;
        else
            in_state_fifo_empty_dly <= in_state_fifo_empty;
    end
    always @(*)begin
        case(cur_sta_t[2:0])
            T_IDLE: next_sta_t[2:0] = !in_state_fifo_empty_dly ? T_SEND : T_IDLE;
            T_SEND: next_sta_t[2:0] = cnt_t[11:0]==12'b1 ? T_WAIT : T_SEND;
            T_WAIT: next_sta_t[2:0] = cnt_t[11:0]==12'b1 ? T_IDLE : T_WAIT;
            default:next_sta_t[2:0] = T_IDLE;
        endcase
    end
    //发送状态时 cnt采样
    always @(posedge tx_clk or negedge rst_n)begin
        if(!rst_n)
            cnt_t[11:0] <= 12'b0;
        else begin
            case(cur_sta_t[2:0])
                T_IDLE: cnt_t[11:0] <= !in_state_fifo_empty_dly ? (in_state_fifo_dout[10:0]+PRE_CNT+CRC_CNT)<<1'b1 : 12'b0;
                T_SEND: cnt_t[11:0] <= cnt_t[11:0]==12'b1 ? FRAME_WAIT_CNT : cnt_t[11:0] - 12'b1;
                T_WAIT: cnt_t[11:0] <= cnt_t[11:0] - 12'b1;
                default: cnt_t[11:0] <= 12'b0;
            endcase
        end
    end
    //internal state fifo 控制
    assign in_state_fifo_rd = cur_sta_t[2:0]==T_IDLE & !in_state_fifo_empty;
    //internal data fifo 控制
    always @(posedge tx_clk or negedge rst_n)begin    //fifo dout需要提前读取后,dout上才有数据,并不是数据先在总线上
        if(!rst_n)
            in_data_fifo_rd <= 1'b0;
        else if( (cur_sta_t[2:0]==T_IDLE & !in_state_fifo_empty) | (cur_sta_t[2:0]==T_SEND & cnt_t[0]) )
            in_data_fifo_rd <= 1'b1;
        else
            in_data_fifo_rd <= 1'b0;
    end
    //MII接口控制
    always @(posedge tx_clk or negedge rst_n)begin
        if(!rst_n)
            tx_dv <= 1'b0;
        else if(cur_sta_t[2:0]==T_SEND)
            tx_dv <= 1'b1;
        else
            tx_dv <= 1'b0;
    end
    assign tx_d[3:0] = cnt_t[0] ? in_data_fifo_dout[3:0] : in_data_fifo_dout[7:4];
        
    data_fifo 
        x_internal_data_fifo(
        .rst(~rst_n),
        .wr_clk(clk),
        .rd_clk(tx_clk),
        .din(in_data_fifo_din[7:0]),
        .wr_en(in_data_fifo_wr),
        .rd_en(in_data_fifo_rd),
        .dout(in_data_fifo_dout[7:0]),
        .full(),
        .empty(),
        .rd_data_count(),
        .wr_data_count(in_data_fifo_wr_cnt[11:0])
      );
      
    state_fifo 
        x_internal_state_fifo(
        .rst(~rst_n),
        .wr_clk(clk),
        .rd_clk(tx_clk),
        .din(in_state_fifo_din[15:0]),
        .wr_en(in_state_fifo_wr),
        .rd_en(in_state_fifo_rd),
        .dout(in_state_fifo_dout[15:0]),
        .full(in_state_fifo_full),
        .empty(in_state_fifo_empty)
      );  
    crc32 x_crc32(
        .clk(clk),
        .rst_n(rst_n),
        .data(crc_din[7:0]),
        .init(crc_init),
        .cal(crc_cal),
        .vld(crc_vld),
        .crc_reg(),
        .crc(crc_dout[7:0])
        );    
        
    endmodule
    




    搜索关注我的微信公众号【IC墨鱼仔】,获取我的更多IC干货分享!