nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Слишком общительный FIFO ушёл в себя...

8-го апреля я хотел запустить "супрематический алгоритм" обнаружения точек в железе (на своём макете), но для этого нужно было увеличить размеры буферов FIFO на вход и выход, чтобы всё влезло, а когда я это сделал - опять перестал "проходить" по таймингам. Мне квартус выдал Critical Warning, что работоспособность этой "схемы" на 25 МГц он не гарантирует.

Чтобы это исправить, пришлось довольно основательно "перебрать" QuatCore - "отбалансировать ему конвейер". Этого самую малость не хватило, и наблюдая дальше failed paths (слишком длинные комбинаторные пути, по которым переходные процессы и не успевают затихнуть к фронту тактовой частоты) увидел, что очень "жадное" исполнение входного буфера FIFO привело к "комбинаторной связи" между потрохами видеопроцессора и основным процессором, что и было, вероятно, главной проблемой.

Балансировки конвейера я очень боялся, был уверен, что чего-то поломаю, поэтому устроил ему жуткие испытания в лице нового алгоритма захвата на ближней дистанции. Как результат, на месяц "отвлёкся" от обнаружения точек, и вообще ушёл в "симуляцию" (the matrix has you), и да, именно с матрицами я и возился до упора :)

А вот вылечивание FIFO от излишней "болтливости" казалось плёвым делом, я даже проверять не стал, и почти что забыл об этом изменении.

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

После танцев с осциллографом, проследив работу АЦП и выведя на отдельный шлейф "отладочные сигналы" с выходов нашего детектора и селектора синхроимпульсов, я понял: они работают нормально. Проверку чётности я тоже отключил от греха подальше, мало ли. Но мы всё равно висли. Только тогда я вспомнил об этом FIFO, поставил только что введённый там параметр AllowRWwhenFull = 1'b1 (т.е вернул "как было"), опять получил Critical Warning и предельную частоту 23,36 МГц, плюнул на это и прошил всё равно - и получил-таки изображение!


Зависли мы наглухо на том месте программы, где хотели получить аж 13 кадровых синхроимпульсов подряд, просто чтобы задержать получение изображения. Это нужно было для отображения экранного меню камеры (OSD, On-Screen Display). В момент прихода команды с компьютера начинал генерироваться ШИМ-сигнал, имитирующий нажатие маленького джойстика от камеры, но камере нужно время, чтобы отследить нажатие кнопки джойстика и её отпускание (если кнопку держать 5 секунд, происходит переключение формата аналогового сигнала), а потом ещё сформировать изображение на экране, в общем, мы методом проб и ошибок сообразили, что всё это работает неторопливо...

Чтобы найти, где зависаем, добавил немножко "отладочной информации":

	@@GetPicture:	OUT	'g'	;get picture - смотрим, где висим
	
			j	12
	@@WaitMenu:	ACQ	VSync	;дождаться кадрового синхроимпульса
			jLOOP	@@WaitMenu
						
			OUT	't'	;Top rows


В общем, символ "g" (get image) пришёл, а вот символ "t" (top rows) - уже нет. На этих 3 строках мы и застряли.

И судя по всему, дело в одной строке верилога, в модуле FIFO_on_LE_GPU (буфер "первым вошёл-первым вышел" на логических элементах, для видеопроцессора):

assign wr_stall = (~nfull) & wrreq & (~(rdreq & AllowRWwhenFull));


AllowRWwhenFull - это параметр. Если здесь единичка, то всё будет работать, как и раньше. Мы "набьём" буфер заданиями "VSync", и когда буфер станет полным (nFull = 0), возникнет wr_stall, который заставит процессор остановиться и подождать, чтобы не получилось, что команды уйдут "в никуда". Когда видеопроцессор выполнит текущее задание, он запросит следующее, с помощью rd_req = 1, и в этот самый момент выйдет wr_stall = 0 - и процессор наконец-то сдвинется с мёртвой точки.

Теперь сообразим, что происходит, когда AllowRWwhenFull = 0. Тогда выражение упрощается до:
assign wr_stall = (~nfull) & wrreq;


Начало такое же: мы набиваем буфер до упора, после чего останавливаемся.

Вспомним "принципиальную схему" такого буфера (см. FIFO на ЛЭ: работа над ошибками)


Здесь R1..R4 - регистры с данными. У каждого два входа для загрузки: D и Prev. Вход Load выбирает, откуда загружать (Load=1: из D, Load=0: из Prev), вход ena (enable) разрешает загрузку, иначе регистр продолжает хранить ранее занесённые данные.

U1..U4 - элементы унарного счётчика. Значение 0 на выходе каждого означает: данная ячейка занята, значение 1: пуста. У них также два входа для загрузки, Prev и Next, вход разрешения ena, и вход выбора WR. Если WR=1 и работа разрешена, загружается из Next, иначе - из Prev.

Нас интересует случай полного заполнения, когда на выходах U1..U4 нули. В это же самое время rdreq=1 (наконец-то видеопроцессор выполнил одно задание и запрашивает следующее) и wrreq=1 (т.к у процессора на шине данных лежит новое задание, которое до сих пор "некуда" положить).

Работа унарного счётчика будет разрешена, т.к rdreq=1 при непустом буфере (на первый вход XOR поступит единица), а вот wrreq=1 при полном (на второй вход поступит ноль). Вроде логично: мы как раз должны указать, что появилось вакантное местечко!

Но вот только вход "WR" оказывается приоритетным, поэтому счётчик так и остаётся заполненным.

Проще говоря, значение уже было занесено куда надо (как и раньше), вот только процессору мы об этом не сказали!

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

Чтобы исправить это, надо на первый бит унарного счётчика подавать не wrreq, а wrreq & (~nfull), тем самым заставляя счётчик двигаться "вниз" во время одновременного прихода wrreq=1 и rdreq=1. Но только при параметре AllowRWwhenFull = 0, в противном случае оставить просто wrreq.

Нужное нам выражение: wrreq & (AllowRWwhenFull | (~nfull)).

Вроде бы исправил, запускаю - и получаю ТРИ БУКВЫ. Приличные: "gtu". Это моя отладка. "g" - get_picture, это самое начало. "t" - top rows, значит, мы выждали 13 кадровых синхроимпульсов и теперь готовимся пропустить верхние ("бесполезные") строки. Наконец, "u" - useful rows, это значит верхние тоже "обработали" и сейчас начнём непосредственно изображение записывать.

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

что-то мы недоглядели...

Вот код, на котором приходит песец:
@@UsefulLoop:	ACQ	WholeRow
		ACQ	HSync
		NOP	GPUH
		;NOP	GPUH	;это ЛИШНЯЯ, проверить UFLO, когда мы застрянем в ожидании команды, а видеопроцессор останется без работы.
		SUB	1
		JGE	@@UsefulLoop


То есть, даём задание "дождаться строчного синхроимпульса" (HSync), затем "прочитать" всю строку, а затем прочитать результаты по этой строке.

Ладно, и сюда расставим "отладочную информацию":

@@UsefulLoop:	ACQ	WholeRow
		OUT	'a'
		ACQ	HSync
		OUT	'b'
		NOP	GPUH
		OUT	'c'
		;NOP	GPUH	;это ЛИШНЯЯ, проверить UFLO, когда мы застрянем в ожидании команды, а видеопроцессор останется без работы.
		SUB	1
		JGE	@@UsefulLoop


и в этот раз получаем сообщение "gtuab", то есть задания мы успешно отправили, но уже НА ПЕРВОЙ ИТЕРАЦИИ цикла так и застряли навечно на получении результатов!

Задания VSync и HSync не дают результатов, в прямом смысле этого слова. После их выполнения, новых элементов в выходной буфер "результатов работы" не поступает. Результаты должно было дать задание ACQ WholeRow. Очевидно, оно заносилось с самый конец буфера (т.к процессору забить его до упора гораздо проще, чем видеопроцессору выполнять по заданию каждые 53 мкс), и с нашей новой логикой то ли так и не попало, то ли попало, но не было учтено, либо попало, а затем было затёрто каким-то мусором...


Продолжение следует... Казалось бы, маленькая правка - а какой эффект.
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Тестируем atan1 на QuatCore

    Пора уже перебираться на "железо" потихоньку. Решил начать с самого первого алгоритма, поскольку он уже был написан на ассемблере. В программу внёс…

  • Формулы приведения, что б их... (и atan на ТРЁХ умножениях)

    Формулу арктангенса на 4 умножениях ещё немножко оптимизировал с помощью алгоритма Ремеза: Ошибка уменьшилась с 4,9 до 4,65 угловой секунды, и…

  • Алгоритм Ремеза в экселе

    Вот и до него руки дошли, причина станет ясна в следующем посте. Изучать чужие библиотеки было лениво (в том же BOOSTе сам чёрт ногу сломит), писать…

  • atan на ЧЕТЫРЁХ умножениях

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

  • Ай да Пафнутий Львович!

    Решил ещё немного поковыряться со своим арктангенсом. Хотел применить алгоритм Ремеза, но начал с узлов Чебышёва. И для начала со своего "линейного…

  • atan(y/x) на двух умножениях!

    Чего-то никак меня не отпустит эта тема, всё кажется, что есть очень простой и эффективный метод, надо только его найти! Сейчас вот такое…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 7 comments