nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Подключаем статическую память к "ускоренному" QuatCore

Надеюсь, что эта статическая память будет чисто в "отладочных целях": записать туда видеокадр целиком и потихоньку переправить на компьютер по UART, чтобы потом можно было проверить, правильно ли сработал видеообработчик. Видеообработчику эта память не нужна, он должен "на лету" работать!

На 4 МГц мы её уже подключали, теперь только нужно модифицировать интерфейс для "ускоренного" QuatCore с его конвейерными заморочками...


В отличие от модуля QuatCoreMem, которому можно заказать под 60 различных адресов (комбинации 2-3 регистров с разными множителями), из-за чего он не может сформировать ответ заблаговременно, модуль для работы с внешней СОЗУ (SRAM) может получить только одно значение, на которое в данный момент указывает регистр ER (External memory Register). Поэтому и значение формируется заблаговременно. Та же история с записью - адрес уже сформирован, остаётся только "мигнуть" #WE - и значение будет записано. Поэтому этот модуль срабатывает всего за один такт, и не требует останавливать конвейер ни на чтении, ни на записи.

Так что единственное дополнение - это входы SrcStall, SrcDiscard и DestStall, чтобы он не срабатывал на команды, которые не должны исполняться, и не инкрементировал регистр ER несколько раз, если "соседняя команда" выполняется больше такта. Получается вот так:

//обращение к внешней памяти
//чуть потеснили IOselector в плане адресов.
//наши DestAddr: 01xx_xxxx
//наши SrcAddr:  1001_1xxx

//DestAddr:
//0100_xxxx - задать младшие 16 бит адреса, ERL (External memory Register Low)
//0101_xxxx - задать старшие 16 бит адреса, ERH (External memory Register High)
//011x_xxxx - записать в память и сделать инкремент, [ER++]

//SrcAddr:
//1001_1xxx - чтение из памяти и инкремент, [ER++]
module QuatCoreFastSRAM (input clk, input [7:0] DestAddr, input [7:0] SrcAddr, input DestStall, input SrcStall, input SrcDiscard, input [15:0] D,
						output [18:0] RAMaddr, output RAM_CE0, output RAM_CE1, output RAM_RW,
						inout [7:0] RAM_data);
						
wire IsOurDest = (~DestStall)&(~DestAddr[7])&DestAddr[6];
wire IsOurSrc = (~SrcStall)&(~SrcDiscard)&(SrcAddr[7:3] == 5'b1001_1);

wire LoadERL = IsOurDest & (~DestAddr[5]) & (~DestAddr[4]);
wire LoadERH = IsOurDest & (~DestAddr[5]) & DestAddr[4];
wire WriteMem = IsOurDest & DestAddr[5];

wire DoIncrement = WriteMem | IsOurSrc;

wire [19:0] ER; //External memory Register
wire TC; //Terminal Count

lpm_counter ERL (	.clock (clk),
			.cnt_en (DoIncrement),
			.sload (LoadERL),
			.data (D),
			.Q (ER[15:0]),
			.cout (TC));
defparam
	ERL.lpm_direction = "UP",
	ERL.lpm_port_updown = "PORT_UNUSED",
	ERL.lpm_type = "LPM_COUNTER",
	ERL.lpm_width = 16;
	
lpm_counter ERH (	.clock (clk),
					.cnt_en (TC & DoIncrement),
					.sload (LoadERH),
					.data (D[3:0]),
					.Q (ER[19:16]));
defparam
	ERH.lpm_direction = "UP",
	ERH.lpm_port_updown = "PORT_UNUSED",
	ERH.lpm_type = "LPM_COUNTER",
	ERH.lpm_width = 4;
	
assign RAMaddr = ER[19:1];
assign RAM_CE0 = ER[0];
assign RAM_CE1 = ~ER[0];
assign RAM_RW = (~WriteMem) | clk;

assign RAM_data = WriteMem? D[7:0] : 8'bzzzz_zzzz;

endmodule


Подсоединяем этот модуль к ядру QuatCore:


И затем - к внешним выводам, на модуле верхнего уровня:


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

По крайней мере, вся эта хреновина нормально синтезируется, в 664 ЛЭ, в них входит и детектор/селектор синхроимпульсов, не хотелось их выкидывать из схемы, скоро пригодятся! Предельная частота 29,07 МГц - неплохо.

По аппаратной части мы как будто бы закончили. Теперь нужна ещё программа, которая будет работать с памятью. Возьмём её с прошлого раза, добавим в неё кусочек, где по SPI настраивается тактовая частота на 25 МГц:


;проверяем работу статической памяти, подключённой к ПЛИС

%include "QuatCoreConsts.inc"
%include "Win1251.inc"
.rodata
	EthDisable	dw	2,3,0x22,0x54,0x00,0x01,3,0x22,0x66,0x00,0x18,2,0x22,0x6F,0x02
	Hello	Int16 'Привет лунатикам! (через СОЗУ)',0x0D,0x800A
.data
	Stack	dw	?,?
.code
	main proc
		SetClock proc
		;конфигурирует Ethernet-контроллер на частоту 25 МГц и отключает собственно Ethernet
		;SP=0 при включении питания, на ПЛИС с этим строго
					SIO		ETH
					j		[SP++]	;количество посылок (счёт с нуля)
			@@EthWord:	k		[SP++]	;количество байт в посылке (счёт с нуля)
			@@EthByte:	OUT		[SP++]
					kLOOP		@@EthByte
					;нужно, чтобы nCS сбросилось в единицу
					NOP		IN
					jLOOP		@@EthWord
		SetClock endp				
				
				SP		Stack
				SIO		UART
				CALL		SetInitAdr
				X		Hello
				CALL		print
			
				CALL		SetInitAdr
				i		31
		@@out:		OUT		[ER++]
				iLOOP		@@out
		@@endless: 	JMP 		@@endless
	main endp
	
	print proc
				[SP++]	i
				i		0
		@@start:	[ER++]	[X+i]	;отправляем во внешнюю статическую память
				Acc		[X+i]
				i++		0
				SUB		0
				JGE		@@start
				i		[--SP]
				JMP		[--SP]
	print endp
	
	SetInitAdr proc
		ERL		0
		ERH		0
		JMP		[--SP]
	SetInitAdr endp


Но процедуру SetClock поначалу "закомментируем", надо сначала проверить на 4 МГц, убедиться, что "ничего не сломали", когда переходили на "ускоренный" QuatCore.

Запускаем компиляцию и получаем следующий "отчёт":

Загружаем файл конфигурации транслятора
Файл конфигурации прочитан, готовы к работе
Обрабатываем файл HelloSRAM.asm
Конфликт (Hazard) между командами i и [X+i], процедура print, строки:
				i		0
		@@start:	[ER++]	[X+i]	;отправляем во внешнюю статическую память
Вставляем NOP

Пытаемся оптимизировать таблицу вызова процедур
print      = 000A 
SetInitAdr = 0014 

Бит 4 адреса
не повторяет предыдущих (сигнатура 1)
Сопоставление его биту SrcAddr[3] прошло успешно
Бит 3 адреса
не повторяет предыдущих (сигнатура 2)
Сопоставление его биту SrcAddr[2] прошло успешно
Бит 2 адреса
в точности повторяет бит 4...
Бит 1 адреса
в точности повторяет бит 3...
Бит 0 адреса
Всегда ноль...
2 входных бит оказались не сопоставленными битам адреса
Извините, данный код ещё не готов...
Компиляция завершена успешно

Ширина адреса сегмента кода (и регистра PC):           5  
Ширина адреса сегмента данных (и регистров X,Y,Z,SP):  6  
Количество инициализированных слов данных:             49 
Количество инициализированных слов кода:               23 
Количество адресов процедур:                           2  

Адреса процедур:
print      = 000A(поле Src = B1) 
SetInitAdr = 0014(поле Src = B2)


И тут меня "кольнули" команды вызова процедур. Дело вот в чём: при работе "оптимизатора", было сказано, что отдельные биты адреса будут привязаны к SrcAddr[3] и SrcAddr[2]. Но в получившихся SrcAddr = 0xB1 и 0xB2, биты 2 и 3 - НУЛЕВЫЕ. Что-то здесь не то.

Похоже, надпись

"2 входных бит оказались не сопоставленными битам адреса
Извините, данный код ещё не готов..."


здесь оказалась неспроста, и моя версия из прошлого как всегда мне задолжала и заслуживает пинка...

Пока у нас была одна-единственная процедура для вызова, QuatCoreCallTable превращалась в константу, и мы могли с равным успехом использовать любую команду от B0 до BF, ничего бы не поменялось. А вот здесь, когда процедуры хотя бы ДВЕ, уже даёт сбой.


[Размышления вслух]Итак, создаётся массив Signatures размером в 5 элементов - "сигнатуры" для каждого из бит адреса. И впридачу SignatureIndex, того же размера.

Ещё один массив, GroupIndex, имеет столько же элементов, сколько у нас процедур, сейчас их 2: print и SetInitAdr. Сначала он заполняется нулями.

Вычисляется константа AllOnes = 3. Если сигнатура будет ноль, значит, данный бит всегда нулевой. А если она будет AllOnes, значит, всегда единицей. Пока логично. Также вычисляется mask = 16 - такой маской выделяется старший бит адреса, на данный момент 4-й (при нумерации с нуля).

Дальше интереснее: создаётся массив GroupsPop[] из 16 элементов, инициализируется нулями, но затем на 1-ю позицию заносится общее число процедур, в данном случае 2:

{2;0;0;....;0}

Ещё инициализируется MaxAllowable=8 (разрешается 8 процедур с нулём в старшем бите адреса и 8 с единицей, в противном случае этот бит использовать нельзя), GroupCount=2 (когда выберем бит, произойдёт разделение на 2 группы), SrcBit=3 (сейчас решаем судьбу бита под номером 3 в SrcAddr, т.е старшего из 4). Эти 3 числа - "хардкод", не зависит ни от ширины адреса ПЗУ, ни от количества процедур.

Ещё один массив, Unresolved, размером 5 (т.е ширина адреса ПЗУ), будет содержать биты, которые не всегда нулевые, не всегда единичные, и к SrcAddr "напрямую" (без логических функций) привязать их не удалось. И UnresolvedCount - сколько их таких объявилось, первоначально 0.

bitIndex=4 (обработка самого старшего бита адресов процедур) - и мы начинаем цикл по всем этим битам.

Условно, все адреса процедур выписываются один под другим:

0_1010 (0A=print)
1_0100 (14=SetInitAdr)


И начинаем с первого столбца:
0
1

прочитав сверху вниз: 01, и прочитав это как двоичное число, получаем сигнатуру 1. Была бы она 0, записали бы, что данный бит всегда нулевой, и соотв. строку: assign Adr[5] = 1'b0;. Была бы она AllOnes=3, записали бы, что данный бит всегда единичный. Но сигнатура 1, такой у нас раньше не было, заносим её в массив Signatures, под нулевым номером, и ещё заносим SignatureIndex[0] = 3, что означает: этот бит у нас будет "жестко" привязан к SrcAddr[3].

Раньше все процедуры принадлежали к одной группе, "нулевой" (как об этом сообщал массив GroupIndex), теперь попытаемся их разделить на 2 группы, первая в которой старший бит нолик, вторая - где старший бит единица. И населённость этих групп пересчитываем. Была одна-единственная группа населённостью 2, теперь две, поровну: {1;1}. И GroupIndex расставляем: {0;1}, т.е процедура print (0x0A) принадлежит нулевой группе, а процедура SetInitAdr (0x14) - к первой.

Проверяем, что в каждой группе меньше элементов, чем MaxAllowable=8, так и есть (ВНЕЗАПНО), значит всё хорошо.

Добавили строку:
assign addr[4] = SrcAddr[3];

победно доложили об этом в логе:
Бит 4 адреса
не повторяет предыдущих (сигнатура 1)
Сопоставление его биту SrcAddr[3] прошло успешно


Уменьшили SrcBit на единичку, до 2, т.е бит 3 мы уже "пристроили".

А после этого предварительно разделяем "населённость групп" ещё на два, превратив {1;1} в {1;0;1;0}. Это подготовка к следующему шагу, где групп будет уже 4. И соответственно, GroupCount умножаем на 2 (сдвигаем влево на 1), становится 4. Логично.

mask сдвигается вправо, с 16 на 8, bitIndex уменьшается на единичку, до 4, и мы начинаем следующую итерацию.

Выделяем очередной столбец:
1
0

Прочитываем сверху вниз: 102 = 2. Такая сигнатура - не 0 ("все нули") и не 3 ("все единицы"), и раньше её не встречалось, поэтому говорим: для кодирования этого бита задействуем SrcAddr[2]. Эту сигнатуру тоже записываем в массив Signatures, под номером 1. И SignatureIndex[1] = 2, т.е кодируется 2-м битом SrcAddr. Хорошо.

Все наши процедуры теперь раскидываем из двух групп в 4, по этому биту, теперь процедура print сидит в группе 1 (поскольку задействованные сейчас биты 012=1), процедура SetInitAdr - в группе 2 (для неё 102=2), а населённость групп: {0;1;1;0}. Ни в одной группе число процедур не превышает MaxAllowable=4, значит всё хорошо.

Добавили строку на верилоге, сдвинулись, вроде бы жизнь налаживается. Населённость групп заблаговременно ещё разбили, уже на 8:
вместо {0;1;1;0} стало {0;0;1;0;1;0;0;0}. Короче, каждый нолик "удваивается", а единичка превращается в 1;0.

mask сдвигается вправо, с 8 на 4, bitIndex уменьшается на единичку, до 3, и мы начинаем следующую итерацию.

И там уже начинаются повторения: либо такие столбцы уже были ранее, поэтому выделять новые биты под них не нужно, либо все нули, или все единицы.

И в итоге, закончив обработку, мы так и остались с GroupIndex = {1;2}. Если бы были применены все 4 бита SrcAddr, то номер группы и стал бы младшими 4 битами команды.

Так что на самом деле "недописанный" кусок был прост до безобразия: если SrcBit неотрицательный по окончании работы (т.е ещё остались неиспользованные биты SrcAddr), то нужно все элементы GroupIndex сдвинуть влево на значение SrcBit+1, т.е умножить на 2SrcBit+1. Ладно, сейчас напишем...


После исправления получается так:
Загружаем файл конфигурации транслятора
Файл конфигурации прочитан, готовы к работе
Обрабатываем файл HelloSRAM.asm
Конфликт (Hazard) между командами i и [X+i], процедура print, строки:
				i		0
		@@start:	[ER++]	[X+i]	;отправляем во внешнюю статическую память
Вставляем NOP

Пытаемся оптимизировать таблицу вызова процедур
print      = 000A 
SetInitAdr = 0014 

Бит 4 адреса
не повторяет предыдущих (сигнатура 1)
Сопоставление его биту SrcAddr[3] прошло успешно
Бит 3 адреса
не повторяет предыдущих (сигнатура 2)
Сопоставление его биту SrcAddr[2] прошло успешно
Бит 2 адреса
в точности повторяет бит 4...
Бит 1 адреса
в точности повторяет бит 3...
Бит 0 адреса
Всегда ноль...
2 входных бит оказались не сопоставленными битам адреса
Компиляция завершена успешно

Ширина адреса сегмента кода (и регистра PC):           5  
Ширина адреса сегмента данных (и регистров X,Y,Z,SP):  6  
Количество инициализированных слов данных:             49 
Количество инициализированных слов кода:               23 
Количество адресов процедур:                           2  

Адреса процедур:
print      = 000A(поле Src = B4) 
SetInitAdr = 0014(поле Src = B8) 


Вот это уже больше похоже на правду!

Отсинтезировал, запустил, получил полный мусор на выходе. Вспомнил, что UART уже настроен на тактовую частоту 25 МГц и скорость 921600 бод, а надо было его настроить на 4 МГц и 460800 бод.



Ага, на 4 МГц хотя бы фурычит. Ну и теперь "смертельный номер": раскомментируем код, который устанавливает 25 МГц, меняем настройки UART назад - и смотрим, что получится.

Загружаем файл конфигурации транслятора
Файл конфигурации прочитан, готовы к работе
Обрабатываем файл HelloSRAM.asm
Конфликт (Hazard) между командами i и [X+i], процедура print, строки:
				i		0
		@@start:	[ER++]	[X+i]	;отправляем во внешнюю статическую память
Вставляем NOP

Пытаемся оптимизировать таблицу вызова процедур
print      = 0011 
SetInitAdr = 001B 

Бит 4 адреса
Всегда единица...
Бит 3 адреса
не повторяет предыдущих (сигнатура 1)
Сопоставление его биту SrcAddr[3] прошло успешно
Бит 2 адреса
Всегда ноль...
Бит 1 адреса
в точности повторяет бит 3...
Бит 0 адреса
Всегда единица...
3 входных бит оказались не сопоставленными битам адреса
Компиляция завершена успешно

Ширина адреса сегмента кода (и регистра PC):           5  
Ширина адреса сегмента данных (и регистров X,Y,Z,SP):  6  
Количество инициализированных слов данных:             49 
Количество инициализированных слов кода:               30 
Количество адресов процедур:                           2  

Адреса процедур:
print      = 0011(поле Src = B0) 
SetInitAdr = 001B(поле Src = B8) 


Откомпилилось без проблем, адреса процедур корректные.




Работает! Это очень радостно, поскольку там могли быть проблемы с таймингами, всё очень "впритык". Возможно, разогрейся эта статическая память градусов до 60, и получи пониженное напряжение, 3 вольта вместо штатных 3,3 вольт - и уже начнёт "глючить", в смысле что не успевать вовремя сформировать выходные значения, либо запишет значения по неверным адресам, поскольку новый адрес не успел "распространиться".

Но как уже говорилось: мне эта память чисто в отладочных целях, и её работа "в крайних режимах" мне особо не нужна. Работает при комнатной температуре на 3,3 вольтах - я счастлив :)

Следующий этап: сделать своеобразный "DMA", так что в эту статическую память будет записываться оцифрованная картинка (вообще минуя ПЛИС), а потом уже QuatCore займётся её пересылкой на компьютер.
Tags: #ce0, #ce1, #we, ПЛИС, программки, работа, странные девайсы
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 

  • 3 comments