Simulação simples de cache mapeado diretamente no FPGA
Este artigo é parte de um curso para alunos do primeiro ano de graduação da Universidade de Innopolis. Todo o trabalho é feito em equipe. O objetivo deste artigo é mostrar um entendimento do tópico ou ajudar a entendê-lo usando simulação.
Link do repositório Git
Princípio do trabalho, mas do lado do usuário deve se parecer com:
- Para gravar quaisquer dados na memória, você precisa acessar a RAM com dados e endereço nos quais queremos escrever.
- Para acessar os dados, precisamos endereçar ao cache. Se o cache não conseguir encontrar os dados necessários, ele acessará a RAM, copiando os dados de lá.
Ao trabalhar com a Verilog, deve-se entender que cada bloco individual do programa é representado como um módulo. Como você sabe, o cache não é uma parte independente da memória rápida e, para seu funcionamento adequado, ele precisa coletar dados de outro bloco de memória - RAM. Portanto, para simular o trabalho do cache no FPGA, precisamos simular todo o módulo de RAM, que também inclui o cache, mas o ponto principal é a simulação do cache.
A implementação consiste em tais módulos:
- ram.v - módulo de memória RAM
- cache.v - módulo de memória cache
- cache_and_ram.v - módulo que opera com dados e memória.
- testbench.v e testbench2.v - módulo para mostrar que os módulos principais funcionam perfeitamente.
Módulo de RAM:
Códigomodule ram(); parameter size = 4096; //size of a ram in bits reg [31:0] ram [0:size-1]; //data matrix for ram endmodule
Descrição do produtoO módulo representa a memória usada como RAM. Possui 4096 células endereçáveis de 32 bits para armazenar alguns dados.

Módulo de cache:
Código module cache(); parameter size = 64; // cache size parameter index_size = 6; // index size reg [31:0] cache [0:size - 1]; //registers for the data in cache reg [11 - index_size:0] tag_array [0:size - 1]; // for all tags in cache reg valid_array [0:size - 1]; //0 - there is no data 1 - there is data initial begin: initialization integer i; for (i = 0; i < size; i = i + 1) begin valid_array[i] = 6'b000000; tag_array[i] = 6'b000000; end end endmodule
Descrição do produtoPortanto, o cache contém mais do que apenas cópias dos dados em
memória ele também possui bits para nos ajudar a encontrar dados no cache e
verifique sua validade.

Módulo de cache e RAM:
Código module cache_and_ram( input [31:0] address, input [31:0] data, input clk, input mode, //mode equal to 1 when we write and equal to 0 when we read output [31:0] out ); //previous values reg [31:0] prev_address, prev_data; reg prev_mode; reg [31:0] temp_out; reg [cache.index_size - 1:0] index; // for keeping index of current address reg [11 - cache.index_size:0] tag; // for keeping tag of ceurrent address ram ram(); cache cache(); initial begin index = 0; tag = 0; prev_address = 0; prev_data = 0; prev_mode = 0; end always @(posedge clk) begin //check if the new input is updated if (prev_address != address || prev_data != data || prev_mode != mode) begin prev_address = address % ram.size; prev_data = data; prev_mode = mode; tag = prev_address >> cache.index_size; // tag = first bits of address except index ones (In our particular case - 6) index = address % cache.size; // index value = last n (n = size of cache) bits of address if (mode == 1) begin ram.ram[prev_address] = data; //write new data to the relevant cache block if there is such one if (cache.valid_array[index] == 1 && cache.tag_array[index] == tag) cache.cache[index] = data; end else begin //write new data to the relevant cache's block, because the one we addressing to will be possibly addressed one more time soon if (cache.valid_array[index] != 1 || cache.tag_array[index] != tag) begin cache.valid_array[index] = 1; cache.tag_array[index] = tag; cache.cache[index] = ram.ram[prev_address]; end temp_out = cache.cache[index]; end end end assign out = temp_out; endmodule
Descrição do produtoRepresenta operações para trabalhar com dados em módulos de memória. Obtém entrada em cada borda positiva do relógio. Verifica se há novas entradas - dependendo do modo (1 para gravação / 0 para leitura) executa operações relevantes. Se o modo for 1 (gravação):
• Escreva os dados no endereço e verifique se o endereço de entrada existe no cache; se houver - substitua os dados, caso contrário, fique parado.
Se o modo for 0 (lido):
• Verifique se o endereço de entrada existe no cache. Se sim - retorne os dados, caso contrário, obtenha os dados do ram. Atualize o endereço no cache com novos dados.
Bancadas de teste:
Code1 module testbench; reg [31:0] address, data; reg mode, clk; wire [31:0] out; cache_and_ram tb( .address(address), .data(data), .mode(mode), .clk(clk), .out(out) ); initial begin clk = 1'b1; address = 32'b00000000000000000000000000000000; // 0 data = 32'b00000000000000000011100011000000; // 14528 mode = 1'b1; #200 address = 32'b10100111111001011111101111011100; // 2816867292 % size = 3036 data = 32'b00000000000010000000100001010101; // 526421 mode = 1'b1; #200 address = 32'b00000000000011110100011111010001; // 1001425 % size = 2001 data = 32'b00000001100000110001101100010110; // 25369366 mode = 1'b1; #200 address = 32'b10100111111001011111101111011100; // 2816867292 % size = 3036 data = 32'b00000000000000000011100011000000; // 14528 mode = 1'b1; #200 address = 32'b00000000000011110100011111010001; // 1001425 % size = 2001 data = 32'b00000000000000000011100011000000; // 14528 mode = 1'b1; #200 address = 32'b00000000000011110100011111010001; // 1001425 % size = 2001 data = 32'b00000000000000000000000000000000; // 0 mode = 1'b0; #200 address = 32'b10100111111001011111101111011100; // 2816867292 % size = 3036 data = 32'b00000000000000000000000000000000; // 0 mode = 1'b0; #200 address = 32'b00000000000000000000000000000000; // 0 data = 32'b00000000000000000011100011000000; // 14528 mode = 1'b0; end initial $monitor("address = %d data = %d mode = %d out = %d", address % 4096, data, mode, out); always #25 clk = ~clk; endmodule
Code2 module testbench2; reg [31:0] address, data; reg mode, clk; wire [31:0] out; cache_and_ram tb( .address(address), .data(data), .mode(mode), .clk(clk), .out(out) ); initial begin clk = 1'b1; address = 32'b00000000000000000000000000000000; // 0 data = 32'b00000000000000000011100011000000; // 14528 mode = 1'b1; #200 address = 32'b10100111111001011111101111011100; // 2816867292 % size = 3036 data = 32'b00000000000010000000100001010101; // 526421 mode = 1'b1; #200 address = 32'b00000000000000000000000000000000; // 0 data = 32'b00000000000000000011100011000000; // 14528 mode = 1'b0; #200 address = 32'b10100111111001011111101111011100; // 2816867292 % size = 3036 data = 32'b00000000000010000000100001010101; // 526421 mode = 1'b0; #200 address = 32'b00000000000011110100011111010001; // 1001425 % size = 2001 data = 32'b00000001100000110001101100010110; // 25369366 mode = 1'b1; #200 address = 32'b00000000000011110100011111010001; // 1001425 % size = 2001 data = 32'b00000001100000110001101100010110; // 25369366 mode = 1'b0; #200 address = 32'b10100111111001011111101111011100; // 2816867292 % size = 3036 data = 32'b00000000000000000011100011000000; // 14528 mode = 1'b1; #200 address = 32'b00000000000011110100011111010001; // 1001425 % size = 2001 data = 32'b00000000000000000011100011000000; // 14528 mode = 1'b1; #200 address = 32'b00000000000011110100011111010001; // 1001425 % size = 2001 data = 32'b00000000000000000000000000000000; // 0 mode = 1'b0; #200 address = 32'b10100111111001011111101111011100; // 2816867292 % size = 3036 data = 32'b00000000000000000000000000000000; // 0 mode = 1'b0; end initial $monitor("address = %d data = %d mode = %d out = %d", address % 4096, data, mode, out); always #25 clk = ~clk; endmodule
Descrição do produtoPara executar um testbench, carregue todos os arquivos no projeto ModelSim e execute uma simulação de um dos arquivos testbench.