Altifigence Academy

57 / 63 · Concepto

Punteros y políticas de frontera de una FIFO de un solo reloj

Implemente los punteros y la ocupación de una FIFO de profundidad 4 y defina cuándo se aceptan push y pop simultáneos al estar vacía o llena.

Conservar el orden sin desplazar todos los datos

Una FIFOFIFO Cola en la que el primer dato que entra es el primero que sale. Las condiciones de lleno y vacío y las reglas de lectura y escritura simultáneas forman parte del contrato. entrega primero el dato que entró primero. En una FIFO de un solo reloj, las lecturas y las escrituras se deciden en el mismo flanco de subida. Aquí fijamos la profundidad en 4 y el ancho de los datos en 8 bits.

rptr apunta a la próxima posición que se leerá y wptr a la próxima que se escribirá. Con 2 bits, los punteros vuelven de 3 a 0 de forma natural. La ocupación debe representar de 0 a 4, así que necesita 3 bits.

Waddr=⌈log⁡2D⌉=2Wcount=⌈log⁡2(D+1)⌉=3\begin{aligned} W_{\mathrm{addr}}&=\lceil\log_2 D\rceil=2\\ W_{\mathrm{count}}&=\lceil\log_2(D+1)\rceil=3 \end{aligned}

rptr=wptr puede darse tanto con la FIFO vacía como llena. La igualdad de punteros no basta para distinguirlas. Si C es la ocupación, empty=(C=0) y full=(C=4).

Distinguir solicitudes y operaciones aceptadas

push y pop son solicitudes. Llamaremos p al push aceptado y q al pop aceptado. Sin reset, se calculan con el estado anterior al flanco:

q=pop∧(C>0)q=pop\land(C>0)
p=push∧((C<4)∨q)p=push\land\big((C<4)\lor q\big)
C+=C+p−qC^+=C+p-q

Si la FIFO está vacía y llegan ambas solicitudes, solo se acepta push: el dato recién escrito no aparece inmediatamente en la salida. Si está llena y llegan ambas, se entrega la cabecera anterior y se almacena el dato nuevo. full significa que hay cuatro elementos almacenados; no prohíbe por sí solo todo push.

Ocupación anteriorSolo pushSolo popPush y pop
C=0Almacena; C=1Se rechazaSolo push; C=1
0<C<4C+1C−1Conserva C
C=4Se rechazaC=3Acepta ambos; C=4

Probar la política con los controles

Primero active push y pop con la FIFO vacía: debe aceptarse solo push. Desactive pop y llene las cuatro posiciones. Después active ambas solicitudes con un nuevo dato: la ocupación debe seguir en 4, debe salir la cabecera anterior y debe almacenarse el nuevo dato al final.

Avanzar una FIFO paso a paso

FIFO de cuatro posiciones, inicialmente vacía y con los punteros de lectura y escritura en 0. Acepta pop si no está vacía. Acepta push si hay espacio o si se acepta un pop en el mismo flanco.

El modelo muestra el estado posterior al flanco de subidaFlanco 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. Un pop aceptado produce una salida con valid=1. Si no se acepta pop en el siguiente flanco, 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 de datos conserva sus bits, pero valid vuelve a 0. Se ocultan los bits antiguos de posiciones vacías. Reiniciar la demostración restaura todo su estado; esto no implica que el resetReset Control que devuelve el estado a un valor inicial especificado. Deben definirse su carácter síncrono o asíncrono y su prioridad frente a otros controles. Más información del RTL borre la memoria.

RTL con salida registrada

SystemVerilog
module fifo4 (
  input  logic       clk, rst, push, pop,
  input  logic [7:0] din,
  output logic [7:0] dout,
  output logic       dout_valid, full, empty,
  output logic [2:0] occupancy
);
  timeunit 1ns;
  timeprecision 1ps;
  logic [7:0] mem [0:3];
  logic [1:0] rptr, wptr;
  logic take_push, take_pop;

  assign empty = (occupancy == 3'd0);
  assign full  = (occupancy == 3'd4);
  assign take_pop  = !rst && pop && !empty;
  assign take_push = !rst && push && (!full || take_pop);

  always_ff @(posedge clk) begin
    if (rst) begin
      rptr <= 2'd0;
      wptr <= 2'd0;
      occupancy <= 3'd0;
      dout_valid <= 1'b0;
    end else begin
      dout_valid <= take_pop;
      if (take_push) begin
        mem[wptr] <= din;
        wptr <= wptr + 2'd1;
      end
      if (take_pop) begin
        dout <= mem[rptr];
        rptr <= rptr + 2'd1;
      end
      case ({take_push, take_pop})
        2'b10: occupancy <= occupancy + 3'd1;
        2'b01: occupancy <= occupancy - 3'd1;
        default: occupancy <= occupancy;
      endcase
    end
  end
endmodule

No actualice count con dos if independientes que lo incrementen y decrementen. Si ambos ejecutan 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 al mismo registro, prevalece la última. Los punteros avanzan según la aceptación correspondiente, mientras que count resuelve conjuntamente las cuatro combinaciones. Las condiciones de aceptación evitan decrementar 0 o incrementar 4.

Leer y escribir la misma dirección cuando está llena

Después de almacenar A, B, C y D, se tiene rptr=wptr=0 y C=4. Con push y pop simultáneos y din=E, el resultado debe ser dout=A, valid=1, cola [B,C,D,E], ambos punteros en 1 y ocupación 4. La posición mem[0] termina conteniendo E, pero la lectura no bloqueante obtiene el A anterior. Aquí es esencial la semántica read-first.

El siguiente pop sin push entrega B y reduce la ocupación a 3. En cambio, con la FIFO vacía, push y pop simultáneos de Z dejan C=1 y valid=0; hace falta aceptar otro pop en un flanco posterior para obtener Z. Permitir un paso directo al estar vacía y permitir un reemplazo al estar llena son decisiones independientes.

rst es síncrono y activo en alto: reinicia punteros, ocupación y valid, pero no borra la memoria ni dout. Los bits antiguos no importan porque una FIFO vacía rechaza pop y cada push escribe antes de una lectura posterior. No se consumen salidas inválidas. valid identifica la respuesta de un pop aceptado; no hay un ready de salida, por lo que el consumidor debe poder recibirla.

Para una profundidad que no sea potencia de dos, el retorno del puntero debe ser explícito: con profundidad 3, la dirección 2 debe pasar a 0. Si se infiere RAM, compruebe el comportamiento de dato anterior en las colisiones. Este diseño usa un solo reloj; no resuelve la sincronización de punteros de una FIFO asíncrona.

Inténtalo tú

Tras el reset, se aplican en flancos sucesivos: (1) push de A y pop simultáneos, (2) solo push de B, (3) solo push de C, (4) solo push de D, (5) push de E y pop simultáneos y (6) solo pop. Indique después de cada flanco la cola, la ocupación, dout_valid y, cuando sea válido, el dato de salida. ¿Por qué el quinto flanco necesita un comportamiento read-first?

Leer la explicación

(1) [A], ocupación 1, valid=0; (2) [A,B], 2, valid=0; (3) [A,B,C], 3, valid=0; (4) [A,B,C,D], 4, valid=0; (5) [B,C,D,E], 4, valid=1, dout=A; (6) [C,D,E], 3, valid=1, dout=B. En el primer flanco no había ningún elemento almacenado, por lo que se rechaza pop. En el quinto, rptr=wptr=0: la lectura y la escritura coinciden en la misma posición y debe salir la cabecera anterior A. Devolver E mediante write-first altera el contenido y el orden de la FIFO. No se evalúa dout cuando valid=0.

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