今天给大侠带来基于FPGA的以太网控制器(MAC)设计,由于篇幅较长,分三篇。今天带来第二篇,中篇,以太网控制器(MAC)程序的实现。话不多说,上货。

导读

当前,互联网已经极大地改变了我们的生产和生活。与之相适应的,在嵌入式系统的研究开发方面,也越来越重视网络功能。嵌入式系统已经不再局限于一个个孤立的控制、处理单元,而是走向网络集成化,从而实现了多个系统的集中控制、信息共享。

以太网(Ethernet)技术在嵌入式系统上的开发应用,已经成为当前嵌入式研究领域的技术热点之一。一方面,与传统的 RS-485、CAN 等相比较,以太网更加高速、通用,而且还可以直接与 Internet 相连接,提供更大范围的远程访问;此外,经过适当剪裁和优化的 TCP/IP 协议栈,也完全可以适应工业用途的需求。另一方面,相对于新兴的 USB 2.0、IEEE 1394 等总线,以太网技术在传输距离、布线成本以及控制软件的通用性上都有明显的优势。

基于以太网的嵌入式系统,在以下方面都有良好的应用前景:

• 工业:工业控制、网络仪表、远程的分布式数据采集……

• 家庭自动化:智能家庭、信息家电、家庭网关……

• 商业:远程销售平台、智能自动售货机、公共电话卡发行系统……

• 环保:水源和空气污染监测,防洪体系及水土质量监测、堤坝安全……

• 其他:交通管理、车辆导航、自动抄表……

因此在使用 FPGA 设计各种嵌入式应用系统时,需要考虑为系统提供以太网接口。本章将 通过 FPGA 实现一个以太网控制器(MAC)的实例,详细介绍实现过程。

第二篇内容摘要:本篇会介绍以太网控制器(MAC)程序的实现,包括顶层程序、媒体无关接口模块(Media Independent Interface Module)、数据发送模块、数据接收模块、控制模块等相关内容。

三、以太网控制器(MAC)程序的实现

本篇主要介绍以太网控制器(MAC)程序的主要模块。

3.1 顶层程序——eth_top

顶层程序主要连接并控制各个子模块,代码如下:

module eth_top(//输入输出列表                wb_clk_i, wb_rst_i, wb_dat_i, wb_dat_o,              ….              );// 输入输出信号  input clk_i; // 时钟信号  input rst_i; // 复位信号  input [31:0] dat_i; // 数据输入信号    output [31:0] dat_o; // 数据输出信号  output err_o; // 错误输出信号  ……  //连接各个子模块//连接媒体无关接口模块  eth_miim miim1(                  .Clk(wb_clk_i), .Reset(wb_rst_i), .Divider(r_                  ClkDiv),                  ……                );                //连接寄存器模块  eth_registers ethreg1(                        .DataIn(wb_dat_i), .Address(wb_adr_i[9:2]), .Rw                        (wb_we_i),                        ……                      );                      //连接控制模块  eth_maccontrol maccontrol1(                              .MTxClk(mtx_clk_pad_i), .TPauseRq(TPauseRq),                              ……                            );//连接数据发送模块  eth_txethmac txethmac1(                          .MTxClk(mtx_clk_pad_i), .Reset(wb_rst_i), .CarrierSense(T                          xCarrierSense),                          ……                        );                        //连接数据接收模块  eth_rxethmac rxethmac1(                          .MRxClk(mrx_clk_pad_i), .MRxDV(MRxDV_Lb), .MRxD(MRxD_                          Lb),                          ……                        );                        //发送数据暂停请求同步  always @ (posedge mtx_clk_pad_i or posedge wb_rst_i)  begin    if(wb_rst_i)      begin        TxPauseRq_sync1 <= #Tp 1'b0;        TxPauseRq_sync2 <= #Tp 1'b0;        TxPauseRq_sync3 <= #Tp 1'b0;      end    else      begin        TxPauseRq_sync1 <= #Tp (r_TxPauseRq & r_TxFlow);        TxPauseRq_sync2 <= #Tp TxPauseRq_sync1;        TxPauseRq_sync3 <= #Tp TxPauseRq_sync2;      end  end        always @ (posedge mtx_clk_pad_i or posedge wb_rst_i)  begin    if(wb_rst_i)      TPauseRq <= #Tp 1'b0;    else      TPauseRq <= #Tp TxPauseRq_sync2 & (~TxPauseRq_sync3);  end  //连接状态显示模块  eth_macstatus macstatus1(                            .MRxClk(mrx_clk_pad_i), .Reset(wb_rst_i),                            ….                          );  endmodule

3.2 媒体无关接口模块(Media Independent Interface Module)

媒体无关接口模块提供一个连接到外部以太网 PHY 控制器的接口,用来设置 PHY 控制器的寄存器并获得其状态信息,如图 8 所示。

图 8 媒体无关接口模块

媒体无关接口模块包括以下 3 个子模块和控制逻辑。

• 时钟产生模块:产生 MII 接口的时钟信号,这个时钟信号需要满足外部 PHY 芯片对时钟的要求。

• 输出控制模块:因为 MII 连接到外部 PHY 的数据线实际只有一根线,输出控制模块需要将输出、输入和使能信号联合形成一个信号。

• 移位寄存器模块:将需要传输到外部 PHY 芯片的数据串行化,同时将从外部 PHY 芯片接收的串行数据并行保存到寄存器中。

• 控制逻辑:实现读、写和查找等请求信号的同步,提供输入数据的锁存信号,提供移位输出数据的字节选择信号,提供 MII 的计数器,提供更新相关寄存器的信号。

下面是媒体无关接口模块的主要代码:

`include "timescale.v"module eth_miim( Clk, Reset, Divider, NoPre, CtrlData, Rgad, Fiad, WCtrlData, RStat, ScanStat,Mdi,Mdo, MdoEn, Mdc, Busy, Prsd, LinkFail, Nvalid, WCtrlDataStart, RStatStart,UpdateMIIRX_DATAReg );//输出输入信号  input Clk; //主时钟  input Reset; // 复位信号  input [7:0] Divider; // 主时钟的分频参数  input [15:0] CtrlData; //写到外部 PHY 芯片寄存器的控制数据  input [4:0] Rgad; // 外部 PHY 芯片寄存器的地址  input [4:0] Fiad; // PHY 的地址  input NoPre; // 无报头  input WCtrlData; // 写控制数据操作  input RStat; // 读状态操作  input ScanStat; // 查找状态操作  input Mdi; // MII 数据输入    output Mdc; // MII 数据时钟  output Mdo; // MII 数据输入  output MdoEn; // MII 数据输出使能信号  output Busy; // 忙信号  output LinkFail; // 连接整体信号  output Nvalid; // 非法状态  output [15:0] Prsd; // 从外部 PHY 芯片读取状态数据  output WCtrlDataStart; // 复位 MII 命令寄存器中 WCTRLDATA 位的信号  output RStatStart; // 复位 MII 命令寄存器中 RSTAT 位的信号  output UpdateMIIRX_DATAReg;//用读数据来更新 MII 的 RX_DATA 寄存器  parameter Tp = 1;  //寄存器  reg Nvalid;  ………  //产生结束忙信号,用来结束 MII 操作  always @ (posedge Clk or posedge Reset)  begin    if(Reset)      begin        EndBusy_d <= #Tp 1'b0;        EndBusy <= #Tp 1'b0;      end    else      begin        EndBusy_d <= #Tp ~InProgress_q2 & InProgress_q3;        EndBusy <= #Tp EndBusy_d;      end  end  // 更新 MII 的 RX_DATA 寄存器  always @ (posedge Clk or posedge Reset)  begin    if(Reset)      UpdateMIIRX_DATAReg <= #Tp 0;    else if(EndBusy & ~WCtrlDataStart_q)      UpdateMIIRX_DATAReg <= #Tp 1;    else      UpdateMIIRX_DATAReg <= #Tp 0;  end  //产生延迟信号  always @ (posedge Clk or posedge Reset)  begin    if(Reset)      begin        WCtrlData_q1 <= #Tp 1'b0;        WCtrlData_q2 <= #Tp 1'b0;        WCtrlData_q3 <= #Tp 1'b0;        RStat_q1 <= #Tp 1'b0;        RStat_q2 <= #Tp 1'b0;        RStat_q3 <= #Tp 1'b0;        ScanStat_q1 <= #Tp 1'b0;        ScanStat_q2 <= #Tp 1'b0;        SyncStatMdcEn <= #Tp 1'b0;      end    else      begin        WCtrlData_q1 <= #Tp WCtrlData;        WCtrlData_q2 <= #Tp WCtrlData_q1;        WCtrlData_q3 <= #Tp WCtrlData_q2;        RStat_q1 <= #Tp RStat;        RStat_q2 <= #Tp RStat_q1;        RStat_q3 <= #Tp RStat_q2;        ScanStat_q1 <= #Tp ScanStat;        ScanStat_q2 <= #Tp ScanStat_q1;        if(MdcEn)        SyncStatMdcEn <= #Tp ScanStat_q2;      end  end  //产生开始命令,写控制数据或者读状态  always @ (posedge Clk or posedge Reset)  begin    if(Reset)      begin        WCtrlDataStart <= #Tp 1'b0;        WCtrlDataStart_q <= #Tp 1'b0;        RStatStart <= #Tp 1'b0;      end    else      begin        if(EndBusy)          begin            WCtrlDataStart <= #Tp 1'b0;            RStatStart <= #Tp 1'b0;          end        else          begin            if(WCtrlData_q2 & ~WCtrlData_q3)              WCtrlDataStart <= #Tp 1'b1;            if(RStat_q2 & ~RStat_q3)              RStatStart <= #Tp 1'b1;              WCtrlDataStart_q <= #Tp WCtrlDataStart;          end      end  end  // 产生非法信号,指示当前状态非法  always @ (posedge Clk or posedge Reset)  begin    if(Reset)      Nvalid <= #Tp 1'b0;    else      begin        if(~InProgress_q2 & InProgress_q3)          begin            Nvalid <= #Tp 1'b0;          end        else          begin            if(ScanStat_q2 & ~SyncStatMdcEn)              Nvalid <= #Tp 1'b1;          end      end  end  // 用来产生各种操作的信号  always @ (posedge Clk or posedge Reset)  begin    if(Reset)      begin        WCtrlDataStart_q1 <= #Tp 1'b0;        WCtrlDataStart_q2 <= #Tp 1'b0;        RStatStart_q1 <= #Tp 1'b0;        RStatStart_q2 <= #Tp 1'b0;        InProgress_q1 <= #Tp 1'b0;        InProgress_q2 <= #Tp 1'b0;        InProgress_q3 <= #Tp 1'b0;        LatchByte0_d <= #Tp 1'b0;        LatchByte1_d <= #Tp 1'b0;        LatchByte <= #Tp 2'b00;      end    else      begin        if(MdcEn)          begin            WCtrlDataStart_q1 <= #Tp WCtrlDataStart;            WCtrlDataStart_q2 <= #Tp WCtrlDataStart_q1;            RStatStart_q1 <= #Tp RStatStart;            RStatStart_q2 <= #Tp RStatStart_q1;            LatchByte[0] <= #Tp LatchByte0_d;            LatchByte[1] <= #Tp LatchByte1_d;            LatchByte0_d <= #Tp LatchByte0_d2;            LatchByte1_d <= #Tp LatchByte1_d2;            InProgress_q1 <= #Tp InProgress;            InProgress_q2 <= #Tp InProgress_q1;            InProgress_q3 <= #Tp InProgress_q2;          end      end  end  // 产生各种操作信号  assign WriteDataOp = WCtrlDataStart_q1 & ~WCtrlDataStart_q2;  assign ReadStatusOp = RStatStart_q1 & ~RStatStart_q2;  assign ScanStatusOp = SyncStatMdcEn & ~InProgress & ~InProgress_q1 & ~InProgress_q2;  assign StartOp = WriteDataOp | ReadStatusOp | ScanStatusOp;  // 忙信号  assign Busy = WCtrlDataStart | RStatStart | SyncStatMdcEn | EndBusy | InProgress | InProgress_q3  | Nvalid;  // 产生进行中信号,指示当前操作正在进行// 产生写操作信号,指示写数据正在进行  always @ (posedge Clk or posedge Reset)  begin    if(Reset)      begin        InProgress <= #Tp 1'b0;        WriteOp <= #Tp 1'b0;      end    else      begin        if(MdcEn)          begin            if(StartOp)              begin                if(~InProgress)                  WriteOp <= #Tp WriteDataOp;                  InProgress <= #Tp 1'b1;              end            else              begin              if(EndOp)                begin                  InProgress <= #Tp 1'b0;                  WriteOp <= #Tp 1'b0;                end              end          end      end  end  // 位计数器  always @ (posedge Clk or posedge Reset)  begin    if(Reset)      BitCounter[6:0] <= #Tp 7'h0;    else      begin        if(MdcEn)          begin            if(InProgress)              begin                if(NoPre & ( BitCounter == 7'h0 ))                  BitCounter[6:0] <= #Tp 7'h21;                else                  BitCounter[6:0] <= #Tp BitCounter[6:0] + 1'b1;              end            else              BitCounter[6:0] <= #Tp 7'h0;          end      end  end  // 当计数器达到 63 时结束操作  assign EndOp = BitCounter==63;  assign ByteSelect[0] = InProgress & ((NoPre & (BitCounter == 7'h0)) | (~NoPre & (BitCounter  == 7'h20)));  assign ByteSelect[1] = InProgress & (BitCounter == 7'h28);  assign ByteSelect[2] = InProgress & WriteOp & (BitCounter == 7'h30);  assign ByteSelect[3] = InProgress & WriteOp & (BitCounter == 7'h38);//当从移位寄存器中读取状态数据时锁存字节选择信号  assign LatchByte1_d2 = InProgress & ~WriteOp & BitCounter == 7'h37;  assign LatchByte0_d2 = InProgress & ~WriteOp & BitCounter == 7'h3F;  //连接时钟产生模块  eth_clockgen  clkgen(        .Clk(Clk),         .Reset(Reset),         .Divider(Divider[7:0]),         .MdcEn(MdcEn),         .MdcEn_n(MdcEn_n),         .Mdc(Mdc)      );      // 连接移位寄存器模块  eth_shiftreg  shftrg(          .Clk(Clk),           .Reset(Reset),           .MdcEn_n(MdcEn_n),           .Mdi(Mdi),           .Fiad(Fiad),           .Rgad(Rgad),          .CtrlData(CtrlData),           .WriteOp(WriteOp),           .ByteSelect(ByteSelect),           .LatchByte(LatchByte),          .ShiftedBit(ShiftedBit),           .Prsd(Prsd),           .LinkFail(LinkFail)        )        // 连接输出控制模块  eth_outputcontrol  outctrl(          .Clk(Clk),           .Reset(Reset),           .MdcEn_n(MdcEn_n),           .InProgress(InProgress),          .ShiftedBit(ShiftedBit),           .BitCounter(BitCounter),           .WriteOp(WriteOp),           .NoPre(NoPre),          .Mdo(Mdo),           .MdoEn(MdoEn)        )        endmodule

3.3 数据发送模块数据

发送模块负责发送数据,通过主机接口从上层协议获得要发送的数据,同时获得数据帧的起始和结束信号;还要设置信号通知上层协议和外部 PHY 芯片。数据发送模块主要由计数器模块、数据发送状态机、错误处理模块和 CRC 校验模块等 4 部分组成,如图 9 所示。

图 9 数据发送模块结构

这 4 部分的功能描述如下。

• 数据发送状态机:完成数据发送的整体控制。

• 计数器模块:包括数据发送中所有需要的计数器。

• CRC 校验模块:产生 32 位的 CRC 校验序列,添加在发送数据的后面。

• 错误处理模块:当发送数据冲突后,在重新发送数据前产生一个随机的延迟,从而减少发送数据冲突的次数

当发生如下情况时,数据发送过程结束。

• 数据发送成功结束,设置 TxDone 信号。

• 数据发送过程需要重复,设置 TxRetry 信号,一般发生在数据冲突后。

• 退出数据发送过程,设置 TxAbort 信号。

数据发送模块的顶层程序完成 4 个部分的连接和控制,主要代码如下:

`include "timescale.v"module eth_txethmac (MTxClk, Reset, TxStartFrm, TxEndFrm, TxUnderRun, TxData, CarrierSense,  Collision, Pad, CrcEn, FullD, HugEn, DlyCrcEn, MinFL, MaxFL, IPGT, IPGR1, IPGR2, CollValid,  MaxRet, NoBckof, ExDfrEn, MTxD, MTxEn, MTxErr, TxDone, TxRetry, TxAbort,  TxUsedData, WillTransmit, ResetCollision, RetryCnt, StartTxDone, StartTxAbort,  MaxCollisionOccured,  LateCollision, DeferIndication, StatePreamble, StateData);  parameter Tp = 1;  //输入、输出信号  input MTxClk; //传输时钟  input Reset; // 复位  input TxStartFrm; // 数据包的起始帧  input TxEndFrm; // 数据包的结束帧  input TxUnderRun;  input [7:0] TxData; //数据内容  …..  //寄存器  reg [3:0] MTxD;  …..  //各个信号  assign ResetCollision = ~(StatePreamble | (|StateData) | StatePAD | StateFCS);  assign ExcessiveDeferOccured = TxStartFrm & StateDefer & ExcessiveDefer &  ~StopExcessiveDeferOccured;  assign StartTxDone = ~Collision & (StateFCS & NibCntEq7 | StateData[1] & TxEndFrm & (~Pad |  Pad & NibbleMinFl) & ~CrcEn);  assign UnderRun = StateData[0] & TxUnderRun & ~Collision;  assign TooBig = ~Collision & MaxFrame & (StateData[0] & ~TxUnderRun | StateFCS);  assign StartTxRetry = StartJam & (ColWindow & ~RetryMax) & ~UnderRun;  assign LateCollision = StartJam & ~ColWindow & ~UnderRun;  assign MaxCollisionOccured = StartJam & ColWindow & RetryMax;  assign StateSFD = StatePreamble & NibCntEq15;  assign StartTxAbort = TooBig | UnderRun | ExcessiveDeferOccured | LateCollision |  MaxCollisionOccured;  // 停止额外的延迟  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      StopExcessiveDeferOccured <= #Tp 1'b0;    else      begin        if(~TxStartFrm)          StopExcessiveDeferOccured <= #Tp 1'b0;        else          if(ExcessiveDeferOccured)            StopExcessiveDeferOccured <= #Tp 1'b1;      end  end  //数据冲突  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      ColWindow <= #Tp 1'b1;    else      begin        if(~Collision & ByteCnt[5:0] == CollValid[5:0] & (StateData[1] | StatePAD & NibCnt[0]          | StateFCS & NibCnt[0]))          ColWindow <= #Tp 1'b0;        else          if(StateIdle | StateIPG)            ColWindow <= #Tp 1'b1;      end  end  // 开始  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      StatusLatch <= #Tp 1'b0;    else      begin        if(~TxStartFrm)          StatusLatch <= #Tp 1'b0;        else          if(ExcessiveDeferOccured | StateIdle)            StatusLatch <= #Tp 1'b1;      end  end  //发送用过的数据包  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      TxUsedData <= #Tp 1'b0;    else      TxUsedData <= #Tp |StartData;  end//发送数据包结束  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      TxDone <= #Tp 1'b0;    else      begin        if(TxStartFrm & ~StatusLatch)          TxDone <= #Tp 1'b0;        else          if(StartTxDone)          TxDone <= #Tp 1'b1;      end  end  // 重新发送数据包  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      TxRetry <= #Tp 1'b0;    else      begin        if(TxStartFrm & ~StatusLatch)          TxRetry <= #Tp 1'b0;        else          if(StartTxRetry)            TxRetry <= #Tp 1'b1;      end  end// 退出数据包发送  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      TxAbort <= #Tp 1'b0;    else      begin        if(TxStartFrm & ~StatusLatch & ~ExcessiveDeferOccured)          TxAbort <= #Tp 1'b0;        else          if(StartTxAbort)            TxAbort <= #Tp 1'b1;      end  end  //重新计数  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      RetryCnt[3:0] <= #Tp 4'h0;    else      begin        if(ExcessiveDeferOccured | UnderRun | TooBig | StartTxDone | TxUnderRun          | StateJam & NibCntEq7 & (~ColWindow | RetryMax))          RetryCnt[3:0] <= #Tp 4'h0;        else          if(StateJam & NibCntEq7 & ColWindow & (RandomEq0 | NoBckof) | StateBackOff &            RandomEqByteCnt)            RetryCnt[3:0] <= #Tp RetryCnt[3:0] + 1'b1;      end  end    assign RetryMax = RetryCnt[3:0] == MaxRet[3:0];  // 同时传输 4 位数据  always @ (StatePreamble or StateData or StateData or StateFCS or StateJam or StateSFD or TxData  or Crc or NibCnt or NibCntEq15)  begin    if(StateData[0])      MTxD_d[3:0] = TxData[3:0]; // 低四位    else      if(StateData[1])        MTxD_d[3:0] = TxData[7:4]; //高四位      else        if(StateFCS)          MTxD_d[3:0] = {~Crc[28], ~Crc[29], ~Crc[30], ~Crc[31]}; // CRC 校验序列        else          if(StateJam)            MTxD_d[3:0] = 4'h9;          else            if(StatePreamble)              if(NibCntEq15)                MTxD_d[3:0] = 4'hd; // 帧起始分隔符,SFD              else                MTxD_d[3:0] = 4'h5; // 报头            else                MTxD_d[3:0] = 4'h0;  end  // 传输使能  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      MTxEn <= #Tp 1'b0;    else      MTxEn <= #Tp StatePreamble | (|StateData) | StatePAD | StateFCS | StateJam;  end  // 传输四位字节  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      MTxD[3:0] <= #Tp 4'h0;    else      MTxD[3:0] <= #Tp MTxD_d[3:0];  end// 传输错误  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      MTxErr <= #Tp 1'b0;    else      MTxErr <= #Tp TooBig | UnderRun;  end  // 即将传输  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      WillTransmit <= #Tp 1'b0;    else      WillTransmit <= #Tp StartPreamble | StatePreamble | (|StateData) | StatePAD | StateFCS|StateJam;  end  //数据包完成标志  assign PacketFinished_d = StartTxDone | TooBig | UnderRun | LateCollision | MaxCollisionOccured  | ExcessiveDeferOccured;// 数据包结束  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      begin        PacketFinished <= #Tp 1'b0;        PacketFinished_q <= #Tp 1'b0;      end    else      begin        PacketFinished <= #Tp PacketFinished_d;        PacketFinished_q <= #Tp PacketFinished;      end  end  // 连接计数器模块  eth_txcounters txcounters1(                              .StatePreamble(StatePreamble),                               .StateIPG(StateIPG),                               .StateData(StateData),                              .StatePAD(StatePAD),                               .StateFCS(StateFCS),                               .StateJam(StateJam),                               .StateBackOff(StateBackOff),                              .StateDefer(StateDefer),                               .StateIdle(StateIdle),                               .StartDefer(StartDefer),                               .StartIPG(StartPG),                              .StartFCS(StartFCS),                               .StartJam(StartJam),                               .TxStartFrm(TxStartFrm),                               .MTxClk(MTxClk),                              .Reset(Reset),                               .MinFL(MinFL),                               .MaxFL(MaxFL),                               .HugEn(HugEn),                               .ExDfrEn(ExDfrEn),                              .PacketFinished_q(PacketFinished_q),                               .DlyCrcEn(DlyCrcEn),                               .StartBackoff(StartBackoff),                              .StateSFD(StateSFD),                               .ByteCnt(ByteCnt),                               .NibCnt(NibCnt),                               .ExcessiveDefer(ExcessiveDefer),                              .NibCntEq7(NibCntEq7),                               .NibCntEq15(NibCntEq15),                               .MaxFrame(MaxFrame),                               .NibbleMinFl(NibbleMinFl),                              .DlyCrcCnt(DlyCrcCnt)                             );// 连接状态机模块  eth_txstatem txstatem1 (                          .MTxClk(MTxClk),                           .Reset(Reset),                           .ExcessiveDefer(ExcessiveDefer),                          .CarrierSense(CarrierSense),                          .NibCnt(NibCnt[6:0]),                           .IPGT(IPGT),                           .IPGR1(IPGR1),                           .IPGR2(IPGR2),                           .FullD(FullD),                           .TxStartFrm(TxStartFrm),                           .TxEndFrm(TxEndFrm),                           .TxUnderRun(TxUnderRun),                           .Collision(Collision),                          .UnderRun(UnderRun),                           .StartTxDone(StartTxDone),                           .TooBig(TooBig),                           .NibCntEq7(NibCntEq7),                          .NibCntEq15(NibCntEq15),                           .MaxFrame(MaxFrame),                           .Pad(Pad), .CrcEn(CrcEn),                          .NibbleMinFl(NibbleMinFl),                          .RandomEq0(RandomEq0),                           .ColWindow(ColWindow),                           .RetryMax(RetryMax),                          .NoBckof(NoBckof),                          .RandomEqByteCnt(RandomEqByteCnt),                           .StateIdle(StateIdle),                          .StateIPG(StateIPG),                          .StatePreamble(StatePreamble),                          .StateData(StateData),                           .StatePAD(StatePAD),                          .StateFCS(StateFCS),                          .StateJam(StateJam),                           .StateJam_q(StateJam_q),                           .StateBackOff(StateBackOff),                          .StateDefer(StateDefer),                          .StartFCS(StartFCS),                           .StartJam(StartJam),                           .StartBackoff(StartBackoff),                          .StartDefer(StartDefer),                          .DeferIndication(DeferIndication),                           .StartPreamble(StartPreamble),                           .StartData(StartData),                          .StartIPG(StartIPG)                        );// 连接 CRC 模块  eth_crc(          .Clk(MTxClk),           .Reset(Reset),           .Data(Data_Crc),           .Enable(Enable_Crc),           .Initialize(Initialize_Crc),          .Crc(Crc),           .CrcError(CrcError)        );        //连接随机延迟模块  eth_random random1(                      .MTxClk(MTxClk),                       .Reset(Reset),                       .StateJam(StateJam),                       .StateJam_q(StateJam_q),                       .RetryCnt(RetryCnt),                      .NibCnt(NibCnt),                       .ByteCnt(ByteCnt[9:0]),                       .RandomEq0(RandomEq0),                       .RandomEqByteCnt(RandomEqByteCnt)                    );  endmodule

A.数据发送状态机

数据发送的整个过程由一个状态机控制完成。数据发送过程主要包括以下状态。

• StateIdle:系统复位后的状态。

• StatePreamble:添加报头。

• StateData0:通知主机接口提供下一个要传输的数据。

• StateData1:发送数据。

• StateFCS:根据要发送的数据产生校验序列。

• StateDefer:延迟。

• StateIPG:帧间隔。

• StatePAD:当发送数据比数据帧最小值(46 字节)小时,补充“0”直到满足帧数据格式的要求。

发送数据的整个流程如图 10 所示。

图 10 发送数据流程图

(1)系统复位后首先进入 StateIdle 状态;上层协议通过主机接口设置 TxStartFrm 信号要求开始数据传输,并同时提供需要传输的第一个数据,系统进入 StatePreamble 状态。

(2)在 StatePreamble 状态,添加报头,同时通知外部 PHY 芯片传输即将开始;在报头和帧起始分隔符(SFD)传输完成后,系统进入 StateData0 状态,同时设置 TxUsedData 信号通知主机接口提供下一个数据;在数据的低 4 位数据发送完成后,系统进入 StateData1 状态;系统开始传输数据的高 4 位,系统开始在 StateData0 状态和 StateData1 状态之间切换,直到主机接口设置 TxEndFrm 信号通知数据传输结束。

(3)如果发送数据的大小大于帧数据格式要求的最小值,并且设置产生 CRC 校验序列,系统将进入 StateFCS 状态,并产生 CRC 校验序列;然后进入 StateDefer 状态,产生一定的延迟;接下来进入 StateIPG 状态,实现需要的帧间隔时间;最后回到 StateIdle 状态;如果发送数据的大小大于帧数据格式要求的最小值,并且设置不产生 CRC 校验序列,系统将进入StateDefer 状态,产生一定的延迟。

(4)接下来进入 StateIPG 状态,实现需要的帧间隔时间;最后回到 StateIdle 状态;如果发送数据的大小小于帧数据格式要求的最小值,并且设置数据长度满足帧数据格式的最小值要求并产生 CRC 校验序列,系统将进入 StatePAD 状态,补充数据长度直到满足帧数据格式的要求(46 个字节);然后进入 StateFCS 状态,并产生 CRC 校验序列;随后进入 StateDefer 状态,产生一定的延迟;接下来进入 StateIPG 状态,实现需要的帧间隔时间。最后回到 StateIdle状态。

(5)最后回到 StateIdle 状态;如果发送数据的大小小于帧数据格式要求的最小值,并且设置产生 CRC 校验序列但不要求数据长度满足最小值要求,系统将进入 StateFCS 状态,并产生 CRC 校验序列;随后进入 StateDefer 状态,产生一定的延迟;接下来进入 StateIPG 状态,实现需要的帧间隔时间;最后回到 StateIdle 状态;如果发送数据的大小小于帧数据格式要求的最小值,并且设置不产生 CRC 校验序列但不要求数据长度满足最小值要求,系统将进入StateDefer 状态,产生一定的延迟;接下来进入 StateIPG 状态,实现需要的帧间隔时间;最后回到 StateIdle 状态。

发送数据状态机的主要代码如下:

`include "timescale.v"module eth_txstatem (MTxClk, Reset, ExcessiveDefer, CarrierSense, NibCnt, IPGT, IPGR1,IPGR2, FullD, TxStartFrm, TxEndFrm, TxUnderRun, Collision, UnderRun, StartTxDone, TooBig,NibCntEq7, NibCntEq15, MaxFrame, Pad, CrcEn, NibbleMinFl, RandomEq0, ColWindow,RetryMax, NoBckof, RandomEqByteCnt, StateIdle, StateIPG, StatePreamble, StateData, StatePAD,StateFCS, StateJam, StateJam_q, StateBackOff, StateDefer, StartFCS, StartJam, StartBackoff,StartDefer, DeferIndication, StartPreamble, StartData, StartIPG );    parameter Tp = 1;  //输入输出信号  input MTxClk;  input Reset;    output StartIPG;  //连线与寄存器  wire StartIdle; //下一个时钟将进入 Idle 状态  wire StartPAD; // 下一个时钟将进入 PAD 状态  ……  reg Rule1;  //定义下一个状态  assign StartIPG = StateDefer & ~ExcessiveDefer & ~CarrierSense;  assign StartIdle = StateIPG & (Rule1 & NibCnt[6:0] >= IPGT | ~Rule1 & NibCnt[6:0] >= IPGR2);  assign StartPreamble = StateIdle & TxStartFrm & ~CarrierSense;  assign StartData[0] = ~Collision & (StatePreamble & NibCntEq15 | StateData[1] & ~TxEndFrm);  assign StartData[1] = ~Collision & StateData[0] & ~TxUnderRun & ~MaxFrame;  assign StartPAD = ~Collision & StateData[1] & TxEndFrm & Pad & ~NibbleMinFl;  assign StartFCS = ~Collision & StateData[1] & TxEndFrm & (~Pad | Pad & NibbleMinFl) & CrcEn| ~Collision & StatePAD & NibbleMinFl & CrcEn;  assign StartJam = (Collision | UnderRun) & ((StatePreamble & NibCntEq15) | (|StateData[1:0])| StatePAD | StateFCS);  assign StartBackoff = StateJam & ~RandomEq0 & ColWindow & ~RetryMax & NibCntEq7 & ~NoBckof;  assign StartDefer = StateIPG & ~Rule1 & CarrierSense & NibCnt[6:0] <= IPGR1 & NibCnt[6:0] !=IPGR2| StateIdle & CarrierSense| StateJam & NibCntEq7 & (NoBckof | RandomEq0 | ~ColWindow | RetryMax)| StateBackOff & (TxUnderRun | RandomEqByteCnt)| StartTxDone | TooBig;  assign DeferIndication = StateIdle & CarrierSense;  //发送数据状态机  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      begin        StateIPG <= #Tp 1'b0;        StateIdle <= #Tp 1'b0;        StatePreamble <= #Tp 1'b0;        StateData[1:0] <= #Tp 2'b0;        StatePAD <= #Tp 1'b0;        StateFCS <= #Tp 1'b0;        StateJam <= #Tp 1'b0;        StateJam_q <= #Tp 1'b0;        StateBackOff <= #Tp 1'b0;        StateDefer <= #Tp 1'b1;      end    else      begin        StateData[1:0] <= #Tp StartData[1:0];        StateJam_q <= #Tp StateJam;        if(StartDefer | StartIdle)        StateIPG <= #Tp 1'b0;    else      if(StartIPG)        StateIPG <= #Tp 1'b1;          if(StartDefer | StartPreamble)            StateIdle <= #Tp 1'b0;          else          if(StartIdle)            StateIdle <= #Tp 1'b1;              if(StartData[0] | StartJam)                StatePreamble <= #Tp 1'b0;          else          if(StartPreamble)              StatePreamble <= #Tp 1'b1;                if(StartFCS | StartJam)                  StatePAD <= #Tp 1'b0;           else           if(StartPAD)             StatePAD <= #Tp 1'b1;               if(StartJam | StartDefer)                  StateFCS <= #Tp 1'b0;           else           if(StartFCS)              StateFCS <= #Tp 1'b1;               if(StartBackoff | StartDefer)                 StateJam <= #Tp 1'b0;           else           if(StartJam)              StateJam <= #Tp 1'b1;                if(StartDefer)                  StateBackOff <= #Tp 1'b0;           else           if(StartBackoff)              StateBackOff <= #Tp 1'b1;                if(StartIPG)                  StateDefer <= #Tp 1'b0;            else            if(StartDefer)              StateDefer <= #Tp 1'b1;       end  end  //定义帧间隔  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      Rule1 <= #Tp 1'b0;    else      begin        if(StateIdle | StateBackOff)          Rule1 <= #Tp 1'b0;        else          if(StatePreamble | FullD)            Rule1 <= #Tp 1'b1;      end  end  endmodule                   

B.计数器模块

计数器模块提供数据发送过程中需要的所有计数器:DlyCrcCnt 用来在 CRC 校验序列产生过程中计数;按照 4 位传输时采用 NibCnt 计数;按照字节传输时采用 ByteCnt 计数。

计数器模块的主要代码如下:

//四位传输计数器  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      NibCnt <= #Tp 16'h0;    else      begin        if(ResetNibCnt)          NibCnt <= #Tp 16'h0;        else        if(IncrementNibCnt)          NibCnt <= #Tp NibCnt + 1'b1;      end  end    assign NibCntEq7 = &NibCnt[2:0];  assign NibCntEq15 = &NibCnt[3:0];  assign NibbleMinFl = NibCnt >= (((MinFL-3'h4)<<1) -1); // FCS should not be included in  NibbleMinFl  assign ExcessiveDeferCnt = NibCnt[13:0] == 16'h17b7;  assign ExcessiveDefer = NibCnt[13:0] == 16'h17b7 & ~ExDfrEn; // 6071 nibbles  assign IncrementByteCnt = StateData[1] & ~ByteCntMax & ~|DlyCrcCnt[2:0]| StateBackOff & (&NibCnt[6:0])| (StatePAD | StateFCS) & NibCnt[0] & ~ByteCntMax;  assign ResetByteCnt = StartBackoff | StateIdle & TxStartFrm | PacketFinished_q;// 字节传输计数器  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      ByteCnt[15:0] <= #Tp 16'h0;    else      begin        if(ResetByteCnt)          ByteCnt[15:0] <= #Tp 16'h0;        else        if(IncrementByteCnt)          ByteCnt[15:0] <= #Tp ByteCnt[15:0] + 1'b1;      end  end  assign MaxFrame = ByteCnt[15:0] == MaxFL[15:0] & ~HugEn;  assign ByteCntMax = &ByteCnt[15:0];  //CRC 计数器  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      DlyCrcCnt <= #Tp 3'h0;    else      begin        if(StateData[1] & DlyCrcCnt == 3'h4 | StartJam | PacketFinished_q)          DlyCrcCnt <= #Tp 3'h0;        else        if(DlyCrcEn & (StateSFD | StateData[1] & (|DlyCrcCnt[2:0])))          DlyCrcCnt <= #Tp DlyCrcCnt + 1'b1;      end  end

C.CRC 校验模块

这个模块计算产生发送数据需要的 CRC 校验序列。计算产生的 CRC 校验需要添加到数据帧上。这个模块同时被接收数据模块用来计算 CRC 校验序列,然后和接收到的 CRC 校验序列进行比较,从而判断传输过程中是否发生错误。

CRC 校验模块的主要代码如下:

include "timescale.v"module eth_crc (Clk, Reset, Data, Enable, Initialize, Crc, CrcError);  parameter Tp = 1;  //输入、输出信号  input Clk;  input Reset;  input [3:0] Data;  input Enable;  input Initialize;    output [31:0] Crc;  output CrcError;    reg [31:0] Crc;    wire [31:0] CrcNext;  //计算获得 CRC 序列  assign CrcNext[0] = Enable & (Data[0] ^ Crc[28]);  assign CrcNext[1] = Enable & (Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29]);  assign CrcNext[2] = Enable & (Data[2] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[30]);  assign CrcNext[3] = Enable & (Data[3] ^ Data[2] ^ Data[1] ^ Crc[29] ^ Crc[30] ^ Crc[31]);  assign CrcNext[4] = (Enable & (Data[3] ^ Data[2] ^ Data[0] ^ Crc[28] ^ Crc[30] ^ Crc[31]))^ Crc[0];  assign CrcNext[5] = (Enable & (Data[3] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[31]))^ Crc[1];  assign CrcNext[6] = (Enable & (Data[2] ^ Data[1] ^ Crc[29] ^ Crc[30])) ^ Crc[ 2];  assign CrcNext[7] = (Enable & (Data[3] ^ Data[2] ^ Data[0] ^ Crc[28] ^ Crc[30] ^ Crc[31]))^ Crc[3];  assign CrcNext[8] = (Enable & (Data[3] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[31]))^ Crc[4];  assign CrcNext[9] = (Enable & (Data[2] ^ Data[1] ^ Crc[29] ^ Crc[30])) ^ Crc[5];  assign CrcNext[10] = (Enable & (Data[3] ^ Data[2] ^ Data[0] ^ Crc[28] ^ Crc[30] ^ Crc[31]))^ Crc[6];  assign CrcNext[11] = (Enable & (Data[3] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[31]))^ Crc[7];  assign CrcNext[12] = (Enable & (Data[2] ^ Data[1] ^ Data[0] ^ Crc[28] ^ Crc[29] ^ Crc[30]))^ Crc[8];  assign CrcNext[13] = (Enable & (Data[3] ^ Data[2] ^ Data[1] ^ Crc[29] ^ Crc[30] ^ Crc[31]))^ Crc[9];  assign CrcNext[14] = (Enable & (Data[3] ^ Data[2] ^ Crc[30] ^ Crc[31])) ^ Crc[10];  assign CrcNext[15] = (Enable & (Data[3] ^ Crc[31])) ^ Crc[11];  assign CrcNext[16] = (Enable & (Data[0] ^ Crc[28])) ^ Crc[12];  assign CrcNext[17] = (Enable & (Data[1] ^ Crc[29])) ^ Crc[13];  assign CrcNext[18] = (Enable & (Data[2] ^ Crc[30])) ^ Crc[14];  assign CrcNext[19] = (Enable & (Data[3] ^ Crc[31])) ^ Crc[15];  assign CrcNext[20] = Crc[16];  assign CrcNext[21] = Crc[17];  assign CrcNext[22] = (Enable & (Data[0] ^ Crc[28])) ^ Crc[18];  assign CrcNext[23] = (Enable & (Data[1] ^ Data[0] ^ Crc[29] ^ Crc[28])) ^ Crc[19];  assign CrcNext[24] = (Enable & (Data[2] ^ Data[1] ^ Crc[30] ^ Crc[29])) ^ Crc[20];  assign CrcNext[25] = (Enable & (Data[3] ^ Data[2] ^ Crc[31] ^ Crc[30])) ^ Crc[21];  assign CrcNext[26] = (Enable & (Data[3] ^ Data[0] ^ Crc[31] ^ Crc[28])) ^ Crc[22];  assign CrcNext[27] = (Enable & (Data[1] ^ Crc[29])) ^ Crc[23];  assign CrcNext[28] = (Enable & (Data[2] ^ Crc[30])) ^ Crc[24];  assign CrcNext[29] = (Enable & (Data[3] ^ Crc[31])) ^ Crc[25];  assign CrcNext[30] = Crc[26];  assign CrcNext[31] = Crc[27];  //初始化和复位 CRC 校验序列  always @ (posedge Clk or posedge Reset)  begin    if (Reset)      Crc <= #1 32'hffffffff;    else    if(Initialize)      Crc <= #Tp 32'hffffffff;    else      Crc <= #Tp CrcNext;  end//发生错误时的结果  assign CrcError = Crc[31:0] != 32'hc704dd7b; // CRC not equal to magic numberendmodule

D.错误处理模块

在数据传输过程中发生冲突时,数据发送模块首先发送“0x99999999”,然后结束传输。在传输重新开始以前,数据发送模块会做一些补偿。即在重新传输数据以前进行一定时间延迟,延迟时间的长短由错误处理模块产生的随机数决定。这样可以减少再次发生数据冲突的次数。

这种随机数采用一种叫做二进制指数补偿算法(Binary Exponential algorithm)产生。这种算法的过程是这样的:发送者在第一次冲突后延迟一个随机时间,如果第二次发送也产生冲突的话,则延迟第一次时延的两倍;若第三次发送还冲突的话,就延迟 4 倍。执行指数补偿算法的考虑是:如果发生许多发送者这同时发送的事件,将发生严重的拥塞。在这种拥塞中,很可能两个站点选择非常接近的随机时间进行补偿。这样,发生另一次冲突的可能性是很高的。通过使延迟时间加倍,指数补偿算法会很快把站点重新发送的时间间隔显著拉开,使发生再一次冲突的可能性变得非常小了。

错误处理模块的主要代码如下:

`include "timescale.v"module eth_random (MTxClk, Reset, StateJam, StateJam_q, RetryCnt, NibCnt, ByteCnt,RandomEq0, RandomEqByteCnt);  parameter Tp = 1;  //输入输出信号  input MTxClk;  input Reset;  input StateJam;  input StateJam_q;  input [3:0] RetryCnt;  input [15:0] NibCnt;  input [9:0] ByteCnt;    output RandomEq0;  output RandomEqByteCnt;  //连线和寄存器  wire Feedback;  reg [9:0] x;  wire [9:0] Random;  reg [9:0] RandomLatched;    always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      x[9:0] <= #Tp 0;    else      x[9:0] <= #Tp {x[8:0], Feedback};    end      assign Feedback = x[2] ~ ^ x[9];  //产生随机数  assign Random [0] = x[0];  assign Random [1] = (RetryCnt > 1) ? x[1] : 1'b0;  assign Random [2] = (RetryCnt > 2) ? x[2] : 1'b0;  assign Random [3] = (RetryCnt > 3) ? x[3] : 1'b0;  assign Random [4] = (RetryCnt > 4) ? x[4] : 1'b0;  assign Random [5] = (RetryCnt > 5) ? x[5] : 1'b0;  assign Random [6] = (RetryCnt > 6) ? x[6] : 1'b0;  assign Random [7] = (RetryCnt > 7) ? x[7] : 1'b0;  assign Random [8] = (RetryCnt > 8) ? x[8] : 1'b0;  assign Random [9] = (RetryCnt > 9) ? x[9] : 1'b0;  //复位后随机数为 0,发送“0x99999999”后锁存产生的随机数  always @ (posedge MTxClk or posedge Reset)  begin    if(Reset)      RandomLatched <= #Tp 10'h000;    else      begin        if(StateJam & StateJam_q)          RandomLatched <= #Tp Random;      end  end  // 随机数为 0  assign RandomEq0 = RandomLatched == 10'h0;  assign RandomEqByteCnt = ByteCnt[9:0] == RandomLatched & (&NibCnt[6:0]);endmodule

3.4 数据接收模块

数据接收模块负责接收数据的整个过程。外部 PHY 从物理层(具体指双绞线等电缆)接收串行的数据,将其还原成四位字节形式然后发送到数据接收模块。数据接收模块将接收到的 4位字节形式的数据合并成 8 位字节形式的数据,随后通过主机接口发送到上层协议。数据接收模块在接收数据的过程中完成去除报头和 CRC 校验序列的工作。

数据接收模块的结构如图 11 所示。

图 10-11 数据接收模块的结构数据

接收模块由 4 部分组成。

• 数据接收状态机:完成数据接收的整体控制。

• 计数器模块:包括数据接收中所有需要的计数器。

• CRC 校验模块:根据接收到的数据产生 32 位的 CRC 校验序列,并与接收到的 CRC 校验数据比较,从而得到数据是否被破坏。

• 地址检查模块:确认接收到的数据地址是否与实际地址相符。

数据发送模块的顶层程序完成 4 个部分的连接和控制,主要代码如下。

`include "timescale.v"module eth_rxethmac (MRxClk, MRxDV, MRxD, Reset, Transmitting, MaxFL, r_IFG, HugEn, DlyCrcEn,RxData, RxValid, RxStartFrm, RxEndFrm, ByteCnt, ByteCntEq0, ByteCntGreat2, ByteCntMaxFrame,CrcError, StateIdle, StatePreamble, StateSFD, StateData, MAC, r_Pro, r_Bro,r_HASH0, r_HASH1,RxAbort,AddressMiss, PassAll, ControlFrmAddressOK );  parameter Tp = 1;  //输入输出信号  input MRxClk;  input MRxDV;  …..  input [47:0] MAC; // 地址  input [31:0] r_HASH0; // 低四位哈希表  input [31:0] r_HASH1; // 高四位哈希表  input PassAll;  ….  //寄存器与连线  reg [7:0] RxData;  reg RxValid;  ….  //连接接收数据状态机  eth_rxstatem rxstatem1(                          .MRxClk(MRxClk),                           .Reset(Reset),                           .MRxDV(MRxDV),                           .ByteCntEq0(ByteCntEq0),                          .ByteCntGreat2(ByteCntGreat2),                           .Transmitting(Transmitting),                           .MRxDEq5(MRxDEq5),                          .MRxDEqD(MRxDEqD),                           .IFGCounterEq24(IFGCounterEq24),                           .ByteCntMaxFrame(ByteCntMaxFrame),                          .StateData(StateData),                           .StateIdle(StateIdle),                           .StatePreamble(StatePreamble),                          .StateSFD(StateSFD),                          .StateDrop(StateDrop)                         ); // 连接接收数据计数器模块  eth_rxcounters rxcounters1(                              .MRxClk(MRxClk),                               .Reset(Reset),                               .MRxDV(MRxDV),                               .StateIdle(StateIdle),                              .StateSFD(StateSFD),                               .StateData(StateData),                               .StateDrop(StateDrop),                              .StatePreamble(StatePreamble),                              .MRxDEqD(MRxDEqD),                               .DlyCrcEn(DlyCrcEn),                              .DlyCrcCnt(DlyCrcCnt),                               .Transmitting(Transmitting),                              .MaxFL(MaxFL),                               .r_IFG(r_IFG),                              .HugEn(HugEn),                               .IFGCounterEq24(IFGCounterEq24),                              .ByteCntEq0(ByteCntEq0),                              .ByteCntEq1(ByteCntEq1),                               .ByteCntEq2(ByteCntEq2),                              .ByteCntEq3(ByteCntEq3),                              .ByteCntEq4(ByteCntEq4),                               .ByteCntEq5(ByteCntEq5),                              .ByteCntEq6(ByteCntEq6),                              .ByteCntEq7(ByteCntEq7),                               .ByteCntGreat2(ByteCntGreat2),                              .ByteCntSmall7(ByteCntSmall7),                               .ByteCntMaxFrame(ByteCntMaxFrame),                              .ByteCnt(ByteCnt)                            );// 连接地址检查模块  eth_rxaddrcheck rxaddrcheck1 (                                  .MRxClk(MRxClk),                                  .Reset(Reset),                                  .RxData(RxData),                                  .Broadcast (Broadcast),                                  .r_Bro(r_Bro),                                  .r_Pro(r_Pro),                                  .ByteCntEq6(ByteCntEq6),                                  .ByteCntEq7(ByteCntEq7),                                  .ByteCntEq2(ByteCntEq2),                                  .ByteCntEq3(ByteCntEq3),                                  .ByteCntEq4(ByteCntEq4),                                  .ByteCntEq5(ByteCntEq5),                                  .HASH0(r_HASH0),                                  .HASH1(r_HASH1),                                  .CrcHash(CrcHash[5:0]),                                   .CrcHashGood(CrcHashGood),                                  .StateData(StateData),                                  .Multicast(Multicast),                                  .MAC(MAC),                                  .RxAbort(RxAbort),                                  .RxEndFrm(RxEndFrm),                                  .AddressMiss(AddressMiss),                                  .PassAll(PassAll),                                  .ControlFrmAddressOK(ControlFrmAddressOK)                                );//设置产生并初始化 CRC 序列  assign Enable_Crc = MRxDV & (|StateData & ~ByteCntMaxFrame);  assign Initialize_Crc = StateSFD | DlyCrcEn & (|DlyCrcCnt[3:0]) & DlyCrcCnt[3:0] < 4'h9;  assign Data_Crc[0] = MRxD[3];  assign Data_Crc[1] = MRxD[2];  assign Data_Crc[2] = MRxD[1];  assign Data_Crc[3] = MRxD[0];  //连接 Crc 校验模块  eth_crc crcrx(                  .Clk(MRxClk),                   .Reset(Reset),                   .Data(Data_Crc),                   .Enable(Enable_Crc),                   .Initialize(Initialize_Crc),                  .Crc(Crc),                   .CrcError(CrcError)                );                //锁存 CRC 序列并用在哈希表中  always @ (posedge MRxClk)  begin    CrcHashGood <= #Tp StateData[0] & ByteCntEq6;  end    always @ (posedge MRxClk)  begin    if(Reset | StateIdle)      CrcHash[8:0] <= #Tp 9'h0;    else    if(StateData[0] & ByteCntEq6)      CrcHash[8:0] <= #Tp Crc[31:23];  end  // 输出按字节保存的数据到上层协议  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      begin        RxData_d[7:0] <= #Tp 8'h0;        DelayData <= #Tp 1'b0;        LatchedNibble[3:0] <= #Tp 4'h0;        LatchedByte[7:0] <= #Tp 8'h0;        RxData[7:0] <= #Tp 8'h0;      end    else      begin        LatchedNibble[3:0] <= #Tp MRxD[3:0]; // 锁存四位字节数据        LatchedByte[7:0] <= #Tp {MRxD[3:0], LatchedNibble[3:0]}; // 锁存八位字节数据        DelayData <= #Tp StateData[0];          if(GenerateRxValid)            RxData_d[7:0] <= #Tp LatchedByte[7:0] & {8{|StateData}};          else          if(~DelayData)            RxData_d[7:0] <= #Tp 8'h0; // 延迟数据            RxData[7:0] <= #Tp RxData_d[7:0]; // 输出按字节保存的数据      end  end    always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      Broadcast <= #Tp 1'b0;    else      begin        if(StateData[0] & ~(&LatchedByte[7:0]) & ByteCntSmall7)          Broadcast <= #Tp 1'b0;        else        if(StateData[0] & (&LatchedByte[7:0]) & ByteCntEq1)          Broadcast <= #Tp 1'b1;        else        if(RxAbort | RxEndFrm)          Broadcast <= #Tp 1'b0;      end  end  //多点传送  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      Multicast <= #Tp 1'b0;    else      begin        if(Reset)          Multicast <= #Tp 1'b0;        else        if(StateData[0] & ByteCntEq1 & LatchedByte == 8'h01)          Multicast <= #Tp 1'b1;        else         if(RxAbort | RxEndFrm)          Multicast <= #Tp 1'b0;      end  end    assign GenerateRxValid = StateData[0] & (~ByteCntEq0 | DlyCrcCnt >= 4'h3);//数据有效  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      begin        RxValid_d <= #Tp 1'b0;        RxValid <= #Tp 1'b0;      end    else      begin        RxValid_d <= #Tp GenerateRxValid;        RxValid <= #Tp RxValid_d;      end  end    assign GenerateRxStartFrm = StateData[0] & (ByteCntEq1 & ~DlyCrcEn | DlyCrcCnt == 4'h3 &DlyCrcEn);//产生帧起始信号  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      begin        RxStartFrm_d <= #Tp 1'b0;        RxStartFrm <= #Tp 1'b0;      end    else      begin        RxStartFrm_d <= #Tp GenerateRxStartFrm;        RxStartFrm <= #Tp RxStartFrm_d;      end  end    assign GenerateRxEndFrm = StateData[0] & (~MRxDV & ByteCntGreat2 | ByteCntMaxFrame);  assign DribbleRxEndFrm = StateData[1] & ~MRxDV & ByteCntGreat2;//产生帧尾信号  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      begin        RxEndFrm_d <= #Tp 1'b0;        RxEndFrm <= #Tp 1'b0;      end    else      begin        RxEndFrm_d <= #Tp GenerateRxEndFrm;        RxEndFrm <= #Tp RxEndFrm_d | DribbleRxEndFrm;      end  end  endmodule

A.数据接收状态机

数据接收的整个过程由一个状态机控制完成。数据的接收与发送是相反的过程:首先去除报头,然后去除 SFD(帧起始分隔符),随后接收数据,最后进行 CRC 校验判断数据在传输过程中是否受损。流程与数据发送的流程相反,这里不再赘述。

B.计数器模块

数据接收的计数器模块包括接收数据过程中的所有计数器。内容与数据发送模块中的计数器模块类似。

C.CRC 校验模块

数据接收模块的 CRC 校验首先根据接收到的数据计算产生 32 位 CRC 校验序列,然后跟接收到的 CRC 校验序列比较,判断数据在传输过程中是否损坏。数据接收模块的 CRC 校验的源代码和数据发送模块的 CRC 校验代码一样。

D.地址检查模块

地址检查模块将检查接收数据中的目的地址是否和接收模块的地址一致。如果地址不一致,接收到的数据将被清除。地址检查模块的主要代码如下:

`include "timescale.v"module eth_rxaddrcheck(MRxClk, Reset, RxData, Broadcast ,r_Bro ,r_Pro, ByteCntEq2,ByteCntEq3,ByteCntEq4, ByteCntEq5, ByteCntEq6, ByteCntEq7, HASH0, HASH1, CrcHash, CrcHashGood,StateData,RxEndFrm, Multicast, MAC, RxAbort, AddressMiss, PassAll,ControlFrmAddressOK);  parameter Tp = 1;  //输入输出信号  input MRxClk;  ….  //连线与寄存器  wire BroadcastOK;  ….  //地址非法标志  assign RxAddressInvalid = ~(UnicastOK | BroadcastOK | MulticastOK | r_Pro);  //广播正确标志  assign BroadcastOK = Broadcast & ~r_Bro;  //检查接收数据使能  assign RxCheckEn = | StateData;// 在地址周期报告地址错误  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      RxAbort <= #Tp 1'b0;    else if(RxAddressInvalid & ByteCntEq7 & RxCheckEn)      RxAbort <= #Tp 1'b1;    else      RxAbort <= #Tp 1'b0;  end  // 写 ff 到 BD 状态寄存器中,表示“地址丢失”  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      AddressMiss <= #Tp 1'b0;    else if(ByteCntEq7 & RxCheckEn)      AddressMiss <= #Tp (~(UnicastOK | BroadcastOK | MulticastOK | (PassAll &ControlFrmAddressOK)));  end  //哈希地址检查,多点发送  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      MulticastOK <= #Tp 1'b0;    else if(RxEndFrm | RxAbort)      MulticastOK <= #Tp 1'b0;    else if(CrcHashGood & Multicast)      MulticastOK <= #Tp HashBit;  end  //地址探测,单点发送  always @ (posedge MRxClk or posedge Reset)  begin    if(Reset)      UnicastOK <= #Tp 1'b0;    else    if(RxCheckEn & ByteCntEq2)      UnicastOK <= #Tp RxData[7:0] == MAC[47:40];    else    if(RxCheckEn & ByteCntEq3)      UnicastOK <= #Tp ( RxData[7:0] == MAC[39:32]) & UnicastOK;    else    if(RxCheckEn & ByteCntEq4)      UnicastOK <= #Tp ( RxData[7:0] == MAC[31:24]) & UnicastOK;    else    if(RxCheckEn & ByteCntEq5)      UnicastOK <= #Tp ( RxData[7:0] == MAC[23:16]) & UnicastOK;    else    if(RxCheckEn & ByteCntEq6)      UnicastOK <= #Tp ( RxData[7:0] == MAC[15:8]) & UnicastOK;    else    if(RxCheckEn & ByteCntEq7)      UnicastOK <= #Tp ( RxData[7:0] == MAC[7:0]) & UnicastOK;    else    if(RxEndFrm | RxAbort)      UnicastOK <= #Tp 1'b0;  end    assign IntHash = (CrcHash[5])? HASH1 : HASH0;    always@(CrcHash or IntHash)  begin    case(CrcHash[4:3])      2'b00: ByteHash = IntHash[7:0];      2'b01: ByteHash = IntHash[15:8];      2'b10: ByteHash = IntHash[23:16];      2'b11: ByteHash = IntHash[31:24];    endcase  end    assign HashBit = ByteHash[CrcHash[2:0]];endmodule

3.5 控制模块

前面介绍的数据发送模块和数据接收模块单独工作时,以太网控制器工作在半双工状态。当以太网控制器工作在 100Mbit/s 全双工时,数据流程由控制模块控制。控制模块由两部分组成。

• 数据传输控制。

• 数据接收控制。

控制模块的顶层程序连接两个子模块,主要代码如下:

`include "timescale.v"module eth_maccontrol (MTxClk, MRxClk, TxReset, RxReset, TPauseRq, TxDataIn, TxStartFrmIn,TxUsedDataIn, TxEndFrmIn, TxDoneIn, TxAbortIn, RxData, RxValid, RxStartFrm, RxEndFrm,ReceiveEnd,ReceivedPacketGood, ReceivedLengthOK, TxFlow, RxFlow, DlyCrcEn, TxPauseTV, MAC, PadIn,PadOut,CrcEnIn, CrcEnOut, TxDataOut, TxStartFrmOut, TxEndFrmOut, TxDoneOut, TxAbortOut,TxUsedDataOut, WillSendControlFrame, TxCtrlEndFrm,ReceivedPauseFrm, ControlFrmAddressOK,SetPauseTimer, r_PassAll, RxStatusWriteLatched_sync2 );  parameter Tp = 1;  //输入输出信号  input MTxClk; //传输时钟信号  input MRxClk; //接收时钟信号  ……  //连线与寄存器  reg TxUsedDataOutDetected;  ……  //正在传输数据  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      TxUsedDataOutDetected <= #Tp 1'b0;    else    if(TxDoneIn | TxAbortIn)      TxUsedDataOutDetected <= #Tp 1'b0;    else    if(TxUsedDataOut)      TxUsedDataOutDetected <= #Tp 1'b1;  end  //锁存变量  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      begin        TxAbortInLatched <= #Tp 1'b0;        TxDoneInLatched <= #Tp 1'b0;      end    else      begin        TxAbortInLatched <= #Tp TxAbortIn;        TxDoneInLatched <= #Tp TxDoneIn;      end  end  //产生复用退出信号  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      MuxedAbort <= #Tp 1'b0;    else    if(TxStartFrmIn)      MuxedAbort <= #Tp 1'b0;    else    if(TxAbortIn & ~TxAbortInLatched & TxUsedDataOutDetected)      MuxedAbort <= #Tp 1'b1;  end  //产生复用结束信号  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      MuxedDone <= #Tp 1'b0;    else    if(TxStartFrmIn)      MuxedDone <= #Tp 1'b0;    else    if(TxDoneIn & (~TxDoneInLatched) & TxUsedDataOutDetected)      MuxedDone <= #Tp 1'b1;  end  // 输出发送数据结束信号  assign TxDoneOut = CtrlMux? ((~TxStartFrmIn) & (~BlockTxDone) & MuxedDone) :((~TxStartFrmIn) & (~BlockTxDone) & TxDoneIn);//发送数据退出信号  assign TxAbortOut = CtrlMux? ((~TxStartFrmIn) & (~BlockTxDone) & MuxedAbort) :((~TxStartFrmIn) & (~BlockTxDone) & TxAbortIn);//发送使用过数据输出信号  assign TxUsedDataOut = ~CtrlMux & TxUsedDataIn;//传输帧起始信号assign TxStartFrmOut = CtrlMux? TxCtrlStartFrm : (TxStartFrmIn & ~Pause);//传输帧结束信号  assign TxEndFrmOut = CtrlMux? TxCtrlEndFrm : TxEndFrmIn;//发送数据内容  assign TxDataOut[7:0] = CtrlMux? ControlData[7:0] : TxDataIn[7:0];  assign PadOut = PadIn | SendingCtrlFrm;//CRC 校验使能信号输出  assign CrcEnOut = CrcEnIn | SendingCtrlFrm;//连接接收控制模块  eth_receivecontrol receivecontrol1(                                      .MTxClk(MTxClk), .MRxClk(MRxClk), .TxReset(TxReset), .RxReset(RxReset), .RxData(RxData),                                      .RxValid(RxValid), .RxStartFrm(RxStartFrm), .RxEndFrm(RxEndFrm), .RxFlow(RxFlow),                                      .ReceiveEnd(ReceiveEnd), .MAC(MAC), .DlyCrcEn(DlyCrcEn), .TxDoneIn(TxDoneIn),                                      .TxAbortIn(TxAbortIn), .TxStartFrmOut(TxStartFrmOut), .ReceivedLengthOK(ReceivedLengthOK),                                      .ReceivedPacketGood(ReceivedPacketGood), .TxUsedDataOutDetected(TxUsedDataOutDetected),                                      .Pause(Pause), .ReceivedPauseFrm(ReceivedPauseFrm), .AddressOK(ControlFrmAddressOK),                                      .r_PassAll(r_PassAll), .RxStatusWriteLatched_sync2(RxStatusWriteLatched_sync2), .SetPaus                                      eTimer(SetPauseTimer)                                    );//连接传输控制模块  eth_transmitcontrol transmitcontrol1(                                        .MTxClk(MTxClk), .TxReset(TxReset), .TxUsedDataIn(TxUsedDataIn), .TxUsedDataOut(TxUsedDa                                        taOut),.TxDoneIn(TxDoneIn), .TxAbortIn(TxAbortIn), .TxStartFrmIn(TxStartFrmIn), .TPauseRq(TPaus                                        eRq),.TxUsedDataOutDetected(TxUsedDataOutDetected), .TxFlow(TxFlow), .DlyCrcEn(DlyCrcEn), .Tx                                        PauseTV(TxPauseTV),.MAC(MAC), .TxCtrlStartFrm(TxCtrlStartFrm), .TxCtrlEndFrm(TxCtrlEndFrm), .SendingCtrlFrm                                        (SendingCtrlFrm),.CtrlMux(CtrlMux), .ControlData(ControlData), .WillSendControlFrame(WillSendControlFrame), .BlockTxDone(BlockTxDone)                                      );                                      endmodule

A.数据传输控制

数据传输控制要保证主机接口和外部 PHY 可以同时发送数据给对方,从而实现全双工的要求。数据传输控制的主要代码如下:

`include "timescale.v"module eth_transmitcontrol (MTxClk, TxReset, TxUsedDataIn, TxUsedDataOut, TxDoneIn,TxAbortIn,TxStartFrmIn, TPauseRq, TxUsedDataOutDetected, TxFlow, DlyCrcEn, TxPauseTV, MAC,TxCtrlStartFrm,TxCtrlEndFrm, SendingCtrlFrm, CtrlMux, ControlData, WillSendControlFrame, BlockTxDone);  parameter Tp = 1;  //输入输出信号  input MTxClk;  ……  //寄存器与连线  reg SendingCtrlFrm;  ……  //发送控制帧数据的命令,高有效  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      WillSendControlFrame <= #Tp 1'b0;    else    if(TxCtrlEndFrm & CtrlMux)      WillSendControlFrame <= #Tp 1'b0;    else    if(TPauseRq & TxFlow)      WillSendControlFrame <= #Tp 1'b1;  end  //产生传输控制数据包的开始帧  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      TxCtrlStartFrm <= #Tp 1'b0;    else    if(TxUsedDataIn_q & CtrlMux)      TxCtrlStartFrm <= #Tp 1'b0;    else    if(WillSendControlFrame & ~TxUsedDataOut & (TxDoneIn | TxAbortIn | TxStartFrmIn |      (~TxUsedDataOutDetected)))      TxCtrlStartFrm <= #Tp 1'b1;  end  //产生传输控制数据包的结束帧  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)    TxCtrlEndFrm <= #Tp 1'b0;    else    if(ControlEnd | ControlEnd_q)      TxCtrlEndFrm <= #Tp 1'b1;    else      TxCtrlEndFrm <= #Tp 1'b0;  end  //产生乘信号  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      CtrlMux <= #Tp 1'b0;    else      if(WillSendControlFrame & ~TxUsedDataOut)        CtrlMux <= #Tp 1'b1;      else      if(TxDoneIn)        CtrlMux <= #Tp 1'b0;  end//产生发送控制帧数据,使能 CRC 校验和 PAD  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      SendingCtrlFrm <= #Tp 1'b0;    else    if(WillSendControlFrame & TxCtrlStartFrm)      SendingCtrlFrm <= #Tp 1'b1;    else    if(TxDoneIn)      SendingCtrlFrm <= #Tp 1'b0;  end      always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      TxUsedDataIn_q <= #Tp 1'b0;    else      TxUsedDataIn_q <= #Tp TxUsedDataIn;  end  //当发送控制帧数据时,产生信号屏蔽发送结束信号到主机接口  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      BlockTxDone <= #Tp 1'b0;    else    if(TxCtrlStartFrm)      BlockTxDone <= #Tp 1'b1;    else    if(TxStartFrmIn)      BlockTxDone <= #Tp 1'b0;  end    always @ (posedge MTxClk)  begin    ControlEnd_q <= #Tp ControlEnd;    TxCtrlStartFrm_q <= #Tp TxCtrlStartFrm;  end  assign IncrementDlyCrcCnt = CtrlMux & TxUsedDataIn & ~DlyCrcCnt[2];// 延迟 CRC 计数器always @ (posedge MTxClk or posedge TxReset)begin  if(TxReset)    DlyCrcCnt <= #Tp 4'h0;  else  if(ResetByteCnt)    DlyCrcCnt <= #Tp 4'h0;  else  if(IncrementDlyCrcCnt)    DlyCrcCnt <= #Tp DlyCrcCnt + 1'b1;end  assign ResetByteCnt = TxReset | (~TxCtrlStartFrm & (TxDoneIn | TxAbortIn));  assign IncrementByteCnt = CtrlMux & (TxCtrlStartFrm & ~TxCtrlStartFrm_q & ~TxUsedDataIn |TxUsedDataIn & ~ControlEnd);  assign IncrementByteCntBy2 = CtrlMux & TxCtrlStartFrm & (~TxCtrlStartFrm_q) & TxUsedDataIn;// When TxUsedDataIn and CtrlMux are set at the same time  assign EnableCnt = (~DlyCrcEn | DlyCrcEn & (&DlyCrcCnt[1:0]));//字节计数器  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      ByteCnt <= #Tp 6'h0;    else    if(ResetByteCnt)      ByteCnt <= #Tp 6'h0;    else    if(IncrementByteCntBy2 & EnableCnt)      ByteCnt <= #Tp (ByteCnt[5:0] ) + 2'h2;    else    if(IncrementByteCnt & EnableCnt)      ByteCnt <= #Tp (ByteCnt[5:0] ) + 1'b1;  end    assign ControlEnd = ByteCnt[5:0] == 6'h22;// 控制数据产生  always @ (ByteCnt or DlyCrcEn or MAC or TxPauseTV or DlyCrcCnt)  begin    case(ByteCnt)        6'h0: if(~DlyCrcEn | DlyCrcEn & (&DlyCrcCnt[1:0]))                    MuxedCtrlData[7:0] = 8'h01;                else                    MuxedCtrlData[7:0] = 8'h0;        6'h2: MuxedCtrlData[7:0] = 8'h80;        6'h4: MuxedCtrlData[7:0] = 8'hC2;        6'h6: MuxedCtrlData[7:0] = 8'h00;        6'h8: MuxedCtrlData[7:0] = 8'h00;        6'hA: MuxedCtrlData[7:0] = 8'h01;        6'hC: MuxedCtrlData[7:0] = MAC[47:40];        6'hE: MuxedCtrlData[7:0] = MAC[39:32];        6'h10: MuxedCtrlData[7:0] = MAC[31:24];        6'h12: MuxedCtrlData[7:0] = MAC[23:16];        6'h14: MuxedCtrlData[7:0] = MAC[15:8];        6'h16: MuxedCtrlData[7:0] = MAC[7:0];        6'h18: MuxedCtrlData[7:0] = 8'h88; // Type/Length        6'h1A: MuxedCtrlData[7:0] = 8'h08;        6'h1C: MuxedCtrlData[7:0] = 8'h00; // Opcode        6'h1E: MuxedCtrlData[7:0] = 8'h01;        6'h20: MuxedCtrlData[7:0] = TxPauseTV[15:8]; // Pause timer value        6'h22: MuxedCtrlData[7:0] = TxPauseTV[7:0];        default: MuxedCtrlData[7:0] = 8'h0;    endcase  end  //锁存控制数据  always @ (posedge MTxClk or posedge TxReset)  begin    if(TxReset)      ControlData[7:0] <= #Tp 8'h0;    else    if(~ByteCnt[0])      ControlData[7:0] <= #Tp MuxedCtrlData[7:0];  end  endmodule

B.数据接收控制

数据接收控制的过程与数据传输控制的过程相反,这里不再赘述。

本篇到此结束,下一篇带来基于FPGA的以太网控制器(MAC)设计(下),会介绍程序的仿真与测试和总结,包括顶层程序、外部 PHY 芯片模拟程序、仿真结果等相关内容。

END

后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。

大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!

精彩推荐

Xilinx FPGA 开发流程及详细说明

ISE 14.7 安装教程及详细说明

FPGA之通信算法工程师面试题3

FPGA工程师就业班,9月份开课!

基于FPGA的以太网控制器(MAC)设计(中)相关推荐

  1. 基于FPGA的以太网控制器(MAC)设计(下)

    今天给大侠带来基于FPGA的以太网控制器(MAC)设计,由于篇幅较长,分三篇.今天带来第三篇,下篇,程序的仿真与测试和总结.话不多说,上货. 导读 当前,互联网已经极大地改变了我们的生产和生活.与之相 ...

  2. 基于 FPGA 的 UART 控制器设计(VHDL)(下)

    今天给大侠带来基于FPGA的 UART 控制器设计(VHDL)(下),由于篇幅较长,分三篇.今天带来第三篇,下篇,使用 FPGA 实现 UART.话不多说,上货. 之前有关于 Veriliog HDL ...

  3. 基于FPGA的数字视频信号处理器设计(中)

    今天给大侠带来基于FPGA的数字视频信号处理器设计,由于篇幅较长,分三篇.今天带来第二篇,中篇,视频信号概述和视频信号处理的框架.话不多说,上货. 之前也有图像处理相关方面的文章,这里超链接几篇,给各 ...

  4. 基于FPGA的VGA/LCD显示控制器设计(中)

    今天给大侠带来基于FPGA的VGA/LCD显示控制器设计,由于篇幅较长,分三篇.今天带来第二篇,中篇,VGA 显示原理以及VGA/LCD 显示控制器的基本框架,话不多说,上货. 之前也有图像处理以及V ...

  5. 基于FPGA的SDRAM控制器设计(二)

    基于FPGA的SDRAM控制器设计(二) 1. SDRAM理论基础 2. SDRAM初始化模块以及仿真 3.TOP模块的仲裁机制 4. SDRAM刷新模块代码以及仿真 5.代码 6.参考资料 1. S ...

  6. 基于 FPGA 的以太网回环测试verilog实现UDP协议

    基于 FPGA 的以太网回环测试verilog实现UDP协议 verilog实现UDP协议: 该 设计使用 UDP 协议,首先通过串口助手接收 PC 发送的 UDP 数据包,然后提取其中的数据部分并使 ...

  7. 以太网控制器模块设计

    以太网控制器模块设计 在Intemet飞速发展的今天,网络已经***到了方方面面.在嵌入式系统中,和网络的结合已经成为嵌入式系统发展的必然.在ARM系统中,以太网 接口(EthemetPort)是与远 ...

  8. 基于FPGA的以太网开发

      基于FPGA的以太网开发,在调试过的FPGA玩家开来,其实算不上很难的技术!但是如果只是菜鸟级别的选手,没有调试过的话,就有些头疼了!早在自己在实习的时候,就接触到XAUI(万兆以太网口)接口,但 ...

  9. 基于FPGA的以太网UDP协议实现过程记录

    基于FPGA的以太网的 UDP协议的学习笔记 一.完整的以太网数据部分包括以下几部分: 前导码 帧起始界定符 以太网帧头 IP首部 UDP首部 UDP数据(有效数据) CRC校验字节 二.针对每一部分 ...

最新文章

  1. struct tm 和 time_t
  2. 项目乱码 GBK转UTF-8工具
  3. TF:利用TF读取数据操作,将CIFAR-10 数据集中的训练图片读取出来,并保存为.jpg格式
  4. 比较和逻辑运算符 011
  5. oracle怎么格式化sql语句,Oracle sqlplus格式化数据
  6. linux防火墙配置连接atlas,ATLAS在ubuntu下的安装使用
  7. DR5白金版 for mac(PS一键磨皮插件Delicious Retouch)支持ps 2022
  8. js lottie 兼容 json、图片缓存问题、修改文字,图片
  9. SSH框架整合——基于XML配置文件
  10. 计算机模拟与生态工程,2018年环境生态工程专业分析及就业前景
  11. 全文检索(LuceneSolr)
  12. python lambda函数介绍
  13. 传统蓝牙BR/EDR的搜索Inquiry
  14. INT_MIN和INT_MAX
  15. 刷机:酷比魔方iwork8 刷回双系统
  16. 天池比赛 Docker 解决无法读取测试集,提交成功
  17. 二十四节气-大寒海报、文案分享。冬寒终有尽,春暖海先觉。
  18. 浙江大学计算机学院 耿卫东教授 是哪个实验室的,文化与科技的碰撞:2018首届中国文化计算大会在京举行...
  19. 计算机应屏后打印不全怎样处理,打印机打印不完整是怎么回事【解决办法】
  20. AD20/Altium designer——如何进行DRC检查、冲突的错误如何改正

热门文章

  1. 单个JVM下支撑100w线程数vm.max_map_count
  2. 2020.4.15华为实习招聘笔试题第三题
  3. 金蝶中间件AAS无法访问管理平台提示404
  4. bit、Byte、bps、Bps、pps、Gbps的单位详细说明及换算
  5. 中国电子学会2022年12月份青少年软件编程Scratch图形化等级考试试卷一级真题(含答案)
  6. 动态循环数组(ArrayList优化)
  7. python爬虫之必应图片
  8. 【Linux】对于make/Makefile的编写
  9. win10 redis集群搭建 ruby
  10. android 模拟器 ROM RAM 空间设置