问:实现稳健的微控制器到 FPGA SPI 接口: 双缓冲区
PWM 的回顾
PWM 模块的顶层接口在这个 Verilog 代码片段中描述。观察该模块使用了位宽参数,并建立了最小和最大占空比限制。最后,观察PWM模块有一个[B - 1:0]输入矢量来设置占空比。没有显示的是在每个 PWM 占空比开始时读取输入的事实。
module PWM #(parameter
B = 12,
D_MIN_PERCENT = 0,
D_MAX_PERCENT = 95
)
(
input wire clk,
input wire enable,
input wire [B - 1:0] d_in,
output reg PWM,
output reg [B - 1:0] cnt
);
同步数据呈现
PWM 设计用于在更大的 uC 到 FPGA SPI 系统中工作。回想一下,SPI自然地使用字节宽度的数据元素进行操作。这与使用B定义的数据宽度操作的PWM形成鲜明对比。为了方便,我们假设 PWM 以16位的位宽度(B)实例化。
当系统更新与 PWM 输入相关的寄存器时,会出现一个问题。如果没有适当的注意,PWM 可能会在更新过程中执行读取操作。其结果是驱动器字节被分割成一个旧字节和一个新字节。这可能导致占空比的显著跃升,持续一个 PWM 周期。如果 PWM 用于LED 指示灯,则可能不会注意到这一点。在更复杂的系统中,故障相当于一个强脉冲,并可能导致系统响铃或变得不稳定,具体取决于错误发生的时间和频率。
解决方案是实现以下三篇文章中提到的双缓冲方案,后面将进行深入讨论。
第1 部分介绍了指导大型系统开发的 Verilog 设计理念。这是介绍寄存器传输电平 (RTL) 设计准则的关键部分,如时钟边界、频闪器的使用和双缓冲区的必要性。 第2部分介绍了 SPI 协议。回想一下,所选协议改编自802.3以太网帧,具有可变有效载荷长度和循环冗余校验 (CRC) 等概念,以提供数据完整性的度量。 第3部分介绍了 uC 到 FPGA 接口的高级视图。那篇文章中最重要的部分是这里重复的框图。
使用一组寄存器来捕获单个字节。当收集到完整的n字节数据时,更新第二个更宽的寄存器。第二个寄存器-双缓冲区-然后用于驱动其他模块,如代表性的 PWM。
双缓冲模块
module double_buffer #(
parameter BYTE_WIDTH = 2,
parameter BASE_ADDRESS = 16'h0200
) (
input wire clk,
input wire [7:0] data,
input wire [15:0] address,
input wire write_strobe,
output reg [((8 * BYTE_WIDTH) - 1): 0] double_buffer_out,
output reg new_data_strobe
);
如图1所示,这个 uC 到 FPGA 接口有一个底层的8位传输过程。在这文章中首先介绍的命令帧中也隐含了一个连续写入操作。为了方便起见,这里将命令帧重复为图2。作为一个例子,让我们假设 PWM 和相关的双缓冲区以地址0x0200实例化。命令帧的写地址将被设置为0x0200,负载的前两个字节将保持所需的16位 PWM 值。
技术贴士 : 数据可能首先访问最高有效字节 (MSB) 或最低有效字节 (LSB)。描述顺序的术语是“端序” (endian)。如果 MSB 先出现,则系统为大端序。如果 LSB 是第一个,则系统是小端序的。本文描述的双缓冲区和关联帧是大端序。
双缓冲区代码
当基址与实例化地址匹配时激活模块。 维护一个计数器指向“制造的”8位寄存器。这个计数器对于连续写入是必不可少的。 对相关的8位寄存器进行频闪。 当N个8位寄存器被填满时,对输出缓冲区进行频闪。
技术贴士 :矢量是导线的一维数组。一个例子是“input wire [15:0] address”,它定义了一个16位的名为 address 的矢量。
结语
最后,虽然这段代码确实很复杂,但 Verilog 生成操作符提供了很大的灵活性。它消除了为每个期望的字节宽度构建独立模块的需要。
文章来源:得捷电子DigiKe