HDLBits-Circuits学习小结(四)D触发器(latches and flip-flops)

1 D触发器基础

1.1 创建D触发器

D触发器是一种存储位的电路,并在时钟信号的(通常)上升沿定期进行更新。当使用clocked always block时,D触发器由逻辑合成器创建。
D触发器是“组合逻辑后跟触发器的blob”的最简单形式,其中组合逻辑部分只是一条线。

创建一个D触发器
D

solution:

module top_module (
    input clk,    // Clocks are used in sequential circuits
    input d,
    output reg q );//

    // Use a clocked always block
    //   copy d to q at every positive edge of clk
    //   Clocked always blocks should use non-blocking assignments
    always @(posedge clk) begin
        q <= d;
    end

endmodule

创建8个D触发器。所有DFF应由clk的上升沿触发。

solution:

module top_module (
    input clk,
    input [7:0] d,
    output [7:0] q
);
    always  @(posedge clk) begin
        q <= d;
    end

endmodule

1.2 D触发器与复位功能

创建一个具有高电平同步复位功能的8位D触发器。所有D触发器都由时钟信号clk的上升沿触发。

异步Asynchronous:清零端与时钟无关,复位取决于clr(rst)的上升沿,always @(posedge clk or negedge rst)
同步Synchronous:复位的动作与时钟同步,复位取决于时钟的上升沿并且clr在高位,always @(posedege clk)

solution:

module top_module(
    input clk,
    input reset,            // Synchronous reset
    input [7:0] d,
    output [7:0] q
);
    always @(posedge clk) begin
        if (reset)
            q <= 0;
        else
        	q <= d;
    end

endmodule

创建具有高电平有效同步复位的8 D触发器。触发器必须重置为0x34而不是零。所有DFF应由clk的下降沿触发。

提示:将寄存器重置为“ 1”有时称为“预设”。

solution:

module top_module(
    input clk,
    input reset,
    input [7:0] d,
    output [7:0] q
);
    always @(negedge clk) begin
        if (reset)
            q <= 6'h34;
        else
            q <= d;
    end

endmodule

创建一个具有高电平异步复位功能的8位D触发器。所有D触发器都由时钟信号clk的上升沿触发。

同步和异步复位触发器之间唯一的代码差异是灵敏度列表。

solution:

module top_module(
    input clk,
    input areset,            // Asynchronous reset
    input [7:0] d,
    output [7:0] q
);
    always @(posedge clk or posedge areset) begin
        if (areset)
            q <= 0;
        else
        	q <= d;
    end

endmodule

2 D触发器进阶

2.1 创建有特定功能的D触发器

创建一个16位D触发器。有时候,只修改一组触发器的一部分是大有用处的。字节使能输入控制16个寄存器的每个字节是否应该在那个周期写入。byteena[1]控制高部分字节d[15:8],而byteena[0]控制低部分字节d[7:0]。
resetn是同步的、低电平有效。所有D触发器都由时钟信号clk的上升沿触发。

注意:这里的byteen有多种情况来控制字节的写入。

solution:

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)
            q <= 0;
        else if (byteena[1] | byteena[0]) begin
            if (byteena[1])
                q[15:8] <= d[15:8];
            if (byteena[0])
                q[7:0] <= d[7:0];
        end
    end
            
endmodule

2.2 D锁存器

实现下面的电路。
latch
请注意,这是一个锁存器,因此会出现一个“having inferred a latch”的Quartus警告。

关于D触发器、D锁存器的概念,不妨查看这篇文章RS锁存器,D锁存器、D触发器简介

由这篇文章可以知道,

  • RS锁存器:当R(复位端)=S(置“1”端)=0时,输出保持不变,这保证了RS同时为0(断电)后,电路输出能够保持不变;
  • 由于RS不可能同时变为0(电路时延不可能完全相同),那么就存在先后问题,就会给电路带来不确定性!因为我们不知道是谁先变成0,就更不知道输出会变成什么样!
  • 为了解决RS锁存器带来的问题(RS不能同时为1),在此基础上,添加两个与门和一个非门,即可避免这种情况。升级版电路名字就叫D锁存器。当E端为0的时候,R端也会恒为0,S端则等于D端输入,亦即是此时输出直接等于输入。

提示:

  • 锁存器是电平敏感的(非边沿敏感的)电路,因此在always块中,它们使用电平敏感的敏感度列表。
  • 但是,它们仍然是顺序元素,因此应使用非阻塞分配
  • D锁存器在启用时的作用类似于导线(或同相缓冲器),而在禁用时保留当前值

solution:

module top_module (
    input d, 
    input ena,
    output q);
    
    always @(*) begin
        if (ena)
            q <= d;
        else
            q <= q;
    end

endmodule

实现下面的电路。
q4b

solution:

module top_module(
    input clk,
    input d, 
    input ar,   // asynchronous reset
    output q);
    always @(posedge clk or posedge ar) begin
        if (ar)
            q <= 0;
        else
            q <= d;
    end
    
endmodule

实现下面的电路。
q4c

solution:

module top_module(
    input clk,
    input d, 
    input r,   // synchronous reset
    output q);
    always @(posedge clk) begin
        if (r)
            q <= 0;
        else
            q <= d;
    end

endmodule

实现下面的电路:
q4d

module top_module (
    input clk,
    input in, 
    output out);
    
    always @(posedge clk) begin
        out <= in ^ out;
    end

endmodule

3 D触发器与门电路的各种操作

3.1 选择器和触发器

我们用3个包含触发器和多路选择器的子模块来实现图中电路。要求我们写出包含一个触发器和一个多路选择器的子模块。

mux_D

module top_module(
	input clk,
	input L,
	input r_in,
	input q_in,
	output reg Q);
    
    wire temp;
    assign temp = L ? r_in : q_in;
    
    always@(posedge clk) begin
    	Q <= temp;    
    end
    
endmodule

考虑一个 n-bit 移位寄存器。如上图所示,我们还是实现包含选择器和触发器的部分。

mux_D2

module top_module(
    input clk,
    input w, R, E, L,
    output Q
);
    wire temp;
    assign temp = L ? R :(E ? w : Q);
    
    always @(posedge clk) begin
        Q <= temp;
    end
    
endmodule

3.2 门电路与触发器

如下图所示的状态机,假设D触发器在状态机启动之前初始化为0,实现该电路:

Dm
提示:本题为门电路与触发器的结合,上图包含三个触发器、异或门、与门和或门。只需要注意后两个触发器输出是取反即可。

module top_module(
    input clk,
    input x,
    output z
); 
    reg temp1,temp2,temp3;
    assign z = ~(temp1 | temp2 | temp3);
    
    always @(posedge clk) begin
    	temp1 <= x ^ temp1;
    	temp2 <= x & ~temp2;
        temp3 <= x | ~temp3;
    end
    
endmodule

⭐️

JK触发器的真值表如下图所示,仅使用D触发器和门电路来实现该JK触发器。其中Qold是D触发器在时钟上升沿之前的输出。

JK
JK触发器和触发器中最基本的RS触发器结构相似,其区别在于,RS触发器不允许R与S同时为1,而JK触发器允许J与K同时为1。当J与K同时变为1的同时,输出的值状态会反转。也就是说,原来是0的话,变成1;原来是1的话,变成0。 对应表如下:
JK触发器

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 <= 0;
            2'b10: Q <= 1;
            2'B11: Q <= ~Q;
        endcase
    end
        
endmodule

3.3 边沿检测(重要)

⭐️ 输入上升沿的检测

对于每个8bit的变量,检测这些信号什么时候从0变为1(类似检测上升沿),输出应该在0到1 变化之后才有值。
下图给我们展示了输入in[1]和输出pedge[1]的时序关系图:

Edgedetect
边沿检测经常用于按键输入检测电路中,按键按下时输入信号 key 变为低电平,按键抬起变为高电平。当输入的信号为理想的高低电平时(不考虑毛刺和抖动),边沿检测就发挥了很重要的作用。

由于输入的信号为一个连续值,我们需要通过时钟进行采样。根据采样定理,采样时钟的频率需要至少为被采信号频率的 2 倍。

module top_module (
    input clk,
    input [7:0] in,
    output [7:0] pedge
);
    reg [7:0] temp;
    always @(posedge clk) begin
        temp <= in;
        pedge <= ~temp & in; //检测上升沿用in(in由0变为1,temp是0),检测下降沿用~in(in由1变为0,temp是1)
    end
        
endmodule

⭐️ 输入上升沿、下降沿的检测

对于每个8bit的变量,检测这些信号什么时候从0变为1(类似检测上升沿),输出应该在0到1 变化之后才有值。
下图给我们展示了输入in[1]和输出pedge[1]的时序关系图:

pedge
边沿检测的特性就是两边电平发生了变化,无非是0变1上升沿,1变0下降沿。

module top_module(
    input clk,
    input [7:0] in,
    output [7:0] anyedge
);
    reg [7:0] temp;
    always@ (posedge clk) begin
        temp <= in;
        anyedge <= temp ^ in; //anyedge <= (~temp & in) | (temp & ~in);
    end

endmodule

🌟 捕获

对于32bit中的每一个变量,当输入信号从一个时钟周期的1变为下一个时钟周期的0时捕获,即我们需要捕获输入信号的下降沿。

其中捕获的意思就是说在寄存器复位之前,输出一直保持为 ‘1’ 。每一个输出bit类似SR触发器:输出信号从1变0发生时会保持一个周期。

输出会在时钟上升沿和reset信号为高时复位。如果上述事件在同一时间发生,reset有更高的优先级。在下图所示的最后4个周期内,reset信号比set信号早一个周期,所以没有冲突发生。

reset
这个图大概是这个意思,out[1]是来捕获in[1]的下降沿的,即输出会在输入的下降沿后的一个周期,变为1,然后一直保持(类似于SR触发器,但是这里输出信号会长期保持)直到复位信号到来。紧接着,复位信号到来,那么输出信号在一个周期后置0,很巧的是,这时输入信号又一次产生了下降沿,于是过一个周期之后,out[1]变为了1。

根据题意,这里我们设置两个过渡的变量,一个用于记录输出捕获的in信号的下降沿,一个类似于前面一个例子中的temp,记录前一个输入的值。

solution:

module top_module (
    input clk,
    input reset,
    input [31:0] in,
    output [31:0] out
);
    reg [31:0] temp;
    wire [31:0] capture;
    always @(posedge clk) begin
        temp <= in;
    end
    
    assign capture = temp & ~in;
    
    integer i;
    always @(posedge clk) begin
        if (reset) 
            out <= 0;
        else begin
            for(i=0;i<=31;i++) begin
                if(capture[i])
                    out[i] <= 1;//捕获的意思就是说在寄存器复位之前,输出一直保持为‘1’ 
            end
        end
    end
    
endmodule

🌟 双边沿检测

设计一个双边沿检测的触发器,时序如下图所示:
dualedge
我们现在对时钟上升沿与下降沿都已经很熟悉了。但是FPGA没有一个同时检测双边沿的触发器,而且always中的敏感列表也不支持(posedge clk or negedge clk)。

本题相当于半个时钟周期上的输入信号的变化都可以检测出来,观察图可以知道q是d延后半个周期的波形,那么也就是说检测出d变化后q将进行翻转,而不是维持不变,本题有两种解题思路,一种是用MUX,一种是用XOR。

module top_module (
    input clk,
    input d,
    output q
);
    reg q1,q2;
    assign q = clk ? q1 : q2; //clk是1的话,说明前一阶段clk是上升沿;是0则说明前一阶段是下降沿
    
    always @(posedge clk) begin
        q1 <= d;
    end
    
    always @(negedge clk) begin
        q2 <= d;
    end
    
endmodule
  • 8
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值