时序逻辑中阻塞赋值引起的仿真问题

最近写了一个分频的代码,但是在仿真中遇到了一个小问题,具体代码如下:

分频代码(该模块功能为将50MHz时钟分频为10Hz)

module div_fre(
//-------------------------------Port Declaration---------------------------------
                
        output          o_clk_100Hz,
        input           i_sys_clk_50MHz,
        input           i_sys_reset
        
    );
//-------------------------------Define Parameter---------------------------------
        parameter       div_cnt_max     =       23'd4999999;
        parameter       div_cnt_half    =       23'd2499999;
//-------------------------------Internal Registers-------------------------------
        reg     [22:0]  div_cnt         =       23'd0;
        reg             fall_en         =       1'b0;
        reg             rise_en         =       1'b0;
        reg             clk_temp        =       1'b0;
//-------------------------------Internal Wire------------------------------------

//-------------------------------Code Starts Here---------------------------------
                
always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        if(i_sys_reset)
                div_cnt <= 23'd0;
        else if(div_cnt == div_cnt_max)
                div_cnt <= 23'd0;
        else
                div_cnt <= div_cnt + 1;
end

/*
always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        if(i_sys_reset)
                fall_en <= 1'b0;
        else begin
                if(div_cnt == div_cnt_half - 1)
                        fall_en <= 1'b1;
                else
                        fall_en <= 1'b0;
        end
end
always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        if(i_sys_reset)
                rise_en <= 1'b0;
        else begin
                if(div_cnt == div_cnt_max - 1)
                        rise_en <= 1'b1;
                else
                        rise_en <= 1'b0;
        end
end
always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        if(i_sys_reset)
                clk_temp <= 1'b1;
        else begin
                if(fall_en)
                        clk_temp <= 1'b0;
                else if(rise_en)
                        clk_temp <= 1'b1;
                else
                        clk_temp <= clk_temp;
        end
end
*/  

always@ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        fall_en = (i_sys_reset) ? (1'b0) : 
                                ((div_cnt == div_cnt_half - 1) ? (1'b1) : (1'b0));
end
always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        rise_en = (i_sys_reset) ? (1'b0) : 
                                ((div_cnt == div_cnt_max - 1) ? (1'b1) : (1'b0));
end

always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        clk_temp = (i_sys_reset) ? (1'b1) : 
                                 ((fall_en) ? (1'b0) : ((rise_en) ? (1'b1) : (clk_temp)));
end 
assign o_clk_100Hz = clk_temp;
endmodule

激励文件为:

module div_fre_tb;

        // Inputs
        reg i_sys_clk_50MHz;
        reg i_sys_reset;

        // Outputs
        wire o_clk_100Hz;
        
        parameter       HALF_PERIOD = 10;

        // Instantiate the Unit Under Test (UUT)
        div_fre uut (
                .o_clk_100Hz(o_clk_100Hz), 
                .i_sys_clk_50MHz(i_sys_clk_50MHz), 
                .i_sys_reset(i_sys_reset)
        );

        initial begin
                // Initialize Inputs
                i_sys_clk_50MHz = 1;
                i_sys_reset = 1;

                // Wait 100 ns for global reset to finish
                #100;
        i_sys_reset = 0;
                // Add stimulus here

        end

        initial begin
                #HALF_PERIOD
                        forever
                                #HALF_PERIOD i_sys_clk_50MHz = ~i_sys_clk_50MHz;
        end
  
endmodule
                        

功能仿真波形为:

从仿真波形可以看出来clk_temp跳变的时刻跟我所设计的跳变时刻不太符合,本来应该是i_sys_clk_50MHz的上升沿去采fall_en的高电平来产生clk_temp = 0,可是从仿真波形中可以看到clk_temp信号是在fall_en变高的一开始就进行了变化。我通过chipscope在线调试,将程序下载到FPGA中进行在线调试却是正常的,可以看到clk_temp在fall_en=1的结束位置进行了跳变。为什么功能仿真会出现这样的问题呢?

我重新看了代码,并重新用传统的always块的方法重新写了程序(具体为程序中注释的部分),如下:

always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        if(i_sys_reset)
                fall_en <= 1'b0;
        else begin
                if(div_cnt == div_cnt_half - 1)
                        fall_en <= 1'b1;
                else
                        fall_en <= 1'b0;
        end
end
always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        if(i_sys_reset)
                rise_en <= 1'b0;
        else begin
                if(div_cnt == div_cnt_max - 1)
                        rise_en <= 1'b1;
                else
                        rise_en <= 1'b0;
        end
end
always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        if(i_sys_reset)
                clk_temp <= 1'b1;
        else begin
                if(fall_en)
                        clk_temp <= 1'b0;
                else if(rise_en)
                        clk_temp <= 1'b1;
                else
                        clk_temp <= clk_temp;
        end
end

通过仿真,波形如下:

可以看到仿真波形跟想要的是一样的。

通过代码对比,发现问题出在了

always@ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        fall_en = (i_sys_reset) ? (1'b0) : 
                                ((div_cnt == div_cnt_half - 1) ? (1'b1) : (1'b0));
end
always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        rise_en = (i_sys_reset) ? (1'b0) : 
                                ((div_cnt == div_cnt_max - 1) ? (1'b1) : (1'b0));
end

always @ (posedge i_sys_clk_50MHz or posedge i_sys_reset) begin
        clk_temp = (i_sys_reset) ? (1'b1) : 
                                 ((fall_en) ? (1'b0) : ((rise_en) ? (1'b1) : (clk_temp)));
end 

这段代码中的阻塞赋值。我开始认为这段代码中每个always块中只有一句话,可以不用考虑阻塞和非阻塞赋值。但是出了问题锁定到这个地方的时候,详细想一想,由于是采用阻塞赋值,在时钟上升沿,两个always块是并行执行的。在verilog中两个always块的执行可以按照任何顺序执行。而这种先后顺序导致的仿真结果也是不一样的。所以以后写代码需要在此加以注意。

By:冰风溪谷

原文地址:https://www.cnblogs.com/icelyb24/p/2221617.html