nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Байтовый Hello, World на QuatCore

Если посмотреть папку с программами для QuatCore, окажется что половина из них Hello, World:
- одна из самых первых с выводом по UART,
- то же самое, но на ЖК-экранчик по "8-проводному" интерфейсу (реально там проводов ещё больше, есть A0 выбирающий между командами и данными, R/W выбирающий между чтением и записью, и "тактовая частота" в придачу, она же "строб"),
- то же самое, но на ЖК-экранчик по "4-проводному" интерфейсу (идёт 2 посылки вместо одной),
- то же самое, но с настройкой тактовой частоты в 25 МГц, для чего нужно по SPI сконфигурировать Ethernet-контроллер,
- то же самое, но строка сначала записывается во внешнюю статическую память, а потом считывается оттуда,
- то же самое, но строка лежит на SD-карточке, и её для начала надо оттуда прочитать!

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

Начнём с такой вот программы в 9 слов:

;Проверяем работу с байтами в QuatCore
;Подумав ещё немножко, решаем расширить на 2 бита не X, а SP (а может ещё подумаем - и всех расширим)
%include "QuatCoreConsts.inc"
%include "Win1251.inc"
.rodata
	;в кои-то веки строка должна оканчиваться нулём! (халява закончилась)
	hello		db	'Hello, World! Live fast at 25 MHz',13,10,0
	EthDisable	db	2,3,0x22,0x54,0x00,0x01,3,0x22,0x66,0x00,0x18,2,0x22,0x6F,0x02
	
		
.code
	main proc
				SIO		UART
				SP		hello
				C		CALL(print)
		@@endless:	JMP		@@endless
	main endp
	
	
	;посылает строку куда-нибудь. Пока модуль ввода-вывода всего один, SIO не нужен (нужный модуль по умолчанию будет активен)
	;SP указывает на начало строки текста
	;конец обозначается отрицательным числом или НУЛЁМ (необходимо для работы с байтами)
	;меняет значение регистра Acc и SP
	;адрес возврата надо заносить в регистр C. 
	print proc
		@@start:	ZAcc		RoundZero
				SUB		[SP]
				JGE		C
				OUT		[SP++]
				JMP		@@start
	print endp



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

Выбираем устройство ввода-вывода (Select Input/Out, SIO) UART, указываем строку, которую хотим вывести - и вызываем print.

Эх, до чего удобны регистры с инкрементом! Настолько, что я всерьёз думаю вызывать print именно так, сохраняя адрес возврата в регистр C, а SP использовать именно как адрес строки. В конце концов, в месте вызова я заранее знаю, где находится вершина стека, поэтому могу даже не сохранять значение SP.

В процедуре print мы загружаем в аккумулятор значение "1/2 младшего разряда", которое обычно применяется нами для правильного округления. Но здесь оно позволяет проверить на нулевое значение. Если мы вычитаем очередной байт и получаем неотрицательный результат - значит там был нолик. Тогда срабатывает JGE (Jump if Greater or Equal) и возвращает нас из процедуры. Очередной приятный момент, когда все прыжки по абсолютным адресам - можно делать "условный выход из процедуры"! Раньше нам приходилось прыгнуть "относительно" куда-то вниз, а там уже JMP [--SP] или JMP C, поскольку JGE требовал относительный адрес.

Если же символ ненулевой, то мы выводим его "наружу", причём в это же самое время к SP прибавляется единица, и потом мы прыгаем в начало цикла.

Дёшево и сердито!

Строка EthDisable у нас "на будущее", а сейчас хотим убедиться, что "не нахватаем" лишних битов при своей байтовой работе.

Компилируется оно успешно, со следующими "общими параметрами":

Компиляция завершена успешно

Ширина адреса сегмента кода (и регистра PC):           4  
Ширина адреса сегмента данных (и регистров X,Y,Z,SP):  6  
Ширина сумматора для относительных прыжков:            0  
Количество инициализированных слов данных:             51 
Количество инициализированных слов кода:               9  
Количество адресов процедур:                           1 


Да, строка hello занимает 36 байт, которые расположены в последовательно идущих 16-битных словах, поэтому нужно 6 бит адреса сегмента данных (0..63). Строка EthDisable делит с hello первые 15 слов:

hello:      00  0x4802
hello[1]:   01  0x6503
hello[2]:   02  0x6C22
hello[3]:   03  0x6C54
hello[4]:   04  0x6F00
hello[5]:   05  0x2C01
hello[6]:   06  0x2003
hello[7]:   07  0x5722
hello[8]:   08  0x6F66
hello[9]:   09  0x7200
hello[10]:  0A  0x6C18
hello[11]:  0B  0x6402
hello[12]:  0C  0x2122
hello[13]:  0D  0x206F
hello[14]:  0E  0x4C02
hello[15]:  0F  0x69??
hello[16]:  10  0x76??
hello[17]:  11  0x65??
hello[18]:  12  0x20??
hello[19]:  13  0x66??
hello[20]:  14  0x61??
hello[21]:  15  0x73??
hello[22]:  16  0x74??
hello[23]:  17  0x20??
hello[24]:  18  0x61??
hello[25]:  19  0x74??
hello[26]:  1A  0x20??
hello[27]:  1B  0x32??
hello[28]:  1C  0x35??
hello[29]:  1D  0x20??
hello[30]:  1E  0x4D??
hello[31]:  1F  0x48??
hello[32]:  20  0x7A??
hello[33]:  21  0x0D??
hello[34]:  22  0x0A??
hello[35]:  23  0x00??
            24  ????
            25  ????
            26  ????
            27  ????
            28  ????
            29  ????
            2A  ????
            2B  ????
            2C  ????
            2D  ????
            2E  ????
            2F  ????
            30  ????
            31  ????
            32  ????
            33  ????
            34  ????
            35  ????
            36  ????
            37  ????
            38  ????
            39  ????
            3A  ????
            3B  ????
            3C  ????
            3D  ????
            3E  ????
            3F  ????


На листинге видны "частично определённые" слова. В ПЛИС на местах вопросиков будут нули.

Ещё посмотрим на список непосредственных значений:

192 00FF SIO UART/SP hello                 
3   000F JMP main::@endless/ZACC RoundZero 
4   000F JMP print::@start  


У первого значения маска FF, т.е используется только 8 младших бит. Действительно: 6 бит показывают адрес, ещё 2 бита - выбор байтов внутри слова (оба, младший или старший). 192 = 0xC0 = 1100_0000 - всё верно, по нулевому адресу брать только старший байт. О чудо - то же самое непосредственное значение используется для выбора UART.

Дальше ещё одно "удивительное совпадение", когда константа "1/2 младшего разряда" (RoundZero) описывается тем же значением, что и метка "бесконечного цикла". Итого, этих непосредственных значений всего 3!

На этом работа с ассемблером завершается - у нас сформировалось 4 файла, QuatCoreData.mif (начальное содержание оперативной памяти), QuatCoreCode.mif (ПЗУ кода), QuatCoreCallTable.v (таблица вызовов процедур) и QuatCoreImmTable.v (таблица непосредственных значений). Копируем всё это дело в папку с проектом для ПЛИС...

Теперь нужно ещё разок причесать "железо" для работы с байтами
В прошлый раз мы выделили 2 лишних бита в регистре X. Давайте пока их уберём, а добавим их лучше в регистр SP :)

Правда, если регистр X был совсем простым (он мог загружаться или хранить своё значение, и более ничего), то SP - это РЕВЕРСИВНЫЙ СЧЁТЧИК. Можно всерьёз задуматься - должен ли счётчик охватывать и дополнительные 2 бита? "Бесшовный переход" с младшего байта на старший ещё мог бы однажды пригодиться, хотя такой сценарий придумать довольно сложно. Это мы должны вообще всю оперативную память забить строками, и одна из этих строк может начаться в младших байтах в последних адресах, а продолжиться в старших байтах в адресах с нулевого. А старший бит, с помощью которого идёт переключение между словами и байтами, при очередном SP++ изменять кажется ещё более бредовым, хотя можно "извернуться" смеху ради.

Ладно, хуже не будет, сделаем весь счётчик поширше:
module QuatCoreMemRegWBand (input clk, input [15:0] D, input WriteX, input WriteY, input WriteZ, input WriteSP, input CountSP, input SPup,
					   output [RamWidth-1:0] X, output reg [RamWidth-1:0] Y = 1'b0, output reg [RamWidth-1:0] Z=1'b0, output [RamWidth+1:0] SP);

parameter RamWidth = 8;
parameter EnableByteAccess = 1'b1;

localparam XWidth = RamWidth + 2 * (EnableByteAccess);
//reg [XWidth-1:0] Xreg = 1'b0;
reg [RamWidth-1:0] Xreg = 1'b0;
assign X = Xreg;


always @(posedge clk) begin
	if (WriteX)
		Xreg <= D[RamWidth-1:0];
	if (WriteY)
		Y <= D[RamWidth-1:0];
	if (WriteZ)
		Z <= D[RamWidth-1:0];
end

lpm_counter SPreg (	.clock (clk),
					.data (D[XWidth-1:0]),
					.sload (WriteSP),
					.cnt_en (CountSP),
					.updown(SPup),
					.Q (SP));
defparam
	SPreg.lpm_type = "LPM_COUNTER",
	SPreg.lpm_width = XWidth;

endmodule

						


Тогда и мультиплексор базового адреса нужно чуть переделать:
module QuatCoreMemBaseMuxWBand (input [RamWidth-1:0] X, input [RamWidth-1:0] Y, input [RamWidth-1:0] Z, input [RamWidth+1:0] SP,
								input [1:0] adr, output [RamWidth-1:0] Q, output [1:0] ByteMode);

parameter RamWidth = 8;
							
assign Q = 	(adr == 2'b00)? X :
		(adr == 2'b01)? Y :
		(adr == 2'b10)? Z :
				SP[RamWidth-1:0];
							
assign ByteMode = (adr == 2'b11)? SP[RamWidth+1:RamWidth] : 2'b00;
							
endmodule


Ну и в мультиплексоре "квадратных скобок" мы почему-то два бита не в том порядке использовали, исправляемся:

//ByteMode[1] = 0: Word
//ByteMode[1] = 1: Byte
//ByteMode[0] = 0: Low
//ByteMode[0] = 1: High

module QuatCoreMemSquareByteMux (input [15:0] Memory, input [RamWidth-1:0] Regs, input DoMemory, input [1:0] ByteMode, output [15:0] Q);

parameter RamWidth = 8;

assign Q = 	(~DoMemory)? 	Regs :
		(~ByteMode[1])? Memory :
		(~ByteMode[0])?	{8'h00, Memory[7:0]} :
				{8'h00, Memory[15:8]};
							

endmodule


Ещё и параметр EnableByteMode из этого модуля убрали: достаточно его в модуле регистров X/Y/Z/SP. Если там EnableByteMode=0, выходом ByteMode всегда будут нули, а дальше синтезатор уже сообразит, как пообрубать лишнюю логику!

Запустим на симуляции отдельно QuatCore, без периферии. Просто чтобы не ждать, как мучительно на малой скорости передаётся каждый символ. Вот что получается:


Для понимания, что творится, приведём листинг, в этот раз он очень короткий:
    main proc
0  1010                  SIO     UART
1  FD10                  SP      hello
2  8AB0                  C       CALL(print)
3  B050      @@endless:  JMP     @@endless
    main endp
    print proc
4  8850      @@start:    ZAcc    RoundZero
5  83FC                  SUB     [SP]
6  BC83                  JGE     C
7  00F3                  OUT     [SP++]
8  B030                  JMP     @@start
    print endp


Уже знакомое нам значение 192 = 0xC0 поступает сначала в селектор ввода-вывода (правда, он не подключён), а потом заносится в SP.

Прыжок на процедуру, как всегда, "спрятался", но благодаря отображению SrcDiscard и DestDiscard от нас не спрячешься!

Странное значение 195 = 0xC3 - это опять результат работы нашего компилятора. Он знал, что для команды ZAcc понадобятся только два младших бита, и они оба должны быть единичными, а для прыжка @@endless: нужны только ЧЕТЫРЕ младших бита, и там должна лежать тройка. Вот тройка на месте.

А дальше, в команду SUB уже должен поступить наш первый символ. И да, это [0]H, то есть старший байт нулевой, а в младшем символ H.
РАБОТАЕТ!

Потом возврат из процедуры не срабатывает (ещё не ноль), зато срабатывает прыжок на @@start. И дальше видно, как достаётся символ за символом.

Наконец, глянем, сможем ли мы вовремя выйти из процедуры:


Видна передача символов CR (Carriage Return, 0xD) и LF (Line Feed, 0xA), после чего мы получаем нолик - и возвращаемся из процедуры аккурат в бесконечный цикл.

И наконец, ЖЕЛЕЗО!
Задолбали уже все эти симуляции, пора возвращаться к железу!

Для этого поднимаемся уровнем выше и настроим параметры:


RamWidth = 6, RomWidth = 4 (не часто встретишь программу из 9 строк!), отключаем пока незадействованные модули (SPI, LCD), UART настраиваем на тактовую частоту 4 МГц и скорость передачи 460 800 бод. На 921600 при тактовой в 4 МГц по-моему не выходит, слишком большое расхождение скорости.

Наконец, тоскливо взираем на самый верхний уровень схемы, toplevel.bdf:


Тут уже присоединены видеопроцессор и генератор тестового изображения, но по логике вещей они не должны никак повлиять на нашу маленькую программку :)

На самом деле, чем толще становится QuatCore - тем спокойнее, ведь он "подмял под себя" уже почти всю периферию, что у меня есть. А потому я могу надеяться, что все nCS=1, т.е присоединённые по SPI устройства "спят", при том "висящих в воздухе" входов также нет.

Со статической памятью (SRAM) и быстродействующим АЦП пока всё не так гладко - они делят одну 8-битную шину. У меня уже заканчивались пины на отладочной плате, поэтому решил таким способом сэкономить. Идея в том, чтобы заставить АЦП гнать данные напрямую в память, "не прогоняя их через ПЛИС", тогда как ПЛИС будет только адресом управлять. А когда мы захотим прочитать из SRAM, мы должны отключить АЦП. Это ещё предстоит. Но сейчас, по крайней мере, всё должно быть нормально.

Многие знания - многие печали! Как-то раньше прошивал ПЛИСку вообще не задумываясь, что там на тех выводах, которые мне В ДАННЫЙ МОМЕНТ НЕ НУЖНЫ. А сейчас почему-то стремает...

Ладно, пора прошиваться!

Синтезируется оно в 1286 ЛЭ - да, видеопроцессор и все FIFO сюда входят, ещё и селектор синхроимпульсов, который и не догадывается что АЦП всё равно отключена.

Максимальная частота 25,06 МГц - замечательно :)



We're back in business!

PS. Сохранился старый "глюк", что при включении QuatCore он сразу подаёт нолик по UART. Будем считать его фичей, как одиночный "бип" при включении персонального компьютера. А сейчас ещё и 0xC0 лишний пришёл. Это я так "замечательно" сделал QuatCoreIOselector, что когда ему отключаешь все устройства, кроме одного, он перестаёт различать между собой команды OUT (0x00) и SIO (0x10), справедливо считая, что когда устройство у тебя всего одно, SIO нам не нужен! Прикольно.
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Нахождение двух самых отдалённых точек

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

  • Слишком общительный счётчик

    Вчера я чуть поторопился отсинтезировать проект,параметры не поменял: RomWidth = 8 вместо 7, RamWidth = 9 вместо 8, и ещё EnableByteAccess=1, чтобы…

  • Балансируем конвейер QuatCore

    В пятницу у нас всё замечательно сработало на симуляции, первые 16 миллисекунд полёт нормальный. А вот прошить весь проект на ПЛИС и попробовать "в…

  • Ковыряемся с сантехникой

    Наконец-то закрыл сколько-нибудь пристойно трубы, подводящие к смесителю, в квартире в Москве: А в воскресенье побывал на даче, там очередная…

  • Мартовское велосипедное

    Продолжаю кататься на работу и с работы на велосипеде, а также в РКК Энергию и на дачу. Хотя на две недели случился перерыв, очередная поломка,…

  • Обнаружение на новом GPU - первые 16 мс

    Закончилась симуляция. UFLO и OFLO ни разу не возникли, что не может не радовать. За это время мы дошли до строки 0x10F = 271. Поглядим дамп памяти:…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments