Verilog预设倒计时计时器初始启动值错误,后续运行正常问题求助
倒计时计时器首次启动异常问题排查与修复
问题现象
基于Verilog开发预设计数器倒计时计时器时,出现首次启动计数异常:
- 设置1分钟,首次启动从1分59秒开始倒计时
- 设置10分钟,首次启动从19分59秒开始倒计时
- 设置1小时,首次启动从1小时59分开始倒计时
后续循环运行完全正常,初步怀疑问题出在预设值加载或初始化逻辑。
相关代码
计时器主模块
//Timer Module module timer(KEY,SW,oCNT0,oCNT1,oCNT2,oCNT3,oCNT4,oCNT5,CLOCK_50,Done); input [1:0] KEY; input [9:0] SW; input CLOCK_50; output [3:0] oCNT0,oCNT1,oCNT2,oCNT3,oCNT4,oCNT5; output wire Done; wire EN0,EN1,EN2,EN3,EN4,EN5; wire [3:0] oCNT0, oCNT1,oCNT2,oCNT3,oCNT4,oCNT5; wire [18:0] oCNT; assign Done = ({oCNT5,oCNT4,oCNT3,oCNT2,oCNT1,oCNT0}==(24'b000000000000000000000000)) ? 1'b0 : 1'b1; assign iRSTn=(SW[7]&~SW[6]&~KEY[0]) ? 1'b0 : 1'b1; reg start; reg [26:0] press_counter; reg long_press; reg button_pressed; //This part of the code implements logic to perform two different functions using a single key. always @(posedge CLOCK_50 or negedge iRSTn) begin if (!iRSTn) begin press_counter <= 0; button_pressed <= 0; long_press <= 0; start <= 0; end else begin if (KEY[1]) begin press_counter <= press_counter + 1; button_pressed <= 1; // 2sec over if (press_counter >27'd100000000) begin long_press <= 1; end end else if (button_pressed) begin if (long_press) begin start <= start; // Preset end else begin start <= ~start; //Toggle end // reset press_counter <= 0; button_pressed <= 0; long_press <= 0; end end end COUNTER_LAB #(.n(23),.k(5000000)) U_Counter_ms_gen( .iRSTn(iRSTn), .iCLK(CLOCK_50), .iEN(start&Done), .oEN(EN0), .oCNT(oCNT) ); // 5M Count COUNTER_PRESET_DOWN #(.n(4),.k(10)) COUNTER_timer_ms(.iCLK(CLOCK_50), .iRSTn(iRSTn), .iPRSTn(1'b0), .iEN(EN0), .iDATA_Preset(4'b0000), .oEN(EN1), .oCNT(oCNT0)); // ms COUNTER_PRESET_DOWN #(.n(4),.k(10)) COUNTER_timer_s0(.iCLK(CLOCK_50), .iRSTn(iRSTn), .iPRSTn(1'b0), .iEN(EN1), .iDATA_Preset(4'b0000), .oEN(EN2), .oCNT(oCNT1)); // seconds (units) COUNTER_PRESET_DOWN #(.n(3),.k(6)) COUNTER_timer_s1(.iCLK(CLOCK_50), .iRSTn(iRSTn), .iPRSTn((~KEY[1] & ~long_press) & (SW[5:4] == 2'b00) & (SW[7:6] == 2'b10)), .iEN(EN2), .iDATA_Preset(SW[3:0]), .oEN(EN3), .oCNT(oCNT2)); // seconds (tens) COUNTER_PRESET_DOWN #(.n(4),.k(10)) COUNTER_timer_m0(.iCLK(CLOCK_50), .iRSTn(iRSTn), .iPRSTn((~KEY[1] & ~long_press) & (SW[5:4] == 2'b01) & (SW[7:6] == 2'b10)), .iEN(EN3), .iDATA_Preset(SW[3:0]), .oEN(EN4), .oCNT(oCNT3)); // minutes (units) COUNTER_PRESET_DOWN #(.n(3),.k(6)) COUNTER_timer_m1(.iCLK(CLOCK_50), .iRSTn(iRSTn), .iPRSTn((~KEY[1] & ~long_press) & (SW[5:4] == 2'b10) & (SW[7:6] == 2'b10)), .iEN(EN4), .iDATA_Preset(SW[2:0]), .oEN(EN5), .oCNT(oCNT4)); // minutes (tens) COUNTER_PRESET_DOWN #(.n(4),.k(13)) COUNTER_timer_h(.iCLK(CLOCK_50), .iRSTn(iRSTn), .iPRSTn((~KEY[1] & ~long_press) & (SW[5:4] == 2'b11) & (SW[7:6] == 2'b10)), .iEN(EN5), .iDATA_Preset(SW[3:0]), .oEN(), .oCNT(oCNT5)); // hours endmodule
预设计数器模块
//Timer Counter module COUNTER_PRESET_DOWN( iCLK, iRSTn, iPRSTn, iEN, iDATA_Preset, oEN, //roll-over signal oCNT ); parameter n=4; parameter k=10; input iCLK,iRSTn,iPRSTn,iEN; input [n-1:0] iDATA_Preset; output [n-1:0] oCNT; output oEN; reg [n-1:0] oCNT; assign oEN =((oCNT == 0) && (iEN == 1'b1)) ? 1'b1 : 1'b0; always@(posedge iCLK or negedge iRSTn) begin if(~iRSTn) begin oCNT <= 0; end else if(iPRSTn) begin oCNT <= iDATA_Preset; end else if(iEN) begin if(oCNT==0) begin oCNT<=k-1; end else begin oCNT<=oCNT-1; end end else begin oCNT<=oCNT; end end endmodule
问题根因
- 计数器初始化与计数逻辑冲突:
COUNTER_PRESET_DOWN复位后初始值为0,当iEN有效时,若oCNT==0会直接加载k-1并触发进位信号oEN。首次启动时,毫秒、秒等低位计数器初始为0,启动后立刻触发进位,导致高位计数器被额外减1次(表现为高位多了一个单位的计数,比如1分钟变成1分59秒=79秒,相当于2分钟减1秒)。 Done信号逻辑导致启动条件异常:Done在计数全0时输出0,否则输出1,启动条件为start&Done。首次启动时,低位计数器初始为0,但整体计数不全0(高位有预设值),Done为1,触发计数后低位立刻触发进位。
修复方案
1. 修正COUNTER_PRESET_DOWN计数逻辑
将计数器的进位触发时机调整为减到0之后,避免初始0直接触发进位:
// 修改后的COUNTER_PRESET_DOWN模块 module COUNTER_PRESET_DOWN( iCLK, iRSTn, iPRSTn, iEN, iDATA_Preset, oEN, //roll-over signal oCNT ); parameter n=4; parameter k=10; input iCLK,iRSTn,iPRSTn,iEN; input [n-1:0] iDATA_Preset; output reg [n-1:0] oCNT; output reg oEN; always@(posedge iCLK or negedge iRSTn) begin if(~iRSTn) begin oCNT <= 0; oEN <= 1'b0; end else if(iPRSTn) begin oCNT <= iDATA_Preset; oEN <= 1'b0; end else if(iEN) begin if(oCNT == 0) begin // 只有当计数器已经到0,且允许计数时,才加载k-1并触发进位 oCNT <= k-1; oEN <= 1'b1; end else begin oCNT <= oCNT - 1'b1; oEN <= 1'b0; end end else begin oCNT <= oCNT; oEN <= 1'b0; end end endmodule
核心调整:将oEN改为时序逻辑输出,避免组合逻辑毛刺;只有当计数器已经减到0且iEN有效时,才加载k-1并触发进位,而非初始0就触发。
2. 优化启动前的预设逻辑
确保在首次启动前,所有计数器都加载正确的初始值,比如给毫秒、秒个位/十位模块添加预设触发条件,在启动前将其预设为0:
// 修改毫秒计数器的iPRSTn,在启动前触发预设 COUNTER_PRESET_DOWN #(.n(4),.k(10)) COUNTER_timer_ms(.iCLK(CLOCK_50), .iRSTn(iRSTn), .iPRSTn((~KEY[1] & ~long_press) & (SW[7:6] == 2'b10)), // 新增预设触发 .iEN(EN0), .iDATA_Preset(4'b0000), .oEN(EN1), .oCNT(oCNT0)); // ms // 同理修改秒个位计数器 COUNTER_PRESET_DOWN #(.n(4),.k(10)) COUNTER_timer_s0(.iCLK(CLOCK_50), .iRSTn(iRSTn), .iPRSTn((~KEY[1] & ~long_press) & (SW[7:6] == 2'b10)), // 新增预设触发 .iEN(EN1), .iDATA_Preset(4'b0000), .oEN(EN2), .oCNT(oCNT1)); // seconds (units)
这样在设置时间后短按按键时,低位计数器也会被预设为0,避免初始0触发进位。
3. 验证Done信号逻辑
确认Done信号逻辑正确,确保只有当计数未到0时才允许计数:
// 保持原逻辑即可,注释说明更清晰 assign Done = ({oCNT5,oCNT4,oCNT3,oCNT2,oCNT1,oCNT0} != 24'd0) ? 1'b1 : 1'b0;
验证方法
- 复位后设置1分钟,短按启动,观察是否从1分00秒开始倒计时
- 设置10分钟,启动后检查是否从10分00秒开始
- 多次启停,确认首次启动异常不再出现
内容的提问来源于stack exchange,提问作者LDY
相关产品推荐
相关产品推荐

