nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Приёмник UART, DUP и ReadLn

Пора проверить работу приёмника UART "в составе QuatCore". Для этого написал такую вот немудрёную программку, HelloUserName.asm:

;проверяем приёмник UART, а также директиву DUP и процедуру ReadLn

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

.code
	main proc
		%include "SetClock.asm"
		
		SP	Stack
		
.rodata
	NamePromptStr	db 	'Как вас зовут?',13,10,0
	HelloStr	db	'Добрый день, ',0
	UserName	db	32 DUP(0)
	
.code
		SIO	UART

		X	NamePromptStr
		CALL	print	

		X	UserName
		CALL	ReadLn

		X	HelloStr
		CALL	print

		X	UserName
		CALL	print

		OUT	13
		OUT	10
		
	@@endless: JMP @@endless
	
	main endp
	
%include "Print.asm"
%include "ReadLn.asm"

.data
	Stack	dw	2 DUP(?)	;1 место под адрес возврата, ещё одно под сохранение Acc в процедуре print


Но прежде чем она сможет заработать, нужно "научить" компилятор понимать директиву DUP (DUPlicate), а также написать процедуру ReadLn...


Честно говоря, синтаксис DUP, принятый в большинстве ассемблеров, мне не очень нравится: компилятору приходится "заглядывать вперёд" по чём зря. То есть, в строке

Username db 32 DUP(0)


прочитав число 32, компилятор не должен тут же упихивать его в очередной байт, он должен прочитать дальше. И увидев DUP, он должен понять, 32 - это не значение, которое помещается в память, а указание, что число 0 (которое в аргументе для DUP) нужно повторить 32 раза!

Как-то криво оно парсится: видимо сначала мы должны прочитать строку до очередной запятой, разделяющей значения. Потом попытаться прочитать число в начале, а за ним - отыскать этот самый DUP. Понятно, ничего особенно навороченного, но мне кажется, можно было и более "прямолинейно", поставить DUP в начало, а потом, к примеру, число повторений и значение передать ему как аргументы.

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

Далее, нужна процедура ReadLn. Вот как она описана в одноимённом файле ReadLn.asm:

;прочитать строку, т.е до символа 13 (CR)

;в регистре X передаётся адрес, куда класть строку
;будет прочитываться очередное значение с ввода-вывода (не обязательно даже 8-битное), и если оно меньше или равно 13 (всевозможные "специальные символы"), мы возвращаемся
;в противном случае записываем символ и ждём следующий

;также мы выйдем по прочтении 32 символов (делать полноценное "динамическое выделение памяти" не хочется, дать возможность пользователю "всё сломать" тоже,
;а 32 нам должно хватить :)

;полагаем, что k=0. По выходу из процедуры k может измениться и будет выражать количество принятых символов
;также может измениться значение Acc, других регистров мы не трогаем

ReadLn proc
	Acc		IN
	SUB		14
	JL		[--SP]	;возврат из процедуры
	[X+k]		Acc		;раз дошли до этого места, значит надо положить принятый символ
	kLoopUp		ReadLn
	;а если дошли до этого места, значит уже приняли 32 символа, хватит!
	JMP		[--SP]
ReadLn endp


Вроде всё объяснено в комментариях: читаем, пока не наткнёмся на "специальный символ" от 0x00 до 0x0D. При этом "таб" тоже окажется недоступен, он в терминале таб не очень-то и введёшь - фокус с поля ввода уйдёт! Ну не хочется мне вводить "флаг нуля", это уже упрямство :) Хотя чтобы проверить на ноль можно двумя дополнительными строками: после вычитания сделать ABS Acc, и SUB 1. Только в случае нуля результат получится отрицательным

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

Компиляция проходит успешно. Сначала посмотрим листинг оперативной памяти (т.е "данных"):

NamePromptStr:      00  0xCA02
NamePromptStr[1]:   01  0xE003
NamePromptStr[2]:   02  0xEA22
NamePromptStr[3]:   03  0x2054
NamePromptStr[4]:   04  0xE200
NamePromptStr[5]:   05  0xE001
NamePromptStr[6]:   06  0xF103
NamePromptStr[7]:   07  0x2022
NamePromptStr[8]:   08  0xE766
NamePromptStr[9]:   09  0xEE00
NamePromptStr[10]:  0A  0xE218
NamePromptStr[11]:  0B  0xF302
NamePromptStr[12]:  0C  0xF222
NamePromptStr[13]:  0D  0x3F6F
NamePromptStr[14]:  0E  0x0D02
NamePromptStr[15]:  0F  0x0AC4
NamePromptStr[16]:  10  0x00EE
UserName:           11  0x00E1
UserName[1]:        12  0x00F0
UserName[2]:        13  0x00FB
UserName[3]:        14  0x00E9
UserName[4]:        15  0x0020
UserName[5]:        16  0x00E4
UserName[6]:        17  0x00E5
UserName[7]:        18  0x00ED
UserName[8]:        19  0x00FC
UserName[9]:        1A  0x002C
UserName[10]:       1B  0x0020
UserName[11]:       1C  0x0000
UserName[12]:       1D  0x00??
UserName[13]:       1E  0x00??
UserName[14]:       1F  0x00??
UserName[15]:       20  0x00??
UserName[16]:       21  0x00??
UserName[17]:       22  0x00??
UserName[18]:       23  0x00??
UserName[19]:       24  0x00??
UserName[20]:       25  0x00??
UserName[21]:       26  0x00??
UserName[22]:       27  0x00??
UserName[23]:       28  0x00??
UserName[24]:       29  0x00??
UserName[25]:       2A  0x00??
UserName[26]:       2B  0x00??
UserName[27]:       2C  0x00??
UserName[28]:       2D  0x00??
UserName[29]:       2E  0x00??
UserName[30]:       2F  0x00??
UserName[31]:       30  0x00??
Stack:              31  ????
Stack[1]:           32  ????
                    33  ????
                    34  ????
                    35  ????
                    36  ????
                    37  ????
                    38  ????
                    39  ????
                    3A  ????
                    3B  ????
                    3C  ????
                    3D  ????
                    3E  ????
                    3F  ????


Да, DUP срабатывает как надо - действительно выделяется 32 байта под UserName и 2 слова под Stack. "Упаковка" байтовых строк не самая оптимальная, на всё про всё ушло 96 байт, хотя хватило бы 92 байта. Ну да ладно, пока нас это устраивает.

Теперь листинг кода:
    main proc
    SetClock proc
00  1021                  SIO     ETH
01  A1F3                  j       [SP++]  ;количество посылок (счёт с нуля)
02  A2F3      @@EthWord:  k       [SP++]  ;количество байт в посылке (счёт с нуля)
03  00F3      @@EthByte:  OUT     [SP++]
04  AA61                  kLOOP   @@EthByte
05  8990                  NOP     IN
06  A921                  jLOOP   @@EthWord
    SetClock endp   
07  FD46          SP      Stack
08  1001          SIO     UART
09  CD01          X       NamePromptStr
0A  F3B4          CALL    print   
0B  CD45          X       UserName
0C  F3B8          CALL    ReadLn
0D  CD78          X       HelloStr
0E  F3B4          CALL    print
0F  CD45          X       UserName
10  F3B4          CALL    print
11  0058          OUT     13
12  0028          OUT     10
13  B065 @@endless: JMP @@endless
    main endp
    print proc
14  F380                  [SP++]  Acc
15  FDCD                  SP      X
16  CDFD                  X       SP
17  8861      @@start:    ZAcc    RoundZero
18  83FC                  SUB     [SP]
19  BC1D                  JGE     @@finish    ;увы, теперь из процедуры так просто не выпрыгнешь
1A  00F3                  OUT     [SP++]
1B  B075                  JMP     @@start
1C  FDCD                  SP      X
1D  CDFD                  X       SP
1E  80FF                  Acc     [--SP]
1F  B0FF                  JMP     [--SP]
    print endp
ReadLn proc
20  8090      Acc     IN
21  8338      SUB     14
22  B8FF      JL      [--SP]  ;возврат из процедуры
23  C880      [X+k]   Acc     ;раз дошли до этого места, значит надо положить принятый символ
24  AE03      kLoopUp ReadLn
25  B0FF      JMP     [--SP]
ReadLn endp


Выглядит неплохо.

Попробуем прошить!

Поскольку эта программа куда компактнее нашего VideoProcessing.asm, то и таблица непосредственных значений здесь совсем "игрушечная", в 1 ЛЭ:
//таблица непосредственных значений, сгенерированная под конкретный исполняемый код
module QuatCoreImmTable (input [7:0] SrcAddr, output [15:0] Q);
	wire[6:0] adr = SrcAddr[6:0];
	assign Q[0]=adr[6];
	assign Q[1]=adr[5];
	assign Q[2]=adr[4];
	assign Q[3]=adr[3];
	assign Q[4]=adr[2];
	assign Q[5]=adr[1];
	assign Q[6]=adr[0];
	assign Q[8]=1'b0;
	assign Q[9]=1'b0;
	assign Q[10]=1'b0;
	assign Q[11]=1'b0;
	assign Q[12]=1'b0;
	assign Q[13]=1'b0;
	assign Q[14]=1'b0;
	assign Q[15]=1'b0;
	wire [2:0] adr7={adr[6],adr[5],adr[0]};
	assign Q[7]=
		(adr7==3'b100)?	1'b0:
		(adr7==3'b010)?	1'b0:
		(adr7==3'b110)?	1'b1:
		(adr7==3'b001)?	1'b1:
		(adr7==3'b101)?	1'b1:
			1'bx;
//Непосредственные значения и их адреса:
// Значение (dec) Значение (hex) Маска Адрес Где используется                        
// 2              0002           003F  0021  SIO ETH/jLOOP SetClock::@EthWord        
// 3              0003           003F  0061  kLOOP SetClock::@EthByte/ZACC RoundZero 
// 49             0031           00FF  0046  SP Stack                                
// 192            00C0           00FF  0001  SIO UART/X NamePromptStr                
// 209            00D1           00FF  0045  X UserName/X UserName                   
// 143            008F           00FF  0078  X HelloStr                              
// 13             000D           03FF  0058  OUT 13                                  
// 10             000A           03FF  0028  OUT 10                                  
// 19             0013           003F  0065  JMP main::@endless                      
// 28             001C           003F  001D  JGE print::@finish                      
// 23             0017           003F  0075  JMP print::@start                       
// 14             000E           FFFF  0038  SUB 14                                  
// 32             0020           003F  0003  kLoopUp ReadLn                          
endmodule


Так что должно всё "летать". Но увы:


Опять не укладывается в свои 25 МГц.

Попробуем костыль:

Мы по сути убрали мультиплексор между выходом UART и SPI, зная, что пока что команда IN на SPI используется лишь чтобы дождаться окончания передачи, чтобы между посылками nCS успевал бы "мигнуть" единицей. А вот сигнал busy важен как от UART, так и от SPI, его трогать мы не могли.

Ещё раз синтезируем:


Ээх, 24,88 МГц, не хватает самой капельки. Всего 11 "заваленных путей". В принципе, этого можно было ожидать: наша "конвейерная логика", увы, преимущественно комбинаторная, и по мере добавления к ней новых модулей она всё сильнее и сильнее "проседает".

Нужно упростить логику формирования DestStallReq и SrcStallReq от устройств ввода-вывода!

Сейчас происходит довольно странная вещь: модуль QuatCoreIOselector мучительно декодирует, к какому из устройств ввода-вывода сейчас обратиться, и выдаёт соответствующие сигналы: UARTtxEN, UARTrxEN, SPItxEN, SPIrxEN, LCD_EN.

Затем каждое из устройств формирует свои индивидуальные сигналы busy, обязательным условием выполнения которых становится наличие соответсвующего "сигнала разрешения работы" на входе.

И наконец, все эти сигналы объединяются по "ИЛИ". Выходит как-то вот так:


Только вход INcommand, изображённый здесь, комбинаторно выражается через 7 сигналов (5 битов SrcAddr, а также SrcStall и SrcDiscard). Также относительно сложные выражения для UARTworking (проверяется, что мы в правильном состоянии, при ce=1 и даже значение на входе задействовано) и SPIworking.

Не знаю, хватает ли у Квартуса прозорливости увидеть всю эту часть целиком и сообразить, что сигнал INcommand можно перенести в конец:


Давайте проверим... Выходы busy в приёмниках UART и SPI больше не будут зависеть от "входа разрешения", и поступят на QuatCoreIOselector. Дальнейшая логика будет реализована там.

Как ни странно, помогло:


ПРОШИВАЕМСЯ!



Очень интересно :) Начальное сообщение он успешно выдаёт. Столь же успешно он ждёт, когда же мы что-нибудь напишем, но потом почему-то зацикливается.

На аппаратную ошибку не похоже. Как будто из процедуры ReadLn он возвращается на нулевой адрес, что сродни reset.

И действительно, дурацкая ошибка, как всегда решил выпендриться!:

ReadLn proc
	Acc		IN
	SUB		14
	JL		[--SP]	;возврат из процедуры
	[X+k]		Acc		;раз дошли до этого места, значит надо положить принятый символ
	kLoopUp	ReadLn
	;а если дошли до этого места, значит уже приняли 32 символа, хватит!
	JMP		[--SP]
ReadLn endp


Это кажется красиво возвращаться из процедуры "условным переходом". Только вот незадача - ПРАВАЯ ЧАСТЬ ВЫЧИСЛЯЕТСЯ ЕЩЁ ДО ТОГО, КАК БУДЕТ ПРИНЯТО РЕШЕНИЕ О ПЕРЕХОДЕ! Поэтому декремент выполняется в любом случае, такая уж логика QuatCore! Вот и выходит, что мы начисто убиваем стек и прыгаем вообще непонятно куда. Но благодаря младшим адресам, инициализированным нулями перед стеком (см. листинг памяти), очень даже понятно - в начало работы мы прыгаем.

Ладно, переправим как надо:
ReadLn proc
	Acc	IN
	SUB	14
	JL	@@ret	;возврат из процедуры
	[X+k]	Acc		;раз дошли до этого места, значит надо положить принятый символ
	kLoopUp	ReadLn
	;а если дошли до этого места, значит уже приняли 32 символа, хватит!
@@ret:	JMP	[--SP]
ReadLn endp


Да, попрыгает немножко - ну и ладно!
Хотели бы скорости - можно было в самом начале поставить NOP [--SP], а потом в середине цикла JL [SP], и в конце JMP [SP]. Но ReadLn вообще не обязана быть быстрой, когда на приём одного байта по UART заведомо уходит 270 тактов!

Ладно, попробуем ещё раз:


Интересное кино...

По некоторым размышлениям, нашёл здесь аж две ошибки:
- программная, всё в том же злосчастном ReadLn - мы заносим не принятое значение, из него вычитается 14.
- аппаратная: "байтовый режим" работает только на чтение, а запись пока производится во всё слово целиком! А если посмотреть листинг в очередной раз, обнаруживаем, что как раз с третьей буквы HelloStr ("Добрый день, ") делит одни и те же слова с UserName, вот и выходит, что "До" отправляется нормально, а потом идёт введённая нами строка, но повёрнутая на 14 :)

Для начала, просто заставим эту хрень работать... Переписываем ReadLn:

ReadLn proc
	C		IN
	Acc		13
	SUB		C
	JGE		@@ret	;возврат из процедуры
	[X+k]		C		;раз дошли до этого места, значит надо положить принятый символ
	kLoopUp		ReadLn
	;а если дошли до этого места, значит уже приняли 32 символа, хватит!
@@ret:	JMP		[--SP]
ReadLn endp


Ну нет у нас CMP (Compare), чтобы два числа сравнить между собой, а значение в аккумуляторе сохранить старое! Приходится извращаться. И операнды местами поменяли (из 13 вычитаем символ), чтобы не допустить Hazard по регистру C, хотя по АЛУ у нас блокировка стоит, автоматически на такт задержит. А так и задерживать не будет. хотя нет, всё равно задержит. Он не понимает, что пока мы делаем вычитание, регистр C не меняется, его можно "старый" взять. И ладно, при умножении C циклически крутится, и тогда его посреди работы действительно брать не надо! Ох, жесть какая. А такой маленький был QuatCore, такой няшный :)

А с байтовым режимом пока что костыль, для хранения UserName вместо db ставим dw, и пробуем ещё разок:



И снова ошибка закралась: пока отлаживал, день закончился, надо было "добрый вечер".

А в целом, жить можно... И мне кажется, программирование вполне себе в досовском стиле, даже покомпактнее. Всяко приятнее, чем в микроконтроллерах, где надо уйму конфигурационных регистров настроить, отправлять байтики, уходить в "бесконечный цикл", ожидая, когда значение в одном из этих регистров переключится, и т.д. Может, это уже стокгольмский синдром...
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Великая Октябрьская резня бензопилой

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

  • Очередная несуразность в единицах измерения

    Когда-то я написал программу PhysUnitCalc - калькулятор, умеющий работать с размерностями. Мне казалось, что я наступил уже на все грабли, которые…

  • Big Data, чтоб их... (3)

    "В предыдущих сериях": мой прибор выдаёт 6 значений: 3 координаты и 3 угла, т.е все 6 степеней свободы твёрдого тела. Причём ошибки измерения этих 6…

  • Покрышки с взрывным характером

    Продолжаю кататься на велосипеде на работу и назад, а также время от времени в РКК Энергию. С 17 мая (когда решил записывать, сколько проехал,…

  • Big Data, чтоб их... (2)

    Вчера получил упоротое уравнение, чтобы найти, с какими весами нужно брать результаты измерений, чтобы получить наименее шумную и при этом…

  • Big Data, чтоб их...

    Решил всё-таки вывести оптимальную обработку N измерений x k, каждое по M компонент (т.е вектор M×1), и на каждое дана своя ковариационная…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments