You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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推断规则:

  1. 用32位地址直接索引8位宽的存储数组,且读写时直接操作连续4个地址,工具识别不出这是规范的RAM端口行为
  2. 异步读采用组合逻辑直接拼接四个字节的写法,偏离了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 10:34:50