Implementación de caché Verilog

Este artículo analiza la implementación más simple de RAM en Verilog.

Antes de continuar con el análisis de código, se recomienda que aprenda la sintaxis básica de Verilog.

Aquí puede encontrar materiales de capacitación .

RAM


Paso 1: declarar el módulo con las señales de entrada / salida correspondientes


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; 

  • datos : datos para escribir.
  • addr : dirección de la memoria en RAM.
  • wr - estado (lectura / escritura).
  • clk - sistema de ciclo de reloj.
  • respuesta - disponibilidad de RAM (1 - si RAM procesó la solicitud de lectura / escritura, 0 - de lo contrario).
  • fuera - datos leídos de la RAM.

Esta implementación se integró en Altera Max 10 FPGA, que tiene una arquitectura de 32 bits y, por lo tanto, el tamaño de los datos y la dirección (word_size) es de 32 bits.

Paso 2: declarando los registros dentro del módulo


Una declaración de matriz para almacenar datos:

 parameter size = 1<<32; reg [word_size-1:0] ram [size-1:0]; 

También necesitamos almacenar los parámetros de entrada anteriores para rastrear sus cambios en el bloque always:

 reg [word_size-1:0] data_reg; reg [word_size-1:0] addr_reg; reg wr_reg; 

Y los dos últimos registros para actualizar las señales de salida después de los cálculos en el bloque siempre:

 reg [word_size-1:0] out_reg; reg response_reg; 

Inicializamos los registros:

 initial begin response_reg = 1; data_reg = 0; addr_reg = 0; wr_reg = 0; end 

Paso 3: implementando la lógica siempre del bloque


 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 

El bloqueo siempre se activa por negedje, es decir en este momento el reloj se mueve de 1 a 0. Esto se hace para sincronizar correctamente la RAM con el caché. De lo contrario, puede haber casos en que la RAM no tenga tiempo para restablecer el estado de preparado de 1 a 0 y en el siguiente reloj, la memoria caché decide que la RAM ha procesado con éxito su solicitud, lo cual es fundamentalmente incorrecto.

La lógica del algoritmo siempre del bloque es la siguiente: si los datos se actualizan, restablezca el estado de preparación a 0 y escriba / lea datos, si la escritura / lectura se completa, actualizamos el estado de preparación a 1.

Al final, agregue la siguiente sección de código:

 assign out = out_reg; assign response = response_reg; 

El tipo de señales de salida de nuestro módulo es el cable. La única forma de cambiar las señales de este tipo es la asignación a largo plazo, que está prohibida dentro del bloque siempre. Por esta razón, el bloque siempre usa registros, que posteriormente se asignan a las señales de salida.

Caché de mapeo directo


El caché de mapeo directo es uno de los tipos más simples de caché. En esta implementación, el caché consta de n elementos, y la RAM se divide condicionalmente en bloques por n, entonces el elemento i-ésimo en el caché corresponde a todos los elementos k-ésimo en RAM que satisfacen la condición i = k% n.

La imagen a continuación muestra un caché de tamaño 4 y RAM de tamaño 16.



Cada elemento de caché contiene la siguiente información:

  • bit de validez : si la información en el caché es relevante.
  • La etiqueta es el número de bloque en la RAM donde se encuentra este elemento.
  • datos : información que escribimos / leemos.

Cuando se le solicita que lea, el caché divide la dirección de entrada en dos partes: una etiqueta y un índice. El tamaño del índice es log (n), donde n es el tamaño de la memoria caché.

Paso 1: declarar el módulo con las señales de entrada / salida correspondientes


 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; 

La declaración del módulo de caché es idéntica a la RAM, con la excepción de la nueva señal de salida is_missrate. Esta salida almacena información sobre si la última solicitud de lectura fue una tasa de error incorrecta.

Paso 2: declarando los registros y la RAM


Antes de declarar los registros, determinamos el tamaño de la caché y el índice:

 parameter size = 64; parameter index_size = 6; 

A continuación, declaramos una matriz en la que se almacenarán los datos que escribimos y leemos:

 reg [word_size-1:0] data_array [size-1:0]; 

También necesitamos almacenar bits y etiquetas de validez para cada elemento en el caché:

 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]; 

Registros en los que se dividirá la dirección de entrada:

 reg [word_size-index_size-1:0] tag; reg [index_size-1:0] index; 

Registros que almacenan los valores de entrada en el reloj anterior (para rastrear cambios en los datos de entrada):

 reg [word_size-1:0] data_reg; reg [word_size-1:0] addr_reg; reg wr_reg; 

Registros para actualizar las señales de salida después de los cálculos en el bloque always:

 reg response_reg; reg is_missrate_reg; reg [word_size-1:0] out_reg; 

Valores de entrada para RAM:

 reg [word_size-1:0] ram_data; reg [word_size-1:0] ram_addr; reg ram_wr; 

Valores de salida para RAM:

 wire ram_response; wire [word_size-1:0] ram_out; 

Declarar un módulo RAM y conectar señales de entrada y salida:

 ram ram( .data(ram_data), .addr(ram_addr), .wr(ram_wr), .clk(clk), .response(ram_response), .out(ram_out)); 

Inicialización de registro:

 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 

Paso 3: implementando la lógica siempre del bloque


Para empezar, para cada reloj tenemos dos estados: los datos de entrada se cambian o no. En base a esto, tenemos la siguiente condición:

 always @(posedge clk) begin if (data_reg != data || addr_reg != addr || wr_reg != wr) begin end // 1:    else begin // 2:     end end 

Bloque 1. En caso de que se modifiquen los datos de entrada, lo primero que hacemos es restablecer el estado de preparación a 0:

 response_reg = 0; 

A continuación, actualizamos los registros que almacenaron los valores de entrada del reloj anterior:

 data_reg = data; addr_reg = addr; wr_reg = wr; 

Rompemos la dirección de entrada en una etiqueta e índice:

 tag = addr >> index_size; index = addr; 

Para calcular la etiqueta, se usa un desplazamiento bit a la derecha, para el índice, es suficiente simplemente asignarlo, porque Los bits adicionales de la dirección no se tienen en cuenta.

El siguiente paso es elegir entre escribir y leer:

 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 

En el caso de la grabación, inicialmente modificamos los datos en la memoria caché, luego actualizamos los datos de entrada para RAM. En el caso de la lectura, verificamos la presencia de este elemento en el caché y, si existe, lo escribimos en out_reg, de lo contrario, accedemos a la RAM.

Bloque 2. Si los datos no han cambiado desde que se ejecutó el reloj anterior, entonces tenemos el siguiente código:

 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 

Aquí esperamos la finalización del acceso a la RAM (si no hubo acceso, ram_response es 1), actualizamos los datos si hubo un comando de lectura y establecemos la disponibilidad de caché en 1.

Y por último, actualice los valores de salida:

 assign out = out_reg; assign is_missrate = is_missrate_reg; assign response = response_reg; 

Source: https://habr.com/ru/post/461611/


All Articles