nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

QuatCore: символьный UART

Пора обзаводиться вводом-выводом, а то мы прошьём свой процессор в ПЛИС, и даже не сообразим, что он работает, поскольку он за 1 мс всё посчитает и уйдёт в бесконечный цикл, ни коим образом не обозначая своего состояния!

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

Но у меня бредовое желание начать всё же с передачи понятного человеку текста - слов и десятичных значений. Сначала по UART, а потом подключить символьный ЖК-экранчик 4х40, и выдавать все параметры на нём. Хочу сделать "демонстрационный макет", который будет работать от одной банки 18650, и выдавать параметры на экранчик - это больше убедит всех причастных, что все вычисления действительно ведутся внутри. Обычно приходится сдавать коробку, подключать к ней 27 вольт и огромный промышленный компьютер рабочего места, запускать на нём программу рабочего места, долго там чего-то запускать по инструкции из ТУ, и затем показывать ряд циферок в окошке, и долго и упорно доказывать, что это те самые циферки, которые нам и нужны, и даёт их, разумеется, та самая коробочка, а не сам промПК.

Пора сделать UART-передатчик, который правильно "встраивается" в QuatCore. Да и вообще, сделать его наконец-то по-нормальному!




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

Далее обнаружилось, что комбинаторный выход tx плохо работает на 921600 бит/с - видать, какие-то "пички" сбивают с толку приёмник, и нам пришлось поставить на выходе регистр.

А ещё позже, когда мы наконец-то спохватились, что для Timing Analysis нужно выставлять тип прибора: EPF10K200SRC240-3, это самый медленный кристалл. И теперь уже время от времени передатчик UART не укладывается во временнЫе рамки, особенно, если использовать малую скорость передачи и тактовую частоту 80 МГц - мало того, что тогда внутри синтезируется довольно широкий делитель частоты, так ещё и для его преждевременного обнуления используется столь же широкий компаратор, а сигнал с этого компаратора управляет всеми прочими цепями передатчика - и выскакивает Critical Warning.

Так что давайте для начала представим хороший модуль UART-передатчика "сам по себе":

`include "math.v"

module StupidUARTtransmitter (input clk, input st, input [7:0] Data, output txd, output ready);

	parameter CLKfreq = 80_000_000;
	parameter BAUDrate = 300;

	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 ce; 	//count enable
	reg r_ce = 1'b0;
	reg r_set = 1'b0;
	always @(posedge clk) begin
		r_ce <= ce;
		r_set <= ce | st | isIdle;
	end
	
	lpm_counter StateMachine (
							.clock (clk),
							.cnt_en (r_ce),
							.sset (st),
							.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;

	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;
							
	reg [8:0] ShiftReg = 9'b1111_1111_1;
	assign txd = ShiftReg[0];

	assign ready = isStopState & r_ce;

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

endmodule


В этот раз мы не стали хитрить, и начальное состояние sIdle - нулевое, чтобы в нём мы и запускались. "Командоаппарат" (называть его конечным автоматом рука не поднимается, чересчур прост) выполнен на 4-битном счётчике. Мы используем вход разрешения счёта, управляемый от делителя частоты. Также задействован вход синхронной установки, чтобы при запуске передачи перейти в состояние sStart. Он запускается напрямую от входа st ("запуск"). И ещё мы используем "выход переноса" cout, на котором появляется единичка, когда мы дошли до последнего состояния, и на следующий раз уже вернёмся в sIdle.

Теперь посмотрим на делитель частоты. Вместо того, чтобы начинать счёт от нуля, а затем использовать компаратор, чтобы определить что мы досчитали до требуемого значения, мы начинаем счёт со значения, и считаем ВНИЗ. Как дойдём до нуля - появится единичка на cout (выход переноса), сигнализирующая, что мы на месте.
Правда, задержка распространения до cout тоже весьма велика, поэтому мы не используем этот сигнал (ce) напрямую, а заносим в регистр r_ce. Это лишь смещает всю нашу работу на 1 такт, зато теперь мы можем работать на высоких частотах.

Установка делителя частоты производится по сигналу r_set. Как мы можем видеть, на этом сигнале всегда единица, пока мы находимся в состоянии sIdle. Мы как бы зафиксировали счётчик в начальном положении, не давая ему считать, и поэтому во всей схеме ничего не происходит. Командоаппарат ждёт прихода сигнала r_ce, а он не поступает.

Также r_set устанавливается по сигналу ce, что логично - как только счётчик дошёл до нуля, пора его перезапускать.

И наконец, рассмотрим формирование выходного сигнала. Для этого используется 9-битный сдвиговый регистр, младший бит которого выведен наружу. Изначально он установлен во все единицы, так что на выходе тоже единица, сигнализируя исправность линии.

Когда приходит импульс запуска st, мы заносим в старшие 8 бит этого регистра наши данные, а в младший - стартовый бит, то есть нолик. Он сразу же, по тактовому импульсу появляется на выходе. В этот же момент командоаппарат переходит в состояние sStart, благодаря чему счётчик наконец-то может стронуться с места и отсчитывать интервал для передачи каждого бита.

Каждый раз при смене состояния сигналом r_ce также осуществляется сдвиг вправо нашего регистра, причём слева он заполняется единицами. Благодаря этому, после выдачи 8 бит данных, он "автоматически" выдаст нам стоповый бит, да так и застрянет с единицей на выходе.

Кстати, данный передатчик очень легко переделать на передачу 2 стоповых бит - такой формат тоже бывает. Для этого нужно лишь установить командоаппарат на одну позицию раньше (sValue = 4'b0101 вместо нынешних 4'b0110), а всё остальное пройдёт как надо.

Кроме выхода tx, у нас есть сигнал ready - на нём появляется единичка на один такт перед окончанием работы.

Давайте глянем на это безобразие в симуляторе, чтобы убедиться, что оно работает как положено. Только коэффициент деления зададим небольшой, чтобы убедиться, что не ошиблись "на единичку". А именно, давайте поделим частоту всего в 4 раза (входная: 80 МГц, выходная: 20 МГц). Результат - в начале поста. Святая простота, глаза отдыхают после процессора :)

Такая схема синтезируется всего в 24 ЛЭ, а предельная частота на нашей ПЛИС: 138,39 МГц.

Вроде бы неплохо...

Теперь нужно сообразить, как встроить эту штуку в QuatCore.

Первым делом нужно выделить ей адрес в DestAddr. Сейчас все адреса от 0 до 128 пустуют, так что для начала можем весь диапазон и выделить! То есть, если старший бит DestAddr нулевой - значит мы дали команду передать байт :) Потом, ясное дело, можно будет и с другой периферией поделиться.

То есть, мы можем написать что-то наподобие:
wire st = ~DestAddr[7]

и запуск мы обеспечили.

Далее, подсоединяем шину данных, и тут тоже всё просто. Для "символьного режима" нам проще всего брать младшие 8 бит из шины данных, а старшие 8 бит игнорировать:

wire [7:0] Data = DataBus[7:0]


Далее, мы могли бы сделать работу UART полностью независимой от процессора - получили очередной байт на передачу - и передаём, и не мешаем процессору заниматься своими делами. Но всё-таки у нас UART это вещь отладочная, и было бы наиболее удобно с ней работать, если ВЫПОЛНЕНИЕ БУДЕТ ОСТАНАВЛИВАТЬСЯ, пока UART занят.

Точнее, можно поступить двумя способами. В первом, более простом, мы запускаем UART - и так и залипаем на данной команде, пока передача не закончится. Более правильный - что при первом запуске UART мы продолжаем выполнение, но если запускаем его повторно, а он ещё не передал прошлый байт - мы останавливаемся, пока он не передастся, после чего мы "защёлкнем" новый байт - и снова пойдём по своим делам. В таком случае, пока передаётся один байт, у нас есть прорва времени, чтобы сформировать следующий - и покорно ждать, пока уйдёт предыдущий.

Давайте и попытаемся так сделать.

Для начала, нужно заблокировать сигнал st в любых состояниях, кроме sIdle, это делается легко - везде заменяем st на

st & isIdle


поэтому, когда мы попытаемся отправить следующий байт, пока не отправлен предыдущий, ничего не произойдёт, сигнал на старт будет проигнорирован до поры до времени.

И ещё, нам нужно сформировать сигнал busy. Как ни странно, и это очень легко:

assign busy = st & (~isIdle)


То есть, если в данный момент процессор выполняет другую команду, то st=0, поэтому здесь сигнала "занято" не возникает. Когда мы запускаем передачу в первый раз, состояние не меняется, пока не пройдёт тактовый импульс, но к следующему тактовому импульсу мы уже перейдём на следующую инструкцию, так что процессор "не застрянет". Если же он опять обращается к UART, а тот ещё старое значение не передал - то да, busy=1, и остаётся им, пока состояние не вернётся в sIdle.

Вот как выглядит полный код передатчика, "заточенного" под QuatCore:

`include "math.v"

module QuatCoreUARTtx (input clk, input [7:0] DestAddr, 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 st = ~DestAddr[7];
	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 | st | 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 Limit = CLKfreq / BAUDrate - 1;

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



Как ни странно, этот модуль синтезируется в такое же количество ЛЭ, в 24 ЛЭ при делителе частоты в 4 раза. Работает ли он в связке с QuatCore - скоро узнаем...
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Ремонт лыжных мостиков

    Вернулся с сегодняшнего субботника. Очень продуктивно: отремонтировали все ТРИ мостика! Правда, для этого надо было разделиться, благо народу…

  • Гетто-байк

    В субботу во время Великой Октябрьской резни бензопилой умудрился петуха сломать в велосипеде. По счастью, уже на следующий день удалось купить…

  • А всё-таки есть польза от ковариаций

    Вчера опробовал "сценарий", когда варьируем дальность от 1 метра до 11 метров. Получилось, что грамотное усреднение - это взять с огромными весами…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 4 comments