在寻找APD最合适的偏压的过程中,一般会用到厂商提供一条曲线,横坐标是温度的变化,纵坐标表示击穿偏压的变化,但每个产品真正的击穿偏压是有差异的。 为了能够快速的找到当前温度下真实的击穿偏压,我们可以这样做,我先根据温度,得到厂商提供的击穿偏压。 然后再用厂商提供的击穿偏压,减去几伏。 我们以此作为寻找真实击穿偏压的起点。以一个固定的步长提升APD的偏压,直到满足我们设定的击穿判断条件。 但是呢,在整个这个流程中,我们需要解决一个问题,就如何在FPGA程序中实时的去获取当前温度下对应的击穿偏压。 如果是dsp或者stm32等嵌入式平台,可以直接把温度-击穿偏压数据做成一个数据表存到一个数组,然后直接查表。 但是在FPGA中,我们不能用数组了,但可以选择用ROM IP核 去实现。 在FPGA中,一些固定的初始化参数也可以用ROM来保存。 本文记录ROM的使用方法。
1 ROM是什么
ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除,且资料不会因为电源关闭而消失。而事实上在 FPGA 中通过 IP 核生成的 ROM 或 RAM 调用的都是 FPGA 内部的 RAM 资源,掉电内容都会丢失(这也很容易解释,FPGA 芯片内部本来就没有掉电非易失存储器单元)。用 IP 核生成的 ROM 模块只是提前添加了数据文件(.coe 格式),在 FPGA 运行时通过数据文件给 ROM 模块初始化,才使得 ROM 模块像个“真正”的掉电非易失存储器;也正是这个原因,ROM 模块的内容必须提前在数据文件中写死,无法在电路中修改。
简单说就是FPGA的ROM你随便搞,不用担心一次写死后,后面再也不能改了。
2 .coe文件生成
ROM 作为只读存储器,在进行 IP 核设置时需要指定初始化文件,即写入存储器中的 数据,数据要以规定的格式才能正确写入 ROM,这种格式就是 coe 文件。coe 是Vivado 规定的一种文件格式,下图为文件格式示意图
下面提供一个.coe文件的 MatLAB生成代码,一键即可生成你想要的.coe
clear;clear all;clc;
FileName=['mycoe','.coe'];
fid = fopen(FileName,'w');
fprintf(fid,'memory_initialization_radix = 16;\n');
fprintf(fid,'memory_initialization_vector =\n');
% 位宽
Width = 16;
% 深度
Depth = 20;
Hexlen = Width / 4;
for i=1:Depth
data = i;
% 根据位宽补0
HexCode = dec2hex(data);
HexSize = size(HexCode,2);
ZeroCode='';
for j=1:(Hexlen-HexSize)
ZeroCode=['0',ZeroCode];
end
str0 = [ZeroCode,HexCode];
% 判断是否为最后一行
if i < Depth
Hexstr = [str0,','];
fprintf(fid,'%s\n',Hexstr);
elseif i==Depth
Hexstr = [str0,';'];
fprintf(fid,'%s',Hexstr);
end
end
fclose(fid);
open(FileName);
3 ROM IP 配置
ROM分单口ROM和双口ROM,它们配置的区别仅仅只是Basic这一页Memory Type的选项的区别,其他的都是一样的。 所以本文仅以单口ROM的配置为例。
傻瓜式截图,从最原始的IP Catalog开始
取名这里,还是关注一下,比如我是用的单口rom,深度20,位宽16位。我的IP核命名就可以是 s_rom_20x16b。 比如我是用双口rom,深度256,位宽8位呢,那IP核命名则为 d_rom_256x8b。 这样的命名,能做到一目了然。
👆上面这个图有个值得关注的点: Totoal Port A Read Latency :1 Clock Cycle 。 意思是读的时候要输出要延迟一个时钟周期。👆
IP生成了,这个参数文件也被成功加载过来了。 | 瞅一眼是怎么例化的。 |
4 Modelsim仿真
ROM在读取数据的时候,要给ROM IP核输入你的ROM地址。 而ROM地址应该是从0开始的。 比如你Rom的深度是20(= 0x14),那你的ROM读地址就是 0x00 ~ 0x13(10进制是19)。那地址 0x00对应的是ROM存的第一个数据(本例中是0x0001)。 0x13对应的是ROM存的最后一个数据(本例中是0x0014)。
源码如下:
`timescale 1ns / 1ps
module lab_rom(
input wire clk,
input wire rst_n
);
//==================================================================
// Parameter define
//==================================================================
parameter MAX_ADDRA = 20 - 1;
//==================================================================
// Internal Signals
//==================================================================
reg [4:0] addra;
wire [15:0] douta;
//----------------------------- addra自增 -----------------------------
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
addra <= 'd0;
end
else if(addra == MAX_ADDRA)begin
addra <= 'd0;
end
else begin
addra <= addra + 1'b1;
end
end
//----------------------------- s_rom_20x16b例化 -----------------------------
s_rom_20x16b s_rom_20x16b_inst (
.clka(clk), // input wire clka
.addra(addra), // input wire [4 : 0] addra
.douta(douta) // output wire [15 : 0] douta
);
endmodule
约束文件
create_clock -period 20.000 [ get_ports clk ]
set_property PACKAGE_PIN N18 [ get_ports clk ]
set_property PACKAGE_PIN T12 [ get_ports rst_n ]
set_property IOSTANDARD LVCMOS33 [ get_ports clk ]
set_property IOSTANDARD LVCMOS33 [ get_ports rst_n ]
仿真代码
`timescale 1ns/1ps
module tb_lab_rom (); /* this is automatically generated */
// clock
reg clk;
initial begin
clk = 1'b0;
forever #(10) clk = ~clk;
end
// asynchronous reset
reg rst_n;
initial begin
rst_n <= 1'b0;
#10
rst_n <= 1'b1;
end
// (*NOTE*) replace reset, clock, others
parameter MAX_ADDRA = 20 - 1;
lab_rom #(.MAX_ADDRA(MAX_ADDRA)) inst_lab_rom (.clk(clk), .rst_n(rst_n));
endmodule
仿真结果
5 ila在线调试
本着再复习一下ila在线调试的原则,这里做一个在线调试的测试。
代码
`timescale 1ns / 1ps
module lab_rom(
input wire clk,
input wire rst_n
);
//==================================================================
// Parameter define
//==================================================================
parameter MAX_ADDRA = 20 - 1;
//==================================================================
// Internal Signals
//==================================================================
(* MARK_DEBUG="true" *) reg [4:0] addra; // 修改🔺
(* MARK_DEBUG="true" *) wire [15:0] douta; // 修改🔺
//----------------------------- addra自增 -----------------------------
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
addra <= 'd0;
end
else if(addra == MAX_ADDRA)begin
addra <= 'd0;
end
else begin
addra <= addra + 1'b1;
end
end
//----------------------------- s_rom_20x16b例化 -----------------------------
s_rom_20x16b s_rom_20x16b_inst (
.clka(clk), // input wire clka
.addra(addra), // input wire [4 : 0] addra
.douta(douta) // output wire [15 : 0] douta
);
endmodule
Run Synthesis - Open Synthesized Design 运行综合 - 打开已综合的设计
Set up Debug → Next 设置调试→下一步
添加要观测的信号并设置时钟域
由于项目比较简单,刚刚截图的瞬间,系统刚刚应该是自动Run synthesis了一遍,并且还Run完了。
编译完之后直接 Open Hareware Manager 打开硬件管理器 - 直接烧写程序,刷新设备
在线调试功能正常。
参考文献
Vivado 下 IP核 之ROM 读写_OliverH-yishuihan的博客-CSDN博客
在给rom IP核命名的时候,我发现FPGA的代码应该有一个统一遵循的规范,包括ip核 模块 变量 参数的命名规则、注释、缩进风格等都应该有一个规范, 后续应该会有一篇博客讲规范,不过目前初学阶段接触的FPGA相关内容还不够全面,因此这篇博客应该是在初学阶段的尾声部分。 另外,约束文件中关于时序部分的约束也是后续关注的一个重点。