Altifigence Academy

47 / 52 · Concept

Single-Clock FIFO Pointers and Boundary Policies

Build a depth-four FIFO with explicit occupancy and simultaneous push/pop policies at empty and full boundaries.

La traduction n’est pas encore disponible. La leçon originale est affichée. (한국어)

데이터를 옮기지 않고 순서를 지키기

FIFO는 먼저 넣은 데이터를 먼저 꺼낸다. 단일 클록에서는 읽기와 쓰기가 같은 상승 엣지에서 결정된다. 이 강의는 DEPTH=4, 데이터 폭 8비트로 고정한다. rptr은 다음에 꺼낼 위치, wptr은 다음에 넣을 위치다. 각각 2비트이므로 3 다음에 0으로 자연스럽게 돌아온다. occupancy는 저장 개수 0~4를 표현해야 하므로 3비트가 필요하다.

rptr=wptr은 empty일 때도 full일 때도 가능하다. 따라서 두 주소의 같음만으로 empty를 정하면 가득 찬 데이터를 잃는다. 여기서는 개수 CC를 따로 유지하며 empty=(C=0)\mathrm{empty}=(C=0), full=(C=4)\mathrm{full}=(C=4)로 구분한다. 깊이 D=4D=4일 때 주소 폭 WaddrW_{\mathrm{addr}}와 개수 폭 WcountW_{\mathrm{count}}는 다음과 같다.

Waddr=log2D=2Wcount=log2(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}

요청과 수락을 구별하는 경계 정책

push와 pop은 요청이다. p와 q를 각각 실제 push·pop 수락이라고 하면, reset이 아닌 엣지 직전 상태에서 다음과 같이 계산한다.

q=pop(C>0)p=push((C<4)q)C+=C+pq\begin{aligned} q&=\mathrm{pop}\land(C>0)\\ p&=\mathrm{push}\land((C<4)\lor q)\\ C^{+}&=C+p-q \end{aligned}

empty에서 두 요청이 와도 pop할 기존 데이터가 없으므로 push만 수락한다. 새 데이터가 그 엣지에 곧바로 출력되지는 않는다. full에서 두 요청이 오면 기존 head를 꺼내 자리를 확보할 수 있으므로 둘 다 수락한다. full은 “현재 4개가 저장되어 있음”이라는 상태이며 이 정책에서는 무조건적인 push 금지 신호가 아니다.

엣지 직전 상태push만pop만push와 pop
C=01개 저장거절push만 수락, C=1
0<C<4C+1C−1둘 다 수락, C 유지
C=4거절C=3둘 다 수락, C=4

클록을 진행하며 경계 조건을 확인하기

처음에는 push와 pop을 함께 요청해 보세요. 비어 있으므로 push만 수락되어야 합니다. 다음에는 pop을 끄고 4칸을 채운 뒤, 새로운 입력 값과 함께 두 요청을 다시 켜 보세요. 점유량은 4를 유지하면서 출력에는 기존 head가, 저장 공간에는 새 tail이 남아야 합니다.

Step through a FIFO

Four-entry FIFO. Initially empty; read and write pointers are 0. Accept pop when nonempty. Accept push when space is available or a pop is accepted on the same edge.

그림은 각 상승 에지 직후의 상태를 보여 주는 교육용 모델입니다. pop이 수락되면 output valid가 1이 되고, 다음 에지에 pop이 수락되지 않으면 출력 레지스터의 값은 유지되지만 valid는 0이 됩니다. 비어 있는 슬롯의 오래된 비트는 숨깁니다. ‘처음으로’는 실습 전체를 초기화하는 기능이며 RTL의 메모리 리셋 동작을 나타내지 않습니다.

등록형 출력까지 포함한 RTL

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

occupancy에 두 개의 독립된 if로 증가와 감소를 대입하지 않았다. 동시 수락 때 같은 레지스터에 nonblocking 대입을 두 번 하면 마지막 대입만 남기 때문이다. 두 포인터는 각 수락 때 진행하지만, 개수는 네 경우를 한 번에 결정한다. 수락 조건으로 0에서 감소하거나 4에서 증가하는 경우도 막는다.

full에서 같은 주소를 읽고 쓸 때

차례로 A, B, C, D를 넣으면 rptr=wptr=0, occupancy=4다. 여기서 pop=push=1, din=E이면 엣지 뒤 dout=A, dout_valid=1이며 논리적 내용은 [B,C,D,E]다. 두 포인터는 1이 되고 개수는 4를 유지한다. mem[0]은 E로 바뀌지만 dout은 nonblocking 대입의 오른쪽에서 이전 A를 읽는다. 이 read-first 충돌 동작은 full 교체 정책에 필수다.

그 다음 push=0, pop=1이면 B가 나오고 개수는 3이 된다. 반대로 empty에서 din=Z와 두 요청을 주면 개수만 1이 되고 dout_valid=0이다. 다음 엣지에 pop을 요청해야 Z가 출력된다. empty의 즉시 전달과 full의 교체는 별개의 설계 선택임을 기억하자.

rst는 active-high 동기 reset이며 포인터, 개수, dout_valid를 초기화한다. 메모리와 dout 값은 지우지 않는다. reset 뒤 남은 비트가 있어도 empty가 pop을 막고, 새 push가 기록한 위치만 나중에 읽으므로 정상 동작에 배열 전체 reset은 필요하지 않다. dout_valid=0인 값은 소비하지 않는다. valid가 1인 출력은 수락한 pop의 응답이고, 출력 쪽에 별도 ready는 없으므로 소비자가 그 응답을 받아야 한다.

DEPTH가 2의 거듭제곱이 아닐 때는 단순한 2비트 overflow를 재사용할 수 없다. 예를 들어 깊이 3은 주소 2 다음에 명시적으로 0으로 돌아가야 한다. 이번 예제의 합성 결과가 RAM을 사용한다면 같은 주소의 old-data 동작을 지원하는지도 확인해야 한다. 이 코드는 단일 클록용이며 클록이 다른 FIFO의 포인터 동기화에는 그대로 적용하지 않는다.

Essayez vous-même

reset 직후 다음 요청을 한 엣지씩 준다: (1) push A와 pop 동시, (2) push B만, (3) push C만, (4) push D만, (5) push E와 pop 동시, (6) pop만. 각 단계 뒤 queue 내용, occupancy, dout_valid와 유효 출력값을 적어라. 다섯 번째 단계에 read-first가 필요한 이유도 설명하라.

Lire l’explication

(1) [A],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. 1단계에는 기존 항목이 없어 pop을 거절한다. 5단계에는 rptr=wptr=0이고 같은 슬롯에 쓰기와 읽기가 겹치므로 이전 head A를 읽어야 한다. write-first로 E를 반환하면 FIFO 순서와 내용이 깨진다. valid=0인 출력값은 채점 대상이 아니다.

Votre choix s’applique à ce navigateur. Modifiez-le à tout moment en bas de page.