一個問題:六位八段數碼管(Verilog)

Handat發表於2024-06-02

【基本資訊】

需求:verilog程式,顯示任意六位字元或數值,包含點號,且能夠按需點亮位數。(學習篇)

晶片型號:cyclone Ⅳ EP4CE10F17C8

數碼管屬性:六位、八段

【最終成果圖】

image

經過多輪測試,最後程式碼程式滿足設計要求,但結合模擬發現了一個問題,模擬和上機不匹配,當然還是要以上機為準。

【模組例化圖】

image

這裡就是簡單地賦個初始值,來測試digital模組,最終是要seg_led和seg_sel的顯示變化。

【verilog程式】

1、digital模組

模組化設計,六個位上的值直接拆開做處理,分析起來清晰。sel_cnt表示目前要顯示的位數(從右往左),即sel_cnt = 6代表全亮。dp_cnt表示點號所在位數,若dp_cnt=0則代表沒有點號。clk_2khz為數碼管的重新整理訊號。

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在過程討論部分做解釋。

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要晚上一個數碼管重新整理週期。

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

根據原理圖,數碼管是共陽極,並且位選拉低有效,這裡測試是沒有問題的。

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模組

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來讀邊沿訊號。

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、頂部例化測試

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模擬

`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判斷為真的話,透過模擬(下圖)觀察到,位選和段選不匹配,就是說,點號的位置不正確,現象是位選落後了。

image

然後想著將賦值方法改為阻塞賦值,還是有問題。強制更改,設定判斷條件為if(dp_cnt == (sel_cnt_tran-1’b1))看看,顯然,這樣雖然有效(圖在下面),但只能保證最終的點號位置有限,比如dp_cnt = 6就有問題,因為sel_cnt_tran在6後賦值是0,達不到條件。後面藉助變數tran1在sel_cnt_tran重新整理處賦值使其落後一個週期,模擬顯示不正確,但上機測試有效。

image

相關文章