现象
本来是在通过Xilinx提供的 Clocking Wizard IP核做有关锁相环的实验,在Vivado中的仿真结果如下:
后面输出的时钟波形是对的,但4路输出前确有一小段高电平,仔细放大看一下:
这个高电平持续了一个周期,这就奇怪了,无论是源代码还是仿真代码,都不涉及到它们四个wire类型的变量的初值之类的。
原始代码
源代码:
verilog
module ip_clk_wiz(
input sys_clk_p,
input sys_clk_n,
input sys_rst_n,
output o_pll_clk
);
wire clk_out1;
wire clk_out2;
wire clk_out3;
wire clk_out4;
assign o_pll_clk = clk_out4;
clk_wiz_0 instance_name(
.clk_out1 (clk_out1 ),
.clk_out2 (clk_out2 ),
.clk_out3 (clk_out3 ),
.clk_out4 (clk_out4 ),
.reset (~sys_rst_n),
.locked ( ),
.clk_in1_p (sys_clk_p ),
.clk_in1_n (sys_clk_n )
);
endmodule
tb代码:
verilog
`timescale 100ps / 100ps
module tb_ip_clk_wiz();
reg sys_clk_p;
reg sys_clk_n;
reg sys_rst_n;
initial begin
sys_clk_p <= 1'b0;
sys_clk_n <= 1'b1;
sys_rst_n <= 1'b0;
#1000
sys_rst_n <= 1'b1;
end
always begin
sys_clk_p <= 1'b0;
sys_clk_n <= 1'b1;
#25;
sys_clk_p <= 1'b1;
sys_clk_n <= 1'b0;
#25;
end
ip_clk_wiz u_ip_clk_wiz(
.sys_clk_p (sys_clk_p),
.sys_clk_n (sys_clk_n),
.sys_rst_n (sys_rst_n),
.o_pll_clk ()
);
endmodule
探究
一开始想着,是不是跟 IP 核的高电平复位有关,因为初始的复位是有效的,对于IP核就一直处于复位状态,然后干脆就IP核的复位都直接不接了,但还是会有这么一段高电平。
然后,尝试把 IP 核的例化注释掉,结果如下:
这样似乎就合乎常理了,因为 wire 类型变量按照语法,在仿真开始时,wire 变量的值是不确定的(通常表现为高阻态或未定义值,即 x 或 z ),这里就是表现为 z 高阻态。
想到这我接着探究,既然IP核的例化都注释掉了,那再用 modelsim 仿真一下,看看,结果如下:
与 Vivado 的结果相同。
综合以上的尝试,我们可以知道,出现最开始现象的原因应该是与 IP 核有关,不过结合 testbench 文件仔细研究一下那个输出:
在 t=100ns 复位变为高电平,对于IP而言,开始有效输出,但根据波形可以看出,最终的有效时钟信号是在 200ns 以后产生的,这是因为,IP 核要获取稳定地输出需要一定的时间,与此同时也让人联想到了我前面源代码中没有接的 locked 信号,Xilinx 对它的解释如下:
即只有稳定了以后,才会把它拉高有效,我们继续仿真试试看,把这个信号加进来。
可以发现这个信号会在我们之前看到的有效时钟出来之后才会拉高,也进一步验证了前面的观点。对于我们的启示是,在实际使用 Clocking Wizard IP核时,最好是在判断 lock 信号拉高后再使用它输出的时钟,不然可能会出现一些隐蔽的隐患。
后来看正点原子的视频时发现,它们也提到,实际使用时,可以再给一个内部的复位信号采用以下逻辑给出这个复位信号,然后在这个复位信号有效的情况下再去进行一些用到PLL输出的时钟的操作。
verilog
wire locked;
wire rst_n;
assign rst_n = sys_rst_n & locked;