当前位置:   article > 正文

从底层结构开始学习FPGA(3)----存储单元之触发器、寄存器与锁存器_fpga寄存器

fpga寄存器

系列目录与传送门

一、基本概念

二、FPGA内部的存储单元

三、触发器

四、锁存器


系列目录与传送门

        《从底层结构开始学习FPGA》目录与传送门


一、基本概念

        触发器触发器是边沿敏感的存储单元,数据存储的动作由某一信号的上升或者下降沿进行同步的。触发器是计算机记忆装置的基本单元,一个触发器能储存一位二进制代码。

        锁存器:锁存器是电平触发的存储单元,数据存储的动作取决于使能信号的电平值,当锁存器处于使能状态时,输出才会随着数据输入发生变化。(简单地说,它有两个输入,分别是一个有效信号EN,一个输入数据信号DATA_IN,它有一个输出Q,它的功能就是在EN有效的时候把DATA_IN的值传给Q,也就是锁存的过程)。

        寄存器:一个触发器可以组成一个一位的寄存器,多个触发器可以组成一个多位的寄存器。存储器是由大量寄存器组成的,其中每一个寄存器就称为一个存储单元。它可以存放一个有独立意义的二进制代码。

        在数字电路中存储单元有两种,一种是触发器,一种是锁存器。它们两者最大的区别是:前者通过时钟沿到来改变存储的输出状态,后者是通过电平变换来改变存储的输出状态。在FPGA中我们基本多使用触发器,锁存器在很多情况下都是要避免使用的,因为其不需要时钟,所以不是时序元件,对毛刺无过滤功能,非常敏感,容易处问题。

        而我们平时所说的寄存器,基本上理解为一个或者一组触发器(Flip Flop,FF)。


二、FPGA内部的存储单元

        在7系列FPGA的底层----CLB、可编程逻辑块中有两个SLICE,其中每个SLICE都含有8个存储单元。虽然说是存储单元,但实际上是4个触发器 FF+ 4个触发器FF或锁存器LATCH(可配置为其中一种)。其结构如下图: 

323179bd737f4b9981950b53fa71106c.png

       

        左边的4个存储单元只能作为触发器使用,而右边的4个存储单元则不光能作为触发器使用,还能作为锁存器使用,但是需要注意的是:一旦SLICE中(也就是8个FF)中的4个FF被作为LATCH使用,那么剩下的4个FF就无法使用了,会造成资源浪费。


三、触发器

        在FPGA内部的触发器都常会被配置成D触发器,根据其复位方式、复位电平和上电电平值的不同,可将其分为不同的种类。

        在Verilog语言中,我们定义一个reg会在FPGA映射成一个或者一组FF。举例如下:

  1. module test(
  2. input clk,
  3. input rst_n,
  4. input in1,
  5. output reg A
  6. );
  7. always@(posedge clk)begin
  8. if(~rst_n)
  9. A <= 1'b0;
  10. else
  11. A <= in1;
  12. end
  13. endmodule

        显然,在FPGA的实现就是1bit的FF:

c6bddd19040e4187b49c956f4c8ede71.png

        而下面的语句在FPGA的实现则会类推到4个FF:

  1. module test(
  2. input clk,
  3. input rst_n,
  4. input [3:0] in1,
  5. output reg [3:0] A
  6. );
  7. always@(posedge clk)begin
  8. if(~rst_n)
  9. A <= 4'd0;
  10. else
  11. A <= in1;
  12. end
  13. endmodule

59c21d4703184f1f8aaad25dc62b2286.png

        如果你还记得数电的话,就应该能看出来,上面配置的触发器实际上就是D触发器(DFF)。通过对复位方式、复位电平参数的配置,可以将D触发器约定为下述4种基本组成情况:

  • 异步复位(FDCE)
  • 异步置位(FDPE)
  • 同步复位(FDRE)
  • 同步置位(FDSE)

       

        这4种类型的D触发器如何用Verilog实现?接下来我们就建个工程看看究竟,Verilog代码:

  1. module test(
  2. input clk,
  3. input rst,
  4. input in1,
  5. input in2,
  6. input in3,
  7. input in4,
  8. output reg out1,
  9. output reg out2,
  10. output reg out3,
  11. output reg out4
  12. );
  13. // FDCE
  14. always @ ( posedge clk or posedge rst)begin
  15. if(rst)
  16. out1 <= 1'b0;
  17. else
  18. out1 <= in1;
  19. end
  20. // FDPE
  21. always @ ( posedge clk or posedge rst )begin
  22. if(rst)
  23. out2 <= 1'b1;
  24. else
  25. out2 <= in2;
  26. end
  27. // FDRE
  28. always @ ( posedge clk )begin
  29. if(rst)
  30. out3 <= 1'b0;
  31. else
  32. out3 <= in3;
  33. end
  34. // FDSE
  35. always @ ( posedge clk )begin
  36. if(rst)
  37. out4 <= 1'b1;
  38. else
  39. out4 <= in4;
  40. end
  41. endmodule

        vivado推断的门级电路如下:

5b8a414af8f5420aab224d63b01b4b2a.png

        推断出了2个同步寄存器和2个异步寄存器,其他信息暂时还看不到,但也和我们预料的基本一致。

        vivado综合的原理图:

abbb20a5056a4aa98ea65795380b0bca.png

        到这一步,基本就是综合出了上面例举的4种不同类型的DFF了。再来看看到FPGA的映射:

d8738e2764644a8bac6526d638e78d89.png

       

        这里有个很有意思的地方就是:4个DFF会映射到2个不同的SLICE里边,而它们所在的SLICE其实都还是有空间的,那这是为什么?

        时钟使能信号CE、时钟信号CLOCK和置位/复位信号SR是DFF的控制信号,它们的一组值构成DFF的一个控制集。在同一个SLICE中,必须保证所有DFF的控制集是相同的。

        在上述Verilog代码中,4个DFF根据控制集的不同,可以分为2类:异步类与同步类(SR区分),所以在FPGA实现时,会把这两类DFF分别映射到2个SLICE里去。

 

        在上面的部分我们还了解了DFF的一些参数如下:

096ab51d7c954f0598bbdd9e4a27ec95.png

  • INIT1:表示FF在上电或者全局复位时初始化值为1
  • INIT0:表示FF在上电或者全局复位时初始化值为0
  • SRHIGH:表示FF在SR置位时(即用户复位),FF的值为1
  • SRLOW:表示FF在SR置位时(即用户复位),FF的值为0

        接下来我举例来说明这几个参数究竟对应着什么:

  1. module test(
  2. input clk,
  3. input rst_n,
  4. input in1,
  5. input in2,
  6. output out1,
  7. output out2
  8. );
  9. reg out1_r = 1'b1; //INIT1
  10. reg out2_r = 1'b0; //INTT0
  11. assign out1 = out1_r;
  12. assign out2 = out2_r;
  13. always@(posedge clk)begin
  14. if(rst)
  15. out1_r <= 1'b1; //SRHIGH
  16. else
  17. out1_r <= in1;
  18. end
  19. always@(posedge clk)begin
  20. if(rst)
  21. out2_r <= 1'b0; //SRLOW
  22. else
  23. out2_r <= in2;
  24. end
  25. endmodule

        这次我们不看图了,我们使用TCL指令:write_verilog -force test.v  来看看实现的网表。如下(截取部分有用信息):

17ec37c2a9b24228814ac3cea9546389.png

        第1个always块的DFF被综合成立FDSE(同步置位set,即1)且上电初始值为1, 这与Verilog代码一致;第2个always块的DFF则被综合成立FDRE(同步复位reset,即0)且上电初始值为0, 同样与Verilog代码一致。

        最后要说明的是:SR默认高电平有效,所以在Xilinx器件的代码中,一般建议使用高电平复位,如果使用低电平复位则需要在前面加个LUT6作为反相器有点浪费资源,而Altera的底层逻辑则是低电平复位有效。当然了,我一般是不建议你使用复位的,除非是一些没办法的控制逻辑。这可以参考:FPGA的复位设计要醒目点啦


四、锁存器

        从寄存数据的角度来讲,触发和锁存器的功能是相同的;它们的区别在于触发是同步时钟控制,而锁存器是电位信号控制。触发器是指有时钟边沿触发的存储单元。锁存器指一个由信号而不是时钟控制的电平敏感的设备。

        锁存器的工作原理:锁存器不同于触发器,锁存器在不锁存数据时,输出端的信号随输入信号变化,就像信号通过一个缓存器一样;一旦锁存信号起锁存作用,则数据被锁住,输入信号不起作用。因此锁存器也称为透明锁存器,值得是不锁存是输出对输入是透明的。

        锁存器具备下列缺点:

  • 对毛刺敏感,不能异步复位,因此在上电后处于不确定的状态。
  • 锁存器会使静态时序分析变得非常复杂,不具备可重用性。(首先, 锁存器没有时钟参与信号传递,无法做 STA;其次,综合工具会将 latch 优化掉,造成前后仿真结果不一致)

        根据锁存器的特点可以看出,在电路设计中,要对锁存器特别谨慎,如果设计经过综合后产生出和设计意图不一致的锁存器,则将导致设计错误,包括仿真和综合。因此,在设计中需要避免产生意想不到的锁存器。

        

        下列场景会产生锁存器(时序逻辑不会产生锁存器):

  • 不完整的组合逻辑语句always块中if-else语句不完整
  • 不完整的组合逻辑语句always块中case语句不完整

        举例1:if语句中缺少else

  1. module test(
  2. input in,
  3. input en,
  4. output reg out
  5. );
  6. always@(*)begin
  7. if(en) out = in;
  8. //else out1_r = in1; //缺少else
  9. end
  10. endmodule

        举例2:组合逻辑的case语句不完整:

  1. module test(
  2. input a,
  3. input b,
  4. input [1:0] en,
  5. output reg out
  6. );
  7. always@(*)begin
  8. case(en)
  9. 2'b00:out = a;
  10. 2'b01:out = b;
  11. //case语句不完整
  12. endcase
  13. end
  14. endmodule

       

        上述两种情况都是,代码缺少完整条件的描述,编译工具认为在此情况下该值保持不变,就推断出了锁存器。

        所以,为了防止锁存器的产生,在组合逻辑一定要将if-else语句、case语句描述完整,而时序逻辑则没有这个问题。        


推荐阅读
相关标签