功能描述
给8个数字,范围在[0,15]。要求把这8个数字显示到8个数码管上。
此处应该有上板运行的效果,但是,我没板子,所以XD们自行想象。
是不是想不出来?我去百度上截了一张图,放出来
知识准备
- 数码管
这是8段数码管。后面我们只用到它的七段,也就是不包括h那个小数点。要让abcdefg亮某几段,以实现显示数字。
这是肯定有小伙伴问了,这只有一个数码管,10怎么显示呢?好问题,所以此时我们用16进制,来显示
所以数字和数码管每段的对应表为:
- 多个数码管的显示
加入有3个数码管012,其中,sel0,sel1,sel2分别控制数码管0,1,2是否工作。右下角的abcdefgh对应八段数码管的8个段。假如此时要让数码管0显示0,那就让sel0为1,然后abcdefgh的值分别为00000011。
现在有一个问题,看图,我们发现,这三个数码管是共用abcdefgh的。如果我现在要让数码管0显示0,数码管1显示1,数码管2显示2,那怎么做?
嘿嘿!是不是发现问题了?这三个数码管不能同时工作。怎么办呢?老板就是要让我实现它啊!注意,硬件的时钟很小,经常以纳秒为单位,我们可以借用操作系统的并发与并行来理解这个过程。宏观上我们看着三个数码管是同时显示的,微观上它们三是轮流给信号的。也就是:
- 第1个ns:sel0=1,sel1=sel2=0 abcdegfh=[数码管0要显示的数字]
- 第2个ns:sel1=1,sel0=sel3=0 abcdegfh=[数码管1要显示的数字]
- 第3个ns:sel2=1,sel0=sel1=0 abcdegfh=[数码管2要显示的数字]
分析功能
现在我们要实现,8个数字显示在8个数码管上,先上图,再来解释。
- disp_data是32位的数据,我们让disp_data[3:0]显示在第一个数码管上,disp_data[7:4]显示在第二个数码管上…
- data_tmp[3:0]就是此时要显示的数据
- divider是一个分频器,实际上它是一个计数器。Clk是20ns为一个周期,现在我们想要美20ms切换一个数码管显示,所以需要分频率,Clk_1K的频率就是1KHz
- shift8是循环移位器。这个器件是给0000_0001实现循环左移的。移位的结果保存到sel_r[7:0]中
- sel[7:0]:这是用来选择哪一个数码管亮,对应功能如下:
- mux2:二选一选择器。如果使能信号en为1的话,则选择某一个数码管亮,sel就是sel_r的值;如果en为0的话,则所有数码管都不亮,sel的值全是0
- mux8:8选一选择器。按照sel的值,选择显示哪一个数
- LUT:查找表。这就是来查找某个数字,对应到7段数码管上abcdefg各是什么值,LUT就相当于下面这张表:
代码实现
OK,Talk is cheap. Show you the code.
`timescale 1ns / 1ps
module HXE8(clk,rst,en,disp_data,sel,seg);
input clk;//50MHz
input rst;
input [31:0]disp_data;
input en;
output [7:0]sel;//选择8个数码管中的某一个
output reg[6:0]seg;//七段数码管
//制作分频器
//分频器实际就是计数器。把原本周期为20ns的改成1ms。
//所以频率从50MHz改成1kHz。所以是模50_000的计数器
//但是,一个周期信号需要变化两次,所以计数到25_000就让信号变化
//25_000需要15位二进制来表示,所以divider_cnt是15位
reg [14:0]divider_cnt;
reg clk_1k;
always @(posedge clk or negedge rst) begin
if(!rst)begin
divider_cnt<=15'b0;
clk_1k<=1'b0;
end
else if (!en) divider_cnt<=15'b0;
else if(divider_cnt==24999)begin
divider_cnt<=15'b0;
clk_1k<=~clk_1k;
end
else divider_cnt<=divider_cnt+1'b1;
end
//循环移位器shift8
reg [7:0]sel_r;
always @(posedge clk_1k or negedge rst) begin
if (!rst)sel_r<=8'b0000_0001;
else if(sel_r==8'b1000_0000)
sel_r<=8'b0000_0001;
else sel_r<=sel_r<<1;
end
//mux8(组合逻辑)
reg [3:0]data_tmp;//待显示数据缓存
always @(*) begin
case(sel_r)
8'b0000_0001:data_tmp=disp_data[3:0];
8'b0000_0010:data_tmp=disp_data[7:4];
8'b0000_0100:data_tmp=disp_data[11:8];
8'b0000_1000:data_tmp=disp_data[15:12];
8'b0001_0000:data_tmp=disp_data[19:16];
8'b0010_0000:data_tmp=disp_data[23:20];
8'b0100_0000:data_tmp=disp_data[27:24];
8'b1000_0000:data_tmp=disp_data[31:28];
default:data_tmp=4'b0;
endcase
end
//LUT(组合逻辑)
//组合逻辑中,reg的赋值可以用= 时序逻辑的话,用<=
always @(*) begin
case(data_tmp)
4'd0:seg=7'b100_0000;
4'd1:seg=7'b111_1001;
4'd2:seg=7'b010_0100;
4'd3:seg=7'b011_0000;
4'd4:seg=7'b001_1001;
4'd5:seg=7'b001_0010;
4'd6:seg=7'b000_0010;
4'd7:seg=7'b111_1000;
4'd8:seg=7'b000_0000;
4'd9:seg=7'b001_0000;
4'ha:seg=7'b000_1000;
4'hb:seg=7'b000_0011;
4'hc:seg=7'b100_0110;
4'hd:seg=7'b010_0001;
4'he:seg=7'b000_0110;
4'hf:seg=7'b000_1110;
endcase
end
//二选一多路器
assign sel=(en)?sel_r:8'b0000_0000;
endmodule
仿真代码:
`timescale 1ns / 1ns
`define clock_period 20
module HXE8_tb;
reg clk,rst,en;
reg [31:0]disp_data;
wire [7:0]sel;
wire [6:0]seg;
HXE8 HXE80(
.clk(clk),
.rst(rst),
.en(en),
.disp_data(disp_data),
.sel(sel),
.seg(seg));
//产生时钟
initial clk=1;
always #(`clock_period/2)clk=~clk;
initial begin
rst=1'b0;
en=1;
disp_data=32'h12345678;
#(`clock_period*20);
rst=1;
#(`clock_period*20);
#20000000;
disp_data=32'h87654321;
#20000000;
disp_data=32'h89abcdef;
#20000000;
$stop;
end
endmodule
仿真结果:
例如,在此刻,disp_data是12345679(16进制的),sel[7:0]为0000_1000,那么选中5这个数字,而5对应的7段数码管是001_0010,和我们的设计相符合!
结语
有点遗憾,没能上板,上板了的小伙伴可以发出来,让俺瞧瞧~
菜鸟一枚,如果有错,欢迎指出。
参考
【小梅哥FPGA设计思想与验证方法视频教程】https://www.bilibili.com/video/BV1KE411h7AZ?p=10&vd_source=a104a55bfae98f2c7faf58a3a2332bfe