Quartus编译双端口RAM代码未推断FPGA存储资源问题求助
解决Quartus中双端口字节寻址RAM无法推断的问题
我写了一个字节可寻址的双端口Verilog存储器,要求异步读、同步写,但Quartus编译时存储位显示为0,被实现成逻辑单元而非推断出RAM,需要在不改变原有功能的前提下解决这个问题。原代码如下:
module dataMemoryTwoPorts( PAaddress , PBaddress , PAwriteData , PBwriteData , PAreadData , PBreadData, PAwriteEn , PBwriteEn , PAreadEn , PBreadEn , clk); // Inputs // clock input clk; // Enables allow reading from memory from port A and port B input PAreadEn , PBreadEn; // Enables allow writing on memory through port A and port B input PAwriteEn , PBwriteEn; // Address of location to be accessed by port A and port B input [31:0] PAaddress , PBaddress; // Data to be written through port A and port B input [31:0] PAwriteData , PBwriteData; // Outputs // Data to be read from port A and port B output [31:0] PAreadData , PBreadData; // Creating the memory vector reg [7:0] dataMemory [0:8191]; //Initialize memory initial begin $readmemh("dataMemory.txt", dataMemory); end // Read operation, read only if the address is aligned and the read port is activated assign PAreadData = ( PAreadEn ) ? { dataMemory[ PAaddress + 3 ] , dataMemory[ PAaddress + 2 ] , dataMemory[ PAaddress + 1] , dataMemory[PAaddress] } : 32'bx ; assign PBreadData = ( PBreadEn ) ? { dataMemory[ PBaddress + 3 ] , dataMemory[ PBaddress + 2 ] , dataMemory[ PBaddress + 1] , dataMemory[PBaddress] } : 32'bx ; // Write operation always @ ( posedge clk ) begin // for port A if ( PAwriteEn ) begin dataMemory [PAaddress ] <= PAwriteData[7 :0 ]; dataMemory [PAaddress+1] <= PAwriteData[15:8 ]; dataMemory [PAaddress+2] <= PAwriteData[23:16]; dataMemory [PAaddress+3] <= PAwriteData[31:24]; end // for port B if ( PBwriteEn ) begin dataMemory [PBaddress ] <= PBwriteData[7 :0 ]; dataMemory [PBaddress+1] <= PBwriteData[15:8 ]; dataMemory [PBaddress+2] <= PBwriteData[23:16]; dataMemory [PBaddress+3] <= PBwriteData[31:24]; end end endmodule
问题根源
Quartus没法推断出RAM,核心是代码的访问方式不符合工具的RAM推断规则:
- 用32位地址直接索引8位宽的存储数组,且读写时直接操作连续4个地址,工具识别不出这是规范的RAM端口行为
- 异步读采用组合逻辑直接拼接四个字节的写法,偏离了Quartus认可的RAM异步读模板
解决方案1:规范端口访问逻辑(推荐)
把32位地址截断为实际需要的13位(因为存储数组是8192个字节,2^13=8192),将连续4字节的读写转换为以32位为单位的RAM访问,再拆分/拼接字节,既符合RAM推断规则,又完全保留原功能。
修改后的代码:
module dataMemoryTwoPorts( PAaddress , PBaddress , PAwriteData , PBwriteData , PAreadData , PBreadData, PAwriteEn , PBwriteEn , PAreadEn , PBreadEn , clk); // Inputs input clk; input PAreadEn , PBreadEn; input PAwriteEn , PBwriteEn; input [31:0] PAaddress , PBaddress; input [31:0] PAwriteData , PBwriteData; // Outputs output [31:0] PAreadData , PBreadData; // 改为32位宽存储单元,总2048个(对应原8192字节) reg [31:0] dataMemory [0:2047]; reg [7:0] byte_mem [0:8191]; // 保持原初始化逻辑:先加载字节数组,再转存为32位单元 initial begin $readmemh("dataMemory.txt", byte_mem); for (int i=0; i<2048; i++) begin dataMemory[i] = {byte_mem[4*i+3], byte_mem[4*i+2], byte_mem[4*i+1], byte_mem[4*i]}; end end // 异步读:拆分地址为字地址和字节偏移,保持原字节寻址逻辑 wire [10:0] PA_word_addr = PAaddress[12:2]; wire [1:0] PA_byte_offset = PAaddress[1:0]; assign PAreadData = PAreadEn ? (PA_byte_offset == 2'b00 ? dataMemory[PA_word_addr] : PA_byte_offset == 2'b01 ? {dataMemory[PA_word_addr][7:0], dataMemory[PA_word_addr+1][31:8]} : PA_byte_offset == 2'b10 ? {dataMemory[PA_word_addr][15:0], dataMemory[PA_word_addr+1][31:16]} : {dataMemory[PA_word_addr][23:0], dataMemory[PA_word_addr+1][31:24]}) : 32'bx; wire [10:0] PB_word_addr = PBaddress[12:2]; wire [1:0] PB_byte_offset = PBaddress[1:0]; assign PBreadData = PBreadEn ? (PB_byte_offset == 2'b00 ? dataMemory[PB_word_addr] : PB_byte_offset == 2'b01 ? {dataMemory[PB_word_addr][7:0], dataMemory[PB_word_addr+1][31:8]} : PB_byte_offset == 2'b10 ? {dataMemory[PB_word_addr][15:0], dataMemory[PB_word_addr+1][31:16]} : {dataMemory[PB_word_addr][23:0], dataMemory[PB_word_addr+1][31:24]}) : 32'bx; // 同步写:根据字节偏移更新对应位置的字节 always @ (posedge clk) begin if (PAwriteEn) begin case(PA_byte_offset) 2'b00: dataMemory[PA_word_addr] <= {PAwriteData[31:8], PAwriteData[7:0]}; 2'b01: begin dataMemory[PA_word_addr] <= {dataMemory[PA_word_addr][31:16], PAwriteData[7:0], dataMemory[PA_word_addr][7:0]}; dataMemory[PA_word_addr+1] <= {PAwriteData[31:8], dataMemory[PA_word_addr+1][7:0]}; end 2'b10: begin dataMemory[PA_word_addr] <= {dataMemory[PA_word_addr][31:24], PAwriteData[15:0], dataMemory[PA_word_addr][7:0]}; dataMemory[PA_word_addr+1] <= {PAwriteData[31:16], dataMemory[PA_word_addr+1][15:0]}; end 2'b11: begin dataMemory[PA_word_addr] <= {PAwriteData[7:0], dataMemory[PA_word_addr][23:0]}; dataMemory[PA_word_addr+1] <= {dataMemory[PA_word_addr+1][31:8], PAwriteData[31:24]}; end endcase end if (PBwriteEn) begin case(PB_byte_offset) 2'b00: dataMemory[PB_word_addr] <= {PBwriteData[31:8], PBwriteData[7:0]}; 2'b01: begin dataMemory[PB_word_addr] <= {dataMemory[PB_word_addr][31:16], PBwriteData[7:0], dataMemory[PB_word_addr][7:0]}; dataMemory[PB_word_addr+1] <= {PBwriteData[31:8], dataMemory[PB_word_addr+1][7:0]}; end 2'b10: begin dataMemory[PB_word_addr] <= {dataMemory[PB_word_addr][31:24], PBwriteData[15:0], dataMemory[PB_word_addr][7:0]}; dataMemory[PB_word_addr+1] <= {PBwriteData[31:16], dataMemory[PB_word_addr+1][15:0]}; end 2'b11: begin dataMemory[PB_word_addr] <= {PBwriteData[7:0], dataMemory[PB_word_addr][23:0]}; dataMemory[PB_word_addr+1] <= {dataMemory[PB_word_addr+1][31:8], PBwriteData[31:24]}; end endcase end end endmodule
解决方案2:直接调用Quartus RAM原语
如果不想修改代码结构,直接用Altera的双端口RAM原语,配置成字节可寻址、异步读、同步写,把原逻辑映射到原语端口上,工具会直接使用硬件RAM资源。
示例(以altsyncram原语为例):
module dataMemoryTwoPorts( PAaddress , PBaddress , PAwriteData , PBwriteData , PAreadData , PBreadData, PAwriteEn , PBwriteEn , PAreadEn , PBreadEn , clk); input clk; input PAreadEn , PBreadEn; input PAwriteEn , PBwriteEn; input [31:0] PAaddress , PBaddress; input [31:0] PAwriteData , PBwriteData; output [31:0] PAreadData , PBreadData; // 截断地址到有效13位 wire [12:0] PA_addr = PAaddress[12:0]; wire [12:0] PB_addr = PBaddress[12:0]; // 实例化双端口字节RAM原语 altsyncram #( .operation_mode("DUAL_PORT"), .width_a(8), // 端口A位宽8位(字节) .widthad_a(13), // 端口A地址位宽13位 .width_b(8), // 端口B位宽8位 .widthad_b(13), // 端口B地址位宽13位 .numwords_a(8192), .numwords_b(8192), .outdata_reg_a("UNREGISTERED"), // 异步读,不寄存输出 .outdata_reg_b("UNREGISTERED"), .wrcontrol_wraddress_reg_a("CLOCK0"), // 同步写 .wrcontrol_wraddress_reg_b("CLOCK0"), .init_file("dataMemory.txt"), // 初始化文件 .init_file_layout("BYTE") // 按字节初始化 ) altsyncram_inst ( .clock0(clk), .wren_a(PAwriteEn), .address_a(PA_addr), .data_a(PAwriteData[7:0]), .q_a(PAreadData[7:0]), .wren_b(PBwriteEn), .address_b(PB_addr), .data_b(PBwriteData[7:0]), .q_b(PBreadData[7:0]), // 扩展端口处理32位读写的其他字节 .address_a1(PA_addr + 1), .data_a1(PAwriteData[15:8]), .q_a1(PAreadData[15:8]), .address_a2(PA_addr + 2), .data_a2(PAwriteData[23:16]), .q_a2(PAreadData[23:16]), .address_a3(PA_addr + 3), .data_a3(PAwriteData[31:24]), .q_a3(PAreadData[31:24]), .address_b1(PB_addr + 1), .data_b1(PBwriteData[15:8]), .q_b1(PBreadData[15:8]), .address_b2(PB_addr + 2), .data_b2(PBwriteData[23:16]), .q_b2(PBreadData[23:16]), .address_b3(PB_addr + 3), .data_b3(PBwriteData[31:24]), .q_b3(PBreadData[31:24]) ); // 拼接32位读数据,保留原使能逻辑 assign PAreadData = PAreadEn ? {altsyncram_inst.q_a3, altsyncram_inst.q_a2, altsyncram_inst.q_a1, altsyncram_inst.q_a} : 32'bx; assign PBreadData = PBreadEn ? {altsyncram_inst.q_b3, altsyncram_inst.q_b2, altsyncram_inst.q_b1, altsyncram_inst.q_b} : 32'bx; endmodule
注:原语的具体端口和参数需根据Quartus版本调整,建议参考官方文档配置。
关键注意事项
- 必须使用实际需要的地址位宽(这里是13位),32位地址会让工具无法识别有效地址范围,导致无法推断RAM
- Quartus对RAM推断要求每个端口的读写对应单一地址和数据位宽,连续多字节操作需拆分为标准端口访问后再组合
- 确保
dataMemory.txt格式正确,每行一个字节的十六进制值,数量不超过8192个
内容的提问来源于stack exchange,提问作者Giannis
相关产品推荐
相关产品推荐

