Verilog缓存实施

本文讨论了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; 

Source: https://habr.com/ru/post/zh-CN461611/


All Articles