1. 驱动八位数码管循环点亮
1.1 数码管结构图
数码管有两种结构,共阴极和共阳极,ACX720板上的是共阳极数码管,低电平点亮。
1.2 三位数码管等效电路图
为了节约I/O接口,各个数码管的各段发光管被连在一起,通过sel端口选择要发光的数码管。
1.3 单个数码管发光的LUT(look up table)
2. 数码管显示与动态扫描逻辑建模
3. 数码管显示与动态扫描的Verilog实现
3.1 不完善的设计代码版本
1. 设计代码
该设计代码有两个地方需要修改
- 分频时钟的使用不合理,在fpga设计中,一定要避免使用计数器(寄存器)分频得到的信号来作为时钟再去驱动其他的寄存器;推荐使用使能时钟,不要使用门控时钟。
- 在fpga设计中,推荐多使用时序逻辑而非组合逻辑。对于case语句,我们要将其放在always语句块中,在每个时钟沿下发生数据的变换。
module hex8(
clk,
rstn,
disp_data,
sel,
led
);
parameter times = 25000; // 0.5ms
input clk;
input rstn;
input [31:0]disp_data;
output reg [7:0] sel;
output reg [7:0] led;
reg [15:0]div_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
div_cnt <= 0;
else if(div_cnt >= times - 1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'd1;
//分频时钟
//用一个寄存器来模拟一个时钟去驱动其他寄存器会存在许多问题
reg clk_lk;
always@(posedge clk or negedge rstn)
if(!rstn)
clk_lk <= 0;
else if(div_cnt == times - 1)
clk_lk <= ~clk_lk;
//cnt累加器
reg [2:0] num_cnt;
always@(posedge clk_lk or negedge rstn)
if(!rstn)
num_cnt <= 0;
else
num_cnt <= num_cnt + 1'd1;
//三八译码器
always@(posedge clk_lk or negedge rstn)
if(!rstn)
sel <= 0;
else case(num_cnt)
0:sel = 8'b0000_0001;
1:sel = 8'b0000_0010;
2:sel = 8'b0000_0100;
3:sel = 8'b0000_1000;
4:sel = 8'b0001_0000;
5:sel = 8'b0010_0000;
6:sel = 8'b0100_0000;
7:sel = 8'b1000_0000;
endcase
//八选一多路器
reg [3:0]disp_tmp;
always@(*)
case(num_cnt)
0:disp_tmp = disp_data[3:0];
1:disp_tmp = disp_data[7:4];
2:disp_tmp = disp_data[11:8];
3:disp_tmp = disp_data[15:12];
4:disp_tmp = disp_data[19:16];
5:disp_tmp = disp_data[23:20];
6:disp_tmp = disp_data[27:24];
7:disp_tmp = disp_data[31:28];
endcase
//四十六译码器
always@(*)
case(disp_tmp)
0:led = 8'hc0;
1:led = 8'hf9;
2:led = 8'ha4;
3:led = 8'hb0;
4:led = 8'h99;
5:led = 8'h92;
6:led = 8'h82;
7:led = 8'hf8;
8:led = 8'h80;
9:led = 8'h90;
4'ha:led = 8'h88;
4'hb:led = 8'h83;
4'hc:led = 8'hc6;
4'hd:led = 8'ha1;
4'he:led = 8'h86;
4'hf:led = 8'h8e;
default:led = 8'hc0;
endcase
endmodule
2. 仿真波形
3.1 修改分频时钟和case语句后的设计代码
1. 设计代码
module hex8_2(
clk,
rstn,
disp_data,
sel,
led
);
parameter times = 50000; // 1ms
input clk;
input rstn;
input [31:0]disp_data;
output reg [7:0] sel;
output reg [7:0] led;
reg [15:0]div_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
div_cnt <= 0;
else if(div_cnt >= times - 1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'd1;
//使能时钟
reg clk_lk;
always@(posedge clk or negedge rstn)
if(!rstn)
clk_lk <= 0;
else if(div_cnt == times - 1)
clk_lk <= 1'd1;
else
clk_lk <= 0;
//cnt累加器
reg [2:0] num_cnt;
always@(posedge clk_lk or negedge rstn)
if(!rstn)
num_cnt <= 0;
else if(clk_lk == 1)
num_cnt <= num_cnt + 1'd1;
//三八译码器
always@(posedge clk or negedge rstn)
if(!rstn)
sel <= 0;
else case(num_cnt)
0:sel = 8'b0000_0001;
1:sel = 8'b0000_0010;
2:sel = 8'b0000_0100;
3:sel = 8'b0000_1000;
4:sel = 8'b0001_0000;
5:sel = 8'b0010_0000;
6:sel = 8'b0100_0000;
7:sel = 8'b1000_0000;
endcase
//八选一多路器
reg [3:0]disp_tmp;
always@(posedge clk)
case(num_cnt)
0:disp_tmp = disp_data[3:0];
1:disp_tmp = disp_data[7:4];
2:disp_tmp = disp_data[11:8];
3:disp_tmp = disp_data[15:12];
4:disp_tmp = disp_data[19:16];
5:disp_tmp = disp_data[23:20];
6:disp_tmp = disp_data[27:24];
7:disp_tmp = disp_data[31:28];
endcase
//四十六译码器
always@(posedge clk)
case(disp_tmp)
0:led = 8'hc0;
1:led = 8'hf9;
2:led = 8'ha4;
3:led = 8'hb0;
4:led = 8'h99;
5:led = 8'h92;
6:led = 8'h82;
7:led = 8'hf8;
8:led = 8'h80;
9:led = 8'h90;
4'ha:led = 8'h88;
4'hb:led = 8'h83;
4'hc:led = 8'hc6;
4'hd:led = 8'ha1;
4'he:led = 8'h86;
4'hf:led = 8'h8e;
default:led = 8'hc0;
endcase
endmodule
2.仿真代码
module hex8_2(
clk,
rstn,
disp_data,
sel,
led
);
parameter times = 50000; // 1ms
input clk;
input rstn;
input [31:0]disp_data;
output reg [7:0] sel;
output reg [7:0] led;
reg [15:0]div_cnt;
always@(posedge clk or negedge rstn)
if(!rstn)
div_cnt <= 0;
else if(div_cnt >= times - 1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'd1;
//使能时钟
reg clk_lk;
always@(posedge clk or negedge rstn)
if(!rstn)
clk_lk <= 0;
else if(div_cnt == times - 1)
clk_lk <= 1'd1;
else
clk_lk <= 0;
//cnt累加器
reg [2:0] num_cnt;
always@(posedge clk_lk or negedge rstn)
if(!rstn)
num_cnt <= 0;
else if(clk_lk == 1)
num_cnt <= num_cnt + 1'd1;
//三八译码器
always@(posedge clk or negedge rstn)
if(!rstn)
sel <= 0;
else case(num_cnt)
0:sel = 8'b0000_0001;
1:sel = 8'b0000_0010;
2:sel = 8'b0000_0100;
3:sel = 8'b0000_1000;
4:sel = 8'b0001_0000;
5:sel = 8'b0010_0000;
6:sel = 8'b0100_0000;
7:sel = 8'b1000_0000;
endcase
//八选一多路器
reg [3:0]disp_tmp;
always@(posedge clk)
case(num_cnt)
0:disp_tmp = disp_data[3:0];
1:disp_tmp = disp_data[7:4];
2:disp_tmp = disp_data[11:8];
3:disp_tmp = disp_data[15:12];
4:disp_tmp = disp_data[19:16];
5:disp_tmp = disp_data[23:20];
6:disp_tmp = disp_data[27:24];
7:disp_tmp = disp_data[31:28];
endcase
//四十六译码器
always@(posedge clk)
case(disp_tmp)
0:led = 8'hc0;
1:led = 8'hf9;
2:led = 8'ha4;
3:led = 8'hb0;
4:led = 8'h99;
5:led = 8'h92;
6:led = 8'h82;
7:led = 8'hf8;
8:led = 8'h80;
9:led = 8'h90;
4'ha:led = 8'h88;
4'hb:led = 8'h83;
4'hc:led = 8'hc6;
4'hd:led = 8'ha1;
4'he:led = 8'h86;
4'hf:led = 8'h8e;
default:led = 8'hc0;
endcase
endmodule
3.仿真波形
4. 使能时钟与门控时钟的原理和差异
1. 使能时钟和门控时钟
-
使用门控时钟时:将一个D触发器的输出作为其他D触发器的时钟输入,忽略ENA。
-
使用使能时钟时:D触发器的工作时钟仍为高质量的全局时钟,利用使能时钟使能D触发器。
2. 使用全局时钟的原因
- 全局时钟是一条“高速公路”,时钟信号到达各个寄存器的时间受连线距离影响比较小,而用寄存器产生的门控时钟,走的是内部连线,随着距离的延长,延迟增加明显,从而破环寄存器的建立保持时间。
- 使用寄存器传输时钟信号时,随着寄存器一级一级的传递,信号会出现波动,时钟信号的波形会越来越差(毛刺,波动等)
- 全局时钟有专门的晶体管来提高时钟的驱动能力,而寄存器产生的时钟会随着寄存器的传递,驱动能力越来越弱。