关于FPGA的memory数据reg[15:0] a[2047:0]综合生成lut还是m9k的问题
使用FPGA来综合较大深度的(>1000)ram时需要特别注意其行为描述的语法,因为一旦使用不当,就会造成消耗资源严重超标,从而不满足设计要求。
首先,FPGA内部的ram主要是两种,一种是分布式ram,即lut和周围的DFF,另一种是专用集成块储存器(vivado叫block ram, quartus ii叫 M9K,M144K等)。
分布式ram的常见用法就是用reg定义的小量寄存器数据如 reg [7:0] a(这里仅仅讨论边沿赋值情况,不考虑电平方式的组合逻辑);集成块储存器一般就是IP管理器里面分配的RAM、ROM。
这两者在行为上最大的区别就是允许的并行读写数。基于reg的lutram 只允许在一个always块内赋值,即只能在一个always内的‘ <= ’左边,但允许同时被多个(>=3)always块读取;基于M9K等专用资源的ram最多允许两套读写异步操作,也即是允许在两个并行的always块同时读,两个并行的always块同时写。
基本情况介绍清楚了,有一种情况是,我们可以通过reg数组直接定义一个m9k的集成块ram或者rom 例如 reg[15:0] a[2047:0], 这种深度大的数组,我们期望将其定义为m9k/block ram,而非是lutram,否则要消耗掉超过10k的lut4,那么我们应该注意什么才能让软件正常综合布线?
当然最不会出错的方式是IP管理器生成,这是万无一失的,但是这种方式引入的IP和平台有关,例如从quartus的IP换到vivado平台后是不支持的,即使是同在xilinx的器件,软件版本不同也可能会导致ram ip出现异常,通用性差。
所以,有必要了解 reg[15:0] a[2047:0] 这种方式定义的ram或者rom综合方式。那么是怎么综合的呢?
情形一:
reg signed [15:0] mem16_s3s7[2047:0] ; //所需ram
reg [14:0] cnt_s3s7; //地址
wire[3:0] pulse1_out;
always@(posedge clk50M)
if(state1==S3 || state1==S7)
cnt_s3s7 <= cnt_s3s7 +1'b1;
else
cnt_s3s7 <= 15'd0;
always@(posedge clk50M)
if((state1==S3 || state1==S7) && cnt_s3s7 <= 2047 ) begin
if(mem16_s3s7[cnt_s3s7[10:0]] >= 16'd50 )
pulse1_out[0] <= 1'b1;
else
pulse1_out[0] <= 1'b0;
if(mem16_s3s7[cnt_s3s7[10:0]] >= 16'd100 )
pulse1_out[1] <= 1'b1;
else
pulse1_out[1] <= 1'b0;
if(mem16_s3s7[cnt_s3s7[10:0]] >= 16'd150 )
pulse1_out[2] <= 1'b1;
else
pulse1_out[2] <= 1'b0;
//
// if(mem16_s3s7[cnt_s3s7[10:0]] >= 16'd150 )
// pulse1_out[3] <= 1'b1;
// else
// pulse1_out[3] <= 1'b0;
//
end
else
//do nothing,let pulse keep the last state to reduce edge loss
;
结果:
情形二:
reg signed [15:0] mem16_s3s7[2047:0] ; //所需ram
reg [14:0] cnt_s3s7; //地址
wire[3:0] pulse1_out;
always@(posedge clk50M)
if(state1==S3 || state1==S7)
cnt_s3s7 <= cnt_s3s7 +1'b1;
else
cnt_s3s7 <= 15'd0;
always@(posedge clk50M)
if((state1==S3 || state1==S7) && cnt_s3s7 <= 2047 ) begin
if(mem16_s3s7[cnt_s3s7[10:0]] >= 16'd50 )
pulse1_out[0] <= 1'b1;
else
pulse1_out[0] <= 1'b0;
if(mem16_s3s7[cnt_s3s7[10:0]] >= 16'd100 )
pulse1_out[1] <= 1'b1;
else
pulse1_out[1] <= 1'b0;
// if(mem16_s3s7[cnt_s3s7[10:0]] >= 16'd150 )
// pulse1_out[2] <= 1'b1;
// else
// pulse1_out[2] <= 1'b0;
//
// if(mem16_s3s7[cnt_s3s7[10:0]] >= 16'd150 )
// pulse1_out[3] <= 1'b1;
// else
// pulse1_out[3] <= 1'b0;
//
end
else
//do nothing,let pulse keep the last state to reduce edge loss
;
结果:
上述情形一中的代码生成的就是lutram,最终占用lut超过20000;而二则是集成块,占用的lut则极少。最大的区别在于一中使用了3个if else来读取 mem16_s3s7的数据,尽管每次都是访问同一个地址的值,也被编译器理解为同时3次ram访问;而情形二而则只有2个if else,最后则可以生成m9k。可以进一步验证,4个5个if else 时生成lutram,而1个if else时同2个一样,也是block ram。
所以关键是,描述一个深度大的reg数组时,其操作行为是多少个并行的读写操作,不超过2读2写就能生成block ram,超过就只能是lutram。
如果既要block ram又要同时有超过2次的读取呢?那就得用寄存器做缓冲,打一拍,然后对寄存器进行同时多次的读操作:
reg [14:0] cnt_s3s7;
reg signed [15:0] pulsevalue;
wire[3:0] pulse1_out;
always@(posedge clk50M)
if(state1==S3 || state1==S7)
cnt_s3s7 <= cnt_s3s7 +1'b1;
else
cnt_s3s7 <= 15'd0;
always@(posedge clk50M)
if((state1==S3 || state1==S7) && cnt_s3s7 <= 2047 ) begin
pulsevalue <= mem16_s3s7[ cnt_s3s7[10:0] ];
if(pulsevalue >= lev0 )
pulse1_out[0] <= 1'b1;
else
pulse1_out[0] <= 1'b0;
if(pulsevalue >= lev1 )
pulse1_out[1] <= 1'b1;
else
pulse1_out[1] <= 1'b0;
if(pulsevalue >= lev2 )
pulse1_out[2] <= 1'b1;
else
pulse1_out[2] <= 1'b0;
if(pulsevalue >= lev3 )
pulse1_out[3] <= 1'b1;
else
pulse1_out[3] <= 1'b0;
if(pulsevalue >= lev4 )
pulse1_out[4] <= 1'b1;
else
pulse1_out[4] <= 1'b0;
end
else
//do nothing,let pulse keep the last state to reduce edge loss
;
如果要同时3次写操作呢?对不起做不到,block ram允许两次,lutram只允许一次。