nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

FIFO на логических элементах

Буфер вида First In - First Out (FIFO, "первый вошёл - первый вышел") - штука чрезвычайно полезная, мало что без неё обходится.

Именно с их помощью я планирую соединить между собой процессор QuatCore и "видеопроцессор" (если данное поделие из 106 ЛЭ можно так назвать), чтобы "сгладить" нагрузку на шину: иногда она будет пустовать в течение сотен (вплоть до тысячи) тактов, а иногда требовалось бы на каждом такте пересылать данные в обе стороны. Даже если сама шина это вытянет, очень тяжело правильно написать программу, которая могла бы "озадачивать" видеопроцессор совсем напрямую: попытка организовать даже простейший цикл сразу создаёт "прорехи" в 3-4 такта.

Разумеется, есть уже готовые модули (IP-блоки) scfifo (Single Clock FIFO) и dcfifo (Dual Clock FIFO), которые предоставлялись Альтерой (теперь: Intel'ом) и которые вроде как должны быть предельно оптимизированы под родное "железо". Но у меня пока что сложилось впечатление, что они создавались специально под использование блоков внутренней памяти (БВП, он же EAB, Embedded Array Block). Проблема в излишней "дискретности" этой памяти - уж если её применяешь в качестве FIFO, то сразу резервируется целый блок в 512 байт, который больше никуда пойти не сможет. Кажется, что эти блоки памяти у меня даже в бОльшем дефиците (их всего 10 по 512 байт), чем ЛЭ (2880), поэтому хотелось бы их пока сберечь.

Есть галочка "использовать взамен логические элементы", она, конечно срабатывает, но реализована "лишь бы работало". Мне так показалось, что структура блока остаётся той же самой - посерёдке двухпортовая память и два "указателя" на неё, которые бегают по кругу - просто теперь и эта память реализована на логических элементах.

Как результат, модуль FIFO на 4х10 бит (т.е сохраняется до четырёх 10-битных "слов") занимает 74 ЛЭ. Он же, на 8х10 бит - уже 144 ЛЭ, а уж 16х10 бит - 275 ЛЭ. Такими темпами два буфера FIFO (на вход и на выход "видеопроцессора") элементарно будут занимать больше ресурсов, чем сам видеопроцессор, да и процессор QuatCore ВМЕСТЕ ВЗЯТЫЕ!!!

Я считаю, что можно сделать получше, так что на FIFO NxM бит будет тратиться Nx(M+2)+1 ЛЭ, т.е в нашей ситуации: 49 ЛЭ, 96 ЛЭ и 192 ЛЭ соответственно...



Идея проста до безобразия: вместо некоего аналога RAM (ОЗУ), где данные "сидят на своих местах", и можно запросить произвольную ячейку и записать в произвольную ячейку, нужно всё-таки сделать сдвиговый регистр. К выходу FIFO всегда будет подключён самый последний регистр, и при каждом извлечении будет производиться сдвиг.

Но при этом, загрузить входные данные можно будет в произвольный регистр, благо соединить все входы параллельной загрузки - это "бесплатно", в отличие от соединения всех выходов, для чего нужен мультиплексор. (Внутри ПЛИС нет двунаправленных шин и выходов с третьим, Z-состоянием!)

Прелесть в том, что четырёх входов логических элементов (не считая clk) вполне хватает, чтобы реализовать такую штуковину:
- вход ena (enable) "разрешает работу" регистра: пока ena=0, он хранит текущее значение, в противном случае заносит значение с одного из входов,
- значение PREV с предыдущего регистра, для обеспечения сдвига,
- значение D для параллельной загрузки, чтобы, к примеру, в пустом FIFO новое значение сразу же "защёлкнулось" на выходном регистре, а в заполненном почти до конца - оно же защёлкивалось на самом первом регистре.
- вход "выбора режима" LOAD - либо загрузить из предыдущего регистра, либо из входа модуля.

Так у нас используется MxN логических элементов. Они заведомо нужны, поскольку каждый логический элемент хранит всего 1 бит информации, тут уж никуда не денешься.

Но ещё нужен счётчик, который будет следить, сколько регистров сейчас заняты, чтобы формировать сигналы empty и full, и защёлкивать входные данные в подходящий регистр. Думаю, что лучше всего здесь подойдёт унарный счёт, он же One-hot, то есть в дополнение к M бит данных в каждом "слоте" FIFO, будет ещё один бит, указывающий, занят ли этот слот в данный момент. Он хорошо подойдёт, поскольку позволяет управлять входами Load напрямую, а к обычному бинарному счётчику понадобился бы ещё дешифратор. А зачем, если One-hot - это и дешифратор, и счётчик в одном лице :)

Почему-то у меня быстрее всего такое дело получается нарисовать на бумаге, см. "картинку для привлечения внимания".

И сразу же "транслируем" в verilog. Для начала, чтобы "проверить концепцию", реализуем конкретно FIFO на 4 позиции:
module FIFO_on_LE (input clk, input [DataWidth-1:0] D, input rdreq, input wrreq, output empty, output nfull, output [DataWidth-1:0] Q);

parameter DataWidth = 10;

reg [DataWidth-1:0] R1;
reg [DataWidth-1:0] R2;
reg [DataWidth-1:0] R3;
reg [DataWidth-1:0] R4; //Registers 1-2-3-4
reg U1 = 1'b1, U2 = 1'b1, U3 = 1'b1, U4 = 1'b1;				//Used 1-2-3-4

assign empty = U4;
assign nfull = U1;
assign Q = R4;

wire ena1 = rdreq | (wrreq & U1);
wire ena2 = rdreq | (wrreq & U2);
wire ena3 = rdreq | (wrreq & U3);
wire ena4 = rdreq | (wrreq & U4);
wire Uena = (rdreq & (~empty)) ^ (wrreq & nfull);

always @(posedge clk) begin

	if	(ena1)
		R1 <= U1? D : R1;
	if	(ena2)
		R2 <= U2? D : R1;
	if 	(ena3)
		R3 <= U3?	D : R2;
	if	(ena4)
		R4 <= U4?	D : R3;
	
	if (Uena) begin
		U1 <= wrreq? U2 : 1'b1;
		U2 <= wrreq? U3 : U1;
		U3 <= wrreq? U4 : U2;
		U4 <= wrreq? 1'b0 : U3;
	end;

end

endmodule


Нижний ряд 1-битных регистров - это наш "унарный счётчик". Он инициализируется всеми единицами. Единица означает, что данная позиция пуста и "разрешает" загрузку туда нового значения. При этом empty=1 (индикация, что буфер пуст) и nfull=1 (индикация что он не заполнен до отказа).

Пока ENA=0, регистры для данных (верхний ряд) "ничего не делают", то есть продолжают хранить данные, которые в них "защёлкнули" ранее. Пока rdreq=0 (запрос на чтение данных) и wrreq=0 (запрос на запись данных), ровно так и происходит. Вообще все регистры неактивны, состояние сохраняется неопределённо долго.

Давайте сначала разберёмся с "унарным счётом". Чтобы активизировать эти регистры, нужно подать либо wrreq=1, при этом буфер не должен быть заполнен до отказа. Либо нужно подать rdreq=1, при этом буфер не должен быть пустым. Если мы запросим одновременно и чтение, и запись, причём и то, и другое "разрешено", то счётчики также сохранят предыдущее состояние, поскольку количество значений в буфере не изменится. Сейчас логика немножко слишком "параноидальная" - она не понимает, что в заполненный до отказа буфер вполне можно занести ещё одно значение, если тут же произвести и чтение. Позже можно будет это доработать.

Когда мы записываем новое значение в буфер, у нас "унарный счётчик" производит сдвиг влево, с занесением нуля в крайний правый регистр. Если изначально было 1111 (все свободны), то после первой записи будет 1110 (один занят), затем 1100, затем 1000 и, наконец, 0000, при этом nfull=0, что означает - буфер "забит до отказа".

Если же мы читаем значение из буфера, то "унарный счётчик" производит сдвиг вправо, с занесением единицы в крайний левый регистр, что даёт последовательность 0000 - 1000 - 1100 - 1110 - 1111.

Наконец, посмотрим на работу регистров данных (верхний ряд). При подаче wrreq=1, активируются только те регистры, которые отмечены как "свободные". На самом деле, нам нужно заносить данные только в крайний правый из свободных регистров, но это неважно - при empty=1 состояние выхода может быть произвольным, оно считается "недействительным". Занятые регистры продолжают хранить свои данные.

При подаче rdreq=1, активируются вообще все регистры, причём для занятых регистров LOAD=0, поэтому производится сдвиг. А для свободных регистров LOAD=1, то есть производится защёлкивание данных из входа D, даже если "защёлкивать нечего". Опять же, нам не страшно - если регистр помечен как свободный, и остаётся таковым, его данные "недействительны" - там может быть любой мусор.

К сожалению, если подать одновременно rdreq=1 и wrreq=1, то модуль сработает чуть-чуть неправильно. Например, если у нас уже хранилось одно значение, т.е унарный счётчик показывал 1110, и мы пытаемся одновременно прочитать значение и занести новое, то новое значение защёлкнется во все регистры, кроме последнего, а в последний регистр сдвинется недействительное значение из предыдущего. Так что и здесь нужно логику несколько усложнить.

Тем не менее, неплохое начало. Данная штуковина успешно синтезируется ровно в 49 ЛЭ - КАК В АПТЕКЕ! 10x4 = 40 ЛЭ - непосредственно на хранение данных. 4 ЛЭ (по числу позиций ) - для управления входом ENA, ещё 4 ЛЭ - унарный счётчик, и один дополнительный ЛЭ - формирование ENA для унарного счётчика.

Теперь надо написать "параметризованный" модуль, чтобы можно было произвольно задавать количество "позиций" в буфере, и подправить управляющую логику. Кажется, что уложиться в тот же размер вполне реально.


Poll #2102404 Представление модулей

Что понятнее: код на верилоге или принципиальная схема?

Код
2(16.7%)
Схема
6(50.0%)
Всё понятно
2(16.7%)
Ничего не понятно
2(16.7%)
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Очередная несуразность в единицах измерения

    Когда-то я написал программу PhysUnitCalc - калькулятор, умеющий работать с размерностями. Мне казалось, что я наступил уже на все грабли, которые…

  • Big Data, чтоб их... (3)

    "В предыдущих сериях": мой прибор выдаёт 6 значений: 3 координаты и 3 угла, т.е все 6 степеней свободы твёрдого тела. Причём ошибки измерения этих 6…

  • Big Data, чтоб их... (2)

    Вчера получил упоротое уравнение, чтобы найти, с какими весами нужно брать результаты измерений, чтобы получить наименее шумную и при этом…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 9 comments