本文讨论了Verilog中最简单的RAM实现。
在继续进行代码解析之前,建议您学习Verilog的基本语法。
在这里您可以找到培训材料 。
内存
步骤1:声明具有相应输入/输出信号的模块
module ram ( input [word_size - 1:0] data, input [word_size - 1:0] addr, input wr, input clk, output response, output [word_size - 1:0] out ); parameter word_size = 32;
- 数据 -要写入的数据。
- addr -RAM中内存的地址。
- wr-状态(读/写)。
- clk-时钟周期系统。
- 响应 -RAM的就绪状态(1-如果RAM处理了读/写请求,否则为0-)。
- out-从RAM读取的数据。
此实现已集成到具有32位架构的Altera Max 10 FPGA中,因此数据和地址的大小(word_size)为32位。
步骤2:在模块内声明寄存器
用于存储数据的数组声明:
parameter size = 1<<32; reg [word_size-1:0] ram [size-1:0];
我们还需要存储以前的输入参数,以便在always块中跟踪它们的变化:
reg [word_size-1:0] data_reg; reg [word_size-1:0] addr_reg; reg wr_reg;
在always块中,最后两个寄存器用于在计算后更新输出信号:
reg [word_size-1:0] out_reg; reg response_reg;
我们初始化寄存器:
initial begin response_reg = 1; data_reg = 0; addr_reg = 0; wr_reg = 0; end
步骤3:实现模块的始终逻辑
always @(negedge clk) begin if ((data != data_reg) || (addr%size != addr_reg)|| (wr != wr_reg)) begin response_reg = 0; data_reg = data; addr_reg = addr%size; wr_reg = wr; end else begin if (response_reg == 0) begin if (wr) ram[addr] = data; else out_reg = ram[addr]; response_reg = 1; end end end
始终阻止是由negedje触发的,即 当时钟从1移到0时。这样做是为了使RAM与高速缓存正确同步。 否则,可能会出现以下情况:RAM没有时间将就绪状态从1重置为0,并且在下一个时钟,高速缓存会确定RAM已成功处理了其请求,这是根本错误的。
该块的Always算法的逻辑如下:如果数据已更新,则将就绪状态重置为0并写入/读取数据,如果写入/读取完成,我们会将就绪状态更新为1。
最后,添加以下代码部分:
assign out = out_reg; assign response = response_reg;
我们模块的输出信号类型为线。 更改此类信号的唯一方法是长期分配,始终在块中禁止使用。 因此,始终块使用寄存器,这些寄存器随后分配给输出信号。
直接映射缓存
直接映射缓存是最简单的缓存类型之一。 在此实现中,高速缓存由n个元素组成,并且RAM被n有条件地划分为块,然后高速缓存中的第i个元素对应于RAM中满足条件i = k%n的所有第k个元素。
下图显示了大小为4的缓存和大小为16的RAM。

每个高速缓存元素包含以下信息:
- 有效性位 -缓存中的信息是否相关。
- 标签是此元素所在的RAM中的块号。
- 数据 -我们写入/读取的信息。
当请求读取时,高速缓存将输入地址分为两部分-标记和索引。 索引的大小是log(n),其中n是缓存的大小。
步骤1:声明具有相应输入/输出信号的模块
module direct_mapping_cache ( input [word_size-1:0] data, input [word_size-1:0] addr, input wr, input clk, output response, output is_missrate, output [word_size-1:0] out ); parameter word_size = 32;
缓存模块的声明与RAM相同,但新的输出信号is_missrate除外。 此输出存储有关上次读取请求是否未命中率的信息。
步骤2:声明寄存器和RAM
在声明寄存器之前,我们确定高速缓存和索引的大小:
parameter size = 64; parameter index_size = 6;
接下来,我们声明一个数组,其中将存储我们写入和读取的数据:
reg [word_size-1:0] data_array [size-1:0];
我们还需要为缓存中的每个项目存储有效性位和标签:
reg validity_array [size-1:0]; reg [word_size-index_size-1:0] tag_array [size-1:0]; reg [index_size-1:0] index_array [size-1:0];
输入地址将被分成的寄存器:
reg [word_size-index_size-1:0] tag; reg [index_size-1:0] index;
将输入值存储在前一个时钟上的寄存器(用于跟踪输入数据的变化):
reg [word_size-1:0] data_reg; reg [word_size-1:0] addr_reg; reg wr_reg;
在always块中进行计算后用于更新输出信号的寄存器:
reg response_reg; reg is_missrate_reg; reg [word_size-1:0] out_reg;
RAM的输入值:
reg [word_size-1:0] ram_data; reg [word_size-1:0] ram_addr; reg ram_wr;
RAM的输出值:
wire ram_response; wire [word_size-1:0] ram_out;
声明一个RAM模块并连接输入和输出信号:
ram ram( .data(ram_data), .addr(ram_addr), .wr(ram_wr), .clk(clk), .response(ram_response), .out(ram_out));
寄存器初始化:
initial integer i initial begin data_reg = 0; addr_reg = 0; wr_reg = 0; for (i = 0; i < size; i=i+1) begin data_array[i] = 0; tag_array[i] = 0; validity_array[i] = 0; end end
步骤3:实现模块的始终逻辑
首先,对于每个时钟,我们有两种状态-输入数据已更改或未更改。 基于此,我们具有以下条件:
always @(posedge clk) begin if (data_reg != data || addr_reg != addr || wr_reg != wr) begin end // 1: else begin // 2: end end
块1。如果输入数据发生更改,我们要做的第一件事就是将就绪状态重置为0:
response_reg = 0;
接下来,我们更新存储先前时钟输入值的寄存器:
data_reg = data; addr_reg = addr; wr_reg = wr;
我们将输入地址分为一个标签和索引:
tag = addr >> index_size; index = addr;
为了计算标签,使用向右的按位移位,对于索引,只需对其进行分配就足够了,因为 不考虑地址的多余位。
下一步是在写作和阅读之间进行选择:
if (wr) begin // data_array[index] = data; tag_array[index] = tag; validity_array[index] = 1; ram_data = data; ram_addr = addr; ram_wr = wr; end else begin // if ((validity_array[index]) && (tag == tag_array[index])) begin // is_missrate_reg = 0; out_reg = data_array[index]; response_reg = 1; end else begin // is_missrate_reg = 1; ram_data = data; ram_addr = addr; ram_wr = wr; end end
对于记录,我们首先修改缓存中的数据,然后更新RAM的输入数据。 在读取的情况下,我们检查缓存中是否存在此元素,如果存在,则将其写入out_reg,否则,我们转向RAM。
块2。如果自从上一个时钟执行以来数据没有更改,那么我们有以下代码:
if ((ram_response) && (!response_reg)) begin if (wr == 0) begin validity_array [index] = 1; data_array [index] = ram_out; tag_array[index] = tag; out_reg = ram_out; end response_reg = 1; end
在这里,我们等待对RAM的访问完成(如果没有访问,ram_response为1),如果存在读取命令,则更新数据,并将缓存就绪状态设置为1。
最后,更新输出值:
assign out = out_reg; assign is_missrate = is_missrate_reg; assign response = response_reg;