nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Прерывания в QuatCore - продолжение

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

;Попробуем передать изображение на компьютер

%include "QuatCoreConsts.inc"
%include "Win1251.inc"

.rodata

	VSync		EQU	0x8000
	HSync		EQU	0x4106	;формат CVI, 9,5 мкс от синхроимпульса до начала строки, и ещё 1 мкс прибавляем, т.к хотим 1024 вместо 1280 (а вообще при 25 МГц полезная строка занимает 1075 пикселей)
	TopRows		EQU	28	;столько синхроимпульсов нужно пропустить, прежде чем начнутся "полезные" строки (минус 1, поскольку считаем до нуля)
	UsefulRows	EQU	719	;количество полезных строк (минус 1, поскольку считаем до нуля)
	WholeRow	EQU	1023	;количество пикселей на строке

.code
	main proc
		%include "SetClock.asm"
		
				SP	Stack
				
.rodata
	InitStr		db	'Начинаем работу',13,10,0
				
		%include "LCD4wire.asm"		
				
				SIO	UART
				X	InitStr
				CALL	print
		;далее мы хотим "бесконечный цикл", где мы записываем изображение в статическую память, выдаём самую простенькую информацию,
		;и ждём нажатия на кнопку, чтобы передать всё изображение (это дохрена)
	@@FrameLoop:
			ERL	0
			ERH	0
				
			;давайте для начала почистим память!
			j	29
	@@OuterCLR:	Acc	24575		;24576 * 30 = 1024 * 720 
	@@InnerCLR:	[ER++]	0
			SUB	1
			JGE	@@InnerCLR
			jLOOP	@@OuterCLR				
				
							
			ACQ	VSync	;дождаться кадрового синхроимпульса
						
			;пропустить верхние "пустые" строки
			j	TopRows
	@@TopRowLoop:	ACQ	HSync
			jLOOP	@@TopRowLoop
				
				
			;начинаются полезные строки
			Acc	UsefulRows				

			ERL	0
			ERH	0						
				
	@@UsefulLoop:	ACQ	WholeRow
			ACQ	HSync
			NOP	GPUH
			SUB	1
			JGE	@@UsefulLoop
				
			;ну вот и всё
			;блин, а ведь мы не можем ER вывести, он только на запись!
			;ну и хрен с ним...
				
			NOP	IN				

			ERL	0
			ERH	0
				
			;самое смешное: у нас в 16-битный регистр не влезет то число, до которого делать итерации!
			;либо применить FMA, либо тупо 2 вложенных цикла
			j	29
	@@OuterLoop:	Acc	24575		;24576 * 30 = 1024 * 720 
	@@InnerLoop:	OUT	[ER++]
			SUB	1
			JGE	@@InnerLoop
			jLOOP	@@OuterLoop
				
			;вот как-то так
			;и теперь дождёмся ввода от юзера

			JMP	@@FrameLoop
	main	endp
	
;Обработка немаскируемого прерывания "нет видеосигнала"
	.data
		NoVideoSignal	dw	0x101,'Ошибка: нет сигнала',0
	.code
	NMI:			SIO	LCD
				X	NoVideoSignal
				CALL	print
		@@endless:	JMP	@@endless
	
	%include "Print.asm"
	
.data
	Stack dw ?,?
	


Сразу за "главной процедурой" (main proc) притаилась метка NMI (Non-Masking Interrupt). Если на неё прыгнуть, то мы очистим ЖК-экранчик (за это отвечает значение 0x101 в строке NoVideoSignal) и выведем сверху надпись "Ошибка: нет сигнала", после чего отправимся в бесконечный цикл, справедливо полагая, что сейчас прибор будет выключен - не лезть же в него "под напряжением"!

Этот код вполне себе компилится, вот получающийся листинг:


    main proc
    SetClock proc
00  1023                  SIO     ETH
01  A1F3                  j       [SP++]  ;количество посылок (счёт с нуля)
02  A2F3      @@EthWord:  k       [SP++]  ;количество байт в посылке (счёт с нуля)
03  00F3      @@EthByte:  OUT     [SP++]
04  AA63                  kLOOP   @@EthByte
05  8990                  NOP     IN
06  A923                  jLOOP   @@EthWord
    SetClock endp   
07  FD75                      SP      Stack
08  1041                      SIO     LCD
09  CD09                      X       InitLCD
0A  F3B0                      CALL    print
0B  CD79                      X       Init9to18
0C  F3B0                      CALL    print
0D  0003                      OUT     Init19
0E  CD22                      X       Row02
0F  F3B0                      CALL    print
10  0002                      OUT     SetRow1
11  CD05                      X       Row13
12  F3B0                      CALL    print
13  1003                      SIO     UART
14  CD03                      X       InitStr
15  F3B0                      CALL    print
16  4001                      ERL     0
17  5003                      ERH     0
18  A15F                      j       29
19  807F      @@OuterCLR:     Acc     24575       ;24576 * 30 = 1024 * 720 
1A  6001      @@InnerCLR:     [ER++]  0
1B  8341                      SUB     1
1C  BC2B                      JGE     @@InnerCLR
1D  A94B                      jLOOP   @@OuterCLR              
1E  2000                      ACQ     VSync   ;дождаться кадрового синхроимпульса
1F  A11F                      j       TopRows
20  2031      @@TopRowLoop:   ACQ     HSync
21  A905                      jLOOP   @@TopRowLoop
22  807B                      Acc     UsefulRows              
23  4001                      ERL     0
24  5003                      ERH     0                       
25  207E      @@UsefulLoop:   ACQ     WholeRow
26  2031                      ACQ     HSync
27  898A                      NOP     GPUH
28  8341                      SUB     1
29  BC57                      JGE     @@UsefulLoop
2A  8990                      NOP     IN              
2B  4001                      ERL     0
2C  5003                      ERH     0
2D  A15F                      j       29
2E  807F      @@OuterLoop:    Acc     24575       ;24576 * 30 = 1024 * 720 
2F  0098      @@InnerLoop:    OUT     [ER++]
30  8341                      SUB     1
31  BC7D                      JGE     @@InnerLoop
32  A93F                      jLOOP   @@OuterLoop
33  B033                      JMP     @@FrameLoop
    main    endp
34  1041      NMI:            SIO     LCD
35  CD21                      X       NoVideoSignal
36  F3B0                      CALL    print
37  B077      @@endless:      JMP     @@endless
    print proc
38  F380                      [SP++]  Acc
39  FDCD                      SP      X
3A  CDFD                      X       SP
3B  8863          @@start:    ZAcc    RoundZero
3C  83FC                      SUB     [SP]
3D  BC02                      JGE     @@finish    ;увы, теперь из процедуры так просто не выпрыгнешь
3E  00F3                      OUT     [SP++]
3F  B06F                      JMP     @@start
40  FDCD                      SP      X
41  CDFD                      X       SP
42  80FF                      Acc     [--SP]
43  B0FF                      JMP     [--SP]
    print endp


Но вот файл QuatCoreCallTable.v всё ещё генерируется "по-старому":

//адреса для вызова процедур, "вшитые" в модуль QuatCorePC
module QuatCoreCallTable (input [7:0] SrcAddr, output [RomWidth-1:0] addr);
parameter RomWidth = 7;
	assign addr[6]=1'b0;
	assign addr[5]=1'b1;
	assign addr[4]=1'b1;
	assign addr[3]=1'b1;
	assign addr[2]=1'b0;
	assign addr[1]=1'b0;
	assign addr[0]=1'b0;
//Адреса процедур:
// print = 0038(поле Src = B0) 
endmodule


Имея перед глазами листинг кода, мы можем переиначить этот файл "ручками":

//адреса для вызова процедур, "вшитые" в модуль QuatCorePC
module QuatCoreCallTable (input [7:0] SrcAddr, input NMI, output [RomWidth-1:0] addr);
parameter RomWidth = 7;
wire [RomWidth-1:0] Caddr;	
	assign Caddr[6]=1'b0;
	assign Caddr[5]=1'b1;
	assign Caddr[4]=1'b1;
	assign Caddr[3]=1'b1;
	assign Caddr[2]=1'b0;
	assign Caddr[1]=1'b0;
	assign Caddr[0]=1'b0;
wire [RomWidth-1:0] Iaddr;
	assign Iaddr = 7'h34;
assign addr = NMI? Iaddr : Caddr;
//Адреса процедур:
// print = 0038(поле Src = B0) 
endmodule


Как бы у нас есть таблица адресов процедур (Call address, или сокращённо Caddr) и есть таблица - "вектор прерываний" (Interrupt address, или просто Iaddr). Первую оставляем как была, во вторую пока что вбиваем ровно одно значение - ту самую метку NMI - и потом, если прерывание приходит, выдаём именно её.

Попробуем теперь синтезировать проект для ПЛИС. Он ещё немножко разжирел, с 1367 до 1373 ЛЭ, что в общем-то логично: как раз 6 ЛЭ для QuatCoreCallTable.v и пришлось ввести.

Прошиваем, запускаем - и тут же:


Хммм. А выключить питание - и включить, чтобы сработала старая прошивка?


Сигнал-то есть! То ли наш хвалёный "сторожевой пёс" протупил, то ли где-то дальше ошибка идёт. Кто бы мог подумать - с первого разу не заработало как надо :)

Давайте-ка подумаем, а сколько времени уходит на обнуление памяти? Мы берём участок 1024х720, аккурат по размеру изображения, и работаем по байтику. Один такт - непосредственно поместить туда нолик, ещё ТРИ такта - вычесть единичку из аккумулятора, и ещё ТРИ - прыгнуть в начало цикла, поскольку сбрасывается конвейер, вроде так. Итого (не считая небольших накладных расходов на внешний цикл) - 7 тактов на байт, или 5 160 960 тактов всего. Длительность такта: 40 нс, и получаем 206 мс. Если что, обнуление было введено для отладки, когда у меня в итоговое изображение шёл какой-то "мусор". Но передача данных из памяти на компьютер ещё дольше, не возникни прерывание здесь, возникло бы попозже...

И всё это время АЦП отключено, поскольку оно делит 8-битную шину данных со статической памятью, с которой мы сейчас работаем. Следовательно, синхроимпульсы не идут - и мы таки ловим это прерывание. Шикарно!

Собаку не ругайте - ОНА ЛАЯЛА! В смысле, сигнала действительно долго не было - вот прерывание и сработало.

Просто я и забыл уже, что АЦП приходится отключать из-за очередного моего приступа жадности. Линии ввода-вывода, идущие ТРАНЗИТОМ на соседние платы, действительно заканчивались:
-8 бит АЦП,
-2 бита управление им (тактовая частота и отключение),
-4 провода - выходы селектора синхроимпульсов LM1881, уже не используются, но прямо-таки всё распаивать жалко.
-1 провод - управление меню камеры (вместо "джойстика"),
-ещё один - управление ИК подсветкой (двунаправленный),
-ещё один - управление подсветкой ЖК экранчика.
-ещё 4 - данные на ЖК,
-ещё 1 - выбор между данными и командами для ЖК,
-ещё 1 - строб для ЖК экрана.
- SPI для подключения SD-карточки, 4 провода (nCS, MOSI, MISO, SCK)
- 19 проводов адреса для статической памяти
- 2 провода CE (Chip Enable) для статической памяти (по одному на каждый корпус)
- 1 провод WE (Write Enable) для неё же (идёт в оба корпуса)

Итого получается 49 проводов, а всего их на этой отладочной плате 64. И там ещё шли отдельные провода для подключения вот этой камеры слева:


Это цифровая камера от OmniVision, с параллельным интерфейсом, на неё ещё 14 проводов идёт, итого уже 63! Вот и вышло, что ещё 8 бит для памяти впихнуть некуда... Точнее, можно из бокового разъёма вытащить, но он у нас в стенку упирается!


Ладно, особой проблемы нет. Добавим дополнительный вход Disable в нашего "сторожевого пса":
module VideoSignalWatchDog (input clk, input ce, input VSync, input Disable, output NMI);

parameter CounterWidth = 16;

lpm_counter WDT (	.clock (clk),
			.clk_en (ce),
			.sclr (VSync | Disable | NMI),
			.cout (NMI));
defparam
	WDT.lpm_type = "LPM_COUNTER",
	WDT.lpm_direction = "UP",
	WDT.lpm_port_updown = "PORT_UNUSED",
	WDT.lpm_width = CounterWidth;

endmodule


И подключим этот Disable к выходу Disable видеопроцессора, который, собственно, отключает АЦП и разрешает процессору осуществлять запись в статическую память.

Ещё раз синтезируем, запускаем - и теперь сообщение об ошибке не появляется. Экран горит непрерывно, поскольку в нынешней версии мы заставили обе подсветки (для ЖК-экрана и ИК-подсветка) гореть всегда. Запросили изображение - и получили его:


Отличается от прошлого изображения как раз включённой ИК подсветкой. Что интересно, бликов гораздо меньше.

И теперь "смертельный номер" - лезем внутрь и отсоединяем шлейф.


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

И - НИЧЕГО. Как висело - так и висит...

Задним числом понимаешь: первой пошла команда ACQ VSync, а за ней ворох ACQ HSync (пока не забьётся буфер заданий видеопроцессора), и где-то там всё замерло из-за сигнала SrcStallReq=1, выданного из видеопроцессора (он просит пока остановиться, пока буфер не освободится хоть на одну позицию). Он автоматически включает PipeStall (остановку конвейера), и поэтому когда мы "превратили" прерывание в DoCall (вызов процедуры), мы не дали ему необходимого приоритета, вот строчки из QuatCoreSithPC (счётчик инструкций Ситхов, поскольку использует только абсолютные адреса):

lpm_counter Counter (	.clock (clk),
			.clk_en (~PipeStall),
			.data (D),
			.sload (LoadEnable),
			.Q (PC));


Тут в кои-то веки был применён вход clk_en, появление на котором нуля запрещает вообще ВСЕ ОПЕРАЦИИ, а не только счёт, как cnt_en.

Вот и получился у нас полный deadlock - процессор не прочь перейти на метку NMI, но только когда завершит текущую операцию, а она никогда не завершится.

И здесь есть два варианта решения, и я традиционно не могу сразу сказать, какой лучше.

Можно-таки дать NMI высочайший приоритет, превышающий PipeStall. То есть в буквальном смысле - "БРОСАЙТЕ ВСЁ И ОТПРАВЛЯЙТЕСЬ В МЕТКУ NMI". Беспокоит только, что из такого прерывания уже точно не получится вернуться В ТО ЖЕ САМОЕ МЕСТО! Говоря по-научному, нарушается атомарность команд. Мы ожидали, что каждая команда либо УСПЕЕТ выполнится, либо НЕ УСПЕЕТ, но теперь она может оказаться выполненной НАПОЛОВИНУ.

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

Сейчас, пожалуй, я "замаскирую" ВНЕШНИЕ сигналы SrcStallReq и DestStallReq, то есть идущие из видеопроцессора и периферии (UART, SPI, LCD). Просто воткну "рассыпухи" на схему (block diagram file):


Таким образом, операции над внутренней памятью QuatCore и с АЛУ всегда будут завершаться (дождёмся 1..18 тактов, поскольку там ПОЛНОЙ ОСТАНОВКИ произойти не может), а вот ожидание ответа какой-то периферии будем игнорировать. Это тоже не позволяет прерывание сделать совсем "безобидным" - если мы отправляли какой-то символ на экран, и как раз застряли, ожидая, пока туда отправится предыдущий символ - то прерывание убедит процессор, что он УЖЕ ОТПРАВИЛ этот символ, команда ВЫПОЛНЕНА, и по возвращении (которого мы пока вообще не планируем) мы уже начнём со следующего. Ну и хрен с ним. Использовать прерывания для ШТАТНОЙ РАБОТЫ я пока не хочу, если они приходят - значит уже что-то пошло серьёзно не так.

Ладно, поглядим, что из всего этого вышло... Синтез выполняется, количество ЛЭ возросло с прошлого раза с 1373 до 1375 - ожидаемо, но не страшно. Fitter проходит на удивление легко, и на Timing Analysis к нам никаких претензий нет, максимальная частота 25,51 МГц. Хорошо :)

Запускаем...


Всё чудесатее и чудесатее! Повторил - то же самое. Это кабель отключён с самого начала. Когда я подключил кабель и перезапустился - всё в порядке, картинка есть, а когда отключил кабель уже "на ходу" - увидел мелькание между "ибка: нет сигнала" и "шибка: нет сигнала".

Пока я вижу такое объяснение: пока я сидел глубоко в отладке, ЖК-экранчик интенсивно грелся, а под ним грелась микросхемка 7805 и гасящий резистор. Вот его "внутренняя частота", задаваемая тупо RC-цепочкой, и просела немножко - не успевает за 1,5 мс очистить экран, и пропускает пару сообщений после этого.

Зато мы явственно увидели, что прерывание продолжает срабатывать каждые 100 мс примерно, ведь в очереди заданий видеопроцессора по-прежнему сидит невыполненное задание, и АЦП вполне себе активен! Почему бы и нет :)

Что ж, залезем в контроллер ЖК-экранчика QuatCoreLCD4wire, только верхние строки, те что с параметрами:

parameter ClockFreq = 4_000_000; //most understood.
parameter is5volts = 1'b1; //1: 5 volts (works faster), 0: 3,3 volts

localparam EDivBy = is5volts? ((ClockFreq + 3_999_999) / 4_000_000) : ((ClockFreq + 1_999_999) / 2_000_000); 
localparam EDivWidth = `CLOG2 (EDivBy);

localparam ActualEFreq = ClockFreq >> EDivWidth; //so we always divide by 2**EDivWidth, for smaller 'busy' counter and to avoid problems with 'divide by 1'

localparam ShortBusyDiv = (ActualEFreq + 24_999) / 25_000; 	//for almost all commands
localparam LongBusyDiv = (ActualEFreq + 666) / 667;			//for CLS
localparam BusyWidth = `CLOG2 (LongBusyDiv);


Чего-то я здесь перемудрил...

Первое, что там происходит - формируется частота строба. Он по таймингам должен быть не менее 500 нс для 5-вольтовых модулей и не менее 1000 нс для 3,3-вольтовых. Мы хотим поделить тактовую частоту на целое число, и берём это значение "по наихудшему случаю". Сейчас, когда указан 5-вольтовый модуль, получается деление на 7, то есть с 25 МГц до 3,57 МГц.

Но дальше мы решили сделать этот делитель всегда на 2N, т.е либо на 4, либо на 8, либо на 16 и т.д. И в данном случае выбрали деление на 8, до 3,125 МГц.

А далее мы уже пытаемся обеспечить задержку не менее 40 мкс для всех операций, кроме "очистки экрана", и 1,5 мс для неё самой.

У нас получается ShortBusyDiv = 125, то есть мы отсчитываем 125 импульсов по 320 нс, что даёт В ТОЧНОСТИ 40 мкс.
И LongBusyDiv = 4686, что даёт 1,49952 мс. Интересно - запасу-то и не получилось особо...

Ладно, добавим ещё одну строку:
localparam ParanoidBusyDiv = (1 >> BusyWidth) - 1;


то есть, мы поняли, что делить надо в 4686 раз, взяли под это дело 13-битный счётчик, отсчитывающий от 0 до 8191 - и теперь стали отсчитывать все эти 8191, НА ВСЯКИЙ ПОЖАРНЫЙ.

Синтезируем ещё разок...



На удивление приятно видеть эту ошибку, ещё и написанную без ошибки!

Кстати, сегодня эта хреновина проработала автономно с 18:30 по 21:14, то есть чуть менее 3 часов, и заряд ещё оставался, напряжение на трёх литий-ионных банках 10,8 вольт. Дальше решил не мучать, включил подзарядку, мало кто любит глубокие разряды. Но хотя бы убедился, что при снижении напряжения проблем больше не возникает - 5 вольт держатся железно, а вслед за ними и все остальные. Думаю, меня такое время автономной работы вполне устраивает.

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

UPD. Народ, а у вас самый верхний код, перед катом, имеет цветовое оформление? Почему-то у меня в ленте оно пропадает - просто всё "курьером" с отступами...
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 5 comments