FPGA开发手册
1 紫光同创IP core的使用及添加
1.1 实验简介
实验目的:了解PDS软件如何安装IP、使用IP以及查看IP手册
实验环境: Window11 PDS2022.2-SP6.4
芯片型号: PG2L50H-484
1.2 实验原理
1.2.1 IP的安装
PDS软件安装完成之后,PDS自带部分基础IP,其他IP需用户下载IP安装包并安装IP。

打开PDS后,点击上图红框部分的IP图标

之后在弹出的选项卡的左上角点击File->Update...

点击左上角Add Package。

如上图是PCIE IP的安装文件,后缀都是**.iar**。大家选择对应的文件后,点击右下角的Open即可。之后勾选上前面的√
, 点击 Install即可。

之后在左边的界面可以看到刚才安装的IP即可。注意如果发现安装后,弹出了警告,并且左边的界面没有任何变化。那就意味着你安装的IP该系列的器件不支持。因为你的工程可能是LOGOS、LOGOS2、或者Tian2等系列,不同芯片型号所用的IP是不太相同的,所以大家注意这一点。
1.2.2 例化IP及查看IP手册

继续点击上图所示红框的图标。

选择想要生成的IP,这里以FIFO为例子,即红框1所示。红框2是用来填写生成的IP的名字。点击红框3后即可生成IP,并弹出该IP的配置界面。如下图所示:

其弹出的提示是询问我们是否要把该IP添加到工程中,点击YES就行。如果我们不知道IP如何使用,可以打开官方参考手册查看,如下图所示:

选择想要查看的IP,如何点击红框1所示的图标,即可自动弹出官方参考文档。

对我们的IP配置完成后,点击左上角红框1处的Generate即可。


没有任何错误测表示生成成功。

同时工具也会自动弹出一个IP的例化模板,供我们使用。只需要把该例化模板添加到自己的工程之中,即可使用我们生成的IP。
2 键控LED实验
2.1 实验简介
实验目的: 从创建工程到编写代码,完成引脚约束,最后生成bit流下载到开发板上,完成Key0控制led0闪烁,Key1控制led1亮灭。
实验环境: Window11 PDS2022.2-SP6.4
芯片型号: PG2L50H-484
2.2 实验原理
通常的时,分,秒的计时进位大家应该不陌生;
1 小时=60 分钟=3600 秒,当时针转动 1 小时,秒针跳动 3600 次;
在数字电路中的时钟信号也是有固定的节奏的,这种节奏的开始到结束的时间,我们通常称之为周期(T)。
在数字系统中通常关注到时钟的频率,那频率与周期的关系如下:
f=1/T
而本次开发板上的晶振提供了一个25MHZ的单端时钟。
所以其周期约为40ns。而在我们FPGA的设计中,我们的always块通常都是在时钟的上升沿时对数据进行赋值,因此我们可以定义一个变量,每到时钟的上升沿就让该变量+1,让其变成一个计数器,该变量每加1就表示经过了40ns,那如果要定时1s的话,只需要让其计数到24999999即可,因为从0开始计数,所以计数到24999999即可,此时就是一秒了。以此类推,12499999就是0.5s。

上图为开发板上2个LED灯的原理图。

上图为开发板上2个按键的原理图。
KEY0控制LED0每1s更换一次LED灯状态,KE1控制LELD1亮灭状态。(高电平用1表示,低电平用0表示)
2.3 接口列表
top.v 顶层模块接口列表:
端口 | I/O | 位宽 | 描述 |
---|---|---|---|
sys_clk | input | 1 | 系统时钟 25MHZ |
key0 | input | 1 | 用户按键0 |
key1 | input | 1 | 用户按键1 |
led_0 | output | 1 | led灯控制信号 |
led_1 | output | 1 | led灯控制信号 |
btn_deb_fix.v 按键消抖模块接口列表:
端口 | I/O | 位宽 | 描述 |
---|---|---|---|
BTN_WIDTH | parameter | 4 | 案件数量 |
sys_clk | input | 1 | 系统时钟 25MHZ |
rst_n | input | 1 | 全局复位 |
btn_in | input | BTN_WIDTH | 用户按键输入 |
btn_deb_fix | output | BTN_WIDTH | 按键消抖后的输出(脉冲信号) |
2.4.工程说明
该工程框架如下所示:

本次工程主要完成按键控制led的状态。按键0控制led0闪烁,按键1控制led1亮灭。
首先,key0和key1均会经过按键消抖模块,因为开发板上使用的是机械按键,所以每次按下均会产生抖动,如果不进行消抖,会造成误判。经过消抖后,每次按下按键均会产生持续一个clk的高电平即key0_flag和key1_flag。key0_flag控制是否打开1s计数器来开启led0的闪烁,key1_flag直接控制LED1翻转,每按下一次key1,led的状态翻转一次。
2.5 代码模块说明
//key0->led0 闪烁
//key1->key1 翻转
module top(
input wire sys_clk "," //系统时钟25MHZ
input wire key0 ","
input wire key1 ","
output reg led_0 ","
output reg led_1
);
//----------------------------------parameter----------------------------------------
parameter CNT_MAX = 32'd25_000_000 ; //1s计数
//----------------------------------reg----------------------------------------------
reg [7:0] rsn_cnt 0 ; //复位计数器
reg [31:0] cnt_1s ; //计数器
reg led0_en ; //led0闪烁使能
//----------------------------------wire----------------------------------------------
wire rst_n ; "//复位信号,低电平有效"
wire key0_flag ; //按键按下后的上升沿
wire key1_flag ; //按键按下后的上升沿
//----------------------------------always & assign----------------------------------------------
//产生复位
always@(posedge sys_clk) begin
if(rsn_cnt >=100)
rsn_cnt <= rsn_cnt;
else
rsn_cnt <= rsn_cnt + 1'b1;
end
assign rst_n = (rsn_cnt>=100)?1'b1:1'b0 ;
//每按下一次key0进行一次翻转
always@(posedge sys_clk) begin
if(!rst_n)
led0_en <= 1'd0;
else if(key0_flag)
led0_en <= ~led0_en;
end
//计数1s
always@(posedge sys_clk) begin
if(!rst_n)
cnt_1s <= 32'd0;
else if(led0_en) //led0闪烁使能
begin
if(cnt_1s == CNT_MAX-1) //1秒
cnt_1s <= 32'd0;
else
cnt_1s <= cnt_1s + 1'b1;
end
else
cnt_1s <= 32'd0;
end
//led0 1s闪烁
always@(posedge sys_clk) begin
if(!rst_n)
led_0 <= 1'd0;
else if(led0_en)
begin
if(cnt_1s == CNT_MAX-1) //1s翻转led
led_0 <= ~led_0;
else
led_0 <= led_0;
end
else
led_0 <= 1'd0;
end
//led1翻转
always@(posedge sys_clk) begin
if(!rst_n)
led_1 <= 1'd0;
else if(key1_flag)
led_1 <= ~led_1;
end
//----------------------------------instance----------------------------------------------
//按键消抖模块
btn_deb_fix#(
BTN_WIDTH ( 4'd2 ) //2个按键
)u_btn_deb_fix(
sys_clk ( sys_clk "),"
rst_n ( rst_n "),"
btn_in ( "{key1,key0}" "),"
btn_deb_fix ( "{key1_flag,key0_flag}" )
);
endmodule
CNT_MAX定义了一个最大的计数值,由于我们的系统时钟是25MHZ,也就是25000000,所以要让LED实现1s闪烁的话就是从0计数到24999999的时候让led进行一次翻转。
在26-31行中,利用系统时钟计数100个周期后产生了一个复位信号用来给后续模块和时序逻辑提供复位。
在43-55行中,当led0_en拉高时才开始计数一秒。否则计数器一直保持0.
在82-89行中,例化了一个按键消抖的模块,按键按下并松开后将产生一个脉冲信号即key0_flag,key1_flag,其中key_0flag控制led0闪烁,key1_flag控制led1翻转。
//按键消抖
`define UD #1
module btn_deb_fix#(
parameter BTN_WIDTH = 4'd8 //按键数量
)
(
input sys_clk ","
input wire rst_n ","
input [BTN_WIDTH-1:0] btn_in ","
output reg [BTN_WIDTH-1:0] btn_deb_fix
);
//----------------------------------parameter----------------------------------------
parameter CNT_20MS_MAX = 32'd500_000 ; //20MS计数
//----------------------------------reg----------------------------------------------
reg [23:0] cnt[BTN_WIDTH-1:0]; //计数器
reg [BTN_WIDTH-1:0] btn_in_reg ; //寄存按键信号
//打一拍
always @(posedge sys_clk) begin
btn_in_reg <= btn_in;
end
//----------------------------------消抖主要逻辑----------------------------------------------
genvar i;
generate
begin
for(i=0;i<BTN_WIDTH;i=i+1)
begin
always @(posedge sys_clk) begin
if(!rst_n)
cnt[i] <= 24'd0;
if (btn_in_reg[i] == 1'b0) //按下时 计数20ms时归零
cnt[i] <= 24'd0;
else if(cnt[i]==CNT_20MS_MAX) //抖动区间有效时计数
cnt[i] <= cnt[i];
else
cnt[i] <= cnt[i] + 1'b1;
end
always @(posedge sys_clk) begin
if(!rst_n)
btn_deb_fix[i] <= 1'd0;
else if(cnt[i]==CNT_20MS_MAX-1) //消抖后输出一个clk的高电平
btn_deb_fix[i] <= 1'b1;
else
btn_deb_fix[i] <= 1'b0;
end
end
end
endgenerate
endmodule
该部分为按键消抖模块,parameter定义了按键输入的数量,模块的输出将产生一个脉冲信号即产生一个持续一个clk的高电平信号。
在30-50行中,cnt会不断进行20ms的计数,当按键按下时,cnt归0,从0开始计数直到20ms。当计数到20ms的时候,就输出一个clk的高电平,即将btn_deb_fix置1,并让其只保持一个clk。
2.6.实验步骤
这里将会详细介绍从新建工程到下载程序的具体步骤,后续的工程将不再详细解释。
2.6.1.打开PDS软件,创建工程
Step1:打开PDS软件,点击NEW Project,然后对其设置完成新建工程。

Step2:单击NEXT

Step3:创建名为 led_water 的工程到对应的文件目录,之后单击 Next。
新建工程大致包括设置工程名和工程路径、工程类型、工程文件及器件信息。
【Project Name】是工程文件名称,默认为 project。(只允许字母、数字、下划线(_)、杠(-)、点(.))。
【Project Location】用于选择新工程的工作路径,文件夹名只允许字母、数字、下划线(_)、杠(-)、点(.)、@、~、,、+、=、#、空格( ),但空格不能出现在路径名首尾,即工程文件放置的路径。
【Create Preject Subdirectory】将工程文件名作为工作目录的一部分。

Step4:选择RTL project,点击Next。
【RTL Project】用于创建 RTL 工程。新建的工程可以执行 synthesize,device map,place& route,report timing, report power, generate netlist 及 generate bitstream 等。
【Post-Synthesize Project】用于创建综合后工程。新建的工程可以执行 device map,place& route,report timing,report power, generate netlist 及 generate bitstream 等。

Step5:单击Next
该界面可以 Add Files 和 Add Directories 来添加 rtl 源文件及新建 rtl 源文件,以及调整 rtl 文件编译顺序,Add Files 添加选中的文件,Add Directories 添加选中的文件夹下所有合适的文件,若勾选了下方的 Add source from subdirecotires 则添加所有的子目录下合适的文件,也可直接 NEXT 跳过添加文件。

Step6:单击Next

Step7:单击Next

Step8:选择器件系列、型号、封装、速率、综合工具,之后单击Next
synthesize tool 中可以选择综合工具为 Synplify Pro 或ADS,在实验中使用ADS综合工具。

Step9:在summary单击Finish,完成工程的创建

2.6.2 添加设计文件
PDS 软件界面如下图:

双击 Designs,将前面设计的 module 新建到文件中,或者将前面编辑好的verilog 文件添加到工程中:

添加文件到工程:
在窗口中点击 Add Files,选择添加文件到工程;

新建文件到工程:
1)在窗口中点击 Create File;

2)选择 Verilog Design File,文件名和 module 名一致,默认路径,点击 OK;

3)点击 OK;

4)点击 Cancel;

5)默认打开新建文件,将前面设计的 代码 复制进去,

6)点击保存,新建文件完成

Crtl+s保存。

双击Designs。

点击Add Files;

添加btn_deb_fix.v模块,即按键消抖模块。

点击OK。
2.6.3 编译
可采用以下方式运行 Compile 流程:
(1)双击 Flow 中的 Compile 进行综合;
(2)右击 Compile 点击 Run 进行综合;

2.6.4 工程约束
点击 Tools 选择 User Constraint Editor(Timing and Logic)或者点击工具栏图标 ,User Constraint Editor(Timing and Logic) 选择 Pre Synthesize UCE,如下图所示。

Tools 下的 User Constraint Editor(Timing and Logic)

工具栏 User Constraint Editor(Timing and Logic)图标
2.6.4.1 时钟约束
打开 UCE 后,选择 Timing Constraints 后选择 Create Clock 添加基准时钟,基准时钟一般是通过输入 port 输入用户所使用的板上时钟。

在弹窗中对时钟命名,关联时钟管脚,添加时钟参数,点击 OK 会创建一条时钟约束,Reset 重置该页面。创建完成如下图所示:

提供给开发板的时钟是25MHZ,即周期为40ns。

2.6.4.2 物理约束
打开 UCE 后,选择 Device 后选择 I/O,根据原理图编辑 IO 的分配。

按照原理图编辑好 IO 分配后,点击保存,会生成.fdc 文件,完成约束。
2.6.5 综合
运行 Synthesize 流程有以下四种方式可以实现:
(1)双击 Flow 中的 Synthesize 进行综合;
(2)右击 Synthesize 点击 Run 进行综合;
完成 Synthesize 操作后,会看到下图所示:

2.6.6 Device Map
Device Map 的主要作用是将设计映射到具体型号的子单元上(LUT、FF、Carry 等)。运行 Device Map 流程有以下方式可以实现:
(1)直接双击 Device Map;
(2)右击 Device Map 点击 Run;
完成 Device Map 操作后,会看到下图所示:

2.6.7 Place & Route
布局布线(Place & Route)根据用户约束和物理约束,对设计模块进行实际的布局及布线。运行 Place & Route 流程有以下方式可以实现:
(1)直接双击 Place & Route;
(2)右击 Place & Route 点击 Run;
完成 Device Map 操作后,会看到下图所示:

2.6.8 Generate Bitstream
Generate Bitstream 生成二进制位流文件。运行 Generate Bitstream 流程有以下方式可以实现:
(1)直接双击 Generate Bitstream;
(2)右击 Generate Bitstream 点击 Run;
完成以上操作,将会产生位流文件。运行 Generate Bitstream,可以看到界面如下图所示:

2.6.9 下载生成的位流文件
点击 Tools 选择 Configuration 或者点击工具栏图标 Configuration,如下图所示。

Tools 下的 Configuration

工具栏 Configuration 图标
打开 Configuration 后直接选择 Scan Device 直接进行扫描 Jtag 链操作,初始化链成功,会将链上扫描到的所有器件显示于工作区内,并在器件属性窗口显示当前器件的器件信息,并弹出对话显示能够为器件添加的配置文件:

初始化链成功
在对话框中选择位流文件,添加该配置文件,提示所载入文件的绝对路径并在信息栏中显示,如下图所示:

下载位流文件

当发现这4个信号均为1时,表示下载成功。
同时,开发板也配置了一个外部flash,其中,若需要将程序固化到板卡上需要将尾流文件转化为.sfc文件。
首先点击Configuration页面的Operations选项的Covert File选项。

点击后会出现如下画面,在Generate Flash Programing File页面选择对应的Flash 器件的厂商名、型号、再在BitStreamFile位置选择位流文件的路径,点击OK。(若使用的flash器件不在可选的flash列表中,需手动添加对应flash型号,操作步骤请参考开发板下载与固化相说明)

转化.sfc文件成功后,页面会如下图所示,点击OK。

用户可通过右键下图位置,点击Scan Outer Flash。

页面会显示板卡搭载的Flash的型号,点击.sfc文件,点击OPEN。

在下图位置点击鼠标右键后,点击Program。

固化Flash成功如下图所示:

此时将板卡断电,再重新上电,如果按下key0和key1能看到对应的实验现象的话,则表示固化成功。(等大概15s)
最后上板结果如下所示
3 Pango的时钟资源——锁相环
3.1 实验简介
实验目的: 了解PLL IP的基本使用方法。
实验环境: Window11 PDS2022.2-SP6.4
芯片型号: PG2L50H-484
3.2 实验原理
3.2.1 PLL介绍
锁相环作为一种反馈控制电路,其特点是利用外部输入的参考信号来控制环路内部震荡信号的频率和相位。因为锁相环可以实现输出信号频率对输入信号频率的自动跟踪,所以锁相环通常用于闭环跟踪电路。锁相环在工作的过程中,当输出信号的频率与输入信号的频率相等时,输出电压与输入电压保持固定的相位差值,即输出电压与输入电压的相位被锁住,这就是锁相环名称的由来。
锁相环拥有强大的性能,可以对输入到 FPGA 的时钟信号进行任意分频、倍频、相位调整、占空比调整,从而输出一个期望时钟;除此之外,在一些复杂的工程中,哪怕我们不需要修改任何时钟参数,也常常会使用 PLL 来优化时钟抖动,以此得到一个更为稳定的时钟信号。正是因为 PLL 的这些性能都是我们在实际设计中所需要的,并且是通过编写代码无法实现的,所以PLL IP核才会成为程序设计中最常用IP核之一。
PLL IP是紫光同创基于PLL及时钟网络资源设计的IP,通过不同的参数配置,可实现时钟信号的调频、调相、同步、频率综合等功能。
3.2.2 IP配置
首先点击快捷工具栏的“IP”图标,进入IP例化设置

然后在IP目录处选择PLL,在Instance name处为本次实例化的IP取一个名字,接着点击Customise进入IP配置页面。操作示意图如下:

PLL的使用可选择Basic和Advanced两种模式,Advanced模式下PLL的内部参数配置完全开放,需要自己填写输入分频系数、输出分频系数、占空比、相位、反馈分频系数等才能正确配置。Basic模式下用户无需关心PLL的内部参数配置,只需输入期望的频 率值、相位值、占空比等,IP将自动计算,得到最佳的配置参数。如果没有特殊应用,建议使用Basic模式配置PLL。本次实验我们选择Basic Configuration。

接下来进行基础配置:
在Public Configurations一栏将输入时钟频率设置为25MHZ。
在Clockout0 Configurations选项卡下,勾选使能clkout0,将输出频率设置为50MHZ。
在Clockout1 Configurations选项卡下,勾选使能clkout1,将输出频率设置为100MHZ。
在Clockout2 Configurations选项卡下,勾选使能clkout2,将输出频率设置为100MHZ,并设置相位偏移为180度。
其他选项可以使用默认设置,若有其他需求可以查阅IP手册了解,本实验我们暂介绍IP基本的使用方法:

点击左上角generate生成IP。

3.3 代码设计
模块接口列表如下所示:
PLL IP使用实验模块接口表
端口 | I/O | 位宽 | 描述 |
---|---|---|---|
sys_clk | input | 1 | 系统时钟 |
clkout0 | output | 1 | 54MHZ时钟 |
clkout1 | output | 1 | 81MHZ时钟 |
clkout2 | output | 1 | 81MHZ时钟,相位偏移180度 |
lock | output | 1 | 时钟锁定信号,当为高电平时,代表IP核输出时钟稳定。 |
PLL_TEST顶层代码:
module PLL_TEST(
input sys_clk ,
output clkout0 ,
output clkout1 ,
output clkout2 ,
output lock
);
PLL PLL_U0 (
.clkout0 (clkout0 ),// output
.clkout1 (clkout1 ),// output
.clkout2 (clkout2 ),// output
.lock (lock ),// output
.clkin1 (sys_clk ) // input
);
endmodule
该模块的功能是例化PLL IP核,功能简单,在此不做说明。
PLL_tb测试代码:
timescale 1ns / 1ps
module PLL_tb();
reg sys_clk ;
wire clkout0 ;
wire clkout1 ;
wire clkout2 ;
wire lock ;
initial
begin
#2
sys_clk <= 0 ;
end
parameter CLK_FREQ = 25;//Mhz
always # ( 1000/CLK_FREQ/2 ) sys_clk = ~sys_clk ;
PLL_TEST u_PLL_TEST(
.sys_clk (sys_clk ),
.clkout0 (clkout0 ),
.clkout1 (clkout1 ),
.clkout2 (clkout2 ),
.lock (lock )
);
endmodule
timescale定义了模块仿真的时间单位和时间精度。时间单位是1纳秒,精度是1皮秒。
initial块负责初始化系统时钟。在仿真启动后的2纳秒,系统时钟sys_clk被设置为0。这是为了在仿真开始时定义一个已知的初始状态。
代码定义了一个时钟频率参数CLK_FREQ为25 MHz,并使用一个always块来翻转系统时钟信号。always块中的逻辑使得sys_clk每40纳秒翻转一次,从而生成一个25 MHz的方波时钟信号。这种时钟信号用于驱动被测试的PLL_TEST模块。
最后,将测试平台的各个信号连接到PLL_TEST模块。这包括将生成的系统时钟sys_clk连接到PLL_TEST的时钟输入端,并将PLL_TEST的输出信号clkout0、clkout1、clkout2和lock使用wire引出观察。
3.4 PDS与Modelsim联合仿真
PDS支持与Modelsim或QuestaSim等第三方仿真器的联合仿真,而Modelsim是较为常用的仿真器,使用PDS与Modelsim来进行联合仿真。
接下来选择Project->Project Setting,打开工程设置,准备设置联合仿真。

选择Simulation选项卡,红框1选择刚才编译生成的仿真库的路径,红框2选择Modelsim的启动路径,之后点击OK。
右键仿真的文件,选择Run Behavior Simulation开始行为仿真。

运行后会自动打开Modelsim。并执行仿真,如果没有任何报错,则表示成功。如果出现错误,请检测PDS与Modelsim的配置。

3.5 实验现象
点击Wave观察PLL输出信号:

使用标尺测量clkout0,发现其一个时钟周期是20ns,也就是50MHZ。

可以看到clkout1的时钟频率是100MHZ,且和clkout2相位偏差180°,符合设置。需要注意PLL的输出时钟应该在时钟锁定信号lock有效之后才能使用,lock信号拉高之前输出的时钟是不确定的。
4 ROM、RAM、FIFO的使用
4.1 实验简介
实验目的: 掌握紫光平台的RAM、ROM、FIFO IP的使用
实验环境: Window11 PDS2022.2-SP6.4
芯片型号: PG2L50H-484
4.2 实验原理
不管是Logos系列或者是Logos2系列,其IP配置以及模式和功能均一致,不会像PLL那样有动态配置以及内部反馈选项的选择等之间的差异,所以是RAM、ROM、FIFO是通用的。
4.2.1 RAM介绍
RAM即随机存取存储器。它可以在运行过程中把数据写进任意地址,也可以把数据从任意地址中读出。其作用可以拿来做数据缓存,也可以跨时钟,也可以存放算法中间的运算结果等。
注意,PDS的IP配置工具中提供两种不同的RAM,一种是Distributed RAM(分布式RAM)另一种是DRM Based RAM,分布式RAM用的是LUT(查找表)资源去构成的RAM,这种RAM会消耗大量LUT资源,因此通常在一些比较小的存储才会用到这种RAM,以节省DRM资源。而DRM Based RAM是利用片内的DRM资源去构成的RAM,不占用逻辑资源,而且速度快,通常设计中均使用DRM Based RAM。
RAM分为三种,如下表所示:
RAM类型 | 特点 |
---|---|
单端口RAM | 只有一个端口可以读写。只有一个读写口和地址口 |
伪双端口RAM | 有wr和rd两个端口,顾名思义,wr只能写,rd只能读 |
真双端口RAM | 提供A和B两个端口,两个端口均可以独立进行读写 |
注意,当使用真双端口时,要避免出现同时读写同个地址,这会造成写入失败,在逻辑设计上需要避开这个情况。
以下给出比较常用的RAM的配置作为介绍,通常我们比较常用伪双端口RAM来设计,如下图所示:

下图为IP配置:

注意,如果勾选Enable Output Register(输出寄存),输出数据会延迟一个时钟周期。
具体每个端口的含义这里参考官方手册,大家也可以自行查看IP手册,如下图所示:

DRM Resource Type:用于配置所建 RAM IP核用的是哪种资源,不同芯片型号可选资源是不一样的,有的是9K,有的是18K,有的是36K,如果没有特殊情况,直接AUTO即可。
4.2.1.1 RAM的读写时序
配置成不同模式的时候,RAM的读写时序是不一样的,真双端口和单端口的RAM配置均有三种模式,而伪双端口只有一种。由于真双端口和单端口的配置是一样的,这里以真双端口为例子。
分为NORMAL_WRITE(正常模式)、TRANSPARENT_WRITE(直写)、READ_BEFORE_WRITE(读优先模式)三种模式。

而伪双端口不属于上面三种模式,有它独特的模式。这几种模式的差异就在于读写时序的不同,接下来,我们来分析读写时序。
以下时序图均来自官方IP手册,并且均未使能输出寄存。注意wr_en为1时表示写数据,为0表示读数据。
(1)NORMAL_WRITE

在NORMAL_WRITE这种模式下,可以看到,当时钟的上升沿到来,且clk_en和wr_en均为高电平时,就会把数据写到对应的地址里面,如图中的1时刻。然后看读数据端口,当wr_en不为0的时候,a_rd_data一直为Don’t Care状态,而当时钟上升沿到来,且clk_en为高电平,wr_en为低电平时,a_rd_data输出当前a_addr里的数据,即Mem(ADDR1)和ADDR0里的D0。
(2)READ_BEFORE_WRITE

在READ_BEFORE_WRITE这种模式下,可以看到在1的时刻,时钟上升沿到来, 且clk_en和wr_en均为高电平,D0写进了ADDR0里面,但是注意看此时的a_rd_data和a_addr,可以发现,此时a_wr_en并不为0,可a_rd_data还是输出了上一刻ADDR0的数据(因为不是输出D0)。之后,a_wr_en拉低,此时才是读数据,在3时刻,把ADDR0的数据读出来,a_rd_data才输出了D0。
所以总结一下,这个模式其实就是进行写操作时,读端口会把当前写的地址的原始数据输出,因此叫读优先模式很合情合理对吧,顾名思义,就是优先把原来的数据读出来。
(3)Transparent_Write

在Transparent_Write这种模式下,可以看到在1的时刻,时钟上升沿到来, 且clk_en和wr_en均为高电平,D0写进了ADDR0里面,但是注意看此时的a_rd_data和a_addr,可以发现,此时a_wr_en并不为0,可a_rd_data居然直接输出了D0,之后a_wr_en拉低,进入读状态,在2时刻,再一次把ADDR0的数据读出来,输出了D0。
分析总结一下,根据1时刻的情况,我们可以得出结论,在这种模式下,当我们进行写操作时,读端口会马上输出我们写入的数据。所以叫直写模式。
(4)伪双端口的读写时序
注意:wr_en为1时是写操作,为0是读操作。
伪双端口的读写时序与上面三种都不同,我们看下图的时序来分析:

注意看1时刻,此时wr_en和wr_clk_en均为高电平,所以是写操作,所以1时刻就是往地址ADDR0里写入D0,注意此时的rd_addr和rd_data,可以看到这一时刻rd_addr是ADDR2,然后进行写操作时,rd_data同样输出了ADDR2里的数据,而此时wr_en还是高电平。接下来看2和3时刻,此时wr_en为0,rd_clk_en是高电平,所以是读操作,此时分别读出ADDR1和ADDR0里的数据,之后rd_clk_en变成低电平,读时钟无效,可以看到rd_data保持D0输出。
分析总结一下,主要是1时刻,大家可以看到1时刻往ADDR0写入了D0,读端口却输出了ADDR2中的数据。仔细观察可以得出结论:伪双端口RAM在进行写操作的时候,会把当前读端口指向的地址的数据输出。是不是有点像直写?只不过直写是输出写入的数据,而伪双端口是输出读端口指向的地址的数据。
2.1.1.2 ROM介绍
ROM即只读存储器,在程序的运行过程中他只能被读取,无法被写入,因此我们应该在初始化的时候就给他配置初值,一般是在生成IP的时候通过导入.dat文件对其进行初值配置。
注意,PDS的IP配置工具中提供两种不同的ROM,一种是Distributed ROM(分布式ROM)另一种是DRM Based ROM,分布式ROM用的是LUT(查找表)资源去构成的ROM,这种ROM会消耗大量LUT资源,因此通常在一些比较小的存储才会用到这种RAM,以节省DRM资源。而DRM Based ROM是利用片内的DRM资源去构成的ROM,不占用逻辑资源,而且速度快,通常设计中均使用DRM Based ROM。
以下给出比较常用的ROM的配置作为介绍,由于只能读,因此其均为单端口ROM,如下图所示:
下图为IP配置:

注意,如果勾选Enable Output Register(输出寄存),输出数据会延迟一个时钟周期。
同时,可以看到Enable Init选项是默认勾选的,并且不可取消。
导入的数据的格式只能为二进制或者是十六进制,demo选择十六进制。
具体每个端口的含义这里参考官方手册,大家也可以自行查看IP手册,如下图所示:

一般我们只需要addr、rd_data、clk、rst这四个信号即可。
以下时序图均来自官方IP手册,并且均未使能输出寄存。
4.2.1.2 ROM的读时序

可以看到该时序是非常简单的,比如在TI时刻,当clk上升沿到来时,且clk_en为高电平时,给出要读出的地址,rd_data就会输出数据,在不勾选输出使能寄存的情况下,rd_data的输出会有延迟,具体时间可以从仿真里看到,所以我们在下个时钟周期的上升沿即T2时刻的上升沿才能获取到ROM读出的值。
所以整体时序非常简单,如果勾选了clk_en信号,就要给clke_en高电平才能读数据,如果不勾选clk_en信号,就一直根据地址读取ROM数据。
4.2.2 FIFO介绍
FIFO即先入先出,在FPGA中,FIFO的作用就是对存储进来的数据具有一个先入先出特性的一个缓存器,经常用作数据缓存或者进行数据跨时钟域传输。FIFO和RAM最大的区别就是FIFO不需要地址,采用的是顺序写入,顺序读出。
在紫光的IP工具中又分为Distribute FIFO和DRM FIFO,其实就是用不同的资源去构成,前者Distribute FIFO也就是分布式FIFO,使用的是片上的LUT资源去构成,而DRM FIFO使用的是片上的DRM资源去构成,DRM构成的FIFO其性能大于LUT资源构成的,不仅容量更大,且可配置更多功能。
本章着重介绍DRM Based FIFO。
注意:FIFO 写满后禁止继续写入数据,否则将会写溢出。
注意:FIFO 读空后禁止继续读数据,否则将会读溢出。
以下给出常用的FIFO的配置作为介绍。


注意,如果勾选Enable Output Register(输出寄存),输出数据会延迟一个时钟周期。
FIFO Type有SYNC和ASYNC两种,第一种是同步FIFO,读写端口共用一个时钟和复位,另一种是异步FIFO,读写时钟和复位均独立。在平常设计中,比较常用的是异步FIFO,因为同步FIFO和异步FIFO的读写时序一模一样,只有读写端口的时钟复位有差异,当异步FIFO的读写端口使用相同的时钟和复位,此时异步FIFO和同步FIFO基本是一致的。
Reset Type也可以选择SYNC和ASYNC两种,SYNC模式下需要时钟的上升沿采样到复位有效才会复位,而在ASYNC模式下,复位一旦有,FIFO立即复位。
其余端口说明引用官方IP手册,如下图所示:

其中rd_water_level和wr_water_level分别代表”可读的数据量”和”已写入的数据量”,其含义与Xilinx的FIFO的wr_data_count和rd_data_count是一致的。

当我们将Enable Almost Full Water Level和Enable Almost Empty Water Level勾选上,才能看到rd_water_level和wr_water_level,而下面的Almost Full Numbers的设置是表示当写入1020个数据时,Almost Full信号就会拉高,Almost Empty Numbers的设置表示当可读数据剩下4个时Almost Empty信号就会拉高。
同时FIFO支持混合位宽,例如写端口16bit,读端口8bit。如果写入16’h0102,那么读出来会是8’h02,8’h01,会先读出低位。
如果写端口8bit,读端口16bit。当写入8’h01,8’h02时,读出来是16’h0201,先写入的数据存放在低位。
4.2.2.1 FIFO的读写时序
因为同步FIFO和异步FIFO的读写时序一致,这里用异步FIFO的读写时序图来做介绍。
注意:复位时高电平有效。读出数据均未勾选Enable Output Register(输出寄存)。
(1)FIFO未满时的写时序

可以看到在1时刻,复位信号时低电平,处于工作状态,此时在wr_clk的上升沿且wr_en为高电平时将数据D0写入FIFO,wr_water_level也从0变1,表示已经写入了一个数据,此时注意看读端口的empty信号,在3时刻empty信号从高变低,意味着读端口已经有数据可以读了,FIFO不再为空,而注意看,rd_clk和wr_clk是不一样的,从1写入到3时刻empty拉低时,经过了3个rd_clk。
所以这里我们可以得出结论:rd_water_level要滞后wr_water_level三个rd_clk。
(2)FIFO将满时的写时序

将满时主要分析full和almost_full信号。假设Almost Full Numbers设置为N-2,在1时刻,此时已经写入了N-6个数据,意味着再写6个数据FIFO就满了,从1时刻到2时刻一共写入了4个数据,因此当wr_water_level变成N-2时,满足条件,可以看到Almost Full信号拉高,再写两个数据FIFO就满了,所以再经过两个时钟周期后,Full信号拉高。
(3)FIFO在满状态下的读时序

在满状态下,FIFO已经有N个数据了,此时在1状态下,rd_clk的上升沿,且rd_en为高电平时,此时从FIFO里读出数据(数据的输出有延时,仿真中延时0.2ns)。此时rd_water_level变成N-1,rd_data输出D0。然后看2时刻,full信号拉低,此时可以看以下,在1时刻到2时刻期间一共经过了3个wr_clk写端口才能判断到此时数据量已经不为满。 所以我们可以得出结论,wr_water_level要滞后rd_water_level三个wr_clk。
(4)FIFO将空时的读时序

在1时刻,可读的数据量剩下4,假设Almost Empty Number设为2,在1时刻和2时刻分别读出了两个数据,所以在2时刻下,可读数据量剩下两个,达到Almost Empty Number触发条件,因此almost_empty信号拉高,再过两个时钟周期,即再读两个数据,FIFO将变成空状态,也就是状态3,此时empty信号拉高。
2.1.2接口列表
该部分介绍每个顶层模块的接口。
ram_test_top.v
端口 | I/O | 位宽 | 描述 |
---|---|---|---|
wr_clk | input | 1 | 写时钟 |
rd_clk | input | 1 | 读时钟 |
rst_n | input | 1 | 全局复位 |
rw_en | input | 1 | 1:写操作 0:读操作 |
wr_addr | input | 5 | 写地址 |
rd_addr | input | 5 | 读地址 |
Wr_data | input | 8 | 写入RAM的数据 |
Rd_data | output | 8 | 从RAM读出的数据 |
rom_test_top.v
端口 | I/O | 位宽 | 描述 |
---|---|---|---|
rd_clk | input | 1 | 读时钟 |
rst_n | input | 1 | 全局复位 |
rd_addr | input | 10 | 读地址 |
rd_data | input | 64 | 从ROM读出的数据 |
fifo_test_top.v
端口 | I/O | 位宽 | 描述 |
---|---|---|---|
sys_clk | input | 1 | 写/读时钟 |
rst_n | input | 1 | 全局复位 |
wr_addr | input | 8 | 写入FIFO的数据 |
wr_en | input | 1 | 写使能 |
rd_en | input | 1 | 读使能 |
wr_water_level | output | 8 | 已写入FIFO的数据量 |
rd_water_level | output | 8 | 可从FIFO读出的数据量 |
Rd_data | output | 8 | 从FIFO读出的数据 |
4.3 工程说明
暂无
4.4 代码仿真说明
本次的顶层模块实际就是例化IP,然后把端口引出而已,主要代码都在testbench里面,所以我们直接介绍仿真代码。
4.4.1 RAM仿真测试
`timescale 1ns/1ns
module ram test tb()
reg sys clk
reg rd clk
reg rst n
reg rw en //读写使能信号
reg [7:0] wr data
reg [4:0] wr addr
reg [4:0] rd addr
wire [7:0] rd data
reg [1:0] state
initial
begin
rst n <= 1'd0
sys clk <= 1'd0
rd clk <= 1'd0
#20
rst n <= 1'd1
end
//读写控制
always@(posedge sys clk or negedge rst n) begin
if(!rst n)
begin
state <= 2'd0
wr data <= 8'd0
rw en <= 1'd0
wr addr <= 8'd0
rd addr <= 8'd0
end
else
begin
case(state)
2'd0:begin
rw en <= 1'd1
state <= 2'd1
end
2'd1:begin
if(wr addr == 5'd31)
begin
rw en <= 1'd0
state <= 2'd2
wr data <= 8'd0
wr addr <= 5'd0
rd addr <= 5'd0
end
else
begin
state <= 2'd1
wr data <= wr data+1'b1
rd addr <= rd addr+1'b1
wr addr <= wr addr+1'b1
end
end
2'd2:begin
if(rd addr == 5'd31)
begin
state <= 2'd3
rd addr <= 5'd0
end
else
begin
state <= 2'd2
rd addr <= rd addr+1'b1
end
end
2'd3:begin
state <= 2'd0
end
default: state <= 2'd0
endcase
end
end
//50MHZ
always#10 sys clk = ~sys clk
//
GTP GRS GRS INST(
GRS N(1'b1)
)
ram test top u ram test top(
wr clk ( sys clk "),"
rd clk ( sys clk "),"
rst n ( rst n "),"
rw en ( rw en "),"
wr addr ( wr addr "),"
rd addr ( rd addr "),"
wr data ( wr data "),"
rd data ( rd data )
)
endmodule
涉及到tb的一些基础操作这里就不再详细讲解,只关注重点逻辑部分。从代码的27行到80行是ram的读写控制状态机。主要用来控制读写地址的生成和使能以及写入的数据。这里只讲解主要实现的功能,首先代码的38-42行,也就是state=0的时候,拉高rw_en,并跳转到状态1,此时进入写操作(没有使能clk_en,可以不管),下个时钟周期开始写入数据(注意是时序逻辑,边沿采样,所以是下个时钟周期才开始写数据),即state=1的时候是一直在往ram里面写数据,在代码的44到60行就是写操作了,可以看到,当wr_addr不等于31的时候,wr_data和wr_addr不断加1(rd_addr这里+1,可以看视频讲解,主要为了验证伪双端口的时序),当wr_addr等于31的时候,在下个时钟周期把数据清0,状态跳转,在当前时钟周期下还会再往地址31里面写入数据,所以在该时钟周期,一共写入了32个数据(从地址0写到地址31)。即状态1完成写入32个数据后跳转到state=2的逻辑。代码的61-72行,也就是state=2的时候,在每个周期的上升沿让rd_addr不断累加,直到rd_addr=31的时候,在下个时钟周期清空地址并让状态跳转的操作,而在当前时钟周期会继续把地址31的数据读出来,完成读取地址0-31的数据,一共32个数据,所以该状态主要完成读取32个数据,然后在下个时钟周期就跳转到state=3。state=3可以看到其主要作用就是等待一个时钟周期,然后跳转回去state=0下,起到一个延时作用。

上图为写数据的波形,数据从0开始递增到31,地址也是从0到31。

上图为读数据波形,从地址0-31读出了0-31个数据。
具体波形大家可以看视频仿真,或者自己尝试仿真,根据波形来看代码。因为这里是时序逻辑,所以如果是初学者,纯看文字可能会对rd_addr=31这一时刻还会再读一个数据感到疑惑,建议直接仿真,或者观看视频讲解的仿真部分,可以帮助快速理解。
可以总结出一句话就是时序逻辑的赋值总在下一个时钟周期才生效。所以在rd_addr=31时执行的操作要在下一个时钟周期才会被采样生效。所以当前时钟还是会再从RAM读出一个数据。
4.4.2 ROM仿真测试
`timescale 1ns/1ns
module rom_test_tb();
reg sys_clk;
reg rst_n;
reg [9:0] rd_addr;
wire [63:0] rd_data;
initial
begin
rst_n <= 1'd0;
sys_clk <= 1'd0;
#20
rst_n <= 1'd1;
end
//50MHZ
always#10 sys_clk = ~sys_clk;
//
GTP_GRS GRS_INST(
.GRS_N(1'b1)
) ;
always@(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
rd_addr <= 10'd0;
else
rd_addr <= #2 rd_addr + 1'b1;
end
rom_test_top u_rom_test_top(
.rd_clk ( sys_clk ),
.rst_n ( rst_n ),
.rd_addr ( rd_addr ),
.rd_data ( rd_data )
);
endmodule
代码31-36行例化了ROM的顶层模块,该模块里面其实就是调用了ROM IP,然后把信号引出端口,没有任何逻辑操作。
代码24-29行通过一个always块不断生成地址,任何给到ROM IP,将数据读出,由于没勾选clk_en信号,所以数据在ROM复位完成后就会不断读出。所以并没有复杂的逻辑,就是让地址从0不断累加,把数据读出。

上图为读出数据的波形图,可以看到读出的数据和dat文件里的数据一致。

4.4.3 FIFO仿真测试
`timescale 1ns/1ns
module fifo_test_tb();
reg sys_clk;
reg rst_n;
reg [7:0] wr_data;
reg wr_en;
reg rd_en;
reg rd_state; //读状态
reg wr_state;
wire [7:0] rd_data;
reg [7:0] rd_cnt;
wire [7:0] rd_water_level;
wire [7:0] wr_water_level;
initial
begin
rst_n <= 1'd0;
sys_clk <= 1'd0;
#20
rst_n <= 1'd1;
end
always#10 sys_clk = ~sys_clk; //50MHZ
always@(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
begin
wr_state <= 1'd0;
wr_en <= 1'd0;
wr_data <= 8'd0;
end
else
begin
case(wr_state)
1'd0: if(wr_water_level == 127) //128个数据
begin
wr_en <= #2 1'd0;
wr_data <= #2 8'd0;
wr_state <= #2 1'd1;
end
else
begin
wr_en <= #2 1'd1;
wr_data <= #2 wr_data+1'b1;
wr_state <= #2 1'd0;
end
1'd1: if(rd_cnt == 127)
wr_state <= #2 1'd0;
default: wr_state <=1'd0;
endcase
end
end
always@(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
begin
rd_state<= 1'd0;
rd_en <= 1'd0;
rd_cnt <= 8'd0;
end
else
begin
case(rd_state)
1'd0: if(rd_water_level >= 8'd128) //等待128个数据
begin
rd_state <= #2 1'd1;
rd_en <= #2 1'd1;
end
else
begin
rd_cnt <= #2 8'd0;
rd_state <= #2 1'd0;
end
1'd1: begin
rd_cnt <= #2 rd_cnt + 1'b1;
if(rd_cnt == 127)
begin
rd_en <= #2 1'd0;
rd_state <= #2 1'd0;
end
end
default: rd_state <= 1'd0;
endcase
end
end
GTP_GRS GRS_INST(
GRS_N(1'b1)
) ;
fifo_test_top u_fifo_test_top(
sys_clk ( sys_clk "),"
rst_n ( rst_n "),"
wr_data ( wr_data "),"
wr_en ( wr_en "),"
rd_en ( rd_en "),"
wr_water_level ( wr_water_level "),"
rd_water_level ( rd_water_level "),"
rd_data ( rd_data )
);
endmodule
涉及到tb的一些基础操作这里就不再详细讲解,只关注重点逻辑部分。整个设计分为读写两个状态的控制。分别完成了写入128个数据,和读出128个数据,由于FIFO不需要地址,所以只需要产生使能信号即可。
首先看写状态,在wr_state=0时,拉高写使能,并让wr_data不断累加,往FIFO里面写数据,当wr_water_level=127的时候,拉低写使能,写数据置0,写状态跳转到1,注意此时还会再写入一个数据,所以到此一个写入了128个数据。至于拉低写使能,写数据置0,写状态跳转到1这些操作将在下一个时钟周期才会被采样生效。之后,在wr_state=1时,不断等待rd_cnt,该条件就是判断当读出128个数据的时候,wr_state跳转到0状态。
接下来看读状态,在rd_state=0的时候,一旦可读的数据量超过128个(包括128),状态跳转到rd_state=1下,然后开始读出数据,同时在rd_state=1下用变量rd_cnt对我们的读出数据也进行计数,rd_cnt从0开始计数,当rd_cnt=127的时候会再往FIFO读出一个数据,所以此时就一共读出了128个数据,下一个时钟周期rd_en和rd_state都将置0。

写数据的波形如上所示,一共写入128个数据,从1写到128。

读数据的波形如上所示,一共读出128个数据,从1读到128。
具体波形大家可以看视频仿真,或者自己尝试仿真,根据波形来看代码。因为这里是时序逻辑,所以如果是初学者,纯看文字可能会对rd_cnt=127这一时刻还会再读一个数据感到疑惑,建议直接仿真,或者观看视频讲解的仿真部分,可以帮助快速理解。
可以总结出一句话就是时序逻辑的赋值总在下一个时钟周期才生效。所以在rd_cnt=127时执行的操作要在下一个时钟周期才会被采样生效。所以当前时钟rd_en还是为1,会再从FIFO读出一个数据。
5 DDR3读写实验例程
5.1 实验简介
实验目的: 完成DDR3的读写测试。
实验环境: Window11 PDS2022.2-SP6.4
芯片型号: PG2L50H-484
5.2 实验原理
开发板集成1颗4Gbit(512MB)DDR3 芯片,型号为MT41K256M16。DDR3 的总线宽度共为16bit。DDR3 SDRAM 的最高数据速率1066Mbps。
5.2.1 DDR3控制器简介
PG2L50H为用户提供一套完整的DDR memory 控制器解决方案,配置方式比较灵活,采用软核实现DDR memory的控制,有如下特点:
- 支持DDR3
- 支持x8、x16 Memory Device
- 最大位宽支持32 bit
- 支持精简的AXI4 总线协议
- 一个AXI4 256 bit Host Port
- 支持Self_refresh,Power down
- 支持Bypass DDRC
- 支持DDR3 Write Leveling 和DQS Gate Training
- DDR3 最快速率达1066 Mbps
5.3 工程说明
PDS安装后,需手动添加DDR3 IP,请按以下步骤完成:
DDR3 IP文件:PG2L_IP\PG2L_IP\DDR3\ips2l_hmic_s_v1_10.iar
5.3.1 DDR3读写Example工程
打开PDS软件,新建工程ddr3_test,点开如下图标,打开IP Compiler;

选择DDR3 IP,取名ddr3_test,然后点击Customize;

在DDR3设置界面中Step1按照如下设置:

Step2按照如下设置,需要自己新建DDR3模型,选择MT41K256M16XX为模板,并保持Timing parameters和地址以及Drive Options和下图设置的一致。

Step3按照如下设置,勾选Custom Control/Address Group,管脚约束参考原理图:


提醒:
在设置IP核时,step 3:pin/bank options 中,管脚设置中的Group Number 与原理图的对应关系如下图
R5表示BANK5,G1表示Group Nmuber为1。

Step4为概要,点击Generate可生成DDR3 IP;

关闭本工程,按此路径打开Example工程:
Xxxxx\ddr3_test\ip_core\ddr3\pnr

打开顶层文件,需要对顶层文件进行修改,具体参考详细代码。下图是经过修改后的顶层文件。

对“Step3已做管脚约束”外的其他管脚,对照原理图使用UCE工具进行修改,移植的话可以直接参考工程的fdc文件进行移植。

以下管脚可约束在LED,方便观察实验现象;

可按以下方式查看IP核的用户指南,了解Example模块组成;

5.4 实验现象
下载程序,可以看到LED1常亮,LED3闪烁, LED4闪烁,LED5常亮;
信号名称 | 参考说明 | LED编号 |
---|---|---|
err_flag_led | 数据检测错误信号 | 3 |
heart_beat_led | 心跳信号 | 4 |
提醒:
Heart_beat_led信号闪烁表示ddrphy系统时钟正常。

err_flag_led 信号闪烁说明数据检测无错误。可在IP核数据手册中找到。

如果正常的话,err_flag_led闪烁的速度快于heart_beat_led的闪烁速度。
6 光纤通信测试实验例程
6.1 实验简介
实验目的: 通过光纤连接实现光模块之间的数据收发。
实验环境: Window11 PDS2022.2-SP6.4
芯片型号: PG2L50H-484
6.2 实验原理
PG2L100H内置了线速率高达6.6Gbps 高速串行接口模块,即HSSTLP,包含1个HSSTLP,共4个全双工收发LANE,除了PMA,HSSTLP 还集成了丰富的PCS 功能,可灵活应用于各种串行协议标准。在产品内部,每个HSST 支持1~4 个全双工收发LANE。HSST 主要特性包括:
- 支持 DataRate 速率:0.6Gbps-6.6Gbps
- 灵活的参考时钟选择方式
- 发送通道和接收通道数据率可独立配置
- 可编程输出摆幅和去加重
- 接收端自适应线性均衡器 Logos2系列FPGA器件数据手册
- PMA Rx 支持 SSC
- 数据通道支持数据位宽:8bit only, 10bit only, 8b10b, 16bit only, 20bit only, 32bit only, 40bit only,64b66b/64b67b 等模式
- 可灵活配置的 PCS,可支持 PCI Express GEN1, PCI Express GEN2,XAUI,千兆以太 网,CPRI,SRIO 等协议
- 灵活的 Word Alignment 功能
- 支持 RxClock Slip 功能以保证固定的 Receive Latency
- 支持协议标准 8b10b 编码解码
- 支持协议标准 64b66b/64b67b 数据适配功能
- 灵活的 CTC 方案
- 支持 x2 和 x4 的 Channel Bonding
- HSSTLP 的配置支持动态修改
- 近端环回和远端环回模式
- 内置 PRBS 功能
- 自适应
6.3 工程说明
6.3.1 安装HSST IP核
PDS安装后,需手动添加HSST IP,请按以下步骤完成:
(1)HSST IP文件:选择1_9.iar
(2)IP安装步骤:请查看 “工具使用篇\03_IP核安装与查看用户指南”

6.3.2 光纤通信测试例程
打开PDS软件,新建工程hsst_test,点开如下图标,打开IP Compiler;

选择HSST IP,取名,然后点击Customize;

在HSST设置界面中Protocol and Rate按照如下设置,Channel0、 Channel1、Channel3为DISABLE,Channel2设置为Fullduplex(全双工),Protocol(协议)选择CUSTOMERIZEDX1(自定义模式),TX Line Rate和RX Line Rate均选择6.25Gbps,Encoder均选择8B10B,Data Width选择32bit,时钟选择Diff_REFCK0,选择125MHZ。

Alignment and CTC按照如下设置,Word Align Mode选择CUSTOMERIZED_MODE。控制字(COMMA code-group select)选择K28.5,CTC_MODE选择Bypassed。

Misc按照如下设置,时钟选择25MHZ,其余保持默认,然后点击Generate可生成HSST IP;

关闭本工程,按此路径打开Example工程: hsst_test\hsst_test\ipcore\hsst_test\pnr\example_design

为了能在开发板上运行,需对顶层文件hsst_test_dut_top的复位进行修改,详情请查看例程顶层文件:

上图是修改前的顶层文件。
下图是修改后的顶层文件。tx_disable需要拉低才能打开SFP发射功能。


上图为部分管脚约束,具体需要查看工程的fdc文件,注意图中红色方框部分是hsst_lane和hsst_pll的位置约束,是必须要添加的,差分数据管脚可以不用约束,提供给hsst的参考时钟必须约束即(i_p_refckn_0和i_p_refckp_0)。

为了观察收发数据是否有误,需要进行Debugger插核操作。
时钟选择o_p_clk2core_tx_2。
可按以下方式查看IP核的用户指南,了解Example模块组成;

6.4 实验现象
注:例程位置:hsst_test\hsst_test\ipcore\hsst_test\pnr\example_design

把光纤两端接入SFP口(用户需购买光模块),进行Debugger在线调试,可看到窗口中发送和接收的数据一致的。

观察tx_data和rx0_data_align,两者一样说明收发没有问题。
7 以太网传输实验例程
7.1 实验简介
实验目的: 完成以太网通信测试。
实验环境: Window11 PDS2022.2-SP6.4
硬件环境: PG2L50H-484
7.2 实验原理
7.2.1 开发板以太网接口简介
开发板使用 裕泰微的 裕太微电子的YT8521SH-CA 实现了一个 10/100/1000 以太网端口。使用时需要用到光电转换模块,通过网线连接电脑的网口和光电转换模块的电口即可完成通信。
7.2.2 以太网协议简介
7.2.2.1 以太网帧格式

前导码(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 字节。

7.2.2.2 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 ff ff ff ff ff|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 应答。
7.2.2.3 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 字节,分别记录源地址和目的地址;
7.2.2.4 UDP 协议
UDP 是 User Datagram Protocol(用户数据报协议)的英文缩写。UDP 只提供一种基本的、低延迟的被称为数据报的通讯。所谓数据报,就是一种自带寻址信息,从发送端走到接收端的数据包。UDP 协议经常用于图像传输、网络监控数据交换等数据传输速度要求比较高的场合。
UDP 协议的报头格式:
UDP 报头由 4 个域组成,其中每个域各占用 2 个字节,具体如下:

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


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

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

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

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

为了保证能够正确采集到数据,在 MDC 上升沿之前就把数据准备好,在本实验中为下降沿发送数据,上升沿接收数据。
7.4 实验设计
本实验以千兆以太网 RGMII 通信为例来设计 verilog 程序,会先发送预设的 UDP 数据到网络,每秒钟发送一次.程序分为两部分,分别为发送和接收,实现了 ARP,UDP 功能。
7.4.1 发送部分
7.4.1.1 MAC 层发送
发送部分中,mac_tx.v 为 MAC 层发送模块,首先在 SEND_START 状态,等待 mac_tx_ready信号,如果有效,表明 IP 或 ARP 的数据已经准备好,可以开始发送。再进入发送前导码状态,结束时发送 mac_data_req,请求 IP 或 ARP 的数据,之后进入发送数据状态,最后进入发送 CRC 状态。在发送数据过程中,需要同时进行 CRC 校验。前导码完成后就将上层协议数据发送出去,这个时候同样把这些上层数据放到 CRC32 模块中做序列生成,上层协议会给一个数据输出完成标志信号,这个时候 mac_tx 知道数据发送完成了,需要结束 CRC32 的序列生成,这个时候就开始提取 FCS,衔接数据之后发送出去。这样就连接了前导码---数据(Mac 帧)----FCS。之后跳转到结束状态,再回到 IDLE 状态,等待下一次的发送请求。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
mac_frame_data | input | 8 | 从 IP 或 ARP 来的数据 |
mac_tx_req | input | 1 | MAC 的发送请求 |
mac_tx_ready | input | 1 | IP 或 ARP 数据已准备好 |
mac_tx_end | input | 1 | IP 或 ARP 数据已经传输完毕 |
mac_tx_data | output | 8 | 向 PHY 发送数据 |
mac_send_end | output | 1 | MAC 数据发送结束 |
mac_data_valid | output | 1 | MAC 数据有效信号,即 gmii_tx_en |
mac_data_req | output | 1 | MAC 层向 IP 或 ARP 请求数据 |
mac_tx_ack | output | 1 | MAC 层发送数据的对于 UPPER 请求的应答 |
7.4.1.2 MAC 发送模式
工程中的 mac_tx_mode.v 为发送模式选择,根据发送模式是 IP 或 ARP 选择相应的信号与数据。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
mac_send_end | input | 1 | MAC 发送结束 |
arp_tx_req | input | 1 | ARP 发送请求 |
arp_tx_ready | input | 1 | ARP 数据已准备好 |
arp_tx_data | input | 8 | ARP 数据 |
arp_tx_end | input | 1 | ARP 数据发送到 MAC 层结束 |
arp_tx_ack | input | 1 | ARP 发送响应信号 |
ip_tx_req | input | 1 | IP 发送请求 |
ip_tx_ready | input | 1 | IP 数据已准备好 |
ip_tx_data | input | 8 | IP 数据 |
ip_tx_end | input | 1 | IP 数据发送到 MAC 层结束 |
mac_tx_ready | output | 1 | MAC 数据已准备好信号 |
ip_tx_ack | output | 1 | IP 发送响应信号 |
mac_tx_ack | output | 1 | MAC 发送响应信号 |
mac_tx_req | output | 1 | MAC 发送请求 |
mac_tx_data | output | 8 | MAC 发送数据 |
mac_tx_end | output | 1 | MAC 数据发送结束 |
7.4.1.3 ARP 发送
发送部分中,arp_tx.v 为 ARP 发送模块, 在 IDLE 状态下,等待 ARP 发送请求或 ARP应答请求信号,之后进入请求或应答等待状态,并通知 MAC 层,数据已经准备好,等待 mac_data_req 信号,之后进入请求或应答数据发送状态。由于数据不足 46 字节,需要补全46 字节发送。

信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
dest_mac_addr | input | 48 | 发送的目的 MAC 地址 |
sour_mac_addr | input | 48 | 发送的源 MAC 地址 |
sour_ip_addr | input | 32 | 发送的源 IP 地址 |
dest_ip_addr | input | 32 | 发送的目的 IP 地址 |
mac_data_req | input | 1 | MAC 层请求数据信号 |
arp_request_req | input | 1 | ARP 请求的请求信号 |
arp_reply_ack | output | 1 | ARP 回复的应答信号 |
arp_reply_req | input | 1 | ARP 回复的请求信号 |
arp_rec_sour_ip_addr | input | 32 | ARP 接收的源 IP 地址,回复时放到目的 IP 地址 |
arp_rec_sour_mac_addr | input | 48 | ARP 接收的源 MAC 地址,回复时放到目的 MAC 地址 |
mac_send_end | input | 1 | MAC 发送结束 |
mac_tx_ack | input | 1 | MAC 发送应答 |
arp_tx_ready | output | 1 | ARP 数据准备好 |
arp_tx_data | output | 8 | ARP 发送数据 |
arp_tx_end | output | 1 | ARP 数据发送结束 |
arp_tx_req | output | 1 | ARP 发送请求信号 |
7.4.1.4 IP 层发送
在发送部分,ip_tx.v 为 IP 层发送模块,在 IDLE 状态下,如果 ip_tx_req 有效,也就是 UDP 或 ICMP 发送请求信号,进入等待发送数据长度状态,之后进入产生校验和状态,校验和是将 IP 首部所有数据以 16 位相加,最后将进位再与低 16 位相加,直到进入为 0,再将低16 位取反,得出校验和结果。
在生成校验和之后,等待 MAC 层数据请求,开始发送数据,并在即将结束发送 IP 首部后请求 UDP 或 ICMP 数据。等发送完,进入 IDLE 状态。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
dest_mac_addr | input | 48 | 发送的目的 MAC 地址 |
sour_mac_addr | input | 48 | 发送的源 MAC 地址 |
sour_ip_addr | input | 32 | 发送的源 IP 地址 |
dest_ip_addr | input | 32 | 发送的目的 IP 地址 |
ttl | input | 8 | 生存时间 |
ip_send_type | input | 8 | 上层协议号,如 UDP,ICMP |
upper_layer_data | output | 8 | 从 UDP 或 ICMP 过来的数据 |
upper_data_req | input | 1 | 向上层请求数据 |
mac_tx_ack | input | 1 | MAC 发送应答 |
mac_send_end | input | 1 | MAC 发送结束信号 |
mac_data_req | input | 1 | MAC 层请求数据信号 |
upper_tx_ready | input | 1 | 上层 UDP 或 ICMP 数据准备好 |
ip_tx_req | input | 1 | 发送请求,从上层过来 |
ip_send_data_length | input | 16 | 发送数据总长度 |
ip_tx_ack | output | 产生 IP 发送应答 | |
ip_tx_ready | output | 1 | IP 数据已准备好 |
ip_tx_data | output | 8 | IP 数据 |
ip_tx_end | output | 1 | IP 数据发送到 MAC 层结束 |
7.4.1.5 IP 发送模式
工程中的 ip_tx_mode.v 为发送模式选择,根据发送模式是 UDP 或 ICMP 选择相应的信号与数据。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
mac_send_end | input | MAC 数据发送结束 | |
udp_tx_req | input | 1 | UDP 发送请求 |
udp_tx_ready | input | 1 | UDP 数据准备好 |
udp_tx_data | input | 8 | UDP 发送数据 |
udp_send_data_length | input | 16 | UDP 发送数据长度 |
udp_tx_ack | output | 1 | 输出 UDP 发送应答 |
icmp_tx_req | input | 1 | ICMP 发送请求 |
icmp_tx_ready | input | 1 | ICMP 数据准备好 |
icmp_tx_data | input | 8 | ICMP 发送数据 |
icmp_send_data_length | input | 16 | ICMP 发送数据长度 |
icmp_tx_ack | output | 1 | ICMP 发送应答 |
ip_tx_ack | input | 1 | IP 发送应答 |
ip_tx_req | input | 1 | IP 发送请求 |
ip_tx_ready | output | 1 | IP 数据已准备好 |
ip_tx_data | output | 8 | IP 数据 |
ip_send_type | output | 8 | 上层协议号,如 UDP,ICM |
ip_send_data_length | output | 16 | 发送数据总长度 |
7.4.1.6 UDP 发送
发送部分中,udp_tx.v 为 UDP 发送模块。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
app_data_in_valid | input | 1 | 从外部所接收的数据输出有效信号 |
app_data_in | input | 8 | 外部所接收的数据 |
app_data_length | input | 16 | 从外部所接收的当前数据包的长度(不含 udp、ip、mac 首部) |
udp_dest_port | input | 16 | 从外部所接收的数据包的源端口号 |
app_data_request | input | 1 | 用户接口数据发送请求 |
udp_send_ready | output | 1 | UDP 数据发送准备 |
udp_send_ack | output | 1 | UDP 数据发送应当 |
ip_send_ready | input | 1 | IP 数据发送准备 |
ip_send_ack | input | 1 | IP 数据发送应当 |
udp_send_request | output | 1 | 用户接口数据发送请求 |
udp_data_out_valid | output | 1 | 发送的数据输出有效信号 |
udp_data_out | output | 8 | 发送的数据输出 |
udp_packet_length | output | 16 | 当前数据包的长度(不含 udp、 ip、mac 首部) |
7.4.2 接收部分
7.4.2.1 MAC 层接收
在接收部分,其中 mac_rx.v 为 mac 层接收文件,首先在 IDLE 状态下当 rx_en 信号为高,进入 REC_PREAMBLE 前导码状态,接收前导码。之后进入接收 MAC 头部状态,即目的 MAC地址,源 MAC 地址,类型,将它们缓存起来,并在此状态判断前导码是否正确,错误则进入REC_ERROR 错误状态,在 REC_IDENTIFY 状态判断类型是 IP(8’h0800)或 ARP(8’h0806)。然后进入接收数据状态,将数据传送到 IP 或 ARP 模块,等待 IP 或 ARP 数据接收完毕,再接收 CRC 数据。并在接收数据的过程中对接收的数据进行 CRC 处理,将结果与接收到的 CRC 数据进行对比,判断数据是否接收正确,正确则结束,错误则进入 ERROR 状态。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
rx_en | input | 1 | 开始接受使能 |
mac_rx_datain | input | 8 | 接受的数据 |
checksum_err | input | 1 | IP 层校验错误信号 |
ip_rx_end | input | 1 | IP 接受结束 |
arp_rx_end | input | 1 | ARP 接受结束 |
ip_rx_req | output | 1 | IP 接受请求 |
arp_rx_req | input | 1 | 请求 ARP 接收 |
mac_rx_dataout | output | 8 | MAC 层接收数据输出给 IP 或 ARP |
mac_rec_error | output | 1 | MAC 层接收错误 |
mac_rx_dest_mac_addr | output | 48 | MAC 接收的目的 IP 地址 |
mac_rx_sour_mac_addr | output | 48 | MAC 接收的源 IP 地址 |
7.4.2.2 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 缓存中。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
local_ip_addr | input | 32 | 本地 IP 地址 |
local_mac_addr | input | 48 | 本地 MAC 地址 |
arp_rx_data | input | 8 | ARP 接收数据 |
arp_rx_req | input | 1 | ARP 接收请求 |
arp_rx_end | output | 1 | ARP 接收完成 |
arp_reply_ack | input | 1 | ARP 回复应答 |
arp_reply_req | output | 1 | ARP 回复请求 |
arp_rec_sour_ip_addr | input | 32 | ARP 接收的源 IP 地址 |
arp_rec_sour_mac_addr | input | 48 | ARP 接收的源 MAC 地址 |
arp_found | output | 1 | ARP 接收到请求应答正确 |
7.4.2.3 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 状态,丢弃此帧数据,等待下次接收。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
local_ip_addr | input | 32 | 本地 IP 地址 |
local_mac_addr | input | 48 | 本地 MAC 地址 |
ip_rx_data | input | 8 | 从 MAC 层接收的数据 |
ip_rx_req | input | 1 | MAC 层发送的 IP 接收请求信号 |
mac_rx_dest_mac_addr | input | 48 | MAC 层接收的目的 MAC 地址 |
udp_rx_req | output | 1 | UDP 接收请求信号 |
icmp_rx_req | output | 1 | ICMP 接收请求信号 |
ip_addr_check_error | output | 1 | 地址检查错误信号 |
upper_layer_data_length | output | 16 | 上层协议的数据长度 |
ip_total_data_length | output | 16 | 数据总长度 |
net_protocol | output | 8 | 网络协议号 |
ip_rec_source_addr | output | 32 | IP 层接收的源 IP 地址 |
ip_rec_dest_addr | output | 32 | IP 层接收的目的 IP 地址 |
ip_rx_end | output | 1 | IP 层接收结束 |
ip_checksum_error | output | 1 | IP 层校验和检查错误信号 |
7.4.2.4 UDP 接收
在工程中,udp_rx.v 为 UDP 接收模块,在此模块首先接收 UDP 首部,再接收数据部分,在接收的同时进行 UDP 校验和检查,如果 UDP 数据是奇数个字节,在计算校验和时,在最后一个字节后加上 8’h00,并进行校验和计算。校验方法与 IP 校验和一样,如果校验正确,将拉高 udp_rec_data_valid 信号,表明接收的 UDP 数据有效,否则无效,等待下次接收。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
udp_rx_data | input | 8 | UDP 接收数据 |
udp_rx_req | input | 1 | UDP 接收请求 |
ip_checksum_error | input | 1 | IP 层校验和检查错误信号 |
ip_addr_check_error | input | 1 | 地址检查错误信号 |
udp_rec_rdata | output | 8 | UDP 接收读数据 |
udp_rec_data_length | output | 16 | UDP 接收数据长度 |
udp_rec_data_valid | output | 1 | UDP 接收数据有效 |
7.4.3 其他部分
7.4.3.1 ICMP 应答
在工程中,icmp_reply.v 实现 ping 功能,首先接收其他设备发过来的 icmp 数据,判断类型是否是回送请求(ECHO REQUEST),如果是,将数据存入 RAM,并计算校验和,判断校验和是否正确,如果正确则进入发送状态,将数据发送出去。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
mac_send_end | input | 1 | Mac 发送结束信号 |
ip_tx_ack | input | 1 | IP 发送应答 |
icmp_rx_data | input | 8 | ICMP 接收数据 |
icmp_rx_req | input | 1 | ICMP 接收请求 |
icmp_rev_error | input | 1 | 接收错误信号 |
upper_layer_data_length | input | 16 | 上层协议长度 |
icmp_data_req | input | 1 | 请求 ICMP 数据 |
icmp_tx_ready | output | 1 | ICMP 发送准备好 |
icmp_tx_data | output | 8 | ICMP 发送数据 |
icmp_tx_end | output | 1 | ICMP 发送结束 |
icmp_tx_req | output | 1 | ICMP 发送请求 |
7.4.3.2 ARP 缓存
在工程中,arp_cache.v 为 arp 缓存模块,将接收到的其他设备 IP 地址和MAC 地址缓存,在发送数据之前,查询目的地址是否存在,如果不存在,则向目的地址发送 ARP 请求,等待应答。在设计文件中,只做了一个缓存空间,如果有需要,可扩展。
信号名称 | 方向 | 位宽 | 说明 |
---|---|---|---|
clk | input | 1 | 系统时钟 |
rstn | input | 1 | 低电平复位 |
arp_found | input | 1 | ARP 接收到回复正确 |
arp_rec_source_ip_addr | input | 32 | ARP 接收的源 IP 地址 |
arp_rec_source_mac_addr | input | 48 | ARP 接收的源 MAC 地址 |
dest_ip_addr | input | 32 | 目的 IP 地址 |
dest_mac_addr | output | 48 | 目的 MAC 地址 |
mac_not_exist | output | 1 | 目的地址对应的 MAC 地址不存在 |
7.4.3.3 CRC 校验模块(crc.v)
CRC32 校验是在目标 MAC 地址开始计算的,一直计算到一个包的最后一个数据为止。一些网站可以自动生成 CRC 算法的 verilog 文件:https://bues.ch/cms/hacking/crcgen.html

7.5 实验现象
用光电转换模块插入SFP口,再用一根网线和 PC 端网口相连;
设置接收端(PC 端)IP 地址为 192.168.0.3,开发板的 IP 地址为 192.168.0.2 如下图:

通过命令提示符,输入 arp -a,可以查到 IP:192.168.0.2 MAC:a0_b1_c2_d3_e1_e1;

通过 Wireshark 软件抓包验证数据链路是否正常连接以及数据传输是否正常。资料包中PC 端打开 Wireshark 软件,烧录重新后进行捕获数据报可以看到如下所示的交互过程。
成功建立连接后会持续发送数据报“www.meyesemi.com”。如下所示:


Ping功能测试,由上图可知,ping基本不丢包。