nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

Hello, world на быстром QuatCore

Вот код нашей программы:
;проверяем UART на разогнанном до 25 МГц QuatCore
;точнее, он пока запустится на 4 МГц, но способен работать и на 25 МГц.
%include "QuatCoreConsts.inc"
%include "Win1251.inc"
.rodata
	hello		dw	'Hello, World!',13,0x800A
.data
	Stack		dw	?
.code
	main proc
				SP		Stack
				X		hello
				CALL		print
		@@endless:	JMP		@@endless
	main endp
	
	
	;посылает строку куда-нибудь. Пока модуль ввода-вывода всего один, SIO не нужен (нужный модуль по умолчанию будет активен)
	;X указывает на начало строки текста
	;конец обозначается отрицательным числом (у нас НЕТ ФЛАГА НУЛЯ!!!)
	;меняет значение регистра Acc и i.
	print proc
				i		0
		@@start:	OUT		[X+i]
				Acc		[X+i]
				i++		0
				SUB		0
				JGE		@@start
				JMP		[--SP]
	print endp


Секция данных содержит строку, сопровождающуюся символом переноса строки (CR+LF), причём последний также содержит единичный старший бит (0x800A вместо 0x000A), что указывает на конец строки. Для стека мы пока зарезервировали всего одно слово, адрес возврата.

Как говорилось, пока подключено всего одно устройство ввода-вывода, нам не нужен SIO (Select I/O), и мы его не написали.

Попробуем откомпилить это дело...


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

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

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

Ширина адреса сегмента кода (и регистра PC):           4  
Ширина адреса сегмента данных (и регистров X,Y,Z,SP):  4  
Количество инициализированных слов данных:             16 
Количество инициализированных слов кода:               12 
Количество адресов процедур:                           1  

Адреса процедур:
print = 0004(поле Src = B0) 


Как видно, был обнаружен один Hazard и довольно успешно исправлен. Как здесь обойтись без NOP'а - не очень понятно, ведь действительно, как только мы инициализировали i=0, нам нужно обратиться по [X+i], никаких других действий у нас нет, пока мы оттуда не получим значение!

Также видно, что "умный компилятор" немножко прифигел от одной-единственной процедуры, он честно сформировал модуль QuatCoreCallTable.v:

//адреса дл¤ вызова процедур, "вшитые" в модуль QuatCorePC
module QuatCoreCallTable (input [7:0] SrcAddr, output [RomWidth-1:0] addr);
parameter RomWidth = 4;
	assign addr[3]=1'b0;
	assign addr[2]=1'b1;
	assign addr[1]=1'b0;
	assign addr[0]=1'b0;
//адреса процедур:
// print = 0004(поле Src = B0) 
endmodule


Да и листинг он сформировал правильный:
    main proc
0  FD0F                      SP      Stack
1  CD00                      X       hello
2  F3B0                      CALL    print
3  B003          @@endless:  JMP     @@endless
    main endp
    print proc
4  A000                      i       0
5  8900      NOP  0 ;AUTOMATICALLY INSERTED BY TRANSLATOR TO PREVENT HAZARD
6  00C4          @@start:    OUT     [X+i]
7  80C4                      Acc     [X+i]
8  A400                      i++     0
9  8300                      SUB     0
A  BC7A                      JGE     @@start
B  B0FF                      JMP     [--SP]
    print endp


Но чувствует себя немного неуютно, что перед ним была такая свобода - можно было любой номер команды указать при вызове, от 0xB0 до 0xCF, и ничего бы не поменялось :) Посмотрим в более сложных случаях, а пока, работает - не лезь :)

Копируем файлы QuatCoreCode.mif, QuatCoreData.mif и QuatCoreCallTable.v в папку с верилоговским проектом, меняем параметры FastQuatCore: RomWidth = 4, RamWidth = 4, и запускаем синтез.

Благодаря уменьшенной ширине регистров как для работы с RAM (X/Y/Z/SP), так и с ROM (PC), вся эта штука синтезировалась в 441 ЛЭ, и с таймингами всё хорошо: 27,78 МГц.

Потом вспоминаем ещё одну подлянку: когда мы только-только запускаем этот драндулет (reset=1), у нас SrcAddr=DestAddr=0, и это почему-то ни фига не команда NOP, это:
OUT   0

что может нам сразу же запустить передатчик UART. Может, оно не очень страшно, и даже своего рода фича, но давайте инициализируем наш конвейер значением 0x8900, вот это уже действительно NOP 0. И это не лучшее решение, поскольку если мы "перезапускаем" процессор, когда он уже, к примеру, застрял в бесконечном цикле, то "застрявшие" внутри конвейера данные могут устроить переполох. О переходе в "известное состояние" речи тогда не идёт. Впрочем, поскольку к тому времени уже могла поменяться память, наверное надёжнее всё равно будет перезапустить всю ПЛИС, чтобы она повторно считала данные из конфигуратора. Так что пока хватит и так:

module QuatCoreCodeROMwSTALL (	input [RomAddrWidth-1:0] Addr, input clk, input stall,
								output [7:0] SrcAddr, output reg [7:0] DestAddr = 8'h89);

	parameter RomAddrWidth = 8;

	wire [15:0] OP;
	reg [15:0] rOP = 16'h8900;
	//reg [15:0] rOP = 16'h0;
	always @(posedge clk) if (~stall) begin
		rOP <= OP;
		DestAddr <= rOP[15:8];
	end
		
	assign SrcAddr =  rOP[7:0];

	lpm_rom	lpm_rom_component (
				.address (Addr),
				.q (OP),
				.memenab (1'b1));
	defparam
		lpm_rom_component.intended_device_family = "FLEX10KE",
		lpm_rom_component.lpm_address_control = "UNREGISTERED",
		lpm_rom_component.lpm_file = "QuatCoreCode.mif",
		lpm_rom_component.lpm_outdata = "UNREGISTERED",
		lpm_rom_component.lpm_type = "LPM_ROM",
		lpm_rom_component.lpm_width = 16,
		lpm_rom_component.lpm_widthad = RomAddrWidth;
		
endmodule


Да, отсинтезировалось в такое же количество ЛЭ, и даже почему-то с бОльшей скоростью, до 28,09 МГц.

Подключим "отладочные пины" PC, SrcAddr, DestAddr, DataBus и MemAddr - и посмотрим, что там вообще творится...

Всё не очень хорошо: почему-то не инициализировался DestAddr = 0x89 (NOP), так и остался нулевым при "подаче питания", и как результат, UART заработал сразу же, передавая нулевой байт. Не так уж страшно, но я так и не понимаю логики: почему иногда инициализация регистров начисто игнорируется, и даже никакого Warning'а не даётся!? И ещё я пока не знаю: это косяк на синтезе, или на симуляции? Это скоро проверим...

Но это ладно, печально другое: после передачи этого нулевого байта, мы, как и положено, передали букву "H", а затем пошли передавать снова нулевой байт, и лишь за ним букву "e". По некоторому размышлению, я понял, что произошло. В этой схеме:


кое-чего не хватает. Да, передатчик UART останавливает процессор, если мы заставляем его передать следующий байт, а он ещё не успел передать предыдущий. Это всё верно. Но мы не ввели сигнал DestStall внутрь нашего ввода-вывода, чтобы не запускать передачу "ошибочно", когда команда "недействительна".

Дело в том, что прыжок (JMP, JGE/JL, JO/JNO) у нас происходит "очень тяжело": на выходе DestAddr аж два такта подряд появляются "недействительные команды". Так, во время прыжка с команды по адресу A:
    print proc
4  A000                  i       0
5  8900      NOP  0 ;AUTOMATICALLY INSERTED BY TRANSLATOR TO PREVENT HAZARD
6  00C4          @@start:    OUT     [X+i]
7  80C4                  Acc     [X+i]
8  A400                  i++     0
9  8300                  SUB     0
A  BC7A                  JGE     @@start
B  B0FF                  JMP     [--SP]
    print endp


к следующему такту, когда PC=6, у нас DestAddr = 0xB0 (команда JMP), который должен быть проигнорирован. А ещё такт спустя в DestAddr поступает команда с адреса 0xC, из неинициализированной области памяти, которая на самом деле инициализирована нулями. Вот и получается DestAddr = 0x00 (команда OUT), и наш передатчик, не долго думая, начинает это дело передавать. А поскольку с прошлого такта нам пришёл аргумент оттуда же, с адреса 0xC (тоже ноль) - вот и получается передача нулевого байта, о которой мы не просили.

Что ж, ещё разок корректируем модуль QuatCoreIOselector:

//DestAddr==000x_xxxx : 'OUT'
//DestAddr==001x_xxxx : select output device

//xxxx_xxxx_xxxx_xx00 - UART
//xxxx_xxxx_xxxx_xx01 - LCD
//xxxx_xxxx_xxxx_xx1x - SPI

//also selecting SPI device
//xxxx_xxxx_xxxx_0xxx - Ethernet
//xxxx_xxxx_xxxx_10xx - SD
//xxxx_xxxx_xxxx_11xx - ADC


//IN command is 
//SrcAddr == 1001_0xxx
//(ALU Src was 100x_xxxx, now it is 1000_xxxx)

module QuatCoreIOselector (	input clk, input [7:0] SrcAddr, input [7:0] DestAddr, input [15:0] DataBus, input DestStall, input SPI_busy,
				output UARTtxEN, output LCD_EN, output SPItxEN, output UARTrxEN, output SPIrxEN,
				output reg [1:0] SPIdevice = 2'b0);

parameter enableLCD = 1'b0;
parameter enableUART = 1'b1;
parameter enableSPI = 1'b0;

localparam HasChoice = enableSPI | ((enableLCD + enableUART + enableSPI) > 1);

wire isSelection = (~DestAddr[7]) & (~DestAddr[6]) & DestAddr[5] & (~DestStall) & HasChoice;
wire isIO_out = (~DestAddr[7])&(~DestAddr[6])&((~DestAddr[5]) | (~HasChoice)) & (~DestStall);
wire isIO_in = (SrcAddr[7:3] == 5'b1001_0);

reg [1:0] sel = enableUART?	2'b00:
		enableLCD?	2'b01:
				2'b10;

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

always @(posedge clk) if (isSelection) begin
	sel <= DataBus[1:0];
	shadowSPI <= DataBus[3:2];
end

always @(posedge clk) if (~SPI_busy)
	SPIdevice <= shadowSPI;
	
assign UARTtxEN = (sel==2'b00) & isIO_out & enableUART;
assign LCD_EN = (sel==2'b01) & isIO_out & enableLCD;
assign SPItxEN = sel[1] & isIO_out & enableSPI;

assign UARTrxEN = (sel==2'b00) & isIO_in & enableUART;
assign SPIrxEN = sel[1] & isIO_in & enableSPI;

endmodule


И добавим ещё один провод:

(и здесь же куча отладочных выводов присоединена).

Всё это дело успешно синтезируется, и тайминги выдерживаются (свыше 27 МГц), попробуем запустить симуляцию:


Много чего здесь происходит. Сначала идёт сброс, и идёт не очень гладко. Как говорилось выше, почему-то DestAddr не хочет инициализироваться в 0x89 (NOP), и сразу же запускается передача. Зато, не было бы счастья, отловили небольшой баг передатчика UART: если его запустить В САМЫЙ ЖЕ ПЕРВЫЙ ТАКТ, он "проглотит" стартовый бит. Связано это с инициализацией делителя частоты: он работает "вниз", но инициализируется нулём, т.е состоянием "уже досчитали". Эх, это я хотел чуточку упростить модуль, считать не от 2DividerBits-1-Limit вверх до 2DividerBits-1, а просто от Limit вниз до нуля. Вот довыпендривался. Будем исправлять... Таким макаром немудрено вообще всю передачу запороть - первая посылка слишком короткая, на стоповый бит приходится стартовый бит следующей посылки, и пока он назад "в синхронизм" войдёт, кто ж его знает.

Посмотрим теперь, что там по процессору происходит. Всё в порядке: инициализируем стек, затем X=0 (адрес строки Hello, world), вызываем процедуру. Кажется, что ничего не произошло, но импульсы SrcDiscard=1 и DestStall=1 показывают, что переход действительно произошёл, команду JMP endless (бесконечный цикл, которым мы оканчиваем выполнение) мы успешно пропустили.

Выходим на команду OUT [X+i], причём [X+i] срабатывает верно, поступает на шину данных значение 0x48 (буква "H"). И теперь это значение держится на шине данных. Срабатывает сигнал busy передатчика UART, так что мы замираем в ожидании, когда он передаст свой нулевой байт. И когда он передан, сразу же начинается передача "H", а процессор двигается дальше. Он заносит то же число в аккумулятор, прибавляет единичку к i, вычитает из аккумулятора ноль (чтобы выставить флаг знака, на простом занесении в акк. он не ставится), и затем, поскольку число там положительное, прыгает в начало цикла.

Во время этого прыжка проскакивает зловредная команда "OUT 0", но, как видно, сигнал busy не формируется, поскольку в этот момент DestStall=1, и команда игнорируется. И только после сброса DestStall, появляется уже правильная команда "OUT [X+i]", т.е передать очередную букву, это "e". И мы замираем в ожидании, когда передастся буква "H"....

Следующий слайд:


Да, всё работает как надо: передаётся буква за буквой, теперь уже со стартовыми и стоповыми битами, без пропусков.

И наконец, посмотрим, что там в конце:


Да, как только мы сбагрили символ "подачи бумаги" (0x0A, Line Feed), мы благополучно вернулись в процедуру main и вошли в бесконечный цикл endless, как и задумывалось.

Что ж, более-менее... Подправляем код QuatCoreUARTtx:
`include "math.v"

module QuatCoreUARTtx (input clk, input st, input [15:0] DataBus, output txd, output busy);

	parameter CLKfreq = 4_000_000;
	parameter BAUDrate = 1_000_000;

	localparam sIdle 	=	4'b0000;
	localparam sStart	=	4'b0110;
	localparam sB1		=	4'b0111;
	localparam sB2	    	=	4'b1000;
	localparam sB3		=	4'b1001;
	localparam sB4		=	4'b1010;
	localparam sB5		=	4'b1011;
	localparam sB6		=	4'b1100;
	localparam sB7		=	4'b1101;
	localparam sB8		=	4'b1110;
	localparam sStop	= 	4'b1111;

	wire [3:0] State;
	wire isStopState;
	wire isIdle = (~State[3]) & (~State[2]); //shortcut as not all states are used
	
	wire DoStart = st & isIdle;
	wire [7:0] Data = DataBus[7:0];
	wire ce; 	//count enable
	reg r_ce = 1'b0;
	reg r_set = 1'b0;
	always @(posedge clk) begin
		r_ce <= ce;
		r_set <= ce | DoStart | isIdle;
	end
	
	lpm_counter StateMachine (
				.clock (clk),
				.cnt_en (r_ce),
				.sset (DoStart),
				.q (State),
				.cout (isStopState) );
	defparam
		StateMachine.lpm_direction = "UP",
		StateMachine.lpm_port_updown = "PORT_UNUSED",
		StateMachine.lpm_type = "LPM_COUNTER",
		StateMachine.lpm_width = 4,
		StateMachine.lpm_svalue = sStart;

	localparam DividerBits = `CLOG2(((CLKfreq + BAUDrate / 2) / BAUDrate - 1));
	localparam Limit = (CLKfreq + BAUDrate / 2) / BAUDrate - 2;
	localparam UpLimit = (1 << DividerBits) - 1 - Limit;

	lpm_counter Divider (
				.clock (clk),
				.sset (r_set),
				.cout (ce) );
  defparam
/*
    Divider.lpm_direction = "DOWN",
    Divider.lpm_port_updown = "PORT_UNUSED",
    Divider.lpm_type = "LPM_COUNTER",
    Divider.lpm_width = DividerBits,
    Divider.lpm_svalue = Limit;
    */
    Divider.lpm_direction = "UP",
    Divider.lpm_port_updown = "PORT_UNUSED",
    Divider.lpm_type = "LPM_COUNTER",
    Divider.lpm_width = DividerBits,
    Divider.lpm_svalue = UpLimit;    
							
	reg [8:0] ShiftReg = 9'b1111_1111_1;
	assign txd = ShiftReg[0];

	assign busy = st & (~isIdle);

always @(posedge clk) if (DoStart | r_ce)
		ShiftReg <= DoStart? {Data, 1'b0} : {1'b1, ShiftReg[8:1]};

endmodule


Синтезируем по-новой и запускаем симуляцию:


Вот, теперь всё нормально. Ну, почти. Раз уж не хочет DestAddr инициализироваться в 0x89, мы могли бы ещё разок все команды "перетряхнуть" - просто инвертировать отдельные биты, чтобы 0x00 превратился в NOP. Это не должно ни на что повлиять, и вообще-то, по логике вещей, именно так "инициализация" иногда и проходит - на полном автомате логика всех цепей перекраивается под инверсию отдельных битов. Но пока мне лениво. Передался лишний "ноль" в начале работы - и хрен с ним...


Кажется, получилось. Осталось только проверить "на железе".
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

Recent Posts from This Journal

  • Лестница для самых жадных

    В эти выходные побывал на даче, после 3-недельной "самоизоляции". Забавно, как будто зима началась! Особенно грязные галоши остались на улице, в…

  • Возвращаемся к макету

    Очень давно макетом видеоизмерителя параметров сближения не занимался: сначала "громко думал" по поводу измерения его положения на аппарате, а потом…

  • Минутка живописи

    В процессе разгребания содержимого квартиры (после нескольких ремонтов) дошёл, наконец, и до картин. В кои-то веки их повесил. Куда их вешать -…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments