在上几节内容中介绍的状态机状态都是采用二进制自然码的编码格式,当状态机的状态比较多,而且状态机的时钟频率较高时,由于二进制编码在状态转换时会有多个位同时变化,会对状态机的安全产生影响;因此,二进制编码虽然简单,但有时为了可靠性还会选择其它的编码方式。比较常用的由格雷码(Gray code)和独热码(one-hot code)。下面就格雷码和独热码编码方式及优缺点加以介绍。
1.格雷码
格雷码是一种可靠性高的编码,与自然二进制编码不同,相邻格雷码的编码每次只有一位不同,因此在状态顺序转换时,是错误最小的编码方式。 在数字系统中,常要求代码按一定顺序变化。例如,按自然数递增计数,若采用8421码,则数从0111变到1000时四位均要变化,而在实际电路中,由于布线延迟等影响,4位的变化不可能绝对同时启动或完成,则计数中可能出现短暂的其它临时编码状态(1100、1111等)。在特定情况下可能导致电路状态错误或输出错误,使用格雷码可以避免这种错误。著名的数字调制编码QAM与QPSK就采用了格雷码的编码方式,同样著名的数字逻辑化简的卡诺图也是采用格雷码编码方式。格雷码具有循环、单步、反射、自补等特点,是左右相邻的编码格式,因此在许多领域获得比较广泛的应用。表1,表2,表3,分别列出了2位,3位,4位自然二进制码、格雷码和独热码的关系。有关更多的理论请参照后续推出的编码理论部分教程。
表1
| 序号 | 自然2进制码 | 格雷码 | 独热码 |
| 0 | 00 | 00 | 0001 |
| 1 | 01 | 01 | 0010 |
| 2 | 10 | 11 | 0100 |
| 3 | 11 | 10 | 1000 |
表2
| 序号 | 自然2进制码 | 格雷码 | 独热码 |
| 0 | 000 | 000 | 0000_0001 |
| 1 | 001 | 001 | 0000_0010 |
| 2 | 010 | 011 | 0000_0100 |
| 3 | 011 | 010 | 0000_1000 |
| 4 | 100 | 110 | 0001_0000 |
| 5 | 101 | 111 | 0010_0000 |
| 6 | 110 | 101 | 0100_0000 |
| 7 | 111 | 100 | 1000_0000 |
表3
| 序号 | 自然2进制码 | 格雷码 | 独热码 |
| 0 | 0000 | 0000 | 0000_0000_0000_0001 |
| 1 | 0001 | 0001 | 0000_0000_0000_0010 |
| 2 | 0010 | 0011 | 0000_0000_0000_0100 |
| 3 | 0011 | 0010 | 0000_0000_0000_1000 |
| 4 | 0100 | 0110 | 0000_0000_0001_0000 |
| 5 | 0101 | 0111 | 0000_0000_0010_0000 |
| 6 | 0110 | 0101 | 0000_0000_0100_0000 |
| 7 | 0111 | 0100 | 0000_0000_1000_0000 |
| 8 | 1000 | 1100 | 0000_0001_0000_0000 |
| 9 | 1001 | 1101 | 0000_0010_0000_0000 |
| 10 | 1010 | 1111 | 0000_0100_0000_0000 |
| 11 | 1011 | 1110 | 0000_1000_0000_0000 |
| 12 | 1100 | 1010 | 0001_0000_0000_0000 |
| 13 | 1101 | 1011 | 0010_0000_0000_0000 |
| 14 | 1110 | 1001 | 0100_0000_0000_0000 |
| 15 | 1111 | 1000 | 1000_0000_0000_0000 |
例1:4位自然二进制数到格雷码的转换
自然二进制码到格雷码的转换,保留自然二进制码的最高位,其它位采用本位与本位相邻的高位异或得到。数学公式描述如下:
公式表示:
Gn=Bn
Gi= Bi+1 ^ Bi (i >= 0; i<=n-1)
(G:格雷码,B:二进制码)
Verilog 代码如下:
module Binary_Gray
(
input [3:0] Bin,
output [3:0] Gray
);
assign Gray[3] = Bin[3];
assign Gray[2] = Bin[3] ^ Bin[2];
assign Gray[1] = Bin[2] ^ Bin[1];
assign Gray[0] = Bin[1] ^ Bin[0];
endmodule
例2,4位二进制格雷码转换为自然二进制码
由格雷码到自然二进制码转换,保留格雷码的最高位,二进制码次高位由二进制码最高位与格雷码次高位异或得到,之后的各位的计算结果由二进制码上一位的计算结果与格雷码本位相异或得到。
公式表示:
Bn=Gn
Bi= Bi+1 ^ Gi
(G:格雷码,B:二进制码)
Verilog 代码如下:
module Gray_Bin
(
input [3:0] Gray,
output [3:0] Bin
);
assign Bin[3] = Gray[3];
assign Bin[2] = Bin[3] ^ Gray[2];
assign Bin[1] = Bin[2] ^ Gray[1];
assign Bin[0] = Bin[1] ^ Gray[0];
endmodule
从例1中可以看出,自然二进制码到格雷码转换过程, 每位计算是独立进行的,因此是可以并行计算的; 但是从例2中可以看出,格雷码到自然二进制码转换过程,是有顺序的,从高位到低位依次计算。
2. 独热码
独热码的编码中只有一位为1,其它位都为0。这种编码方式每次只有两位发生变化,从误差最小的角度看不如格雷码,但独热码的编码和解码都比较简单,实际上独热码可以看成自然二进制码的译码格式。如2/4,3/8,4/16译码器等都可以认为自然二进制码到独热码的转换。
3. 编码方式比较,如表2
表2
| 自然二进制编码 | 格雷码 | 独热码 | |
| 优点 | 编码密度高
便于理解和调试 |
编码密度高
安全可靠 |
占用逻辑资源少
比较安全可靠 便于理解和调试 |
| 缺点 | 不利于综合器的化简
判断状态时资源占用多 可靠性差 |
判断状态时资源占用多
不便于理解和调试 |
编码密度低
占用触发器资源多 |
| 适用范围 | 状态多,逻辑复杂,资源充足的情况下 | 状态多,对状态机稳定性要求高的情况
注:应尽量安排相邻状态跳转 |
状态少,判断条件复杂的情况
许多综合器默认将自然二进制编码译码成独热码后使用 |
例3:将按键去抖程序按格雷码的编码方式改写
图1为自然二进制编码状态转移图,如果用格雷码编码,状态转移图就会如图2所示。

图1 自然二进制编码状态转移图

图2 Gray编码状态转移图
module debouncing
(
input rst,
input clk,
input ms_p, //毫秒脉冲
input sw_in, //按键输入
output sw_out //按键输出
);
localparam [1:0]
PD_CAPTURE = 2'b00,//按下捕获
PD_DEBOUNCING = 2'b01,
RLS_CAPTURE = 2'b11,//释放捕获(release)
RLS_DEBOUNCING = 2'b10;
reg [2:0] bn_r; //按键输入锁存
reg bn_hp_debounce; //按键_高电平脉冲_去抖
reg [3:0] deb_count;
reg [1:0] deb_st; //按键去抖状态机的状态
assign sw_out = bn_hp_debounce;
always@(posedge clk or posedge rst )
if(rst) begin
bn_r <= 0;
end
else begin
bn_r <= {bn_r[1:0], sw_in}; //按键三次锁存,消除亚稳态
end
always@(posedge clk or posedge rst )
if(rst)
begin
deb_st <= PD_CAPTURE;
deb_count <= 0;
bn_hp_debounce <= 0;
end
else
begin
case(deb_st)
PD_CAPTURE:
begin
deb_count <= 0;
bn_hp_debounce <= 0;
if(bn_r[2])
deb_st <=PD_DEBOUNCING;
end
PD_DEBOUNCING:
begin
if(~bn_r[2])
deb_st <= PD_CAPTURE;
else if(deb_count >= 10)
deb_st <=RLS_CAPTURE;
else if(ms_p)
deb_count <= deb_count + 1;
end
RLS_CAPTURE:
begin
deb_count <= 0;
if(~bn_r[2])
deb_st <= RLS_DEBOUNCING;
end
RLS_DEBOUNCING:
begin
if(bn_r[2])
deb_st <= RLS_CAPTURE;
else if(deb_count >= 10)
begin
deb_st <= PD_CAPTURE;
bn_hp_debounce <= 1'b1;
end
else if(ms_p)
deb_count <= deb_count + 1;
end
default: deb_st <= PD_CAPTURE;
endcase
end
endmodule
例4:将按键去抖程序状态机的状态按独热码方式实现
图3为独热码编码状态转移图。

图3 One-hot编码状态转移图
module debouncing
(
input rst,
input clk,
input ms_p, //毫秒脉冲
input sw_in, //按键输入
output sw_out //按键输出
);
localparam [3:0]
PD_CAPTURE = 4'b0001, //按下捕获
PD_DEBOUNCING = 4'b0010,
RLS_CAPTURE = 4'b0100, //释放捕获(release)
RLS_DEBOUNCING= 4'b1000;
reg [2:0] bn_r; //按键输入锁存
reg bn_hp_debounce; //按键_高电平脉冲_去抖
reg [3:0] deb_count;
reg [3:0] deb_st; //按键去抖状态机的状态
assign sw_out = bn_hp_debounce;
always@(posedge clk or posedge rst )
if(rst) begin
bn_r <= 0;
end
else begin
bn_r <= {bn_r[1:0], sw_in}; //按键三次锁存,消除亚稳态
end
always@(posedge clk or posedge rst )
if(rst)
begin
deb_st <= PD_CAPTURE;
deb_count <= 0;
bn_hp_debounce <= 0;
end
else
begin
case(deb_st)
PD_CAPTURE:
begin
deb_count <= 0;
bn_hp_debounce <= 0;
if(bn_r[2])
deb_st <= PD_DEBOUNCING;
end
PD_DEBOUNCING:
begin
if(~bn_r[2])
deb_st <= PD_CAPTURE;
else if(deb_count >= 10)
deb_st <= RLS_CAPTURE;
else if(ms_p)
deb_count <= deb_count + 1;
end
RLS_CAPTURE:
begin
deb_count <= 0;
if(~bn_r[2])
deb_st <= RLS_DEBOUNCING;
end
RLS_DEBOUNCING:
begin
if(bn_r[2])
deb_st <= RLS_CAPTURE;
else if(deb_count >= 10)
begin
deb_st <= PD_CAPTURE;
bn_hp_debounce <= 1'b1;
end
else if(ms_p)
deb_count <= deb_count + 1;
end
default: deb_st <= PD_CAPTURE;
endcase
end
endmodule
以上介绍了不同的编码格式及在状态机中的使用,所使用的例子也都是比较熟悉的例子,便于把主要精力放在基本概念的理解上。除了上文介绍的格雷码、独热码可以用在状态机编码外,还有其他的编码可以使用,如Johnson编码等,这里不再一一介绍。
其实除了手工编码外,还可以利用综合器强制进行编码选择,如在Vivado 或Quartus下可以利用如下格式进行编码格式选择。
Vivado:
(* fsm_encoding = “{auto|one-hot|compact|sequential|gray|johnson|speed1|user}” *)
举例: (* fsm_encoding = "gray" *) reg [3:0] fsm_st;
参考文章:Vivado Design Suite User Guide — Synthesis
Quartus:
(* syn_encoding= “{default|one-hot|compact|sequential|gray|johnson|user}” *)
举例: (* syn_encoding = "one-hot" *) reg [3:0] fsm_st;
参考文章: Quartus — Design and Synthesis
练习题:
- 将改写后的按键去抖程序在Quartus下编译,观察逻辑资源及寄存器使用情况。
- 将改写后的按键去抖程序在Vivado下编译,观察逻辑资源及寄存器使用情况。
- 设计8输入的2进制自然码到格雷码的转换。
- 设计8输入的格雷码到自然2进制码的转换。