数字IC经典电路设计
经典电路设计是数字IC设计里基础中的基础,盖大房子的第一部是打造结实可靠的地基,每一篇笔者都会分门别类给出设计原理、设计方法、verilog代码、Testbench、仿真波形。然而实际的数字IC设计过程中考虑的问题远多于此,通过本系列希望大家对数字IC中一些经典电路的设计有初步入门了解。能力有限,纰漏难免,欢迎大家交流指正。快速导航链接如下:
一、毛刺消除
1.1 毛刺产生与消除
毛刺是如何产生的?又有什么危害?
由于延迟的作用,多个信号到达终点的时间有先有后,形成了竞争,由竞争产生的错误输出就是毛刺。所以,毛刺发生的条件就是在同一时刻有多个信号输入发生改变。
当一个逻辑门的输入有两个或两个以上的变量发生改变时,由于这些变量是经过不同路径产生的,使得它们状态改变的时刻有先有后,这种时差引起的现象称为竞争(Race)。竞争的结果将很可能导致冒险(Hazard)发生(例如产生毛刺),造成错误的后果,并影响系统的工作。
信号在FPGA器件内部通过连线和逻辑单元时,都有一定的延时。延时的大小与连线的长短和逻辑单元的数目有关,同时还受器件的制造工艺、工作电压、温度等条件的影响。信号的高低电平转换也需要一定的过渡时间。由于存在这两方面因素,多路信号的电平值发生变化时,在信号变化的瞬间,组合逻辑的输出有先后顺序,并不是同时变化(及竞争现象),往往会出现一些不正确的尖峰信号,这些尖峰信号称为“毛刺”。如果一个组合逻辑电路中有“毛刺”出现,就说明该电路“冒险”。
总的来说:由于延迟的作用,多个信号到达终点的时间有先有后,形成了竞争,由竞争产生的错误输出就是毛刺。所以,毛刺发生的条件就是在同一时刻有多个信号输入发生改变。
组合逻辑电路的冒险仅在信号状态改变的时刻出现毛刺,这种冒险是过渡性的,它不会使稳态值偏离正常值,但在时序电路中,冒险是本质的,可导致电路的输出值永远偏离正常值或者发生振荡。
毛刺消除有哪些方法?
毛刺是数字电路设计中的棘手问题,它的出现会影响电路工作的稳定性、可靠性,严重时会导致整个数字系统的误动作和逻辑紊乱。目前,有许多方法可以消除毛刺或者减少毛刺对电路的影响。
消除因为竞争冒险而产生的毛刺的常用方法有:_增加冗余项、接入滤波电容和引入封锁脉冲或选通脉冲以及使用格雷码代替二进制码_等。
- 增加冗余项消除竞争冒险:增加冗余项的方法是通过在函数表达式中“加”上多余的“与”项或“乘”上多余的“或”项,使原函数不可能在某种条件下化成X+X或X·X的形式,从而消除可能产生的竞争冒险,冗余项的选择可用代数法或卡诺图法。用增加冗余项的方法修改逻辑设计,可以消除一些竞争冒险现象。但是,这种方法的适用范围是有限的。增加冗余项,需增加额外电路,但增加了电路可靠性,如果运用得当,可以收到最理想的效果。
- 输出端并联电容器消除竞争冒险:竞争冒险所产生的干扰脉冲一般很窄。逻辑电路在较慢速度下工作时,可以在输出端并接一个不大的滤波电容。并用门电路的输出电阻和电容器构成低通滤波电路,对很窄的尖峰脉冲(其频率很高)起到了平波的作用。这时在输出端便不会出现逻辑错误。
接人滤波电容的方法简单易行,但输出电压波形随之变化,故只适用于对输出波形前后沿无严格要求的场合。 - 加选通脉冲、引入封锁脉冲消除竞争冒险:选通脉冲是当电路输出端达到新的稳定状态之后,引人选通脉冲,从而使输出信号是正确的逻辑信号而不包含干扰脉冲。封锁脉冲是在输入信号发生竞争的时间内,引入一个脉冲将可能产生尖峰干扰脉冲的门封锁住,从而消除竞争冒险。封锁脉冲应在输入信号转换前到来,转换结束后消失。
引入封锁脉冲或者选通脉冲的方法比较简单,而且不增加器件数目。但这种方法有一个局限性,就是必须找到一个合适的封锁脉冲或选通脉冲。 - 用格雷码替代二进制代码消除竞争冒险,确保每一时刻只有一个代码变化
1.2 从硬件描述的角度消除毛刺(单边毛刺)
如何消除毛刺呢?常采用的方法两级寄存器打拍子然后做逻辑运算。
以剔除小于一个时钟周期的毛刺信号为例,一级寄存器延迟一拍(一个时钟)输出信号din_r1,二级寄存器延迟两拍(两个时钟)输出信号din_r2。
对于剔除毛刺的类型不同,需要做的逻辑运算不同:
剔除高电平采用“与”逻辑运算
剔除低电平采用“或”逻辑运算
以下为消除高电平毛刺与低电平毛刺的原理图:
根据上图分别给出消除高电平毛刺和低电平毛刺的verilog代码描述、testbench、仿真结果。
verilog代码描述如下:
//消除高电平毛刺
module burr_remove(
input rst_n, //异步复位信号
input clk, //时钟
input din, //输入数据
output dout_rp //消除高电平毛刺
);
//打拍子
reg din_r1;
reg din_r2;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
din_r1 <= 1'b0;
din_r2 <= 1'b0;
end
else begin
din_r1 <= din;
din_r2 <= din_r1;
end
end
//逻辑运算输出
assign dout_rp = din_r1 & din_r2;
Endmodule
Testbench仿真如下:
`timescale 1ns/1ps
module burr_remove_tb();
reg rst_n;
reg clk;
reg din;
wire dout_rp;
burr_remove u_burr_remove(
.clk (clk),
.rst_n (rst_n),
.din (din),
.dout_rp 斜体(dout_rp)
);
always #5 clk = ~clk;
initial begin
clk = 0;
#5 rst_n = 1;
#10 rst_n = 0;
#10 rst_n = 1;
din = 0;
# 18 din = 1;
# 5 din = 0;
#100;
$finish;
end
endmodule
仿真结果:
verilog代码描述如下:
//消除低电平毛刺
module burr_remove(
input rst_n, //异步复位信号
input clk, //时钟
input din, //输入数据
output dout_rn //消除低电平毛刺
);
//打拍子
reg din_r1;
reg din_r2;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
din_r1 <= 1'b0;
din_r2 <= 1'b0;
end
else begin
din_r1 <= din;
din_r2 <= din_r1;
end
end
//组合逻辑输出
assign dout_rn = din_r1 | din_r2;
endmodule
Testbench仿真激励如下:
`timescale 1ns/1ps
module burr_remove_tb();
reg rst_n;
reg clk;
reg din;
wire dout_rn;
burr_remove u_burr_remove(
.clk (clk),
.rst_n (rst_n),
.din (din),
.dout_rn (dout_rn)
);
always #5 clk = ~clk;
initial begin
clk = 0;
#5 rst_n = 1;
#10 rst_n = 0;
#10 rst_n = 1;
din = 1;
# 18 din = 0;
# 5 din = 1;
#100;
$finish;
end
endmodule
仿真结果:
然而对于大于一个时钟周期的毛刺信号“打两拍子”是远远不够的,如下图所示:若想剔除大于一时钟周期的毛刺信号,需要“打三拍”!下图圈红部分在采用打两拍时不满足毛刺消除的要求,而打三拍可以满足。
//消除大于一周期的高电平毛刺
module burr_remove(
input rst_n, //异步复位信号
input clk, //时钟
input din, //输入数据
output dout_rp//消除高电平毛刺
);
//打拍子
reg din_r1;
reg din_r2;
reg din_r3;
always @ (posedge clk or negedge rst_n) begin
if (!rst_n) begin
din_r1 <= 1'b0;
din_r2 <= 1'b0;
din_r3 <= 1'b0;
end
else begin
din_r1 <= din;
din_r2 <= din_r1;
din_r3 <= din_r2;
end
end
//组合逻辑输出
wire dout_rp_12;
assign dout_rp_12 = din_r1 & din_r2;
assign dout_rp = din_r1 & din_r2 & din_r3;
endmodule
`timescale 1ns/1ps
module burr_remove_tb();
reg rst_n;
reg clk;
reg din;
wire dout_rp;
burr_remove u_burr_remove(
.clk (clk),
.rst_n (rst_n),
.din (din),
.dout_rp (dout_rp)
);
always #5 clk = ~clk;
initial begin
clk = 0;
#5 rst_n = 1;
#10 rst_n = 0;
#10 rst_n = 1;
din = 0;
# 18 din = 1;
# 18 din = 0;
#100;
$finish;
end
endmodule
仿真结果:
可以总结得到如下:若毛刺宽度介于N和N+1个周期,则需要N+2级触发器采样,然后再进行与(或)。
二、抖动消除电路
1.1 消抖电路原理
消抖电路与毛刺消除很相似,都是滤除不需要的信号。但是前者消除的是单边毛刺,而抖动的信号需要滤除的是双边信号,所以上文的单边毛刺信号滤除不适用在抖动消除电路中。
消除电路中不必要的信号抖动,主要分为两部分——边沿检测和计数器。关于边沿检测详细部分可以看看这篇边沿检测通过计数信号的边沿跳变距离可以确定不同信号的宽度,从从而消除信号抖动。
例如在设计按键部分通常会用到按键消抖部分:
如上图所示,按键的输入信号存在抖动现象,其前后沿抖动时间一般在5ms~10ms之间。由于运行速度非常快,如果没有消抖动电路则会出现频繁检测到按键这一动作。例如,经过A时刻的时候会检测到低电平判断按键被按下,当到了B时刻的时候同样会检测到高电平,电路误以为松开按键,然后又到了C时刻检测到低电平,判断到按键被按下。周而复始,在5-10ms内可能会出现很多次按下的动作,每一次按键的动作判断的次数都不相同。所以设计出输入消抖是十分有必要的!
1.2 从硬件描述角度消除抖动(双边毛刺)
verilog代码描述如下:
//消除双边毛刺
module glitch_filter(
input clk,
input rst_n,
input din,
output reg dout
);
//打一拍
reg din_r;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
din_r <= 0;
end
else begin
din_r <= din;
end
end
//边沿检测
assign up_edge = ~din_r & din;
assign down_edge = din_r & ~din;
assign both_edge = din_r ^ din;
//计数器
reg [1:0] cnt;
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 0;
end
else if(both_edge) begin
cnt <= 0;
end
else if(cnt == 3) begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
//计数器测毛刺周期
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dout <= 0;
end
else if(cnt == 2) begin
dout <= din_r;
end
else begin
dout <= dout;
end
end
endmodule
Testbench仿真激励如下:
`timescale 1ns/1ps
module glitch_filter_tb();
reg rst_n;
reg clk;
reg din;
wire dout;
glitch_filter u_glitch_filter(
.clk (clk),
.rst_n (rst_n),
.din (din),
.dout (dout)
);
always #5 clk = ~clk;
initial begin
clk = 0;
din = 0;
#5 rst_n = 1;
#10 rst_n = 0;
#10 rst_n = 1;
#5;
din = 1; #10;
din = 0; #20;
din = 1; #30;
din = 0; #40;
din = 1; #50;
#200;
$finish;
end
endmodule
仿真结果
三、总结
- 消除单边毛刺:核心思想为“打拍子 + 逻辑运算”。
毛刺宽度介于N和N+1个周期,则需要N+2级触发器采样。
消除高电平采用“与”逻辑运算,消除低电平采用“或”逻辑运算
- 消除双边毛刺:核心思想为“双边沿检测 + 计数器”。
通过双边沿检测得到信号变化的位置,用计数器则得到信号变化的周期,若变化周期大于要求的宽度(即非毛刺)则赋值,相反则是维持信号不变。
更多可查看个人主页链接
软件版本:Modelsim 10.6c
不定期纠错补充,欢迎随时交流
最后修改日期:2023.6.2