问题背景
今天在写uart串口的代码,仿真的时候遇到了一个问题。在uart模块中的两个信号send_en和tx_state。本来期望的是send_en出现一个时钟高电平,tx_state拉高代表了串口正在传送数据。代码如下:
//tx_state信号
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
tx_state <= 1'b0;
else if(send_en)
tx_state <= 1'b1;
else if(baud_clk_cnt == 4'd11)
tx_state <= 1'b0;
else
tx_state <= tx_state;
end
但是仿真的时候出现了一个问题:在send_en信号出现一个时钟的高电平之前,tx_state信号就已经被拉高了。
仿真如图:
从图中可以看出send_en信号还没有拉高,但是tx_state信号已经发生了变化。
原因
这是由于在仿真代码没有特别区分阻塞赋值和非阻塞赋值所引起了。
仿真代码如下:
initial clk = 1;
always #(`clock_period/2) clk = ~clk;
initial begin
rst_n = 0;
send_en = 0;
tx_data = 8'h55;
baud_set = 3'b001;
#(`clock_period*50);
rst_n = 1;
#(`clock_period*500);
send_en = 1;
#(`clock_period);
send_en = 0;
@(posedge tx_done);
#(`clock_period);
send_en = 1;
tx_data = 8'haa;
#`clock_period;
send_en = 0;
#(`clock_period*500);
@(posedge tx_done);
#(`clock_period*50000);
$stop;
end
从上面的代码可以看出所有的仿真语句用的都是非阻塞赋值,那所有的信号都是由非阻塞赋值产生的,此时clk采样如果遇到了信号的上升沿就会提取上升沿之后的信号值。而之前产生的问题就是在图中可以看clk信号在上升沿提取的信号是send_en信号上升沿之后的值也就是高电平,而此时就可以驱动tx_state信号变为高电平,这样就出现了send_en的变化没有引起tx_state信号变化的问题了。
解决方法
这里只需要将send_en信号的赋值由非阻塞赋值转为阻塞赋值就可以了。同时我们也将除了时钟信号的其他信号也转为阻塞赋值。
initial clk = 1;
always #(`clock_period/2) clk = ~clk;
initial begin
rst_n <= 0;
send_en <= 0;
tx_data <= 8'h55;
baud_set <= 3'b001;
#(`clock_period*50);
rst_n <= 1;
#(`clock_period*500);
send_en <= 1;
#(`clock_period);
send_en <= 0;
@(posedge tx_done);
#(`clock_period);
send_en <= 1;
tx_data <= 8'haa;
#`clock_period;
send_en <= 0;
#(`clock_period*500);
@(posedge tx_done);
#(`clock_period*50000);
$stop;
end
仿真结果如下图
思考
这个问题让我们注意到在仿真的时候也需要考虑到阻塞赋值和非阻塞赋值的问题,不然很可能和造成代码明明是正确的却出现和仿真不一致的问题。
这里我还特意写一个计数器来确定rst_n信号在仿真中使用阻塞赋值和非阻塞赋值的区别。
cnt_test.v
module cnt_test(
clk,
rst_n,
cnt
);
input clk;
input rst_n;
output reg [7:0]cnt;
always@(posedge clk or negedge rst_n)begin
if(~rst_n)
cnt <= 8'd0;
else if(cnt == 8'd99)
cnt <= 8'd0;
else
cnt <= cnt + 1'b1;
end
endmodule
仿真代码
cnt_test_tb.v
`timescale 1ns/1ns
`define clock_period 20
module test_tb;
reg clk;
reg rst_n;
wire [7:0]cnt;
test u_test(
.clk ( clk ),
.rst_n ( rst_n ),
.cnt ( cnt )
);
initial begin
clk = 1'b1;
forever begin
#10 clk = ~clk;
end
end
initial begin
rst_n <= 1'b0;
#(`clock_period*10);
rst_n <= 1'b1;
#(`clock_period*500);
$stop;
end
endmodule
有兴趣的朋友可以看看将仿真代码里的阻塞赋值改为非阻塞赋值,其中rst_n对计数器cnt的影响。