组合逻辑&时序逻辑
- 波形图中,表达时序逻辑时如果时钟和数据是对齐的,则默认当前时钟沿采集到的数据位在该时钟上升沿前一时刻的值。表达组合逻辑时如果时钟和数据是对齐的,则默认当前时钟沿采集到的数据为该始终上升沿同一时刻的值。
- 组合逻辑和时序逻辑的区别:
主要是看数据工作是不是在时钟沿下进行的。在 FPGA 的设计中,复杂的电路设计都要用到时序逻辑电路,往往都是以时序逻辑电路为主,组合逻辑为辅的混合逻辑电路。
- 组合逻辑会存在险竞争冒险,会引起电路的不稳定性和工作时的不确定性
- 时序逻辑最基本的单元就是寄存器,寄存器一般由D触发器构成
- 时序电路“延一拍”,key_in为高,led_out延迟一拍才变高
寄存器、触发器、锁存器辨析
寄存器(register)
-
寄存器(register):用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果,它被广泛的用于各类数字系统和计算机中。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。 工程中的寄存器一般按计算机中字节的位数设计,所以一般有8位寄存器、16位寄存器等 ;
-
寄存器的应用
a)可以完成数据的并串、串并转换;
b)可以用做显示数据锁存器:许多设备需要显示计数器的记数值,以8421BCD码记数,以七段显示器显示,如果记数速度较高,人眼则无法辨认迅速变化的显示字符。在计数器和译码器之间加入一个锁存器,控制数据的显示时间是常用的方法。
c)用作缓冲器;
d)组成计数器:移位寄存器可以组成移位型计数器,如环形或扭环形计数器。
https://blog.csdn.net/bleauchat/article/details/85312172
触发器 (Flip-Flop,FF)
- 触发器(Flip-Flop,简写为 FF)—对脉冲边沿敏感,其状态只在时钟脉冲的上升沿或下降沿的瞬间改变 ;
触发器也叫双稳态门,又称双稳态触发器,是一种可以在两种状态下运行的数字逻辑电路。触发器一直保持它们的状态,直到它们收到输入脉冲,又称为触发。当收到输入脉冲时,触发器输出就会根据规则改变状态,然后保持这种状态直到收到另一个触发 ; - 应用场合:时钟有效迟后于数据有效,这意味着数据信号先建立,时钟信号后建立,在CP有效沿时刻打入到寄存器 ;
锁存器(latch)
-
锁存器是电平触发的存储单元,数据存储的动作取决于输入时钟(或者使能)信号的电平值,当锁存器处于使能状态时,输出才会随着数据输入发生变化。(简单地说,它有两个输入,分别是一个有效信号EN,一个输入数据信号DATA_IN,它有一个输出Q,它的功能就是在EN有效的时候把DATA_IN的值传给Q,也就是锁存的过程);
-
应用场合:数据有效滞后于时钟信号有效,这意味着时钟信号先到,数据信号后到。在某些运算器电路中有时采用锁存器作为数据暂存器 ;
-
优点:
面积小、锁存器比FF快,所以用在地址锁存是很合适的,不过一定要保证所有的latch信号源的质量,锁存器在CPU设计中很常见,正是由于它的应用使得CPU的速度比外部IO部件逻辑快许多。latch完成同一个功能所需要的门较触发器要少,所以在asic中用的较多; -
缺点:时序分析较困难;
-
不用锁存器的原因有二:
1、锁存器容易产生毛刺,
2、锁存器在ASIC设计中应该说比FF要简单,但是在FPGA的资源中,大部分器件没有锁存器这个东西,所以需要用一个逻辑门和FF来组成锁存器,这样就浪费了资源;
寄存器访问类型
r/w | Field Description |
---|---|
R or RO | Read-Only |
W or WO | Write-Only |
R/W | Read/Write access |
ROC | Read-Only and Clear |
RWS | Read/Write ‘1’ to Set |
RWC | Read/Write ‘1’ to clear |
W1T | Write ‘1’ to Level Toggle, write-only |
W1P | Write ‘1’ to Pulse, write-only |
1. RO(Read-Only):
- 只读寄存器,软件只能读取其值,不能修改。
- 典型应用:状态寄存器、只读配置寄存器。
reg [7:0] read_only_reg;
assign read_only_reg = some_value; // 只能读取
2. RW(Read-Write):
- 可读可写寄存器,软件可以读取和写入其值。
- 典型应用:控制寄存器、配置寄存器。
reg [7:0] read_write_reg;
always @(posedge clk) begin
if (write_enable)
read_write_reg <= write_data;
end
3. ROC(Read-Only Clear):
- 只读清除寄存器,软件读取该寄存器后,其值会被自动清零。
- 典型应用:中断状态寄存器。
reg [7:0] read_only_clear_reg;
always @(posedge clk) begin
if (read_enable)
read_only_clear_reg <= 8'b0; // 读取后清零
end
4. RWC(Read-Write Clear):
- 可读可写清除寄存器,软件可以读取和写入其值,写入后寄存器值会被清零。
- 典型应用:计数器寄存器。
reg [7:0] read_write_clear_reg;
always @(posedge clk) begin
if (write_enable)
read_write_clear_reg <= 8'b0; // 写入后清零
end
5. RWS(Read-Write Set):
- 可读可写设置寄存器,软件可以读取和写入其值,写入后寄存器值会被置为全1。
- 典型应用:标志寄存器。
reg [7:0] read_write_set_reg;
always @(posedge clk) begin
if (write_enable)
read_write_set_reg <= 8'hFF; // 写入后置为全1
end
6. W1T(Write-1-to-Toggle):
- 写1翻转寄存器,软件写入1会翻转寄存器的当前值,写0无效。
- 典型应用:状态切换寄存器。
reg [7:0] write_1_toggle_reg;
always @(posedge clk) begin
if (write_enable && write_data == 1)
write_1_toggle_reg <= ~write_1_toggle_reg; // 写1翻转
end
7. W1P(Write-1-to-Pulse):
- 写1脉冲寄存器,软件写入1会生成一个脉冲信号,写0无效。
- 典型应用:触发器寄存器。
reg write_1_pulse_reg;
always @(posedge clk) begin
if (write_enable && write_data == 1)
write_1_pulse_reg <= 1; // 写1生成脉冲
else
write_1_pulse_reg <= 0; // 脉冲结束
end
8. RW1P(Read-Write-1-to-Pulse):
- 可读可写1脉冲寄存器,软件可以读取和写入其值,写入1会生成一个脉冲信号。
- 典型应用:事件触发寄存器。
reg read_write_1_pulse_reg;
always @(posedge clk) begin
if (write_enable && write_data == 1)
read_write_1_pulse_reg <= 1; // 写1生成脉冲
else
read_write_1_pulse_reg <= 0; // 脉冲结束
end
Shadow Register(影子寄存器)
影子寄存器是一种用于保存寄存器当前值的临时寄存器,通常用于确保数据一致性或在数据更新过程中避免中断。影子寄存器可以用来在写入主寄存器之前暂时保存数据,确保在特定条件下才更新主寄存器。
reg [7:0] main_reg;
reg [7:0] shadow_reg;
always @(posedge clk) begin
if (write_enable)
shadow_reg <= write_data; // 写入影子寄存器
if (update_condition)
main_reg <= shadow_reg; // 条件满足时更新主寄存器
end
触发器
D触发器(D Flip Flop,DFF)
- D触发器的功能:
1、在一个脉冲信号上升沿或下降沿的作用下,将信号从输入端D送到输出端Q,如果时钟脉冲的边沿信号未出现,即使输入信号改变,输出信号仍然保持原值。
2、寄存器拥有复位清零功能,复位分为同步复位和异步复位
3、能够存储一位二进制码
- D触发器分为两种,一种是同步复位D触发器,一种是异步触发D触发器。
- 电路符号
- 真值表
同步复位D触发器
- 同步复位:当时钟上升沿检测到复位信号,执行复位操作(有效的时钟沿是前提)。always @ ( posedge clk );
- 代码实现
//同步复位D触发器
module filp_flop (
input wire sys_clk,sys_rst_n,
input wire key_in,
output reg led_out
);
always @(posedge sys_clk) begin
if(sys_rst_n == 1'b0) begin //rst低电平时复位,并且大前提是clk为上升沿。
led_out <= 1'b0; //sys_clk上升沿到来时,若检测到rst为低电平时复位有效
end else begin
led_out <= key_in;
end
end
endmodule
异步复位D触发器
- 无论时钟沿是否到来,只要复位信号有效,就对系统进行复位。always @ ( posedge clk or negedge rst_n );
但是复位释放时仍需要等到时钟上升沿
(异步复位、同步释放)
- 代码实现
module dff ( //异步复位
input wire sys_clk,sys_rst_n,
input wire key_in,
output reg led_out
);
always @(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0) begin //rst低电平时复位,但是大前提不需要clk为一定上升沿。
led_out <= 1'b0; //检测到 sys_rst_n 的下/降沿时立刻复位,不需等待 sys_clk 的上升沿来到后再复位
end else begin
led_out <= key_in;
end
end
endmodule
时钟双沿触发器*
构建一个功能类似于双边沿触发触发器的电路:
知识拓展:
DDR在原有的SDRAM的基础上改进而来,SDRAM在一个CLK周期传输一次数据,而DDR在一个CLK周期传输两次数据,分别在上升沿和下降沿各传输一次数据
- 代码实现:
方法一:
//方法一简单明了,但因为触发器Tc_to_q延时的存在,输出波形会产生glitch(毛刺)
//即p、q的值还没更新,就已经执行了assign q = clk? q1:q2
module top_module (
input clk,
input d,
output q
);
reg pos_q, neg_q;
always@(posedge clk)begin pos_q <= d;end
always@(negedge clk)begin c<= d;end
assign q = clk ? pos_q : neg_q;
endmodule
方法二:
//推荐使用方法二进行双边检测
//方法二相较于方法一少了使用clk信号进行选择,可以避免产生毛刺,但电路也会复杂
module top_module (
input clk,
input d,
output q
);
reg pos_q,neg_q;
always@(posedge clk) begin pos_q <= d ^ neg_q; end
always@(negedge clk) begin neg_q <= d ^ pos_q; end
assign q = pos_q ^ neg_q;
endmodule
验证结果:
带字节使能的D触发器
创建16个D触发器,有时我们仅需要修改部分触发器中的值。字节使能信号控制当前时钟周期中16个寄存器中哪个字节需被修改。byteena[1]控制高字节d[15:8],而byteena[0]控制低字节d[7:0]。
resetn是一个同步,低电平有效的复位信号。
所有的D触发器由时钟的上升沿触发。
代码实现:
module top_module (
input clk,
input resetn,
input [1:0] byteena,
input [15:0] d,
output [15:0] q
);
always@(posedge clk)begin
if(!resetn)begin
q <= 16'd0;
end else begin
if(byteena[1] == 1'b1)begin
q[15:8] <= d[15:8];
end
if(byteena[0] == 1'b1)begin
q[7:0] <= d[7:0];
end
end
end
endmodule
- 验证结果
Synchronous active-low reset
DFF with byte enables
T触发器
T触发器知识回顾
1)构成:将JK触发器的输入端J、K连接在一起,作为输入端T,就构成了T触发器。
2)特征方程:
Q
n
+
1
=
T
Q
n
′
+
T
′
Q
n
=
T
⊕
Q
n
Q_{n+1} = T Q'_n + T'Q_n = T⊕Q_n
Qn+1=TQn′+T′Qn=T⊕Qn(其中Qn为现态,Qn+1为次态)
3)特性表
当时钟由0转为1时,如果T和Q不相同时,其输出值会是1
输入端T为0的时候,输出端的状态Q保持不变
输入端T为1的时候,输出端的状态Q发生反转
当输入T为0时,
Q
n
+
1
=
Q
n
Q_{n+1} = Q_n
Qn+1=Qn(保持);
当输入T为1时,
Q
n
+
1
=
Q
ˉ
n
Q_{n+1} = \bar{Q}_n
Qn+1=Qˉn(翻转)
T T T | Q Q Q | Q ˉ \bar{Q} Qˉ |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
5)实现代码
module t(
input clk,
input rst,
input T,
output reg Q
);
always@(posedge clk or negedge rst)begin
Q <= 1'b0;
end else begin
if(data_in == 1’b1)
Q <= ~Q;
else
Q <= Q;
end
endmodule
异步复位的串联T触发器
- 题目描述:
用verilog实现两个串联的异步复位的T触发器的逻辑,结构如图:
- 波形示意图:
- 输入描述:
输入信号 data, clk, rst
类型 wire
在testbench中,clk为周期5ns的时钟,rst为低电平复位 - 输出描述:
输出信号 q
类型 reg - 代码
`timescale 1ns/1ns
module Tff_2 (
input wire data, clk, rst,
output reg q
);
reg q1;
always@(posedge clk or negedge rst)begin
if(!rst)begin
q1<= 1'b0;
end else begin
if(data == 1'b1)begin
q1 <= ~q1;
end else begin
q1 <= q1;
end
end
end
always@(posedge clk or negedge rst)begin
if(!rst)begin
q <= 1'b0;
end else begin
if(q1 == 1'b1)begin
q <= ~q;
end else begin
q <= q;
end
end
end
endmodule
JK触发器
纪念Jack Kilby,德仪工程师,诺贝尔物理学奖获得者,发明第一块IC的人
J-K触发器是时钟边沿敏感的基本存储单元。逻辑电路和逻辑符号如下图所示:
- 真值表
- 代码
module top_module (
input clk,
input j,
input k,
output Q);
always@(posedge clk) begin
case({j,k})
2'b00: Q <= Q;
2'b01: Q <= 1'b0;
2'b10: Q <= 1'b1;
2'b11: Q <= ~Q;
default: Q <= Q;
endcase
end
endmodule
- 验证结果
同步复位和异步复位的优缺点
- 同步复位:当时钟上升沿检测到复位信号,执行复位操作(有效的时钟沿是前提)。always @ ( posedge clk );
优点:
a、有利于仿真器的仿真;
b、可以使所设计的系统成为 100% 的同步时序电路,有利于时序分析,而且可综合出较高的 Fmax;
c、由于只在时钟有效电平到来时才有效,所以可以滤除高于时钟频率的复位毛刺。
缺点:
a、复位信号的有效时长必须大于时钟周期,才能真正被系统识别并完成复位任务。同时还要考虑诸如 clk skew 、组合逻辑路径延时 、复位延时等因素(所以复位信号有时需要脉冲展宽,用以保证时钟有效期间有足够的复位宽度);
b、由于大多数的逻辑器件的目标库内的 DFF 都只有异步复位端口,所以,倘若采用同步复位的话,综合器就会在寄存器的数据输入端口插入组合逻辑,这样就会一方面额外增加FPGA内部的逻辑资源,另一方面也增加了相应的组合逻辑门时延。
c、在采用门控时钟的设计中,复位信号有效是时钟可能处于关闭状态,复位无效;
- 异步复位:无论时钟沿是否到来,只要复位信号有效,就对系统进行复位。always @ ( posedge clk or negedge rst_n );
优点:
a、大多数目标器件库的 DFF 都有异步复位端口,那么该触发器的复位端口就不需要额外的组合逻辑,这样就可以节省资源;
b、设计相对简单;
c、异步复位信号识别方便(电路在任何情况下都能复位而不管是否有时钟出现)。
缺点:
a、最大的问题在于它属于异步逻辑,问题出现在复位释放时,而不是有效时,如果复位释放接近时钟有效沿,则触发器的输出可能进入亚稳态(此时 clk 检测到的 rst_n 的状态就会是一个亚稳态,即是0是1是不确定的),从而导致复位失败。
b、可能因为噪声或者毛刺造成虚假复位信号(比如以前的游戏机玩到一半突然复位)(注意:时钟端口、清零和置位端口对毛刺信号十分敏感,任何一点毛刺都可能会使系统出错,因此判断逻辑电路中是否存在冒险以及如何避免冒险是设计人员必须要考虑的问题);
c、静态定时分析比较困难。
d、对于 DFT (Design For Test可测性设计)设计,如果复位信号不是直接来自于 I/O 引脚,在 DFT 扫描和测试时,复位信号必须被禁止,因此需要额外的同步电路。
标准锁存器
- 电路符号
// ===================================
// Description:
// Verilog module for general latch
//====================================
module sirv_gnrl_ltch # (
parameter DW = 32
) (
//input test_mode,
input lden,
input [DW-1:0] dnxt,
output [DW-1:0] qout
);
reg [DW-1:0] qout_r;
always @ *
begin : LTCH_PROC
if (lden == 1'b1)
qout_r <= dnxt;
end
assign qout = qout_r;
endmodule
标准DFF模块大合集
为什么要使用标准DFF模块
- 寄存器是数字同步电路中最基本的单元。使用 Verilog 进行数字电路设计时,最常见的
方式是使用 always 块语法生成寄存器。蜂鸟 E200 处理器核推荐如下原则,本原则来自于严
谨的工业级开发标准,其要点如下。
对于寄存器避免直接使用 always 块编写,而是应该采用模块化的标准 DFF 模块进
行例化。示例如下所示,一个名为 flg_dfflr 的寄存器,除了时钟(clk)和复位信号(rst_n)
之外,还带有使能信号 flg_ena 和输入(flg_nxt)/输出信号(flg_r)。
wire flg_r;
wire flg_nxt = ~flg_r;
wire flg_ena = (ptr_r == ('E203_OITF_DEPTH-1)) & ptr_ena;
//此处使用例化 sirv_gnrl_dfflr 的方式实现寄存器,而不是使用显示的 always 块
sirv_gnrl_dfflr #(1) flg_dfflrs(flg_ena, flg_nxt, flg_r, clk, rst_n);
使用标准 DFF 模块例化的好处包括以下内容。
• 便于全局替换寄存器类型。
• 便于在寄存器中全局插入延迟。
• 明确的 load-enable 使能信号(如下例的 flg_ena)方便综合工具自动插入寄存器级别
的门控时钟以降低动态功耗(参见第 15.1.5 节了解更多此低功耗设计的信息)。
• 便于规避 Verilog 语法 if-else 不能传播不定态的问题,有关此内容可以参考第 5.3.2 节。
标准DFF代码实现
sirv_gnrl_dfflrs //带有 load-enable 使能,带有异步 reset,复位默认值为 1 的寄存器
sirv_gnrl_dfflr //带有 load-enable 使能,带有异步 reset,复位默认值为 0 的寄存器
sirv_gnrl_dffl //带有 load-enable 使能,不带有 reset 的寄存器
sirv_gnrl_dffrs //不带有 load-enable 使能,带有异步 reset,复位默认值为 1 的寄存器
sirv_gnrl_dffr //不带有 load-enable 使能,带有异步 reset,复位默认值为 0 的寄存器
sirv_gnrl_ltch //Latch 锁存器模块
sirv_gnrl_dfflrs, load-enable使能/ 异步reset/ 复位值为1
// =========================================
// Description:
// Verilog module gnrl DFF with Load-enable and Reset
// Default reset value is 1
// =========================================
module gnrl_dfflrs #(
parameter DW = 8
) (
input lden,
input [DW-1:0] dnxt,
output [DW-1:0] qout,
input clk,
input rst_n
);
reg [DW-1:0] qout_r ;
always @(posedge clk or negedge rst_n)
begin: DFFLRS_PORC
if(rst_n == 1'b0) begin
qout_r <= {DW{1'b1}}; // Default reset value is 1
end
else if (lden == 1'b1) begin
qout_r <= #1 dnxt;
end
end
assign qout = qout_r;
endmodule
sirv_gnrl_dfflr,load-enable使能/ 异步reset/ 复位值为0
// ===========================================================================
// Description:
// Verilog module gnrl DFF with Load-enable and Reset
// Default reset value is 0
// ===========================================================================
module gnrl_dfflr # (
parameter DW = 8
) (
input lden,
input [DW-1:0] dnxt,
output [DW-1:0] qout,
input clk,
input rst_n
);
reg [DW-1:0] qout_r;
always @(posedge clk or negedge rst_n)
begin : DFFLR_PROC
if (rst_n == 1'b0)
qout_r <= {DW{1'b0}}; // Default reset value is 0
else if (lden == 1'b1)
qout_r <= #1 dnxt;
end
assign qout = qout_r;
endmodule
sirv_gnrl_dffl,load-enable使能/ 不带reset
// ===========================================================================
// Description:
// Verilog module gnrl DFF with Load-enable, no reset
// ===========================================================================
module gnrl_dffl # (
parameter DW = 8
) (
input lden,
input [DW-1:0] dnxt,
output [DW-1:0] qout,
input clk
);
reg [DW-1:0] qout_r;
always @(posedge clk)
begin : DFFL_PROC
if (lden == 1'b1) //no reset
qout_r <= #1 dnxt;
end
assign qout = qout_r;
endmodule
sirv_gnrl_dffrs,不带load-enable/ 带异步reset/ 复位值为1
// ===========================================================================
// Description:
// Verilog module gnrl DFF with Reset, no load-enable
// Default reset value is 1
// ===========================================================================
module gnrl_dffrs # (
parameter DW = 8
) (
input [DW-1:0] dnxt,
output [DW-1:0] qout,
input clk,
input rst_n
);
reg [DW-1:0] qout_r;
always @(posedge clk or negedge rst_n)
begin : DFFRS_PROC
if (rst_n == 1'b0)
qout_r <= {DW{1'b1}}; //Default reset value is 1
else //no load-enable
qout_r <= #1 dnxt;
end
assign qout = qout_r;
endmodule
sirv_gnrl_dffr,不带load-enable/ 带异步reset/ 复位值为0
// ===========================================================================
// Description:
// Verilog module gnrl DFF with Reset, no load-enable
// Default reset value is 0
// ===========================================================================
module gnrl_dffr # (
parameter DW = 8
) (
input [DW-1:0] dnxt,
output [DW-1:0] qout,
input clk,
input rst_n
);
reg [DW-1:0] qout_r;
always @(posedge clk or negedge rst_n)
begin : DFFR_PROC
if (rst_n == 1'b0)
qout_r <= {DW{1'b0}}; // Default reset value is 0
else
qout_r <= #1 dnxt;
end
assign qout = qout_r;
endmodule
sirv_gnrl_ltch,Latch锁存器模块
// ===========================================================================
// Description:
// Verilog module for general latch
// ===========================================================================
module sirv_gnrl_ltch # (
parameter DW = 32
) (
//input test_mode,
input lden,
input [DW-1:0] dnxt,
output [DW-1:0] qout
);
reg [DW-1:0] qout_r;
always @ *
begin : LTCH_PROC
if (lden == 1'b1)
qout_r <= dnxt;
end
//assign qout = test_mode ? dnxt : qout_r;
assign qout = qout_r;
`ifndef FPGA_SOURCE//{
`ifndef DISABLE_SV_ASSERTION//{
//synopsys translate_off
always_comb
begin
CHECK_THE_X_VALUE:
assert (lden !== 1'bx)
else $fatal ("\n Error: Oops, detected a X value!!! This should never happen. \n");
end
//synopsys translate_on
`endif//}
`endif//}
endmodule
参考链接
- 《手把手教你设计CPU.RISC-V处理器》
- 锁存器、触发器和寄存器
- 触发器详解——(二)JK触发器
- 一步一步带你理解DDR基本原理
- 同步复位和异步复位二者各自的优缺点