HELLO-FPGA_KU5P_example
FPGA demo 说明
![]()
|
Hello-FPGA INFO@HELLO-FPGA.COM |
通过四个按键实现对应位置处的 LED 灯亮灭,检测开发板按键功能是否正常,初步了解Vivado 的使用方法及工作流程。
Vivado 2022.2
Hello-FPGA-KU5P 开发板
按键硬件电路

按键按下为低电平,松开为高电平。

LED 输入为高电平点亮,低电平熄灭。
本实验目标为按键按下 LED 灯点亮,松开 LED 灯熄灭,因此需要让按键输入信号经过一个非门。取反后的按键信号还要经过两个 D 触发器,用于稳定按键输入信号,减少抖动和亚稳态的影响,然后将处理后的信号输出到 LED 。 由于板卡提供的时钟为差分时钟,还需要经过 IBUFGDS(差分信号缓冲器),将差分信号转换为单端信号,至此程序设计完毕,如下图所示。

Vivado 初始界面如图所示,若已有工程可点 Open Project ,选择 xpr 文件打开工程。现在我们建立一个新工程,点击 Create Project。

点击 NEXT 后出现工程命名及选择工程路径界面,更改工程名称 key_test ,选择要将工程存放的路径(路径中不能有中文存在),点击 next。

一直点击 NEXT 直到出现下图界面,此界面为选择板卡型号,可在用户手册或硬件原理图查询,本实验使用的板卡型号为 XCKU5P-2FFVB676I ,因此在界面中选择红框中的型号。点击 next ,再点击 FINISH 。至此工程建立完毕。

此时工程中什么都没有,要建立.v 文件编写代码,点击下图红框中加号或者右侧 add source (快捷键 Alt+A)。

选择 Add or create deisgn sources ,点击 NEXT 。

进入到下图界面,若已有编写好的.V 文件,可点击 Add Files将其加入工程。这里我们选择创建,输入名称后文件在中间显示,点击 FINISH,再点击 OK 。文件成功创建并加入工程。


将以下代码写入文件。 `timescale 1ns / 1ps
module key_test
(
|
input |
sys_clk_p, |
// Differentia system clock 200Mhz input on board |
|
input |
sys_clk_n, |
|
|
input [3:0] |
key_in, |
//input four key signal,when the keydown,the value is 0 |
|
output[3:0] ); |
led |
//LED display ,when the siganl high,LED lighten |
|
reg[3:0] |
led_r; |
//define the first stage register , generate four D |
|
Flip-flop |
|
|
|
reg[3:0] |
led_r1; |
//define the second stage register ,generate four D |
|
Flip flop |
|
|
//======================================================================= ====
//Differentia system clock to single end clock
//======================================================================= ====
wire sys_clk;
IBUFGDS u_ibufg_sys_clk
.I (sys_clk_p),
.IB (sys_clk_n),
.O (sys_clk ) );
always@(posedge sys_clk) begin
led_r <= ~key_in; //first stage latched data end
always@(posedge sys_clk) begin
led_r1 <= led_r; //second stage latched data end
assign led = led_r1;
endmodule
点击红框部分可以使用 RTL ANALSYS 工具查看设计

分析 RTL 图,和预期设计一致。

点击图中加号,选择 Add or create constraints 添加 XDC 约束文件,

写入代码,定义管脚以及类型。管脚定义可查看具体硬件原理图。
点击 Run Synthesis 开始综合

完成后出现下图窗口,点击 ok 即可开始布线。点击右侧 Run Implementation 是一样的功能。

完成后出现下图窗口,勾选 Generate Bitstream 即可开始生成 bit 文件。这里我们先不生成。

点击下图红框中的 Generate Bitstream 生成 bit 文件,若还没有进行之前两步会自动综合然后再进行 bit文件的生成。我们先右键点击进入设置,在设置里勾选 bin 文件,为固化作准备。

然后点击 Generate Bitstream 生成 bit 文件和 bin 文件 ,生成的文件一般在工程 目录下的.runs/impl_ 1 中。
生成好之后,板卡上电,通过 JTAG 连接电脑。点击最下面的 Open Hardware Manager ,之后点击下图位置处的 Open targer , 再点击 auto connect ,连接板卡。

‘ 点击 Program device,选择刚生成的 bit 文件,点击 program ,等待进度条结束,烧录完成。

可以看到按下按键,对应位置的 LED 灯点亮,松开熄灭。
下载 Bit 文件到 FPGA 后,开发板重新上电后配置程序已经丢失,还需要 JTAG 下载。把配置程序固化到开发板上的 FLASH 中,这样不用担心掉电后程序丢失了。
在我们的开发板上有俩个 256Mbit 的 FLASH, 用于存储配置程序。我们不能直接把 Bit文件下载到这个 FLASH 中,需要把 Bit 文件转换成 BIN 文件或者 MCS 文件。下面以下载BIN 文件为例为大家介绍 FLASH 程序的固化。右键板卡,点击 add configuration memory
de7vice.

出现选择 flash 型号界面,本实验使用板卡 flash 型号为 MT25QU256ABA1EW9 ,选择如下型号即可。

选择刚才生成的 bin 文件,等待完成即可。这里需要注意,有的板卡在连接下载器时重新上电后并不会运行固化后的程序,这时需要断开下载器,重新上电。拔掉下载器后,重新上电,按下按键,可以发现板卡能正常按照程序点亮 LED 灯。本实验结束。
通过板卡内置时钟和计数器实现 LED 流水灯,检测开发板 LED 灯是否正常。
Vivado 2022.2
Hello-FPGA-KU5P 开发板

LED 输入为高电平点亮,低电平熄灭。
FPGA 的设计中通常使用计数器来计时,板卡时钟频率为 200MHz,即一秒钟 2* 108 个时钟周期,换成二进制为 28 位二进制数,本实验中流水灯循环一次所需时间为 2 秒,因此计数器位宽最小为 29 位,实验中选择 32 位;每 0.5 秒改变一次灯的状态,其他时间保持不变。最终代码如下:
`timescale 1ns / 1ps
module led_test #(
parameter TIMER_LED1 = 32'd99_999_999,
parameter TIMER_LED2 = 32'd199_999_999,
parameter TIMER_LED3 = 32'd249_999_999,
parameter TIMER_LED4 = 32'd399_999_999 )
(
input sys_clk_p,
input sys_clk_n,
input rst_n,
output reg[3:0] led, output fan_pwm
);
//define the time counter reg [31:0] timer;
assign fan_pwm = 1'b0;
wire sys_clk;
IBUFGDS u_ibufg_sys_clk (
.I (sys_clk_p),
.IB (sys_clk_n),
.O (sys_clk ) );
always @(posedge sys_clk or negedge rst_n)
begin
if (~rst_n)
timer <= 32'd0;
else if (timer == TIMER_LED4)
timer <= 32'd0; else
timer <= timer + 1'b1; end
always @(posedge sys_clk or negedge rst_n)
begin
if (~rst_n)
led <= 4'b0000;
else if (timer == TIMER_LED1)
led <= 4'b0001;
else if (timer == TIMER_LED2) begin
led <= 4'b0010; end
else if (timer == TIMER_LED3)
led <= 4'b0100;
else if (timer == TIMER_LED4)
led <= 4'b1000;
end
endmodule
按照按键实验中的步骤建立工程,创建 led_test.v 文件,并将代码写入 led_test.v 文件中。
Vivado 有内部工具能来输出波形验证程序设计结果和我们的预期是否一致。点击 4.2 中的加号,选择 Add or create simulation sources 添加仿真文件。

点击 create file

命名需要和源文件有区别,此处命名为 vtf_key_test。

点击依次 ok ,finish ,ok ,yes ,右侧 Source 中的 simulation source 会出现此仿真文件,右键该文件,点击 set as top ,将其至于顶层,并写入如下代码。

`timescale 100ps / 1ps
module vtf_led_test;
reg sys_clk_p;
wire sys_clk_n;
reg rst_n;
wire [3:0] led;
led_test #(
.TIMER_LED1 (32'd9), .TIMER_LED2 (32'd19), .TIMER_LED3 (32'd29),
.TIMER_LED4 (32'd39)
) uut (
.sys_clk_p(sys_clk_p),
.sys_clk_n(sys_clk_n),
.rst_n(rst_n),
.led(led)
);
initial begin
sys_clk_p = 0;
rst_n = 0;
#1000;
rst_n = 1;
#20000;
end
always #25 sys_clk_p = ~ sys_clk_p;
assign sys_clk_n=~sys_clk_p;
endmodule
保存之后点击右侧 simulation 下的 run simulation 中 run behavioral simulation

没有出错的话,可以直接出现波形图。默认仿真 1ns ,红框中的部分可以设置仿真时间,左边的箭头表示一直运行直到停止,由于本实验中仿真程序没有结束,因此点击该按钮将一直运行程序,点击右下角有 T 的箭头为程序运行右边设定的时间。 由于仿真时间过长,因此仿真程序中缩减了每一个状态持续的时间。这可以和仿真波形图中看出,说明代码基本正确。

按照之前的步骤编译,生成 bit文件和 bin 文件,烧录至板子中,观察到 led 灯按所定顺序亮灭。
通过 PLL 锁相环生成不同频率的时钟信号。
Vivado 2022.2
Hello-FPGA-KU5P 开发板
PLL(phase-locked loop) ,即锁相环。是 FPGA 中的重要资源。由于一个复杂的 FPGA 系统往往需要多个不同频率的时钟信号。所以,一个 FPGA 芯片中 PLL 的数量是衡量 FPGA 芯片能力的重要指标。FPGA 的设计中,时钟系统的 FPGA 高速的设计极其重要,一个低抖动, 低延迟的系统时钟会增加 FPGA 设计的成功率。本实验将通过使用 PLL, 输出一个方波。
FPGA 使用了专用的全局(Global)和区域(Regional)IO 和时钟资源来管理设计中各种的时钟需求。Clock Management Tiles(CMT)提供了时钟合成(Clock frequency synthesis) ,倾斜矫正(deskew) ,过滤抖动(jitter filtering)功能。每个 CMTs 包含一个 MMCM(mixed-mode clock
manager)和一个 PLL。如下图所示,CMT 的输入可以是 BUFR,IBUFG,BUFG,GT,BUFH,本地布线(不推荐使用),输出需要接到 BUFG 或者 BUFH 后再使用。

(1)混合模式时钟管理器(MMCM)
MMCM 用于在与给定输入时钟有设定的相位和频率关系的情况下,生成不同的时钟信
号。 MMCM 提供了广泛而强大的时钟管理功能,
MMCM 内部的功能框图如下图所示:

(2)数字锁相环(PLL)
锁相环(PLL)主要用于频率综合。使用一个 PLL 可以从一个输入时钟信号生成多个时钟信号。
PLL 内部的功能框图如下图所示:

本实验中为大家演示如果调用 Xilinx 提供的 PLL IP 核来产生不同频率的时钟, 并把其中的一个时钟输出到 FPGA 外部 IO 上, 也就是开发板扩展口的 PIN3 脚。
下面为程序设计的详细步骤。
按照第一节内容建立工程,命名为 PLL_test。
点击下图中红框部分,进入 IP 核配置界面。

选择 Clocking Wizard ,并双击。

进入到 PLL IP 核配置界面。首先配置输入时钟,选择 200MHz ,source 中选择 Differential clock capable pir(差分时钟)。

在 output clocks 界面配置我们 PLL IP 核的输出时钟信号。FREQ 为时钟频率,这里我们测试 200 ,100 和 50MHz ,PHASE 为相位配置,这里暂时不配置,DUTY CYCLE 为占空比,选择默认的 50 即可。点击 ok ,然后点击 Generate 按钮生成 PLL IP 的设计文件。

IP 核已经生成完毕,现在我们要编写一个顶层文件来调用 IP 核,按照之前的步骤建立一个PLL_test.v 文件,写入如下代码:
`timescale 1ns / 1ps
////////////////////////////////////////////////////////////////////////////////// module pll_test(
input wire sys_clk_p,
input wire sys_clk_n,
input wire rst_n,
output wire pll_clk_ 1,
output wire pll_clk_2 );
wire locked;
wire pll_clk_o;
clk_wiz_0 clk_wiz_0_inst (
.clk_in1_p (sys_clk_p ),
.clk_in1_n (sys_clk_n ),
.clk_out1 (pll_clk_ 1),
.clk_out2 (pll_clk_2),
.reset (~rst_n),
.locked (locked) );
endmodule 其中包含了IP 核(其他模块)的调用,括号内为当前函数中的名称,用于连接调用模块的输入输出接口,其名称可与模块中的名称不一致,输出接 wire 型,输入可以是 wire、 reg 或者常数;前面的部分(以.clk_in1_p 为例),clk_in1_p 的位置处要填写调用模块中的输入或者输出接口,名称要与该模块的定义一致。模块 u 名称分两部分,前面为模块名称,后面可自己定义调用模块在主模块中的名称,用于当调用多个相同模块时,便于区分。IP 核的例化可在如下图所示的地方直接复制过来,注意:括号内的名称要改为主函数中的寄存器名称。保存后.v 文件自动变为顶层模块,IP 核变为其子模块。

仿真文件较好编写,只需要编写一个差分时钟信号和复位信号即可。按照之前的步骤添加仿真文件,并写入如下仿真代码。
`timescale 1ns / 1ps
module pll_test_tb();
// Inputs
reg sys_clk_p;
wire sys_clk_n;
reg rst_n;
// Outputs
wire pll_clk_ 1;
wire pll_clk_2;
wire clk_out;
// Instantiate the Unit Under Test (UUT) pll_test uut (
.sys_clk_p(sys_clk_p),
.sys_clk_n(sys_clk_n),
.rst_n(rst_n),
.pll_clk_ 1(pll_clk_ 1),
.pll_clk_2(pll_clk_2) );
// Generate 200 MHz differential clock (2.5 ns period)
initial begin
sys_clk_p = 0;
forever #2.5 sys_clk_p = ~sys_clk_p;
end
assign sys_clk_n = ~sys_clk_p; // Reset sequence
initial begin
rst_n = 0; // Active low reset
#100; // Hold reset for 100 ns
rst_n = 1 ; // Release reset
end
endmodule
点击运行方仿真,运行 10us ,可看见如下波形图。可以看到 pll_clk_ 1 输出 100MHz 时钟, pll_clk_2 输出 50MHz 时钟。PLL 锁相环实验结束

通过串口收发程序编写,了解串口工作原理,掌握串口通信方法。本工程中涉及了状态机的使用,帮助掌握状态机。
Vivado 2022.2
Hello-FPGA-KU5P 开发板串口调试助手
本 文 所 述 的 串 口 指 异 步 串 行 通 信 ,异 步 串 行 是 指 UART( Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。UART 是一个并行输入成为串行输出的芯片,通常集成在主板上。UART 包含 TTL 电平的串口和 RS232 电平的串口。 TTL 电平是 3.3V 的,而 RS232 是负逻辑电平,它定义+5~+12V 为低电平,而-12~-5V 为高电平, MDS2710 、MDS SD4 、EL805 等是 RS232 接口,EL806 有 TTL 接口。
串行接口按电气标准及协议来分包括 RS-232-C、RS-422、RS485 等。RS-232-C、RS-422 与 RS- 485 标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。
开发板的串口通信通过 USB 转串口方式,主要是解决很多人电脑不带串口接口的问题,所以这里不涉及到电气协议标准,用法和 TTL 电平串口类似。FPGA 芯片使用 2 个 IO 口和 USB转串口芯片 CH9102 相连。

消息帧从一个低位起始位开始,后面是 7 个或 8 个数据位,一个可用的奇偶校验位和一个或几个高位停止位。接收器发现开始位时它就知道数据准备发送,并尝试与发送器时钟频率同步。如果选择了奇偶校验,UART 就在数据位后面加上奇偶校验位。奇偶校验位可用来帮助错误校验。在接收过程中,UART 从消息帧中去掉起始位和结束位,对进来的字节进行奇偶校验,并将数据字节从串行转换成并行。UART 传输时序如下图所示:

从波形上可以看出起始位是低电平,停止位和空闲位都是高电平,也就是说没有数据传输时是
高电平,利用这个特点我们可以准确接收数据,当一个下降沿事件发生时,我们认为将进行一次数据传输。
常见的串口通信波特率有 2400 、9600 、115200 等,发送和接收波特率必须保持一致才能正确通信。波特率是指 1 秒最大传输的数据位数,包括起始位、数据位、校验位、停止位。假如通信波特率设定为 9600 ,那么一个数据位的时间长度是 1/9600 秒。
串口接收模块是个参数化可配置模块,参数“CLK_FRE”定义接收模块的系统时钟频率,单位
是 MHz ,参数“BAUD_RATE”是波特率。接收状态机状态转换图如下:

S_IDLE”状态为空闲状态,上电后进入“S_IDLE” ,如果信号“rx_pin” 下降沿,代表串口起始位到来,状态机进入状态“S_START”,等一个 BIT 时间起始位结束后进入数据位接收状态
“S_REC_BYTE”,本实验中数据位设计是 8 位,等待八位数据接收完成以后进入“S_STOP”状态,在“S_STOP” 只等待了半个 BIT 时间 ,这是因为如果等待了一个周期,有可能会错过下一个数
据的起始位判断,最后进入“S_DATA”状态,将接收到的数据送到其他模块。在这个模块我们提一点:为了满足采样定理,在接受数据时每个数据都在波特率计数器的时间中点进行采样,以避免数据出错的情况.
发送模式设计和接收模块相似,也是使用状态机,状态转换图如下:

上电后进入“S_IDLE” 空闲状态,如果有发送请求,进入发送起始位状态“S_START”,起始位发送完成后进入发送数据位状态“S_SEND_BYTE”, 数据位发送完成后进入发送停止位状态“S_STOP”,停止位发送完成后又进入空闲状态。在数据发送模块中,从顶层模块写入的数据直接传递给寄存器 ≤tx_reg ’,并通过 ≤tx_reg’寄存器模拟串口传输协议在状态机的条件转换下进行数据传送。
按之前的步骤建立.v 文件,并将代码写入其中,之后编写仿真测试代码,测试内容位将八位数据用 tx 模块转为串行数据发送给 rx 模块并输出一个八位数据。
仿真结果如图所示,向 tx 发送 8’h55 ,8’ha5 ,8’h3c ,rx 模块可以正确输出数据,证明程序可以实现串口通信功能。

由于开发板的串口使用 USB 转串口芯片,首先要安装串口驱动程序,正确安装驱动状态如下图所示(当然要连接串口的 USB 到电脑)。如果没有正确连接请参考本文附录“ 串口驱动的安装 ”。

串口驱动正常的状态
从图中可以看出系统给串口分配的串口号是“COM3 ”,串口号的分配是系统完成的, 自动分配情况下每台电脑可能会有差异,笔者这里是“COM3 ”,使用串口号时要根据自己的分配情况选择。
打开串口调试,端口选择“COM3 ”(根据自己情况选择),波特率设置 115200,检验位选None,数据位选 8,停止位选 1,然后点击“打开串口 ”。如果找不到这个小软件使用 windows搜索功能,在黑金给的资料文件夹里搜索“ 串口调试 ”。

打开串口以后,每秒可收到“HELLO ALINX ”,在发送区输入框输入要发送的文字,点击“手动发送 ”,可以看到接收到自己发送的字符。

本文主要讲解按键消抖原理及程序编写。
Vivado 2022.2
Hello-FPGA-KU5P 开发板
在按键按下或松开的时候,按键输入值是有抖动的,无论按下去是多平稳,都难以消除抖动,按键消抖方式有很多,本实验主要是通过 FPGA 计时来消抖。实验中设计了一个计数器,当按键输入有变化时,计时器清零,否则就累加,直到加到一个预定值(例如 10ms) ,就认为按键稳定,输出按键值,这样就得到以后没有抖动的按键值。
按之前的步骤建立仿真测试文件
测试代码测试了抖动按键输入以及稳定输入,仿真波形图如下图所示。可以看到前面高频噪声部分输出按键并没有输出,等到按键稳定按下后才输出低电平;按键松开阶段也是在按键稳定在高电平后才输出,抖动部分被消除。证明程序可以实现消抖功能。

SD 卡是现在嵌入式设备重要的存储模块, 内部集成了 nand flash 控制器,方便了主机的的管理。本实验主要是练习对 sd 卡的扇区进行读写。
Vivado 2022.2
Hello-FPGA-KU5P 开发板
开发板上装有一个 Micro SD 卡座,FPGA 通过 SPI 数据总线访问 Micro SD 卡, SD 卡座和 FPGA 的硬件电路连接如下:

在 SD 卡数据读写速度要求不高的情况下,选用 SPI 通信模式可以说是一种最佳的解决方案。因为在 SPI 模式下,通过四条线就可以完成所有的数据交换。本实验将为大家介绍 FPGA通过 SPI 总线读写 SD 卡。要完成 SD 卡的 FPGA 读写,用户需要理解 SD 卡的命令协议。
SD 卡的协议是一种简单的命令/响应的协议。全部命令由主机发起,SD 卡接收到命令后并返回响应数据。根据命令的不同,返回的数据内容和长度也不同。SD 卡命令是一个 6 字节组成的命令包,其中第一个字节为命令号, 命令号高位 bit7 和 bit6 为固定的“01“,其它 6 个bit 为具体的命令号。第 2 个字节到第 5 个字节为命令参数。第 6 个字节为 7 个 bit 的 CRC校验加 1 个 bit 的结束位。 如果在 SPI 模式的时候, CRC 校验位为可选 。如下图所示, Command 表示命令,通常使用十进制表示名称,例如 CMD17,这个时候 Command 就是十进制的 17 。SD 卡具体的协议本实验不讲解,可自行找相关资料学习。

SD 卡对每个命令会返回一个响应,每个命令有一定的响应格式。响应的格式跟给它的命令号有关。在 SPI 模式中,有三种响应格式:R1 ,R2 ,R3。



(1)初始化
1.上电后延时至少 74clock ,等待 SD 卡内部操作完成;
2.片选 CS 低电平选中 SD 卡;
3.发送 CMD0 ,需要返回 0x01 ,进入 Idle 状态;
4.为了区别 SD 卡是 2.0 还是 1.0 ,或是 MMC 卡,这里根据协议向上兼容的,首先发送只有SD2.0 才有的命令 CMD8 ,如果 CMD8 返回无错误,则初步判断为 2.0 卡,进一步循环发送命令 CMD55+ACMD41 ,直到返回 0x00 ,确定 SD2.0 卡;
5.如果 CMD8 返回错误则判断为 1.0 卡还是 MMC 卡,循环发送 CMD55+ACMD41,返回无错误,则为 SD1.0 卡,到此 SD1.0 卡初始成功,如果在一定的循环次数下,返回为错误,则进一步发送 CMD1 进行初始化,如果返回无错误,则确定为 MMC 卡,如果在一定的次数下,返回为错误,则不能识别该卡,初始化结束。(通过 CMD16 可以改变 SD 卡一次性读写的长度);
6.CS 拉高。
(2)读步骤
1.发送 CMD17(单块)或 CMD18(多块)读命令,返回 0X00;
2.接收数据开始令牌 fe(或 fc)+正式数据 512Bytes + CRC 校验 2Bytes 默认正式传输的数据长度是 512Bytes。
(3)写步骤
1. 发送 CMD24(单块)或 CMD25(多块)写命令,返回 0X00;
2. 发送数据开始令牌 fe(或 fc)+正式数据 512Bytes + CRC 校验 2Bytes
sd_card_top 包含 3 个子程序,分别为 sd_card_sec_read_write.v ,sd_card_cmd.v 和spi_master.v 文件。它们的逻辑关系如下图所示:

以下为 sd_card_sec_read_write 模块端口说明:
|
信号名称 |
方向 |
说明 |
|
clk |
in |
输入时钟信号 |
|
rst |
in |
异步复位信号,高有效 |
|
sd_init_done |
out |
初始化完成 |
|
sd_sec_read |
in |
扇区读请求 |
|
sd_sec_read_addr |
in |
扇区读地址 |
|
sd_sec_read_data |
out |
扇区读数据 |
|
sd_sec_read_data_valid |
out |
扇区读数据有效 |
|
sd_sec_read_end |
out |
扇区读完成 |
|
sd_sec_write |
in |
扇区写请求 |
|
sd_sec_write_addr |
in |
扇区写应答 |
|
sd_sec_write_data |
in |
扇区写数据 |
|
sd_sec_write_data_req |
out |
扇区写请求数据读取,提前 sd_sec_write_data 一个周期 |
|
sd_sec_write_end |
out |
扇区写完成 |
|
spi_clk_div |
in |
SPI 分频时钟 |
|
cmd_req |
in |
命令请求 |
|
cmd_req_ack |
out |
命令请求应答 |
|
cmd_req_error |
out |
命令请求错误 |
|
cmd |
in |
命令,命令+参数+CRC ,一共 48bit |
|
cmd_r1 |
in |
命令期待的 R1 响应 |
|
cmd_data_len |
in |
命令后读取的数据长度,大部分命令没有读取数据 |
|
block_read_req |
in |
块数据读请求 |
|
block_read_valid |
out |
块数据读数据有效 |
|
block_read_data |
out |
块数据读数据 |
|
block_read_req_ack |
out |
块数据读请求应答 |
|
block_write_req |
in |
块数据写请求 |
|
block_write_data |
in |
块数据写数据 |
|
block_write_data_rd |
out |
块数据写数据请求,提前 block_write_data 一个时钟周期 |
|
block_write_req_ack |
out |
块数据写请求应答 |
sd_card_sec_read_write 模块有一个状态机,首先完成 SD 卡初始化,下图为模块的初始化状态机转换图,首先发送 CMD0 命令,然后发送 CMD8 命令,再发送 CMD55 ,接着发送ACMD41 和 CMD16 。如果应答正常,sd 卡初始化完成,等待 SD 卡扇区的读写命令。

然后等待扇区读写指令,并完成扇区的读写操作,下图为模块的读写状态机转换图。

在此模块中定义了两个参数,SD 卡的初始化过程是需要先用慢时钟来发送命令和配置,等待初始化成功后再用快时钟来进行数据读写。
sd_card_cmd 模块端口的说明如下:
|
信号名称 |
方向 |
说明 |
|
sys_clk |
in |
输入时钟信号 |
|
rst |
in |
异步复位信号,高有效 |
|
spi_clk_div |
in |
SPI 分频时钟 |
|
cmd_req |
in |
命令请求 |
|
cmd_req_ack |
out |
命令请求应答 |
|
cmd_req_error |
out |
命令请求错误 |
|
cmd |
in |
命令,命令+参数+CRC ,一共 48bit |
|
cmd_r1 |
in |
命令期待的 R1 响应 |
|
cmd_data_len |
in |
命令后读取的数据长度,大部分命令没有读取数据 |
|
block_read_req |
in |
块数据读请求 |
|
block_read_valid |
out |
块数据读数据有效 |
|
block_read_data |
out |
块数据读数据 |
|
block_read_req_ack |
out |
块数据读请求应答 |
|
block_write_req |
in |
块数据写请求 |
|
block_write_data |
in |
块数据写数据 |
|
block_write_data_rd |
out |
块数据写数据请求,提前 block_write_data 一个时钟周期 |
|
block_write_req_ack |
out |
块数据写请求应答 |
|
nCS_ctrl |
out |
到 SPI master 控制器,cs 片选控制 |
|
clk_div |
out |
到 SPI Master 控制器,时钟分频参数 |
|
spi_wr_req |
out |
到 SPI Master 控制器,写一个字节请求 |
|
spi_wr_ack |
in |
来自 SPI Master 控制器,写请求应答 |
|
spi_data_in |
out |
到 SPI Master 控制器,写数据 |
|
spi_data_out |
in |
来自 SPI Master 控制器,读数据 |
sd_card_cmd 模块主要实现 sd 卡基本命令操作,还有上电初始化的 88 个周期的时钟,数据块的命令和读写的状态机如下。

从 SD2.0 的标准里我们可以看到,从主控设备写命令到 SD 卡, 最高两位 47~46 位必须为“01” ,代表命令发送开始。所以代码中都是将 48 位命令的高八位与十六进制 0x40 做或操作得到的结果再写入
spi_master 模块主要完成 SPI 一个字节的读写,当 SPI 状态机在 idle 的时候,检测到wr_req 的信号为高,会产生 8 个 DCLK ,并把 datain 的数据从高位依次输出到 MOSI 信号线上。如下图的 ILA 观察波形可见,MOSI 在 8 个 DCLK 的输出数据为 datain 的值 0x58。同时 spi_master 程序也会读取 MISO 输入的数据,转换成 8 位的 data_out 数据输出实现
SPI 的一个字节的数据读取。
下载实验程序后,可以看到 LED 显示一个二进制数字,这个数字是存储在 sd 卡中第一扇区的第一个数据,数据是随机的,这个时候按键 KEY2 按下,数字加一,并写入了 sd 卡,再次下载程序,可以看到 LED 直接显示更新后的数据, 用户可以结合 vivado 中的逻辑分析仪ila 查看更明显。
本文主要讲解按键消抖原理及程序编写。
Vivado 2022.2
Hello-FPGA-KU5P 开发板
串口调制助手
LM75A 是一个高速 I2C 接口的温度传感器,可以在 -55°C ~ +125°C 的温度范围内将温度直接转换为数字信号,并可实现 0.125°C 的精度。控制器可以通过 I2C 总线直接读取其内部寄存器中的数据,并可通过 I2C 对 4 个数据寄存器进行操作, 以设置成不同的工作模式。 LM75A 有 3 个可选的逻辑地址管脚,使得同一点线上可同时连接 8 个器件而不发生地址冲突。LM75A 可配置成不同的工作模式。它可设置成在正常工作模式下周期性地对环境温度进行监控,或进入关断模式来将器件功耗降至最低。OS 输出有 2 种可选的工作模式:OS 比较器模式和 OS 中断模式,OS 输出可选择高电平或低电平有效。正常工作模式下,当器件上电时,OS 工作在比较器模式、温度阈值为 80°C ,滞后阈值为 75°C 。低功耗设计,工作电流典型值为 250uA ,掉电模式为 3.5uA;宽工作电压范围:2.8V ~ 5.5V 。LM75 管脚说明如下图:
SDA: I2C 串行双向数据线,开漏口
SCL: I2C 串行时钟输入,开漏口
OS: 过热关断输出。开漏输出
GND: 地,连接到系统地
A2: 用户定义的地址位 2
A1: 用户定义的地址位 1
A0: 用户定义的地址位 0
Vcc: 电源
温度寄存器是一个只读寄存器,包含 2 个 8 位的数据字节,由一个高数据字节(MS)和一个低数据字节(LS)组成。这两个字节中只有 11 位用来存放分辨率为 0. 125℃的 Temp 数据(以二进制补码数据的形式) 。对于 8 位的 I2C 总线来说,只要从 LM75A 的"00 地址"连续读两个字节即可(温度的高 8 位在前)。

根据 11 位的 Temp 数据来计算 Temp 值的方法:
若 D10=0 ,温度值 (℃) =(+Temp 数据) ×0. 125℃;
若 D10= 1 ,温度值 (℃) =(-Temp 数据的二进制补码) ×0. 125℃。
b) 配置寄存器(地址 0x01)
配置寄存器为 8 位可读写寄存器,其位功能分配如下:

B7-B5:保留,默认为 0。
B4-B3:用来编程 OS 故障队列。00 到 11 代表的值为 1 、2 、4 、6 ,默认值为 0。
B2:用来选择 OS 极性。B2=0 ,OS 低电平有效(默认);B2= 1 ,OS 高电平有效。
B1:选择 OS 工作模式。B1=0 ,配置成比较器模式,直接控制外围电路;B1= 1 ,OS 控制输出功能配置成中断模式, 以通知 MCU 进行相应处理。
B0:选择器件工作模式。B0=0 ,LM75A 处于正常工作模式(默认);B0= 1 ,LM75A 进入关断模式。
滞后寄存器是读/写寄存器,也称为设定点寄存器,提供了温度控制范围的下限温度。每次转换结束后,Temp 数据(取其高 9 位)会与该寄存器数据比较,当环境温度低于设定温度时,根据当前模式(比较/中断)控制 OS 引脚响应。寄存器包含 2 个 8 位数据,实际使用 9 位存储设定点数据(二进制补码格式)。分辨率为 0.5℃ 。数据格式如下表所示,默认为 75℃

超温关断寄存器提供了温度控制范围的上限温度。每次转换结束后,Temp 数据(取其高 9位)会与该寄存器数据比较,当环境温度高于设定温度时,根据当前模式(比较/中断)控制OS 引脚响应。数据格式如上表所示,默认为 80℃。
OS 输出为开漏输出口。为了观察到这个输出的状态,需要接一个外部上拉电阻,其阻值应当足够大(高达 200kΩ) , 以减少温度读取误差。OS 输出可通过编程配置寄存器的 B2 位设置为高或低有效。OS 设为低有效。

当 LM75A 工作在比较器模式时,当温度高于 Tos 时,OS 输出低电平。此时采取了降温措施,启动降温设备(如风扇) ,直到温度再降到 Thyst ,则停止降温,因此在这种模式下, LM75A 可以直接控制外部电路来保持环境温度;而在中断模式,则在温度高于 Tos 或低于Thyst 时产生中断。注意:在中断模式下,只有当 MCU 对 LM75A 进行读操作后,其中断信号才会消失(图中 OS 变为高电平)。
LM75A 可以通过 SCL 和 SDA 作为从器件连接到 I2C 总线上。主控器必须提供 SCL时钟信号,可以通过 SDA 读出器件数据或将数据写入到器件中。LM75A 从地址(7 位地址)的低 3 位可由地址引脚 A2、A1 和 A0 的逻辑电平来决定。地址的高 4 位预先设置为" 1001"。同一总线上可连接 8 个器件而不会产生地址冲突。 由于输入管脚 SCL 、SDA 、A2-A0 内部无偏置,因此在任何应用中它们都不能悬空。
如下为板卡中 LM75 原理图:

该程序功能是 FPGA 驱动 LM75 温度传感器不断地读取温度值并送到串口进行显示。
代码说明:
temp_test.v 是顶层模块,包含了 i2c_read_lm75 ,hextobcd 和 uart_send 模块;
i2c_read_lm75.v 是 LM75 的温度读取模块,实时读取温度值;
hextobcd.v 为十六进制转 BCD 模块;
uart_send.v 是温度数据发送模块,每隔一段时间发送一次温度

这个模块实现了一个完整的I2C 主控制器,专门用于与 LM75 温度传感器通信并读取温度数据。
首先,模块通过两个关键寄存器生成精确的 I2C 时钟信号(SCL):cnt_delay(8 位)作为分频计数器,将系统时钟(50MHz)分频为 250kHz 的 SCL 时钟;cnt(3 位)则进一步将 SCL 周期划分为 4 个相位:上升沿(SCL_POS) 、高电平(SCL_HIG) 、下降沿(SCL_NEG)和低电平(SCL_LOW)
模块的核心是一个状态机(cstate) ,它按顺序执行以下 I2C 协议步骤:IDLE 状态:初始化等待,当计时器 tim[25]触发时加载设备读地址 0x91(DEVICE_READ)到 db_r 寄存器。START 状态:在 SCL 高电平时拉低 SDA ,产生起始条件。ADDR 状态:逐位发送 7 位设备地址(0x48)和读标志位(1) ,随后通过 ACK1 、DATA1 、ACK2 、DATA2 状态完成两字节温度数据的读取,最后通过 NACK 和 STOP 状态结束通信。数据传送通过 sda_r(数据寄存器)和 sda_link(方向控制)实现:发送时:sda_link= 1,Master 驱动 sda_r 到 SDA 线。接收时:sda_link=0,Master 释放 SDA 线(高阻态) ,读取 Slave 数据。读取的温度数据存储在 read_data(16 位)中:高 8 位包含符号和整数部分 ; 低 8 位包含小数部分 。模块还完成 了数据格式转换: 将补码转换为绝对值 , 通过(data_conv* 125)/32 计算实际温度值,最终输出 17 位 data ,包含符号位和温度数值
这个模块实现了一个将 16 位十六进制数(hex)转换为 20 位 BCD 码(dec)的数字电路,采用流水线结构和查表法来提高转换效率。
核心处理流程:
(1) 符号处理:
- 通过 rrhex = hex[ 16] ? ~hex[15:0]+1'b1 : hex[15:0] 完成补码转换
- 将 16 位数值拆分为 4 个 4 位段存入 rhex[3:0]数组
(2) 查表转换:
- 使用 4 个 always 块实现并行查表:
* rhexd:处理最高 4 位 (×4096 权重)
* rhexc:处理次高 4 位 (×256 权重)
* rhexb:处理第三 4 位 (×16 权重)
* rhexa:处理最低 4 位 (×1 权重)
(3) BCD 加法器:
- 采用 4 级流水线加法(addbcd4 函数):
1) 从低位开始逐级相加
2) 每级结果包含 6 位(高 2 位是进位,低 4 位是 BCD 码)
3) 每级加法后自动进行 BCD 校正(加 6 调整)
3. 输出组合:
- 最终将 5 级结果{rese,resd[3:0],resc[3:0],resb[3:0],resa[3:0]}拼接为 20 位 BCD 码
该模块实现了一个 全双工 UART 串口通信系统,核心功能包括:发送功能:将 20 位 BCD格式的温度数据(read_bcd_Temp) 格式化为可读字符串(如 "Temp: 25.5\r\n" 或 "Temp:
-25.5\r\n"),通过串口发送到上位机。接收功能:监听上位机发送的数据,并支持回传(Echo功能)。 自动重发:通过状态机控制,定时重新发送温度数据。
.状态机工作流程:IDLE 状态:初始化寄存器,立即跳转到 SEND 状态。 SEND 状态:根据 sign选择发送模板(tx_str0 正温度或 tx_str1 负温度) 。通过 tx_cnt 依次发送 14 个字符(包括温度值、符号、换行符等)。发送完成后转入 WAIT 状态。WAIT 状态:接收处理:若 rx_data_valid= 1,
将接收到的数据回传(Echo)。 超时重发:当 wait_cnt > Dly_wait 时,返回 SEND 状态重新发送温度数据。
其中调用的 uart_tx,uart_rx ,为第 4 节中编写的子模块,将.V 文件加入工程即可直接调用。
其中包含了之前的三个核心模块,还有一个 PLL 锁相环,将 200MHz 差分系统时钟,转换为50MHz 的单端时钟信号,传输给各个模块。
按照之前的步骤,编译生成 bit 文件,连接板卡,将 bit文件烧录至板卡。打开串口工具,打开通道,可看到温度数据一秒钟接收一个。实验成功


本实验通过使用开源软件 opencores 上的 I2C master 控制器去控制 I2C 接口的EEPROM 读写,练习如何有效的使用开源代码提升开发效率。
Vivado 2022.2
Hello-FPGA-KU5P 开发板串口调试助手
I2C 是一种双线制串行通信协议,由两条线构成:时钟线(SCL)和数据线(SDA)。它使用一种主从结构,其中有一个主设备(Master)和一个或多个从设备(Slave) 。主设备控制通信过程,而从设备被动地接受主设备的控制并进行数据的发送和接收。下面是 I2C 通信的基本步骤:
(1)主设备发送一个开始信号,表示开始通信。
(2)主设备发送一个从设备的地址和读/写位。地址用于选中特定的从设备,读/写位用于指示数据的方向(读或写)。
(3)选中的从设备确认收到地址并发送应答信号。
(4)主设备继续发送或接收数据。
(5)数据传输完成后,主设备发送停止信号,表示通信结束。
I2C 通信过程是由一系列的状态和操作组成的。
(1)空闲状态:SDA 和 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线, 由两条信号线各自的上拉电阻把电平拉高。
(2)启动信号:SCL 保持高电平,SDA 信号拉低,为启动信号,标志着一次数据传输的开始。启动信号是由主控器主动建立的,在建立该信号之前 I2C 总线必须处于空闲状态。
(3)停止信号:SCL 保持高电平期间,数据线 SDA 被释放,使得 SDA 返回高电平,称
为 I2C 总线的停止信号,它标志着一次数据传输的终止。停止信号也是由主控器主动建立的,建立该信号之后,I2C 总线将返回空闲状态。

(4)数据传送:在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。进行数据传送时,在SCL 呈现高电平期间,SDA 上的电平必须保持稳定,低电平为数据 0 ,高电平为数据 1 。只有在 SCL 为低电平期间,才允许 SDA 上的电平改变状态。
(5)应答信号:I2C 总线上的所有数据都是以 8 位字节传送的,发送器每发送一个字节,就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK 简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK的要求是,接收器在第 9 个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK 信号,以通知被控发送器结束数据发送,并释放 SDA 线,以便主控接收器发送一个停止信号。

传输过程:主机先向 SDA 发送一个起始信号后(表示主机要开始传输数据了) ,再发送从机
地址(7 位) + 读/写位(0 写/1读)。紧接着每一个从机开始将自己的地址和主机发送的地址进行匹
配,如果发现相同,则从机会发送一个应答信号 ACK(表示从机已匹配上,可以与主机进行数据传输) 。主机接收到从机的 ACK 信号后,主机继续会再发送一个从机内部内存的地址来指定主机要写入的内存位置。从机接收到从机发送的数据后会产生应答信号 ACK 返回,主机接收到从机的 ACK 信号后,主机开始进行数据的传输。每次从机接收到数据都会应答发送 ACK 信号,当主机发送完数据不想发了,会向 SDA 发送停止信号 P 。注意:无论主机还是从机,发送或接收数据都是把数据先放入自己的移位寄存器里保存的,之后再进行数据的发送到 SDA/写入到自己的内存中。
但是读写过程有一些差别,具体如下图所示:读操作需要一个伪写操作,告诉从设备,主机接下来要读取的数据,是从哪个“ 内部地址”开始的。绝大多数 I2C 从设备(如 EEPROM 、传感器、 RTC 等) 内部都不是只有一个数据单元,而是有多个寄存器或存储地址,构成一个数组。当主设备想读取数据时,它必须明确地告诉从设备:“我不是要随便读点数据,我是要读你内部地址为 0x02 的这个寄存器里的值” 。这个“ 告诉” 的过程,就是通过一次伪写操作来完成的。之所以叫“伪” 写,是因为主设备并不是真的要向这个地址写入数据。它只是利用 I2C 协议中写操作的流程,将一个地址值(0x02)“写”过去。这个操作的目的不是为了改变从设备的数据,而是为了设置从设备内部的一个指针(Pointer)。这个指针会指向主机接下来想要访问的那个寄存器。之后,当主机切换到读模式时,从设备就知道该从指针所指的位置开始返回数据了。

在开源网站 http://opencores.org/上我们可以找到很多非常好的代码,这些代码大部分都提供详细的文档和仿真。 由于代码大部分都是经过很长时间反复修改,反复精炼后的,所以有些代码理解起来可能比较困难,在不能很好的理解别人代码的时候,最好的办法就是仿真。
从 IP core 文档得知,i2c_master_byte_ctrl 模块主要完成一个字节的读写,我们只需要按照 I2C 读写的要求,完成设备地址、寄存器地址、数据等读写即可。
i2c_master_top 模块是对 i2c_master_byte_ctrl 模块的再次封装,完成一个寄存器的读写,由于不同的设备寄存器可能是 8bit ,也可能是 16bit ,这里 i2c_addr_2byte 信号来控制寄存器地址是 8 位还是 16 位。
i2c_master_top 模块状态机,如果是写寄存器操作,先写一个字节设备地址(写操作) ,再写 1 个字节或 2 个字节的寄存器地址,再写一个字节的数据;如果是读操作,先写一个字节的设备地址(写操作),再写 1 个字节或 2 字节的寄存器地址,完成地址的写入,再次写
设备地址(读操作),然后读取一个字节的数据。不管怎么说,程序设计都是要满足芯片时序
要求的,所以在阅读程序之前最好先把芯片的数据手册仔细阅读一遍。

|
信号名称 |
方向 |
说明 |
|
clk |
in |
时钟 |
|
rst |
in |
异步复位,高有效 |
|
clk_div_cnt |
in |
I2C 时钟分频因子,等于系统时钟频率/(5*I2C 时 钟频率)- 1 |
|
scl_pad_i |
in |
I2C 时钟输入,本实验可忽略 |
|
scl_pad_o |
out |
I2C 时钟输出 |
|
scl_padoen_o |
out |
I2C 时钟输出使能,低有效,I2C 外部有上拉电阻,如果输出高阻态,则会被拉到高电平,在本实验中,高电平输出时输出高阻 |
|
sda_pad_i |
in |
I2C 数据输入 |
|
sda_pad_o |
out |
I2C 数据输出 |
|
sda_padoen_o |
out |
I2C 数据输出使能,低有效。在本实验中,高电平输出时输出高阻 |
|
i2c_addr_2byte |
in |
寄存器地址是 8 还是 16 位,1 :16 位,0 :8 位 |
|
i2c_read_req |
in |
I2C 寄存器读请求 |
|
i2c_read_req_ack |
out |
I2C 寄存器读请求应答 |
|
i2c_write_req |
in |
I2C 寄存器写请求 |
|
i2c_write_req_ack |
out |
I2C 寄存器写请求应答 |
|
i2c_slave_dev_addr |
in |
I2C 设备地址,8bit ,最低位忽略,高 7 位有效 |
|
i2c_slave_reg_addr |
in |
寄存器地址,8 位地址时,低 8 位有效 |
|
i2c_write_data |
in |
写寄存器数据 |
|
i2c_read_data |
out |
读寄存器数据 |
|
error |
out |
设备无应答错误 |
i2c_eeprom_test 模块完成 EEPROM 的读写,EEPROM 设备地址是 A0,程序中将地址 00的数据读出,然后通过 LED 显示,在 KEY2 按下时,数字加一并再次写入 EEPROM 并显示出来。在 I2C 控制器中,代码的大部分功能在备注中也有很多批注。
下载实验程序后,可以看到 LED 显示一个二进制数字,这个数字是存储在 EEPROM 中 00 地址的数据,数据是随机的,这个时候按键 KEY2 按下,数字加一,并写入了 EEPROM ,再次下载程序,可以看到直接显示更新后的数据。
实验通过阅读 DS1338 芯片手册,了解 DS1338 操作时序和相关寄存器,然后设计程序将DS1338 RTC 时间通过串口发送到 PC ,通过串口调试助手可以看到时间信息。
Vivado 2022.2
Hello-FPGA-KU5P 开发板串口调试助手
RTC(Real-Time Clock)实时时钟为系统提供一个可靠的时间,并且在断电的情况下,RTC 实时时钟也可以通过电池供电,一直运行下去。RTC 通过 I2C 总线向 FPGA 传送 8 位数据(BCD 码)。数据包括秒,分,小时,日期,天,月和年。在本实验中我们将读取 RTC 的时,分,秒的数据通过串口发送到 PC 。开发板上 RTC 设计采用 DALLAS 公司的低功耗实时时钟芯片 DS1338, DS1338 的 VCC 为主电源,VBAT 为后备电源。在主电源关闭的情况下,也可以通过电池保持时钟的连续运行。DS1338 外接 32.768kHz 晶振为 RTC 电路提供振荡源。 RTC 部分的原理图如下图所示:

数据的读写遵循标准的 I2C 总线协议。下图是 DS1338 芯片写操作的时序图。先写从设备
地址,再写寄存器地址,再写数据到相应寄存器中

读操作与写操作差不多,下图是 DS1338 芯片读操作时许图。

下图是 DS1338 芯片的寄存器/RAM 地址和数据格式

通过分析 DS1338 读写时序,ds1338_i2c 模块用于读写单个 DS1338 寄存器的数据,而数据的获取通过调用 I2C 总线模块 i2c_master_top;ds1338 模块的作用是用于设置或读取秒、分、时、周、日、月、年等寄存器的值;ds1338_test 模块完成时间的设置和读取;顶层 top 模块作用是调用 uart_send 模块把 RTC 的时间通过串口显示出来。
ds1338_i2c 模块完成 DS1338 寄存器读写控制。
|
信号名称 |
方向 |
说明 |
|
clk |
in |
输入时钟信号 |
|
rst |
in |
异步复位信号,高有效 |
|
i2c_sda |
inout |
i2c 数据 |
|
i2c_scl |
inout |
i2c 时钟 |
|
cmd_read |
in |
读寄存器请求,发出请求时准备好地址 |
|
cmd_write |
in |
写寄存器请求,发出请求时准备好地址和数据 |
|
cmd_read_ack |
out |
读寄存器应答,应答时读取数据有效 |
|
cmd_write_ack |
out |
写寄存器应答 |
|
read_addr |
in |
读寄存器地址 |
|
write_addr |
in |
读寄存器地址 |
|
read_data |
out |
读出的数据 |
|
write_data |
in |
写寄存器数据 |
ds1338 模块主要完成时间寄存器的读写控制
|
信号名称 |
方向 |
说明 |
|
clk |
in |
输入时钟信号 |
|
rst |
in |
异步复位信号,高有效 |
|
ds1338_i2c_sda |
inout |
ds1338 数据线 |
|
ds1338_i2c_scl |
inout |
ds1338 时钟线 |
|
write_time_req |
in |
DS1338 写时间请求,请求发出时,时间数据 write_second 、write_minute 、write_hour、 write_date 、write_month 、write_week、 write_year 要 有效 |
|
write_time_ack |
out |
写时间请求应答 |
|
write_second |
in |
写时间:秒,BCD 码,00-59 |
|
write_minute |
in |
写时间:分,BCD 码, ,00-59 |
|
write_hour |
in |
写时间:时,BCD 码, ,00-23 |
|
write_date |
in |
写时间: 日,BCD 码, ,01-31 |
|
write_month |
in |
写时间:月,BCD 码, ,01- 12 |
|
write_week |
in |
写时间:周,BCD 码, ,01-07 |
|
write_year |
in |
写时间:年,BCD 码, ,00-99 |
|
read_time_req |
in |
读时间请求 |
|
read_time_ack |
out |
读时间请求应答 |
|
read_second |
out |
读时间:秒,BCD 码,00-59 |
|
read_minute |
out |
读时间:分,BCD 码, ,00-59 |
|
read_hour |
out |
读时间:时,BCD 码, ,00-23 |
|
read_date |
out |
读时间: 日,BCD 码, ,01-31 |
|
read_month |
out |
读时间:月,BCD 码, ,01- 12 |
|
read_week |
out |
读时间:周,BCD 码, ,01-07 |
|
read_year |
out |
读时间:年,BCD 码, ,00-99 |
DS1338_test 模块主要 CH 状态检测,CH 位于秒寄存器的 BIT7 位,上电后首先读取时间,判断秒寄存器的 CH 状态,如果为高,表示 DS1338 暂停,状态机进入“S_WRITE_CH”,将 CH 写 0 ,并将一个初始时间写入,然后循环不断的读取时间寄存器。
|
信号名称 |
方向 |
说明 |
|
clk |
in |
输入时钟信号 |
|
rst |
in |
异步复位信号,高有效 |
|
ds1338_i2c_sda |
inout |
ds1338 数据线 |
|
ds1338_i2c_scl |
inout |
ds1338 时钟线 |
|
read_second |
out |
时间:秒,BCD 码,00-59 |
|
read_minute |
out |
时间:分,BCD 码,00-59 |
|
read_hour |
out |
时间:时,BCD 码,00-23 |
|
read_date |
out |
时间: 日,BCD 码,01-31 |
|
read_month |
out |
时间:月,BCD 码,01- 12 |
|
read_week |
out |
时间:周,BCD 码,01-07 |
|
read_year |
out |
时间:年,BCD 码,00-99 |
将程序下载到开发板以后,连接 uart 转串口到 PC 机,打开串口调试助手,选择端口,波特率选择“ 115200” ,其他值默认。我们可以看到串口每秒会收到一条数据,显示一个时间。
本实验通过读写 DDR4 内存,了解 DDR4 工作原理以及应用方法。
Vivado 2022.2
Hello-FPGA-KU5P 开发板
DDR SDRAM 全称为 Double Data Rate SDRAM ,中文名为“双倍数据流 SDRAM” 。DDR SDRAM 在原有的 SDRAM 的基础上改进而来。也正因为如此,DDR 能够凭借着转产成本优势来打败昔日的对手 RDRAM,成为当今的主流。本文只着重讲 DDR 的原理和 DDR SDRAM相对于传统 SDRAM(又称 SDR SDRAM)的不同。

数据的传输在 CLK 与CLK#的交叉点进行,可见在 CLK 的上升与下降沿都有数据被触发,从而实现 DDR 。在此,我们可以说通过差分信号达到了 DDR 的目的,甚至讲 CLK#帮助了第二个数据的触发,但这只是对表面现象的简单描述,从严格的定义上讲并不能这么说。之所以能实现 DDR ,还要从其内部的改进说起。

这是一颗 128Mbit 的内存芯片,从图中可以看出来, 白色区域内与 SDRAM 的结构基本相同,但请注意灰色区域,这是与 SDRAM 的不同之处。首先就是内部的 L-Bank 规格。SDRAM中 L-Bank 存储单元的容量与芯片位宽相同,但在 DDR SDRAM 中并不是这样,存储单元的容 量是芯片位宽的一倍,所以在此不能再套用讲解 SDRAM 时“芯片位宽=存储单元容量” 的公式了。也因此,真正的行、列地址数量也与同规格 SDRAM 不一样了。
在读取时,L-Bank 在内部时钟信号的触发下一次传送 8bit 的数据给读取锁存器,再分成两路 4bit数据传给复用器,由后者将它们合并为一路 4bit数据流,然后由发送器在 DOS 的控制下在外部时钟上升与下降沿分两次传输 4bit 的数据给北桥。这样,如果时钟频率为 100MHz ,那么在 I/O端口处, 由于是上下沿触发,那么就是传输频率就是 200MHz。
这种内部存储单元容量(也可以称为芯片内部总线位宽)=2×芯片位宽(也可称为芯片 I/O 总线位宽) 的设计,就是所谓的两位预取(2-bit Prefetch) ,有的公司则贴切的称之为 2-n Prefetch (n 代表芯片位宽)。
DDR SDRAM 与 SDRAM 的不同主要体现在以下几个方面。
|
|
SDRAM |
DDR SDRAM |
|
全页式突发传输 |
支持 |
不支持 |
|
时钟信号挂起 |
支持 |
不支持 |
|
读出数据屏蔽 |
支持 |
不支持 |
|
写入数据屏蔽 |
支持 |
支持 |
|
时钟 |
单一时钟 |
差分时钟 |
|
预取设计 |
1bit |
2bit |
|
数据传输率 |
1/时钟周期 |
2/时钟周期 |
|
CAS 潜伏期 |
2 ,3 |
1.5 ,2 ,2.5 ,3 |
|
写入潜伏期 |
0 |
可变 |
|
突发长度 |
1 ,2 ,4 ,8 ,全页 |
2 ,4 ,8 |
|
延迟锁定回路 |
可选 |
工作时必需 |
|
自动刷新间隔 |
固定 |
弹性设计 |
|
数据选取脉冲 |
无 |
必需 |
DDR SDRAM 与 SDRAM 一样,在开机时也要进行 MRS ,不过由于操作功能的增多, DDR SDRAM 在 MRS 之前还多了一个 EMRS 阶段 (Extended Mode Register Set,扩展模式寄存器设置) ,这个扩展模式寄存器控制着 DLL 的有效/禁止、输出驱动强度、QFC 有效/无效等。由于 EMRS 与 MRS 的操作方法与 SDRAM 的 MRS 大同小异,在此就不再列出具体的模式表了,有兴趣的话可查看相关的 DDR 内存资料。下面我们就着重说说 DDR SDRAM 的新设计与新功能。
(1)差分时钟
差分时钟(参见上文“DDR SDRAM 读操作时序图”)是 DDR 的一个必要设计,但 CK#的作用,并不能理解为第二个触发时钟(你可以在讲述 DDR 原理时简单地这么比喻),而是起到触发时钟校准的作用。由于数据是在 CK 的上下沿触发,造成传输周期缩短了一半,因此必须要保证传输周期的稳定以确保数据的正确传输,这就要求 CK 的上下沿间距要有精确的控制。但因为温度、电阻性能的改变等原因,CK 上下沿间距可能发生变化,此时与其反相的 CK#就起到纠正的作用(CK 上升快下降慢,CK#则是上升慢下降快)。而由于上下沿触发的原因,也使 CL= 1.5 和 2.5 成为可能,并容易实现。与 CK 反相的 CK#保证了触发时机的准确性。

(2)数据选取脉冲(DQS)
总结 DQS:它是双向信号;读内存时,由内存产生,DQS 的沿和数据的沿对齐;写入内存时,由外部产生,DQS 的中间对应数据的沿,即此时 DQS 的沿对应数据最稳定的中间时刻。
DQS 是 DDR SDRAM 中的重要功能,它的功能主要用来在一个时钟周期内准确的区分出每个传输周期,并便于接收方准确接收数据。每一颗芯片都有一个 DQS 信号线,它是双向的,在写入时它用来传送出北桥发来的 DQS 信号,读取时,则由芯片生成 DQS 向北桥发送。完全可以说,它就是数据的同步信号。
在读取时,DQS 与数据信号同时生成(也是在 CK 与 CK#的交叉点)。而 DDR 内存中的 CL 也就是从 CAS 发出到 DQS 生成的间隔,数据真正出现在数据 I/O 总线上相对于
DQS 触发的时间间隔被称为 tAC 。注意,这与 SDRAM 中的 tAC 的不同。实际上,DQS 生成时,芯片内部的预取已经完毕了,tAC 是指上文给图中灰色部分的数据输出时间,由于预取的原因,实际的数据传出可能会提前于 DQS 发生(数据提前于 DQS 传出)。 由于是并行传输,DDR 内存对 tAC 也有一定的要求,对于 DDR266 ,tAC 的允许范围是±0.75ns ,对于
DDR33 ,则是±0.7ns ,有关它们的时序图示见前文,其中 CL 里包含了一段 DQS 的导入期。
前文已经说了 DQS 是为了保证接收方的选择数据,DQS 在读取时与数据同步传输,那么接收时也是以 DQS 的上下沿为准吗?不,如果以 DQS 的上下沿区分数据周期的危险很大。由于芯片有预取的操作,所以输出时的同步很难控制,只能限制在一定的时间范围内,数据在各 I/O 端口的出现时间可能有快慢,会与 DQS 有一定的间隔,这也就是为什么要有一个 tAC规定的原因。而在接收方,一切必须保证同步接收,不能有 tAC 之类的偏差。这样在写入时,芯片不再自己生成 DQS,而以发送方传来的 DQS 为基准,并相应延后一定的时间,在 DQS 的中部为数据周期的选取分割点(在读取时分割点就是上下沿),从这里分隔开两个传输周期。这样做的好处是,由于各数据信号都会有一个逻辑电平保持周期,即使发送时不同步,在 DQS上下沿时都处于保持周期中,此时数据接收触发的准确性无疑是最高的。在写入时,以 DQS 的高/低电平期中部为数据周期分割点,而不是上/下沿,但数据的接收触发仍为 DQS 的上/下沿。

(3)写入延迟
在上面的 DQS 写入时序图中,可以发现写入延迟已经不是 0 了,在发出写入命令后,
DQS 与写入数据要等一段时间才会送达。这个周期被称为 DQS 相对于写入命令的延迟时间(tDQSS ,WRITE Command to the first corresponding rising edge of DQS),对于这个时间大家应该很好理解了。
为什么要有这样的延迟设计呢?原因也在于同步,毕竟一个时钟周期两次传送,需要很高的控制精度,它必须要等接收方做好充分的准备才行。tDQSS 是 DDR 内存写入操作的一个重要参数,太短的话恐怕接受有限,太长则会造成总线空闲。tDQSS 最短不能小于 0.75 个时钟周期,最长不能超过 1.25 个时钟周期。有人可能会说,如果这样,DQS 不就与芯片内的时钟
不同步了吗?对,正常情况下,tDQSS 是一个时钟周期,但写入时接受方的时钟只用来控制命令信号的同步,而数据的接受则完全依靠 DQS 进行同步,所以 DQS 与时钟不同步也无所谓。不过,tDQSS 产生了一个不利影响——读后写操作延迟的增加,如果 CL=2.5 ,还要在 tDQSS基础上加入半个时钟周期,因为命令都要在 CK 的上升沿发出。

当 CL=2.5 时,读后写的延迟将为 tDQSS+0.5 个时钟周期(图中 BL=2)
另外,DDR 内存的数据真正写入由于要经过更多步骤的处理,所以写回时间(tWR)也明显延长,一般在 3 个时钟周期左右,而在 DDR-II 规范中更是将 tWR 列为模式寄存器的一项,可见它的重要性。
(4)突发长度与写入掩码
在 DDR SDRAM 中,突发长度只有 2 、4 、8 三种选择,没有了随机存取的操作(突发长度为 1)和全页式突发。这是为什么呢?因为 L-Bank 一次就存取两倍于芯片位宽的数据,所以芯片至少也要进行两次传输才可以,否则内部多出来的数据怎么处理?而全页式突发事实证明在 PC 内存中是很难用得上的,所以被取消也不希奇。
但是,突发长度的定义也与 SDRAM 的不一样了(见本章节最前那幅 DDR 简示图),
它不再指所连续寻址的存储单元数量,而是指连续的传输周期数,每次是一个芯片位宽的数据。对于突发写入,如果其中有不想存入的数据,仍可以运用 DM 信号进行屏蔽。DM 信号和数据信号同时发出,接收方在 DQS 的上升与下降沿来判断 DM 的状态,如果 DM 为高电平,那么之前从 DQS 中部选取的数据就被屏蔽了。有人可能会觉得,DM 是输入信号,意味着芯片不能发出 DM 信号给北桥作为屏蔽读取数据的参考。其实,该读哪个数据也是由北桥芯片决定的,所以芯片也无需参与北桥的工作,哪个数据是有用的就留给北桥自己去选吧。
(5)延迟锁定回路(DLL)
DDR SDRAM 对时钟的精确性有着很高的要求,而 DDR SDRAM 有两个时钟,一个是外部的总线时钟,一个是内部的工作时钟,在理论上 DDR SDRAM 这两个时钟应该是同步的,
但由于种种原因,如温度、 电压波动而产生延迟使两者很难同步,更何况时钟频率本身也有不稳定的情况(SDRAM 也内部时钟,不过因为它的工作/传输频率较低,所以内外同步问题并不突出)。DDR SDRAM 的 tAC 就是因为内部时钟与外部时钟有偏差而引起的,它很可能造成因数据不同步而产生错误的恶果。实际上,不同步就是一种正/负延迟,如果延迟不可避免,那么若是设定一个延迟值,如一个时钟周期,那么内外时钟的上升与下降沿还是同步的。鉴于外
部时钟周期也不会绝对统一,所以需要根据外部时钟动态修正内部时钟的延迟来实现与外部时钟的同步,这就是 DLL 的任务。
DLL 不同于主板上的 PLL ,它不涉及频率与电压转换,而是生成一个延迟量给内部时钟。目前 DLL 有两种实现方法,一个是时钟频率测量法(CFM ,Clock Frequency Measurement),一个是时钟比较法(CC ,Clock Comparator)。
CFM 是测量外部时钟的频率周期,然后以此周期为延迟值控制内部时钟,这样内外时钟正好就相差了一个时钟周期,从而实现同步。DLL 就这样反复测量反复控制延迟值,使内部时钟与外部时钟保持同步。

CC 的方法则是比较内外部时钟的长短,如果内部时钟周期短了,就将所少的延迟加到下一个内部时钟周期里,然后再与外部时钟做比较,若是内部时钟周期长了,就将多出的延迟从下一个内部时钟中刨除,如此往复,最终使内外时钟同步。

CFM 与 CC 各有优缺点,CFM 的校正速度快,仅用两个时钟周期,但容易受到噪音干扰,并且如果测量失误,则内部的延迟就永远错下去了。CC 的优点则是更稳定可靠,如果比较失败,延迟受影响的只是一个数据(而且不会太严重),不会涉及到后面的延迟修正,但它的修
正时间要比 CFM 长。DLL 功能在 DDR SDRAM 中可以被禁止,但仅限于除错与评估操作,正常工作状态是自动有效的。
开发板上使用了 2 个 DDR4 的颗粒 MT40A512M16LY-062EIT ,每个 DDR 芯片的容量为 1GB 。2 个 DDR4 芯片组合成 32 位的数据总线宽度和 FPGA 相连接。开发板板上对
DDR4 的地址线和控制线都做了端接电阻上拉到 VT 电压,保证信号的质量。在 PCB 的设计上,完全遵照 XILINX 的 DDR 设计规范,严格保证等长设计和阻抗控制。在进行 DDR 设计时,FPGA 的 DDR 管脚分配是要有所考虑的,而不能随意分配。原理图如下

MIG IP 控制器是 xilinx 为用户提供的一个 DDR 控制的 IP ,这样用户即使不了解 DDR的控制和读写时序也能通过 DDR 控制器方便的读写 DDR 存储器。
DDR 控制器包含 3 部分: 用户接口模块(User interface Block) ,存储器控制模块(Memory Controller)和 DDR 的物理接口(Physical Layer) 。开发人员只需要开发用户的逻辑设计跟 DDR控制器的用户接口对接来读写 DDR 的数据。关于 DDR 控制器用户端的接口定义和时序的更多介绍,大家还是参考 Xilinx 提供的文档(PG150),接下来为大家介绍如何生成和配置 DDR控制器。
建立工程,打开 IP Catalog,选择 DDR4 SDRAM(MIG) 。Basic 界面,参考时钟我们选择开发板对应的系统时钟 200MHz 。由于版本原因这里选择相近 DDR 型号
MT40A512M16HA-075E ,Data Width 选择 32 位。

AXI Options 里也需要进行对应的位宽设置,位宽的大小根据程序设计来设置。这里选择64 。其他默认即可。

在 Advanced Clocking 界面,将系统时钟设置为 Differential 。点击 ok ,生成 IP。

AXI 的核心是通道分离和握手协议
(1)通道分离
AXI4 协议将一次数据传输过程分解成多个独立的通道
写地址通道 (AW Channel):
主设备(aq_axi_master)告诉从设备(DDR IP)我要开始写数据(M_AXI_AWVALID),并传输写入地址(M_AXI_AWADDR),突发长度(M_AXI_AWLEN)等信息。
写数据通道 (W Channel):
主设备把真正的数据通过这个通道运过去。主要传输写数据(M_AXI_WDATA),写选通(M_AXI_WSTRB),写最后数据标识(M_AXI_WLAST)和写有效(M_AXI_WVALID)。
写响应通道 (B Channel):
从设备收到数据后,通过这个通道给主设备回执信息:数据已正确收到 或出问题。
读地址通道 (AR Channel):
主设备告诉从设备:我要从 XXX 地址开始,取 ZZZ 长度数据。
读数据通道 (R Channel):
从设备通过这个通道把大师要的数据(货物)送回来。
(2)握手协议
这是 AXI4 保证可靠性的核心。每个通道都使用 VALID/READY 信号进行握手。 VALID 信号:“我准备好了”
发送方(如主设备发地址)拉起 VALID ,表示“我提供的信息已经有效放在总线上了”。
READY 信号:“我可以接收了”
接收方(如从设备收地址)拉起 READY ,表示“我准备好了,你可以把信息给我了”。
一次成功的握手发生在 VALID 和 READY 同时为高的那个时钟上升沿。只要两者没同时为高,发送方就必须保持数据稳定等待。
(3)突发传输
这是实现高带宽的关键。AXI4 不支持单次只传输 1 个数据,而是一次交易传输一“ 串”连续的数据(一个突发)。一次突发传输由起始地址、突发长度、突发大小共同决定:
突发长度 (Burst Length, AxLEN):这次突发传多少个传输节拍( beats)。AXI4 最大支持 256个节拍。
突发大小 (Burst Size, AxSIZE):每个节拍传输多少字节(如 1 字节、2 字节、4 字节...最大是总线宽度,如 64 位/8 字节)。
突发类型 (Burst Type, AxBURST):地址怎么变化?主要是递增(INCR),即下一次传输的地址是当前地址+突发大小。这对于访问连续的 DDR 内存空间极其高效。
了解了AXI 总先后,我们来编写一个顶层程序 top.v ,在顶层模块里面我们分别例化了mem_test 、aq_axi_master 、以及例化的 ddr4ip
aq_axi_master 主要与我们 ddr4 进行通信,可以看到与这个模块接口比较多代码没有多少行通过 axi 协议与 ddr4 进行通信。
这个模块包含一个 7 状态的状态机:
S_WR_IDLE:空闲状态,当 WR_START 到来,进入 S_WA_WAIT;
S_WA_WAIT:当输入 FIFO 数据充足或传输数据长度较小时,进入 S_WA_START;
S_WA_START:传输地址,并拉高 vaild 信号;
S_WD_WAIT:等待从设备回应信号(M_AXI_AWREADY),进入 S_WD_PROC;
S_WD_PROC:传输数据,数据传输完成后进入 S_WR_WAIT;
S_WR_WAIT:等待从设备回应信号(M_AXI_BVALID),如果是一次传输的最后一组数据进入 S_WR_DONE ,否则进入 S_WA_WAIT;
S_WR_DONE;传输结束,拉高 WR_DONE。
接下去我们来编写一个 DDR 的测试程序 mem_test.v
mem_test.v 测试程序里主要实现 ddr 的突发读取和突发写的功能,程序里产生读写请求信号,地址和测试数据并校验读取到的数据是否正确,这里 burst 的数据长度是 128。如果 DDR的读写数据错误(写入的数据和读出的数据不一致) ,error 信号会变高。
具体的读写时序大家可以结合 ila 看具体波形,这样容易理解一些。
添加 ila IP,打开 IP Catalog 界面,选择 Debug & Verification\Debug 下的 ILA(Integrated Logic Analyzer),双击打开。在 General Options 中选择通道数量以及数据深度,这里我们选择 13 个通道,深度选择 4096。

再后面两个分页设置通道数据宽度。


点击 ok ,然后生成 IP。
再来添加 xdc 管脚约束文件 DDR_test.xdc (见工程文件) ,把 error 信号连接到开发板的LED1 上,如果 error 为高电平,LED2 亮。
将工程编译生成 bit文件,烧录至板卡中。烧录完成后软件会自动打开 ila 界面,点击红框中的 Run trigger for ILA core,采集数据。蓝框中为设置触发界面,里面可以配置想要的触发源,这里我们用 burst_reg 的上升沿触发。

该例程中通过利用 XILINX 的 XDMA IP 来实现 PCIE 的发送和接收测试。
Vivado 2022.2
Hello-FPGA-KU5P 开发板PCIE 驱动
PCIE(PCI Express)采用了目前业内流行的点对点串行连接,比起 PCI 以及更早期的计算机总线的共享并行架构,每个设备都有自己的专用连接,不需要向整个总线请求带宽,而且可以把数据传输率提高到一个很高的频率,达到 PCI 所不能提供的高带宽。开发板中的 FPGA单通道通信速率可高达 5Gbit 带宽,可配置成 X1 、X2 、X4 、X8 模式。
在开发板上 PCIe 例程中,FPGA 端程序采用 XILINX 的 PCIe Core 进行设计,例中配置成x8 进行 PCIe 通信。PCIe 的相关基础知识可参考提供的《PCIe 概述》或查阅其它进行整体了解。
PCIe 通信例程由三部分组成:FPGA 端程序、PCIe 卡驱动、PCIe 上位机测试程序。 FPGA 端程序:负责建立与 PCIe 通信需具备的 FPGA 框架,PCIe 通信协议的构建;
PCIe 卡驱动:负责上位机测试程序与 PCIe 卡的数据交换;
PCIe 上位机测试程序:PCIe 测速。
开发板上提供一个工业级高速数据传输 PCIe x8 接口,PCIE 卡的外形尺寸符合标准 PCIe卡电气规范要求,可直接在普通 PC 的 x8 PCIe 插槽上使用。
PCIe 接口的收发信号直接跟 FPGA 的 GTP 收发器相连接,八通道的 TX 信号和 RX 信号都是以差分信号方式连接到 FPGA ,单通道通信速率可高达 5G bit 带宽。PCIe 的参考时钟由PC 的 PCIe 插槽提供给开发板,参考时钟频率为 100MHz 。 在电路设计中硬件电路部分如下图:

整体工程 block design 如下图所示。

整个程序 由六个模块构成 :util_ds_buf_0 、 util_vector_logic_0 、proc_sys_reset_0 、 axi_interconnect_0 、ddr4_0 、XDMA_0。
util_ds_buf_0 是对外部的 PCIe 输入时钟进行 buffer;util_vector_logic_0 是一个逻辑门 IP核,本实验中是非门,用于反向复位信号;proc_sys_reset_0 是复位模块为 axi_interconnect_0 和ddr4_0 提供参考信号;axi_interconnect_0 是 AXI 的 Master 和 Slave 接口设备互联的协议模块;XDMA_0 模块是 PCIe 通信模块,内部具备 DMA 功能,只在 Vivado2016 以上版本才具备。
点击下图红框中的 Create Block Design

出现下图界面,取一个项目名称后点击 OK

进入空 Block Design 界面,点击红框中的加号,加入 IP 核或者 Verilog 代码模块。在搜索框中搜索所需的 IP 核,这里我们选择蓝框中的 Utility Buffer。

双击后 IP 核加入了 Block Design。

双击该模块进入 IP 核配置界面。选择 IBUFOSGTE:高速收发器差分输入缓冲器,专为 GT收发器的参考时钟输入设计的差分缓冲器,具有高性能、低抖动的特性。该选项必须用于连接GT 收发器的参考时钟引脚。普通 IBUFDS 不能用于此目的。点击 ok。

配置后的IP 核如图所示,接下来给模块的 CLK_IN_D 管脚分配外部时钟接口,鼠标指向接口并右键选择 Make External 即可完成,


在左侧红框中可以给接口命名,这里命名 PCIE。

点击加号,搜索 MIG ,将 DDR4 STRAM 加入 Block Design 。双击 IP 核进入配置界面。按下图配置。



继续创建一个 axi interconnect ,作用是连接 PCIe 和 DDR4 的 AXI 接口,单击加号,在弹出的搜索框输入 axi interconnect ,即可找到并双击 axi interconnect ,按下图配置:

创建一个 Processor system reset ,作用是为 axi interconnect 和 ddr4 提供同步后的复位信号,单击加号,在弹出的对话框输入 Processor system reset ,默认即可。

按照下图连接各个模块。并配置管脚接口。
然后编写 XDC 文件,编译综合后下载到开发部的 FLASH 中。这里需要注意,demo 中的工程使用的是 SPI×8 ,与 SPI×4 有很大区别。SPI×8 需要向两个 FLASH 烧录两个不同的文件,这时我们需要在生成 bit 文件后,使用该 bit 文件生成两个 mcs 文件。点击 Tools 栏中的 Generate Memory Configuration File...。

进入 mcs 文件生成界面。

生成文件类型选择 MCS ,flash 型号选择下图红框中的型号。

接口选择 SPI×8 ,bit 文件选择刚才生成的 bit 文件,点击 OK 。等待如下图所示界面,表明mcs 文件生成完毕。
接着按照之前的步骤烧录 mcs 文件,选择 flash 型号,进入如下界面。在红框中选择我们刚刚生成的 mcs 文件。点击 OK 。等待文件烧录完毕。


在烧录结束后,弹出如下窗口时,便可判断烧录成功,此时先把板卡断电,再拔掉烧录器,重新上电,flash 内程序在上电后即可启动。

(1)windows
首先要在系统中禁用驱动程序签名强制执行首先按下 Win 键+I 打开设置。
点击“更新与安全” ,然后点击“恢复”。
在“ 高级启动”部分,点击“立即重启”。
计算机将进入高级启动选项。在这里,选择“疑难解答”。
接着选择“ 高级选项”。
在高级选项中,选择“ 启动设置”。
在启动设置中,点击“重新启动”。
计算机将重新启动并显示启动设置。在这里,找到“禁用驱动程序签名强制执行” 的选项,按其对应的数字键(一般是 7)。
计算机将继续启动,此时驱动程序强制签名已被禁用。
需要注意的是,使用此方法禁用驱动程序强制签名只在本次启动时生效。当你再次重启计算机时,驱动程序强制签名将自动恢复。
以 Win10 为例(如果测试计算机系统和范例不一样,驱动可能会不可用) 。进入解压缩后的 x64\XDMA_Driver\Win10_Release 目录下,鼠标右键点击 XDMA.cer 文件,在弹出的菜单中选择“安装证书(I)” , 如下图所示:

然后一直点击 next 即可,显示下图表示证书导入成功

安装完证书后,进入当前目录下的 XDMA_Driver 目录,鼠标右键点击 XDMA.inf 文件,在弹出的菜单中选择“安装(I)” , 如下图所示:

在弹出的“Windows 安全中心”界面中选择“始终安装此驱动程序软件(I)” , 如下图所示

弹出操作成功完成窗口, 点击“确定”按钮如下图所示:

在操作完成之后,关闭电脑并将板卡插入 PCIE 插槽,重新启动电脑,打开设备管理器,
在里面可以看到如下图所示 PCIE 驱动设备,则表明我们成功安装好了PCIE 驱动,并且主机正常识别到该 PCIE。

(2)linux
依次进入 demo 中的 dma_ip_drivers-master ,XDMA ,linux-kernel ,xdma ,右键,点击从终端打开,输入 make ,等待编译完成。

然 后 进 入 tests 文 件 夹 , 进 入 终 端 , 输 入 ”chmod +x load_driver.sh” 并 敲 回 车 , 输入”sudo ./load_driver.sh”并敲回车。驱动加载完成
打开该文件,运行生成测试文件。

生成的 exe 文件如下。

Ctrl+shife+右键,点击在此处打开 Powershell 窗口

输入.\xdma_test.exe ,出现如下界面,证明测试成功。

本实验将实现 FPGA 芯片和 PC 之间进行千兆以太网数据通信, 通信协议采用
EthernetUDP 通信协议。 FPGA 通过 RGMII 总线和开发板上的 Gigabit PHY 芯片通信, Gigabit PHY 芯片把数据通过网线发给 PC ,程序中实现了 ARP ,UDP ,PING 功能。
Vivado 2022.2
Hello-FPGA-KU5P 开发板网络调试工具
下图为以太网的帧格式:

前导码(Preamble):8 字节,连续 7 个 8’h55 加 1 个 8’hd5 ,表示一个帧的开始,用于双方设备数据的同步。
目的 MAC 地址:6 字节,存放目的设备的物理地址,即 MAC 地址
源 MAC 地址:6 字节,存放发送端设备的物理地址
类型:2 字节,用于指定协议类型,常用的有 0800 表示 IP 协议,0806 表示 ARP 协议,8035表示 RARP 协议
数据:46 到 1500 字节,最少 46 字节,不足需要补全 46 字节,例如 IP 协议层就包含在数据部分,包括其 IP 头及数据。
FCS:帧尾,4 字节,称为帧校验序列,采用 32 位 CRC 校验,对目的 MAC 地址字段到数据字段进行校验。
进一步扩展, 以 UDP 协议为例,可以看到其结构如下,除了以太网首部的 14 字节,数据部分包含 IP 首部,UDP 首部,应用数据共 46~ 1500 字节。

ARP 数据报格式
ARP 地址解析协议,即 ARP(Address Resolution Protocol),根据 IP 地址获取物理地址。主机发送包含目的 IP 地址的 ARP 请求广播(MAC 地址为 48’hff_ff_ff_ff_ff_ff)到网络上的主机,并接收返回消息, 以此确定目标的物理地址,收到返回消息后将 IP 地址和物理地址保存到缓存中,并保留一段时间,下次请求时直接查询 ARP 缓存以节约资源。下图为 ARP 数据报格式 。

帧类型:ARP 帧类型为两字节 0806
硬件类型:指链路层网络类型,1 为以太网
协议类型:指要转换的地址类型,采用 0x0800 IP 类型,之后的硬件地址长度和协议地址长度分别对应 6 和 4
OP 字段中 1 表示 ARP 请求,2 表示 ARP 应答
例如:|ff ff ff ff ff ff|00 0a 35 01 fe c0|08 06|00 01|08 00|06|04|00 01|00 0a 35 01 fe c0|c0 a8 00 02| ff ffff ffffff|c0 a8 00 03| 表示向 192.168.0.3 地址发送 ARP 请求。
|00 0a 35 01 fe c0 | 60 ab c1 a2 d5 15 |08 06|00 01|08 00|06|04|00 02| 60 ab c1 a2 d5 15|c0 a8 00 03|00 0a 35 01 fe c0|c0 a8 00 02|表示向 192.168.0.2 地址发送 ARP 应答。
IP 数据报格式
因为 UDP 协议包只是 IP 包中的一种, 所以我们来介绍一下 IP 包的数据格式。下图为 IP 分组的报文头格式,报文头的前 20 个字节是固定的,后面的可变

版本: 占 4 位,指 IP 协议的版本目前的 IP 协议版本号为 4 (即 IPv4)
首部长度: 占 4 位,可表示的最大数值是 15 个单位(一个单位为 4 字节)因此 IP 的首部长度的最大值是 60 字节
区分服务: 占 8 位,用来获得更好的服务,在旧标准中叫做服务类型,但实际上一直未被使用
过.1998 年这个字段改名为区分服务. 只有在使用区分服务(DiffServ)时,这个字段才起作用.一般的情况下都不使用这个字段
总长度: 占 16 位,指首部和数据之和的长度,单位为字节, 因此数据报的最大长度为 65535 字节.总长度必须不超过最大传送单元 MTU
标识: 占 16 位,它是一个计数器,用来产生数据报的标识
标志(flag): 占 3 位, 目前只有前两位有意义MF
标志字段的最低位是 MF (More Fragment)
MF= 1 表示后面“还有分片” 。MF=0 表示最后一个分片DF
标志字段中间的一位是 DF (Don't Fragment)只有当 DF=0 时才允许分片
片偏移: 占 12 位,指较长的分组在分片后某片在原分组中的相对位置.片偏移以 8 个字节为偏移单位
生存时间: 占 8 位,记为 TTL (Time To Live) 数据报在网络中可通过的路由器数的最大值,TTL字段是由发送端初始设置一个 8 bit 字段.推荐的初始值由分配数字 RFC 指定, 当前值为 64.发送 ICMP 回显应答时经常把 TTL 设为最大值 255
协议: 占 8 位,指出此数据报携带的数据使用何种协议以便目的主机的 IP 层将数据部分上交给哪个处理过程, 1 表示为 ICMP 协议, 2 表示为 IGMP 协议, 6 表示为 TCP 协议, 17 表示为UDP 协议
首部检验和: 占 16 位,只检验数据报的首部不检验数据部分,采用二进制反码求和,即将 16 位数据相加后,再将进位与低 16 位相加,直到进位为 0 ,最后将 16 位取反。
源地址和目的地址:都各占 4 字节,分别记录源地址和目的地址
UDP 协议
UDP 是 User Datagram Protocol(用户数据报协议)的英文缩写。UDP 只提供一种基本的、低延迟的被称为数据报的通讯。所谓数据报,就是一种自带寻址信息,从发送端走到接收端的数据包。UDP 协议经常用于图像传输、网络监控数据交换等数据传输速度要求比较高的场合。
UDP 协议的报头格式:
UDP 报头由 4 个域组成,其中每个域各占用 2 个字节,具体如下:

① UDP 源端口号
② 目标端口号
③ 数据报长度
④ 校验和
UDP 协议使用端口号为不同的应用保留其各自的数据传输通道。数据发送一方将 UDP 数据报通过源端口发送出去,而数据接收一方则通过目标端口接收数据。
数据报的长度是指包括报头和数据部分在内的总字节数。因为报头的长度是固定的,所以该域主要被用来计算可变长度的数据部分(又称为数据负载)。数据报的最大长度根据操作环境不同而各异。从理论上说,包含报头在内的数据报的最大长度为 65535 字节。不过,一些实际应用往往会限制数据报的大小,有时会降低到 8192 字节。
UDP 协议使用报头中的校验值来保证数据的安全。校验值首先在数据发送方通过特殊的算法计算得出,在传递到接收方之后,还需要再重新计算。如果某个数据报在传输过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算值将不会相符,由此 UDP 协议可以检测是否出错。虽然 UDP 提供有错误检测,但检测到错误时,错误校正,只是简单地把损坏的消息段扔掉,或者给应用程序提供警告信息。
Ping 功能
ICMP 是 TCP/IP 协议族的一个 IP 层子协议,包含在 IP 数据报里,用于 IP 主机、路由器之间传递控制消息。控制消息是指网络是否连通,主机是否可达等功能。其中 ping 功能采用回送请求和回答报文,回送请求报文类型为 8'h08 ,回答报文类型为 8'h00

串行管理接口(Serial Management Interface),也被称作 MII 管理接口(MII
ManagementInterface),包括 MDC 和 MDIO 两条信号线。MDIO 是一个 PHY 的管理接口,用来读/写 PHY 的寄存器,以控制 PHY 的行为或获取 PHY 的状态,MDC 为 MDIO 提供时钟,由 MAC 端提供,在本实验中也就是 FPGA 端。在 RTL8211EG 文档里可以看到 MDC的周期最小为 400ns ,也就是最大时钟为 2.5MHz。

SMI 帧格式
如下图, 为 SMI 的读写帧格式:

|
名称 |
说明 |
|
Preamble |
由 MAC 发送 32 个连续的逻辑 1 ,同步于 MDC 信号,用于 MAC 与PHY 之间的同步 |
|
ST |
帧开始位,固定 01 |
|
OP |
操作码,10 为读,01 为写 |
|
PHYAD |
PHY 地址,5bits |
|
REGAD |
寄存器地址,5bits |
|
TA |
Turn Around ,MDIO 方向转换,写状态不需要转换方向,为 10;读状态输出高阻态,在第二个周期,PHY 将 MDIO 拉低 |
|
DATA |
16bits |
|
IDLE |
空闲状态,MDIO 为高阻, 由外部上拉电阻拉高 |
读写时序读时序

可以看到在 Turn Around 状态下,第一个周期 MDIO 为高阻态,第二个周期由 PHY 端拉低。

为了保证能够正确采集到数据,在 MDC 上升沿之前就把数据准备好,在本实验中为下降沿发送数据, 上升沿接收数据。
本实验以千兆以太网 RGMII 通信为例来设计 verilog 程序,会先发送预设的 UDP 数据到网络,每秒钟发送一次,如果 FPGA 检测网口发来的 UDP 的数据包,会把接收到的数据包存储在
FPGA 内部的 RAM 中 ,再不断的把 RAM 中的数据包通过网口发回到 ethernet 网络。程序分为两部分,分别为发送和接收, 实现了 ARP, UDP, Ping 功能 。 以下为原理实现框图:

发送部分中, mac_tx.v 为 MAC 层发送模块,首先在 SEND_START 状态,等待 mac_tx_read信号,如果有效,表明 lP 或 ARP 的数据已经准备好,可以开始发送。再进入发送前导码状态,结束时发送 mac_data_req,请求 lP 或 ARP 的数据, 之后进入发送数据状态,最后进入发送CRC 状态 。在发送数据过程中, 需要同时进行 CRC 校验。

|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
crc_result |
in |
32 |
CRC32 结果 |
|
crcen |
out |
1 |
CRC 使能信号 |
|
crcre |
out |
1 |
CRC 复位信号 |
|
crc_din |
out |
8 |
CRC 模块输入信号 |
|
mac_frame_data |
in |
8 |
从 lP 或 ARP 来的数据 |
|
mac_tx_req |
in |
1 |
MAC 发送请求 |
|
mac_tx_ready |
in |
1 |
lP 或 ARP 数据已准备好 |
|
mac_tx_end |
in |
1 |
lP 或 ARP 数据传输完成 |
|
mac_tx_data |
out |
8 |
向 PHY 发送的数据 |
|
mac_send_end |
out |
1 |
MAC 数据发送完成 |
|
mac_data_valid |
out |
1 |
MAC 数据有效信号 |
|
mac_data_req |
out |
1 |
MAC 层向 lP 或 ARP 请求数据 |
MAC 发送模式
工程中的 mac_tx_mode.v 为发送模式选择,根据发送模式是 lP 或 ARP 选择相应的信号与数据。
|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
mac_send_end |
in |
1 |
MAC 发送完成 |
|
arp_tx_req |
in |
1 |
ARP 发送请求 |
|
arp_tx_ready |
in |
1 |
ARP 数据已准备好 |
|
arp_tx_data |
in |
8 |
ARP 数据 |
|
arp_tx_end |
in |
1 |
ARP 数据发送到 MAC 层结束 |
|
arp_tx_ack |
in |
1 |
ARP 发送响应信号 |
|
ip_tx_req |
in |
1 |
lP 发送请求 |
|
ip_tx_ready |
in |
1 |
lP 数据已准备好 |
|
ip_tx_data |
in |
8 |
lP 数据 |
|
ip_tx_end |
in |
1 |
lP 数据发送到 MAC 层结束 |
|
mac_tx_ready |
out |
1 |
MAC 数据已准备好 |
|
ip_tx_ack |
out |
1 |
lP 发送响应信号 |
|
mac_tx_ack |
in |
1 |
MAC 发送响应信号 |
|
mac_tx_req |
out |
1 |
MAC 发送请求 |
|
mac_tx_data |
out |
8 |
MAC 发送数据 |
|
mac_tx_end |
out |
1 |
MAC 数据发送结束 |
ARP 发送
发送部分中,arp_tx.v 为 ARP 发送模块, 在 lDLE 状态下,等待 ARP 发送请求或 ARP 应答请求信号, 之后进入请求或应答等待状态, 并通知 MAC 层 ,数据已经准备好,等待
mac_data_req 信号,之后进入请求或应答数据发送状态。由于数据不足 46 字节,需要补全 46字节发送。

|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
destination_mac_addr |
in |
48 |
发送的目的 MAC 地址 |
|
source_mac_addr |
in |
48 |
发送的源 MAC 地址 |
|
source_ip_addr |
in |
32 |
发送的源 lP 地址 |
|
destination_ip_addr |
in |
32 |
发送的目的 lP 地址 |
|
mac_data_reg |
in |
1 |
MAC 层请求数据信号 |
|
arp_request_reg |
in |
1 |
ARP 请求的请求信号 |
|
arp_reply_ack |
out |
1 |
ARP 回复的应答信号 |
|
arp_reply_req |
in |
1 |
ARP 回复的请求信号 |
|
arp_rec_source_ip_addr |
in |
32 |
ARP 接收的源 lP 地址, 回复时放到目的 lP 地址 |
|
arp_rec_source_mac_addr |
in |
48 |
ARP 接收的源 MAC 地址,回复时放到目的 MAC地址 |
|
mac_send_end |
in |
1 |
MAC 发送结束 |
|
mac_tx_ack |
in |
1 |
MAC 发送应答 |
|
arp_tx_ready |
out |
1 |
ARP 数据准备好 |
|
arp_tx_data |
out |
8 |
ARP 发送数据 |
|
arp_tx_end |
out |
1 |
ARP 数据发送结束 |
lP 层发送
在发送部分,ip_tx.v 为 lP 层发送模块,在 lDLE 状态下,如果 ip_tx_req 有效,也就是 UDP 或lCMP 发送请求信号,进入等待发送数据长度状态,之后进入产生校验和状态,校验和是将 lP 首部所有数据以 16 位相加,最后将进位再与低 16 位相加,直到进入为 0,再将低 16 位取反,得出校验和结果 。此程序中校验和以树型加法结构, 并插入流水线, 能有效降低加法部分的延迟 ,但缺点是会消耗较多逻辑资源 。如下图 ABCD 四个输入,A 与 B 相加,结果为 E,C 与D 相加,结果为 F,再将 E 与 F 相加,结果为 G,在每个相加结果之后都有寄存器。

在生成校验和之后,等待 MAC 层数据请求, 开始发送数据, 并在即将结束发送 IP 首部后请求 UDP 或 ICMP 数据 。等发送完,进入 IDLE 状态。
|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
destination_mac_addr |
in |
48 |
发送的目的 MAC 地址 |
|
source_mac_addr |
in |
48 |
发送的源 MAC 地址 |
|
source_ip_addr |
in |
32 |
发送的源 IP 地址 |
|
destination_ip_addr |
in |
32 |
发送的目的 IP 地址 |
|
TTL |
in |
8 |
生存时间 |
|
ip_send_tyoe |
in |
8 |
上层协议号 |
|
upper_layer_data |
in |
8 |
从 UDP 或 ICMP 发过来的数据 |
|
upper_data_req |
out |
1 |
向上层请求数据 |
|
mac_tx_ack |
in |
1 |
MAC 发送应答 |
|
mac_send_end |
in |
1 |
MAC 发送结束信号 |
|
mac_data_req |
in |
1 |
MAC 层请求数据信号 |
|
upper_tx_ready |
in |
1 |
上层 UDP 或 ICMP 数据准备好 |
|
ip_tx_req |
in |
1 |
发送请求,从上层过来 |
|
ip_send_data_length |
in |
16 |
发送数据总长度 |
|
ip_tx_ack |
out |
|
产生 IP 发送应答 |
|
ip_tx_busy |
out |
|
IP 发送忙信号 |
|
ip_tx_ready |
out |
1 |
IP 数据已准备好 |
|
ip_tx_data |
out |
8 |
IP 数据 |
|
ip_tx_end |
out |
1 |
IP 数据发送到 MAC 结束 |
IP 发送模式
工程中的 ip_tx_mode.v 为发送模式选择,根据发送模式是 UDP 或 ICMP 选择相应的信号与数据。
|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
mac_send_end |
in |
1 |
MAC 数据发送结束 |
|
udp_tx_req |
in |
|
UDP 发送请求 |
|
udp_tx_ready |
in |
1 |
UDP 数据准备好 |
|
udp_tx_data |
in |
8 |
UDP 发送数据 |
|
udp_send_data_length |
in |
16 |
UDP 发送数据长度 |
|
udp_tx_ack |
out |
1 |
输出 UDP 发送应答 |
|
icmp_tx_req |
in |
1 |
ICMP 发送请求 |
|
icmp_tx_ready |
in |
1 |
ICMP 数据准备好 |
|
icmp_tx_data |
in |
8 |
ICMP 发送数据 |
|
icmp_send_data_length |
in |
16 |
ICMP 发送数据长度 |
|
icmp_tx_ack |
out |
1 |
ICMP 发送应答 |
|
ip_tx_ack |
in |
1 |
IP 发送应答 |
|
ip_tx_req |
in |
1 |
IP 发送请求 |
|
ip_tx_ready |
out |
1 |
IP 数据已准备好 |
|
ip_tx_data |
out |
8 |
IP 数据 |
|
ip_send_type |
out |
8 |
上层协议号 |
|
ip_send_data_length |
out |
16 |
发送数据总长度 |
UDP 发送
发送部分中,udp_tx.v 为 UDP 发送模块,第一步将数据写入 UDP 发送 RAM,同时计算校验和 ,第二步将 RAM 中数据发送出去 。UDP 校验和与 IP 校验和计算方法一致 。在计算时需要将伪首部加上,伪首部包括目的 IP 地址,源 IP 地址, 网络类型, UDP 数据长度。
|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
source_ip_addr |
in |
32 |
发送的源 IP 地址 |
|
destination_ip_addr |
in |
32 |
发送的目的 IP 地址 |
|
udp_send_source_port |
in |
16 |
源端口号 |
|
udp_send_destination_port |
in |
16 |
目的端口号 |
|
udp_send_data_length |
in |
8 |
UDP 发送数据长度, 用户需给出其值 |
|
ram_wr_data |
in |
1 |
写 UDP 的 RAM 数据 |
|
ram wr en |
in |
1 |
写 RAM 使能 |
|
udp_ram_data_req |
out |
1 |
请求写 RAM 数据 |
|
mac_send_end |
in |
1 |
MAC 发送结束信号 |
|
udp_tx_req |
in |
1 |
UDP 发送请求 |
|
ip_tx_req |
out |
1 |
IP 发送请求 |
|
ip_tx_ack |
in |
1 |
IP 应答 |
|
udp_data_req |
in |
1 |
IP 层请求数据 |
|
udp_tx_ready |
out |
1 |
UDP 数据准备好 |
|
udp_tx_data |
out |
8 |
UDP 数据 |
|
udp_tx_end |
out |
1 |
UDP 发送结束(未使用) |
|
almost_full |
out |
1 |
FI FO 接近满信号 |
MAC 层接收
在接收部分,其中 mac_rx.v 为 mac 层接收文件,首先在 IDLE 状态下需要判断 rx_dv 信号是否为高,在 REC_PREAMBLE 前导码状态下, 接收前导码 。之后进入接收 MAC 头部状态, 即目的 MAC 地址,源 MAC 地址,类型,将它们缓存起来, 并在此状态判断前导码是否正确,错误则进入 REC_ERROR 错误状态,在 REC_IDENTIFY 状态判断类型是 IP(8'h0800) 或
ARP(8'h0806) 。然后进入接收数据状态,将数据传送到 IP 或 ARP 模块,等待 IP 或 ARP 数据接收完毕,再接收 CRC 数据。并在接收数据的过程中对接收的数据进行 CRC 处理,将结果与接收到的 CRC 数据进行对比,判断数据是否接收正确,正确则结束,错误则进入 ERROR 状态。

|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
crc_result |
in |
32 |
CRC32 结果 |
|
crcen |
out |
1 |
CRC 使能信号 |
|
crcre |
out |
1 |
CRC 复位信号 |
|
crc_din |
out |
8 |
CRC 模块输入信号 |
|
rx_dv |
in |
1 |
从 PHY 层过来的 rx_dv 信号 |
|
mac_rx_datain |
in |
8 |
从 PHY 层接收的数据 |
|
checksum_err |
in |
1 |
IP 层校验和错误 |
|
ip_rx_end |
in |
1 |
IP 层接收结束 |
|
arp_rx_end |
in |
1 |
ARP 层接收结束 |
|
ip_rx_req |
out |
1 |
请求 IP 层接收 |
|
arp_rx_req |
out |
1 |
请求 ARP 接收 |
|
mac_rx_dataout |
out |
8 |
MAC 层接收数据输出给 IP 或ARP |
|
mac_rec_error |
out |
1 |
MAC 层接收错误 |
|
mac_rx_destination_mac_addr |
out |
48 |
MAC 接收的目的 IP 地址 |
|
mac_rx_source_mac_addr |
out |
48 |
MAC 接收的源 IP 地址 |
ARP 接收
工程中的 arp_rx.v 为 ARP 接收模块,实现 ARP 数据接收,在 IDLE 状态下,接收到从 MAC层发来的 arp_rx_req 信号,进入 ARP 接收状态,在此状态下,提取出目的 MAC 地址,源 MAC
地址, 目的 IP 地址,源 IP 地址, 并判断操作码 OP 是请求还是应答 。如果是请求,则判断
接收到的目的 IP 地址是否为本机地址,如果是,发送应答请求信号 arp_reply_req,如果不是,则忽略 。如果 OP 是应答,则判断接收到的目的 IP 地址及目的 MAC 地址是否与本机一致,如果是,则拉高 arp_found 信号,表明接收到了对方的地址 。 并将对方的 MAC 地址及 IP 地址存入 ARP 缓存中。

|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
local_lp_addr |
in |
32 |
本地 IP 地址 |
|
local_mac_addr |
in |
48 |
本地 MAC 地址 |
|
arp_rx_data |
in |
8 |
ARP 接收数据 |
|
arp_rx_req |
in |
1 |
ARP 接收请求 |
|
arp_rx_end |
out |
1 |
ARP 接收完成 |
|
arp_reply_ack |
in |
1 |
ARP 回复应答 |
|
arp_reply_req |
out |
1 |
ARP 回复请求 |
|
arp_rec_source_ip_addr |
in |
32 |
ARP 接收的源 IP 地址 |
|
arp_rec_source_mac_addr |
in |
48 |
ARP 接收的源 MAC 地址 |
|
arp_found |
out |
1 |
ARP 接收到请求应答正确 |
IP 层接收模块
在工程中, ip_rx 为 IP 层接收模块, 实现 IP 层的数据接收,信息提取, 并进行校验和检查 。首先在 IDLE 状态下, 判断从 MAC 层发过来的 ip_rx_req 信号,进入接收 IP 首部状态,先在 REC_HEADER0 提取出首部长度及 IP 总长度,进入 REC_HEADER1 状态,在此状态提取出目的 IP 地址,源 IP 地址,协议类型,根据协议类型发送 udp_rx_req 或 icmp_rx_req 。在接收首部的同时进行校验和的检查,将首部接收的所有数据相加,存入 32 位寄存器,再将高 16
位与低 16 位相加, 直到高 16 位为 0 ,再将低 16 位取反, 判断其是否为 0 ,如果是 0,则检验正确,否则错误,进入 IDLE 状态, 丢弃此帧数据,等待下次接收。

|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
local_lp_addr |
in |
32 |
本地 IP 地址 |
|
local_mac_addr |
in |
48 |
本地 MAC 地址 |
|
lp_rx_data |
in |
8 |
从 MAC 层接收的数据 |
|
lp_rx_req |
in |
1 |
MAC 层发送的 IP 接收请求信号 |
|
mac_rx_destination_mac_addr |
in |
48 |
MAC 层接收的目的 MAC 地址 |
|
udp_rx_req |
out |
1 |
UDP 接收请求信号 |
|
icmp_rx_req |
out |
1 |
ICMP 接收请求信号 |
|
ip_addr_check_error |
out |
1 |
地址检查错误信号 |
|
upper_layer_data_length |
out |
16 |
上层协议的数据长度 |
|
ip_total_data_length |
out |
16 |
数据总长度 |
|
net_protocol |
out |
8 |
网络协议号 |
|
ip_rec_source_addr |
out |
32 |
IP 层接收的源 IP 地址 |
|
ip_rec_destination_addr |
out |
32 |
IP 层接收的目的 IP 地址 |
|
ip_rx_end |
out |
1 |
IP 层接收结束 |
|
ip_checksum_error |
out |
1 |
IP 层校验和检查错误信号 |
UDP 接收
在工程中, udp_rx.v 为 UDP 接收模块,在此模块首先接收 UDP 首部,再接收数据部分, 并将数据部分存入 RAM 中 ,在接收的同时进行 UDP 校验和检查,如果 UDP 数据是奇数个字节 ,在计算校验和时,在最后一个字节后加上 8 ’h00, 并进行校验和计算 。校验方法与 IP 校验和一样,如果校验正确,将拉高 udp_rec_data_valid 信号,表明接收的 UDP 数据有效,否则无效,等待下次接收。
|
信号 |
方 向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位信号,低有效 |
|
udp_rx_data |
in |
8 |
UDP 接收数据 |
|
udp_rx_req |
in |
1 |
UDP 接收请求 |
|
mac_rec_error |
in |
1 |
MAC 层接收错误 |
|
net_protocol |
in |
8 |
网络协议号 |
|
ip_rec_source_addr |
in |
32 |
IP 层接收的源 IP 地址 |
|
ip_rec_destination_addr |
in |
32 |
IP 层接收的目的 IP 地址 |
|
ip_checksum_error |
in |
1 |
IP 层校验和检查错误信号 |
|
ip_addr_check_error |
in |
1 |
地址检查错误信号 |
|
upper_layer_data_length |
in |
16 |
上层协议的数据长度 |
|
udp_rec_ram_rdata |
out |
8 |
UDP 接收 RAM 读数据 |
|
udp_rec_ram_read_addr |
in |
11 |
UDP 接收 RAM 读地址 |
|
udp_rec_data_length |
out |
16 |
UDP 接收数据长度 |
|
udp_rec_data_valid |
out |
1 |
UDP 接收数据有效 |
SMI 读写控制模块
smi_read_write.v 文件用于 SMI 接口的读写控制,在 IDLE 状态下根据写请求或读请求到相应的状态,再根据 SMI 时序实现 MDC 和 MDIO 的控制。
|
信号 |
方向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位,低电平复位 |
|
mdc |
out |
1 |
MDC 时钟 |
|
mdio |
inout |
1 |
MDIO 数据 |
|
phy_addr |
in |
5 |
PHY 地址 |
|
reg_addr |
in |
5 |
寄存器地址 |
|
write_reg |
in |
1 |
写请求 |
|
write_data |
in |
16 |
写数据 |
|
read_reg |
in |
1 |
读请求 |
|
read_data |
out |
16 |
读数据 |
|
data_valid |
out |
1 |
数据有效 |
|
done |
out |
1 |
读或写结束 |
由于 10/100M 传输数据时是单边沿采集数据,使用 MII 总线传输, 因此一个周期传输 4bit数据 。 当 1000M 传输数据的时候, RGMII 为双边沿采集数据, RGMII 转换成 GMII 后 ,一个周期传输 8bit 数据 。所以 10/100M 传输数据时要进行数据的转换,如发送时, 要将一周期 8bit 数据转成一周期 4bit 数据, 因此要设置数据缓存,读数据速率为写数据的一半。
TX buffer
gmii_tx_buffer.v 将 10/100M 要发送的 8bit 缓存到 eth_data_fifo 中 ,同时将数据长度缓存到len_fifo 中,在状态机中判断长度 FI FO 中的数据是否大于 0,是则表明已经缓存了数据,之后进入读 FI FO 数据状态,每两个周期读一次数据,此功能不好理解,最好用 SignalTap 抓取信号帮助理解。
|
信号 |
方向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位,低电平复位 |
|
eth_10_100m_en |
in |
1 |
10/100M 使能信号 |
|
link |
in |
1 |
Link 信号 |
|
gmii_tx_en |
in |
1 |
内部的 gmii 发送使能 |
|
gmii_txd |
in |
8 |
内部的 gmii 发送数据 |
|
e10_100_tx_en |
out |
1 |
10/100M 发送使能 |
|
e10_100_txd |
out |
8 |
10/100M 发送数据 |
RX buffer
gmii_rx_buffer.v 将接收的 10/100M 数据缓存到 eth_data_fifo 中 ,这里是 2 个时钟周期写入一次数据。同时将数据长度缓存到 len_fifo 中,在状态机中判断长度 FI FO 中的数据是否大于 0,是则表明已经缓存了数据, 之后进入读 FI FO 数据状态,每两个周期读一次数据,最好用
signaltap 抓取信号帮助理解。
|
信号 |
方向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位,低电平复位 |
|
eth_10_100m_en |
in |
1 |
100M 使能信号 |
|
eth_10m_en |
in |
1 |
10M 使能信号 |
|
link |
in |
1 |
Link 信号 |
|
gmii_rx_dv |
in |
1 |
外部的 gmii 接收有效 |
|
gmii_rxd |
in |
8 |
外部的 gmii 接收数据 |
|
e10_100_rx_dv |
out |
1 |
10/100M 接收有效信号 |
|
e10_100_rxd |
out |
8 |
10/100M 接收数据 |
仲裁
gmii_arbi.v 用于三种速度的仲裁, 同时设定了发送的间隔都为 1S。
|
信号 |
方向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位,低电平复位 |
|
speed |
in |
2 |
以太网速度 |
|
link |
in |
1 |
Link 信号 |
|
gmii_rx_dv |
in |
1 |
外部的 gmii 接收有效 |
|
gmii_rxd |
in |
8 |
外部的 gmii 接收数据 |
|
gmii_rx_en |
in |
1 |
内部的 gmii 发送使能 |
|
gmii_txd |
out |
8 |
内部的 gmii 发送数据 |
|
pack_total_len |
out |
32 |
延迟数值,每种速度都设置为 15 |
|
e_rst_n |
out |
1 |
复位 MAC 信号 |
|
e_rx_dv |
out |
1 |
仲裁后接收有效信号 |
|
e_rxd |
out |
8 |
仲裁后接收有效数据 |
|
e_tx_en |
out |
1 |
仲裁后发送使能信号 |
|
e_txd |
out |
8 |
仲裁后发送数据信号 |
ICMP 应答
在工程中, icmp_reply.v 实现 ping 功能, 首先接收其他设备发过来的 icmp 数据, 判断类型是否是回送请求(ECHO REQUEST) ,如果是,将数据存入 RAM,并计算校验和,判断校验和是否正确,如果正确则进入发送状态,将数据发送出去
|
信号 |
方向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位,低电平复位 |
|
mac_send_end |
in |
1 |
Mac 发送结束信号 |
|
ip_tx_ack |
in |
1 |
IP 发送应答 |
|
icmp_rx_data |
in |
8 |
ICMP 接收数据 |
|
icmp_rx_req |
in |
1 |
ICMP 接收请求 |
|
icmp_rev_error |
in |
1 |
接收错误信号 |
|
upper_layer_data_length |
in |
16 |
上层协议长度 |
|
icmp_data_req |
in |
1 |
发送请求 ICMP 数据 |
|
icmp_tx_ready |
out |
1 |
ICMP 发送准备好 |
|
icmp_tx_data |
out |
8 |
ICMP 发送数据 |
|
icmp_tx_end |
out |
1 |
ICMP 发送结束 |
|
icmp_tx_req |
out |
1 |
ICMP 发送请求 |
ARP 缓存
在工程中,arp_cache.v 为 arp 缓存模块,将接收到的其他设备 IP 地址和 MAC 地址缓存,在发送数据之前,查询目的地址是否存在,如果不存在,则向目的地址发送 ARP 请求,等待应答 。在设计文件中, 只做了一个缓存空间,如果有需要, 可扩展。
|
信号 |
方向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位,低电平复位 |
|
arp_found |
in |
1 |
ARP 接收到回复正确 |
|
arp_rec_source_lp_addr |
in |
32 |
ARP 接收的源 IP 地址 |
|
arp_rec_source_mac_addr |
in |
48 |
ARP 接收的源 MAC 地址 |
|
destination_lp_addr |
in |
32 |
目的 IP 地址 |
|
destination_mac_addr |
out |
48 |
目的 MAC 地址 |
|
mac_not_exist |
out |
1 |
目的地处对应的 MAC 地址不存在 |
CRC 校验模块(crc.v)
一个 IP 数据包的 CRC32 校验是在目标 MAC 地址开始计算的, 一直计算到一个包的最后一个数据为止 。 以太网的 CRC32 的 verilog 的算法和多项式可以在以下网站中直接生成:
http://www.easics.com/webtools/crctool

以太网测试模块(mac_test.v)
测试模块实现数据流的控制, 首先在按下按键后发送 ARP 请求信号, 直到对方回应,进入发送 UDP 数据状态,如果在 WAIT 状态时发现 UDP 接收数据有效,则将接收的数据发送出去。循环 1 秒发送,在每次发送前需要检查目的 IP 地址是否能在 ARP 缓存里找到,如果没有,则发送 ARP 请求。
|
信号 |
方向 |
宽度 (bits) |
说明 |
|
clk |
in |
1 |
系统时钟 |
|
rst_n |
in |
1 |
异步复位,低电平复位 |
|
push_button |
in |
1 |
按键信号 |
|
pack_total_len |
in |
32 |
包长度 |
|
gmil_tx_clk |
in |
1 |
GMII 发送时钟 |
|
gmil_rx_clk |
in |
1 |
GMII 接收时钟 |
|
gmil_rx_dv |
in |
1 |
接收数据有效信号 |
|
gmil_rxd |
in |
8 |
接收数据 |
|
gmil_tx_en |
out |
1 |
发送数据有效信号 |
|
gmil_txd |
out |
8 |
发送数据 |
RGMII 转 GMII 模块
util_gmii_to_rgmii.v 文件是将 RGMII 与 GMII 转换,提取出控制信号与数据信号 。 与 PHY 连接是 RGMII 接口。

RGMII 接口是 GMII 接口的简化版,在时钟的上升沿及下降沿都采样数据, 上升沿发送
TXD[3:0]/RXD[3:0], 下降沿发送 TXD[7:4]/RXD[7:4],TX_EN 传送 TX_EN(上升沿) 和 TX_ER (下降沿) 两种信息, RX_DV 传送 RX_DV(上升沿) 和 RX_ER(下降沿) 两种信息。
在建立工程之前要替换 lw ip 库, 用我们提供的 lw ip 文件替换 vivado 和 vitis 文件夹中的 lw ip 文件 , 此 文 件 一 般 在 Vivado/ 2022.2/ data/ embeddedsw/ Third Party/sw_services 和Vitis/2022.2/data/embeddedsw/Third Party/sw_services 路径下。
打开示例中的 vivado 工程,打开 Block Design。

首先生成 XSA 文件 。点击 Files 中的 Exports 选项中的 Exports Hardware 。点击 next 。再下一页面勾选 In lude bitstream.之后点击 next

再下一页面填写生成的 XSA 文件名称以及路径 。之后点击 next 。等待文件生成完毕。

之后点击 Tools 中的 Launch Vitis IDE 。进入 VITIS。
首先选择工程路径 。选择后点击 Launch。

在这里填写工程名称 。填写完成后点击 next。

在下一界面选择刚才生成的XSA 文件 。点击finish 。 工程建立完毕。

勾选 lw ip211。

点击红框中的锤子, 编译工程 。等待编译完成, 完成后可看到叹号消失。

这时我们点击 file, 新建一个 APPLication project。

点击 next ,选择我们刚才建立的工程。

点击 next ,在红框中输入新建工程名称。点击 next。

再次点击 next ,之后选择红框中的 lw ip echo server ,点击 finish。

建立完毕后如下图所示。在其中双击 main.c 文件,在其中找到如下图所示的 IP 地址。
双击 xaxiemacif_physpeed.c ,搜索 2121 ,若没有找到,则表面 lw ip 库没有替换成功,需检查替换操作是否正确进行。

将电脑有线网的 IP 和掩码按代码中修改。

右键工程左下角 application 中 DEBUG ,点击 build project ,等待操作完成后,再次右键,点击Run as ,选择 3.launch hardware (single aPLLication debug(gdb) ), 串口助手可收到回传信息,如下图所示。

这时打开网络调试助手,将 IP 输入进服务器 ip 地址。注意串口助手打印 port 为7 ,所以还需要将服务器端口设置为 7 。点击开始

发送数据,可以看到接收到了回传的数据,以太网数据传输实验成功。

该例程中通过利用 XILINX 串并转换模块来 来实现CameraLink的数据接收与串并转换。常见方式为使用专用串并转换芯片实现串并转换,本实验通过Xilinx FPGA直接实现,无需专用串并转换芯片,可以大大节约IO 资源。
如下图所示:DS90CR288是串并转换芯片,将输入的差分LVDS穿行信号转换为27bit并行信号输出,这需要27个FPGA IO与之对应,非常消耗IO 引脚个数。


Vivado 2022.2
Hello-FPGA-KU5P 开发板 带 Hello-FPGA CL-FULL-FMC子卡
PCIe4005 Camera Link模拟相机源
Camera Link 工业相机线缆
Camera Link 是一种专为工业图像处理应用制定的高性能数字图像传输协议,由自动化成像协会(AIA)发布。它基于 Channel Link (LVDS) 技术,旨在解决高速 CMOS/CCD 相机与图像采集卡之间大数据量传输的标准化问题。Camera Link 采用并行转串行的机制,通过极少的连线实现极高的带宽。其基本组成部分包括:
(1)相机端:将并行的图像数据转换为串行低压差分信号(LVDS)发送。
(2)线缆:专用的 26 针双屏蔽线缆,确保信号在高频下的抗干扰能力。
(3)采集卡端:接收 LVDS 信号并将其还原为并行图像数据,供 FPGA 或处理器处理
本次实验,Hello-FPGA-KU5P 开发板担任采集卡端的角色

Channel Link 由驱动器和接收器组成。驱动器接收 28 个单端数据信号和一个单端时钟信号。数据按 7:1 串行化,四个数据流和一个专用时钟信号通过五个 LVDS 对传输。接收器接收四个 LVDS 数据流和 LVDS 时钟信号,然后将 28 位数据和一个时钟信号传输到电路板
Camera Link 接口有三种配置。由于单个 Channel Link 芯片的容量限制为 28 位,因此某些摄像机可能需要多个芯片才能高效地传输数据。各种配置的命名规则如下:
(1)Base 模式:使用单个通道,包含 24 位图像数据和 4 位同步信号。
(2)Medium 模式:使用两个通道,数据位宽增加到 48 位,4位同步信号。
(3)Full 模式:使用三个通道,数据位宽达到 64 位,4位同步信号。

同步信号:在数据传输过程中,有四个关键的同步信号随数据位一起发送,用于界定一帧图像的有效范围:
(1)FVAL (Frame Valid):帧有效信号。
(2)LVAL (Line Valid):行有效信号。
(3)DVAL (Data Valid):数据有效信号。
(4)Spare:备用信号,通常保留。

该程序功能是 FPGA 接收并解析外部 Camera Link(支持 Base/Medium/Full 模式)的高速视频流信号,完成 LVDS 1:7 反序列化与协议位分解。同时,本实验观测的核心重点在于:通过内部计数统计电路与大规模 ILA (集成逻辑分析仪) 直接抓取视频前 24 帧的同步信号(LVAL、FVAL、DVAL)计数值,以此来精准验证高速视频接口数据传输的正确性与完整性。
13.4.2 rx_channel_1to7 与 rx_clkgen_1to7 模块
这些模块合并实现了一个完整的针对 Camera Link 协议的高带宽 LVDS 视频数据解串收发器。每对 LVDS 差分数据线在每一个像素时钟周期内传输 7 位数据。
rx_clkgen_1to7 模块接收随路输入的差分像素时钟(clkin_p/n),生成高速采样时钟,并对数据位流进行时钟校准与相位对齐,寻找时钟模式匹配(如 7'b1100011)。rx_channel_1to7 模块则依赖输入时钟,同步执行多路 LVDS 输入信号(datain_p/n)的反序列化操作(Deserialization)。通过例化器件专用的 ISERDES 资源,最终将极高速的串行数据流转化为低速并行数据总线 data_out,并恢复出低频的像素时钟 px_clk 输出供后续处理。
13.4.3CameraLink_bit_allocation_rx 模块
该模块实现了一个将解串后的并行数据流(在 Full 模式下 data_in 为 84 位宽度,即 3 通道 × 4组线 × 7位/线)重映射为标准 Camera Link 协议规定的视频端口像素数据格式的组合逻辑映射电路。
核心处理流程:
(1) 模式匹配与选择:通过 N (即 CAMERA_LINK_MODE 参数)判断当前的链路模式(1代表 Base,2代表 Medium,3代表 Full),并利用 generate 语句按需生成分配逻辑。
(2) 位分配解析:
Base 模式 (Chip X 信号): 截取 data_in 中对应的数据位段,提取出视频的行有效 (xLVAL)、帧有效 (xFVAL)、数据有效 (xDVAL) 控制信号,并将像素字节按照引脚和协议要求重新拼接分配给 PortA、PortB、PortC 端口。
Medium 模式 (Chip Y 信号): 加入固定比特偏移(X*7),在基础模式之外继续解析出第二路同步及控制信号 yLVAL/FVAL/DVAL,截取对应字节分配给 PortD、PortE、PortF 端口。
Full 模式 (Chip Z 信号): 加入双重比特偏移(X*2*7),获取剩余的控制信号 zLVAL/FVAL/DVAL 和对应的 PortG、PortH 数据端口。
该模块是 Camera Link 链路接收处理的工作中心:首先,它实例化特定于硬件系列的 IDELAYCTRL,利用 300MHz 基准时钟精密校准接收通道各个输入引脚的延时以确保数据眼图的对齐。根据配置参数动态合成所需要的差分输入线路(datain_p 和 datain_n)。将分散在引脚上的 X, Y, Z 三组差分信号汇总通过 rx_channel_1to7 实例提取出并行数据总线 data_out。随后,再将其作为输入喂给CameraLink_bit_allocation_rx 实例,完成最终的视频协议层提取操作,并将有效像素输出口、时钟和状态旗标暴露到上一层。
本实验能否成功观测与验证的关键就在于这个模块。 此模块对解串提取出的 LVAL/FVAL/DVAL 三大同步标志在各通道像素时钟下进行离散化统计:
帧沿触发与追踪:通过寄存器打拍检测帧有效信号的上升沿 (xFVAL_pos),利用计数器 frame_cnt 对接收到的总帧数进行追踪,限定只在前 24 帧期间开启统计(frame_cnt <= 24)。
像素级精细统计(二维数组记录):代码建立了深度为 24 的寄存器数组,分别用于存储:
xlval_cnt [0:23]:记录每帧中 LVAL 处于高电平的总时钟数(表征行数/垂直分辨率);
xfval_cnt [0:23]:记录每帧中 FVAL 处于高电平的总时钟数;
xdval_cnt [0:23]:记录每帧中 DVAL 处于高电平的数据有效周期总数(表征有效像素荷载总量)。
在每帧有效的周期内不断累加 +1。通过这种硬件统计手段,能够直观地量化出 Camera Link 输出的时序是否吻合预期的帧分辨率参数。
顶层模块整合上述逻辑的同时,最核心的设计在于安插了多套 ILA (集成逻辑分析仪) 进行实验结果的观测证明:
底层连线波形监控 (ila_clout):在 top.v 中例化的该 ILA,其探针直连解串重组后的视频时钟、LVAL / FVAL / DVAL 以及 Port 输出通道。它的作用是让实验者在示波器波形层面去鉴别信号边沿对齐关系、以及并行图像数据是否已经稳定释出。
24帧阵列统计监控(ila_xdval_cnt、ila_xlval_cnt 等):在 counter.v 内部,分别针对 DVAL、LVAL 等计数值例化了高带宽(32位)、多通道(带 24 个 probe 探口)的专属 ILA 核。
实验观测方式:实验员无需外接实际显示器和上位机软件,只需在线上电后,打开 Vivado Hardware Manager 连入 ILA 触发板卡。直接读取 probe0 到 probe23 这 24 帧独立的计数值缓存。只要这些统计器吐出的 xlval_cnt 等数值恰好稳定等于目标摄像头的已知分辨率规格(如无漏行、无错位像素),即证明 Camera Link 采集和数据恢复全部正确,实验获得最强有力的硬件信服众的实验信服力的实验验证结论。
首先将Hello-FPGA CL-FULL-FMC子卡插在Hello-FPGA-KU5P 开发板上,然后将Hello-FPGA-KU5P 开发板和PCIe4005 Camera Link模拟相机源插在主板的PCIe插槽上,再将两张板卡通过Camera Link 工业相机线缆连接

然后将bit文件烧录至板卡中。烧录完成后软件会自动打开 ila 界面

然后按下KU5P上的key4,复位设备,不设置触发直接捕获ila1的数据

发现此时数据均为0,说明已经复位了计数器。
打开ila4的界面,设置触发为帧有效信号的上升沿,表示每一帧抓取一次,同时设置触发窗口数,用于捕获多次触发的结果,设置为30

然后开始捕获,等待模拟相机源发送数据

打开PCIe4005的上位机软件,发送24帧图片

观察到capture status从1变到了25,说明捕获到了帧有效信号的24个上升沿
然后停止触发,即可看到ila4的界面被触发了24次
此时再分别打开ila1,ila2,ila3的界面,不设置触发信号直接捕获,即可得到KU5P接收这24张图片后计数器的结果,观察到每一帧的数据都相同







