Altifigence Academy

57 / 63 · Теория

Указатели и граничная политика однотактового FIFO

Реализуйте указатели и occupancy FIFO глубины 4, явно определяя одновременные push/pop при empty и full.

Сохраняйте порядок, не перемещая все данные

FIFOFIFO — очередь, выдающая данные в порядке их поступления. Правила заполненности, пустоты и допустимости одновременного чтения и записи входят в контракт поведения. выдаёт первым то, что первым принято. При одном такте чтение и запись определяются на одном положительном фронте. Здесь 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=⌈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}

Граничная политика различает запрос и принятие

Push и pop — запросы. Пусть p и q — фактически принятые push и pop, вычисляемые по состоянию до фронта без reset:

q=pop∧(C>0)p=push∧((C<4)∨q)C+=C+p−q\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 и двух запросах можно извлечь старую голову и освободить место: принимаются оба. Full означает «сейчас хранится четыре элемента» и в этой политике не является безусловным запретом push.

Состояние до фронтаТолько pushТолько popPush и pop
C=0Сохранить одинОтклонитьТолько push, C=1
0<C<4C+1C−1Оба, C сохраняется
C=4ОтклонитьC=3Оба, C=4

Проверьте границы, продвигая такт

Сначала запросите push и pop вместе. В пустой очереди должен приниматься только push. Затем выключите pop, заполните четыре места и снова включите оба запроса с новым входом. Occupancy остаётся 4, выход получает прежнюю голову, память — новый хвост.

Пошаговая работа FIFO

FIFO на четыре элемента. Изначально пуст; указатели чтения и записи равны 0. pop принимается, если FIFO непуст. push принимается при наличии места или при принятом pop на том же фронте.

Рисунок — учебная модель состояния после положительного фронтаПоложительный фронт — момент перехода такта из 0 в 1. Его следует отличать от уровня, означающего весь интервал CLK=1. Подробнее. При принятом 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-присваиванияхНеблокирующее присваивание обозначается <= в последовательностном RTL. Оно вычисляет правую часть, затем планирует обновление, позволяя регистрам на одном фронте рассчитываться по предыдущему состоянию. Подробнее одному регистру останется последнее. Каждый указатель продвигается при собственном принятии, а счётчик определяется сразу по четырём случаям. Условия принятия также предотвращают уменьшение из 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, но правая часть nonblocking для dout читает прежнее A. Read-first-коллизия обязательна для этой политики замены при full.

Далее push=0,pop=1 выдаёт B и уменьшает число до 3. Напротив, при empty с din=Z и двумя запросами число становится 1, dout_valid=0; Z выдаётся только по pop следующего фронта. Немедленная передача при empty и замена при full — независимые проектные решения.

Rst — синхронный active-high, сбрасывающий указатели, число и dout_valid. Память и dout не стираются. Даже при оставшихся битах после reset empty блокирует pop, а позже читаются только места, записанные новыми push, поэтому полный reset массива для нормальной работы не нужен. Значение с dout_valid=0 не потребляется. Valid=1 обозначает ответ принятого pop; отдельного ready выхода нет, потребитель обязан принять его.

Если DEPTH не степень двойки, естественный 2-битный wrap неприменим. Например, глубина 3 требует явного возврата из адреса 2 в 0. Если синтез использует RAM, проверьте поддержку old-data при коллизии адреса. Код однотактовый и напрямую неприменим к синхронизации указателей FIFO разных доменов.

Попробуйте сами

Сразу после reset подайте по одному фронту: (1) push A и pop, (2) только push B, (3) только push C, (4) только push D, (5) push E и pop, (6) только pop. Укажите очередь, occupancy, dout_valid и действительный выход после каждого. Почему на пятом шаге нужен read-first?

Прочитать объяснение

(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. На первом шаге старого элемента нет, pop отклоняется. На пятом rptr=wptr=0 и чтение с записью одного слота должны вернуть прежнюю голову A. Write-first с E нарушил бы порядок и содержимое FIFO. Выход при valid=0 не сравнивается.

Выбор действует в этом браузере. Его можно изменить внизу страницы.