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, чтобы нам всё-таки позволено было не работать с изображением НЕПРЕРЫВНО, но уж если взялся обрабатывать кадр - будь добр обработать ВСЮ ПОЛЕЗНУЮ ОБЛАСТЬ, без прорех!
Продолжение следует...