nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Записываем изображение в статическую память

У нас на макете уже заканчивались свободные выводы, поэтому пришлось сделать общую 8-битную шину как для быстродействующего АЦП, так и для статической памяти. Теперь осталось сообразить, как со всем этим хозяйством поступить...


У АЦП AD9283BRS-100, которую мы используем в макете, есть вывод PWRDOWN. Если на него подаёшь лог. "1" (то есть, +3,3 вольта), АЦП "уходит в спячку" и освобождает шину, т.е её выходы переходят в Z-состояние с высоким выходным сопротивлением.

У нас в проекте этот вывод называется FastAdcDisable, и пока что мы его "ручками" либо коммутировали на 0, чтобы понаблюдать синхроимпульсы, либо на 1, когда отлаживали работу со статической памятью, чтобы "не мешал".

Статическая память управляется сигналами CE0 (Chip Enable), CE1 и WE (Write Enable). У меня стоит 2 микросхемы по 512 килобайт. На них подаются одинаковые адреса, а самый младший бит "адресного регистра внешней памяти" управляет как раз CE0 и CE1:

assign RAM_CE0 = ER[0];
assign RAM_CE1 = ~ER[0];
assign RAM_RW = (~WriteMem) | clk;

assign RAM_data = WriteMem? D[7:0] : 8'bzzzz_zzzz;


Когда CE=1, соответствующая микросхема отключается - её выходы переключаются в Z-состояние, и сигнал на запись она тоже не воспринимает. Когда CE=0, но WE=1, микросхема работает на чтение - она выдаёт на шину байт, лежащий по запрошенному адресу.

Наконец, когда CE=0 и WE=0, микросхема работает на запись - в этот момент шину занимает ПЛИС, выдаёт туда байт, а микросхема памяти этот байт записывает.

Что мы хотим:
1. Исходно АЦП отключён, статическая память соответственно включена на чтение адреса ER (External memory Register), скорее всего нулевой адрес.
2. QuatCore устанавливает ER в ноль - готовится к записи изображения (это у нас уже реализовано)
3. QuatCore шлёт на видеопроцессор команду ACQ VSync - "ожидание кадрового синхроимпульса". Это уже есть, но видеопроцессор должен сам включить АЦП, при этом отключая чтение из статической памяти.
4. Вместе с включением АЦП запускаются детектор и селектор синхроимпульсов, а статическая память пока неактивна, мы ждём прихода кадрового синхроимпульса.
5. QuatCore посылает пачку команд ACQ HSync - "ожидание строчного синхроимпульса", это пока "невидимые" строки, лежащие выше "экрана". Видеопроцессор их честно прочитывает, а статическая память всё ещё неактивна.
6. QuatCore посылает команду ACQ WholeRow - "найти яркую точку на всей строке". Любая команда, не содержащая "ожидание синхроимпульсов", должна активировать статическую память на запись с автоматическим инкрементом адреса. Тем самым, та область изображения, которую мы считаем полезной, будет записываться в память, фактически в обход ПЛИС, в смысле что АЦП и память соединены напрямую! Разве что ПЛИС выдаёт правильные адреса и непрерывно переключается между двумя микросхемами с помощью CE0 и CE1.
7. Прочитав весь кадр, QuatCore "отстаёт" от видеопроцессора - тот перестаёт получать задания на обработку. Столкнувшись с опустошением буфера заданий, он должен отключить АЦП и вернуть управление шиной статической памяти.
8. Получив запрос по UART на получение изображения, QuatCore начинает извлекать изображение из статической памяти, на черепашьей скорости в 1 байт за 270 тактов, поскольку именно с такой скоростью данные будут передаваться по UART, на максимальной скорости 921600 бод.

Мы не рассматриваем ситуацию, когда мы должны продолжить как не в чём не бывало обрабатывать следующие кадры, и "в фоновом режиме" передавать на компьютер один записанный кадр. Это вполне реально - надо лишь поставить довольно большой входной буфер для UART, чтобы ему "было чем заняться" всю полезную часть кадра, а заполнять этот буфер из статической памяти во время обратного хода, когда свободен и сам QuatCore, и внешняя шина. Нужно только добавить новый "флажок" в видеопроцессор, REC ("включение записи"), чтобы он мог один кадр обработать с записью в память, а на последующих он бы её не трогал. Место есть и под него. Из 16 бит "аргумента" команды ACQ мы пока применили 11 бит для координаты и 2 бита для режима синхронизации, есть ещё 3 свободных! Но я считаю, что можно пока без всего этого обойтись...

Сначала подкорректируем модуль управления статической памятью, QuatCoreFastSRAM

Вот его код:
//обращение к внешней памяти
//чуть потеснили IOselector в плане адресов.
//наши DestAddr: 01xx_xxxx
//наши SrcAddr:  1001_1xxx

//DestAddr:
//0100_xxxx - задать младшие 16 бит адреса, ERL (External memory Register Low)
//0101_xxxx - задать старшие 16 бит адреса, ERH (External memory Register High)
//011x_xxxx - записать в память и сделать инкремент, [ER++]

//SrcAddr:
//1001_1xxx - чтение из памяти и инкремент, [ER++]
module QuatCoreFastSRAM (input clk, input [7:0] DestAddr, input [7:0] SrcAddr, input DestStall, input SrcStall, input SrcDiscard, input [15:0] D,
			output [18:0] RAMaddr, output RAM_CE0, output RAM_CE1, output RAM_RW,
			inout [7:0] RAM_data);
						
wire IsOurDest = (~DestStall)&(~DestAddr[7])&DestAddr[6];
wire IsOurSrc = (~SrcStall)&(~SrcDiscard)&(SrcAddr[7:3] == 5'b1001_1);

wire LoadERL = IsOurDest & (~DestAddr[5]) & (~DestAddr[4]);
wire LoadERH = IsOurDest & (~DestAddr[5]) & DestAddr[4];
wire WriteMem = IsOurDest & DestAddr[5];

wire DoIncrement = WriteMem | IsOurSrc;

wire [19:0] ER; //External memory Register
wire TC; //Terminal Count

lpm_counter ERL (	.clock (clk),
			.cnt_en (DoIncrement),
			.sload (LoadERL),
			.data (D),
			.Q (ER[15:0]),
			.cout (TC));
defparam
	ERL.lpm_direction = "UP",
	ERL.lpm_port_updown = "PORT_UNUSED",
	ERL.lpm_type = "LPM_COUNTER",
	ERL.lpm_width = 16;
	
lpm_counter ERH (	.clock (clk),
			.cnt_en (TC & DoIncrement),
			.sload (LoadERH),
			.data (D[3:0]),
			.Q (ER[19:16]));
defparam
	ERH.lpm_direction = "UP",
	ERH.lpm_port_updown = "PORT_UNUSED",
	ERH.lpm_type = "LPM_COUNTER",
	ERH.lpm_width = 4;
	
assign RAMaddr = ER[19:1];
assign RAM_CE0 = ER[0];
assign RAM_CE1 = ~ER[0];
assign RAM_RW = (~WriteMem) | clk;

assign RAM_data = WriteMem? D[7:0] : 8'bzzzz_zzzz;

endmodule


Для начала проверим, не сократилось ли выделенное под него адресное пространство? По счастью, компилятор после чтения файла конфигурации, где описаны все команды, составляет нам таблички в HTML:

DestAddr


Адрес +0 +1 +2 +3 +4 +5 +6 +7
00 OUT OUT OUT OUT OUT OUT OUT OUT
08 OUT OUT OUT OUT OUT OUT OUT OUT
10 SIO SIO SIO SIO SIO SIO SIO SIO
18 SIO SIO SIO SIO SIO SIO SIO SIO
20 ACQ ACQ ACQ ACQ ACQ ACQ ACQ ACQ
28 ACQ ACQ ACQ ACQ ACQ ACQ ACQ ACQ
30 TRK TRK TRK TRK TRK TRK TRK TRK
38 TRK TRK TRK TRK TRK TRK TRK TRK
40 ERL ERL ERL ERL ERL ERL ERL ERL
48 ERL ERL ERL ERL ERL ERL ERL ERL
50 ERH ERH ERH ERH ERH ERH ERH ERH
58 ERH ERH ERH ERH ERH ERH ERH ERH
60 [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++]
68 [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++]
70 [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++]
78 [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++]
80 Acc PM ADD SUB ABS ABSPM ABSA ABSS
88 ZACC NOP C C DIV2 CDIV2PM DIV2A DIV2S
90 MUL FMPM FMA FMS MULSU SUFMPM SUFMA SUFMS
98 MULU UFMPM UFMA UFMS SQRD2 SQRPMD2 SQRAD2 SQRSD2
A0 i j k Inv i++ j++ k++ ijk
A8 iLOOP jLOOP kLOOP Jik iLoopUp jLoopUp kLoopUp JNik
B0 JMP JMP JMP JMP JMP JMP JMP JMP
B8 JL JL JO JO JGE JGE JNO JNO
C0 [X+1] [X+2i+1] [X+2j+1] [X+4j+1] [X+i] [X+3i] [X+2j+i] [X+4j+i]
C8 [X+k] [X+2i+k] [X+2j+k] [X+4j+k] [X+i^j] X [X+2j+i^j] [X+4j+i^j]
D0 [Y+1] [Y+2i+1] [Y+2j+1] [Y+Treug[j]+1] [Y+i] [Y+3i] [Y+2j+i] [Y+Treug[j]+i]
D8 [Y+k] [Y+2i+k] [Y+2j+k] [Y+Treug[j]+k] [Y+i^j] Y [Y+2j+i^j] [Y+Treug[j]+i^j]
E0 [Z+1] [Z+2i+1] [Z+2j+1] [Z+4j+1] [Z+i] [Z+3i] [Z+2j+i] [Z+4j+i]
E8 [Z+k] [Z+2i+k] [Z+2j+k] [Z+4j+k] [Z+i^j] Z [Z+2j+i^j] [Z+4j+i^j]
F0 [SP+1] [SP+2i+1] [SP+2j+1] [SP++] [SP+i] [SP+3i] [SP+2j+i] [i-1+SP++]
F8 [SP+k] [SP+2i+k] [SP+2j+k] [--SP+k] [SP] SP [SP+2j] [--SP]


К работе со статической памятью здесь относится 3 команды: ERL (External Register Low, загрузить младшие 16 бит регистра ER), ERH (External Register High, загрузить старшие 4 бита ER) и [ER++] (записать в память по адресу ER и сделать автоинкремент), они занимают адреса от 0x40 до 0x7F, то есть 01xx_xxxx. Сравнивая с комментариями к модулю QuatCoreFastSram, видим: по DestAddr мы этот модуль ничуть не потеснили!

Далее, глянем по чтению из памяти:

SrcAddr


Адрес +0 +1 +2 +3 +4 +5 +6 +7
00 IMM0 IMM1 IMM2 IMM3 IMM4 IMM5 IMM6 IMM7
08 IMM8 IMM9 IMM10 IMM11 IMM12 IMM13 IMM14 IMM15
10 IMM16 IMM17 IMM18 IMM19 IMM20 IMM21 IMM22 IMM23
18 IMM24 IMM25 IMM26 IMM27 IMM28 IMM29 IMM30 IMM31
20 IMM32 IMM33 IMM34 IMM35 IMM36 IMM37 IMM38 IMM39
28 IMM40 IMM41 IMM42 IMM43 IMM44 IMM45 IMM46 IMM47
30 IMM48 IMM49 IMM50 IMM51 IMM52 IMM53 IMM54 IMM55
38 IMM56 IMM57 IMM58 IMM59 IMM60 IMM61 IMM62 IMM63
40 IMM-64 IMM-63 IMM-62 IMM-61 IMM-60 IMM-59 IMM-58 IMM-57
48 IMM-56 IMM-55 IMM-54 IMM-53 IMM-52 IMM-51 IMM-50 IMM-49
50 IMM-48 IMM-47 IMM-46 IMM-45 IMM-44 IMM-43 IMM-42 IMM-41
58 IMM-40 IMM-39 IMM-38 IMM-37 IMM-36 IMM-35 IMM-34 IMM-33
60 IMM-32 IMM-31 IMM-30 IMM-29 IMM-28 IMM-27 IMM-26 IMM-25
68 IMM-24 IMM-23 IMM-22 IMM-21 IMM-20 IMM-19 IMM-18 IMM-17
70 IMM-16 IMM-15 IMM-14 IMM-13 IMM-12 IMM-11 IMM-10 IMM-9
78 IMM-8 IMM-7 IMM-6 IMM-5 IMM-4 IMM-3 IMM-2 IMM-1
80 Acc Acc UAC C Acc Acc UAC C
88 GPUL GPUL GPUH GPUH GPUPL GPUPL GPUPH GPUPH
90 IN IN IN IN IN IN IN IN
98 [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++] [ER++]
A0 i j k Inv ijk ijk ijk ijk
A8 i j k Inv ijk ijk ijk ijk
B0 CALL0 CALL1 CALL2 CALL3 CALL4 CALL5 CALL6 CALL7
B8 CALL8 CALL9 CALLA CALLB CALLC CALLD CALLE CALLF
C0 [X+1] [X+2i+1] [X+2j+1] [X+4j+1] [X+i] [X+3i] [X+2j+i] [X+4j+i]
C8 [X+k] [X+2i+k] [X+2j+k] [X+4j+k] [X+i^j] X [X+2j+i^j] [X+4j+i^j]
D0 [Y+1] [Y+2i+1] [Y+2j+1] [Y+Treug[j]+1] [Y+i] [Y+3i] [Y+2j+i] [Y+Treug[j]+i]
D8 [Y+k] [Y+2i+k] [Y+2j+k] [Y+Treug[j]+k] [Y+i^j] Y [Y+2j+i^j] [Y+Treug[j]+i^j]
E0 [Z+1] [Z+2i+1] [Z+2j+1] [Z+4j+1] [Z+i] [Z+3i] [Z+2j+i] [Z+4j+i]
E8 [Z+k] [Z+2i+k] [Z+2j+k] [Z+4j+k] [Z+i^j] Z [Z+2j+i^j] [Z+4j+i^j]
F0 [SP+1] [SP+2i+1] [SP+2j+1] [SP++] [SP+i] [SP+3i] [SP+2j+i] [i-1+SP++]
F8 [SP+k] [SP+2i+k] [SP+2j+k] [--SP+k] [SP] SP [SP+2j] [--SP]


Здесь всего одна команда, [ER++] (чтение по заданному адресу с автоинкрементом), занимающая адреса 0x98..0x9F, или 1001_1xxx. Опять же сверяемся с кодом модуля - и видим, здесь тоже всё в порядке, мы АЛУ тогда потеснили с видеопроцессором, а статическую память не тронули!

Что ж, уже радость...

Теперь нам нужно сделать управление этим модулем со стороны видеопроцессора. Нужно два управляющих сигнала ReadEnable (разрешение чтения из статической памяти) и WriteFromAdc (запись с инкрементом напрямую из АЦП). По счастью, модуль очень простой, и добавляется это в пол-пинка:

//обращение к внешней памяти
//чуть потеснили IOselector в плане адресов.
//наши DestAddr: 01xx_xxxx
//наши SrcAddr:  1001_1xxx

//DestAddr:
//0100_xxxx - задать младшие 16 бит адреса, ERL (External memory Register Low)
//0101_xxxx - задать старшие 16 бит адреса, ERH (External memory Register High)
//011x_xxxx - записать в память и сделать инкремент, [ER++]

//SrcAddr:
//1001_1xxx - чтение из памяти и инкремент, [ER++]
module QuatCoreFastSRAM (input clk, input [7:0] DestAddr, input [7:0] SrcAddr, input DestStall, input SrcStall, input SrcDiscard, input [15:0] D,
			input ReadEnable, input WriteFromADC,
			output [18:0] RAMaddr, output RAM_CE0, output RAM_CE1, output RAM_RW,
			inout [7:0] RAM_data);
						
wire IsOurDest = (~DestStall)&(~DestAddr[7])&DestAddr[6];
wire IsOurSrc = (~SrcStall)&(~SrcDiscard)&(SrcAddr[7:3] == 5'b1001_1)&ReadEnable;

wire LoadERL = IsOurDest & (~DestAddr[5]) & (~DestAddr[4]);
wire LoadERH = IsOurDest & (~DestAddr[5]) & DestAddr[4];
wire WriteMem = IsOurDest & DestAddr[5];

wire DoIncrement = WriteMem | IsOurSrc | WriteFromADC ;

wire [19:0] ER; //External memory Register
wire TC; //Terminal Count

lpm_counter ERL (	.clock (clk),
			.cnt_en (DoIncrement),
			.sload (LoadERL),
			.data (D),
			.Q (ER[15:0]),
			.cout (TC));
defparam
	ERL.lpm_direction = "UP",
	ERL.lpm_port_updown = "PORT_UNUSED",
	ERL.lpm_type = "LPM_COUNTER",
	ERL.lpm_width = 16;
	
lpm_counter ERH (	.clock (clk),
			.cnt_en (TC & DoIncrement),
			.sload (LoadERH),
			.data (D[3:0]),
			.Q (ER[19:16]));
defparam
	ERH.lpm_direction = "UP",
	ERH.lpm_port_updown = "PORT_UNUSED",
	ERH.lpm_type = "LPM_COUNTER",
	ERH.lpm_width = 4;
	
assign RAMaddr = ER[19:1];
assign RAM_CE0 = ~(ER[0]&(ReadEnable | WriteMem | WriteFromADC));
assign RAM_CE1 = ~(~ER[0]&(ReadEnable | WriteMem | WriteFromADC));
assign RAM_RW = ((~WriteMem) & (~WriteFromADC)) | clk;

assign RAM_data = (WriteMem&ReadEnable)? D[7:0] : 8'bzzzz_zzzz;

endmodule


Не самый красивый код, поскольку все сигналы для управления микросхемами статической памяти ИНВЕРТИРОВАНЫ, т.е активный уровень - это лог "0". Наверное, можно было бы по законам де-Моргана немножко покрасивше записать, но так мне хотя бы понятно, что происходит. "Активируем первый чип, если самый младший бит единичка, а ещё должно быть либо разрешено чтение, либо работать запись из QuatCore, либо запись из АЦП. И берём это условие с инверсией."

И по RW получается: только если оба сигнала на запись нулевые, RW=1. Если есть запись хоть из QuatCore, хоть из АЦП - RW установится в ноль, но лишь пока clk=0. Запись будет "вестись непрерывно", поэтому самым последним записанным значением будет то, что установилось к фронту тактового импульса! Всё верно.

Ну и управление внешней шиной со стороны ПЛИС подкорректировано: если АЦП активирован (и соответственно ReadEnable=0), со стороны ПЛИС занимать шину нельзя!

Синтезируется это безобразие в 33 ЛЭ - нормально.

Далее, нужно подрихтовать видеопроцессор!

Сигнал empty буфера команд нужно "вывести наружу", он будет непосредственно будет соединяться с выводом AdcDisable (отключение АЦП) и ReadEnable модуля QuatCoreFastSRAM. Так что, пока буфер пуст, АЦП отключено, статическая память доступна и на запись, и на чтение. Возможно, стоит и сигнал start генерировать только если empty=0, хотя до сих пор нам не мешало, если "без нашего присмотра" выходной буфер переполняется, поскольку новый кадр мы начинаем с толпы HSync, которые всё обнуляют.

Далее, у нас есть регистр FrontPorch, который устанавливался в единицу, когда у нас шёл отрезок после строчного синхроимпульса. Если объединить по OR два бита SyncOut[1:0] (старший означает, что мы ждём кадровый синхроимпульс, младший - что ждём строчный. Приход синхроимпульса сбрасывает соответствующий бит) и ещё и FrontPorch впридачу, мы получим сигнал, равный единице на всех частях видеосигнала, кроме информативной его части. Инвертировав его, получим WriteFromADC.

Выходит нечто такое:
//сначала у нас все адреса 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
				);

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;
	//в момент прихода синхроимпульса, если его мы и ждали, защёлкивается, а когда отсчитали сколько хотели - сбрасывается
	
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



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

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments