七种计数器总结(格雷码计数器、环形计数器、约翰逊计数器、FLSR、简易时分秒数字秒表|verilog代码|Testbench|仿真结果)

七种计数器总结(格雷码计数器、环形计数器、约翰逊计数器、FLSR、简易时分秒数字秒表|verilog代码|Testbench|仿真结果)

七种计数器总结

一、可复位/置数计数器1.1 可复位/置数计数器1.2 Verilog代码1.3 Testbench1.4 仿真结果

二、双向(可加可减)计数器2.1 双向(可加可减)计数器2.2 Verilog代码2.3 Testbench2.4 仿真结果

三、格雷码计数器3.1 格雷码计数器3.2 Verilog代码3.3 Testbench3.4 仿真结果

四、环形计数器和扭环形计数器4.1 环形计数器和扭环形计数器4.2 Verilog代码4.3 Testbench4.4 仿真结果

五、线性反馈移位寄存器LFSR计数器5.1 斐波那契LFSR5.1.1 斐波那契LFSR5.1.2 verilog代码5.1.3 Testbench5.1.4 仿真结果

5.2 伽罗瓦LFSR5.2.1 伽罗瓦LFSR5.2.2 verilog代码5.2.3 Testbench5.2.4 仿真结果

六、简易时分秒数字秒表6.1 简易时分秒数字秒表6.2Verilog代码6.3Testbench6.4 仿真结果

七、总结

数字IC经典电路设计

经典电路设计是数字IC设计里基础中的基础,盖大房子的第一部是打造结实可靠的地基,每一篇笔者都会分门别类给出设计原理、设计方法、verilog代码、Testbench、仿真波形。然而实际的数字IC设计过程中考虑的问题远多于此,通过本系列希望大家对数字IC中一些经典电路的设计有初步入门了解。能力有限,纰漏难免,欢迎大家交流指正。快速导航链接如下:

个人主页链接

1.数字分频器设计

2.序列检测器设计

3.序列发生器设计

4.序列模三检测器设计

5.奇偶校验器设计

6.自然二进制数与格雷码转换

7.线性反馈移位寄存器LFSR

8.四类九种移位寄存器总结

9.串并转换

一、可复位/置数计数器

1.1 可复位/置数计数器

根据要求可以知道有四个输入和两个输出,主要包括累加计数、置位、指示信号,此题较简单,分开设计即可。

模块的接口信号图如下:

1.2 Verilog代码

要求:设计一个位宽为4的带复位端和置位端的计数器,并且计数器输出信号递增每次到达0,指示信号zero拔高至“1”,当置位信号set 有效时,将当前输出置为输入的数值set_num。

//可复位可置位的简易计数器

module count_bin01(

input clk,

input rst_n,

input set,

input [3:0] set_num,

output reg [3:0]number,

output reg zero

);

reg [3:0] cnt;

always@(posedge clk or negedge rst_n)begin

if(!rst_n) begin

cnt <= 0;

end

else if(set) begin //置数

cnt <= set_num;

end

else begin

cnt <= (cnt==15)?0:cnt+1; //累加计数,到顶清零

end

end

//计数圈数的指示信号

always@(posedge clk or negedge rst_n)begin

if(!rst_n) begin

zero <= 0;

end

else begin

zero <= (cnt==0)?1:0;

end

end

//时序逻辑打一拍子输出

always@(posedge clk or negedge rst_n)begin

if(!rst_n) begin

number <= 0;

end

else begin

number <= cnt;

end

end

endmodule

1.3 Testbench

`timescale 1ns/1ps;////仿真时间单位1ns 仿真时间精度1ps

module count_bin01_tb();

//信号申明

reg clk;

reg rst_n;

reg set;

reg [3:0] set_num;

wire [3:0] number;

wire zero;

//模块实例化(将申明的信号连接起来即可)

count_bin01 u_count_bin01(

.clk (clk),

.rst_n (rst_n),

.set (set),

.set_num (set_num),

.number (number),

.zero (zero)

);

//生成时钟信号

always #5 clk =~ clk;

//为输入数据赋值

initial begin

clk = 0;

set = 0;

set_num = 4'b1010;

#5 rst_n = 1;

#5 rst_n =0;

#5 rst_n = 1;

#400 set = 1;

#100 set = 0;

#1000;

$finish;

end

endmodule

1.4 仿真结果

二、双向(可加可减)计数器

2.1 双向(可加可减)计数器

一个十进制计数器模块,当mode信号为1,计数器输出信号递增,当mode信号为0,计数器输出信号递减。每次到达0,给出指示信号zero。此题较简单,分开设计即可。

模块的接口信号图如下:

2.2 Verilog代码

要求:设计一个双向计数器,分别实现从0 ~ 9加法计数和9 ~ 0减法计数,并且计数器输出信号每次到达0,指示信号zero拔高至“1”。

//设计双向(可加可减)计数器

module count_bin02(

input clk,

input rst_n,

input mode,

output [3:0]number,

output reg zero

);

//双向计数模块

reg[3:0] number_r; //定义中间寄存器

always@(posedge clk or negedge rst_n)begin

if(!rst_n) begin

number_r<=0;

end

else if(mode)begin//选择加计数器

number_r<=(number_r==9) ? 0 : number_r + 1; //累加计数,到顶清零

end

else if(~mode) begin //选择减计数器

number_r<=(number_r==0) ? 9 : number_r - 1; //累加计数,到底置9

end

else begin

number_r<=number_r;

end

end

assign number =number_r; //组合逻辑输出

//计数圈数的指示信号

always@(posedge clk or negedge rst_n)begin

if(!rst_n) begin

zero<=0;

end

else if(number==0) begin

zero<=1;

end

else begin

zero<=0;

end

end

endmodule

2.3 Testbench

`timescale 1ns/1ps; //仿真时间单位1ns 仿真时间精度1ps

module count_bin02_tb();

//信号申明

reg clk;

reg rst_n;

reg mode;

wire [3:0] number;

wire zero;

//模块实例化(将申明的信号连接起来即可)

count_bin02 u_count_bin02(

.clk (clk),

.rst_n (rst_n),

.mode (mode),

.number (number),

.zero (zero)

);

always #5 clk =~ clk; //生成时钟信号

//为输入数据赋值

initial begin

clk = 0;

mode = 1;

#5 rst_n = 1;

#5 rst_n = 0;

#5 rst_n = 1;

#150 mode = 0;

#1000;

$finish;

end

endmodule

2.4 仿真结果

三、格雷码计数器

3.1 格雷码计数器

此处详细可以查看上篇自然二进制数与格雷码转换。

什么是格雷码?

格雷码(Gray code)是一种二进制数码系统,格雷码的特点是从一个数变为相邻的一个数时,只有一个数据位发生跳变,由于这种特点,就可以避免二进制编码计数组合电路中出现的亚稳态。在某些应用中,格雷码具有排除歧义和减少数据传输错误的功能。四位格雷码和自然二进制数关系如下图所示:

自然二进制如何转换成格雷码?

从自然二进制码到格雷码的转换具体方法是:从二进制的最低位起,依次起与相邻左边的一位数进行异或逻辑运算,并且作为对应格雷码该位的值,最高位保持不变。简而言之就是,将二进制码与逻辑右移的二进制码进行异或可得到格雷码。具体原理图如下所示:

格雷码有哪些优点和应用场合?

避免计数器状态的冗余转换,在格雷码中,两个连续的数值仅仅只有一位不同,而在二进制码中两个连续的数值可能会有多位不同,这会导致在计数器发生器中产生大量的冗余状态转换。格雷码可以通过降低状态转换次数来设计出更简单的计数器。

降低传输干扰和误差。在数据传输过程中,如果使用二进制码,由于两个相邻的数值可能会有多位不同,数据在传输过程中可能会因为电磁干扰等原因而发生错误。而使用格雷码则能够避免这种情况,因为任何相邻的两个数值之间只有一位不同。格雷码广泛应用在FIFO、跨时钟域的通信(CDC)、RAM地址寻址计数器、数据纠错等电路设计中。

格雷码的特点决定了它适用于数据传输,比如在异步时钟域之间传递计数结果而用到的计数器。常见的异步FIFO空满信号的信号就是用格雷码进行比较的(因为格雷码计数器计数时相邻的数之间只有一个bit发生了变化,例如:000-001-011-010-110-111-101-100)。 也常用在状态机的状态编码。 而由于格雷码是一种变权码,每一位码没有固定的大小,很难直接进行比较大小和算术运算,因此在实际的数据运算中并不使用格雷码,如异步FIFO中读写地址仍然是使用二进制编码。

格雷码计数可以用三种方式实现

一种是状态机,但是如果计数器的位数很大,比如6位,就得用至少64个状态,非常麻烦。一种是设计一个二进制计数器,通过它来计数,然后利用binary-gray的编码就可以得到对应的格雷码计数器。还有一种方法,不是很常用,也很复杂,是通过组合逻辑直接产生格雷码的,而不需要先产生二进制,再进行转换。但是较为复杂,应用场合没前两者多。

本文采用简单易操作的的第二种方法。二进制码转格雷码的基本思路:从最右边一位起,依次将每一位与左边一位异或(XOR),作为对应格雷码该位的值,最左边一位不变。详情可以查看自然二进制数与格雷码转换。

模块的接口信号图如下:

3.2 Verilog代码

要求:实现4bit位宽的格雷码计数器。

//设计四位格雷码计数器

module counter_gray(

input clk,

input rst_n,

output reg [3:0] cnt_gray,

output reg [3:0] cnt_bin

);

//自然二进制计数器

reg [3:0] cnt_bin_r;

always@(posedge clk or negedge rst_n) begin

if(!rst_n) begin

cnt_bin_r <= 0;

end

else begin

cnt_bin_r <= cnt_bin_r + 1'b1;

end

end

//自然二进制码转格雷码

wire [3:0] cnt_gray_r;

assign cnt_gray_r = (cnt_bin_r >> 1) ^ cnt_bin_r;

//时序逻辑打一拍子输出

always@(posedge clk or negedge rst_n) begin

if(!rst_n) begin

cnt_gray <= 0;

cnt_bin <= 0;

end

else begin

cnt_gray <= cnt_gray_r;

cnt_bin <= cnt_bin_r;

end

end

endmodule

3.3 Testbench

`timescale 1ns/1ps;//仿真时间单位1ns 仿真时间精度1ps

module counter_gray_tb();

//信号申明

reg clk;

reg rst_n;

wire [3:0] cnt_gray;

wire [3:0] cnt_bin;

//模块实例化(将申明的信号连接起来即可)

counter_gray u_counter_gray(

.clk (clk),

.rst_n (rst_n),

.cnt_gray (cnt_gray),

.cnt_bin (cnt_bin)

);

always #5 clk =~ clk;//生成时钟信号

//为输入数据赋值

initial begin

clk = 0;

#5 rst_n = 1;

#5 rst_n = 0;

#5 rst_n = 1;

#1000;

$finish;

end

endmodule

3.4 仿真结果

四、环形计数器和扭环形计数器

4.1 环形计数器和扭环形计数器

什么是环形计数器?

环形计数器是基于移位寄存器的计数器,对于n个移位寄存器构成的计数器,只有n个有效状态。设置一个初始状态,通过移位即可进行循环。如下图所示为环形计数器的电路结构以及循环的有效和无效编码。

Tips:实际中,因为该计数器有

2

n

n

2^n-n

2n−n 个无效状态,因此存在自锁的问题,这可以通过设计可以自启动(自动从无效状态转移到有效状态,进入有效循环)的电路来解决。自启动的设计可通过修改状态逻辑实现,本质是改变无效状态的次态,使其为有效状态。

什么是独热码?

所谓的独热码是指对任意给定的状态,状态向量中只有1位为1,其余位都是为0。独热码经常用在状态机的状态编码中。n状态的状态机需要n个触发器。当状态机的状态增加时,如果使用二进制编码,那么状态机速度会明显下降,且由于翻转的寄存器较多容易出编码错误。而采用独热码,虽然多用了触发器,但由于状态译码简单,节省和简化了组合逻辑电路。独热编码还具有设计简单、修改灵活、易于综合和调试等优点。对于寄存器数量多、而门逻辑相对缺乏的FPGA器件,采用独热编码可以有效提高电路的速度和可靠性,也有利于提高器件资源的利用率。独热编码有很多无效状态,应该确保状态机一旦进入无效状态时,可以立即跳转到确定的已知状态。通过独热码可是实现简单的有限状态机。

one-hot(独热码)计数器与环形移位计数器实际上相同

独热码只有一位为1,也就是下面的环形计数器产生的计数序列。如4bit one-hot计数器的计术序列即为:0001-0010-0100-1000循环。

这种计数器的优点是速度快,且每次只有两个bit发生跳变,而且不需外加译码电路,可以直接以各个触发器输出端的1状态表示计数。主要缺点是没有有效利用电路的状态,对于 n bit,有

2

n

n

2^n-n

2n−n 个状态没有利用。

应用:在状态机的状态编码时,经常用到。实际上,大多情况下这种独热码计数器不被称作计数器,而是状态编码的一种。

什么是扭环形计数器?

扭环形计数器又称约翰逊计数器,是基于移位寄存器的计数器,是对环形计数器的改进,对于n个移位寄存器构成的计数器,有 2n 个有效状态。如下图所示为扭环形计数器的电路结构以及循环的有效和无效编码。

Tips:与环形计数器类似,实际中,因为该计数器有

2

n

2

n

2^n-2n

2n−2n 个无效状态,因此存在自锁的问题,这可以通过设计可以自启动(自动从无效状态转移到有效状态,进入有效循环)的电路来解决。自启动的设计可通过修改状态逻辑实现,本质是改变无效状态的次态,使其为有效状态。

扭环形计数器仍然有很多状态是无效的,一旦计数器进入这些状态就会陷入死循环,无法正常工作。计数器的初始状态必须位于有效循环的几种状态之中才能启动。通过添加门电路可以拆掉无效循环,也就是可以自启动的扭环形计数器。

设置一个初始状态,将最高位取反,作为最低位的输入,通过移位即可得到。下面的代码仅仅是简单的实现,模拟环形计数器和扭环形计数器的工作方式,并没有过多的考虑自启动的问题。

4.2 Verilog代码

要求:实现4bit位宽的计数器,可实现环形计数器独热码输出和扭环形计数器(约翰逊计数器)输出。

//设计环形计数器和扭环形计数器

module counter_circle #(

parameter WIDTH = 4 //定义数据位宽

)(

input clk,

input rst_n,

output [WIDTH - 1 : 0] counter_circle,

output [WIDTH - 1 : 0] counter_john

);

//环形计数器(独热码计数器)模块

reg [WIDTH - 1 : 0] counter_circle_r; //中间寄存器

always@(posedge clk or negedge rst_n) begin

if(!rst_n) begin

counter_circle_r <= 4'b0001;

end

else begin

counter_circle_r <= {counter_circle_r[0],counter_circle_r[WIDTH - 1 : 1]};

end

end

//扭环形计数器(约翰逊计数器)模块

reg [WIDTH - 1 : 0] counter_john_r; //中间寄存器

always@(posedge clk or negedge rst_n) begin

if(!rst_n) begin

counter_john_r <= 4'b0000;

end

else begin

counter_john_r <= {~counter_john_r[0],counter_john_r[WIDTH - 1 : 1]};

end

end

//组合逻辑输出

assign counter_circle = counter_circle_r;

assign counter_john = counter_john_r;

endmodule

4.3 Testbench

`timescale 1ns/1ps //仿真时间单位1ns 仿真时间精度1ps

module counter_circle_tb #(

parameter WIDTH = 4 //定义数据位宽

);

//信号申明

reg clk;

reg rst_n;

wire [WIDTH - 1 : 0] counter_circle;

wire [WIDTH - 1 : 0] counter_john;

//模块实例化(将申明的信号连接起来即可)

counter_circle u_counter_circle(

.clk (clk),

.rst_n (rst_n),

.counter_circle (counter_circle),

.counter_john (counter_john)

);

always #5 clk = ~clk; //生成时钟信号

//为输入数据赋值

initial begin

clk = 0;

rst_n = 1;

#5 rst_n = 0;

#5 rst_n = 1;

#100 $finsh;

end

endmodule

4.4 仿真结果

五、线性反馈移位寄存器LFSR计数器

此处更详细可以参考线性反馈移位寄存器LFSR(斐波那契LFSR(多到一型)和伽罗瓦LFSR(一到多型)|verilog代码|Testbench|仿真结果)。

5.1 斐波那契LFSR

5.1.1 斐波那契LFSR

斐波那契LFSR为多到一型LFSR,即多个触发器的输出经过异或逻辑来驱动一个触发器的输入。反馈多项式为

f

(

x

)

=

x

3

+

x

2

+

1

f(x)=x^3 + x^2 +1

f(x)=x3+x2+1 ,即

x

1

x_1

x1​ 的输入为

x

3

x_3

x3​ 和

x

2

x_2

x2​ 的输出异或后的结果,电路图如下所示:

输出序列的顺序为:111-110-100-001-010-101-011-111

5.1.2 verilog代码

//三级斐波那契LFSR设计

//反馈多项式为 f(x)=x^3 + x^2 +1

module lfsr_fibonacci(

input clk,

input rst_n,

output reg [2:0] q

);

//时序逻辑LFSR移位模块

always @(posedge clk or rst_n) begin

if (!rst_n) begin

q <= 3'b111; //种子值为111

end

else begin

q <= {q[1],q[0],q[1]^q[2]}; //根据三级斐波那契LFSR电路拼接输出

end

end

endmodule

5.1.3 Testbench

`timescale 1ns/1ps //仿真时间单位1ns 仿真时间精度1ps

module lfsr_fibonacci_tb();

//信号申明

reg clk;

reg rst_n;

wire [2:0] q;

//模块实例化(将申明的信号连接起来即可)

lfsr_fibonacci u_lfsr_fibonacci(

.clk (clk),

.rst_n (rst_n),

.q (q)

);

always #5 clk = ~clk; //生成时钟信号

//为输入数据赋值

initial begin

clk = 1;

rst_n = 1;

#5 rst_n = 0;

#5 rst_n = 1;

#1000

$stop;

end

endmodule

5.1.4 仿真结果

5.2 伽罗瓦LFSR

5.2.1 伽罗瓦LFSR

伽罗瓦LFSR为一到多型LFSR,即一个触发器的输出经过异或逻辑来驱动多个触发器的输入。对于同样的反馈多项式

x

3

+

x

2

+

1

x^3+x^2+1

x3+x2+1 而言:触发器

x

1

x_1

x1​ 的输入通常来源于触发器

x

2

x_2

x2​ 的输出,

x

3

x_3

x3​ (最高项)的输入通常来自于

x

1

x_1

x1​ 的输出,此多项式中剩余触发器的输入是

x

1

x_1

x1​ 的输出与前级输出异或的结果,

x

2

x_2

x2​ 的输入由

x

1

x_1

x1​ 的输出与

x

3

x_3

x3​ 的输出通过异或运算得到。其电路图如下所示:

输出序列的顺序为:111-101-100-010-001-110-011-111

5.2.2 verilog代码

//三级伽罗瓦LFSR设计

//反馈多项式为 f(x)=x^3 + x^2 +1

module lfsr_galois(

input clk,

input rst_n,

output reg [2:0] q

);

//时序逻辑LFSR移位模块

always @(posedge clk or rst_n) begin

if (!rst_n) begin

q <= 3'b111; //种子值为111

end

else begin

q <= {q[0],q[2]^q[0],q[1]}; //根据三级伽罗瓦LFSR电路拼接输出

end

end

endmodule

5.2.3 Testbench

`timescale 1ns/1ps //仿真时间单位1ns 仿真时间精度1ps

module lfsr_galois_tb();

//信号申明

reg clk;

reg rst_n;

wire [2:0] q;

//模块实例化(将申明的信号连接起来即可)

lfsr_galois u_lfsr_galois(

.clk (clk),

.rst_n (rst_n),

.q (q)

);

always #5 clk = ~clk; //时钟信号生成

//为输入数据赋值

initial begin

clk = 1;

rst_n = 1;

#5 rst_n = 0;

#5 rst_n = 1;

#1000

$stop;

end

endmodule

5.2.4 仿真结果

六、简易时分秒数字秒表

6.1 简易时分秒数字秒表

其实本质还是二进制计数器,只不过判断逻辑稍微多一些。首先是秒:复位后可以开始计数,当计数器到达最大值即59后清零;其次是分:复位后且只能在秒到达最大值后才能计数,当计数器到达最大值即59后清零。最后是时,复位后且只能在分到达最大值后才能计数,当计数器到达最大值即23后清零。

6.2Verilog代码

要求:实现一个时分秒的简易秒表。

//实现时分秒简易数字秒表

module count_calendar(

input clk,

input rst_n,

output [4:0] hour,

output [5:0] minute,

output [5:0] second

);

//时分秒最大计数

parameter SECOND_MAX = 59;

parameter MINUTE_MAX = 59;

parameter HOUR_MAX = 23;

//秒计数器

reg [5:0] second_r;

always@(posedge clk or negedge rst_n) begin

if (!rst_n) begin

second_r <= 6'd0;

end

else if (second_r == SECOND_MAX) begin

second_r <= 6'd0;

end

else begin

second_r <= second_r + 1'b1;

end

end

//分计数器

reg [5:0] minute_r;

always@(posedge clk or negedge rst_n) begin

if (!rst_n) begin

minute_r <= 6'd0;

end

else if ( (minute_r == MINUTE_MAX) && (second_r == SECOND_MAX) ) begin

minute_r <= 6'd0;

end

else if ( second_r >= SECOND_MAX) begin

minute_r <= minute_r + 1'b1;

end

else begin

minute_r <= minute_r;

end

end

//时计数器

reg [5:0] hour_r;

always@(posedge clk or negedge rst_n) begin

if (!rst_n) begin

hour_r <= 5'd0;

end

else if ( (hour_r == HOUR_MAX) && (minute_r == MINUTE_MAX) && (second_r == SECOND_MAX) ) begin

hour_r <= 5'd0;

end

else if ( (minute_r == MINUTE_MAX) && (second_r == SECOND_MAX) ) begin

hour_r <= hour_r + 1'b1;

end

else begin

hour_r <= hour_r;

end

end

//组合逻辑输出

assign hour = hour_r;

assign minute = minute_r;

assign second = second_r;

endmodule

6.3Testbench

`timescale 1ns/1ps; //仿真时间单位1ns 仿真时间精度1ps

module count_calendar_tb();

//信号申明

reg clk;

reg rst_n;

wire [4:0] hour;

wire [5:0] minute;

wire [5:0] second;

//模块实例化(将申明的信号连接起来即可)

count_calendar u_count_calendar(

.clk (clk),

.rst_n (rst_n),

.hour (hour),

.minute (minute),

.second (second)

);

//调用系统命令——监视

initial begin

$monitor ("hour = %b, minute = %b, second = %b",

hour, minute, second);

end

always #5 clk =~ clk;//生成时钟信号

//为输入数据赋值

initial begin

clk = 0;

#5;

rst_n = 1;

#5;

rst_n=0;

#5;

rst_n = 1;

#100000;

$finish;

end

endmodule

6.4 仿真结果

七、总结

可以将计数器大致分为这几类:

普通二进制计数器:大致包括可复位/置数计数器、双向(可加可减)计数器等,这类计数器代码逻辑简单,直观易懂。格雷码计数器:格雷码因翻转的次数少,因此具有减少数据传输错误的作用。格雷码广泛应用在FIFO、跨时钟域的通信(CDC)、RAM地址寻址计数器、数据纠错等电路设计中。产生格雷码有状态机法、自然二进制转格雷码、组合逻辑产生这三种方法。移位寄存器类计数器:包括环形计数器、扭环形计数器(又称约翰逊计数器)、LFSR计数器,三种都可归结于由寄存器与一个反馈回路组成,只不过对于环形计数器,没有反馈回路;对于扭环形计数器,反馈回路只是将最低位取反,作为最高位的输入;而LFSR的反馈回路比较复杂,对于不同的位数,由不同的生成多项式指定。

一定程度上说,生成可循环序列的过程就是计数的过程!

更多可查看个人主页链接 软件版本:Modelsim 10.6c 不定期纠错补充,欢迎随时交流 最后修改日期:2023.6.2

相关文章

梦特娇的包怎么样,梦特娇女包怎么样?整理多少
365bet体育足球比分

梦特娇的包怎么样,梦特娇女包怎么样?整理多少

🕒 06-28 👁️ 2120
时空猎人用什么登录
beat365官方入口素描网

时空猎人用什么登录

🕒 07-22 👁️ 7235
早 餐,午 餐,晚 餐 用英语怎么说 , 早餐,午餐,晚餐有哪些单词呢?