记得刚上高中那会,数学课本的前几章说的就是函数,函数的本质是什么?函数的本质是映射,即定义域和值域之间的对应关系,X和Y之间的对应关系……
对于FPGA来说,相对简单的加减乘除在特定系列的FPGA中可以直接使用“+”、“-”、“*”、“/”这些符号进行计算,如果遇到复杂一点的函数,FPGA中这些基本的运算满足不了,比如Y=ln(x),该怎么办?
函数的关系既然是映射,如果先通过其他的工具将定义域和值域的数据先列出来,比如EXCEL表,只要确定定义域中的某个值,就一定能找到对应的函数值。
**定义域和值域的关系怎么这么像FPGA的存储单元呢!**将定义域等同于地址,将值域等同于地址中的存储内容,那么复杂函数的问题不就解决了?
对喽,就是FPGA中ROM这个工具,只要给出相应的地址,就得到相应的函数值。
操作起来也就简单了,将Y=F(X)的值域和定义域在EXCEL表对应起来,先来个简单的例子,Y=X2。
1.确定定义域,利用EXCEL等工具求出值域
定义X的范围为0-255,正好一个字节8位,放置在第一列,再用EXCEL的“=POWER(number,power)”函数求出0-255的平方值Y为0-65025,列在第二列,
同时将第二列利用“DEC2HEX”等进制转换函数进行转换,放置第三列。
2.创建COE文件
第(1)步确定的第三列数据已进行进制转换,比如本次使用的是16进制,那么新建一个txt文件,第一行写上“MEMORY_INITIALIZATION_RADIX=16”,表示数据内容为16进制;第二行写上“MEMORY_INITIALIZATION_VECTOR=”;第三行开始将EXECL表格的第三列数据粘贴到txt文件中,关闭txt文件,修改文件属性后缀为“coe”.
3.创建ROM
这个你肯定会的,对吧?!
4,上代码
模块代码如下:
module ROM_rd(
input clk,
input reset,
input [7:0] adress_rd,
output [15:0] data_rd
);
reg [7:0] adress_rd_d1,adress_rd_d2;
wire [15:0] data_rd_reg;
assign data_rd = data_rd_reg;
always @(posedge clk or negedge reset)
begin
if (!reset)begin
adress_rd_d1<=0;
adress_rd_d2<=0;
end
else begin
adress_rd_d1<=adress_rd;
adress_rd_d2<=adress_rd_d1;
end
end
ROM_X2 u1 (
.clka(clk), // input clka
.addra(adress_rd_d2), // input [7 : 0] addra
.douta(data_rd_reg) // output [15 : 0] douta
);
endmodule
测试代码如下:
module tb_ROM_rd;
// Inputs
reg clk;
reg reset;
reg [7:0] adress_rd;
// Outputs
wire [15:0] data_rd;
// Instantiate the Unit Under Test (UUT)
ROM_rd uut (
.clk(clk),
.reset(reset),
.adress_rd(adress_rd),
.data_rd(data_rd)
);
initial begin
// Initialize Inputs
clk = 0;
reset = 0;
adress_rd = 0;
// Wait 100 ns for global reset to finish
#100
reset = 1;
end
// Add stimulus here
always #10 clk = ~clk;
always @(posedge clk or negedge reset)
begin
adress_rd <= adress_rd + 1;
end
endmodule
仿真结果如下:
从图中可以看出,地址和读出的数据相差一个时钟周期。
5.几点思考延伸
(1)文章一开始说的Y=ln(X)中,这样的函数一般有小数,而FPGA不擅长处理小数,怎么办?
(2)对于含有负数的X、Y怎么处理?