在上节课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调制波形