今天给大侠带来基于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 number
endmodule
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等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。
大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!
精彩推荐