从零开始 verilog 以太网交换机(二)MAC接收控制器的设计与实现
🔈声明:
😃博主主页:王_嘻嘻的CSDN主页
🧨 从零开始 verilog 以太网交换机系列专栏:点击这里
🔑未经作者允许,禁止转载,侵权必删
🚩关注本专题的朋友们可以收获一个经典交换机设计的全流程,包括设计与验证(FPGA);以太网MAC的基础知识。新手朋友们还将获得一个具有竞争力的项目经历,后续整个工程和代码下载链接也都会放在csdn和公众号内
本章进行MAC控制器的设计与实现,共分为两个部分:接收控制器和发送控制器。整体架构可以参考:从零开始 verilog 以太网交换机(一)架构分析,本文将首先分析MAC接收控制器的设计。
1、MAC接收控制器功能
MAC控制器负责以太网的MAC层和PHY层之间的数据交换,主要是根据MII标准接口规范进行数据收发。
对于本章的MAC接收控制器而言,其功能包括以下4点:
- 识别接收数据帧的起始符;
- 识别数据帧的字节边界,将半字节的数据转换为字节流;
- 对数据帧长度进行计数,检测帧长度是否符合规范,CRC校验,并上报错误标识;
- 存储数据帧的指针信息(规范标志、帧长度等);
2、MAC接收控制器接口
MAC接收控制器一端连接标准MII接口,另一端连接交换机内部的数据帧处理单元。因为在本级就要完成数据帧的检错,而后级的数据处理单元、队列管理器可能出现满载,来不及处理接收的数据,所以在MAC接收器内部,我们设计一个同步FIFO,以完成数据缓冲的作用,另一方面FIFO也可以完成握手信号的逻辑。
所以,MAC接收控制器的输入为MII接口,输出为FIFO接口形式,由于FIFO比较基础,下面就仅罗列MII接口。
3、MAC接收控制器实现细节
根据以太网MAC层的规范,控制器还需要对数据帧进行CRC-32校验,由于CRC本质就是反馈多项式,和LFSR类似,在本专题就不展开介绍了,通常CRC的多项式公式可以直接通过生成器来生成。(CRC生成器将放在本号的资源栏中,有需要的可以下载,或者关注博主的公众号,也有下载链接)
综合上述FIFO和CRC的需求,最后MAC接收控制器的整体架构图如下:
3.1、功能细节分析
- 传递正确的帧信息给后级,例如是错误帧,就可以直接读出并丢掉;这就需要2个FIFO,一个存放数据,一个存放帧信息,两者并不是一 一对应的关系,一个帧信息可能对应多字节的数据帧;
- 根据MII数据格式识别帧开始标志、帧起始符、帧结束标志;
- 检测数据帧长度是否符合规范(64 bytes < length < 1518 bytes),否则都为错误帧;
- 检测数据帧是否byte对齐,如果从MII收到的数据量为奇数则为错误帧;
- CRC校验错误,标记为错误帧;
3.2、SFIFO设计
MAC接收控制器中有两个FIFO,我们将存放数据的FIFO称作data_fifo,将存放状态的称作state_fifo。
- 为了后级模块更为简单高效的处理数据,MAC控制器需要以字节的形式传递数据,故data_fifo的位宽为8-bits,具体深度一般会根据要求的性能进行设置,本项目中暂时将深度设为4k,能正好存放2个最长帧。
- 而state_fifo需要记录数据帧的帧长度(以便后级模块知道帧的边界)以及帧的错误信息,所以宽度设置为16-bits,具体格式如下图。考虑到data_fifo深度为4k,若全是最小帧,可以存放64个,所以state_fifo的深度设置为16。
3.3、MAC接收器核心电路设计
由于在发送端每个数据帧的末尾会插入CRC-32的计算值,这会使得在接收端每一个正确的数据帧的CRC校验值为一固定值,当不为此值时,即代表发生CRC error。在设计中,我们可以将该固定值设置为一个parameter,以便大家使用不同的多项式实现CRC。
此外,我们通过检查数据帧的起始标志和结束标志,来判断帧边界,并不断记录数据帧的字节数,当到达结束标志后,检查数据长度是否合规,数据是否是字节对齐的,若发生错误则在state_fifo中记录,这样就完成了MAC接收控制器的所有功能。
需要注意的是数据帧的开始符按照MAC帧格式来看是0x10101011,其中前6位的101010是MAC前导符的延申。前导符是比特‘10’的集合,用于PHY serdes做时钟恢复用(因为serdes的时钟和数据合并在一根数据线上,所以接收端需要做时钟恢复)。所以接收到0x1011即代表接收到有效数据帧(SFD,start frame describe)。
另一点是MAC帧要求在帧与帧之间必须存在间隔,即一个数据帧发送完毕后rx_dv必定存在低电平时刻,所以在设计中,我们使用一个状态机来表示数据帧处于的不同状态,并用于控制各个变量,状态跳转图如下:
将状态机共分为4个状态:ST_SOF、ST_WAIT、ST_EOF、ST_DONE,分别对应等待MAC帧开始、等待帧起始符、等待MAC结束、将各信息传递至fifo。
- 当MAC帧开始,没有接收到SFD时,状态进入ST_WAIT,直到接收到SFD进入ST_EOF,或者rx_dv拉低,该帧为错误帧,回到ST_SOF。此外当data_fifo空间不足以容纳一个最大帧时,会停止接收任何帧,该帧会被丢弃;
- 进入ST_EOF后,接收的数据都为有效数据,需要不断记录接收的字节数,且每收够1字节数据,就写入data_fifo中,直到接收到eof信号,数据帧结束,进入ST_DONE状态,进行数据帧的各类检错;
- ST_DONE内,需要检查帧长度、byte align、CRC error,并将状态写入state_fifo中,下一拍回到ST_SOF,等待下一数据帧。
3.4、MAC接收器代码
控制器的设计并不复杂,Verilog代码将放在下面,Testbench就不展示了,有需要的可以等专题结束后在资源中下载,或者去我的公众号获得链接。
module mac_r(
//system interface
input clk,
input rst_n,
//MII interface
input rx_clk,
input rx_dv,
input [3:0] rx_d,
//mac-r - interface mux
input data_fifo_rd,
output [7:0] data_fifo_dout,
input state_fifo_rd,
output [15:0] state_fifo_dout,
output state_fifo_empty
);
parameter CRC_RESULT = 32'hc704dd7b;
parameter ST_SOF = 4'b0001;
parameter ST_WAIT = 4'b0010;
parameter ST_EOF = 4'b0100;
parameter ST_DONE = 4'b1000;
parameter BCNT_MAX = 1518;
parameter BCNT_MIN = 64;
reg rx_dv_dly0; //用于输入数据有效的采样
reg rx_dv_dly1;
reg [3:0] rx_d_dly0;
reg [3:0] rx_d_dly1;
//信号有效信号的上升沿表示帧开始,下降沿则表示帧结束
wire sof; //start of frame 帧开始标志 只能表示有MAC帧来了
wire eof; //end of frame 帧结束标志
wire sfd; //start frame decribe 帧起始符 sof不代表真正数据帧的开始,sdf才代表!
wire mac_r_rdy; //表示mac_r当前有接收能力
//控制状态机
reg [3:0] cur_state;
reg [3:0] next_state;
//byte cnt变量
wire bcnt_clr; //清零信号
reg [11:0] bcnt;
reg frame_vld; //数据帧有效信号
//fifo
wire [7:0] data_fifo_din;
wire data_fifo_wr;
wire [11:0] data_fifo_wr_cnt;
reg [15:0] state_fifo_din;
reg state_fifo_wr;
wire state_fifo_full;
wire [31:0] crc_reg;
always @(posedge rx_clk or negedge rst_n)begin
if(!rst_n)begin
rx_dv_dly0 <= 1'b0;
rx_dv_dly1 <= 1'b0;
rx_d_dly0[3:0] <= 4'b0;
rx_d_dly1[3:0] <= 4'b0;
end
else begin
rx_dv_dly0 <= rx_dv;
rx_dv_dly1 <= rx_dv_dly0;
rx_d_dly0[3:0] <= rx_d[3:0];
rx_d_dly1[3:0] <= rx_d_dly0[3:0];
end
end
assign sof = !rx_dv_dly1 & rx_dv_dly0;
assign eof = rx_dv_dly1 & !rx_dv_dly0;
assign sfd = rx_dv_dly0 & (rx_d_dly0[3:0]==4'b1011);
//三段式状态机
always @(posedge rx_clk or negedge rst_n)begin
if(!rst_n)
cur_state[3:0] <= ST_SOF;
else
cur_state[3:0] <= next_state[3:0];
end
always @(*)begin
case(cur_state[3:0])
ST_SOF: next_state[3:0] = (sof & mac_r_rdy) ? !sfd ? ST_WAIT : ST_EOF : ST_SOF;
ST_WAIT: next_state[3:0] = rx_dv_dly0 ? sfd ? ST_EOF : ST_WAIT : ST_SOF;
ST_EOF: next_state[3:0] = eof ? ST_DONE : ST_EOF;
ST_DONE: next_state[3:0] = ST_SOF;
default: next_state[3:0] = ST_SOF;
endcase
end
assign bcnt_clr = (sof & sfd) | (cur_state[3:0]==ST_WAIT & sfd);
always @(posedge rx_clk or negedge rst_n)begin
if(!rst_n)
bcnt[11:0] <= 12'b0;
else if(bcnt_clr)
bcnt[11:0] <= 12'b0;
else
bcnt[11:0] <= bcnt[11:0] + 12'b1;
end
always @(posedge rx_clk or negedge rst_n)begin
if(!rst_n)
frame_vld <= 1'b0;
else if(cur_state[3:0]==ST_EOF & eof)
frame_vld <= 1'b0;
else if( (cur_state[3:0]==ST_SOF & sof & sfd) | (cur_state[3:0]==ST_WAIT & rx_dv_dly0 & sfd))
frame_vld <= 1'b1;
end
assign data_fifo_wr = frame_vld & bcnt[0] & rx_dv_dly0; //当存够两个rx_data时,且帧有效时,向data_fifo中存一次数据
assign data_fifo_din = {rx_d_dly0,rx_d_dly1};
assign mac_r_rdy = (data_fifo_wr_cnt[11:0]> 4096 - BCNT_MAX);
always @(posedge rx_clk or negedge rst_n)begin
if(!rst_n)
state_fifo_wr <= 1'b0;
else if(cur_state[3:0]==ST_EOF & eof)
state_fifo_wr <= 1'b1;
else
state_fifo_wr <= 1'b0;
end
always @(posedge rx_clk or negedge rst_n)begin
if(!rst_n)
state_fifo_din[15:0] <= 16'b0;
else if(cur_state[3:0]==ST_EOF & eof)begin
state_fifo_din[10:0] <= bcnt[11:1];
state_fifo_din[14] <=( (bcnt[11:1] > BCNT_MAX) | (bcnt[11:1] < BCNT_MIN) | bcnt[0] ) ? 1'b1 :1'b0;
state_fifo_din[15] <= (crc_reg[31:0]==CRC_RESULT) ? 1'b0 : 1'b1;
end
end
data_fifo x_data_fifo(
.rst(~rst_n),
.wr_clk(rx_clk),
.rd_clk(clk),
.din(data_fifo_din[7:0]),
.wr_en(data_fifo_wr),
.rd_en(data_fifo_rd),
.dout(data_fifo_dout[7:0]),
.full(),
.empty(),
.rd_data_count(),
.wr_data_count(data_fifo_wr_cnt[11:0])
);
state_fifo x_state_fifo(
.rst(~rst_n),
.wr_clk(rx_clk),
.rd_clk(clk),
.din(state_fifo_din[15:0]),
.wr_en(state_fifo_wr),
.rd_en(state_fifo_rd),
.dout(state_fifo_dout[15:0]),
.full(state_fifo_full),
.empty(state_fifo_empty)
);
crc32 x_crc32(
.clk(clk),
.rst_n(rst_n),
.data(data_fifo_din[7:0]),
.init(sof),
.cal(data_fifo_wr),
.vld(data_fifo_wr),
.crc_reg(crc_reg[31:0]),
.crc() //大端输出
);
endmodule
搜索关注我的微信公众号【IC墨鱼仔】,获取我的更多IC干货分享!