在上节课Vivado Block ROM 的生成与配置详解中学习了在Vivado下如何配置一个ROM IP核。这篇文章将使用之前生成的ROM IP核,设计一个用拨码开关控制数码管显示的实验。
1.实验目的
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://2743.com/archives/16307%EF%BC%89
最后生成的*.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的代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
`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 )分别显示在对应的数码管上,并实现动态扫描,代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
`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板载测试,注意按键去抖和拨码开关去抖略有差异,原因在于按键是“释放—按下,按下—释放”两个过程一般是代表一次操作;而拨码开关从上往下拨动,从下往上拨动应看作是两个操作,所以如果需要在按键去抖的工程上修改代码,需要将输出的脉冲信号位置和值稍作修改,修改后的代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
`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 语句),代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
`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模块是由组合逻辑构成,不需要时钟,其它的模块都需要系统时钟,或是分频模块产生的毫秒脉冲,秒脉冲,比如拨码开关去抖,需要使用系统时钟和毫秒脉冲。分频模块代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
`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开发板上的晶振,拨码开关和数码管相关管脚。
代码如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
`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开发板的硬件原理图,管脚约束文件如下:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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调制波形