一、DDS
DDS(Data Distribution Service) 是一种面向实时分布式系统的通信中间件标准,专为高性能、高可靠性、低延迟的数据传输场景设计。它由对象管理组织(OMG) 制定并维护,广泛应用于物联网(IoT)、工业自动化、航空航天、自动驾驶、医疗设备等领域。
(一).核心特点
发布-订阅模型(Pub/Sub)
数据生产者(发布者)和消费者(订阅者)通过主题(Topic)进行通信,实现松耦合的分布式架构,无需直接依赖对方地址或接口。服务质量(QoS, Quality of Service)
DDS 提供丰富的 QoS 策略(如可靠性、持久性、时效性、优先级等),允许开发者根据场景需求(如实时性、容错性)灵活配置数据传输行为。实时性与低延迟
针对关键任务场景(如自动驾驶、机器人控制),DDS 支持微秒级延迟和确定性响应,确保数据高效传输。去中心化架构
无单点故障,节点间直接通信(P2P),适合高可靠、高扩展性的分布式系统。跨平台与标准化
支持多种操作系统(Linux、Windows、嵌入式 RTOS)和编程语言(C/C++、Java、Python等),遵循 DDSI-RTPS 协议保障互操作性。
(二).典型应用场景
工业物联网(IIoT):工厂设备实时监控与协同控制。
自动驾驶:车辆传感器、决策系统间高速数据共享。
航空航天:飞行器子系统(导航、通信)的可靠数据交换。
医疗设备:手术机器人、监护仪等实时数据同步。
智能交通:交通信号、车辆与云端系统的动态协调。
(三).技术优势
可靠性:通过 QoS 确保关键数据不丢失(如“可靠传输”模式)。
灵活性:动态发现节点,支持系统动态扩展。
安全性:支持数据加密、身份认证等安全机制(DDS Security 标准)。
高效性:零拷贝传输、多播通信优化网络带宽。
(四).主流实现
OpenDDS(开源)
RTI Connext DDS(商业级,广泛应用在关键领域)
Eclipse Cyclone DDS(开源,被 ROS 2 采用)
二、FPGA存储器
FPGA存储器 是FPGA(现场可编程门阵列)中用于数据存储的关键资源,通常分为内部存储器和外部存储器接口两类。FPGA的灵活性和可编程性使其在存储管理方面具有独特的优势,广泛应用于高速缓存、数据缓冲、状态机存储等场景。以下是关于FPGA存储器的详细介绍:
(一)FPGA内部存储器
FPGA芯片内部集成了多种专用存储资源,支持高速、低延迟的片上数据存储。主要类型包括:
1. 分布式RAM(Distributed RAM)
结构:由FPGA逻辑单元(LUT,查找表)配置而成,利用逻辑资源实现小容量存储。
特点:
容量小(通常几十到几百比特),但分布灵活,适合分散存储。
支持单端口或简单双端口读写。
读写延迟低(1~2个时钟周期)。
典型应用:状态机、小型查找表(LUT)、系数存储(如滤波器参数)。
2. Block RAM(BRAM)
结构:FPGA内部专用的块存储器,每个BRAM容量较大(如Xilinx UltraScale+的36Kb/块)。
特点:
大容量、高密度存储,可级联实现更大容量。
支持多种配置模式(单端口、双端口、真双端口)。
支持同步读写,带宽高(例如72位宽)。
独立于逻辑资源,不占用LUT。
典型应用:数据缓存(如图像帧缓冲)、FIFO、大规模查找表。
3. UltraRAM(URAM)
结构:某些高端FPGA(如Xilinx UltraScale+)中的超大容量存储块。
特点:
单块容量可达288Kb,适合超大规模数据存储。
低功耗、高带宽,支持复杂存储需求。
典型应用:深度学习加速器权重存储、大规模数据库缓存。
4. 寄存器(Register)
结构:FPGA逻辑单元中的触发器(Flip-Flop),用于存储1位数据。
特点:
超低延迟(单周期访问),但资源有限。
适合存储控制信号、计数器、状态位。
典型应用:流水线寄存器、状态机状态存储。
(二)FPGA外部存储器接口
当片上存储资源不足时,FPGA可通过高速接口连接外部存储器,常见类型包括:
1. DDR SDRAM(如DDR4、LPDDR4)
特点:
大容量(GB级)、高带宽(数十GB/s)。
需通过FPGA的硬核存储器控制器或软核IP实现接口。
应用:视频帧缓存、大数据处理。
2. SRAM
特点:
高速、低延迟,但容量较小(MB级)。
接口简单,无需复杂控制器。
应用:高速数据采集、实时信号处理。
3. Flash存储器
特点:
非易失性存储,适合配置存储或启动代码。
读写速度较慢,但容量大。
应用:FPGA配置文件的存储、系统启动引导。
4. QSPI Flash
特点:
低引脚数、低成本,用于小容量非易失存储。
通常用于存储FPGA的比特流(Bitstream)。
应用:嵌入式系统启动配置。
三、FPGA存储器的设计要点
资源分配优化:
根据需求选择分布式RAM、BRAM或URAM,平衡速度和资源占用。
例如,小容量频繁访问的数据用分布式RAM,大块数据用BRAM。
时序约束:
外部存储器接口需严格满足时序(如DDR的建立/保持时间)。
使用FPGA工具的时序分析工具(如Vivado的Timing Report)进行验证。
带宽与延迟权衡:
片上存储器(BRAM)延迟低但容量小,外部存储器容量大但延迟高。
可通过缓存(Cache)或预取(Prefetch)策略优化性能。
存储器安全:
对敏感数据(如加密密钥)启用ECC(纠错码)或物理保护机制。
(一)、ROM波形存储器的设计步骤
ROM波形存储器 是一种利用只读存储器(ROM) 预存周期性或非周期性波形数据(如正弦波、方波、三角波等)的硬件设计,广泛应用于数字信号合成、通信调制解调、音频生成等领域。
1、调用IP
(1)创建文件
我们单独创建一个ROM文件来熟悉ROM调用IP,我们在Quartus中创建一个新项目命名为ROM。
(2)调用IP
在左侧的IP Catalog搜索栏搜索“ROM”,然后选择“ROM:1-PORT”。
接下来在最开始我们创建的文件夹中添加一个名为IP的文件夹,随后我们将把IP文件保存在其中。
(3)配置IP
第一部分是IP核的输出数据位宽以及IP核的存储容量
第二部分是存储单元类型,默认即可
第三部分是选择时钟模式,单时钟或者双时钟,我们这里选择的是单时钟。
随后点击next。
接着
选择输出端口Q是否寄存。
时钟使能信号,通常默认不勾选。
选择是否创建已补复位信号“acir”和读使能信号“rden”。这里两项都不勾选。
这一步是将生成的MIF文件添加进去。本次采用matlab生成一个FPGA所需要的正弦波MIF文件,sin_wave_8x256.mif会生成在你的资源管理器中,把他添加到你的FPGA工程文件下
MIF文件代码如下,
clc, clear, close all
F1=1; %信号频率
Fs=10^2; %采样频率
P1=0; %信号初始相位
N=10^2; %采样点数
t=[0:1/Fs:(N-1)/Fs]; %采样时刻
ADC=2^7 - 1; %直流分量
A=2^7; %信号幅度
%生成正弦信号
s=A*sin(2*pi*F1*t + pi*P1/180) + ADC;
plot(s); %绘制图形
%创建 coe 文件
fild = fopen('sin_wave_100x8.coe','wt');
%写入 coe 文件头
%固定写法,表示写入的数据是 10 进制表示
fprintf(fild, '%s\n','memory_initialization_radix=10;');
%固定写法,下面开始写入数据
fprintf(fild, '%s\n\n','memory_initialization_vector =');
for i = 1:N
s2(i) = round(s(i)); %对小数四舍五入以取整
if s2(i) <0 %负 1 强制置零
s2(i) = 0
end
fprintf(fild, '%d',s2(i)); %数据写入
if i==N
fprintf(fild, '%s\n',';'); %最后一个数据用;
else
fprintf(fild,',\n'); % 其他数据用,
end
end
fclose(fild); % 写完了,关闭文件
复卷机: 04-09 16:48:55
% 方波信号波形采集参考代码(square_wave.m):
F1 = 1; %信号频率
Fs = 10^2; %采样频率
P1 = 0; %信号初始相位
N = 10^2; %采样点数
t = [0:1/Fs:(N-1)/Fs]; %采样时刻
ADC = 2^7 - 1; %直流分量
A = 2^7; %信号幅度
%生成方波信号
s = A*square(2*pi*F1*t + pi*P1/180) + ADC;
plot(s); %绘制图形
%创建 coe 文件
fild = fopen('squ_wave_100x8.coe','wt');
%写入 coe 文件头
%固定写法,表示写入的数据是 10 进制表示
fprintf(fild, '%s\n','memory_initialization_radix=10;');
%固定写法,下面开始写入数据
fprintf(fild, '%s\n\n','memory_initialization_vector =');
for i = 1:N
s2(i) = round(s(i)); %对小数四舍五入以取整
if s2(i) <0 %负 1 强制置零
s2(i) = 0
end
fprintf(fild, '%d',s2(i)); %数据写入
if i==N
fprintf(fild, '%s\n',';'); %最后一个数据用分号
else
fprintf(fild,',\n'); % 其他数据用 ,
end
end
fclose(fild); % 写完了,关闭文件
MATLAB生成的正弦信号波形图如下,
2、代码
(1)单端口ROM
顶层模块rom
module rom
(
input wire sys_clk ,
input wire sys_rst_n,
output wire [7:0] rom_data
);
wire [7:0] rom_addr;
rom_ctrl rom_ctrl_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rom_addr (rom_addr)
);
rom_sin rom_sin_inst (
.address ( rom_addr ),
.clock ( sys_clk ),
.q ( rom_data )
);
endmodule
rom_Ctrl模块代码:
module rom_ctrl
(
input wire sys_clk ,
input wire sys_rst_n ,
output reg [7:0] rom_addr
);
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rom_addr <= 8'd0;
else
rom_addr <= rom_addr + 1'b1;
endmodule
测试代码,
`timescale 1ns/1ns
module tb_rom();
reg sys_clk;
reg sys_rst_n;
wire rom_data;
initial
begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
#30
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
rom rom_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rom_data (rom_data)
);
endmodule
结果如下
双端口ROM
在原来的基础,采用双端口ROM输出两路信号
MIF文件代码
clc; %清除命令行命令
clear all; %清除工作区变量,释放内存空间
F1=1; %信号频率
Fs=2^8; %采样频率
P1=0; %信号初始相位
N=2^8; %采样点数
t=[0:1/Fs:(N-1)/Fs]; %采样时刻
ADC=2^7 - 1; %直流分量
A=2^7; %信号幅度
s1=A*sin(2*pi*F1*t + pi*P1/180) + ADC; %正弦波信号
s2=A*square(2*pi*F1*t + pi*P1/180) + ADC; %方波信号
%创建mif文件
fild = fopen('wave_512x8.mif','wt');
%写入mif文件头
fprintf(fild, '%s\n','WIDTH=8;'); %位宽
fprintf(fild, '%s\n\n','DEPTH=512;'); %深度
fprintf(fild, '%s\n','ADDRESS_RADIX=UNS;'); %地址格式
fprintf(fild, '%s\n\n','DATA_RADIX=UNS;'); %数据格式
fprintf(fild, '%s\t','CONTENT'); %地址
fprintf(fild, '%s\n','BEGIN'); %开始
for j = 1:2
for i = 1:N
if j == 1 %打印正弦信号数据
s0(i) = round(s1(i)); %对小数四舍五入以取整
fprintf(fild, '\t%g\t',i-1); %地址编码
end
if j == 2 %打印方波信号数据
s0(i) = round(s2(i)); %对小数四舍五入以取整
fprintf(fild, '\t%g\t',i-1+N); %地址编码
end
if s0(i) <0 %负1强制置零
s0(i) = 0
end
fprintf(fild, '%s\t',':'); %冒号
fprintf(fild, '%d',s0(i)); %数据写入
fprintf(fild, '%s\n',';'); %分号,换行
end
end
fprintf(fild, '%s\n','END;'); %结束
fclose(fild);
顶层模块rom
module rom
(
input wire sys_clk ,
input wire sys_rst_n ,
output wire [7:0] rom_data ,
output wire [7:0] sin_d ,
output wire [7:0] squ_d
);
wire [7:0] rom_addr;
wire [8:0] rom_sin_d;
wire [8:0] rom_squ_d;
rom_ctrl rom_ctrl_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rom_addr (rom_addr),
.rom_sin_d (rom_sin_d),
.rom_squ_d (rom_squ_d)
);
rom_double rom_double_inst ( //双端口ROM
.address_a ( rom_sin_d ),
.address_b ( rom_squ_d ),
.clock ( sys_clk ),
.q_a ( sin_d ),
.q_b ( squ_d )
);
rom_sin rom_sin_inst (
.address ( rom_addr ),
.clock ( sys_clk ),
.q ( rom_data )
);
endmodule
rom_ctro模块:
module rom_ctrl
(
input wire sys_clk ,
input wire sys_rst_n ,
output reg [7:0] rom_addr ,
output reg [8:0] rom_sin_d ,
output reg [8:0] rom_squ_d
);
localparam SQU_Z = 9'd256;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rom_addr <= 8'd0;
else
rom_addr <= rom_addr + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rom_sin_d <= 9'd0;
else if(rom_sin_d == 9'd255)
rom_sin_d <= 9'd0;
else
rom_sin_d <= rom_addr + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rom_squ_d <= SQU_Z;
else
rom_squ_d <= rom_addr + SQU_Z;
endmodule
测试代码
`timescale 1ns/1ns
module tb_rom();
reg sys_clk;
reg sys_rst_n;
wire rom_data;
wire rom_sin_d;
wire rom_squ_d;
initial
begin
sys_clk = 1'b0;
sys_rst_n <= 1'b0;
#30
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
rom rom_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.rom_data (rom_data),
.sin_d (rom_sin_d),
.squ_d (rom_squ_d)
);
endmodule
结果如下:
(二)RAM的设计步骤
1、调用IP
(1)创建文件
我们单独创建一个RAM文件来熟悉RAM调用IP,我们在Quartus中创建一个新项目命名为RAM。
(2)调用IP
在左侧的IP Catalog搜索栏搜索“RAM”,然后选择“RAM:1-PORT”。
接下来在最开始我们创建的文件夹中添加一个名为IP的文件夹,随后我们将把IP文件保存在其中。
(3)配置IP
第一部分是IP核的输出数据位宽
第二部分是IP核的存储容量
第三部分是存储单元类型,默认即可
第四部分是选择时钟模式,单时钟或者双时钟,我们这里选择的是单时钟。
随后点击next进入下一步配置。
这一步包括以下三个部分
第一部分是选择输出端口Q是否寄存。
第二部分是时钟使能信号,通常默认不勾选。
第三部分是选择是否创建已补复位信号“acir”和读使能信号“rden”。这里两项都勾选。
随后点击next进入下一步配置。
这一步是配置某个地址写入数据的同时读取数据,通常默认“New data”即可,随后点击next进入下一步配置。
最后一步是将inst文件勾选上点击finish完成配置,随后点击Yes。
.v代码
module ram_ip(
input clk ,
input rst_n ,
input [5:0] address ,
input [7:0] data ,
input redn ,
input wren ,
output [7:0] q
);
ram ram_inst (
.aclr ( ~rst_n ),
.address ( address ),
.clock ( clk ),
.data ( data ),
.rden ( rden ),
.wren ( wren ),
.q ( q )
);
endmodule
测试代码
`timescale 1ns/1ns
module tb_ram();
reg tb_clk ;
reg tb_rst_n ;
reg [5:0] address ;
reg [7:0] data ;
reg rden ;
reg wren ;
wire [7:0] q ;
parameter CYCLE = 20;
ram_ip ram_ip_inst(
/*input */.clk ( tb_clk ) ,
/*input */.rst_n ( tb_rst_n ) ,
/*input [5:0] */.address ( address ) ,
/*input [7:0] */.data ( data ) ,
/*input */.redn ( rden ) ,
/*input */.wren ( wren ) ,
/*output [7:0] */.q ( q )
);
always #(CYCLE/2) tb_clk = ~tb_clk;
integer i;
initial begin
tb_clk = 1'b1;
tb_rst_n = 1'b1;
#(CYCLE*2);
tb_rst_n = 1'b0;
address = 0;//复位赋初值
data = 0;
rden = 0;
wren = 0;
#(CYCLE*10);
tb_rst_n = 1'b1;
#(100*CYCLE);
for (i=0;i<64 ;i=i+1 ) begin //写入数据
address = i;
data = i+1;
wren = 1'b1;
#CYCLE;
end
wren = 0;
#(CYCLE*10);
for (i=10;i<32 ;i=i+1 ) begin //读取数据
address = i;
rden = 1'b1;
#CYCLE;
end
rden = 0;
$stop;
end
endmodule
结果如下
(三)FIFO先进先出存储器
FIFO(先进先出存储器) 是一种特殊类型的缓冲存储器,其数据存取遵循先进先出(First-In-First-Out)原则,即最先写入的数据会被最先读出。它在数字系统中广泛应用于数据缓冲、时钟域隔离、流量控制等场景,尤其在FPGA设计中是解决多模块间数据速率不匹配问题的核心组件。
FIFO的核心概念
基本结构
存储单元:通常由寄存器链、Block RAM或分布式RAM实现。
写指针(Write Pointer):指示下一个数据写入的位置。
读指针(Read Pointer):指示下一个数据读取的位置。
状态信号:
full
(满标志):禁止继续写入。empty
(空标志):禁止继续读取。almost_full
/almost_empty
:阈值预警信号。
工作原理
写入操作:数据按顺序存入存储单元,写指针递增。
读取操作:数据按存入顺序被取出,读指针递增。
指针循环:当指针到达存储空间末尾时,自动回到起始位置(环形缓冲)。
FIFO的设计关键参数
深度(Depth)
决定FIFO的容量,需根据数据生产与消费的最大速率差计算。
公式:
最小深度=突发数据量×(写速率−读速率)写速率最小深度=写速率突发数据量×(写速率−读速率)
宽度(Width)
数据位宽(如8位、16位、32位),需与上下游模块匹配。
满/空判断逻辑
计数器法:通过记录数据数量判断状态,资源占用较多。
指针比较法:利用读写指针的位置关系判断,资源更高效。
创建文件步骤如上RAM,ROM。
.v代码
module fifo_test(
input wire wr_clk ,//写时钟
input wire rd_clk ,//读时钟
input wire rst_n ,
input wire [7:0] wr_din ,//写入fifo数据
input wire wr_en ,//写使能
input wire rd_en ,//读使能
output reg [7:0] rd_dout ,//读出的数据
output reg rd_out_vld //读有效信号
);
//------------<参数说明>--------------------------------------------
wire [7:0] wr_data ;
wire [7:0] q ;
wire wr_req ;//写请求
wire rd_req ;//读请求
wire wr_empty ;//写空
wire wr_full ;//写满
wire rd_empty ;//读空
wire rd_full ;//读满
wire [7:0] wr_usedw ;//在写时钟域下,FIFO 中剩余的数据量;
wire [7:0] rd_usedw ;//在读时钟域下,FIFO 中剩余的数据量。
fifo fifo_inst (
.data ( wr_data ),
.rdclk ( rd_clk ),
.rdreq ( rd_req ),
.wrclk ( wr_clk ),
.wrreq ( wr_req ),
.q ( q ),
.rdempty ( rd_empty ),
.rdfull ( rd_full ),
.rdusedw ( rd_usedw ),
.wrempty ( wr_empty ),
.wrfull ( wr_full ),
.wrusedw ( wr_usedw )
);
assign wr_data = wr_din;
assign wr_req = (wr_full == 1'b0)?wr_en:1'b0;
assign rd_req = (rd_empty == 1'b0)?rd_en:1'b0;
always @(posedge rd_clk or negedge rst_n)begin
if(!rst_n)begin
rd_dout <= 0;
end
else begin
rd_dout <= q;
end
end
always @(posedge rd_clk or negedge rst_n)begin
if(!rst_n)begin
rd_out_vld <= 1'b0;
end
else begin
rd_out_vld <= rd_req;
end
end
endmodule
测试代码:
`timescale 1 ns/1 ns
module tb_fifo();
//时钟和复位
reg wr_clk;
reg rd_clk;
reg rst_n ;
//输入信号
reg [7:0] wr_din;
reg wr_en ;
reg rd_en ;
//输出信号
wire rd_out_vld;
wire [7:0] rd_dout;
parameter WR_CYCLE = 20;//写时钟周期,单位为 ns,
parameter RD_CYCLE = 30;//读时钟周期,单位为 ns,
parameter RST_TIME = 3 ;//复位时间,此时表示复位 3 个时钟周期的时间。
//待测试的模块例化
fifo_test fifo_test_inst(
/*input wire */.wr_clk ( wr_clk ) ,//写时钟
/*input wire */.rd_clk ( rd_clk ) ,//读时钟
/*input wire */.rst_n ( rst_n ) ,
/*input wire [7:0] */.wr_din ( wr_din ) ,//写入fifo数据
/*input wire */.wr_en ( wr_en ) ,//写使能
/*input wire */.rd_en ( rd_en ) ,//读使能
/*output reg [7:0] */.rd_dout ( rd_dout ) ,//读出的数据
/*output reg */.rd_out_vld ( rd_out_vld ) //读有效信号
);
integer i = 0;
//生成本地时钟 50M
initial wr_clk = 0;
always #(WR_CYCLE/2) wr_clk=~wr_clk;
initial rd_clk = 0;
always #(RD_CYCLE/2) rd_clk=~rd_clk;
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(WR_CYCLE*RST_TIME);
rst_n = 1;
end
//输入信号赋值
initial begin
#1;
wr_din = 0;//赋初值
wr_en = 0;
#(10*WR_CYCLE);
for(i=0;i<500;i=i+1)begin//开始赋值
wr_din = {$random};
wr_en = {$random};
#(1*WR_CYCLE);
end
#(100*WR_CYCLE);
end
initial begin
#1;
rd_en = 0;//赋初值
#(12*RD_CYCLE);
for(i=0;i<500;i=i+1)begin//开始赋值
rd_en = {$random};
#(1*RD_CYCLE);
end
#(100*RD_CYCLE);
$stop;
end
endmodule
结果如下
四、总结
在近期的FPGA开发学习中,我重点攻克了RAM、ROM、FIFO等存储类IP核的设计与应用。这段学习历程不仅让我掌握了硬件描述语言与工具链的协同工作逻辑,更让我深刻体会到“硬件思维”与“软件思维”的差异。