FPGA 上的 2 位 BCD 计数器

2 digit BCD counter on FPGA

我正在尝试制作一个从 0 到 99 计数的 2 位 BCD 计数器。我面临的问题是板上的两个 7 段显示同时改变数字。场景是这样的 - 00, 11, 22...99.

主要逻辑代码如下:

module PartD(
output reg[0:6] lcd, //for one particular 7 segment LCD.
input clock,
output reg[0:7] sel // for selecting which LCD to be used
);
integer count=0;
//integer i=0, j=0;

reg[3:0] i, j;  //4 bit reg for counting

always@(posedge clock)
begin
    if(count==100000000)  //(100MHz) count till 100M cycles...(1 sec delay)
    begin
        count = 0;

        sel=00000001;  //selecting the most significant digit
        case (i)
              0: lcd = 7'b0000001;
              1: lcd = 7'b1001111;
              2: lcd = 7'b0010010;
              3: lcd = 7'b0000110;
              4: lcd = 7'b1001100;
              5: lcd = 7'b0100100;
              6: lcd = 7'b0100000;
              7: lcd = 7'b0001111;
              8: lcd = 7'b0000000;
              9: lcd = 7'b0000100;
        endcase


        sel=00000010; //selecting the least significant digit
        case (j)
              0: lcd = 7'b0000001;
              1: lcd = 7'b1001111;
              2: lcd = 7'b0010010;
              3: lcd = 7'b0000110;
              4: lcd = 7'b1001100;
              5: lcd = 7'b0100100;
              6: lcd = 7'b0100000;
              7: lcd = 7'b0001111;
              8: lcd = 7'b0000000;
              9: lcd = 7'b0000100;
        endcase
        j = j+1;
        if(j>9)
        begin
            j=0;
            i = i+1;    //increment i only when j overflows.
            if(i>9)
            i = 0;
        end  
    end
    else count = count + 1;
end
endmodule

由于两个显示同时发生变化,因此似乎存在一些逻辑错误。会是什么呢。我是不是犯了没有考虑硬件的错误?

您的代码存在的主要问题是,当您的计数器为 100000000 时,您正试图在同一周期内激活两个 LCD 段。从逻辑上讲,您使用的是阻塞赋值“=”,而第二个赋值有效地覆盖了第一个一.

您需要决定何时打开每个分段。

根据您的要求,我会找到一种方法来驱动每个细分市场 50% 的时间。鉴于10^8的最大2^n约数是256,我选择用count[7]来决定段1和段2,这样每个段刷新128个周期后才移动到另一个。

此外,您有语法错误,忘记在 sel 赋值上使用二进制前缀(0000010 而不是 'b000010)。

这是让它正常工作的最低要求。

在实践中,我会进一步改变它并将其分成几个 'always' 块:
一个用于柜台
一个用于展示
一个用于递增 i 和 j。

我也选择了将计数器从整数类型替换为寄存器类型。为 non-hardware 构造保留整数(用于循环计数器等)

向 case 语句添加了默认值,将 x 驱动为 'lcd'。这有助于针对意外的 i、j 值 (10..15) 优化合成以及传播用于模拟的 x。

我还添加了一个复位输入并将时钟和复位信号移动到模块接口上的第一个信号。如果没有重置,您的 count,i,j 将从随机值开始。在您的情况下,它们最终会合法化,但这会使模拟变得更加困难,因为它们以 'x' 值开头。

另外,请记住阻塞 ('=') 和 non-blocking ('<=') 赋值之间的区别。尝试避免像您一样对 clocked always block 使用阻塞分配。

module PartD(
    input clock,
    input reset,
    output reg[0:6] lcd, //for one particular 7 segment LCD.
    output reg[0:7] sel // for selecting which LCD to be used
);
reg [26:0]count;
reg[3:0] i, j;  //4 bit reg for counting

always@(posedge clock)
begin
    count <= reset ? 'd0 : (count == 100000000) ? 'd0 : (count + 1);
end

always@(posedge clock)
begin
    j <= reset ? 'b0 : (j < 9) ? j+1 : 'b0;
    i <= reset ? 'b0 : (j < 9) ? i : (i < 9) ? (i + 1) : 'b0;
end

always@(posedge clock)
begin
    if (count & 128)
    begin
        sel <= 'b00000001;  //selecting the most significant digit
        case (i)
              0: lcd <= 7'b0000001;
              1: lcd <= 7'b1001111;
              2: lcd <= 7'b0010010;
              3: lcd <= 7'b0000110;
              4: lcd <= 7'b1001100;
              5: lcd <= 7'b0100100;
              6: lcd <= 7'b0100000;
              7: lcd <= 7'b0001111;
              8: lcd <= 7'b0000000;
              9: lcd <= 7'b0000100;
              default:
                 lcd <= 'bx;
        endcase
    end
    else
    begin
        sel <= 'b00000010; //selecting the least significant digit
        case (j)
              0: lcd <= 7'b0000001;
              1: lcd <= 7'b1001111;
              2: lcd <= 7'b0010010;
              3: lcd <= 7'b0000110;
              4: lcd <= 7'b1001100;
              5: lcd <= 7'b0100100;
              6: lcd <= 7'b0100000;
              7: lcd <= 7'b0001111;
              8: lcd <= 7'b0000000;
              9: lcd <= 7'b0000100;
              default:
                 lcd <= 'bx;
        endcase
    end
end

endmodule

The problem I'm facing is that both the 7-segment dispays on the board are changing digits at the same time

  1. 该代码缺少控制 8 位显示器上两个数字之间切换机制的时序约束。您将不得不考虑刷新率;这就是使您能够不断更新显示的原因。按照惯例,刷新率可以介于 1 kHz 和 60 Hz 之间。如果我们选择的频率值太低,显示屏会开始闪烁,这意味着 切换不够快

假设您选择刷新周期为 1 毫秒。这意味着每个数字在 1/8 周期内保持有效(对于 8 位显示)。换言之,数字周期为 1/8 毫秒。这个循环不断发生,从而呈现出独立且持续发光的数字,这是生产 2 位 BCD 计数器的目标之一。

我们可以将现有的 FPGA 时钟转换成一个计数器,该计数器递增到与 1 到 16 毫秒之间的刷新周期相对应的值。如果我们将内部时钟频率除以刷新频率,这将为我们提供整个刷新周期的计数器值。

  • 例如,使用 100MHz 时钟的 1 ms 刷​​新周期将生成 10 的计数器值 * 10^4。因此,为了让寄存器保存计数器值,它必须至少有 17 位宽。

接下来,我们必须决定如何在使用数字周期从 0 数到 99 的同时触发正确的数字。在下面的代码中,我们利用寄存器“refresh_counter”的宽度作为第一个 case 结构

  • 我们只有两位数可以控制,所以刷新周期被除以二。假设有 1 毫秒的刷新周期,那么我们就有 0.5 毫秒的数字周期。基于此,我们可以找到寄存器“refresh_counter”中的哪个位位置将在 0.5 ms 后切换。

BCD 计数器代码

module PartD(
             input MHZ,
             output reg DP = 1'b1,
             output reg [6:0] A_TO_G,
             output reg [7:0] AN
             );

localparam divisor = 50000000;
reg [25:0] counter = 0;
reg clock = 0;

always@(posedge MHZ) begin
if (counter < divisor) begin
    counter <= counter + 1;
    clock <= clock; 
end

else begin
    counter <= 0;
    clock <= ~clock; end
 end

reg [16:0] refresh_counter = 0; //17 bits wide
always@(posedge MHZ) begin
    refresh_counter <= refresh_counter + 1;
end

reg [3:0] num_one, num_two;
always@(posedge clock) begin

        if(num_one == 4'd9) begin
               num_one <= 0;
        
               if (num_two == 4'd9)
                      num_two <= 0;
             
               else
                      num_two <= num_two + 1; 
         end
                    
         else 
            num_one <= num_one + 1; 
 end                          

 reg [3:0] NUMBER;
 always@(refresh_counter or num_one or num_two) begin

 // digit_per = 0.5 ms ---> digit_freq = 2000 Hz

 // mhz_freq = 100*10^6 Hz

 // bit = log(mhz_freq/digit_freq)/log(2)

 // bit_position = bit - 1

 // refresh_counter[bit_position]

 case(refresh_counter[15]) 
    1'b0: begin
         AN = 8'b11111110;
         NUMBER  = num_one;
    end
    1'b1: begin
         AN = 8'b11111101;
         NUMBER = num_two;
    end
endcase   
end

always@(NUMBER) begin
case(NUMBER)
    0: A_TO_G = 7'b0000001;
    1: A_TO_G = 7'b1001111;
    2: A_TO_G = 7'b0010010;
    3: A_TO_G = 7'b0000110;
    4: A_TO_G = 7'b1001100;
    5: A_TO_G = 7'b0100100;
    6: A_TO_G = 7'b0100000;
    7: A_TO_G = 7'b0001111;
    8: A_TO_G = 7'b0000000;
    9: A_TO_G = 7'b0000100;
    default: A_TO_G = 7'b0000001;
endcase
end


endmodule