ROM (只读存储器) 模块:实现了一个简单的内存模型,其中可以存储数据并且可以读取数据。
1. 输入输出端口:
input wire clk, // 时钟信号
input wire rst, // 复位信号
input wire we_i, // 写使能信号
input wire[`MemAddrBus] addr_i, // 传递的地址信号
input wire[`MemBus] data_i, // 写入的数据
output reg[`MemBus] data_o // 读取的数据
clk
和rst
:时钟和复位信号,用于同步电路的操作。we_i
:写使能信号,用于决定是否向 ROM 中写入数据。如果we_i
为1
,表示要执行写操作。addr_i
:地址输入信号,用于指定要访问的 ROM 地址。data_i
:要写入 ROM 的数据。data_o
:ROM 的输出数据,用于读取指定地址的数据。
2. ROM 存储单元:
reg[`MemBus] _rom[0:`RomNum - 1];
_rom
:一个 ROM 存储器数组,它用于存储从地址0
到RomNum - 1
的数据。_rom
的大小是由RomNum
参数定义的。
3. 写操作:
always @ (posedge clk) begin
if (we_i == `WriteEnable) begin
_rom[addr_i[31:2]] <= data_i;
end
end
- 写操作:当
we_i
为WriteEnable
时(即允许写操作),ROM 会根据传入的地址 (addr_i
) 和数据 (data_i
) 将数据写入到_rom
数组中。 addr_i[31:2]
:地址信号的高 30 位被用作 ROM 数组的索引。由于每个数据单元是 32 位宽(即 4 字节),addr_i[31:2]
去除了最低的两位地址(即对齐到 4 字节的边界),因此地址从0
到RomNum - 1
的数据可以有效地映射到 ROM 数组中。
理解 ROM 地址映射时,我们需要理解 字节对齐(byte alignment)的概念,尤其是针对像 ROM 这样的存储器。
为什么我们要使用 addr_i[31:2]
来做地址映射。
问题背景:
_rom[addr_i[31:2]] <= data_i;
为什么低地址是四字节边界?
- ROM 中每个数据单元大小是 32 位,即每个数据单元占 4 字节。
- 在计算机中,内存的 字节对齐 是一种优化方式,要求数据按照其大小对齐到特定的边界。在这种情况下,32 位数据(4 字节)通常对齐到 4 字节边界。换句话说,每次读写操作都需要访问 4 字节对齐的地址。
解释地址映射的步骤:
-
addr_i[31:2]
:在 Verilog 中,addr_i
是一个 32 位地址,而每个数据单元是 4 字节(32 位)。因为addr_i
的最低两位(addr_i[1:0]
)对应的是 字节偏移,对于 4 字节对齐的地址,最低的两位一定为00
,所以我们只需要关注 高 30 位(addr_i[31:2]
)来索引 ROM 中的数据。- 低两位的作用:例如,地址
0x00000000
对应ROM[0]
,而地址0x00000001
、0x00000002
、0x00000003
都不能单独访问 32 位数据单元,因为它们不满足 4 字节对齐的要求。通过addr_i[31:2]
,我们实际上在访问的是按 4 字节边界对齐的地址。
- 低两位的作用:例如,地址
-
为什么要去掉低两位?:由于每个数据单元是 4 字节,所以我们不需要关心地址的低两位。通过将地址的低两位丢弃,我们确保访问的是 4 字节对齐的地址。
- 例如,地址
0x00000000
映射到 ROM 的索引 0(_rom[0]
)。 - 地址
0x00000004
映射到 ROM 的索引 1(_rom[1]
)。 - 地址
0x00000008
映射到 ROM 的索引 2(_rom[2]
)。 - 依此类推……
- 例如,地址
-
ROM 数组的索引:所以,实际上存储器数组
_rom
以 4 字节为单位来存储数据。当访问地址时,我们需要将 32 位的地址addr_i
按 4 字节对齐来映射到 ROM 中的单元,这就是通过addr_i[31:2]
来实现的。
更清楚的例子:
假设 RomNum
定义为 256,那么地址范围将从 0x00000000
到 0x000003FF
(即 256 个 32 位数据单元)。每个数据单元占 4 字节,所以我们需要去除地址的低两位,确保访问的是每 4 字节一个单元的结构。(0x表示16进制。)
例如:
- 地址
0x00000000
映射到_rom[0]
。 - 地址
0x00000004
映射到_rom[1]
。 - 地址
0x00000008
映射到_rom[2]
。 - …
- 地址
0x000003FF
映射到_rom[255]
。
总结:
- 由于每个数据单元是 32 位(4 字节),为了访问对齐的内存块,地址的最低两位(
addr_i[1:0]
)必须为 00。 - 因此,通过
addr_i[31:2]
来计算 ROM 中的数据单元索引,去掉最低的两位地址。 - 这样做确保了访问的内存是 4 字节对齐 的。
4. 读操作:
always @ (*) begin
if (rst == `RstEnable) begin
data_o = `ZeroWord;
end else begin
data_o = _rom[addr_i[31:2]];
end
end
- 读操作:该
always
块是一个组合逻辑块,它在任何信号变化时都会被触发。 - 如果 复位信号
rst
为有效(RstEnable
),则输出的数据data_o
被设置为ZeroWord
,即0
。 - 如果没有复位,则根据传入的地址
addr_i
,从_rom
数组中读取对应位置的数据,并将其输出到data_o
。
可能的修改/扩展:
- ROM 的初始化:当前 ROM 是空的,如果需要在仿真或硬件中加载预设数据,可以在初始化阶段填充
_rom
数组(比如通过一个初始化文件或者将数据直接嵌入到代码中)。 - 大小限制:目前 ROM 数组的大小是由
RomNum
定义的,可以根据需要调整其大小。如果 ROM 的数据量较大,还需要考虑是否需要在硬件设计中支持更多的存储单元。