移位寄存器(Shift Registers)是我们在 FPGA 设计中包含的最简单的同步结构之一。
虽然设计简单,但我们的设计中却时刻包含它,包括信号边沿检测、平衡流水线延迟以及实现 UART、SPI 和 I2C 等通信协议。通常,这些用于通信协议的移位寄存器也使用时钟使能,以低于主时钟频率的频率将数据移入或移出移位寄存器。
从最简单的层面来说,这些“寄存器”是相互连接的寄存器。然而,我们如何连接这些寄存器会影响资源利用率。这也与理解《 AMD Ultrafast Design Methodology》息息相关。
如果我们在目标设备中将移位寄存器实现为分立寄存器(discrete registers),这将使用 CLB 切片中可用的寄存器,其中每个CLB包含八个可置位或复位的寄存器。
综合工具正是利用这些寄存器来实现移位寄存器。如果置位或复位,会显著影响设计的性能,因为寄存器之间的布线会限制时钟速率。
library ieee;
use ieee.std_logic_1164.all;
entity shift_example is port (
i_clk : in std_logic;
i_rst : in std_logic;
i_ip : in std_logic;
o_op : out std_logic);
end entity;
architecture rtl of shift_example is
signal s_input : std_logic_vector(31 downto 0);
begin
process(i_clk, i_rst)
begin
if i_rst = '1' then
s_input <= (others => '0');
elsif rising_edge(i_clk) then
s_input <= s_input(s_input'high-1 downto s_input'low) & i_ip;
end if;
end process;
o_op <= s_input(s_input'high);
end architecture;
最终架构;
上面的代码将综合成下面由连接为移位寄存器的触发器组成的结构。

而设备中的实现则显示了CLB中触发器的使用。

但是,如果我们不使用寄存器的置位/复位功能,我们可以利用 SliceM 函数生成器来实现 32 位移位寄存器。在 7 系列器件中,有两种类型的 Slice:SliceL 和 SliceM。虽然两者都可以实现函数生成,但 SliceM CLB 还可以实现分布式存储器,并进而实现固定长度和可变长度的移位寄存器。

我们可以进一步将这个 SRL32 分成两个独立的 SRL16,它们在单个函数发生器内实现两个 16 位移位寄存器。
可以用两种不同的方式实现移位寄存器:可以以一种允许综合工具推断 SliceM 中的 SRL 的方式编写代码,如下所示。
library ieee;
use ieee.std_logic_1164.all;
entity shift_example is port (
i_clk : in std_logic;
i_ip : in std_logic;
o_op : out std_logic);
end entity;
architecture rtl of shift_example is
signal s_input : std_logic_vector(31 downto 0);
begin
process(i_clk)
begin
if rising_edge(i_clk) then
s_input <= s_input(s_input'high-1 downto s_input'low) & i_ip;
end if;
end process;
o_op <= s_input(s_input'high);
end architecture;
最终架构;
查看实现情况表明,SliceM 用于为移位寄存器中的 32 个元素中的 30 个实现 SRL。

如果我们想将整个移位寄存器置位或复位为初始值,我们可以利用所选语言的初始化功能。
当然,推理的另一种选择是实例化原语。在这种情况下,可以利用 Vivado 内部的语言模板。

为了计算所需SRL的深度,我们使用地址引脚。通过将地址引脚设置为特定值,定义移位寄存器的深度:
深度 = (16 x A4) + (8 x A3) + (4 x A2) + (2 x A1) + A0 + 1
如果地址设置为 0000,则从输入到输出的延迟为一个时钟周期。因此,将地址设置为所需延迟时间减一。
本文要探讨的最后一个问题是【可变长度】移位寄存器,它可在运行时进行长度调整。在这种情况下,我们可以在运行过程中动态更改移位寄存器的延迟。
如果我们想要实例化一个可变长度的移位寄存器,可以使用如下例所示的代码:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity shift_example is port (
i_clk : in std_logic;
i_lng : in std_logic_vector(4 downto 0);
i_ip : in std_logic;
o_op : out std_logic);
end entity;
architecture rtl of shift_example is
signal s_input : std_logic_vector(31 downto 0);
begin
process(i_clk)
begin
if rising_edge(i_clk) then
s_input <= s_input(s_input'high-1 downto s_input'low) & i_ip;
end if;
end process;
o_op <= s_input(to_integer(unsigned(i_lng)));
end architecture;
最终架构;
这里我们可以看到在 SliceM 中使用 SRL32 实现的可变长度移位寄存器。

在本文中,多次提到推理或实例化( inference or instantiation)。实际使用过程中,尽量使用实例化方式,不仅使代码可读性更高,后续的可移植性也更强。