在数字电路设计中,ASIC和FPGA是两种常见的实现方式。ASIC(专用集成电路)追求极致的面积和功耗优化,而FPGA(现场可编程门阵列)则更注重灵活性和开发效率。当我们需要将ASIC设计移植到FPGA平台时,存储器的处理是一个需要特别注意的环节。
我最近完成了一个将ASIC设计移植到FPGA的项目,其中ROM和RAM的改造是工作量最大的部分之一。通过这次实践,我总结出了一些实用的经验,特别是关于如何高效地将ASIC中的存储器结构适配到FPGA平台。
在ASIC设计中,ROM的实现通常非常"抠门"。为了最大限度地节省芯片面积,工程师们会采用多个小容量ROM拼接的方式,而不是直接使用一个大ROM。这种设计思路源于ASIC对面积的极致追求。
举个例子,如果你的代码编译后大小是381KB,ASIC设计可能会使用三个128KB的ROM来组成384KB的总容量,刚好满足需求。这种精确控制的做法在ASIC设计中非常常见。
verilog复制rom_0_domain u_rom0(
.hclk_main (hclk ),
.rom_0_cs (rom_0_cs ),
.rom_addr (rom_addr ),
.rom_rdata (rom_rdata )
);
rom_1_domain u_rom1(
.hclk_main (hclk ),
.rom_1_cs (rom_1_cs ),
.rom_addr (rom_addr ),
.rom_rdata (rom_rdata )
);
rom_2_domain u_rom2(
.hclk_main (hclk ),
.rom_2_cs (rom_2_cs ),
.rom_addr (rom_addr ),
.rom_rdata (rom_rdata )
);
ASIC设计中,多个ROM的协同工作需要精细的片选(CS)控制。通常,高位地址线会被用来生成各个ROM的片选信号:
verilog复制assign rom_0_cs=(rom_addr[x+n:x] ==2'b00) ? rom_cs :1'b0;
assign rom_1_cs=(rom_addr[x+n:x] ==2'b01) ? rom_cs :1'b0;
assign rom_2_cs=(rom_addr[x+n:x] ==2'b10) ? rom_cs :1'b0;
数据输出则通过多路选择器实现:
verilog复制assign rom_rdata[31:0]=rom_2_cs_latch?rom_2_rdata:rom_1_cs_latch?rom_1_rdata:rom_0_rdata;
ASIC设计中,ROM的时序控制也很关键。通常会在时钟边沿锁存片选信号,确保数据稳定:
verilog复制always @(negedge hrestn or posedge hclk)
if (!hrestn_main)
begin
rom_0_cs_latch <= 1'b0;
rom_1_cs_latch <= 1'b0;
rom_2_cs_latch <= 1'b0;
end
else
begin
rom_0_cs_latch <= rom_0_cs;
rom_1_cs_latch <= rom_1_cs;
rom_2_cs_latch <= rom_2_cs;
end
这种设计在ASIC中非常高效,但在移植到FPGA时就需要进行调整。
FPGA与ASIC最大的区别之一就是资源特性。FPGA中有丰富的Block RAM资源,而且这些资源通常是以较大的块(如36Kb)组织的。因此,在FPGA中,我们会把ASIC中的多个小ROM合并为一个大ROM。
这样做有几个好处:
在FPGA中,ROM的实现通常使用厂商提供的IP核。以Xilinx为例,ROM的实现可以这样写:
verilog复制ROM_AW_sram_bus_wrapper_x32 #( .AW(17) ) u_rom_0(
.Q(rom_0_rdata),
.ADR(rom_addr),
.ME(1),
.CLK(clk),
.LS(1'b0),
.WEN(1),
.D()
);
重要提示:在FPGA实现中,通常会将存储器使能信号(ME)直接拉高,不再使用片选信号。这是因为FPGA的Block RAM在使能信号为低时,输出会保持上一次的值,而不是像ASIC那样完全关闭。
由于ASIC中的ROM容量通常很紧凑,而FPGA中的Block RAM有固定的大小,因此地址位宽往往需要调整。例如:
这种调整需要在代码中明确体现,通常通过参数化设计来实现:
verilog复制parameter ROM_AW = 19; // FPGA中需要的地址位宽
相比ROM,RAM在ASIC到FPGA的移植通常更简单,主要是因为:
FPGA中RAM的实现也使用厂商提供的IP核。不同厂商的接口可能略有不同,但核心信号基本一致:
verilog复制RAM_IP u_ram(
.clk(clk),
.addr(ram_addr),
.din(ram_wdata),
.dout(ram_rdata),
.we(ram_wen), // 写使能,注意有效电平
.en(ram_cs) // 片选信号,通常可以直接拉高
);
有效电平:不同厂商的IP核可能对写使能(WE)的有效电平定义不同,有的是高有效,有的是低有效,必须仔细查阅文档。
时序特性:FPGA的Block RAM通常有固定的流水线级数,这与ASIC中的自定义RAM可能不同,需要调整设计中的时序预期。
初始化:ASIC中的RAM可能有特殊的初始化要求,而FPGA的RAM初始化方式可能不同,需要特别注意。
在移植过程中,存储器相关的问题往往比较隐蔽。以下是我总结的几个调试技巧:
地址映射检查:使用逻辑分析仪或嵌入式逻辑分析仪(如Xilinx的ILA)检查地址是否正确映射。
数据比对:在仿真阶段,建立ASIC和FPGA版本的黄金参考模型,进行逐周期比对。
时序分析:特别注意跨时钟域的情况,FPGA中的时钟网络与ASIC可能不同。
FPGA中的存储器性能可以通过以下方式优化:
合理使用流水线:FPGA的Block RAM通常支持输出寄存器,合理使用可以提高时序性能。
宽度转换:如果数据位宽不匹配,可以使用FPGA提供的宽度转换功能,而不是自己实现。
Bank选择:对于大容量存储器,合理分布在不同Bank可以改善布线拥塞。
问题: FPGA实现中存储器内容不正确
问题: 时序违例
问题: 功能仿真通过但硬件不正常
ASIC版本:
verilog复制// 多个小ROM拼接
rom_0_domain u_rom0(...);
rom_1_domain u_rom1(...);
rom_2_domain u_rom2(...);
// 复杂的选择逻辑
assign rom_rdata = rom_2_cs ? rom_2_rdata :
rom_1_cs ? rom_1_rdata : rom_0_rdata;
FPGA版本:
verilog复制// 单个大ROM
ROM_AW_sram_bus_wrapper_x32 #( .AW(19) ) u_rom(
.Q(rom_rdata),
.ADR(rom_addr),
.ME(1'b1), // 常使能
.CLK(clk),
.LS(1'b0),
.WEN(1'b1),
.D()
);
ASIC版本:
verilog复制custom_ram u_ram(
.clk(clk),
.addr(ram_addr),
.wdata(ram_wdata),
.rdata(ram_rdata),
.wen(ram_wen), // 低有效
.cen(ram_cs) // 复杂的片选逻辑
);
FPGA版本:
verilog复制RAM_IP u_ram(
.clk(clk),
.addr(ram_addr),
.din(ram_wdata),
.dout(ram_rdata),
.we(ram_wen), // 注意有效电平可能不同
.en(1'b1) // 通常常使能
);
单元测试:对每个存储器模块进行独立测试,验证基本读写功能。
集成测试:在整个系统中验证存储器的访问是否正确。
边界测试:特别测试地址边界情况,确保不会出现越界访问。
时序分析:使用静态时序分析工具验证存储器接口的时序是否满足要求。
带宽测试:测量存储器的实际访问带宽,确保满足系统需求。
功耗评估:评估存储器子系统在FPGA中的功耗特性。
压力测试:长时间运行测试,验证存储器的稳定性。
温度测试:在不同温度条件下验证存储器功能。
电源波动测试:验证在电源波动情况下存储器的可靠性。
在实际项目中,我发现FPGA实现通常比ASIC更宽容,但也不能掉以轻心。特别是在高速设计中,存储器的时序问题往往会成为系统稳定性的瓶颈。建议在移植完成后,至少预留两周时间进行全面的验证和调试。