常用计数器的verilog实现(binary、gray、one-hot、LFSR、环形、扭环形)

时间:2024-03-10 18:56:37

2013-06-15 22:11:35

常用计数器的verilog实现(binary、gray、one-hot、LFSR、环形、扭环形)

代码测试功能正确,时间有限,错误难免;如有错误,欢迎指正。


 binary(二进制)计数器

很简单,可根据需要完成同步或异步复位、置数、使能的功能。

在ISE的language template中有各种计数器,可进行参考。下面给出一个带有同步复位、使能、置数端的计数器。

8bit二进制计数器的代码:

 1 module binary_counter(
 2                             rst_n,
 3                             clk,
 4                             en,
 5                             load,
 6                             cnt_load,
 7                             cnt
 8                                  );
 9                                  
10 parameter CNT_SIZE = 8;
11 
12 input rst_n;
13 input clk;
14 input en;
15 input load;
16 input [CNT_SIZE - 1 : 0] cnt_load;
17 
18 output [CNT_SIZE - 1 : 0] cnt;
19 reg [CNT_SIZE - 1 : 0] cnt;
20 
21 //在ISE的language template中有各种计数器,可进行参考
22 //下面给出一个带有同步复位、使能、置数端的计数器
23 
24 always@(posedge clk)
25     if(!rst_n)
26         cnt <= 8\'d0;
27     else if(en)
28         if(load)
29             cnt <= cnt_load;
30         else 
31             cnt <= cnt + 1;
32 
33 endmodule

  gray(格雷码)计数器

优点、应用场合
  • 格雷码的特点决定了它适用于数据传输,比如在异步时钟域之间传递计数结果而用到的计数器。常见的异步FIFO空满信号的信号就是用格雷码进行比较的(因为格雷码计数器计数时相邻的数之间只有一个bit发生了变化,例如:000-001-011-010-110-111-101-100
  • 也常用在状态机的状态编码
  • 而由于格雷码是一种变权码,每一位码没有固定的大小,很难直接进行比较大小和算术运算,因此在实际的数据运算中并不使用格雷码,如异步FIFO中读写地址仍然是使用二进制编码
简介
Gray Code是由贝尔实验室的Frank Gray在20世纪40年代提出的(是1880年由法国工程师Jean-Maurice-EmlleBaudot发明的),用来在使用PCM(Pusle Code Modulation)方法传送讯号时避免出错,并于1953年3月17日取得美国专利。
格雷码是一种绝对编码方式,典型格雷码是一种具有反射特性和循环特性的单步自补码,它的循环、单步特性消除了随机取数时出现重大误差的可能,它的反射、自补特性使得求反非常方便格雷码属于可靠性编码,是一种错误最小化的编码方式,因为,虽然自然二进制码可以直接由数/模转换器转换成模拟信号,但在某些情况,例如从十进制的3转换为4时二进制码的每一位都要变,能使数字电路产生很大的尖峰电流脉冲。而格雷码则没有这一缺点,它在相邻位间转换时,只有一位产生变化。它大大地减少了由一个状态到下一个状态时逻辑的混淆。由于这种编码相邻的两个码组之间只有一位不同,因而在用于风向的转角位移量-数字量的转换中,当风向的转角位移量发生微小变化(而可能引起数字量发生变化时,格雷码仅改变一位,这样与其它编码同时改变两位或多位的情况相比更为可靠,即可减少出错的可能性。

 与普通二进制码之间的转换

一般的,普通二进制码与格雷码可以按以下方法互相转换(如果觉得不好记,会混淆,实际应用时,可举例验证):
二进制码->格雷码(编码):从最右边一位起,依次将每一位与左边一位异或(XOR),作为对应格雷码该位的值,最左边一位不变(相当于左边是0);
即gray_cnt = (bin_cnt>>1) ^ bin_cnt;
格雷码-〉二进制码(解码):从左边第二位起,将每位与左边一位解码后的值异或,作为该位解码后的值(最左边一位依然不变)。
bin_cnt[7] = gray_cnt[7];
bin_cnt[6] = gray_cnt[6]^bin_cnt[7]=gray_cnt[6]^gray_cnt[7];
bin_cnt[5] = gray_cnt[5]^bin_cnt[6]=gray_cnt[5]^gray_cnt[6]^gray_cnt[7];
bin_cnt[4] = gray_cnt[4]^bin_cnt[5]=gray_cnt[4]^gray_cnt[5]^gray_cnt[6]^gray_cnt[7];
bin_cnt[3] = gray_cnt[3]^bin_cnt[2]=gray_cnt[3]^gray_cnt[4]^gray_cnt[5]^gray_cnt[6]^gray_cnt[7];
bin_cnt[2] = gray_cnt[2]^bin_cnt[3]=gray_cnt[2]^gray_cnt[3]^gray_cnt[4]^gray_cnt[5]^gray_cnt[6]^gray_cnt[7];
bin_cnt[1] = gray_cnt[1]^bin_cnt[2]=gray_cnt[1]^gray_cnt[2]^gray_cnt[3]^gray_cnt[4]^gray_cnt[5]^gray_cnt[6]^gray_cnt[7];
bin_cnt[0] = gray_cnt[0]^bin_cnt[1]=gray_cnt[0]^gray_cnt[1]^gray_cnt[2]^gray_cnt[3]^gray_cnt[4]^gray_cnt[5]^gray_cnt[6]^gray_cnt[7];
verilog实现:
格雷码计数可以用两种方式实现,一种是状态机,但是如果计数器的位数很大,比如6位,就得用至少64个状态,非常麻烦,另外一种方法是设计一个二进制计数器,通过它来计数,然后利用binary-gray的编码就可以得到对应的格雷码计数器。
还有一种方法,不是很常用,也很复杂,是通过组合逻辑直接产生格雷码的,而不需要先产生二进制,再进行转换。个人感觉没什么优点,具体参见文章:“多位格雷码计数器的VerilogHDL描述方法”。

 8bit格雷码计数器的代码:

 1 module gray_counter(
 2                             rst_n,
 3                             clk,
 4                             bin_cnt,  //输出二进制,可用于同步时钟域的计算、比较等
 5                             gray_cnt     //输出格雷码,可用于异步传输
 6                                  );
 7 
 8 parameter CNT_SIZE = 8;
 9 
10 input rst_n;
11 input clk;
12 
13 output [CNT_SIZE - 1 : 0] bin_cnt;
14 output [CNT_SIZE - 1 : 0] gray_cnt;
15 
16 reg [CNT_SIZE - 1 : 0] bin_cnt_tmp;
17 wire [CNT_SIZE - 1 : 0] gray_cnt_tmp;
18 
19 reg [CNT_SIZE - 1 : 0] bin_cnt;
20 reg [CNT_SIZE - 1 : 0] gray_cnt;
21 
22 //二进制转换为格雷码
23 assign gray_cnt_tmp = (bin_cnt_tmp>>1) ^ bin_cnt_tmp;
24 
25 //二进制计数
26 always@(posedge clk)
27     if(!rst_n)
28         begin
29             bin_cnt_tmp <= 8\'d0;
30         end    
31     else 
32         bin_cnt_tmp <= bin_cnt_tmp + 1;
33 
34 //输出打一拍    
35 always@(posedge clk)
36     if(!rst_n)
37         begin
38             bin_cnt <= 8\'d0;
39             gray_cnt <= 8\'d0;
40         end    
41     else 
42         begin
43             bin_cnt <= bin_cnt_tmp;    
44             gray_cnt <= gray_cnt_tmp;                
45         end
46          
47 endmodule

 


 one-hot(独热码)计数器

简介

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

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

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

主要缺点是没有有效利用电路的状态,对于nbit,有2^n-n个状态没有利用。

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

 one-hot(独热码)计数器与环形移位计数器相同,见下面环形计数器代码。 


基于移位寄存器的计数器

移位寄存器为何可用作计数器?

通过移位寄存器可以产生不同状态,在时钟下,电路状态循环变化,用电路的不同状态能够表示输入时钟的数目,从而作为时钟脉冲的计数器。但这种计数器有一个缺点,就是计数序列不是通常的递增或递减,常用作伪随机数发生器。

主要包括LFSR计数器、环形计数器、扭环形计数器(又称约翰逊计数器)三种。

三种都可归结于由寄存器与一个反馈回路组成,只不过对于环形计数器,没有反馈回路;对于扭环形计数器,反馈回路只是将最高位取反,作为最低位的输入;而LFSR的反馈回路比较复杂,对于不同的位数,由不同的生成多项式指定。


 LFSR(线性反馈移位寄存器,又称为伪随机序列发生器)

在通信领域lfsr 有着很广泛的应用,比如说M序列,扰码,信道编码,密码学这方面都有很广泛的应用,而不仅仅用于计数器。

 LFSR Applications (LFSR应用)
• Pattern Generators
• Counters
• Built-in Self-Test (BIST)
• Encryption
• Compression
• Checksums
• Pseudo-Random Bit Sequences (PRBS)

注意到 LFSR总是将 0状态转化成0状态,  因此对于一个n级LFSR, 最多可输出周期为2^(n− 1)的周期序列.

An address counter supplies sequential addresses, but there is no need for a conventional binary address sequence. Any repetitive pattern is
acceptable, and a linear feedback shift register counter is the most efficient.

(也就是说,地址计数器提供顺序地址,但是没必要是传统的二进制地址序列,任何重复的序列都是可接受的,而LFSR计数器是最有效的

Conventional binary counters use complex or wide fan-in logic to generate high end carry signals. A much simpler structure sacrifices the binary count sequence, but achieves very high speed with very simple logic, easily packing two bits into every CLB. Such Linear Feedback Shift-Register (LFSR) counters are also known as pseudo random sequence generators.

传统的二进制计数器用复杂的或大扇入逻辑产生进位信号,LFSR以牺牲二进制计数序列为代价,用相当简单的结构与逻辑实现很高的速度,这种移位寄存器又称为伪随机序列发生器;LFSR计数器的计数序列就是伪随机序列

A possible disadvantage is that the count sequence is not the normal bina r y increment or decrement sequence.

LFSR的缺点是计数序列不是通常的递增或递减

LFSR计数器优缺点:

Comparison to other counter types „ Comparison  to  other  counter  types
„ PROS:
„ Requires ver y little log ic to imp lement(逻辑资源少
„ Even long counters are very efficient(速度高
ƒ Low gate count
ƒ Hi g h sp eed gp
„ Easy to test for faults - typically only need 2*n clocks(容易测试错误
„ CONS:
Pr i m iti ve forms must  b e  i n iti a li zed to va lid s ta te(本原形式必须初始化为有效状态
„ Some applications require binary count sequences(某些场合需要二进制计数序列
„ Not easy to predict count sequence (不易预测计数序列

LFSR的产生需要一个生成多项式,生成多项式指定反馈逻辑中抽头的有无。

LFSR不同长度的序列产生可根据下表得到(也就是生成多项式中的系数):

This table lists the appropriate taps for maximum-length LFSR counters of up to 168 bits. The basic description and the table for the first 40 bits was originally published in XCELL and reprinted on page 9-24 of the 1993 and 1994 Xilinx Data Books.

Responding to repeated requests, the list is here extended to 168 bits. This information is based on unpublished research done by Wayne Stahnke while he was at Fairchild Semiconductor in 1970.

 

References

P. Alfke, “Efficient Shift Registers, LFSR, Counters, and  Long Pseudo-Random Sequence Generators,” XAPP 052, July 7,1996 (Version 1.1)

说明:

实际中,LFSR计数器的实现有两大类:一对多与多对一。根据反馈回路是异或还是异或之后再经过一个非门,分为XOR与XNOR。

下面是多对一、XNOR的方式,此处的代码仅仅是模拟LFSR计数器的工作,具体使用时需根据需要进行改进。

下面给出8bit LFSR计数器的verilog代码。

根据上表,可知tap为8、6、5、4,因此,代码如下:

 

 1 module lfsr_counter(rst_n,
 2                             clk,
 3                             cnt
 4                                  );
 5 
 6 parameter CNT_SIZE = 8;
 7 
 8 input rst_n;
 9 input clk;
10 
11 output [CNT_SIZE - 1 : 0] cnt;
12 
13 reg [CNT_SIZE - 1 : 0] cnt;
14 
15 always@(posedge clk)
16     if(!rst_n)
17         cnt <= 8\'b0000_0000;  //初始值
18     else
19         begin
20             cnt[7:1] <= cnt[6:0];         //移位寄存
21             cnt[0] <= ~(^{cnt[7],cnt[5:3]});  //xnor
22         end
23 
24 endmodule

 


 环形计数器

也是基于移位寄存器的计数器,对于n个移位寄存器构成的计数器,只有n个有效状态。

设置一个初始状态,通过移位即可得到。

说明:

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

下面的代码仅仅是简单的实现,模拟环形计数器的工作方式,并没有过多的考虑自启动的问题。

代码:

 1 module circle_counter(rst_n,
 2                             clk,
 3                             cnt
 4                                  );
 5 
 6 parameter CNT_SIZE = 8;
 7 
 8 input rst_n;
 9 input clk;
10 
11 output [CNT_SIZE - 1 : 0] cnt;
12 
13 reg [CNT_SIZE - 1 : 0] cnt;
14 
15 always@(posedge clk)
16     if(!rst_n)
17         cnt <= 8\'b0000_0001;  //初始值
18     else
19         //cnt <= cnt>>1;
20         cnt <= {cnt[0],cnt[CNT_SIZE - 1 : 1]};    //注意是循环移位,而非简单的移位
21 
22 endmodule

 


 约翰逊(扭环形)计数器

也是基于移位寄存器的计数器,是对环形计数器的改进,对于n个移位寄存器构成的计数器,有2n个有效状态。

说明:

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

下面的代码仅仅是简单的实现,模拟环形计数器的工作方式,并没有过多的考虑自启动的问题。

设置一个初始状态,将最高位取反,作为最低位的输入,通过移位即可得到。

代码:

 

 1 module john_counter(rst_n,
 2                             clk,
 3                             cnt
 4                                  );
 5 
 6 parameter CNT_SIZE = 8;
 7 
 8 input rst_n;
 9 input clk;
10 
11 output [CNT_SIZE - 1 : 0] cnt;
12 
13 reg [CNT_SIZE - 1 : 0] cnt;
14 
15 always@(posedge clk)
16     if(!rst_n)
17         cnt <= 8\'b0000_0000;  //初始值
18     else
19         cnt <= {~cnt[0],cnt[CNT_SIZE - 1 : 1]};    //注意是循环移位,而非简单的移位
20 
21 endmodule