注:此版本没有添加ARP PING 等,未完待续。
注:项目采用Verilog开发,基于Vivado编译器。
注:本版本没有计算校验
与上一篇相同开发环境,采用三段式状态机。
同样,接收后将数据写入FIFO,相比于数据发送更为简单,只需在写入数据时同步拉高FIFO使能就可以。
对外接口如下:
input clk_i, //RX_clk
input rst_n,
/*eth interface*/
input Rxer_i,
input Rxdv_i,
input [ 7:0] Rxd_o,
/*control interface*/
input [ 3:0] Eth_Command,
/*fifo interface*/
output reg [ 7:0] Fifo_Data_i,
output reg Fifo_Wr_en
其中Rxdv_i为关键信号数据输入有效,只有当此信号为高,才证明这个数据是正确的。
Rxer_i 这个信号暂时没有用,具体在板上验证时做修改。
本模块状态划分如下:
localparam idle = 8'b1111_1110; //fe
localparam Rec_MAC_addr = 8'b1111_1101; //fd
localparam Rec_IP_addr = 8'b1111_1011; //fb
localparam Rec_ARP = 8'b1111_0111; //f7
localparam Rec_Data = 8'b1110_1111; //ef
localparam Rec_Over = 8'b1101_1111; //df
localparam Rec_Zero = 8'b1011_1111; //bf
状态1、空闲状态:当使能开启时,等待外部输入正确引导码启动
状态2、接收MAC地址,同时判断是否正确
状态3、同上接收正确IP地址
状态4、保留,上次还没有做ARP的发送
状态5、开始接收数据,从上面信息中提取数据数量,来判断需要读取多少数据
状态6、接收完成,写入本次数据的帧尾(包计数、数据量),用于判断是否丢包或丢数据
状态7、将各寄存器清零。如果不清,会在起始状态时寄存器内就有正确数据。
切换状态条件:
idle: nstate <= ( (Eth_Command[3] == 1'b1) &&
(Identifiy_Data[63:0] == `Leading_code))
? Rec_MAC_addr : idle;
Rec_MAC_addr: begin
if( State_turn == 1'b1 ) begin
if(MAC_Data[ 63:16] == `Destination_MAC && MAC_Data[111:64] == `Source_MAC) begin
if(MAC_Data[ 15: 0] == `IP_TYPE) begin
nstate <= Rec_IP_addr;
end
else if(MAC_Data[ 15: 0] == `IP_TYPE_ARP) begin
nstate <= Rec_ARP;
end
else begin
nstate <= idle;
end
end
else begin
nstate <= idle;
end
end
else begin
nstate <= Rec_MAC_addr;
end
end
Rec_IP_addr: begin
if( State_turn == 1'b1 )begin
if(Header_Data[ 47:32] == `Destination_Port &&
Header_Data[ 63:48] == `Source_Port &&
Header_Data[ 95:64] == `Destination_IP &&
Header_Data[127:96] == `Source_IP ) begin
nstate <= Rec_Data;
end
else begin
nstate <= idle;
end
end
else begin
nstate <= Rec_IP_addr;
end
end
Rec_Data: nstate <= (State_turn == 1'b1) ? Rec_Over : Rec_Data;
Rec_Over: nstate <= (State_turn == 1'b1) ? Rec_Zero : Rec_Over;
Rec_Zero: nstate <= idle;
当外部命令开启使能,就开始接收数据,接收到争取引导码后,跳入下一状态
Start_turn :与上一篇判断条件一样,接收正确数量的数据后,判断各地址是否正确,正确进入下一状态,不正确跳回idle。
数据管理代码
idle: begin
if(Rxdv_i)begin
Identifiy_Data[63:0] <= {Identifiy_Data[55:0],Rxd_o[7:0]};
end
else begin
Identifiy_Data[63:0] <= Identifiy_Data[63:0];
end
end
Rec_MAC_addr: begin
if(Rxdv_i)begin
MAC_Data[111:0] <= {MAC_Data[104:0],Rxd_o[7:0]};
end
else begin
MAC_Data[111:0] <= MAC_Data[111:0];
end
end
Rec_IP_addr: begin
if(Rxdv_i)begin
Header_Data[223:0] <= {Header_Data[216:0],Rxd_o[7:0]};
end
else begin
Header_Data[223:0] <= Header_Data[223:0];
end
end
Rec_Data,Rec_Over: begin
Identifiy_Data[ 63:0] <= Identifiy_Data[ 63:0];
MAC_Data [111:0] <= MAC_Data [111:0];
Header_Data [223:0] <= Header_Data [223:0];
end
Rec_Zero:begin
Identifiy_Data[ 63:0] <= { 8{8'h00}};
MAC_Data [111:0] <= {14{8'h00}};
Header_Data [223:0] <= {28{8'h00}};
end
Rxdv_i拉高,就收取数据,做移位操作,写入寄存器,接收到正确数量后,进行判断实现状态跳转。
FIFO写入代码
Rec_Data: begin
if(Rxdv_i)begin
Fifo_Data_i[7:0]<= Rxd_o[7:0];
Fifo_Wr_en <= 1'b1;
end
else begin
Fifo_Data_i[7:0]<= Fifo_Data_i[7:0];
Fifo_Wr_en <= 1'b0;
end
end
能正确跳入这个状态后,就开始按照提取出的Data_length个数,开始写就行了。
接收整体代码不多,没加校验,具体等实际用的时候看情况需不需要。
本工程是用于记录以太网数据,并写入存储器内。
还在等板子中…先看看仿真叭,代码:
initial begin
clk_o = 1'b1;
forever begin
#10ns clk_o = ~clk_o; //10Mhz
end
//#10200 RX_START = 1'b0;
end
initial begin
rst_n = 1'b0;
Data_length = 16'd1500;
#1000ns rst_n = 1'b1;
end
always @(posedge clk_o or negedge rst_n)
begin
if(!rst_n) begin
Fifo_Data_i <= 8'hFF;
end
else if (Fifo_Rd_en)begin
Fifo_Data_i <= Fifo_Data_i + 1'b1;
end
else begin
Fifo_Data_i <= Fifo_Data_i;
end
end
always @(posedge clk_o or negedge rst_n)
begin
if(!rst_n) begin
Eth_Command = 4'h0;
#500ns
Eth_Command = 4'h0;
end
else begin
if(Eth_Write_Busy) begin
Eth_Command = 4'h0;
end
else begin
#1000ns
Eth_Command = 4'hA;
end
end
end
endmodule
顶层连接关系:ETH_Send是上篇文章的代码
test test_u(
.clk_o (clk_i),
.rst_n (rst_n),
.Eth_Command (Eth_Command),
.Data_length (Data_length),
.Eth_Write_Busy (Eth_Write_Busy),
.Fifo_Data_i (Fifo_Data_o),
.Fifo_Rd_en (Fifo_Rd_en)
);
ETH_Send ETH_Send_u(
.clk_i (clk_i),
.rst_n (rst_n),
.Txen_o (Txen_o),
.Txer_o (Txer_o),
.Txd_o (Txd_o),
.Eth_Command (Eth_Command),
.Data_length (Data_length),
.Eth_Write_Busy (Eth_Write_Busy),
.Fifo_Data_o (Fifo_Data_o),
.Fifo_Rd_en (Fifo_Rd_en)
);
ETH_Receive ETH_Receive_u(
.clk_i (clk_i),
.rst_n (rst_n),
.Rxer_i (1'b0),
.Rxdv_i (Txen_o),
.Rxd_o (Txd_o),
.Eth_Command (Eth_Command),
.Fifo_Data_i (Fifo_Data_i),
.Fifo_Wr_en (Fifo_Wr_en)
);
问题不大,下面看看细节
正确提取引导码、MAC、IP、并提取出数据长度、包计数。
同时把发送读fifo的首个数据写入fifo中。
本项目还有个问题:读出数据后,会在下一个时钟发送出去,这样会时钟存在一个数据在线上,所以会首先发送出一个数据是上次读出来。这样就会在第一帧数据内多一个无效数,对后面没有任何影响,保证连续不丢。对我的应用没有影响…也就懒得改了。
暂时的想法是将读fifo的vaild信号与Txen_o强关联。具体上板验证时,再调叭。
对于接收,因为发送端fifo的问题,在接收过程中就是写入实际收到的数据。
可以看到把1500数据写入后写入我的帧尾 FF_FF+包计数+数据长度+FF_FF,然后将用于判断的寄存器清零。
简单收发已经实现,下一步准备添加其他模式。