nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

QuatCore: производство средств производства

Мучительное отлаживание "программно-аппаратного комплекса", отображающего на ЖК-экранчике напряжения питания, показало, что средства разработки и отладки для QuatCore нуждаются в доработке :)

На повестке дня: модуль QuatCoreDummyUART для отображения текста во время симуляции в Quartus, "мощнейший" препроцессор для транслятора QuatCore, теперь понимающий целую строку в кавычках, добавление входа stall в АЛУ, параметризация IOselector, и 8-битный контроллер SPI.





QuatCoreDummyUART
Когда мы работаем "в железе", то любую отладочную информацию удобно отправлять по UART на компьютер, и смотреть там. Но когда ту же самую программу мы попытаемся запустить в симуляции, то UART-головная боль та ещё, он нам затормозит всё выполнение, и посмотреть на переданные символы негде. Разве что шину данных изобразить не в hex, а в ascii, и самому внимательно смотреть, когда именно подаётся символ на UART. Решение очень простое, мы штатный UART заменяем на такую хреновину:

//для удобства отладки
module QuatCoreDummyUARTtx (input clk, input st, input [15:0] DataBus, output busy, output txd, output reg [7:0] Char = 8'h00);

always @(posedge clk) if (st)
	Char <= DataBus[7:0];

assign busy = 1'b0;
assign txd = 1'b1;

endmodule


Она не тормозит выполнение программы и выводит последний переданный символ на отдельной шине Char. Добавим её в Vector Waveform File - и готово. В "картинке для привлечения внимания" мы отчётливо наблюдаем сообщение, которое предполагалось отправить на компьютер. Ну, оно достигло цели :)

В левой части экрана, увы, виден большой недостаток - русские символы Quartus показывает в виде чисел от 128 до 255, вот жеж буржуйская техника! Так что для такой отладки придётся использовать английский текст. Как его переубедить - понятия не имею.

Строка (больше символа) в кавычках в ассемблере
Задолбало каждую буковку одинарными кавычками выделять, вроде такого:
Row1		dw	0x1C0,'Г','а','м','и','л','ь','т','о','н',' ',' ','Р','о','д','р','и','г','е','с'


Особенно сейчас, когда попытался записать все-все сообщения по инициализации SD-карточки: CMD0 (и варианты ответа - успешно, таймаут, неверная команда), CMD8 (карточка старая или карточка новая), ACMD41 (карточка обычной ёмкости или High Capacity).

Да и затея с разными кодировками себя не сильно оправдала - в МЭЛТовском ЖК экранчике обнаружилась вполне себе стандартная Win1251, хотя позже может пригодиться.

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

Так что теперь можно определять строки вот так:
	CMD0str	dw	'Send CMD0',13,0x800A


До чего же кайфово!

Параметризуемый селектор ввода-вывода


Если мы какой-то из модулей не применяем, то можем не удалять его со схемы, а просто поставить нолик в одном из параметров: enableLCD, enableUART, enableSPI. Вот код обновлённого селектора ввода-вывода:

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);

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

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 & enableUART;
assign LCD_EN = (sel==2'b01) & isIO_out & enableLCD;
assign SDtxEN = sel[1] & isIO_out & enableSPI;

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

endmodule


Если команда запуска никогда не проходит в модуль, то синтезатор умудряется сообразить, что там вообще ничего не может происходить - все выходы он соединяет на нули или единицы, и выкидывает все "внутренности" подчистую! По крайней мере, для устройств вывода это срабатывает. Отключать SPI ещё не пробовал, но скорее всего и там большая часть логики, если не вся, будет отброшена.

Сейчас, к примеру, мы отключили ЖК-экран.

На той же схеме видно, как мы поставили DummyUART вместо "настоящего", а вместо 16-битного SPI - 8-битный.

8-битный SPI
Хотел всё-таки реализовать свою исходную идею специализированного SPI-контроллера для работы с SD-карточкой. Там было необходимо, чтобы сам контроллер пропустил бы входные байты 0xFF и продолжал бы сам отсылать 0xFF, пока не придёт байт, начинающийся с нуля. Но если 8 байт подряд были "пустые", то делать нечего - надо вернуть 0xFFFF, а потом уже программа воспримет это как таймаут.

Но в итоге стало лениво его делать, как-то там всё немножечко не клеится. Если сдвиговый регистр может не только загружаться из входа, "стоять на месте" и сдвигаться, а ещё и загружаться во все единицы, то по 1 ЛЭ на каждый бит уже не хватает, нужно 2 ЛЭ, а это как-то неаккуратненько!

Так что решил, наоборот, подрезать наш дуплексный 16-битный модуль, чтобы "ручками" принимать по 1 байту, сравнивать его с 0xFF, и дальше либо принимать следующий байт, либо объявлять таймаут. Если мы принимаем по 16 бит, то код страшно неудобный становится - велика вероятность, что у нас всё сдвинется на полслова, а у нас нормальных команд сдвига пока вообще нет!

"Подрезанный" модуль выглядит так:

`include "math.v"
module QuatCore8bitDuplexSPI (	input clk, input ce, input [15:0] D, input TXen, input RXen,
				inout MISO,
				output [15:0] Q,
				output reg SCK=1'b0, output MOSI, output nCS, output busy, output ceo);

	assign MISO = nCS? 1'b1 : 1'bz; //"неактивным" здесь считается лог. "1", на манер UART
	
	//нужно более 8 состояний
	localparam sIdle =  4'h0;
	localparam sB0 =    4'h8;
	localparam sB1 =    4'h9;
	localparam sB2 =    4'hA;
	localparam sB3 =    4'hB;
	localparam sB4 =    4'hC;
	localparam sB5 =    4'hD;
	localparam sB6 =    4'hE; 
	localparam sB7 =    4'hF;
	
	wire [3:0] State; //вставим его отдельным модулем, чтобы задействовать режим счётчика ЛЭ

	reg [8:0] SR = 9'h1FF; //нужно одним битом больше, потому что пока защёлкиваем по MISO, не успеваем сдвинуть младший и затираем его нахрен
	//могли бы, учитывая полудуплекс, иногда защёлкивать, а иногда нет, но нужен регистр для определения - приём или передача - то на то и выходит.
	
	assign nCS = ~State[3]; //по сути, "isIdle"

	wire NEG_E = ce & SCK; //спад SCK, когда мы переключаем состояния и сдвигаем данные в регистре
	wire POS_E = ce & (~SCK); //фронт SCK, когда мы защёлкиваем входной бит

	wire IsFinalBit; //генерируется логикой переноса (cout)
	
	wire TXbusy = TXen & (~(NEG_E & (nCS | IsFinalBit))); //т.е ждём, пока не будет спада SCK в режиме Idle или на передаче последнего бита
	wire RXbusy = RXen & (~(POS_E & (nCS | IsFinalBit))); //т.е ждём, пока не будет фронта SCK в режиме Idle или на передаче последнего бита
	assign busy = TXbusy | RXbusy;
	wire start = TXen & NEG_E & (nCS | IsFinalBit); //то есть, старт всегда по TXen, а по RXen можно получить ответ, что же там было!
	//assign busy = ((TXen & (~NEG_E))|(RXen & (~POS_E))) & (nCS | IsFinalBit);
	//правда, и такой вариант не очень, т.к возникнет задержка до следующего слова. 
				
	lpm_counter StateCounter (.clock (clk),
		                  .cnt_en (NEG_E & (~nCS)),
			          .sset (start), 
			          .Q (State),													
			          .cout (IsFinalBit));
	defparam
		StateCounter.lpm_direction = "UP",
		StateCounter.lpm_port_updown = "PORT_UNUSED",
		StateCounter.lpm_type = "LPM_COUNTER",
		StateCounter.lpm_width = 4,
		StateCounter.lpm_svalue = sB0;			                    
	

	assign MOSI = SR[8];
	assign Q = {8'h00, SR[7:1], MISO}; //экономит один такт, позволяет не накладывать огр 
				
	always @(posedge clk) begin
		SCK <= ce? ~SCK : SCK;
		if (NEG_E)
			SR[8:1] <= start? D[7:0]: SR[7:0];
		if (POS_E)
			SR[0] <= MISO;
	end
	
	assign ceo = ce & SCK; //просто сэкономили 1 бит в делителях частоты, поскольку мы ОЧЕНЬ жадные.
endmodule


Синтезируется в 23 ЛЭ - вот это мне больше по душе (16-битный дуплексный синтезировался в 32 ЛЭ). Вроде бы работает, но чтобы его основательно проверить, мне нужна ещё одна приблуда - "имитатор SD-карточки". Опыт показывает, что так я быстрее всё безобразие запущу, хоть потом и покажется - "лучше бы сразу в железе стал проверять - всё же было правильно изначально!".
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Огари разговаривают

    Сегодня по пути на работу встретил огарей прямо в Лосином острове, на берегу Яузы. Эти были на удивление бесстрашны, занимались своими делами, не…

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

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

  • Очень запоздало о лыжах

    Давненько (с 16 февраля) не писал о лыжах, хотя каждую неделю катался. Первый раз - на Гремячий и назад, с разведкой нового пути к маленькому…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 2 comments