Menu Close

Vivado下利用ROM显示数码管实验课

在上节课Vivado Block ROM 的生成与配置详解中学习了在Vivado下如何配置一个ROM IP核。这篇文章将使用之前生成的ROM IP核,设计一个用拨码开关控制数码管显示的实验。

1.实验目的

  • 学会使用Xilinx的Block Memory Generator,生成相应的ROM IP核。
  • 复习*.coe文件的格式,时钟分频,数码管的扫描显示,按键的去抖,并将各模块一起综合调度。

2.实验设计

  • 设计16位数据输出的ROM,地址范围0-255,地址宽度8-bit,针对每一个地址,存储地址的平方值,比如:在地址为0x11时,存储的值为0x121。
  • 使用FII-PRX100-D 板上的8位拨码开关作为ROM的寻址,在使用SW时注意去抖。
  • 要求用移位加的快速算法,将16进制转成BCD,使用5位数码管显示ROM中的内容。

图1 工程框图

3.设计步骤

1)创建*.coe文件

在ROM IP核 rom16x256中填载了初始化的*.coe文件。按照实验设计要求,这个*.coe文件在每个地址中填充的是地址的平方值。这里使用了Verilog仿真工程生成相应的*.coe文件(参考文章verilog 仿真工程应用-生成coe/mif 文件,https://ed.2743.com/archives/16307)

最后生成的*.coe文件部分截图如下所示:(点击下面链接注册用户下载)

[content_control]test_rom.zip[/content_control]

 

图2

2)HEX2BCD

使用移位加的快速算法,将16进制的16-bit数转换成BCD码。参见文章权值变换法实现16进制到BCD码转换。

因为拨码开关输入最大值为0xFF,0xFF地址对应存储的数为0xFF X 0xFF = 0xFE01,换成十进制数是65025,在6位数码管的显示量程内。

HEX2BCD的代码如下:

`timescale 1ns / 1ps


module top#(
parameter SIM_DEBUG = "FALSE"
)
(
    input	clk_50m,
    input	[7:0] SW,
    output	[5:0] SEAT,	
    output	[6:0] SEVEN_SEG 
);

wire locked;
wire inclk;
wire rst = ~locked;

clk_wiz_0 PLL_inst
(
    .clk_out1	(inclk),    //100 MHz
    .reset		(1'b0), 
    .locked		(locked),    
    .clk_in1	(clk_50m)
); 

wire f_1ms;
wire s_f;

f_div #
(	.SIM_DEBUG (SIM_DEBUG)
)f_div_inst
(
    .inclk		(inclk),
    .rst        (rst),
                
    .f_1ms      (f_1ms),
    .s_f	    (s_f)
);

wire [7:0] SW_debounce;
sw_debounce sw_debounce_inst(
    .inclk		(inclk),
    .rst        (rst),
    .ms_f       (f_1ms),
    .SW_in      (SW),
    .SW_out     (SW_debounce)
);


 wire [15:0] rom_q;


rom16x256 rom16x256_inst 
(
    .clka(inclk),
    .addra(SW_debounce),  
    .douta(rom_q)     //not ready after 2 clock cycles
);

wire [3:0]  ones;
wire [3:0]  tens;
wire [3:0]  hundreds;
wire [3:0]  thousands;
wire [3:0]  ten_thousands;

HEX2BCD HEX2BCD_inst
(
    //.hex				(rom_q_r ),  
    .hex				(rom_q ),  	
    .ones               (ones),
    .tens               (tens),
    .hundreds           (hundreds),
    .thousands          (thousands),
    .ten_thousands      (ten_thousands)
);

seg_display seg_display_inst(
    .inclk				(inclk),
    .rst                (rst),
                     
    .f_1ms              (f_1ms),
    .s_f                (s_f),
    .ones               (ones),
    .tens               (tens),
    .hundreds           (hundreds),
    .thousands          (thousands),
    .ten_thousands      (ten_thousands),

    .SEAT 	            (SEAT),
    .SEVEN_SEG          (SEVEN_SEG)
);



endmodule

 

注意在文章权值变换法实现16进制到BCD码转换里 HEX2BCD是转换了32-bit的十六进制数,这里只需要保留16-bit十六进制数所能表达的 个,十,百,千,万(ones,tens,hundreds, thousands,ten_thousands ),这里也省略了与上层控制程序的握手,因此纯是组合逻辑,不需要时钟。

 

3)数码管显示

数码管显示部分和文章复杂数字钟设计(4)–分频与显示译码,文章数码管动态扫描显示Verilog实现–秒表。

本工程使用的是的数码管工程,实现了将个,十,百,千,万(ones,tens,hundreds, thousands,ten_thousands )分别显示在对应的数码管上,并实现动态扫描,代码如下:

`timescale 1ns / 1ps

module seg_display(
    input inclk,
    input rst,
    
    input f_1ms,
    input s_f,
    input [3:0]  ones,
    input [3:0]  tens,
    input [3:0]  hundreds,
    input [3:0]  thousands,
    input [3:0]  ten_thousands,
    
    output	reg [5:0] SEAT,	
    output	reg [6:0] SEVEN_SEG 
);

    
reg	[3:0]  ones_r;
reg	[3:0]  tens_r;
reg	[3:0]  hundreds_r;
reg	[3:0]  thousands_r;
reg	[3:0]  ten_thousands_r;

    
reg [3:0] count_sel;


always@(posedge inclk or posedge rst)
if(rst)
begin
    ones_r			<=	0;
    tens_r			<=	0;
    hundreds_r		<=	0;
    thousands_r		<=	0;
    ten_thousands_r <=	0;
end
else 
begin
    if(s_f)                
    begin
        ones_r			<=	ones;
        tens_r			<=	tens;
        hundreds_r		<=	hundreds;
        thousands_r		<=	thousands;
        ten_thousands_r	<=	ten_thousands;
    end
end

always@(posedge inclk or posedge rst )
if(rst) 
begin
    SEAT <= 6'b11_1111;
    count_sel <= 0;

end
else 
begin
    if(f_1ms) 
    begin
        case (SEAT)
        6'b11_1110:     
        begin
            SEAT <= 6'b11_1101;
            count_sel <= tens_r;
        end
        6'b11_1101: 
        begin
            SEAT <= 6'b11_1011;
            count_sel <= hundreds_r;
        end
        6'b11_1011: 
        begin
            SEAT <= 6'b11_0111;
            count_sel <= thousands_r;
        end
        6'b11_0111: 
        begin
            SEAT <= 6'b10_1111;
            count_sel <= ten_thousands_r;
        end
        6'b10_1111: 
        begin
            SEAT <= 6'b01_1111;
            count_sel <= 0;
        end
        6'b01_1111:    //nothing displayed here 
        begin
            SEAT <= 6'b11_1110;
            count_sel <= ones_r;
        end
        default: SEAT <= 6'b01_1111;
        endcase
    end
end

always@(*)
case(count_sel)
    0 :      SEVEN_SEG <= 7'b100_0000 ;
    1 :      SEVEN_SEG <= 7'b111_1001 ;
    2 :      SEVEN_SEG <= 7'b010_0100 ;
    3 :      SEVEN_SEG <= 7'b011_0000 ;
    4 :      SEVEN_SEG <= 7'b001_1001 ;
    5 :      SEVEN_SEG <= 7'b001_0010 ;
    6 :      SEVEN_SEG <= 7'b000_0010 ;
    7 :      SEVEN_SEG <= 7'b111_1000 ;
    8 :      SEVEN_SEG <= 7'b000_0000 ;
    9 :      SEVEN_SEG <= 7'b001_0000 ;
    default: SEVEN_SEG <= 7'b100_0000 ;   
endcase

endmodule

 

 

4)拨码开关的去抖

FII-PRX100-D FPGA开发板上拨码开关如下图所示:

图3

关于拨码开关去抖的原因和逻辑,参见文章自适应按键去抖Verilog实现与FPGA板载测试,注意按键去抖和拨码开关去抖略有差异,原因在于按键是“释放—按下,按下—释放”两个过程一般是代表一次操作;而拨码开关从上往下拨动,从下往上拨动应看作是两个操作,所以如果需要在按键去抖的工程上修改代码,需要将输出的脉冲信号位置和值稍作修改,修改后的代码如下:

`timescale 1ns / 1ps

module debounce_1(
    input inclk,
    input rst,
    
    input ms_f,
    input SW_in,
    
    output SW_out
    );
    

reg [ 2: 0 ] SW_r = 0;

reg SW_out_r;
reg [ 3: 0 ] deb_count;
reg [ 1: 0 ]deb_st  ;

always@(posedge inclk or posedge rst)
if(rst)
    SW_r <=  {3{SW_in}};
else
    SW_r <= { SW_r[1:0], SW_in };

assign SW_out = SW_out_r;

always@( posedge inclk or posedge rst )	
if ( rst )
begin
    deb_st <= 0;
    deb_count <= 0;
    SW_out_r <= 0;
end
else
begin
    case ( deb_st )
    0:
    begin
        deb_count <= 0;
        SW_out_r <= SW_r[2];
        
        if (SW_r[2 :1] == 2'b01)
            deb_st <= 1;
        else if (SW_r[2 :1] == 2'b10)
            deb_st <= 3;
    end
    1:
    begin
        if ( ~SW_r[ 2 ] )
            deb_st <= 0;
        else if ( deb_count >= 10 )
        begin
            SW_out_r <= 1'b1;
            deb_st <= 2;
        end
        else if ( ms_f )
            deb_count <= deb_count + 1;
    end
    2:
    begin
        deb_count <= 0;
        SW_out_r <= 1'b1;
        
        if ( ~SW_r[ 2 ] )
            deb_st <= 3;
    end
    3:
    begin
        if ( SW_r[ 2 ] )
            deb_st <= 2;
        else if ( deb_count >= 10 )
        begin
            deb_st <= 0;
            SW_out_r <= 1'b0;
        end
        else if ( ms_f )
            deb_count <= deb_count + 1;
    end
    default:
        deb_st <= 0;
    endcase
end

endmodule

 

上面的代码是对单bit拨码开关的去抖,在其上再包装一层,使用Verilog generate语句例化8个拨码开关(关于Verilog generate语句参见文章Verilog 中generate 语句),代码如下:

`timescale 1ns / 1ps

module sw_debounce(
    input inclk,
    input rst,
    
    input ms_f,
    input [7:0] SW_in,
    
    output [7:0] SW_out
    );

genvar i;
generate
    for(i = 7; i >= 0 ; i = i -1)
    begin
        debounce_1 debounce_1_inst(
            .inclk		(inclk),
            .rst        (rst),
        
            .ms_f       (ms_f),
            .SW_in      (SW_in[i]),
        
            .SW_out    (SW_out[i])
        );
    end
endgenerate
endmodule

 

5)时钟与分频模块

时钟和分频模块主要是由PLL和f_div两个模块完成。PLL IP核的输入是FII-PRX100-D FPGA开发板的50 MHz晶振提供,输出的是100 MHz系统时钟。

注意除了上面的HEX2BCD模块是由组合逻辑构成,不需要时钟,其它的模块都需要系统时钟,或是分频模块产生的毫秒脉冲,秒脉冲,比如拨码开关去抖,需要使用系统时钟和毫秒脉冲。分频模块代码如下:

`timescale 1ns / 1ps



module f_div#(
    parameter SIM_DEBUG = "FALSE"
)
(
    input inclk,          //100 MHz
    input rst,
    
    output reg f_1ms,
    output reg s_f	
);

localparam para_1ms = (SIM_DEBUG == "FALSE") ? 99999 : 2;
localparam para_1s = (SIM_DEBUG == "FALSE") ? 999 : 2;
reg [16:0] ms_r;
reg [9:0]  s_r;
 
always@ (posedge inclk or posedge rst)
if(rst) 
begin
    ms_r <= 0;
    f_1ms <= 0;
end
else  
begin
    f_1ms <= 0;
    if(ms_r == para_1ms) 
    begin
        ms_r <= 0;
        f_1ms <= 1'b1;
    end
    else
        ms_r <= ms_r + 1;
end
 
always@ (posedge inclk or posedge rst)
if(rst) 
begin
    s_r <= 0;
    s_f <= 0;
end
else  
begin
    s_f <= 1'b0;
    if(f_1ms )
        if(s_r == para_1s) 
        begin
            s_r <= 0;
            s_f <= 1'b1;
        end
        else 
            s_r <= s_r + 1;
end


endmodule

 

6)模块的组合与调度

本工程的顶层模块是top.v ,主要作用是组织各个模块,连接中间信号,接口FII-PRX100-D FPGA开发板上的晶振,拨码开关和数码管相关管脚。

代码如下:

`timescale 1ns / 1ps


module top#(
parameter SIM_DEBUG = "FALSE"
)
(
    input	clk_50m,
    input	[7:0] SW,
    output	[5:0] SEAT,	
    output	[6:0] SEVEN_SEG 
);

wire locked;
wire inclk;
wire rst = ~locked;

clk_wiz_0 PLL_inst
(
    .clk_out1	(inclk),    //100 MHz
    .reset		(1'b0), 
    .locked		(locked),    
    .clk_in1	(clk_50m)
); 

wire f_1ms;
wire s_f;

f_div #
(	.SIM_DEBUG (SIM_DEBUG)
)f_div_inst
(
    .inclk		(inclk),
    .rst        (rst),
                
    .f_1ms      (f_1ms),
    .s_f	    (s_f)
);

wire [7:0] SW_debounce;
sw_debounce sw_debounce_inst(
    .inclk		(inclk),
    .rst        (rst),
    .ms_f       (f_1ms),
    .SW_in      (SW),
    .SW_out     (SW_debounce)
);


 wire [15:0] rom_q;


rom16x256 rom16x256_inst 
(
    .clka(inclk),
    .addra(SW_debounce),  
    .douta(rom_q)     
);

wire [3:0]  ones;
wire [3:0]  tens;
wire [3:0]  hundreds;
wire [3:0]  thousands;
wire [3:0]  ten_thousands;

HEX2BCD HEX2BCD_inst
(
    .hex				(rom_q ),  	
    .ones               (ones),
    .tens               (tens),
    .hundreds           (hundreds),
    .thousands          (thousands),
    .ten_thousands      (ten_thousands)
);

seg_display seg_display_inst(
    .inclk				(inclk),
    .rst                (rst),
                     
    .f_1ms              (f_1ms),
    .s_f                (s_f),
    .ones               (ones),
    .tens               (tens),
    .hundreds           (hundreds),
    .thousands          (thousands),
    .ten_thousands      (ten_thousands),

    .SEAT 	            (SEAT),
    .SEVEN_SEG          (SEVEN_SEG)
);



endmodule

 

7)管脚约束*.xdc文件

参考FII-PRX100-D FPGA开发板的硬件原理图,管脚约束文件如下:

set_property PACKAGE_PIN D18 [get_ports clk_50m]


set_property -dict {PACKAGE_PIN K26 IOSTANDARD LVCMOS33} [get_ports {SEVEN_SEG[0]}]
set_property -dict {PACKAGE_PIN M20 IOSTANDARD LVCMOS33} [get_ports {SEVEN_SEG[1]}]
set_property -dict {PACKAGE_PIN L20 IOSTANDARD LVCMOS33} [get_ports {SEVEN_SEG[2]}]
set_property -dict {PACKAGE_PIN N21 IOSTANDARD LVCMOS33} [get_ports {SEVEN_SEG[3]}]
set_property -dict {PACKAGE_PIN N22 IOSTANDARD LVCMOS33} [get_ports {SEVEN_SEG[4]}]
set_property -dict {PACKAGE_PIN T24 IOSTANDARD LVCMOS33} [get_ports {SEVEN_SEG[5]}]
set_property -dict {PACKAGE_PIN P23 IOSTANDARD LVCMOS33} [get_ports {SEVEN_SEG[6]}]

set_property -dict {PACKAGE_PIN P21 IOSTANDARD LVCMOS33} [get_ports {SEAT[0]}]
set_property -dict {PACKAGE_PIN R25 IOSTANDARD LVCMOS33} [get_ports {SEAT[1]}]
set_property -dict {PACKAGE_PIN K25 IOSTANDARD LVCMOS33} [get_ports {SEAT[2]}]
set_property -dict {PACKAGE_PIN N18 IOSTANDARD LVCMOS33} [get_ports {SEAT[3]}]
set_property -dict {PACKAGE_PIN R17 IOSTANDARD LVCMOS33} [get_ports {SEAT[4]}]
set_property -dict {PACKAGE_PIN R16 IOSTANDARD LVCMOS33} [get_ports {SEAT[5]}]

###### SW  #################

set_property PACKAGE_PIN N8 [get_ports {SW[0]}]
set_property PACKAGE_PIN M5 [get_ports {SW[1]}]
set_property PACKAGE_PIN P4 [get_ports {SW[2]}]
set_property PACKAGE_PIN N4 [get_ports {SW[3]}]
set_property PACKAGE_PIN U6 [get_ports {SW[4]}]
set_property PACKAGE_PIN U5 [get_ports {SW[5]}]
set_property PACKAGE_PIN R8 [get_ports {SW[6]}]
set_property PACKAGE_PIN P8 [get_ports {SW[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {SW[7]}]
set_property BITSTREAM.CONFIG.UNUSEDPIN Pullnone [current_design]
set_property IOSTANDARD LVCMOS33 [get_ports clk_50m]



 

4.下板实验现象

本工程在FII-PRX100-D上的实验现象如下所示:

5.实验思考

  • 如何编写*.coe文件形成正弦波形,余弦波形等函数发生器
  • 综合应用,结合ROM和PWM的特点,形成SPWM调制波形

 

Posted in FPGA, FPGA 教材教案, FPGA开发板, Verilog, Verilog, Vivado, Vivado, 开发工具, 开发工具, 开发板, 开发语言, 教材与教案, 文章, 编程语言

发表回复

相关链接