HDLBits——Counters

HDLBits——Counters

Problem 98 Four-bit binary counter

Requirement:

设计一个 4bit 的计数器,从 0~15,共 16 个周期,reset 是同步复位且复位为 0。

image-20211209151727407


Solution:
module top_module (
    input clk,
    input reset,      // Synchronous active-high reset
    output reg [3:0] q);

    always @(posedge clk) begin
        if(reset)begin
            q<=4'b0000;
        end
        else begin
            q<=q+1;
        end
    end

endmodule

Timing Diagram:

image-20211209152037060


Problem 99 Decade counter

Requirement:

同样是 4bit 计数器,本题只计数到 0~9,周期(period)为 10,同步复位且复位为 0。时序图如下:

image-20211209152330118


Solution:
module top_module (
    input clk,
    input reset,        // Synchronous active-high reset
    output reg [3:0] q);

    always @(posedge clk) begin
        if(reset | q==4'b1001) begin
            q<=4'b0000;
        end
        else begin
            q<=q+1'b1;
        end
    end

endmodule

与上一题的区别是:上一题计数到 15 再加 1,进位溢出,会自动变为 0000,而本题加到 9 后需要手动判断。

也可:

always @ (posedge clk)
        begin
            if(reset)
                q <= 4'b0000;
            else if(q <= 4'b1000)
                q <= q + 1'b1;
            else
                q <= 4'b0000;
        end

Timing Diagram:

image-20211209152728019


Problem 100 Decade counter again

Requirement:

本题和 Problem 99 类似,但是 1~10 的计数器,且同步复位为 1。

image-20211209161000794


Solution:
module top_module (
    input clk,
    input reset,
    output reg [3:0] q);

    always @(posedge clk) begin
        if(reset | q==4'b1010)begin
            q<=4'b0001;
        end
        else begin
            q<=q+1'b1;
        end
    end

endmodule

错过:q==4’b1010 时赋值 1,因为下一个时钟到来时才会赋值,也就相当于下一周期 q 的值。


Timing Diagram:

image-20211209161603424


Problem 101 Slow decade counter

Requirement:

设计一个 0~9 的计数器,共 10 个周期。该计数器采用同步复位且复位为 0。但是本题是希望该计数器并不是随着 clk 的变化而递增,而是随着一个 slowena 使能信号来控制增加。时序图如下图所示:

image-20211209161715286


Solution:
module top_module (
    input clk,
    input slowena,
    input reset,
    output reg [3:0] q);

    always @(posedge clk) begin
        if(reset) begin
            q<=4'b0;
        end
        else if (slowena) begin
            if(q==4'b1001) q<=4'b0000;
            else q<=q+4'b0001;
        end
        else begin
            q<=q;
        end
    end

endmodule

错过:刚开始判断的是只要这个周期内 q=9,下个周期就变回 0,但这是不一定的,只有本周期 slowena=1 ,下个周期才会加一,否则维持上周期值不变。


Timing Diagram:

image-20211209163204326


Problem 102 Counter 1-12

Requirement:

根据以下输入输出信号设计一个计算 1~12 的计数器:

Reset:同步复位信号,高复位,将计数器复位为 1

Enable:使能信号高有效

Clk:时钟上升沿触发计数器工作

Q[3:0]:计数器输出

c_enable, c_load(同步置位), c_d[3:0]:题目提供了一个 4-bit 的计数器,这三个信号是用于该 4-bit 计数器的控制信号。

题目提供的 4-bit 计数器:

  1. 有 enable 信号,带复位和置位的计数器,将该计数器例化至我们的代码中。(load 优先级高于 enable)
  2. 再用一些其他的逻辑门来完成本题。
module count4(
    input clk,
    input enable,
    input load,
    input [3:0] d,
    output reg [3:0] Q
);

image-20211209194857570


Solution:
module top_module (
    input clk,
    input reset,
    input enable,
    output reg [3:0] Q,
    output c_enable,
    output c_load,
    output [3:0] c_d
); //

    count4 the_counter (clk, c_enable, c_load, c_d, Q);
    assign c_enable = enable;
    assign c_load = reset | (Q==4'd12 & enable == 1'b1);
    assign c_d = 4'd1;

endmodule

注意:已经给了子模块就不用自己实现时序电路了,只需要实现组合电路就行。

在实现 c_load 语句时第一想到的是判断置位的情况,用条件语句(if)来写,实际上可以直接把结果表达成条件的逻辑表达式。

& 按位与,&& 逻辑与。

load 的作用:load 有效时把 d 数值加载到 Q 端口。

注意:reset 是同步复位,用 assign 会使得 c_load 立即变为 1,但 c_load 是同步置位,等到下一个时钟上升沿才会赋值,效果相当于同步复位。


Timing Diagram:

image-20211209200830840


Problem 103 Counter 1000

Requirement:

从 1000Hz 中分离出 1Hz 的信号,叫做 OneHertz。利用一个模 10 的 BCD 计数器和尽量少的逻辑门来建立一个时钟分频器。同时输出每个 BCD 计算器的使能信号(c_enable[0] 为高位,c_enable[2] 为低位)。

题目已经给我们提供了 BCD 计数器。Enable 信号高有效,Reset 信号高有效且复位为 0,我们设计的电路中均要采用 1000Hz 的时钟。

module bcdcount (
    input clk,
    input reset,
    input enable,
    output reg [3:0] Q
);

Solution:
module top_module (
    input clk,
    input reset,
    output OneHertz,
    output [2:0] c_enable
); //

    reg [3:0] Q1,Q2,Q3;
    bcdcount counter0 (clk, reset, c_enable[0], Q1);
    bcdcount counter1 (clk, reset, c_enable[1], Q2);
    bcdcount counter2 (clk, reset, c_enable[2], Q3);
    assign c_enable[0] = 1'b1;
    assign c_enable[1] = Q1==4'b1001;
    assign c_enable[2] = (Q1==4'b1001) && (Q2==4'b1001) ;
    assign OneHertz = (Q1==4'b1001) && (Q2==4'b1001) && (Q3==4'b1001);

endmodule

错过:assign c_enable[2] = (Q1==4’b1001) && (Q2==4’b1001) ; 一开始只写了 assign c_enable[2] = (Q1==4’b1001) ,因为只想着十位满九进一,百位加一,没想到 99 的下一个才是 100,90 虽然满九但还是要下一个十位进位时,百位才会加一。

一直纠结怎么让每个小计数器每十个数循环计数,因为只有 reset 还是和大的复位信号相连,也没有置位信号,后来突然意识到 BCD计数器本身就是模十计数器呀!根本不用手动跳转状态。


Timing Diagram:

image-20211209211343060

image-20211209211357880

image-20211209211828036


Problem 104 4-digit decimal counter

Requirement:

设计一个 4 位 BCD(二进制编码十进制)计数器。每个十进制数字使用 4-bit 来表示:q[3:0] 是个位,q[7:4] 是十位等。对于 ena[3:1],该信号用来表示个位、十位和百位的进位。时序图如下图所示:

image-20211209212100626


Solution:
module top_module (
    input clk,
    input reset,   // Synchronous active-high reset
    output [3:1] ena,
    output reg [15:0] q);

    wire reset1,reset2,reset3,reset4;
    BCDcounter c1(clk,reset1,1'b1,q[3:0]);
    BCDcounter c2(clk,reset2,ena[1],q[7:4]);
    BCDcounter c3(clk,reset3,ena[2],q[11:8]);
    BCDcounter c4(clk,reset4,ena[3],q[15:12]);

    assign reset1 = reset|ena[1];
    assign reset2 = reset|ena[2];
    assign reset3 = reset|ena[3];
    assign reset4 = reset|(ena[3]&&q[15:12] == 4'd9);
    
    assign ena[1] = (q[3:0] == 4'd9);
    assign ena[2] = (q[3:0] == 4'd9) && (q[7:4] == 4'd9);
    assign ena[3] = (q[3:0] == 4'd9) && (q[7:4] == 4'd9) && (q[11:8] == 4'd9);

endmodule

module BCDcounter (
    input clk,
    input reset,
    input ena,
    output reg [3:0] q
);
    always @(posedge clk) begin
        if(reset)begin
            q<=4'd0;
        end
        else begin
            if(ena) q<=q+1'b1;
            else q<=q;
        end
    end
endmodule

思路:如果直接写的话,进位上来是加一还是继续进位,条件判断非常麻烦,所以写个小的 BCD 计数器子模块会更方便。

注意:[3:1] ena 只有三位,最低位不需要使能变量来控制,一直为 1 就对了。


错过:清零的条件没写满九,导致进位后,低位还在 9 的基础上加 1 变成 a,b,c,d……

改正:在子模块里改

always @(posedge clk) begin
        if(reset|q==4'd9)begin
            q<=4'd0;
        end

但这样又会出现新的错误,十位满九进一,而后复位,但实际中只有十位和个位都满九才能进一,因此正确的改正方法是设置三个不同的复位变量,当且仅当它及其低位全都为 9 时才能进位和复位。

assign reset1 = reset;
assign reset2 = reset|ena[1];
assign reset3 = reset|ena[2];
assign reset4 = reset|ena[3];

改完还是错,检查发现对应错了,q[3:0] 满 9 后清零的是 q[3:0],也就是 reset1 = 1。

assign reset1 = reset|ena[1];
assign reset2 = reset|ena[2];
assign reset3 = reset|ena[3];
assign reset4 = reset|(ena[3]&&q[15:12] == 4'd9);

终于成了呜呜。


也有简单的方法,不用设置复位变量,那就是异名例化:

//thousand
    BCDcounter Inst4_count
    (
        .clk(clk),
        .reset(q[15:12] == 4'd9 && q[11:8] == 4'd9 && q[7:4] == 4'd9 && q[3:0] == 4'd9),
        .ena(q[11:8] == 4'd9 && q[7:4] == 4'd9 && q[3:0] == 4'd9),
        .q(q[15:12])
    );


也可:把复位和进位后归 0 两种情况分开判断,设置端口 eni,eno 分别代表进位输入和进位输出。

module top_module (
input clk,
input reset, // Synchronous active-high reset
output [3:1] ena,
output [15:0] q);

wire eno;
bcd_cnt cnt0(clk, reset, 1'b1, ena[1], q[3:0]);
bcd_cnt cnt1(clk, reset, ena[1], ena[2], q[7:4]);
bcd_cnt cnt2(clk, reset, ena[2], ena[3], q[11:8]);
bcd_cnt cnt3(clk, reset, ena[3], eno, q[15:12]);

endmodule

module bcd_cnt(input clk, reset, eni, output eno, output [3:0]q);

assign eno = (q == 4'h9) & eni;
always @(posedge clk) begin
if(reset) q <= 4'h0;
else if(eni) begin
q <= (q == 4'h9) ? 4'h0 : q+4'h1;
end
end
endmodule

Timing Diagram:(图之大,一行放不下)

image-20211209215747897

image-20211209220122905image-20211209220144657


Problem 105 12-hour clock

Requirement:

用计数器设计一个带 am/pm 的 12 小时时钟。该计数器通过一个 CLK 进行计时,用 ena 使能信号来驱动时钟的递增。reset 信号将时钟复位为 12:00 AM。 信号 pm 为 0 代表 AM,为 1 代表 PM。hh、mm 和 ss 由两个 BCD 计数器构成, hours(01~12), minutes(00~59) , second(00~59)。Reset 信号比 enable 信号有更高的优先级,即使没有 enable 信号也可以进行复位操作。

下图所示的时序图给出了从 11:59:59 AM 到 12:00:00 PM 的变化。

image-20211209220741073

需要注意的是从 11:59:59 PM 到 12:00:00 AM 和从 12:59:59 PM 到 01:00:00 PM 的变化。


Solution:

方法一:

module top_module(
    input clk,
    input reset,
    input ena,
    output reg pm,
    output reg [7:0] hh,
    output reg [7:0] mm,
    output reg [7:0] ss); 

    always @(posedge clk) begin
        if(reset) begin
            pm<=1'b0;
            hh<=8'b00010010;
            mm<=8'b00000000;
            ss<=8'b00000000;
        end
        else if(ena) begin
            if(ss<8'b01011001) begin
                if(ss[3:0]<4'b1001) ss[3:0]<=ss[3:0]+1'b1;
                else begin
                    ss[3:0]<=4'b0;
                    ss[7:4]<=ss[7:4]+1;
                end
            end

            else begin
                ss<=8'b0;
                if(mm<8'b01011001) begin
                    if(mm[3:0]<4'b1001) mm[3:0]<=mm[3:0]+1'b1;
                    else begin
                        ss[3:0]<=4'b0;
                        ss[7:4]<=4'b0;
                        mm[3:0]<=4'b0;
                        mm[7:4]<=mm[7:4]+1;
                    end
                end

                else begin
                    mm<=8'b0;
                    if(hh<8'b00010010) begin
                        if(hh==8'b00010001) begin
                            pm<=~pm;
                            hh<=8'b00010010;
                            mm<=8'b00000000;
                            ss<=8'b00000000;
                        end
                        else if(hh[3:0]<4'b1001) hh[3:0]<=hh[3:0]+1'b1;
                        else begin
                            ss[3:0]<=4'b0;
                            ss[7:4]<=4'b0;
                            mm[3:0]<=4'b0;
                            mm[7:4]<=4'b0;
                            hh[3:0]<=4'b0;
                            hh[7:4]<=hh[7:4]+1;
                        end
                    end
                    else begin
                        hh<=8'b00000001;
                        mm<=8'b0;
                        ss<=8'b0;
                    end
                end
            end
        end
        else begin
            hh<=hh;
            mm<=mm;
            ss<=ss;
        end
    end

endmodule

错过:

  1. 低位直接加一的条件本来是:mm[3:0]<9,我错误地写作了:mm[3:0]<=9,导致低位出现了 a。类似的,本来满 59 进一,应该是<59 直接加,我错误地写成了 <=59。(范围 0-59)

  2. 忘了用 ena 了。。

  3. mm 给 hh 进位的时候少写了 mm<=8’b0;但官方的仿真波形提示是 PM roll-over 有错,找了一下午 pm 变号附近的问题,最后写了 testbench 在 modelsim 里仿真,在好心人的帮助下才发现问题出在什么地方,如下图:mm 的 59 持续的时间太长了,合理怀疑是忘了溢出后复位。

    image-20211210215616973

  4. 仿真时直接选“simulation”,不要选什么优化或不优化啥的,会产生各种玄学错误,就算忽略错误,连信号都没了画什么的波形啊呜呜。

  5. 经验教训:

    1. 做题网站的 mistake 提示不可信;
    2. 不要用一堆 if……else,你不出错谁出错;
    3. 不要用软件思维写硬件代码,总是想着什么什么条件下是什么结果,而是要想这个结果在什么情况下出现,学会用一些逻辑门把子模块连接起来。

方法二:(错误方法,但思路宝贵就留下来了,引以为鉴)

module top_module(
    input clk,
    input reset,
    input ena,
    output reg pm,
    output reg [7:0] hh,
    output reg [7:0] mm,
    output reg [7:0] ss); 

    wire slh,sm,mlh,mh,hlh,hhc;
    assign slh = (ss[3:0]==4'd9);
    assign sm = (ss[3:0]==4'd9 && ss[7:4]==4'd5);
    assign mlh = (ss[3:0]==4'd9 && ss[7:4]==4'd5 && mm[3:0]==4'd9);
    assign mh = (ss[3:0]==4'd9 && ss[7:4]==4'd5 && mm[3:0]==4'd9 && mm[7:4]==4'd5);
    assign hlh = (ss[3:0]==4'd9 && ss[7:4]==4'd5 && mm[3:0]==4'd9 && mm[7:4]==4'd5 && hh[3:0]==4'd9);
    assign hhc = (ss[3:0]==4'd9 && ss[7:4]==4'd5 && mm[3:0]==4'd9 && mm[7:4]==4'd5 && hh[3:0]==4'd2 && hh[7:4]==4'd1);

    always @(posedge clk) begin
        if(reset) begin
            pm<=1'b0;
            hh<=8'b00010010;
            mm<=8'b00000000;
            ss<=8'b00000000;
        end
        else if (hhc) begin
            pm <= ~pm;
        end
    end

    
    BCDcounter ssl (
    .clk(clk),
    .enable(ena|~reset),
    .load(slh|~reset),
    .D(4'd0),
    .q(ss[3:0]));
    BCDcounter ssh (
    .clk(clk),
    .enable(slh|~reset),
    .load(sm|~reset),
    .D(4'd0),
    .q(ss[7:4]));

    BCDcounter mml (
    .clk(clk),
    .enable(sm|~reset),
    .load(mlh|~reset),
    .D(4'd0),
    .q(mm[3:0]));
    BCDcounter mmh (
    .clk(clk),
    .enable(mlh|~reset),
    .load(mh|~reset),
    .D(4'd0),
    .q(mm[7:4]));

    BCDcounter hhl (
    .clk(clk),
    .enable(mh|~reset),
    .load(mlh|~reset),
    .D(4'd1),
    .q(hh[3:0]));
    BCDcounter hhh (
    .clk(clk),
    .enable(hlh|~reset),
    .load(hhc|~reset),
    .D(4'd0),
    .q(hh[7:4]));

endmodule

module BCDcounter (
    input clk,
    input enable,
    input load,
    input D,
    output reg[3:0] q);

    always @(posedge clk) begin
        if (load) q<=D;
        else if(enable) begin
            if(q<=4'd9) q<=q+1;
            else q<=4'd0;
        end
    end

endmodule

错过:

  1. 虽然 hh 范围是 1-12,但复位后 hh 是 12 不是 1。

  2. 一开始 load 实现无论是复位还是进位后都置位为某个初始值,但问题是 hh 置位和进位的结果不一样,复位是变成 12:00:00,进位是 12:59:59 的下一步要变成 01:00:00,所以 D 无法确定为一个定值,这就需要把 reset 单独拿出来判断。

  3. 因为 enable 和 load 也是在给 hh,mm,ss 赋值,reset 也是给其赋值,会产生冲突。若在条件中要加以说明,比如:

    .enable(mh|~reset),
    .load(mlh|~reset),
    

    改完还是冲突??

    因为子模块中还有除了 enable、load 信号还有效的 else 对应的操作,但这是无解的,因为人家也要控制满 9 进 1,不能删去。

    综上,这种方法不能实现。。。


方法三:来自学姐的方法

module BCD_12(input clk,input reset,input ena,output [7:0] d);
    always @(posedge clk) begin
        if(reset) begin d=8'h12; end
        else begin
            if(ena) begin
                if(d==8'h12) begin d=8'h01; end
                else if(d[3:0]==9) begin d=8'h10;end
                else if(d[3:0]<9) begin
                    d[3:0]=d[3:0]+1;
                end               
            end
            else d=d;
        end
    end
endmodule

module BCD_60(input clk,input reset,input ena,output [7:0] d);
    always @(posedge clk) begin
        if(reset) begin d=8'h00; end
        else begin
            if(ena) begin
                if(d==8'h59) begin d=8'h00; end
                else if(d[3:0]==4'h9) begin 
                    d[7:4]=d[7:4]+1;
                    d[3:0]=4'h0;
                end
                else if(d[3:0]<4'h9) begin
                    d[3:0]=d[3:0]+1;
                end 
            end
            else d=d;
        end
    end
endmodule

module top_module(
    input clk,
    input reset,
    input ena,
    output pm,
    output [7:0] hh,
    output [7:0] mm,
    output [7:0] ss);

    wire ena_m,ena_h;
    assign ena_m=ena&ss==8'h59;
    assign ena_h=ena&ss==8'h59&mm==8'h59;
    
    BCD_12 my_hh(clk,reset,ena_h,hh);
    BCD_60 my_mm(clk,reset,ena_m,mm);
    BCD_60 my_ss(clk,reset,ena,ss);
    
    always @(posedge clk) begin
        if(reset) begin pm=0; end
        else begin
            if(ena&hh==8'h11&mm==8'h59&ss==8'h59)begin
                pm=~pm;
            end
            else begin pm=pm; end
        end
    end
endmodule

PS:相比我没有实现的方法二,方法三把 60 进位的和 12 进位的分开为两个子模块,此外把 pm 单独提出来实现,其实 pm 就是 11:59:59 的下一个周期 pm 取反,其他加一的规则等完全没有变化,因此把他从计数器里拿出来是明智的。

方法四:大佬方法(目前见到最简单的)

module top_module (
    input clk,
    input reset,
    input ena,
    output pm,
    output [7:0] hh,
    output [7:0] mm,
    output [7:0] ss
);

wire [4:1] enable;
assign enable[1] = ena && ss[3:0] == 9;
assign enable[2] = enable[1] && ss[7:4] == 5;
assign enable[3] = enable[2] && mm[3:0] == 9;
assign enable[4] = enable[3] && mm[7:4] == 5;
    
cnt #(.START(0), .END(9)) ss9 (clk, reset, ena, ss[3:0]);
cnt #(.START(0), .END(5)) ss5 (clk, reset, enable[1], ss[7:4]);
cnt #(.START(0), .END(9)) mm9 (clk, reset, enable[2], mm[3:0]);
cnt #(.START(0), .END(5)) mm5 (clk, reset, enable[3], mm[7:4]);
cnt_hour hh12 (clk, reset, enable[4], hh[7:0], pm);

endmodule

//BCD counter
module cnt (
    input clk,
    input reset,
    input ena,
    output reg [3:0] q
);
parameter START = 0, END = 9;
always @(posedge clk)
    if (reset) q <= START;
    else if (~ena) q <= q;
    else q <= q < END ? q + 1 : START;
endmodule

module cnt_hour (
    input clk,
    input reset,
    input ena,
    output reg [7:0] q,
    output reg pm
);
always @(posedge clk)
    if (reset) q <= 8'h12;
    else if (~ena) q <= q;
    else case (q)
        8'h12: q <= 8'h01;
        8'h09: q <= 8'h10;
        default: q[3:0] <= q[3:0] + 1;
    endcase
always @(posedge clk)
    if (reset) pm <= 0;
    else if (ena && q == 8'h11) pm <= ~pm;
    else pm <= pm;
endmodule

该方法的重点在于:单独判断 1-12 的计数器,其他都可以视作 START-END 的计数器。而且使能信号采用了数组和迭代的方法,减少判断语句。是呀,高位进位发生是基于低位进位的(次低位使能其实代表着更低位的进位),所以低位使能信号是高位使能信号的条件之一。

最后注意:BCD 码常用 16 进制,正好每 4bit 可以表示一个十进制的数,比如 8’h45,就是 BCD 的 45。


Timing Diagram:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QSBAQi6X-1639922049110)(C:\Users\16041\AppData\Roaming\Typora\typora-user-images\image-20211210215029448.png)]

image-20211210215047169

image-20211210215105425

image-20211210215124368



PS:102 题图示来源见水印。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值