一个问题:六位八段数码管(Verilog)

【基本信息】

需求:verilog程序,显示任意六位字符或数值,包含点号,且能够按需点亮位数。(学习篇)

芯片型号:cyclone Ⅳ EP4CE10F17C8

数码管属性:六位、八段

【最终成果图】

经过多轮测试,最后代码程序满足设计要求,但结合仿真发现了一个问题,仿真和上机不匹配,当然还是要以上机为准。

【模块例化图】

这里就是简单地赋个初始值,来测试digital模块,最终是要seg_led和seg_sel的显示变化。

【verilog程序】

1、digital模块

模块化设计,六个位上的值直接拆开做处理,分析起来清晰。sel_cnt表示目前要显示的位数(从右往左),即sel_cnt = 6代表全亮。dp_cnt表示点号所在位数,若dp_cnt=0则代表没有点号。clk_2khz为数码管的刷新信号。

verilog 复制代码
module digital
(
    input           sys_clk      ,
    input           sys_rst      ,
    input           clk_2khz     ,
    
    input [3:0]     num6,
    input [3:0]     num5,
    input [3:0]     num4,
    input [3:0]     num3,
    input [3:0]     num2,
    input [3:0]     num1,
    input [2:0]     sel_cnt     , 
    input [2:0]     dp_cnt      ,
   
    output reg[5:0] seg_sel     ,
    output reg[7:0] seg_led      
);
reg[2:0] sel_cnt_tran;
reg[3:0] num;
reg[2:0] tran1;
//endmodule

定义了几个变量,sel_cnt_tran用来根据数码管刷新信号更替数码管的位选,而num用来接引各位上的值,数码管根据num编号段选信号。tran1在过程讨论部分做解释。

verilog 复制代码
always @(posedge sys_clk or negedge sys_rst)
begin
    if(!sys_rst)
        sel_cnt_tran = 3'd0;
    else 
    if(clk_2khz)begin 
        if(sel_cnt_tran < sel_cnt)begin
            tran1 = sel_cnt_tran;
            sel_cnt_tran = sel_cnt_tran + 1'b1;
        end
        else begin
            tran1 = sel_cnt_tran;
            sel_cnt_tran = 3'd0;
        end
    end
end

上述代码就是位选更替,可以直观得到,tran1比sel_cnt_tran要晚上一个数码管刷新周期。

verilog 复制代码
always @(posedge sys_clk or negedge sys_rst)
begin
    if(!sys_rst)
        seg_sel <= 6'b111_111;
    else if(clk_2khz)begin
        case(sel_cnt_tran)
            3'd1:begin
                seg_sel <= 6'b111_110;
                num <= num1;
            end
            3'd2:begin
                seg_sel <= 6'b111_101;
                num <= num2;
            end
            3'd3:begin
                seg_sel <= 6'b111_011;
                num <= num3;
            end
            3'd4:begin
                seg_sel <= 6'b110_111;
                num <= num4;
            end
            3'd5:begin
                seg_sel <= 6'b101_111;
                num <= num5;
            end
            3'd6:begin
                seg_sel <= 6'b011_111;
                num <= num6;
            end
            default:begin
                seg_sel <= 6'b111_111;
            end
        endcase
    end
end

根据原理图,数码管是共阳极,并且位选拉低有效,这里测试是没有问题的。

verilog 复制代码
always @(posedge sys_clk or negedge sys_rst)begin
    if (!sys_rst)
        seg_led = 8'b0;
    else begin
        case (num)
            4'h0 :    seg_led = 8'b1100_0000;
            4'h1 :    seg_led = 8'b1111_1001;
            4'h2 :    seg_led = 8'b1010_0100;
            4'h3 :    seg_led = 8'b1011_0000;
            4'h4 :    seg_led = 8'b1001_1001;
            4'h5 :    seg_led = 8'b1001_0010;
            4'h6 :    seg_led = 8'b1000_0010;
            4'h7 :    seg_led = 8'b1111_1000;
            4'h8 :    seg_led = 8'b1000_0000;
            4'h9 :    seg_led = 8'b1001_0000;
            4'ha :    seg_led = 8'b1000_1000;
            4'hb :    seg_led = 8'b1000_0011;
            4'hc :    seg_led = 8'b1100_0110;
            4'hd :    seg_led = 8'b1010_0001;
            4'he :    seg_led = 8'b1000_0110;
            4'hf :    seg_led = 8'b1000_1110;
            default : seg_led = 8'b1100_0000;//多余
        endcase
        if(dp_cnt == (tran1))begin	//确定点号,这里不是sel_cnt_tran
            seg_led = seg_led & 8'b0111_1111;
        end 
    end
end

2、clk_2khz模块

verilog 复制代码
module clk_2khz
(
    input               sys_clk      ,
    input               sys_rst      ,
    
    output reg          clk_out 
);
parameter   cnt_2khz_max = 25_000;
reg[14:0]   cnt;

clk_2khz时钟产生很简单,cnt_2khz_max = 1/2_000x50_000_000,在这就放下代码。注意两个问题:一个是不要直接posedge clk_2khz方式来读上升沿,另外尽量不要采用取反方式产生分频时钟信号,如果是取反,可以采用wire en;assign en = d1 & d0来读边沿信号。

verilog 复制代码
always @(posedge sys_clk or negedge sys_rst)begin
    if(!sys_rst)begin
        cnt <= 15'd0;
        clk_out <= 1'b0;
    end
    else if(cnt <(cnt_2khz_max-1'b1))begin
        cnt <= cnt + 1'b1;
        clk_out <= 1'b0;
    end
    else begin
        cnt <= 15'd0;verilog
        clk_out <= 1'b1;
    end
end
endmodule

3、顶部例化测试

verilog 复制代码
module digital_top  (  
    input           sys_clk ,  
    input           sys_rst ,  
    output [5:0] seg_sel ,  
    output [7:0] seg_led   
);  
wire clk_2khz_tran;
reg[3:0] num1       = 4'd2;
reg[3:0] num2       = 4'h1;
reg[3:0] num3       = 4'd5;
reg[2:0] sel_cnt    = 3'd6;
reg[2:0] dp_cnt     = 3'd6;
digital digital_inst();  //例化省略
clk_2khz clk_generator1(); 
endmodule

4、Modelsim仿真

verilog 复制代码
`timescale 1ns/1ns
module digital_tb();

parameter   T1 =20 ;
parameter   T2 =200;
parameter max = 14;
reg sys_clk;
reg clk_2khz;
reg sys_rst;
reg[3:0] num1       = 4'h1;
reg[3:0] num2       = 4'h1;
reg[3:0] num3       = 4'h1;
reg[2:0] sel_cnt    = 3'd6;
reg[2:0] dp_cnt     = 3'd6;
reg[3:0] cnt;
wire[7:0] seg_led;
wire[5:0] seg_sel;

always # (T1/2) sys_clk <= ~sys_clk;

always @(posedge sys_clk or negedge sys_rst)begin
    if(!sys_rst)
        cnt <= 4'd0;
    else begin 
        if(cnt < max)begin
            cnt <= cnt + 1'b1;
            clk_2khz <= 1'b0;
        end
        else begin 
            cnt <= 4'd0;
            clk_2khz <= 1'b1;
        end
    end
end

initial begin
    sys_clk              <=1'b0;
    sys_rst              <=1'b0;
    #50 sys_rst          <=1'b1;  
end
    digital digital_inst();//例化省略  
endmodule

【过程讨论】

首先由于自己疏忽,数码管位选刷新位置采用的是非阻塞赋值的方法,通过用sel_cnt_tran判断为真的话,通过仿真(下图)观察到,位选和段选不匹配,就是说,点号的位置不正确,现象是位选落后了。

然后想着将赋值方法改为阻塞赋值,还是有问题。强制更改,设定判断条件为if(dp_cnt == (sel_cnt_tran-1'b1))看看,显然,这样虽然有效(图在下面),但只能保证最终的点号位置有限,比如dp_cnt = 6就有问题,因为sel_cnt_tran在6后赋值是0,达不到条件。后面借助变量tran1在sel_cnt_tran刷新处赋值使其落后一个周期,仿真显示不正确,但上机测试有效。