本文设计思想采用明德扬至简设计法。上一篇博文中定制了自定义mac ip的结构,在用户侧需要位宽转换及数据缓存。本文以tx方向为例,设计并验证发送缓存模块。这里定义该模块可缓存4个最大长度数据包,用户根据需求改动即可。
该模块核心是利用异步fifo进行跨时钟域处理,位宽转换由veriloghdl实现。需要注意的是用户数据包位宽32bit,因此包尾可能有无效字节,而转换为8bit位宽数据帧后是要丢弃无效字节的。内部逻辑非常简单,直接上代码:
`timescale 1ns / 1ps
// description: mac ip tx方向用户数据缓存及位宽转换模块
// 整体功能:将tx方向用户32bit位宽的数据包转换成8bit位宽数据包
//用户侧时钟100mhz,mac侧125mhz
//缓存深度:保证能缓存4个最长数据包,tx方向用户数据包包括
//目的mac地址 源mac地址 类型/长度 数据 最长1514byte
module tx_buffer#(parameter data_w = 32)//位宽不能改动
(
//全局信号
input rst_n,//保证拉低三个时钟周期,否则fif可能不会正确复位
//用户侧信号
input user_clk,
input [data_w-1:0] din,
input din_vld,
input din_sop,
input din_eop,
input [2-1:0] din_mod,
output rdy,
//mac侧信号
input eth_tx_clk,
output reg [8-1:0] dout,
output reg dout_sop,
output reg dout_eop,
output reg dout_vld
);
reg wr_en = 0;
reg [data_w+4-1:0] fifo_din = 0;
reg [ (2-1):0] rd_cnt = 0 ;
wire add_rd_cnt ;
wire end_rd_cnt ;
wire rd_en;
wire [data_w+4-1:0] fifo_dout;
wire rst;
reg [ (2-1):0] rst_cnt =0 ;
wire add_rst_cnt ;
wire end_rst_cnt ;
reg rst_flag = 0;
wire [11 : 0] wr_data_count;
wire empty;
wire full;
/****************************************写侧*************************************************/
always @(posedge user_clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_en end
else if(rdy)
wr_en end
always @(posedge user_clk or negedge rst_n)begin
if(rst_n==1'b0)begin
fifo_din end
else begin//[35] din_sop [34] din_eop [33:32] din_mod [31:0] din
fifo_din end
end
assign rdy = wr_data_count
/****************************************读侧*************************************************/
always @(posedge eth_tx_clk or negedge rst_n) begin
if (rst_n==0) begin
rd_cnt end
else if(add_rd_cnt) begin
if(end_rd_cnt)
rd_cnt else
rd_cnt end
end
assign add_rd_cnt = (!empty);
assign end_rd_cnt = add_rd_cnt && rd_cnt == (4)-1 ;
assign rd_en = end_rd_cnt;
always @(posedge eth_tx_clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout end
else if(add_rd_cnt)begin
dout end
end
always @(posedge eth_tx_clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout_vld end
else if(add_rd_cnt && ((rd_cnt dout_vld end
else
dout_vld end
always @(posedge eth_tx_clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout_sop end
else if(add_rd_cnt && rd_cnt == 0 && fifo_dout[35])begin
dout_sop end
else
dout_sop end
always @(posedge eth_tx_clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout_eop end
else if(add_rd_cnt && rd_cnt == 3 - fifo_dout[33:32] && fifo_dout[34])begin
dout_eop end
else
dout_eop end
/******************************fifo复位逻辑****************************************/
assign rst = !rst_n || rst_flag;
always @(posedge user_clk or negedge rst_n)begin
if(!rst_n)begin
rst_flag end
else if(end_rst_cnt)
rst_flag end
always @(posedge user_clk or negedge rst_n) begin
if (rst_n==0) begin
rst_cnt end
else if(add_rst_cnt) begin
if(end_rst_cnt)
rst_cnt else
rst_cnt end
end
assign add_rst_cnt = (rst_flag);
assign end_rst_cnt = add_rst_cnt && rst_cnt == (3)-1 ;
//fifo位宽32bit 一帧数据最长1514byte,即379个16bit数据
//fifo深度:379*4 = 1516 需要2048
//异步fifo例化
fifo_generator_0 fifo (
.rst(rst), // input wire rst
.wr_clk(user_clk), // input wire wr_clk 100mhz
.rd_clk(eth_tx_clk), // input wire rd_clk 125mhz
.din(fifo_din), // input wire [33 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(fifo_dout), // output wire [33 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.wr_data_count(wr_data_count) // output wire [11 : 0] wr_data_count
);
endmodule
tx_buffer
接下来是验证部分,也就是本文的重点。以下的testbench包含了最基本的测试思想:发送测试激励给uut,将uut输出与黄金参考值进行比较,通过记分牌输出比较结果。
`timescale 1ns / 1ps
module tx_buffer_tb( );
parameter user_clk_cyc = 10,
eth_clk_cyc = 8,
rst_tim = 3;
parameter sim_tim = 10_000;
reg user_clk;
reg rst_n;
reg [32-1:0] din;
reg din_vld,din_sop,din_eop;
reg [2-1:0] din_mod;
wire rdy;
reg eth_tx_clk;
wire [8-1:0] dout;
wire dout_sop,dout_eop,dout_vld;
reg [8-1:0] dout_buf [0:1024-1];
reg [16-1:0] len [0:100-1];
reg [2-1:0] mod [0:100-1];
reg err_flag = 0;
tx_buffer#(.data_w(32))//位宽不能改动
dut
(
//全局信号
.rst_n (rst_n) ,//保证拉低三个时钟周期,否则fif可能不会正确复位
.user_clk (user_clk) ,
.din (din) ,
.din_vld (din_vld) ,
.din_sop (din_sop) ,
.din_eop (din_eop) ,
.din_mod (din_mod) ,
.rdy (rdy) ,
.eth_tx_clk (eth_tx_clk) ,
.dout (dout) ,
.dout_sop (dout_sop) ,
.dout_eop (dout_eop) ,
.dout_vld (dout_vld)
);
/***********************************时钟******************************************/
initial begin
user_clk = 1;
forever #(user_clk_cyc/2) user_clk = ~user_clk;
end
initial begin
eth_tx_clk = 1;
forever #(eth_clk_cyc/2) eth_tx_clk = ~eth_tx_clk;
end
/***********************************复位逻辑******************************************/
initial begin
rst_n = 1;
#1;
rst_n = 0;
#(rst_tim*user_clk_cyc);
rst_n = 1;
end
/***********************************输入激励******************************************/
integer gen_time = 0;
initial begin
#1;
packet_initial;
#(rst_tim*user_clk_cyc);
packet_gen(20,2);
#(user_clk_cyc*10);
packet_gen(30,1);
end
/***********************************输出缓存与检测******************************************/
integer j = 0;
integer chk_time = 0;
initial begin
forever begin
@(posedge eth_tx_clk)
if(dout_vld)begin
if(dout_sop)begin
dout_buf[0] = dout;
j = 1;
end
else if(dout_eop)begin
dout_buf[j] = dout;
j = j+1;
packet_check;
end
else begin
dout_buf[j] = dout;
j = j+1;
end
end
end
end
/***********************************score board******************************************/
integer fid;
initial begin
fid = $fopen(test.txt);
$fdisplay(fid, start testing /n);
#sim_tim;
if(err_flag)
$fdisplay(fid,check is failed/n);
else
$fdisplay(fid,check is successful/n);
$fdisplay(fid, testing is finished /n);
$fclose(fid);
$stop;
end
/***********************************子任务******************************************/
//包生成子任务
task packet_gen;
input [16-1:0] length;
input [2-1:0] invalid_byte;
integer i;
begin
len[gen_time] = length;
mod[gen_time] = invalid_byte;
for(i = 1;i if(rdy == 1)begin
din_vld = 1;
if(i==1)
din_sop = 1;
else if(i == length)begin
din_eop = 1;
din_mod = invalid_byte;
end
else begin
din_sop = 0;
din_eop = 0;
din_mod = 0;
end
din = i ;
end
else begin
din_sop = din_sop;
din_eop = din_eop;
din_vld = 0;
din_mod = din_mod;
din = din;
i = i - 1;
end
#(user_clk_cyc*1);
end
packet_initial;
gen_time = gen_time + 1;
end
endtask
task packet_initial;
begin
din_sop = 0;
din_eop = 0;
din_vld = 0;
din = 0;
din_mod = 0;
end
endtask
//包检测子任务
task packet_check;
integer k;
integer num,packet_len;
begin
num = 1;
$fdisplay(fid,%dth:packet checking.../n,chk_time);
packet_len = 4*len[chk_time]-mod[chk_time];
if(j != packet_len)begin
$fdisplay(fid,length of the packet is wrong./n);
err_flag = 1;
disable packet_check;
end
for(k=0;k
if(k%4 == 3)begin
if(dout_buf[k] != num)begin
$fdisplay(fid,data of the packet is wrong!/n);
err_flag = 1;
end
num = num+1;
end
else if(dout_buf[k] != 0)begin
$fdisplay(fid,data of the packet is wrong,it should be zero!/n);
err_flag = 1;
end
end
chk_time = chk_time + 1;
end
endtask
endmodule
tx_buffer_tb
可见主要是task编写及文件读写操作帮了大忙,如果都用眼睛看波形来验证设计正确性,真的是要搞到眼瞎。为保证测试完备性,测试包生成task可通过输入接口产生不同长度和无效字节数的递增数据包。testbench中每检测到输出包尾指示信号eop即调用packet_check task对数值进行检测。本文的testbench结构较具通用性,可以用来验证任意对数据包进行处理的逻辑单元。
之前modelsim独立仿真带有ip核的vivado工程时经常报错,只好使用vivado自带的仿真工具。一直很头痛这个问题,这次终于有了进展!首先按照常规流程使用vivado调用modelsim进行行为仿真,启动后会在工程目录下产生些有用的文件,帮助我们脱离vivado进行独立仿真。
在新建modelsim工程时,在红框内选择vivado工程中
.sim -> sim_1 -> behav下的modelsim.ini文件。之后添加文件包括:待测试设计文件、testbench以及ip核可综合文件。第三个文件在
.srcs -> sources_1 -> ip -> -> synth下。
现在可以顺利启动仿真了。我们来看下仿真结果:
文件中信息打印情况:
从波形和打印信息的结果来看,基本可以证明数据缓存及位宽转换模块逻辑功能无误。为充分验证要进一步给出覆盖率较高的测试数据集,后期通过编写do文件批量仿真实现。在fpga或ic设计中,验证占据大半开发周期,可见veriloghdl的非综合子集也是至关重要的,今后会多总结高效的验证方法!
高性能VLS-II系列燃料电池量产,可提供每升4.2kW的高功率密度
入手STM32单片机的知识点总结
springboot自动配置的原理介绍
巴西电信表示未来网络发展需要考虑社会经济和服务的统一化因素
LED光源实现植物生长动态补光控制的几大要点
FPGA设计案例:数据缓存模块设计与验证实验
什么蓝牙耳机好?口碑逆天的五款蓝牙耳机
美国军方正在考虑利用AR/VR功能来进行军队训练
颈挂式蓝牙耳机哪个牌子好?性价比颈挂式蓝牙耳机排名
贴片Y电容的规格参数有哪些?弗瑞鑫来解答!
中国光伏企业的未来,筹码是国外新兴市场?
2K+骁龙835+8G+全面屏,一加5即将发布碾压小米6
小米上下折叠手机专利被曝光 配备翻转式镜头
Ripple推出的XRP代币将成为下一个比特币
爱特梅尔推出低功耗8位微控制器ATtiny 10/20/40
瑞萨电子扩展“云实验室”,重塑远程设计
到2030年电池市场年价值将达到1160亿美元
Google的“任务”应用的功能类似于数字任务列表
22日麻辣手机:山寨机厂商拟印度建厂
莱斯大学科学家首次成功磁约束超冷等离子体