软件开发工程师就业前景,怎样做网站关键词优化,免费发布黄页广告网站,广告公司名字大全创意对于初学者#xff0c;一般会遇到这种情况#xff0c;明明写的时序逻辑#xff0c;结果仿真结果却是组合逻辑#xff0c;然后看遍设计代码#xff0c;始终找不到原因#xff0c;交流群、知乎这种问题随处可见。但不要怀疑软件问题#xff0c;modelsim这些专用软件基本不… 对于初学者一般会遇到这种情况明明写的时序逻辑结果仿真结果却是组合逻辑然后看遍设计代码始终找不到原因交流群、知乎这种问题随处可见。但不要怀疑软件问题modelsim这些专用软件基本不会遇见软件自身问题原因其实很简单因为多数人只关注设计文件不会关注TestBentch的合理性导致找不到问题原因后文分析原因并给出避免这种问题的方法。 在仿真时经常会使用“#”和“(posedge clk)”来实现延迟“#”后面跟数字表示延迟数字对应最小的时间单位而“(posedge clk)”则用来检测clk信号上升沿如果CYCLE表示始终周期对应的时间长度那么“#(CYCLE)”表示延迟一个时钟周期长度的时间。在时钟上升沿输出数据后使用“(posedge clk)”会延迟到下个时钟上升沿同样也可以表示延迟一个时钟周期那有没有区别 分析以下代码输出dout就是两个输入dataa与datab相加由于是时序逻辑dout会延迟dataa或datab变化后的一个时钟周期。
module add( input clk ,//系统时钟 input rst_n ,//系统复位低电平有效 input [3 : 0] data_a ,//加数dataa input [3 : 0] data_b ,//加数datab output reg [4 : 0] dout );always(posedge clk or negedge rst_n)begin if(rst_n1b0)begin//初始值为0; dout 4d0; end else begin dout data_a data_b; end endendmoduleTestbench如下所示
timescale 1 ns/1 ns
module test(); parameter CYCLE 10 ;//系统时钟周期单位ns默认10ns parameter RST_TIME 5 ;//系统复位持续时间 parameter STOP_TIME 100 ;//仿真运行时间复位完成后运行100个系统时钟后停止 reg clk ;//系统时钟默认100MHz reg rst_n ;//系统复位默认低电平有效 reg [3 : 0] data_a; reg [3 : 0] data_b;wire [4 : 0] dout ; add u_add ( .clk ( clk ), .rst_n ( rst_n ), .data_a ( data_a ), .data_b ( data_b ), .dout ( dout ) ); //生成周期为CYCLE数值的系统时钟; initial begin clk 1; forever #(CYCLE/2) clk~clk; end//生成复位信号 initial begin rst_n 1; #2; rst_n 0;//开始时复位10个时钟 #(RST_TIME*CYCLE); rst_n 1; end //生成输入信号din; initial begin data_a 0;data_b0;//输入数据初始化为0 #(10*CYCLE);//延迟10个时钟周期 repeat(STOP_TIME)begin//循环STOP_TIME次#(CYCLE); data_a {$random} % 16; data_b {$random} % 16; end $stop;//停止仿真 endendmodule使用modelsim仿真如下图所示奇怪的是为什么时序逻辑仿真成组合逻辑了 图1 使用#延迟仿真结果 分析加法器代码肯定是没有问题的modelsim软件也是经过fpga设计以及IC设计人员多年使用是最常用的仿真工具也不可能出现这样的低级bug。如果你把代码下载到开发板上使用在线逻辑分析仪抓取数据能够得到正确的运行结果但是仿真就是错误的。这是为什么那就只剩下写的testbench文件了来看下testbench与输出相关的信号首先时钟和复位信号是没有问题的那就只剩下dataa与datab的产生模块了如下所示
//生成输入信号din;
initial begin data_a 0;data_b0;//输入数据初始化为0 #(10*CYCLE);//延迟10个时钟周期 repeat(STOP_TIME)begin//循环STOP_TIME次#(CYCLE); data_a {$random} % 16; data_b {$random} % 16; end $stop;//停止仿真
end开始仿真时两个输入信号都被赋值为0经10个时钟延迟之后进入forever循环内每次循环之前都会把数据延迟一个时钟周期然后在对两个输入信号赋一个0~15的随机值。逻辑上其实没有问题但是注意一个问题每次给dataa和datab赋值时间与时钟clk上升沿是对齐的导致D触发器的输入信号在时钟上升沿时发生变化由此导致D触发器数据采集错误最终导致D触发器输出信号dout提前更新数据。这里实际上与保持时间违例有点类似D触发器的下一个输入数据来得过快影响了上一个数据的采集。 解决方法很简单因为是数据刚好在时钟上升沿时发生更新导致D触发器数据采集错误那么把两个输入数据全部延迟一点不就行了修改如下将两个输入数据的所有变化均延迟1ns与时钟上升沿错开。
//生成输入信号din;
initial begin #1; data_a 0;data_b0;//输入数据初始化为0 #(10*CYCLE);//延迟10个时钟周期 repeat(STOP_TIME)begin//循环STOP_TIME次 #(CYCLE); data_a {$random} % 16; data_b {$random} % 16; end $stop;//停止仿真
end修改后仿真结果如下 图2 添加#1的仿真结果 从上面仿真结果就可以看到dataa与datab的变化都延迟时钟上升沿1ns之后就没有在影响仿真结果了。这也是为什么很多代码在对信号赋值之前会延迟1ns的原因就是为了数据变化与时钟上升沿错开避免发生上面这种由于testbench书写问题所引发的离奇结果。 使用“#”会引发上面问题那如果过使用“(posedge clk)”这种写法还会出现那样的仿真结果 先给答案不会出现类似问题因为“(posedge clk)”表示已经检测到时钟上升沿了那么在这之后更新的数据自然与时钟上升沿就是错开的了。 同样的案例只是把dataa和datab赋值的部分改成如下代码。
//生成输入信号din;
initial begin data_a 0;data_b0;//输入数据初始化为0 #(10*CYCLE);//延迟10个时钟周期 repeat(STOP_TIME) (posedge clk)begin//循环STOP_TIME次 data_a {$random} % 16; data_b {$random} % 16; end $stop;//停止仿真
end上面代码表达结果与下面代码一致。
//生成输入信号din;
initial begin data_a 0;data_b0;//输入数据初始化为0 #(10*CYCLE);//延迟10个时钟周期 repeat(STOP_TIME)begin//循环STOP_TIME次 (posedge clk); data_a {$random} % 16; data_b {$random} % 16; end $stop;//停止仿真
end仿真结果如下没有出现任何问题数据变化近似与时钟上升沿对齐但是输出数据dout没有受到影响这就是“(posedge clk)”的效果。 图3 使用(posedge clk)的仿真结果 “#”和“(posedge clk)”虽然都可以写成延时几个时钟周期的形式但是他们是有区别的区别在与“#”延迟与时钟其实没有关系就有可能和时钟上升沿重合这是使用是需要注意的建议在仿真开始时就对数据延迟1ns然后在赋值与时钟信号变化错位。而“(posedge clk)”本质是检测时钟上升沿在时钟上升沿之后才会去执行后面的语句所以数据变化与时钟上升沿变化是错位的不会出现“#”那种问题。 上述从效果看肯定是使用“(posedge clk)”作为延迟更好使用“#(CYCLE)”延迟一个时钟更号理解但是要注意可能遇到的问题一般使用模板时赋值语句开头会带有“#1;”或者在每次赋值前用“(posedge clk)”作为延迟如下我常用赋值模板。
//生成输入信号din;
initial begin #1;din 0;//输入数据初始化为0 #(10*CYCLE);//延迟10个时钟周期 end