[FPGA] 基于FPGA数码管设计

[复制链接]
382|0
 楼主 | 2018-11-5 11:16 | 显示全部楼层 |阅读模式
本帖最后由 明德扬fpga 于 2018-11-5 11:21 编辑

我们先分析要实现的功能,数码管0显示数字0,翻译成信号就是seg_sel的值为8’b1111_1110,seg_ment的值为7’b000_0001。数码管1显示数字1,也就是说seg_sel的值为8’b1111_1101,seg_ment的值为7’b100_1111。以此类推,数码管7显示数字7,就是seg_sel的值为8’b0111_1111,seg_ment的值为7’b000_1111。
再留意下,以上都是每隔1秒进行变化,并且是8个数码管轮流显示,那么波形示意图如下图所示。


上图就是seg_sel和seg_seg信号的变化波形图。在显示第1个时,seg_sel=8’hfe,seg_ment=7’h01并持续1秒;在第1个时,seg_sel=8’hfd,seg_ment=7’h4f并持续1秒;以此类推,第8个时,seg_sel=8’h7f,seg_ment=7’h0f并持续1秒。然后又再次重复。
由波形图可知,我们需要1个计数器用来计算1秒的时间。本工程的工作时钟是50MHz,即周期为20ns,计数器计数到1_000_000_000/20=50_000_000个,我们就能知道1秒时间到了。另外,由于该计数器是不停地计数,永远不停止的,可以认为加1条件一直有效,可写成:assign add_cnt==1。综上所述,该计数器的代码如下。

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end

assign add_cnt0 = 1 ;
assign end_cnt0 = add_cnt0 && cnt0==50_000_000-1 ;

再次观察波形图,我们发现有第1个,第2个直到第8个,说明这还需要另外一个计数器来表示第几个。该计数器表示第几个,自然是完成1秒就加1,因为加1条件可为end_cnt0。该计数器一共要数8次。所以代码为:

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==8-1 ;

有了两个计数器,我们来思考输出信号seg_sel的变化。概括起来,在第1次的时候输出值为8’hfe;在第2次的时候输出值为8’hfd;以此类推,在第8次的时候输出值为8’h7f。我们用信号cnt1来代替第几次,也就是:当cnt1==0的时候,输出值为8’hfe;在cnt1==1的时候输出值为8’hfd;以此类推,在cnt1==7的时候输出值为8’h7f。再进一步翻译成代码,就变成如下:

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        seg_sel <= 8'hfe;
    end
    else if(cnt1==0) begin
        seg_sel <= 8'hfe;
    end
    else if(cnt1==1) begin
        seg_sel <= 8'hfd;
    end
    else if(cnt1==2) begin
        seg_sel <= 8'hfb;
    end
    else if(cnt1==3) begin
        seg_sel <= 8'hf7;
    end
    else if(cnt1==4) begin
        seg_sel <= 8'hef;
    end
    else if(cnt1==5) begin
        seg_sel <= 8'hdf;
    end
    else if(cnt1==6) begin
        seg_sel <= 8'hbf;
    end
    else if(cnt1==7) begin
        seg_sel <= 8'h7f;
    end
end

读者有没有发现,上面代码基本上和文字描述是一模一样的,这进一步展现了verilog是“硬件描述语言”。上面的代码是能正确实现seg_sel功能的,从实现角度和资源角度来说,都挺好。但代码进一步概括,可以化简如下:

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        seg_sel <= 8'hfe ;
    end
    else begin
        seg_sel <= ~(8'b1<<cnt1) ;
    end
end

对上面代码解释一下,第131行是指先将8’b1向左移位,再取反后的值,赋给seg_sel。假设此时cnt1等于0,那么8’b1<<0的结果是8’b0000_0001,取反的值为8’hfe;假设cnt1等于3,那么8’b1<<3的结果为8’b000_1000,取反后的结果为8’b1111_0111,即8’hf7。与第一种写法的结果都是相同的。

我们来思考输出信号seg_ment的变化。概括起来,在第1次的时候输出值为7’h01;在第2次的时候输出值为7’h4f;以此类推,在第8次的时候输出值为7’h0f。我们用信号cnt1来代替第几次,也就是:当cnt1==0的时候,输出值为7’h01;在cnt1==1的时候输出值为7’h4f;以此类推,在cnt1==7的时候输出值为7’h0f。再进一步翻译成代码,就变成如下:

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        seg_ment <= 7'h01;
    end
    else if(cnt1==0) begin
        seg_ment <= 7'h01;
    end
    else if(cnt1==1) begin
        seg_ment <= 7'h4f;
    end
    else if(cnt1==2) begin
        seg_ment <= 7'h12;
    end
    else if(cnt1==3) begin
        seg_ment <= 7'h06;
    end
    else if(cnt1==4) begin
        seg_ment <= 7'h4c;
    end
    else if(cnt1==5) begin
        seg_ment <= 7'h24;
    end
    else if(cnt1==6) begin
        seg_ment <= 7'h20;
    end
    else if(cnt1==7) begin
        seg_ment <= 7'h0f;
    end
end

上面的代码正确地实现了seg_ment的功能,对于本工程说已经完美。但我们分析一下,就知道上面代码实现了类似译码的功能,将数字设成数码管显示的值,代码里只对0~7进行译码。很自然的,我先做一个通用的译码模块,将0~9都进行译码,以后就方便调用了。例如改成下面代码。

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        seg_ment <= 7'h01;
    end
    else if(data==0) begin
        seg_ment <= 7'h01;
    end
    else if(data==1) begin
        seg_ment <= 7'h4f;
    end
    else if(data==2) begin
        seg_ment <= 7'h12;
    end
    else if(data==3) begin
        seg_ment <= 7'h06;
    end
    else if(data==4) begin
        seg_ment <= 7'h4c;
    end
    else if(data==5) begin
        seg_ment <= 7'h24;
    end
    else if(data==6) begin
        seg_ment <= 7'h20;
    end
    else if(data==7) begin
        seg_ment <= 7'h0f;
    end
    else if(data==8) begin
        seg_ment <= 7'h00;
    end
    else if(data==9) begin
        seg_ment <= 7'h04;
    end
end


然后我们只要控制好data就能实现想要在数码管显示的数字,如下面代码。

1        assign data = cnt1 ;

当cnt1=0,则数码管会显示0。当cnt1=1,则数码管会显示1。
在代码的最后一行写下endmodule
        
1        endmodule

至此,主体程序已经完成。接下来是将module补充完整。

3.3 信号定义


接下来定义信号类型。
cnt0是用always产生的信号,因此类型为reg。cnt0计数的最大值为50_000_000,需要用26根线表示,即位宽是26位。add_cnt0和end_cnt0都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1个线表示即可。因此代码如下:

reg    [25:0]            cnt0        ;
wire                     add_cnt0    ;
wire                     end_cnt0    ;

cnt1是用always产生的信号,因此类型为reg。cnt1计数的最大值为7,需要用3根线表示,即位宽是3位。add_cnt1和end_cnt1都是用assign方式设计的,因此类型为wire。并且其值是0或者1,1根线表示即可。因此代码如下:

reg    [ 2:0]            cnt1        ;
wire                     add_cnt1    ;
wire                     end_cnt1    ;

seg_sel是用always方式设计的,因此类型为reg,其一共有8根线,即位宽为8。因此代码如下:

1        reg    [ 7:0]            seg_sel         ;

seg_ ment是用always方式设计的,因此类型为reg,其一共有7根线,即位宽为7。因此代码如下:

1        reg    [ 6:0]            segment         ;


如果做了译码电路,即用到了data这个信号。那么data是用assign设计的,所以类型为wire,其最大值为9,所以需要4位位宽。代码如下:

1        wire  [3:0]   data     ;


至此,整个代码的设计工作已经完成。下一步是新建工程和上板查看现象。
IMG_3784.JPG IMG_3789.JPG

扫描二维码,随时随地手机跟帖
您需要登录后才可以回帖 登录 | 注册

本版积分规则

我要发帖 投诉建议 创建版块 申请版主

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式

论坛热帖

关闭

热门推荐上一条 /5 下一条

在线客服 快速回复 返回顶部 返回列表