nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Подправляем видеопроцессор QuatCore (2)

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

Ну и разберёмся со входным модулем:
//сначала у нас все адреса 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 reg DEmptySet = 1'b0
				/*, 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);

always @(posedge clk)
	DEmptySet <= Vsync? 1'b0 : (BufOut[XregWidth-1:0] < X)&(~SyncOut[0])&(~SyncOut[1])&(~FrontPorch)? 1'b1 : DEmptySet;

endmodule


Здесь тоже используется буфер FIFO (First In - First Out, "первый вошёл - первый вышел"), но чуть модифицированный, под названием FIFO_on_LE_for_GPU. Модификация состоит в том, что добавлены входы HSync и VSync (строчный и кадровый синхроимпульсы), которые "сбрасывают" соответствующие биты в текущем задании.

Добавление новых заданий в очередь (по командам ACQ или TRK) - вещь тривиальная, хотя из 16-битного слова данных берётся только младшие 11 бит и старшие 2. Но ещё в серединку поступает один бит из DestAddr, как раз отличающий ACQ (ACQuire, захват) от TRK (TRacKing, сопровождение). Старшие 2 бита определяют, какого из синхроимпульсов надо дождаться, но также отслеживает заказ отрезка до отрицательного числа (так бывает, когда пятно частично выходит из поля зрения, а в программе не проводится проверки диапазона). Лишний 11-й бит нужен, чтобы отследить пиксель, выходящий из поля зрения справа (т.е свыше 1023), что также возможно, когда пятно на самом краю.

Силами самого FIFO формируется сигнал SrcStallReq, если буфер уже забит, а мы пытаемся положить очередное задание. Тут всё хорошо.

Когда очередь пуста, формируется Disable = 1, который в нашем старом исполнении отключал АЦП, освобождая шину для SRAM. Сейчас, в общем-то, тоже, хотя теперь это необязательно, память обрела свою собственную шину.

Исполнение заданий - посложнее!

У нас "висит" текущее задание, и ничего не происходит, пока не выполнится одно из 4 условий:
- в задании было отрицательное число (start_neg)
- в задании было число больше количества пикселей в строке, и при этом мы уже дошли до конца строки (start_oflo),
- в задании было число от 0 до 1023 (при XregWidth = 10) и мы уже дошли до этого числа.
- выставлен параметр UseRowCount = 1, и у нас уже прошло RowCount строк.

При этом также проверяется, что очередь не пуста (т.к если она пуста, на выходе может содержаться любой мусор, причём довольно непредсказуемым образом попадать туда с шины данных - Undefined Behaviour во всей красе!).

Если одно из 4 условий выполнилось на непустой очереди, то формируется rdreq=1 (текущее задание "выталкивается" из буфера и на его место приходит следующее) и start=1. Последний сбрасывает наши "первый" и "второй" сумматоры, и в это же самое время подаётся wrreq=1 на QuatCoreGPUout, тем самым "защёлкивая" туда результаты работы по только что окончившемуся отрезку.

Далее, есть 1-битный регистр FrontPorch, который скорее всего надо было назвать BackPorch - вечно путаю, где что. Вроде бы народ привык изображать полезное изображение СВЕРХУ СЛЕВА, правее него идёт область строчных синхроимпульсов, а ниже - область кадровых. И вот на такой картинке то что левее синхроимпульса - это front porch ("передняя полочка"), а то что правее - back porch ("задняя полочка").



Надо бы переправить уже...


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

У нас был сигнал flush ("слив"), который как раз срабатывал на окончании этого FrontPorch. И этот сигнал мы оставим внутри данного модуля - он делает полезное дело, обнуляет счётчик координат. Благодаря этому у нас пиксели отсчитываются не от начала синхроимпульса, а от окончания "полочки", что куда как приятнее.

Далее, у нас есть счётчик по координате X, то бишь по горизонтали. Он обнуляется дважды, сначала по строчному синхроимпульсу, чтобы с его помощью сосчитать длительность "полочки", и потом уже по окончании полочки, чтобы уже работать с пикселями. Дойдя "до упора", он так и останавливается на максимальном значении (сейчас 1023), чтобы воспользоваться выходом cout. Выглядит логично.

И,наконец, счётчик по координате Y, по вертикали. Он на самом деле считает "вниз", и это было принципиально важно, чтобы наша хреновина вообще запустилась! Потому как мы ставили Y, "не дошедший до упора" и при этом опустошённую очередь заданий, как условие для возникновения прерывания UFLO ("опустошение очереди заданий GPU"). В начале работы очередь заданий, очевидно, пуста, и если бы счётчик стартовал "вверх", то мы в первый же такт и словили прерывание! А так он стартует в нуле, но это и есть финальное положение.


Пора внести изменения в эту идиллию!

Вывести сигнал odd - проще всего, мы просто возьмём младший бит из счётчика по вертикали:

wire [YregWidth-1:0] Y;		
assign odd = Y[0];
		
lpm_counter Ycounter (	.clock (clk),		
			.cnt_en (Hsync & (~Yfinished)),		
			.sset (Vsync),		
			.sload (1'b0),
			.data (),
			.Q (Y),
			.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;


Сразу же подумалось: нам нужна ещё одна команда для GPUout, которая конкретно ОБНУЛИТ бит чётности в начале кадра, чтобы заведомо у нас ничего не "прыгало" на одну строку вверх-вниз. Не сказать, что это самая страшная ошибка, ну ошибёмся в угле активного тангажа на 0,023° при допустимых по ТЗ 0,1° - вообще пофиг! Но лучше ввести.

Следующая задача: убрать из результатов работы команды HSync / VSync. Вообще не факт, что нам стоит это делать: могли бы уже в программе их обрабатывать, но почему бы и не сделать... Нужно лишь вместо одного выхода start, идущего и на сумматоры, и на GPUout, ввести в явном виде wrreq для GPUout:
assign wrreq = start & (~FrontPorch);



Вот так в итоге выглядит видеопроцессор "в сборе":


Он синтезируется в 602 ЛЭ, из которых львиная доля используется в буферах FIFO. Сам по себе он показывает предельную частоту 35 МГц, нормально.

Тестовая "схема", включающая в себя дурацкий распаковщик ч/б картинки, ядро QuatCore и видеопроцессор успешно отсинтезировалась в 1200 ЛЭ:



Тайминги соблюдены, аж 25,07 МГц :) Осталось посмотреть, оно вообще фурычит?
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

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

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

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

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

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

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

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

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

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

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

  • Обнаружение на новом GPU - первые 16 мс

    Закончилась симуляция. UFLO и OFLO ни разу не возникли, что не может не радовать. За это время мы дошли до строки 0x10F = 271. Поглядим дамп памяти:…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments