nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

QuatCore: с прерываниями пока всё

Попытаемся всё-таки довести до ума выдачу прерываний OFLO и UFLO.

Вот видеопроцессор "в сборе":


"Корень зла" заключается в модуле QuatCoreFullGPUinput, причём "ошибочно" может пойти не только UFLO (как мы наблюдали в прошлый раз), но и OFLO!


Вот его код:
//сначала у нас все адреса 0xxx_xxxx были отданы под OUT
//затем мы ужались до 00xx_xxxx, поскольку 01xx_xxxx отдали под SRAM 
//и было 000x_xxxx: OUT,
//       001x_xxxx: SIO
//но давайте ещё пару адресов подкинем для GPU!
//и будет
//0000_xxxx: OUT,
//0001_xxxx: SIO,
//0010_xxxx: ACQ (Acquire, захват)
//0011_xxxx: TRK (Tracking, сопровождение)

//содержание шины данных
//старшие 2 бита: режим синхронизации
//чтобы случайно затесавшиеся МАЛЕНЬКИЕ отрицательные значения не портили малину, надо так (укуренность, но что делать):
//00, 11 - запускаемся по завершении предыдущего отрезка,
//10 - запускаемся по кадровому синхроимпульсу,
//01 - запускаемся по строчному синхроимпульсу
//кстати, это позволяет заодно обнаружить отрицательное значение, так что 2 лишних бит нам не надо!

//младшие биты (от 8 до 10, пока громко думаем) - длина очередного отрезка

`include "math.v"
module QuatCoreFullGPUinput (	input clk, input Hsync, input Vsync, input [7:0] DestAddr, input DestStall, input [15:0] D,
				output [XregWidth-1:0] X, output start, output flush, output isSum, output SrcStallReq, output UFLO,
				output Disable, output WriteFromADC
				/*, output [1:0] DSyncOut, output [XregWidth:0] DBufOut*/);

parameter XregWidth = 10;
parameter ElemCount = 4;	//ставим буфер FIFO, чтобы 
parameter UseRowCount = 1'b1;
parameter RowCount = 720;	//остальное надо автоматом определить

localparam YregWidth = `CLOG2(RowCount);
localparam YinitVal = (1 << YregWidth) - RowCount;	//возможно, на единичку ошиблись, потом проверим
wire Yfinished;

wire isOurAddr = (~DestStall)&(DestAddr[7:5]==3'b001); //ACQ либо TRK
wire [XregWidth:0] BufOut;	//очередное задание на выполнение
//assign DBufOut = BufOut;
wire [1:0] SyncOut;				//режим синхронизации на данный момент
//assign DSyncOut = SyncOut;
wire rdreq;						//когда заканчиваем обработку очередного задания

	FIFO_on_LE_for_GPU buff (	.clk(clk),
					.D({D[15:14],DestAddr[4],D[XregWidth:0]}),	//старшие биты: режим синхронизации, младшие 12: коорд. X, до которой надо идти
					.rdreq(rdreq),
					.wrreq(isOurAddr),
					.Vsync(Vsync),
					.Hsync(Hsync),
					.Q({SyncOut,isSum,BufOut}),
					.wr_stall(SrcStallReq),	//QuatCore останавливается и ждёт, пока мы освободимся
					.rd_stall(UFLO),		//видеопроцессор всегда должен быть занят. Если ему нечего делать - это подозрительно!
					.empty (Disable)
							);
	defparam
		buff.DataWidth = XregWidth+4,	//1 бит добавляем для "граничных условий", ещё 2 на синхроимпульс и 1 на тип команды (Acq или Trk)
		buff.ElemCount = ElemCount;				//пока не можем сообразить, сколько их надо

wire XCout;	//счётчик по координате X досчитал до упора
//старт по сигналу с компаратора (но ещё и с учётом "переполнения")
wire start_neg = SyncOut[1]&SyncOut[0];	//если попало отрицательное число, выдаём незамедлительно!
wire start_oflo = (~SyncOut[1])&(~SyncOut[0])&(BufOut[XregWidth] & XCout);	//число положительное, но ООЧЕНЬ большое, больше нашей разрядности!
wire start_normal = (~SyncOut[1])&(~SyncOut[0])&(BufOut[XregWidth-1:0] == X);	//нормальное число, и совпало с номером пикселя

assign rdreq = start_neg | start_oflo | start_normal | (Yfinished & UseRowCount & (~SyncOut[1]));	//хоть что-нибудь из этого выполнится - и мы стартуем
//assign start = rdreq & (~UFLO); 
assign start = rdreq;
//хотим, чтобы flush запустился уже ПОСЛЕ прихода строчного синхроимпульса
//для этого нужен дополнительный регистр, запоминающий, что именно строчных синхроимпульсов мы и ждали
reg FrontPorch = 1'b0; //"признак" передней полочки перед началом строки
always @(posedge clk)
	//FrontPorch <= (SyncOut[0] & Hsync)? 1'b1 : start? 1'b0 : FrontPorch;
	FrontPorch <= (Hsync&SyncOut[0] | Vsync&SyncOut[1])? 1'b1 : start? 1'b0 : FrontPorch;
	//в момент прихода синхроимпульса, если его мы и ждали, защёлкивается, а когда отсчитали сколько хотели - сбрасывается
	
assign flush = start & FrontPorch;
assign WriteFromADC = ~ (SyncOut[0] | SyncOut[1] | FrontPorch);

lpm_counter Xcounter (	.clock (clk),		//не мудрствуя лукаво, считаем пиксели на строке, слева направо. Сбрасываемся только по синхроимпульсу
			.cnt_en (~XCout),		//(хотя для аналогового сигнала может захотеться ещё по окончанию обработки задания с с.и повторно сбросить)
			.sclr (Hsync | flush),		
			.sload (1'b0),
			.data (),
			.Q (X),
			.cout (XCout));			//в кои-то веки использовать не будем, вместо этого компаратор. Хотя для граничных случаев может и пригодиться?
	defparam
		Xcounter.lpm_direction = "UP",
		Xcounter.lpm_port_updown = "PORT_UNUSED",
		Xcounter.lpm_type = "LPM_COUNTER",
		Xcounter.lpm_width = XregWidth;

//счётчик строк нужен только если мы пытаемся упростить программу и прогнать дополнительные строки "вхолостую" (с выдачей сплошных нулей) чтобы все активные точки
//"автоматом" перешли в список обнаруженных точек
		
lpm_counter Ycounter (	.clock (clk),		
			.cnt_en (Hsync & (~Yfinished)),		
			.sset (Vsync),		
			.sload (1'b0),
			.data (),
			.Q (),
			.cout (Yfinished));			
	defparam
		Ycounter.lpm_direction = "UP",
		Ycounter.lpm_port_updown = "PORT_UNUSED",
		Ycounter.lpm_type = "LPM_COUNTER",
		Ycounter.lpm_width = YregWidth,
		Ycounter.lpm_svalue = YinitVal;

endmodule


Сейчас тут творится классический "сишный" Undefined Behaviour: опустошённый входной буфер "имеет полное право" защёлкивать в любой свой регистр значения с шины данных, раз уж внутри него всё равно данных нет, и он честно об этом сообщает сигналом empty.

Но вот видеопроцессор на эти "мусорные" значения, тем не менее, реагирует. Мы считали это нормальным явлением - "пущай себе работает, мы просто не будем использовать его результаты. А когда начнём очередной кадр обрабатывать, по строчному синхроимпульсу всё равно выходной буфер очистится!

Теперь, когда мы хотим использовать UFLO и OFLO как прерывания, такой подход, очевидно, не годится!

Для начала, сигнал start должен появляться только если буфер заданий не пустой:

assign rdreq = (~Disable)&(start_neg | start_oflo | start_normal | (Yfinished & UseRowCount & (~SyncOut[1])));	
assign start = rdreq;


Теперь мы, по крайней мере, не будем бояться ошибочного сигнала OFLO (переполнение буфера результатов работы), поскольку "из мусора" задания рождаться больше не будут. Если уж сработал OFLO - значит мы не успели обработать вполне себе корректные результаты.

Но UFLO в том виде, как он реализован сейчас, формироваться вообще никогда не будет! У нас была такая логика: UFLO формировался, если буфер пуст, и при этом МЫ ПЫТАЕМСЯ ИЗ НЕГО ЧТО-ТО СЧИТАТЬ, то есть подать rdreq (запрос на чтение). Но теперь, когда буфер пустой, мы этого запроса в принципе не подадим.

Поэтому UFLO нужно формировать как-то по-другому. Напомним: в начале работы входной буфер пуст, и это должно быть нормальной ситуацией: отключается АЦП, процессор может спокойно работать со статической памятью. То есть, формировать прерывания по пустому буферу, как таковому, не нужно!

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

Затем мы будем обрабатывать полезные строки. В наших задачах считается, что все строки так или иначе надо пройти. Даже если мы уже "догадываемся", где именно будут наши мишени, нужно ещё убедиться что в других местах ничего нет. Поэтому если на этом этапе видеопроцессор ВДРУГ останется без заданий, с пустым буфером - он ДОЛЖЕН ВЫЗВАТЬ UFLO!

А вот как только последняя полезная строка закончится - можно и расслабиться! То есть, с этого момента пустой входной буфер снова становится нормой.

Другими словами, нужно подсчитывать, на какой строке мы сидим, и в зависимости от этого решать - нужно ли ставить UFLO=1.

Счётчик строк у нас уже есть, мы его хотели несколько для другого применять - как раз-таки чтобы по истечении "полезных" строк все последующие задания "щёлкались" моментально. Ну, кстати, можно и оставить эту функциональность, почему бы и нет.

Страшно не хотелось бы делать компараторы, проверяющие что "номер строки больше нуля и меньше предельного значения" - это всё довольно громоздко и медленно в плане таймингов (т.е есть риск, что схема не сможет корректно работать на 25 МГц). Лучше бы обойтись одним-единственным cout (carry out), на котором появляется единичка, когда счётчик "доходит до упора".

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

Кроме того, нужно заставить его считать не "вверх", а "вниз". Принципиальная разница в том, что все счётчики инициализируются нулём. Поэтому, если он считает "вверх", то изначально cout=0, и нужно пройти весь интервал, чтобы установился cout=1. Если же счёт идёт "вниз", то сразу же при включении получим cout=1. Именно так мы и хотим, чтобы UFLO не возник ТУТ ЖЕ.

Далее, поскольку заданий нет, АЦП отключено, а значит, синхроимпульсы не идут, и этот счётчик не считает.

Подаём задание VSync - и АЦП включается. В какой-то момент приходит кадровый синхроимпульс VSync - и устанавливает наш счётчик, тем самым сбрасывая cout. Вот и началась нормальная работа. Строчные импульсы также поступают, вычитая из счётчика по единичке. Наконец, он доходит до нуля - и снова устанавливается cout, тем самым позволяя нам прекратить подавать новые задания. Но можем и подавать - никаких препятствий нет. Наконец, когда буфер заданий опустошается - отключается АЦП, и счётчик так и застревает в этом "нулевом" положении. Вроде всё логично.

Приведём исправленный код модуля:

//сначала у нас все адреса 0xxx_xxxx были отданы под OUT
//затем мы ужались до 00xx_xxxx, поскольку 01xx_xxxx отдали под SRAM 
//и было 000x_xxxx: OUT,
//       001x_xxxx: SIO
//но давайте ещё пару адресов подкинем для GPU!
//и будет
//0000_xxxx: OUT,
//0001_xxxx: SIO,
//0010_xxxx: ACQ (Acquire, захват)
//0011_xxxx: TRK (Tracking, сопровождение)

//содержание шины данных
//старшие 2 бита: режим синхронизации
//чтобы случайно затесавшиеся МАЛЕНЬКИЕ отрицательные значения не портили малину, надо так (укуренность, но что делать):
//00, 11 - запускаемся по завершении предыдущего отрезка,
//10 - запускаемся по кадровому синхроимпульсу,
//01 - запускаемся по строчному синхроимпульсу
//кстати, это позволяет заодно обнаружить отрицательное значение, так что 2 лишних бит нам не надо!

//младшие биты (от 8 до 10, пока громко думаем) - длина очередного отрезка

`include "math.v"
module QuatCoreFullGPUinput (	input clk, input Hsync, input Vsync, input [7:0] DestAddr, input DestStall, input [15:0] D,
				output [XregWidth-1:0] X, output start, output flush, output isSum, output SrcStallReq, output UFLO,
				output Disable, output WriteFromADC
				/*, output [1:0] DSyncOut, output [XregWidth:0] DBufOut*/);

parameter XregWidth = 10;
parameter ElemCount = 4;	//ставим буфер FIFO, чтобы 
parameter UseRowCount = 1'b1;
parameter RowCount = 720;	//остальное надо автоматом определить

localparam YregWidth = `CLOG2(RowCount);
//localparam YinitVal = (1 << YregWidth) - RowCount;	//возможно, на единичку ошиблись, потом проверим
localparam YinitVal = RowCount - 1;
wire Yfinished;

wire isOurAddr = (~DestStall)&(DestAddr[7:5]==3'b001); //ACQ либо TRK
wire [XregWidth:0] BufOut;	//очередное задание на выполнение
//assign DBufOut = BufOut;
wire [1:0] SyncOut;				//режим синхронизации на данный момент
//assign DSyncOut = SyncOut;
wire rdreq;						//когда заканчиваем обработку очередного задания

	FIFO_on_LE_for_GPU buff (.clk(clk),
				.D({D[15:14],DestAddr[4],D[XregWidth:0]}),	//старшие биты: режим синхронизации, младшие 12: коорд. X, до которой надо идти
				.rdreq(rdreq),
				.wrreq(isOurAddr),
				.Vsync(Vsync),
				.Hsync(Hsync),
				.Q({SyncOut,isSum,BufOut}),
				.wr_stall(SrcStallReq),	//QuatCore останавливается и ждёт, пока мы освободимся
				.rd_stall(),		
				.empty (Disable)
				);
	defparam
		buff.DataWidth = XregWidth+4,	//1 бит добавляем для "граничных условий", ещё 2 на синхроимпульс и 1 на тип команды (Acq или Trk)
		buff.ElemCount = ElemCount;				//пока не можем сообразить, сколько их надо

wire XCout;	//счётчик по координате X досчитал до упора
//старт по сигналу с компаратора (но ещё и с учётом "переполнения")
wire start_neg = SyncOut[1]&SyncOut[0];	//если попало отрицательное число, выдаём незамедлительно!
wire start_oflo = (~SyncOut[1])&(~SyncOut[0])&(BufOut[XregWidth] & XCout);	//число положительное, но ООЧЕНЬ большое, больше нашей разрядности!
wire start_normal = (~SyncOut[1])&(~SyncOut[0])&(BufOut[XregWidth-1:0] == X);	//нормальное число, и совпало с номером пикселя

assign rdreq = (~Disable)&(start_neg | start_oflo | start_normal | (Yfinished & UseRowCount & (~SyncOut[1])));	//хоть что-нибудь из этого выполнится - и мы стартуем
//assign start = rdreq & (~UFLO); 
assign start = rdreq;
//хотим, чтобы flush запустился уже ПОСЛЕ прихода строчного синхроимпульса
//для этого нужен дополнительный регистр, запоминающий, что именно строчных синхроимпульсов мы и ждали
reg FrontPorch = 1'b0; //"признак" передней полочки перед началом строки
always @(posedge clk)
	//FrontPorch <= (SyncOut[0] & Hsync)? 1'b1 : start? 1'b0 : FrontPorch;
	FrontPorch <= (Hsync&SyncOut[0] | Vsync&SyncOut[1])? 1'b1 : start? 1'b0 : FrontPorch;
	//в момент прихода синхроимпульса, если его мы и ждали, защёлкивается, а когда отсчитали сколько хотели - сбрасывается
	
assign flush = start & FrontPorch;
assign WriteFromADC = ~ (SyncOut[0] | SyncOut[1] | FrontPorch);

lpm_counter Xcounter (	.clock (clk),		//не мудрствуя лукаво, считаем пиксели на строке, слева направо. Сбрасываемся только по синхроимпульсу
			.cnt_en (~XCout),		//(хотя для аналогового сигнала может захотеться ещё по окончанию обработки задания с с.и повторно сбросить)
			.sclr (Hsync | flush),		
			.sload (1'b0),
			.data (),
			.Q (X),
			.cout (XCout));			//в кои-то веки использовать не будем, вместо этого компаратор. Хотя для граничных случаев может и пригодиться?
	defparam
		Xcounter.lpm_direction = "UP",
		Xcounter.lpm_port_updown = "PORT_UNUSED",
		Xcounter.lpm_type = "LPM_COUNTER",
		Xcounter.lpm_width = XregWidth;

//счётчик строк нужен только если мы пытаемся упростить программу и прогнать дополнительные строки "вхолостую" (с выдачей сплошных нулей) чтобы все активные точки
//"автоматом" перешли в список обнаруженных точек
		
lpm_counter Ycounter (	.clock (clk),		
			.cnt_en (Hsync & (~Yfinished)),		
			.sset (Vsync),		
			.sload (1'b0),
			.data (),
			.Q (),
			.cout (Yfinished));			
	defparam
		Ycounter.lpm_direction = "DOWN",
		Ycounter.lpm_port_updown = "PORT_UNUSED",
		Ycounter.lpm_type = "LPM_COUNTER",
		Ycounter.lpm_width = YregWidth,
		Ycounter.lpm_svalue = YinitVal;

assign UFLO = Disable & (~Yfinished);

endmodule


Наконец, отсинтезировал всё это дело - проект целиком разросся до 1453 ЛЭ (после Place&Route).

Включаю - "Ошибка: нет сигнала". Традиционно подумал, что "что-то сломал", половину "закоротил", снова синтезирую - то же самое, "нет сигнала". Наконец, догадался включить прошивку из ПЗУ (она более старая, вообще без прерываний), получить картинку с неё - и ТИШИНА. Снял крышку, постучал пальцами по плате - ЗАРАБОТАЛО.

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

Итого: прерывание "нет сигнала" проверили, нормальную работу проверили. Теперь бы убедиться, что прерывания UFLO и OFLO всё-таки сработают, когда это надо.

Заставим программу посылать 8 заданий на обработку на каждой строке, таких, которые исполнятся сразу же:

			;начинаются полезные строки
			Acc	UsefulRows				

			ERL		0
			ERH		0						
				
	@@UsefulLoop:	k	7
	@@Useless:	ACQ	-1
			kLOOP 	@@Useless
			ACQ	HSync
			NOP	GPUH
			SUB	1
			JGE	@@UsefulLoop

Сейчас размер выходного буфера 6 элементов, должен переполниться!



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

localparam ParanoidBusyDiv = (1 >> BusyWidth) - 1;


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

Разумеется, ничего не изменилось. Взял сейчас - и ещё удвоил это время - и всё равно не помогло!

Возможно, я не совсем верно понимаю проблему. Тем не менее, когда сразу за командой 0x101 (очистить экран) я поставил безобидную команду 0x106 (установка режима ввода данных - курсор двигается вправо, уже введённые символы влево не двигаются) - сделалось в кои-то веки правильно:


Как будто теперь очистка экрана "сгрызла" эту команду, чего мы не заметили (всё стояло ровно так и при инициализации), и текст уже отобразился нормально. В общем, хотелось бы разобраться - в кои-то веки присоединить вывод R/W и не только посылать команды и текст, но и запрашивать статус - и посмотреть, как долго он реально "занят". Но не сегодня, и не в ближайший месяц - я очень хочу, чтобы до нового года у меня прибор хоть в каком-то виде заработал, в смысле что выдал показания дальности и углов...

Ладно, теперь проверим UFLO. Это сделать ещё проще - мы отправим ОДНУ команду на обработку строки, а получить попытаемся сразу два результата, каковых там не будет. Как-то так:

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


Компилируем, синтезируем, запускаем:



ДА!! Все 4 варианта (нормальная работа, прерывание "нет сигнала", прерывание "исчерпание заданий GPU" и прерывание "переполнение результатов GPU") работают правильно. Напрягает плохой контакт где-то на плате, и странное поведение ЖК-экранчика, но жить можно.

На очереди: вспомнить работу с экранным меню камеры и настроить экспозицию, чтобы пятна не сливались в одно большое пятно.
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Нахождение двух самых отдалённых точек

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

  • Слишком общительный счётчик

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

  • Балансируем конвейер QuatCore

    В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в…

  • Огари разговаривают

    Сегодня по пути на работу встретил огарей прямо в Лосином острове, на берегу Яузы. Эти были на удивление бесстрашны, занимались своими делами, не…

  • Ковыряемся с сантехникой

    Наконец-то закрыл сколько-нибудь пристойно трубы, подводящие к смесителю, в квартире в Москве: А в воскресенье побывал на даче, там очередная…

  • Мартовское велосипедное

    Продолжаю кататься на работу и с работы на велосипеде, а также в РКК Энергию и на дачу. Хотя на две недели случился перерыв, очередная поломка,…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments