🎉欢迎来到FPGA专栏~BCD计数器设计
一、效果演示
顶层模块中的BCD模块级联:
Verilog实现:
调用ip核实现:
当计数到12‘h999
时,产生一个进位输出:
二、BCD码基础知识
BCD 码中最常用的是 8421 码,其各个 bit 权值分别是 8d、4d、2d、1d;同理 5421 码各位的权依次为 5d、4d、2d、1d。5421 码特点是最高位连续 5 个 0 后连续 5 个 1,故其当计数器采用这种编码时,最高位可产生对称方波输出;余 3 码是在 8421 码上加 0011b 得出来的;格雷码的特点是任意两个相邻的代码只有一位二进制数不同,编码格式不唯一;余 3 循环码具有格雷码的特点并且编码的首尾可以连接来进行循环,这样可用反馈移位寄存器来实现,硬件实现简单。
在实际使用中如不特指 BCD 码格式,则均为 8421 码。通过以上介绍将十进制 895 转换为BCD 码就是 1000_1001_0101,同理若将 BCD 码 1001_0110_0100 转换为十进制数即为 964。
BCD 码的主要应用之一就是数码管,假设要将十进制数 158 显示,一般解决办法是把需要显示的十进制数的个、十、百、千位数等进行拆分,即把 158 拆分出 1、5、8,然后查出对应的数码管显示段码再送去给数码管连接的 IO 口。这个过程可以进行下面的运算:先进行除法运算 158/100 得出百位 1,再取余 158%100 = 58 后继续进行除法运算 58 / 10 得出十位5,再进行一次取余 158%10 ,得到个位 8。以上过程可以看出需要除法,但是由于除法运算是比较消耗计算时间导致整体需要的指令周期太久。但是如果先将其转换为 BCD 码,则可大幅度减少运算时间。
三、BCD计数器Verilog实现
我们使用Verilog HDL实现一个BCD计数器。
一个BCD计数模块结构如下:
其中,各端口信号的含义如下表所示:
端口信号 | I/O | 功能 |
---|---|---|
Clk | input | 50M系统时钟 |
Rst_n | input | 全局复位 |
Cin | input | 进位输入信号 |
Cout | output | 进位输出信号 |
q | output | 计数值输出 |
对于计数过程,最大计数值设定为4‘d9
:
//执行计数过程
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 4'd0;
else if(Cin == 1'b1)begin
if(cnt == 4'd9)
cnt <= 4'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= cnt;
end
📜需要注意的是,只有当计数值达到4’d9
,且Cin信号为高电平时,Cout才会产生一个高电平信号。
Verilog HDL代码:
module BCD_Counter(
input Clk, //系统时钟信号
input Rst_n, //系统复位信号
input Cin, //进位输入信号
output reg Cout, //进位输出信号
output [3:0] q //计数器值
);
reg [3:0]cnt;
//执行计数过程
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 4'd0;
else if(Cin == 1'b1)begin
if(cnt == 4'd9)
cnt <= 4'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= cnt;
end
//产生进位输出信号
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
Cout <= 1'b0;
else if(cnt == 4'd9 && Cin == 1'b1)
Cout <= 1'b1;
else
Cout <= 1'b0;
end
assign q = cnt;
endmodule
RTL视图:
在仿真测试过程中,为了便于观察,我们产生占空比为1:5
的Cin信号:
repeat(30)begin
Cin = 1'b1;
#(`clock_period*1);
Cin = 1'b0;
#(`clock_period*5);
end
测试激励文件:
`timescale 1ns/1ns
`define clock_period 20
module BCD_Counter_tb;
reg Clk;
reg Rst_n;
reg Cin;
wire Cout;
wire [3:0]q;
BCD_Counter BCD_Counter0(
.Clk(Clk), //系统时钟信号
.Rst_n(Rst_n), //系统复位信号
.Cin(Cin), //进位输入信号
.Cout(Cout), //进位输出信号
.q(q) //计数器值
);
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
Cin = 1'b0;
#(`clock_period*20);
Rst_n = 1;
#(`clock_period*20);
repeat(30)begin
Cin = 1'b1;
#(`clock_period*1);
Cin = 1'b0;
#(`clock_period*5);
end
#(`clock_period*20);
$stop;
end
endmodule
仿真结果:
四、级联BCD计数器实现
BCD计数器的级联RTL视图:
在上述设计过程中,一个BCD计数器的计数范围为:4'd0~4'd9
,即4'h0~4'h9
。
在此使用了三个BCD计数器进行级联,因此顶层输出的hex格式
数据范围为000~999
。
4.1 Verilog实现
在顶层文件中调用已编写好的BCD模块:
module BCD_Counter_top(
input Clk, //系统时钟信号
input Rst_n, //系统复位信号
input Cin, //进位输入信号
output Cout, //进位输出信号
output [11:0] q //计数器值
);
wire Cout0;
wire Cout1;
wire Cout2;
wire [3:0]q0;
wire [3:0]q1;
wire [3:0]q2;
BCD_Counter BCD_Counter0(
.Clk(Clk),
.Rst_n(Rst_n),
.Cin(Cin),
.Cout(Cout0),
.q(q0)
);
BCD_Counter BCD_Counter1(
.Clk(Clk),
.Rst_n(Rst_n),
.Cin(Cout0),
.Cout(Cout1),
.q(q1)
);
BCD_Counter BCD_Counter2(
.Clk(Clk),
.Rst_n(Rst_n),
.Cin(Cout1),
.Cout(Cout2),
.q(q2)
);
assign Cout = Cout2;
assign q = {q2,q1,q0};
endmodule
由于级联的BCD计数器计数范围较大,在编写测试激励文件的时候可以直接把Cin信号置为高电平:
`timescale 1ns/1ns
`define clock_period 20
module BCD_Counter_top_tb;
reg Clk;
reg Rst_n;
reg Cin;
wire Cout;
wire [11:0]q;
BCD_Counter_top BCD_Counter_top0(
.Clk(Clk), //系统时钟信号
.Rst_n(Rst_n), //系统复位信号
.Cin(Cin), //进位输入信号
.Cout(Cout), //进位输出信号
.q(q) //计数器值
);
initial Clk = 1;
always#(`clock_period) Clk = ~Clk;
initial begin
Rst_n = 0;
Cin = 1'b0;
#(`clock_period*10)
Rst_n = 1;
#(`clock_period*5)
Cin = 1'b1;
#(`clock_period*5000)
$stop;
end
endmodule
仿真结果如下图:
从仿真结果中可以看出来:Cout信号
延迟了三个系统时钟周期才出现,设计存在错误,不符合设计要求。
加入具体波形,发现BCD_Counter0
、BCD_Counter1
和BCD_Counter2
的输出已经出现系统时钟周期的延迟:
在基础的BCD_Counter模块中,Cout信号的产生使用了D触发器,所以造成了一拍的延迟。为了消除延迟效果,需要使用组合逻辑电路:
...
output Cout, //进位输出信号
...
//产生进位输出信号
assign Cout = (cnt == 4'd9 && Cin == 1'b1);
...
即:
module BCD_Counter(
input Clk, //系统时钟信号
input Rst_n, //系统复位信号
input Cin, //进位输入信号
output Cout, //进位输出信号
output [3:0] q //计数器值
);
reg [3:0]cnt;
//执行计数过程
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 4'd0;
else if(Cin == 1'b1)begin
if(cnt == 4'd9)
cnt <= 4'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= cnt;
end
//产生进位输出信号
assign Cout = (cnt == 4'd9 && Cin == 1'b1);
assign q = cnt;
endmodule
重新仿真,计数结果正确:
4.2 ip核实现
使用ip核创建计数器的过程:【FPGA零基础学习之旅#6】ip核基础知识之计数器。
首先使用ip核创建一个计数器,在此步骤勾选同步复位:
ip核生成代码如下:
// megafunction wizard: %LPM_COUNTER%
// GENERATION: STANDARD
// VERSION: WM1.0
// MODULE: LPM_COUNTER
// ============================================================
// File Name: Counter.v
// Megafunction Name(s):
// LPM_COUNTER
//
// Simulation Library Files(s):
// lpm
// ============================================================
// ************************************************************
// THIS IS A WIZARD-GENERATED FILE. DO NOT EDIT THIS FILE!
//
// 13.0.0 Build 156 04/24/2013 SJ Full Version
// ************************************************************
//Copyright (C) 1991-2013 Altera Corporation
//Your use of Altera Corporation's design tools, logic functions
//and other software and tools, and its AMPP partner logic
//functions, and any output files from any of the foregoing
//(including device programming or simulation files), and any
//associated documentation or information are expressly subject
//to the terms and conditions of the Altera Program License
//Subscription Agreement, Altera MegaCore Function License
//Agreement, or other applicable license agreement, including,
//without limitation, that your use is for the sole purpose of
//programming logic devices manufactured by Altera and sold by
//Altera or its authorized distributors. Please refer to the
//applicable agreement for further details.
// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module Counter (
cin,
clock,
sclr,
cout,
q);
input cin;
input clock;
input sclr;
output cout;
output [3:0] q;
wire sub_wire0;
wire [3:0] sub_wire1;
wire cout = sub_wire0;
wire [3:0] q = sub_wire1[3:0];
lpm_counter LPM_COUNTER_component (
.cin (cin),
.clock (clock),
.sclr (sclr),
.cout (sub_wire0),
.q (sub_wire1),
.aclr (1'b0),
.aload (1'b0),
.aset (1'b0),
.clk_en (1'b1),
.cnt_en (1'b1),
.data ({4{1'b0}}),
.eq (),
.sload (1'b0),
.sset (1'b0),
.updown (1'b1));
defparam
LPM_COUNTER_component.lpm_direction = "UP",
LPM_COUNTER_component.lpm_modulus = 10,
LPM_COUNTER_component.lpm_port_updown = "PORT_UNUSED",
LPM_COUNTER_component.lpm_type = "LPM_COUNTER",
LPM_COUNTER_component.lpm_width = 4;
endmodule
// ============================================================
// CNX file retrieval info
// ============================================================
// Retrieval info: PRIVATE: ACLR NUMERIC "0"
// Retrieval info: PRIVATE: ALOAD NUMERIC "0"
// Retrieval info: PRIVATE: ASET NUMERIC "0"
// Retrieval info: PRIVATE: ASET_ALL1 NUMERIC "1"
// Retrieval info: PRIVATE: CLK_EN NUMERIC "0"
// Retrieval info: PRIVATE: CNT_EN NUMERIC "0"
// Retrieval info: PRIVATE: CarryIn NUMERIC "1"
// Retrieval info: PRIVATE: CarryOut NUMERIC "1"
// Retrieval info: PRIVATE: Direction NUMERIC "0"
// Retrieval info: PRIVATE: INTENDED_DEVICE_FAMILY STRING "Cyclone IV E"
// Retrieval info: PRIVATE: ModulusCounter NUMERIC "1"
// Retrieval info: PRIVATE: ModulusValue NUMERIC "10"
// Retrieval info: PRIVATE: SCLR NUMERIC "1"
// Retrieval info: PRIVATE: SLOAD NUMERIC "0"
// Retrieval info: PRIVATE: SSET NUMERIC "0"
// Retrieval info: PRIVATE: SSET_ALL1 NUMERIC "1"
// Retrieval info: PRIVATE: SYNTH_WRAPPER_GEN_POSTFIX STRING "0"
// Retrieval info: PRIVATE: nBit NUMERIC "4"
// Retrieval info: PRIVATE: new_diagram STRING "1"
// Retrieval info: LIBRARY: lpm lpm.lpm_components.all
// Retrieval info: CONSTANT: LPM_DIRECTION STRING "UP"
// Retrieval info: CONSTANT: LPM_MODULUS NUMERIC "10"
// Retrieval info: CONSTANT: LPM_PORT_UPDOWN STRING "PORT_UNUSED"
// Retrieval info: CONSTANT: LPM_TYPE STRING "LPM_COUNTER"
// Retrieval info: CONSTANT: LPM_WIDTH NUMERIC "4"
// Retrieval info: USED_PORT: cin 0 0 0 0 INPUT NODEFVAL "cin"
// Retrieval info: USED_PORT: clock 0 0 0 0 INPUT NODEFVAL "clock"
// Retrieval info: USED_PORT: cout 0 0 0 0 OUTPUT NODEFVAL "cout"
// Retrieval info: USED_PORT: q 0 0 4 0 OUTPUT NODEFVAL "q[3..0]"
// Retrieval info: USED_PORT: sclr 0 0 0 0 INPUT NODEFVAL "sclr"
// Retrieval info: CONNECT: @cin 0 0 0 0 cin 0 0 0 0
// Retrieval info: CONNECT: @clock 0 0 0 0 clock 0 0 0 0
// Retrieval info: CONNECT: @sclr 0 0 0 0 sclr 0 0 0 0
// Retrieval info: CONNECT: cout 0 0 0 0 @cout 0 0 0 0
// Retrieval info: CONNECT: q 0 0 4 0 @q 0 0 4 0
// Retrieval info: GEN_FILE: TYPE_NORMAL Counter.v TRUE
// Retrieval info: GEN_FILE: TYPE_NORMAL Counter.inc FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL Counter.cmp FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL Counter.bsf FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL Counter_inst.v FALSE
// Retrieval info: GEN_FILE: TYPE_NORMAL Counter_bb.v TRUE
// Retrieval info: LIB_FILE: lpm
创建一个顶层文件,并在顶层中调用ip核实现级联:
module Counter_ip_top(
input Clk,
input Rst_n,
input Cin,
output Cout,
output [11:0] q
);
wire Cout0;
wire Cout1;
wire Cout2;
wire [3:0]q0;
wire [3:0]q1;
wire [3:0]q2;
Counter Counter0(
.cin(Cin),
.clock(Clk),
.sclr(Rst_n),
.cout(Cout0),
.q(q0)
);
Counter Counter1(
.cin(Cout0),
.clock(Clk),
.sclr(Rst_n),
.cout(Cout1),
.q(q1)
);
Counter Counter2(
.cin(Cout1),
.clock(Clk),
.sclr(Rst_n),
.cout(Cout2),
.q(q2)
);
assign Cout = Cout2;
assign q = {q2,q1,q0};
endmodule
测试激励文件:
`timescale 1ns/1ns
`define clock_period 20
module Counter_ip_top_tb;
reg Clk;
reg Rst_n;
reg Cin;
wire Cout;
wire [11:0]q;
Counter_ip_top ounter_ip_top(
.Clk(Clk),
.Rst_n(Rst_n),
.Cin(Cin),
.Cout(Cout),
.q(q)
);
initial Clk = 1;
always#(`clock_period) Clk = ~Clk;
initial begin
Rst_n = 0;
Cin = 1'b0;
#(`clock_period*10)
Rst_n = 1;
#(`clock_period*5)
Cin = 1'b1;
#(`clock_period*5000)
$stop;
end
endmodule
运行仿真,看到的结果如下:
Cout
和q
没有任何变化,说明刚才由ip核生成的计数器的同步复位信号是高电平触发,即高电平触发系统复位。
在一般的系统设计中,我们习惯于低电平触发系统复位,我们在顶层模块中稍加修改:
//使系统复位信号翻转以匹配ip核Counter的复位
wire Rst_n0;
assign Rst_n0 = ~Rst_n;
即:
module Counter_ip_top(
input Clk,
input Rst_n,
input Cin,
output Cout,
output [11:0] q
);
wire Cout0;
wire Cout1;
wire Cout2;
wire [3:0]q0;
wire [3:0]q1;
wire [3:0]q2;
wire Rst_n0;
assign Rst_n0 = ~Rst_n;
Counter Counter0(
.cin(Cin),
.clock(Clk),
.sclr(Rst_n0),
.cout(Cout0),
.q(q0)
);
Counter Counter1(
.cin(Cout0),
.clock(Clk),
.sclr(Rst_n0),
.cout(Cout1),
.q(q1)
);
Counter Counter2(
.cin(Cout1),
.clock(Clk),
.sclr(Rst_n0),
.cout(Cout2),
.q(q2)
);
assign Cout = Cout2;
assign q = {q2,q1,q0};
endmodule
仿真结果符合设计预期:
RTL视图:
🧸结尾