nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

QuatCore: ещё два прерывания (продолжение)

Пора взяться за компилятор. И уже традиционно внедрение каждой новой "фичи" я начинаю с добавления новых полей и объектов в файл конфигурации. Теперь его начало выглядит так:

UTF8object TQuatCoreTranslator
  RelJumpLatency = 2
  ActiveHazards = [crAcc, crC, crX, crY, crZ, crSP, cri, crj, crk, crInv, crMemBus]
  AllowRelJumps = false
  NMIwidth = 2
  object TQuatCoreNMI
    Key = 'GPU_WDT'
    Description = 'WatchDog Timer - срабатывает, если при работающем АЦП пропадают кадровые синхроимпульсы, т.е нет видеосигнала'
    Code = 0
  end
  object TQuatCoreNMI
    Key = 'GPU_OFLO'
    Description = 'Overflow - не успеваем забрать результаты обработки изображения из выходного буфера видеопроцессора, из-за чего новые результаты "уходят в пустоту"'
    Code = 1
  end
  object TQuatCoreNMI
    Key = 'GPU_UFLO'
    Description = 'Underflow - мы не успеваем загрузить видеопроцессор работой, поэтому часть кадра оказывается необработанной и потерянной'
    Code = 2
  end
  object TQuatCoreCommand
    Key = 'OUT'
    Code = 0
    Mask = 240
    DataMask = 1023
    Description = 'Sends value to chosen output'
    Place = [cpDest]
    Resources = []
    DataType = dtNumeric
  end
  object TQuatCoreCommand
    Key = 'SIO'
    Code = 16
    Mask = 240
    DataMask = 15
    Description = 'Selects I/O device'
    Place = [cpDest]
    Resources = []
    DataType = dtNumeric
  end


Здесь мы указали, сколько бит отводится на "номер прерывания" и привели соответствие между метками в коде и номером прерывания. Запускаем компилятор - и убеждаемся хотя бы, что он корректно подгрузил файл конфигурации (превратил его в иерархию объектов со своими свойствами) и затем сохранил его под именем CloneTranslConfig.txt, т.е провёл сериализацию этих объектов назад в текстовый файл. При этом он чуть-чуть другой:


UTF8object TQuatCoreTranslator
  RelJumpLatency = 2
  ActiveHazards = [crAcc, crC, crX, crY, crZ, crSP, cri, crj, crk, crInv, crMemBus]
  AllowRelJumps = False
  NMIwidth = 2
  object TQuatCoreNMI
    Key = 'GPU_WDT'
    Description = 
      'WatchDog Timer - срабатывает, если при работающем АЦП пропадают ' +
      'кадровые синхроимпульсы, т.е нет видеосигнала'
    Code = 0
  end
  object TQuatCoreNMI
    Key = 'GPU_OFLO'
    Description = 
      'Overflow - не успеваем забрать результаты обработки изображения ' +
      'из выходного буфера видеопроцессора, из-за чего новые результаты' +
      ' "уходят в пустоту"'
    Code = 1
  end
  object TQuatCoreNMI
    Key = 'GPU_UFLO'
    Description = 
      'Underflow - мы не успеваем загрузить видеопроцессор работой, поэ' +
      'тому часть кадра оказывается необработанной и потерянной'
    Code = 2
  end
  object TQuatCoreCommand
    Key = 'OUT'
    Code = 0
    Mask = 240
    DataMask = 1023
    Description = 'Sends value to chosen output'
    Place = [cpDest]
    Resources = []
    DataType = dtNumeric
  end
  object TQuatCoreCommand
    Key = 'SIO'
    Code = 16
    Mask = 240
    DataMask = 15
    Description = 'Selects I/O device'
    Place = [cpDest]
    Resources = []
    DataType = dtNumeric
  end


Как видно, длинные строки описания были разбиты на несколько кусков, так уж принято. Да-да, это формат старого доброго Delphi, они его использовали для представления форм (файлы .dfm), но на самом деле штука очень мощная, зря они только не распространялись особо об этом инструменте, может, не пришлось бы изобретать XML и прочий JSON :) Здесь есть строгий контроль, в отличие от. На автомате идёт сопоставление - "таак, это у нас класc TQuatCoreTranslator, всё верно, есть такой. Здесь свойство RelJumpLatency - да, есть такое, тип Integer. Ага, и здесь действительно число - хорошо, заносим. А тут свойство ActiveHazards, тип TCommandResources, который описывается как set of TCommandResource. Ага, это множество, значит в квадратных скобках должны быть перечислены некоторые метки из TCommandResource. Да, вот они". Как только где-то ошибёшся - он грязно выругается, причём достаточно информативно, дескать, нет в этом классе такого свойства, или нет такого наименования, и пр.

Приятно с этого начинать - дело нехитрое, а уже чувствуется какой-то прогресс :) Да и вся информация, сидящая "в нужных местах" - уже половина дела!

Далее, при загрузке файла конфигурации сделаем массив прерываний по номерам, и убедимся, что несколько прерываний не имеют одного и того же номера, и номера не выходят за указанную битность. Проверим, что будет, если "случайно" дать GPU_WDT и GPU_OFLO код 0, а GPU_UFLO - код 4:

Загружаем файл конфигурации транслятора
ПРЕДУПРЕЖДЕНИЕ: NMI GPU_WDT и GPU_OFLO имеют одинаковый код 0,  последняя будет проигнорирована
ПРЕДУПРЕЖДЕНИЕ: NMI GPU_UFLO имеет недопустимый номер 4 при допустимой ширине 2 бит, и будет проигнорирована
Файл конфигурации прочитан, готовы к работе


А когда исправим всё назад, вернёмся к привычным 2 строкам:
Загружаем файл конфигурации транслятора
Файл конфигурации прочитан, готовы к работе


Хорошо :)

Осталось научиться правильно составлять QuatCoreCallTable.v. В заголовок модуля добавим вход nmiN (номер прерывания) нужной ширины.

То, что мы раньше называли addr, теперь назовём Caddr (Call Addr), здесь особых изменений не будет.

Введём ещё один wire, Iaddr (Interrupt Addr), и ему присвоим один из адресов в соответствии с теми метками, что мы нашли. Тут сразу возникает вопрос, "а что делать, если одной из меток не было в коде?" Прерывание всё равно сработает, здесь у компилятора "нет власти" (хотя можно было бы заставить его генерить ещё и этот самый InterruptEncoder, вот тогда да), ну и пущай отправляет нас аккурат на нулевой адрес, это выйдет своего рода Reset. Правда, мы немножко "изнежились" - привыкли, что при включении питания всё инициализировано нулями, и в оперативной памяти уже лежит ровно то, что мы положили в "файл инициализации", так что далеко не каждая программа при такой перезагрузке заработает правильно. Так что самое главное - ВЫРУГАТЬСЯ ХОТЯ БЫ В ЛОГ, что отдельные прерывания остались без обработчика. Метку проверяем, что это именно метка КОДА, поскольку у нас есть ещё метки данных (адреса строк, чисел и пр) и ЛИТЕРАЛЫ.

Для программы из прошлого поста мы получаем такой листинг:
    main proc
    SetClock proc
00  1027                  SIO     ETH
01  A1F3                  j       [SP++]  ;количество посылок (счёт с нуля)
02  A2F3      @@EthWord:  k       [SP++]  ;количество байт в посылке (счёт с нуля)
03  00F3      @@EthByte:  OUT     [SP++]
04  AA67                  kLOOP   @@EthByte
05  8990                  NOP     IN
06  A927                  jLOOP   @@EthWord
    SetClock endp   
07  FD17                      SP      Stack
08  1041                      SIO     LCD
09  CD0B                      X       InitLCD
0A  F3B0                      CALL    print
0B  CD7B                      X       Init9to18
0C  F3B0                      CALL    print
0D  0007                      OUT     Init19
0E  CD23                      X       Row02
0F  F3B0                      CALL    print
10  0005                      OUT     SetRow1
11  CD03                      X       Row13
12  F3B0                      CALL    print
13  1007                      SIO     UART
14  CD02                      X       InitStr
15  F3B0                      CALL    print
16  4001                      ERL     0
17  5007                      ERH     0
18  A15F                      j       29
19  807F      @@OuterCLR:     Acc     24575       ;24576 * 30 = 1024 * 720 
1A  6002      @@InnerCLR:     [ER++]  0
1B  8341                      SUB     1
1C  BC2F                      JGE     @@InnerCLR
1D  A94F                      jLOOP   @@OuterCLR              
1E  2000                      ACQ     VSync   ;дождаться кадрового синхроимпульса
1F  A11B                      j       TopRows
20  2033      @@TopRowLoop:   ACQ     HSync
21  A903                      jLOOP   @@TopRowLoop
22  807D                      Acc     UsefulRows              
23  4001                      ERL     0
24  5007                      ERH     0                       
25  207E      @@UsefulLoop:   ACQ     WholeRow
26  2033                      ACQ     HSync
27  898A                      NOP     GPUH
28  8341                      SUB     1
29  BC57                      JGE     @@UsefulLoop
2A  8990                      NOP     IN              
2B  4001                      ERL     0
2C  5007                      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  BC79                      JGE     @@InnerLoop
32  A93F                      jLOOP   @@OuterLoop
33  B037                      JMP     @@FrameLoop
    main    endp
34  DD1B      GPU_WDT:    Y       NoVideoSignal
35  B04B                  JMP     IntHandler
36  DD19      GPU_UFLO:   Y       UnderflowInt
37  B04B                  JMP     IntHandler
38  DD09      GPU_OFLO:   Y       OverflowInt
39  1041      IntHandler: SIO     LCD
3A  CD21                  X       CommonError
3B  F3B0                  CALL    print
3C  CDDD                  X       Y
3D  F3B0                  CALL    print
3E  B03B      @@endless:  JMP     @@endless       
    print proc
3F  F380                  [SP++]  Acc
40  FDCD                  SP      X
41  CDFD                  X       SP
42  8867      @@start:    ZAcc    RoundZero
43  83FC                  SUB     [SP]
44  BC77                  JGE     @@finish    ;увы, теперь из процедуры так просто не выпрыгнешь
45  00F3                  OUT     [SP++]
46  B025                  JMP     @@start
47  FDCD                  SP      X
48  CDFD                  X       SP
49  80FF                  Acc     [--SP]
4A  B0FF                  JMP     [--SP]
    print endp


Только-только перевалили за 64 (0x40) слова кода :)

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

Самое главное, файл QuatCoreCallTable.v, теперь уже сгенерированный автоматически:
//адреса для вызова процедур, "вшитые" в модуль QuatCorePC
module QuatCoreCallTable (input [7:0] SrcAddr, input NMI, input [nmiWidth-1:0] nmiN, output [RomWidth-1:0] addr);
parameter RomWidth = 7;
parameter nmiWidth = 2;
wire [6:0] Caddr;
wire [6:0] Iaddr;
	assign Caddr[6]=1'b0;
	assign Caddr[5]=1'b1;
	assign Caddr[4]=1'b1;
	assign Caddr[3]=1'b1;
	assign Caddr[2]=1'b1;
	assign Caddr[1]=1'b1;
	assign Caddr[0]=1'b1;
//Адреса процедур:
// print = 003F(поле Src = B0) 
	assign Iaddr=
		(nmiN == 2'h0)? 7'h34:
		(nmiN == 2'h1)? 7'h38:
		(nmiN == 2'h2)? 7'h36:
				7'h0;
assign addr = NMI? Iaddr : Caddr;
endmodule


И если сравнить адреса здесь и адреса GPU_WDT, GPU_OFLO и GPU_UFLO в листинге - видим, что они совпадают. Маленькая радость!

Копируем все эти файлы (QuatCoreCode.mif, QuatCoreData.mif, QuatCoreCallTable.v и QuatCoreImmTable.v) в папку проекта для ПЛИС.

А ещё немножко переписываем QuatCoreCVinterruptEncoder:

module QuatCoreCVinterruptEncoder (input clk, input OFLO, input UFLO, input WDT, output StallEn, output NMI, output [1:0] intN);

wire wNMI = OFLO | UFLO | WDT;
reg rNMI = 1'b0;

always @(posedge clk)
	rNMI <= wNMI;
	
assign NMI = wNMI & (~rNMI);

assign StallEn = ~NMI;

assign intN = 	WDT? 	2'b00:
		OFLO?	2'b01:
			2'b10;

endmodule


Теперь, независимо от длительности входных сигналов OFLO и UFLO, длительность NMI всегда составит 1 такт, то есть прыжок осуществляется ровно один раз, и потом в процессе обработки прерывания нам уже не помешают, не будут раз за разом возвращать на начало!

Что ж, попробуем это дело отсинтезировать...

Всё бы хорошо, но тайминги заваливаем конкретно:


Похоже, что именно линия прерываний оказалась очень длинной. Не страшно: задержим выдачу прерывания на один такт, мы не торопимся.

module QuatCoreCVinterruptEncoder (input clk, input OFLO, input UFLO, input WDT, output StallEn, output reg NMI=1'b0, output reg [1:0] intN = 2'b00);

wire wNMI = OFLO | UFLO | WDT;
reg rNMI = 1'b0;

always @(posedge clk) begin
	rNMI <= wNMI;
	NMI <= wNMI & (~rNMI);
	intN <= WDT? 	2'b00:
		OFLO?	2'b01:
			2'b10;
end

assign StallEn = ~NMI;

endmodule


Попробуем ещё разок...


Так-то лучше! И, наконец, прошиваемся в прибор.



Ах да, это опять жадность! Когда я вводил "байтовый режим", я добавил лишние 2 бита регистрам X и SP, а вот регистры Y,Z оставил "в сторонке". Поэтому вторая часть сообщения не сработала. Ладно, заменим Y на C, он 16-битный:
;Обработка немаскируемых прерываний (а других пока и нет :))
	.data
		CommonError	dw	0x101,'Ошибка: ',0
		NoVideoSignal	db	'нет сигнала',0
		UnderflowInt	db	'исчерпание  ','заданий GPU',0
		OverflowInt	db	'переполнение','результатов GPU',0
	.code
	GPU_WDT:	C	NoVideoSignal
			JMP	IntHandler
	GPU_UFLO:	C	UnderflowInt
			JMP	IntHandler
	GPU_OFLO:	C	OverflowInt
	IntHandler:	SIO	LCD
			X	CommonError
			CALL	print
			X	C
			CALL	print
	@@endless:	JMP	@@endless		


И перед тем, как повторно всё синтезировать, чуть упростим "сторожевого пса":

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),
			.cout (NMI));
defparam
	WDT.lpm_type = "LPM_COUNTER",
	WDT.lpm_direction = "UP",
	WDT.lpm_port_updown = "PORT_UNUSED",
	WDT.lpm_width = CounterWidth;

endmodule


Теперь сразу при появлении NMI счётчик не сбрасывается, что при делении частоты в 32 раза на ce даёт длительность NMI в 32 такта. Но мы же знаем, что QuatCoreCVinterruptEncoder обеспечит длительность в 1 такт, поэтому всё в порядке. Зато логика sclr в "сторожевом псе" немножко упростилась, может 1 ЛЭ сэкономим, но это не точно :)

Нет, идея была не очень хорошей. Похоже, что-то сэкономить действительно получилось, но фиттер всю схему перекроил по-другому - и не смог уложиться в 25 МГц самую малость. Ладно, вернули как было. Теперь всё в порядке, и:



ДА, так и было задумано!

Осталось только подправить выдачу этого Underflow, чтобы нам всё-таки позволено было не работать с изображением НЕПРЕРЫВНО, но уж если взялся обрабатывать кадр - будь добр обработать ВСЮ ПОЛЕЗНУЮ ОБЛАСТЬ, без прорех!

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

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 2 comments