55 / 63 · Conceito
Memória síncrona e contrato de colisões entre leitura e escrita
Distinga resposta registrada, read-first, write-first e no-change, e separe valid da inicialização de endereços.
As aulas podem ser lidas gratuitamente. Inscreva-se para salvar o progresso.
Antes do endereço, defina quando responder
Uma memória exige capacidade, latência de leitura e política de colisões. Construiremos 16 endereços de oito bits, com uma leitura e uma escrita por borda de subida. Compartilham clock, mas seus endereços são independentes. A capacidade é 16×8=128 bits e o endereço mede bits.
Estabilize endereço e rd_en antes da borda; depois aparecem rd_data e rd_valid registrados. É um estágio de leitura registrada. Outro always_ff que leia essas saídas na mesma borda vê os valores anteriores; o registradorRegistrador Circuito que armazena vários bits de estado. Os registradores síncronos deste curso capturam a entrada especificada na borda de clock. Saiba mais consumidor recebe a resposta na próxima. Marque solicitação e observação separadamente. Uma solicitação por ciclo é permitida: um estágio de latência não significa taxaTaxa de processamento Quantidade de operações aceitas ou concluídas por unidade de tempo. Um pipeline pode aumentá-la sem reduzir a latência de uma operação individual. Saiba mais de 1/2.
Leitura e escrita no mesmo endereço
Suponha M[3]=8'h25 antes da borda e leitura de 3 simultânea à escrita de 8'h9A. Read-first responde 25 e atualiza a memória para 9A. Write-first responde o novo 9A. No-change mantém o registrador de saída, que não necessariamente contém o valor anterior do endereço 3. São contratos válidos diferentes, também distinguidos nos modos de sincronização de RAM da AMD.
Usaremos read-first. Sejam e a matriz antes e depois de uma borda sem reset. Para qualquer endereço :
Mesmo com colisão, lê-se , não . Sem solicitação, rd_data é mantido.
module sync_mem16x8 (
input logic clk, rst,
input logic wr_en, rd_en,
input logic [3:0] wr_addr, rd_addr,
input logic [7:0] wr_data,
output logic [7:0] rd_data,
output logic rd_valid
);
timeunit 1ns;
timeprecision 1ps;
logic [7:0] mem [0:15];
always_ff @(posedge clk) begin
if (rst) begin
rd_valid <= 1'b0;
end else begin
rd_valid <= rd_en;
if (wr_en)
mem[wr_addr] <= wr_data;
if (rd_en)
rd_data <= mem[rd_addr];
end
end
endmoduleAmbos os lados direitos usam valores anteriores e as atribuições não bloqueantesAtribuição não bloqueante Atribuição escrita com <= em RTL sequencial. Avalia o lado direito e agenda a atualização, permitindo que os registradores da mesma borda calculem a partir do estado anterior. Saiba mais são aplicadas depois. Colocar a instrução de escrita primeiro no código não faz a leitura ver o novo dado. Para write-first, explicite (wr_en && wr_addr==rd_addr) ? wr_data : mem[rd_addr]. Esse bypass mantém o registrador de saída.
Valid e inicialização são compromissos diferentes
rst é síncrono ativo em nível alto. Com rst=1 numa borda, ambas as solicitações são ignoradas e apenas rd_valid é zerado. Matriz e rd_data não são zerados. O conteúdo de um endereço nunca escrito não é definido: pode aparecer X na simulação e nenhum número deve ser pressuposto no hardware.
rd_valid=1 significa «ciclo de resposta de leitura», não «endereço inicializado e valor conhecido». Leia apenas endereços previamente escritos ou mantenha uma tabela de inicialização por endereço. Read-first durante a primeira escrita ainda devolve o conteúdo anterior desconhecido. Além disso, não use rd_data com valid=0. Essa proteção e a inicialização não substituem uma à outra.
Reset não apaga o armazenamento: o que foi escrito permanece em RTL. Se o sistema descartar o conteúdo após reset, gerencie também sua tabela externa de inicialização. Apagar informação sensível é um requisito diferente de reiniciar controles.
Cada coluna registra uma borda de subidaBorda de subida Instante em que o clock passa de 0 para 1. É diferente do nível alto, que abrange todo o intervalo com CLK=1. Saiba mais, entradas anteriores e valores «depois» atualizados; não representa propagação físicaAtraso de propagação Tempo entre uma mudança de entrada e a estabilização da saída no valor correto. Equivalência lógica e comportamento temporal são propriedades distintas. Saiba mais. Todos os endereços são 3. E0 escreve 25 pela primeira vez; E1 lê enquanto o substitui por 9A. O traço em data significa valor indeterminado; valid=0 exclui uma resposta. Os dados são mostrados em hexadecimal.
Ver os dados da forma de onda
| Sinal | Forma de onda | Valores do barramento |
|---|---|---|
| borda | 2345 | E0 → E1 → E2 → E3 |
| wr_en | 1.0. | |
| wr_data | 23.. | 25 → 9A |
| rd_en | 01.0 | |
| M[3] depois | 23.. | 25 → 9A |
| rd_data depois | 234. | - → 25 → 9A |
| rd_valid depois | 01.0 |
Siga quatro bordas manualmente
E0 escreve 25 em 3 com rd_en=0. E1 escreve 9A e lê 3: depois, valid=1 e data=25. E2 lê sem escrever: data=9A. E3, com rd_en=0, mantém 9A, mas valid=0. Contá-lo como nova resposta apenas por manter 9A processaria o dado duas vezes.
Com endereços diferentes, leitura e escrita são independentes. Ler 7 enquanto escreve 3 responde o valor anterior de 7. Confira na síntese e na documentação do dispositivo qual recurso RAM implementa este RTL e como trata colisões. Definir semântica RTL e verificar uma implementação concreta são etapas diferentes.
Experimente
M[6]=8'h10 já está armazenado. Em E0, leia o endereço 6 enquanto escreve 8'h20; em E1, leia novamente sem escrever; em E2, coloque rd_en=0. Calcule data/valid após cada borda em read-first. O que muda com write-first? A colisão read-first da primeira escrita num endereço nunca escrito dá um valor definido?
Ler a explicação
E0: data=10, valid=1 e a matriz passa para 20. E1: data=20, valid=1. E2 mantém data=20, mas valid=0 e não conta como resposta. Write-first só muda data de E0 para 20. A leitura read-first simultânea à primeira escrita devolve conteúdo anterior não inicializado: não se pode fixar uma resposta numérica. valid=1 também não garante inicialização.