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 только совпадением адресов, заполненные данные потеряются. Здесь хранится отдельный счётчик : , . Для глубины ширины адреса и счётчика :
Граничная политика различает запрос и принятие
Push и pop — запросы. Пусть p и q — фактически принятые push и pop, вычисляемые по состоянию до фронта без reset:
При empty и двух запросах старого элемента для pop нет, принимается только push. Новое значение не выдаётся сразу на этом фронте. При full и двух запросах можно извлечь старую голову и освободить место: принимаются оба. Full означает «сейчас хранится четыре элемента» и в этой политике не является безусловным запретом push.
| Состояние до фронта | Только push | Только pop | Push и pop |
|---|---|---|---|
| C=0 | Сохранить один | Отклонить | Только push, C=1 |
| 0<C<4 | C+1 | C−1 | Оба, C сохраняется |
| C=4 | Отклонить | C=3 | Оба, C=4 |
Проверьте границы, продвигая такт
Сначала запросите push и pop вместе. В пустой очереди должен приниматься только push. Затем выключите pop, заполните четыре места и снова включите оба запроса с новым входом. Occupancy остаётся 4, выход получает прежнюю голову, память — новый хвост.
FIFO на четыре элемента. Изначально пуст; указатели чтения и записи равны 0. pop принимается, если FIFO непуст. push принимается при наличии места или при принятом pop на том же фронте.
Рисунок — учебная модель состояния после положительного фронтаПоложительный фронт — момент перехода такта из 0 в 1. Его следует отличать от уровня, означающего весь интервал CLK=1. Подробнее. При принятом pop output valid=1; если следующий фронт pop не принимает, регистрРегистр хранит многобитное состояние. Синхронные регистры в этом курсе записывают определённый вход на фронте тактового сигнала. Подробнее данных выхода сохраняется, но valid=0. Старые биты пустых слотов скрыты. «Сначала» инициализирует всё упражнение и не изображает сбросСброс возвращает состояние к заданному начальному значению. Нужно определить, синхронен он или асинхронен и имеет ли приоритет над другими управляющими сигналами. Подробнее памяти в RTL.
RTL с выходным регистром
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 не сравнивается.