作者:李西锐,文章来源: FPGA技术江湖微信公众号
ROM的英文全称为Read-Only Memory,即只读存储器。可以从任意地址上读取数据,但是不能写入。那么我们ROM中的数据,就需要我们提前存放进去,在IP核中,我们可以通过.coe文件进行数据存放,文件格式我们可以参考Xilinx官方标准。
数据文件的格式是固定的,我们在填充数据时,需要严格按照官方的格式进行书写。

在示例文件中,第一行规定了数据的格式,此处规定的是二进制,那么下面的数据,我们必须用二进制的形式。大家在写的时候,规定什么进制就用什么进制。数据与数据之间用逗号隔开,最后一个数据用分号结尾。
了解了数据文件格式之后,接下来我们提前准备一个数据文件,以便于后续我们调用IP核去使用。写数据文件的方法有很多,在此给大家介绍一种:MATLAB。
我们打开MATLAB之后,首先先选择一下工作路径,以便于我们去找到我们生成的文件以及保存我们的代码。如图:

打开如图所示的图标之后,选择好工程路径。
选择好之后,我们新建脚本文件,然后写入代码。
1 data = 0:1:255;
2 fid = fopen('sin_data.coe','w');
3 fprintf(fid,'memory_initialization_radix = 10;\n');
4 fprintf(fid,'memory_initialization_vector = \n');
5 for i = 1:1:1024
6 fprintf(fid,'%d',round(127*sin(2*pi/1024*i)+127));
7
8 if i == 1024
9 fprintf(fid,';');
10 else
11 fprintf(fid,',');
12 end
13
14 if mod(i,1) == 0
15 fprintf(fid,'\n');
16 end
17 end
18 fclose(fid);
写好代码,点击运行,即可生成我们想要的.coe文件。数据文件准备好之后,接下来我们就可以调用IP核了。
首先我们新建一个工程

在第二步选择路径

第三步直接跳过,第四步选择我们的芯片,芯片型号为XC7A35TFGG484-2。

选中型号之后,点击Next。
工程新建完成之后,开始新建文件。
首先我们先新建IP核,打开IP Catalog,在窗口搜索block


找到如图所示选项,然后双击打开。

我们在框选的选项中,选择Single Port ROM。这个选项中总共有五个选项。第一个为单端口RAM,第二个为伪双端口RAM,第三个为真双端口RAM,第四个为单端口ROM,第五个为真双端口ROM。我们此次使用的是单端口ROM。

图中框选出了四处,第一处需要我们修改一下数据的位宽以及深度,位宽我们默认使用8bit,深度为1024。因为我们在前面做了一个数据量为1024的.coe文件,所以这里深度改为1024。第二处为数据输出使能,在此我们选择为Always Enabled。使我们的输出使能一直有效。第三处为输出寄存器,输出会在时钟下输出,导致结果会慢一拍,在此处我们不需要这个选项,因此取消勾选。第四处为ROM复位的设置,如果有需要,可以进行勾选,此处,我没有使用复位信号,大家在使用时自行选择。

此处我们需要勾选中加载初始化文件的选项,然后点击Browse找到我们提前生成好的数据文件。选择好之后点击OK,生成IP核。

直接点击Generate。
IP核生成好之后,我们新建文件,写一下我们的地址控制模块。代码如下:
1 module addr_ctrl(
2
3 input wire clk,
4 input wire rst_n,
5 output reg [9:0] addr
6 );
7
8 always @ (posedge clk, negedge rst_n)
9 begin
10 if(rst_n == 1'b0)
11 addr <= 10'd0;
12 else if(addr == 10'd1023)
13 addr <= 10'd0;
14 else
15 addr <= addr + 1'b1;
16 end
17
18 endmodule
然后我们新建顶层文件。写好端口之后,我们将IP核与地址控制模块例化到顶层当中。

点击Next,选择Create File,新建顶层开始写代码。


打开IP核例化的头文件。

复制粘贴到顶层当中。地址控制模块也同样进行例化。顶层代码如下:
1 module rom(
2
3 input wire clk,
4 input wire rst_n,
5 output wire [7:0] q
6 );
7
8 wire [9:0] addr;
9
10 addr_ctrl addr_ctrl_inst(
11
12 .clk (clk),
13 .rst_n (rst_n),
14 .addr (addr)
15 );
16
17 blk_mem_gen_0 blk_mem_gen_0_inst (
18 .clka(clk), // input wire clka
19 .addra(addr), // input wire [9 : 0] addra
20 .douta(q) // output wire [7 : 0] douta
21 );
22
23 endmodule
代码写好之后,保存编译,没有错误,那么我们写一下仿真看一下仿真波形。

选中新建仿真文件,点击Next输入名字。

点击OK,开始写代码。仿真代码如下:
1 `timescale 1ns / 1ps
2
3 module rom_tb;
4
5 reg clk;
6 reg rst_n;
7 wire [7:0] q;
8
9 initial begin
10 clk = 0;
11 rst_n = 0;
12 #105;
13 rst_n = 1;
14 #10000;
15 $stop;
16 end
17
18 always #10 clk = ~clk;
19
20 rom rom_inst(
21
22 .clk (clk ),
23 .rst_n (rst_n ),
24 .q (q )
25 );
26
27 endmodule
代码写好之后,打开仿真。

波形窗口打开后,点击run all让波形继续运行

然后看到如图所示波形。

然后选中输出q,右键选择wavaform style,然后选择analog就可以看到我们的数字信号就变成了模拟信号。

但是此时波形只有一部分,我们再次点击run all ,然后点击break

就可以看到完整的正弦波。

我们的数据文件就是做的正弦波,仿真显示正确。