指令监测模块实现介绍
如下图所示,为指令监测模块的运行框图
将指令设置为8bytes数据,故需要一个64位寄存器进行缓存,在进行数据缓存时,数据不可以输出至下一级模块,故对数据和有效指示信号也应该进行相应延迟,指令缓存8周期,检验一周期,为了数据的稳定输出,故延迟10周期,使用shift_ram ip实现该功能即可。
当监测到头部8个数据不属于指令数据后,进行有效数据输出,而若监测到为指令数据,则在输出有效信号置0,下一级模块即不会进行数据缓存,避免数据文件缓存错误。
指令监测模块代码编写
根据上述框图的介绍,以及逻辑的梳理,本节代码可以清晰写出,其代码实现如下:
module udp_cmd_check(input i_clk ,input i_rst ,input [7 :0] i_udp_data ,input i_udp_valid ,output [7 :0] o_udp_data ,output o_udp_valid ,output o_store_done ,output o_raddr_clear );reg [63:0] r_check_data ;
reg [1 :0] r_check_res ;
reg [1 :0] r_clear_res ;
reg [1 :0] ro_raddr_clear ;
wire [8 :0] w_dly_din ;
wire [8 :0] w_dly_dout ;
wire w_store_done ;
wire w_raddr_clear ;assign w_dly_din = {i_udp_data,i_udp_valid};dly_w9d10 dly_w9d10_u0 (.D (w_dly_din ),.CLK (i_clk ),.Q (w_dly_dout )
);assign w_store_done = &r_check_res ? 1'b1 : 1'b0;
assign w_raddr_clear= &r_clear_res ? 1'b1 : 1'b0;
assign o_udp_data = w_dly_dout[8 :1] ;
assign o_udp_valid = (w_store_done == 1'b1 || w_raddr_clear == 1'b1) ? 1'b0 : w_dly_dout[0];
assign o_store_done = w_store_done ;
assign o_raddr_clear= w_raddr_clear ;always @(posedge i_clk,posedge i_rst) beginif(i_rst)r_check_data <= 64'd0;else if(i_udp_valid) r_check_data <= {r_check_data[55:0],i_udp_data};
endalways @(posedge i_clk,posedge i_rst) beginif(i_rst)r_check_res <= 2'b00;else if(r_check_data[63:32] == 32'HA5A5A5A5 && r_check_data[31 :0] == 32'HBCBCBCBC) r_check_res <= 2'b11;elser_check_res <= 2'b00;
endalways @(posedge i_clk,posedge i_rst) beginif(i_rst)r_clear_res <= 2'b00;else if(r_check_data[63:32] == 32'HD5D5D5D5 && r_check_data[31 :0] == 32'HFCFCFCFC) r_clear_res <= 2'b11;else if(r_dly_valid & ~w_dly_dout[0])r_clear_res <= 2'b00;
endendmodule
实际r_check_res 和 r_clear_res 只需1bit也可,笔者之前为了降低逻辑位宽过大,曾对高32位和低32位进行分别判断,测试来看,上述代码也可以正常运转,时序不存在问题。
指令监测模块仿真
在代码编写完成后,便需要进行仿真测试,编写仿真文件,因为在之后的各个模块检验中,都需要进行udp数据接收的模拟,笔者便将udp数据发送、指令发送,编写为对应的任务块,方便进行复用,tb文件代码如下
reg i_udp_clk = 1'b0;
reg i_udp_rst = 1'b0;
wire [7 :0] w_store_udp_data ;
wire w_store_udp_valid ;
wire w_store_done ;
wire w_raddr_clear ;
integer i = 0;
integer j = 0;
always #4 i_udp_clk = ~i_udp_clk;
initial begini_udp_rst = 1;i_sfp_rst = 1;i_ui_rst = 1;#100@(i_sfp_clk) begini_udp_rst <= 1'b0;i_sfp_rst <= 1'b0;i_ui_rst <= 1'b0;end#100/*传输擦除指令*/@(posedge i_udp_clk)udp_cmd(64'HD5D5D5D5_FCFCFCFC);/*传输32KB*/@(posedge i_udp_clk)for(i = 0;i < 32; i = i + 1) begin@(posedge i_udp_clk)udp_send(1024);#500@(posedge i_udp_clk);end/*传输完成指令*/@(posedge i_udp_clk)udp_cmd(64'HA5A5A5A5_BCBCBCBC);
/*指令监测,输出监测后数据*/
udp_cmd_check udp_cmd_check_u0(.i_clk (i_udp_clk ),.i_rst (i_udp_rst ),.i_udp_data (i_udp_data ),.i_udp_valid (i_udp_valid ),.o_udp_data (w_store_udp_data ),.o_udp_valid (w_store_udp_valid ),.o_store_done (w_store_done ),.o_raddr_clear (w_raddr_clear ));
task udp_send(input [15:0] byte_len);begin : datainteger i;i_udp_data = 8'd0;i_udp_valid = 1'd0;@(posedge i_udp_clk);for(i = 0;i < byte_len ;i = i + 1)begini_udp_data <= i_udp_data + 1'b1;i_udp_valid <= 1'b1;@(posedge i_udp_clk);endi_udp_data <= 8'd0;i_udp_valid <= 1'd0;
end
endtasktask udp_cmd(input [63:0] i_cmd);begin : cmdinteger i;i_udp_data = 8'd0;i_udp_valid = 1'd0;@(posedge i_udp_clk);for(i = 0;i < 8 ;i = i + 1)begini_udp_data <= i_cmd[63:56];i_cmd <= {i_cmd[55:0],8'h0};i_udp_valid <= 1'b1;@(posedge i_udp_clk);endi_udp_data <= 8'd0;i_udp_valid <= 1'd0;
end
endtask
其模拟过程即是,首先发送擦除指令,然后进行数据发送,在数据传输完成后,发送数据完成指令,在仿真过程中,就这三项测试做验证,监测模块是否成功识别指令,阻止指令数据输出,以及是否成功输出有效数据。
由图片可知,模块成功监测到DDR擦除指令,并将清除信号输出置高多周期,数据输出有效信号置低,在标准以太网中帧长间隔为12bytes,在仿真测试中,并未严格要求该参数,但实际模块工作可以适应这类情况。
由图片可知,成功对数据进行了下一级模块转发。
观察文件传输完成,发送结束码,模块成功监测出结束码,拉高传输完成信号,并且数据未进行有效输出,避免了错误缓存。
经过上述仿真,可以看出该模块正常工作,对于该模块的编写是较为简单的,但指令识别功能,在开发中,是十分常用的模块,有必要进行掌握。关于本节代码的问题,以及优化意见,欢迎大家在评论区指出,如果想要对应工程进行学习,欢迎大家私信。