nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

QuatCore: селектор ввода-вывода

Опять вожжа шлея под хвост попала - надо бы вовсю заняться обработкой видео, но я вместо этого ковыряю MicroSD-карточку. Дескать, хочется легко и непринуждённо все этапы видеообработки наблюдать на большом экране компьютера, через UART картинку в реальном времени чего-то как-то не передашь, через Ethernet как-то можно (там на макетке стоит Ethernet-контроллер ENC624J600), но очень уж навороченно, все эти 7 кругов ада уровней OSI, которые со стороны ПЛИС придётся реализовать практически все. Но узким местом всё равно будет передача по SPI между ПЛИС и Ethernet-контроллером, не более 14 МБит/с, а параллельный интерфейс на макетке не распаян.

Так что уж лучше MicroSD для начала по SPI, там хоть 25 МБит/с - всяко побольше. Пока что припаял гнездо для карточки, тоже из Линовских запасов ардуиновскую платку "Wireless SD shield" и мучительно соображаю, как реализовать инициализацию и передачу данных.

Алгоритм инициализации, особенно поддерживающий карточки SDHC (свыше 2 ГБ) довольно муторный: подать 74 такта на SCK при CS=1, потом CMD0, потом CMD8 и т.д., обязательно дожидаясь, когда карта соизволит ответить.

Чтобы это отладить за разумное время, нужно результат каждого этапа передавать по UART. До сих пор у нас было одно устройство ввода-вывода, то это был UART, потом LCD, теперь будет всё вместе. Можно было бы им просто присвоить разные адреса, может в итоге так и будет, но тогда нужны отдельные процедуры для разных устройств ввода-вывода. А ведь LCD и UART до сих пор "приводились в действие" одним и тем же print, и потенциально - одной и той же процедурой преобразования 16-битного числа в десятичную форму "с фиксированной запятой".

Так что сейчас решил ввести один адрес для вывода: "OUT", один для ввода: "IN", и ещё адрес для выбора устройства, с которым общаемся по шине в данный момент: "SIO" (Select I/O). Вроде бы получилось:



Заодно чуть улучшил компилятор, чтобы он не настаивал на вызове процедур через стек :) В итоге, сейчас весь код программы, которая инициализирует ЖК-экран, добавляет туда 2 новых символа, выводит 3 строки, а потом отправляет 2 строки по UART, занимает 14 слов, или 28 байт :) Полный размер схемы составил 509 ЛЭ - это процессор, контроллеры UART и LCD, и ещё таймер, мигающий светодиодиком раз в секунду, почему-то успокаивает он меня.


Приведём изменившийся фрагмент QuatCore:



У нас появился новый модуль QuatCoreIOselector. Именно он "висит" на шинах DestAddr и SrcAddr, тогда как модули UART и LCD теперь запускаются от него, сами они адреса больше не проверяют.

Вот код данного модуля:
module QuatCoreIOselector (input clk, input [7:0] DestAddr, input [7:0] SrcAddr, input [15:0] DataBus, output UARTtxEN, output LCD_EN, output SDtxEN, output UARTrxEN, output SDrxEN);

wire isSelection = (~DestAddr[7])&DestAddr[6];
wire isIO_out = (~DestAddr[7])&(~DestAddr[6]);
wire isIO_in = (SrcAddr[7:4] == 4'b1001);

reg [1:0] sel = 2'b0;

always @(posedge clk) if (isSelection)
	sel <= DataBus[1:0];
	
assign UARTtxEN = (sel==2'b00) & isIO_out;
assign LCD_EN = (sel==2'b01) & isIO_out;
assign SDtxEN = sel[1] & isIO_out;

assign UARTrxEN = (sel==2'b00) & isIO_in;
assign SDrxEN = sel[1] & isIO_in;

endmodule


На шине DestAddr этот модуль откликается на 2 команды. OUT (00xx_xxxx, или 0x00..0x3F) - вывод информации через выбранное устройство. SIO (01xx_xxxx, оно же 0x40..0x7F) - задать текущее устройство ввода-вывода.

Чтобы запомнить, какое устройство выбрано, внутри находится 2-битный регистр sel, и по команде SIO в него "защёлкиваются" младшие два бита из шины данных. 00 соответствует UART, 01 - LCD, 1x - SD.

На шине SrcAddr модуль откликается на команду IN (1001_xxxx) - ввод информации через выбранное устройство. Если шина DestAddr была "наполовину пуста" за счёт отсутствия модуля Imm (immediate), то в SrcAddr пришлось немножко потеснить АЛУ. Ему были отведены команды 100x_xxx (32 штуки), но реально задействовалось всего 2 бита, чтобы выбрать между Acc, UAC и C, так что половину диапазона мы с чистой совестью откусили. Теперь 1000_xxxx принадлежит АЛУ, а 1001_xxxx - на I/O.

Но пока устройства ввода у нас не реализованы, да и контроллер SD ещё не готов.

Покажем теперь код очередной тестовой программы:

;то же самое, но дополнительно выдаём Good News, everyone на компьютер
%include "QuatCoreConsts.inc"
%include "LCD_code_page_1.inc"
.rodata
;адр 0
	InitLCD	dw	0x330		;установить разрядность интерфейса (одиночная посылка, команда, 0011)
	Init1		dw	0x330		;установить разрядность
	Init2		dw	0x330		;установить разрядность
	Init3		dw	0x320		;установить разрядность 4 бита
	Init4		dw	0x12A		;установка параметров (двойная посылка, команда, 0010 1010 = 4 бита, страница 1)
	Init5		dw	0x10C		;включение дисплея (двойная посылка, команда, 0000 1100 = включить, курсора нет, ничего не мигает)
	Init6		dw	0x101 	;очистка дисплея (двойная посылка, команда, 0000 0001)
	Init7		dw	0x106		;установка режима ввода данных (двойная посылка, команда, 0000 0110 = курсор вправо, дисплей не двигается)
	Init8		dw	0x140		;выбор области CGRAM, установка на нулевом адресе
	Init9		dw	0x00E		;нулевой символ, 0-я строка
	InitA		dw	0x00A		;1-я строка
	InitB		dw	0x00A		;2-я строка
	InitC		dw	0x00B		;3-я строка
	InitD		dw	0x00B		;4-я строка
	InitE		dw	0x00A		;5-я строка 
	InitF		dw	0x00A		;6-я строка
	Init10	dw	0x00E		;7-я строка (для курсора вообще, но мы его не исп)
	Init11	dw	0x00E		;1-й символ, 0-я строка
	Init12	dw	0x00A		;1-я строка
	Init13	dw	0x00A		;2-я строка
	Init14	dw	0x01A		;3-я строка
	Init15	dw	0x01A		;4-я
	Init16	dw	0x00A		;5-я
	Init17	dw	0x00A		;6-я
	Init18	dw	0x00E		;7-я
	Init19	dw	0x8180	;выбор области DDRAM,нулевая позиция  (адр. в памяти 0x19 = 25)
	Row0		dw	0,1,'-','з','н','а','к',' ','ч','е','т','ы','р','ё','х'	;(адр. 40)
	Row1		dw	0x1C0,'Г','а','м','и','л','ь','т','о','н',' ',' ','Р','о','д','р','и','г','е','с'	;(адр. 60 в конце)
	Row2		dw	0x194,'Б','р','а','н','е','ц',' ',' ','Ш','м','ы','г','л','е','в','с','к','и',f'й'	;(адр. 80 в конце)
ORG -64	
	UartStr	dw	'G','o','o','d',' ','n','e','w','s',',',' ','e','v','e','r','y','o','n','e','!',13,10
	UartStr1	dw	'м','ы',' ','р','а','б','о','т','а','е','м',' ','и',' ','с',' ','U','A','R','T',',',' ','и',' ','с',' ','Ж','К',f'!'
;для интересу, сейчас без стека. Адрес возврата сохраним в регистр C	
.code
	main proc
				SIO		LCD
				SP		InitLCD
				C		CALL(print)
				SP		Row0
				C 		CALL(print)
				SIO		UART
				SP		UartStr
				C		CALL(print)
		@@endless: 	JMP 		@@endless
	main endp
	
	;изменяет только SP (после вызова он указывает сразу ПОСЛЕ последнего символа строки)
	;адрес возврата ищет в рег. C
	print	proc
		@@start:	OUT		[SP]	
				Acc		[SP++]		
				SUB		0
				JGE		@@start
				JMP		C	
	print	endp


В самом начале мы видим новый заголовочный файл, QuatCoreConsts.inc. Вот его содержание:
;константы для аргументов QuatCore
;можно было бы их в сам компилятор "зашить", но пущай так
.data ;костыль нашего компилятора - он ищет EQU только в сегменте данных

;устройства ввода-вывода. Выбор производится командой SIO
UART	EQU	0
LCD	EQU	1
SD	EQU	2

;константы, которыми можно инициализировать аккумулятор, командой ZAcc
MinusThreeHalf	EQU	0
MinusOne	EQU	1
ThreeHalf	EQU	2
RoundZero	EQU	3


Дальше идут уже знакомые "строки" для инициализации ЖК-экрана, для показа сообщения, а потом мы видим директиву:

ORG -64


т.е последующие строки размещаются по адресу "-64". Проблема в том, что строка UartStr попадала на адрес 0x51, а такое значение присвоить регистру "непосредственно" нельзя. Можно только первые 64 и последние 64 значения в памяти, так выходит, поскольку для Imm-значений происходит расширение знака.

Директива ORG -64 заставляет компилятор запихать данные за 64 слова до окончания области памяти, чтобы к ним был простой доступ. Вот что значит, память забили!

Место под стек мы не зарезервировали вообще, т.к решили на этот раз обойтись без него. По сути, вся память здесь могла быть ROM - и всё бы отработало как надо.

В коде программы видим, что решили для разнообразия использовать SP как аргумент - указатель на строку, которую надо вывести :) Адрес возврата хранить в регистре "C" (он заведомо 16-битный, в него всё влезет!).

Теперь кроме "макроса" / директивы CALL, стоящей на месте DestAddr, у нас появилась CALL(...), стоящая на SrcAddr. Эта директива просит занести метку в таблицу вызовов функций, а вместо CALL(...) подставить одну из команд Call0..CallF, осуществляющие переход по соответствующим адресам с выводом адреса возврата на шину данных.

Регистр PC сейчас занимает 4 бита :) Но программа всё же делает что-то осмысленное.


Дальше на очереди - модуль SPI для SD-карточки и её инициализация...
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

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

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

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

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

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

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

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 4 comments