ROM硬件实现双指针:如何通过case语句在单时钟周期读取两个值?
好问题!我来给你拆解一下怎么在单时钟周期内从case实现的ROM里读出两个值——核心是要让ROM支持并行访问,下面是几个实用的方案,你可以根据自己的场景来选:
方案一:复制ROM存储体(双端口ROM)
这是最直接的思路:做两个完全一样的ROM块,每个块对应一个读地址指针。同一个时钟周期里,两个指针分别访问各自的ROM,就能同时读出两个独立的值。
比如用Verilog实现的话,大概是这样:
module dual_port_rom #( parameter WIDTH = 8, parameter DEPTH = 256 )( input clk, input [7:0] addr1, addr2, output reg [WIDTH-1:0] data1, data2 ); // 定义两个完全相同的ROM存储数组 reg [WIDTH-1:0] rom_block1 [0:DEPTH-1]; reg [WIDTH-1:0] rom_block2 [0:DEPTH-1]; // 初始化ROM(假设用hex文件加载数据) initial begin $readmemh("rom_data.hex", rom_block1); $readmemh("rom_data.hex", rom_block2); end always @(posedge clk) begin data1 <= rom_block1[addr1]; data2 <= rom_block2[addr2]; end endmodule
优缺点:逻辑简单,时序容易收敛;但会占用双倍的存储资源,适合FPGA/ASIC资源充足的场景。
方案二:使用宽位宽ROM,合并相邻/关联地址的数值
如果你的两个读地址是固定关联的(比如总是相邻,或者差一个固定偏移),可以把ROM的位宽翻倍,每个存储单元存两个连续的值。比如原来每个单元存8位,现在改成16位,存addr和addr+1的数值。这样读的时候,只需要给一个基地址,读出宽位数据后拆分出两个目标值就行。
示例代码:
module wide_rom #( parameter WIDTH = 8, parameter DEPTH = 128 // 因为每个单元存两个值,深度减半 )( input clk, input [6:0] base_addr, output reg [WIDTH-1:0] data_low, data_high ); reg [2*WIDTH-1:0] rom_block [0:DEPTH-1]; initial begin // 注意数据文件里每一行是两个8位值拼接成的16位(高8位是addr+1,低8位是addr) $readmemh("wide_rom_data.hex", rom_block); end always @(posedge clk) begin {data_high, data_low} <= rom_block[base_addr]; end endmodule
优缺点:能节省一半的存储资源,但只适用于地址有固定关联的场景,如果两个地址是完全随机的,这个方法就不适用了。
方案三:利用FPGA原生双端口ROM资源
现在主流FPGA(比如Xilinx、Intel的)都内置了双端口存储块(比如BRAM),这些硬件本身就支持同一个时钟周期内两个独立地址的读操作。如果你是在FPGA上实现,这是最优解。
你可以直接例化厂商提供的双端口ROM IP,或者用综合工具能识别的语法来描述,比如:
module fpga_dual_port_rom #( parameter WIDTH = 8, parameter DEPTH = 256 )( input clk, input [7:0] addr1, addr2, output reg [WIDTH-1:0] data1, data2 ); reg [WIDTH-1:0] rom_block [0:DEPTH-1]; initial begin $readmemh("rom_data.hex", rom_block); end // 两个独立的读操作,综合工具会自动映射到双端口BRAM always @(posedge clk) begin data1 <= rom_block[addr1]; end always @(posedge clk) begin data2 <= rom_block[addr2]; end endmodule
优缺点:完全利用硬件原生资源,不浪费逻辑,时序表现最好;但要注意不同厂商的综合语法可能有细微差别,最好参考对应FPGA的综合手册。
避坑提醒:不要用双case组合逻辑实现
如果你想着直接写两个独立的case块来做双读,比如:
// 非常不推荐的组合逻辑ROM双读方式 module bad_dual_read_rom( input [7:0] addr1, addr2, output reg [7:0] data1, data2 ); always @(*) begin case(addr1) 8'h00: data1 = 8'h11; 8'h01: data1 = 8'h22; // ... 其他地址映射 default: data1 = 8'h00; endcase end always @(*) begin case(addr2) 8'h00: data2 = 8'h11; 8'h01: data2 = 8'h22; // ... 其他地址映射 default: data2 = 8'h00; endcase end endmodule
这种方式会生成大量的组合逻辑,资源占用极高,而且时序很难收敛,只适合极小容量的ROM(比如几个地址),绝对不要用于中等或大容量的场景。
内容的提问来源于stack exchange,提问作者Aren dg

