Altifigence Academy

57 / 63 · Conceito

Ponteiros e políticas de fronteira de uma FIFO de clock único

Implemente os ponteiros e a ocupação de uma FIFO de profundidade 4 e defina quando push e pop simultâneos são aceitos com a fila vazia ou cheia.

Manter a ordem sem deslocar todos os dados

Uma FIFOFIFO Fila na qual o primeiro dado a entrar é o primeiro a sair. As condições de cheio e vazio e as regras de leitura e escrita simultâneas fazem parte do contrato. entrega primeiro o dado que entrou primeiro. Em uma FIFO de clock único, leituras e escritas são decididas na mesma borda de subida. Aqui fixamos a profundidade em 4 e a largura dos dados em 8 bits.

rptr aponta para a próxima posição a ser lida e wptr para a próxima posição a ser escrita. Com 2 bits, os ponteiros voltam naturalmente de 3 para 0. A ocupação precisa representar de 0 a 4, portanto exige 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 pode ocorrer tanto com a FIFO vazia quanto cheia. A igualdade dos ponteiros não basta para distingui-las. Sendo C a ocupação, empty=(C=0) e full=(C=4).

Separar solicitações de operações aceitas

push e pop são solicitações. Chamaremos p de push aceito e q de pop aceito. Sem reset, eles são calculados a partir do estado anterior à borda:

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

Se a FIFO estiver vazia e receber as duas solicitações, somente push será aceito: o dado recém-escrito não aparece imediatamente na saída. Se estiver cheia e receber ambas, a cabeça anterior sai e o novo dado é armazenado. full significa que existem quatro elementos armazenados; por si só, não proíbe todo push.

Ocupação anteriorSomente pushSomente popPush e pop
C=0Armazena; C=1RejeitadoSomente push; C=1
0<C<4C+1C−1Mantém C
C=4RejeitadoC=3Aceita ambos; C=4

Testar a política pelos controles

Primeiro ative push e pop com a FIFO vazia: somente push deve ser aceito. Desative pop e preencha as quatro posições. Depois ative as duas solicitações com um novo dado: a ocupação deve continuar em 4, a cabeça anterior deve sair e o novo dado deve ser armazenado no final.

Avançar uma FIFO passo a passo

FIFO de quatro posições, inicialmente vazia e com os ponteiros de leitura e escrita em 0. Aceita pop se não estiver vazia. Aceita push se houver espaço ou se um pop for aceito na mesma borda.

O modelo exibe o estado após a 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. Um pop aceito produz uma saída com valid=1. Se nenhum pop for aceito na borda seguinte, 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 de dados mantém seus bits, mas valid volta a 0. Os bits antigos de posições vazias ficam ocultos. Reiniciar a demonstração restaura todo o seu estado; isso não significa que o resetReset Controle que retorna o estado a um valor inicial especificado. É preciso definir se é síncrono ou assíncrono e sua prioridade em relação a outros controles. Saiba mais do RTL apague a memória.

RTL com saída 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

Não atualize count com dois if independentes que incrementem e decrementem o valor. Se ambos executarem 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 ao mesmo registrador, prevalece a última. Os ponteiros avançam conforme a aceitação correspondente, enquanto count resolve conjuntamente as quatro combinações. As condições de aceitação impedem decrementar 0 ou incrementar 4.

Ler e escrever o mesmo endereço quando a fila está cheia

Após armazenar A, B, C e D, temos rptr=wptr=0 e C=4. Com push e pop simultâneos e din=E, o resultado deve ser dout=A, valid=1, fila [B,C,D,E], os dois ponteiros em 1 e ocupação 4. A posição mem[0] termina com E, mas a leitura não bloqueante obtém o A anterior. A semântica read-first é essencial neste caso.

O próximo pop sem push entrega B e reduz a ocupação para 3. Já com a FIFO vazia, push e pop simultâneos de Z deixam C=1 e valid=0; é preciso aceitar outro pop em uma borda posterior para obter Z. Permitir passagem direta quando vazia e permitir substituição quando cheia são decisões independentes.

rst é síncrono e ativo em nível alto: reinicia ponteiros, ocupação e valid, mas não apaga a memória nem dout. Os bits antigos não importam, pois a FIFO vazia rejeita pop e cada push escreve antes de uma leitura posterior. Saídas inválidas não são consumidas. valid identifica a resposta de um pop aceito; não existe ready na saída, portanto o consumidor deve poder recebê-la.

Para uma profundidade que não seja potência de dois, o retorno do ponteiro deve ser explícito: com profundidade 3, o endereço 2 deve voltar a 0. Se houver inferência de RAM, confira o comportamento de dado antigo nas colisões. Este projeto usa um único clock; ele não resolve a sincronização dos ponteiros de uma FIFO assíncrona.

Experimente

Após o reset, aplique em bordas sucessivas: (1) push de A e pop simultâneos, (2) somente push de B, (3) somente push de C, (4) somente push de D, (5) push de E e pop simultâneos e (6) somente pop. Indique, após cada borda, a fila, a ocupação, dout_valid e, quando válido, o dado de saída. Por que a quinta borda exige comportamento read-first?

Ler a explicação

(1) [A], ocupação 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. Na primeira borda não havia elemento armazenado, portanto pop é rejeitado. Na quinta, rptr=wptr=0: leitura e escrita usam a mesma posição e a saída deve ser a cabeça anterior A. Retornar E por write-first altera o conteúdo e a ordem da FIFO. Não se avalia dout quando valid=0.

Sua escolha vale neste navegador. Altere-a a qualquer momento no rodapé.