Altifigence Academy

55 / 63 · Concepto

Memoria síncrona y contrato de colisiones entre lectura y escritura

Distinga respuesta registrada, read-first, write-first y no-change, y separe valid de inicialización de direcciones.

Antes de la dirección, defina cuándo responde

Una memoria requiere capacidad, latencia de lectura y política de colisiones. Construiremos una memoria de 16 posiciones de ocho bits, con una lectura y una escritura por flanco ascendente. Comparten reloj, pero sus direcciones son independientes. La capacidad es 16×8=128 bits y la dirección mide log⁡216=4\log_2 16=4 bits.

Estabilice dirección y rd_en antes del flanco; después aparecen rd_data y rd_valid registrados. Es una etapa de lectura registrada. Otro always_ff que lea esas salidas en el mismo flanco ve sus valores anteriores; el registroRegistro Circuito que almacena varios bits de estado. Los registros síncronos de este curso capturan la entrada especificada en el flanco de reloj. Más información consumidor recibe la respuesta en el siguiente. Marque por separado solicitud y observación. Se admite una solicitud cada ciclo: una etapa de latencia no significa tasaTasa de procesamiento Número de operaciones aceptadas o completadas por unidad de tiempo. Un pipeline puede aumentarla sin reducir la latencia de una operación individual. Más información de 1/2.

Lectura y escritura de la misma dirección

Suponga M[3]=8'h25 antes del flanco y lectura de 3 simultánea con escritura de 8'h9A. Read-first responde 25 y actualiza la memoria a 9A. Write-first responde el nuevo 9A. No-change conserva el registro de salida, que no necesariamente contiene el valor anterior de la dirección 3. Son contratos válidos distintos, también diferenciados en los modos de sincronización de RAM de AMD.

Usaremos read-first. Sean M−M^{-} y M+M^{+} la matriz antes y después de un flanco sin reset. Para cualquier dirección aa:

M+[a]={wr_data,wr_en=1∧a=wr_addrM−[a],en otro casoM^{+}[a]=\begin{cases} \mathrm{wr\_data}, & \mathrm{wr\_en}=1\land a=\mathrm{wr\_addr}\\ M^{-}[a], & \text{en otro caso} \end{cases}
rd_data+={M−[rd_addr],rd_en=1rd_data−,rd_en=0rd_valid+=rd_en\begin{aligned} \mathrm{rd\_data}^{+}&=\begin{cases} M^{-}[\mathrm{rd\_addr}], & \mathrm{rd\_en}=1\\ \mathrm{rd\_data}^{-}, & \mathrm{rd\_en}=0 \end{cases}\\ \mathrm{rd\_valid}^{+}&=\mathrm{rd\_en} \end{aligned}

Incluso con colisión, se lee M−M^{-}, no M+M^{+}. Sin solicitud, rd_data se conserva.

SystemVerilog
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
endmodule

Ambos lados derechos usan valores previos y las asignaciones no bloqueantesAsignación no bloqueante Asignación escrita con <= en RTL secuencial. Evalúa el lado derecho y programa la actualización, permitiendo que los registros del mismo flanco calculen a partir del estado anterior. Más información se aplican después. Escribir antes la sentencia de escritura no hace que la lectura vea el dato nuevo. Para write-first, explicite (wr_en && wr_addr==rd_addr) ? wr_data : mem[rd_addr]. Ese bypass mantiene el registro de salida.

Valid e inicialización son compromisos diferentes

rst es síncrono activo en alto. Con rst=1 en un flanco, se ignoran ambas solicitudes y solo se borra rd_valid. No se ponen a cero matriz ni rd_data. El contenido de una dirección nunca escrita no está definido: puede aparecer X en simulación y no debe suponerse ningún número en hardware.

rd_valid=1 significa «ciclo de respuesta de lectura», no «dirección inicializada y valor conocido». Lea solo direcciones previamente escritas o mantenga una tabla de inicialización por dirección. Read-first durante la primera escritura sigue devolviendo el contenido previo desconocido. Además, no use rd_data con valid=0. Esa protección y la inicialización no se sustituyen entre sí.

Reset no borra el almacenamiento: lo escrito permanece en RTL. Si el sistema descarta el contenido tras reset, gestione también su tabla externa de inicialización. Borrar información sensible es un requisito distinto de reiniciar controles.

Cada columna registra un flanco ascendenteFlanco ascendente Instante en que el reloj pasa de 0 a 1. Se distingue del nivel alto, que abarca todo el intervalo con CLK=1. Más información, entradas previas y valores «después» actualizados; no representa propagación físicaRetardo de propagación Tiempo desde un cambio de entrada hasta que la salida se estabiliza en el valor correcto. La equivalencia lógica y el comportamiento temporal son propiedades distintas. Más información. Todas las direcciones son 3. E0 escribe 25 por primera vez; E1 lee mientras lo sustituye por 9A. El guion en data significa valor indeterminado; valid=0 excluye una respuesta. Los datos se muestran en hexadecimal.

Lea el valor anterior en una colisión read-first
Ver los datos de la forma de onda
Datos de la forma de onda: cada carácter es un intervalo; un punto conserva el estado anterior; p representa un ciclo de reloj.
SeñalForma de ondaValores del bus
flanco2345E0 → E1 → E2 → E3
wr_en1.0.
wr_data23..25 → 9A
rd_en01.0
M[3] después23..25 → 9A
rd_data después234.- → 25 → 9A
rd_valid después01.0

Siga cuatro flancos a mano

E0 escribe 25 en 3 con rd_en=0. E1 escribe 9A y lee 3: después, valid=1 y data=25. E2 lee sin escribir: data=9A. E3, con rd_en=0, conserva 9A pero valid=0. Contarlo como nueva respuesta solo por conservar 9A procesaría el dato dos veces.

Con direcciones distintas, lectura y escritura son independientes. Leer 7 mientras escribe 3 responde el valor previo de 7. Compruebe en síntesis y documentación del dispositivo qué recurso RAM implementa este RTL y cómo trata colisiones. Definir semántica RTL y verificar una implementación concreta son pasos distintos.

Inténtalo tú

M[6]=8'h10 ya está almacenado. En E0 lea la dirección 6 mientras escribe 8'h20; en E1 lea otra vez sin escribir; en E2 ponga rd_en=0. Calcule data/valid tras cada flanco en read-first. ¿Qué cambia con write-first? ¿La colisión read-first de la primera escritura de una dirección nunca escrita da un valor definido?

Leer la explicación

E0: data=10, valid=1 y la matriz pasa a 20. E1: data=20, valid=1. E2 conserva data=20, pero valid=0 y no cuenta como respuesta. Write-first solo cambia data de E0 a 20. La lectura read-first simultánea con la primera escritura devuelve contenido anterior no inicializado: no puede fijarse una respuesta numérica. valid=1 tampoco garantiza inicialización.

Tu elección se aplica a este navegador. Puedes cambiarla en el pie de página.