Мучаем 5576ХС4Т - часть 'h26 - работа над ошибками (быстрый UART)

Apr 15, 2019 20:25

Часть 0 - покупаем, паяем, ставим драйвера и софт
Часть 1 - что это вообще за зверь?
Часть 2 - наша первая схема!
Часть 3 - кнопочки и лампочки
Часть 4 - делитель частоты
Часть 5 - подавление дребезга кнопки
Часть 6 - заканчиваем кнопочки и лампочки
Часть 7 - счетчики и жаба
Часть 8 - передатчик UART
Часть 9 - Hello, wolf!
Часть 'hA - приёмник UART
Часть 'hB - UART и жаба
Часть 'hC - полудуплексный UART.
Часть 'hD - МКО (МКИО, Mil-Std 1553) для бедных, введение.
Часть 'hE - приёмопередатчик МКО "из подручных материалов" (в процессе)
Часть 'hF - модуль передатчика МКО
Часть 'h10 - передатчик сообщений МКО
Часть 'h20 - работа с АЦП ADC124s051
Часть 'h21 - преобразование двоичного кода в двоично-десятичный (BCD)
Часть 'h22 - Bin2Bcd с последовательной выдачей данных
Часть 'h23 - перемножитель беззнаковых чисел с округлением
Часть 'h24 - перемножитель беззнаковых чисел, реализация
Часть 'h25 - передаём показания АЦП на компьютер
Часть 'h26 - работа над ошибками (быстрый UART)
Часть 'h27 - PNG и коды коррекции ошибок CRC32
Часть 'h28 - передатчик изображения PNG
Часть 'h29 - принимаем с ПЛИС изображение PNG
Часть 'h2A - ZLIB и коды коррекции ошибок Adler32
Часть 'h2B - ускоряем Adler32
Часть 'h2C - формирователь потока Zlib
Часть 'h2D - передаём сгенерированное PNG-изображение
Часть 'h2E - делим отрезок на равные части
Часть 'h2F - знаковые умножители, тысячи их!
Часть 'h30 - вычислитель множества Мандельброта
Часть 'h31 - ускоренные сумматоры
Часть 'h32 - ускоренные счётчики (делаем часы)
Часть 'h33 - ускоряем ВСЁ
Часть 'h34 - ускоренные перемножители
Часть 'h35 - умножители совсем просто
Часть 'h36 - уравновешенный четверичный умножитель

В прошлой части мы "собрали" довольно большую схему, которая передаёт показания 4-х каналов АЦП по UART на компьютер, причём предварительно преобразовав в формат, понятный человеку.

И всё бы хорошо, но поигравшись с ней, я обнаружил: "шаг влево-шаг вправо" - и она накрывается медным тазом. Немножко подкорректируешь константы в ADCconstants (чтобы показания соответствовали тем, что даёт хороший вольтметр) - и внезапно начинается полнейшая чехарда с показаниями. Пытаешься повысить скорость работы UART до 921 600 бод (максимальная, в которую умеет cp2102 согласно документации) - и либо передача совсем прекращается, либо мы получаем строго каждый второй байт.

К счастью, причины этого удалось найти и исправить, это несколько "мелких штрихов".


В первую очередь, насчёт чехарды. Здесь очень обидно - вовремя не обратил внимания на Critical Warning. До этого они маячили сначала по поводу неинициализированного блока RAM (сначала я схему "собирал", и только под самый конец записал туда строку), затем по поводу разного объёма памяти - в блоке её 128 байт, а в файле инициализации - всего 80.

В итоге, не заметил, когда после небольшого (казалось бы) изменения Bin2Bcd (когда он стал выдавать ещё и десятичную точку между цифрами) всплыл новый Critical Warning, на этот раз действительно критический! А именно - "провалился" Timing Analyzer, сказал, что есть пути, по которым задержка распространения недопустимо высока!

Эти пути - от выхода Chan нашего специализированного SPI-контроллера, сквозь чисто комбинаторный модуль ADCconstants через выход DecimalPointPos (где поставить десятичную точку) в модуль Bin2Bcd, а из него (как оказалось, чисто комбинаторно), через выход Char - непосредственно в оперативную память.

Я вроде бы даже осознавал, что такой длинный комбинаторный путь здесь присутствует, но казалось, что он совсем не может навредить - между "защёлкиванием" номера канала на выходе из SPI-контроллера и началом его использования проходит 16 тактов SPI, а это 2 мкс - времени вагон! (жалоба на то, что в 12,5 нс укладываемся еле-еле!)

Но это были не единственные пути. Другие - как будто бы более короткие - шли от счётчика десятичных разрядов Bin2Bcd туда же, в память. Вот эти строки:

wire ShowingDP = (DecimalPointPos == State[3:2]) & (DecimalPointPos != 2'b11) & State[4] & State[1] & (~State[0]);

assign char = ShowingDP? DecimalPointChar : {4'h3, senior};

Кажется, что не так уж много: два скаскадированных генератора функций (ГФ, LUT) вычисляют выражение ShowingDP (нужно ли в данный момент показать десятичную точку?), а затем ещё один ГФ на каждый бит (работающие параллельно) определяет, что подавать на выход, чтобы записать в память.

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

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

И вот этот путь уже конкретно мог навредить - выходило, что в память поступал символ, который не успел ещё "установиться" - вот и приехали.

Причём в зависимости от констант, используемых в ADCconstants, вид модуля немножечко менялся, и менялась "разводка". Первый раз мне повезло, во второй раз - нет. А также - в третий, четвёртый и пятый. В общем, "если схема начинает работать с первого раза - с ней что-то категорически не так, и вы с ней проведёте ещё множество счастливых часов".

Один из способов это исправить - поставить дополнительную "защёлку" в злополучные ADCconstants:

module ADCconstants (input clk, input [1:0] Chan, output [15:0] Mult, output [6:0] DigitAddr, output reg [1:0] DecimalPos = 1'b0);

assign Mult = (Chan == 2'b00)? 16'd13848 : //12 V
(Chan == 2'b01)? 16'd55298 : //5 V
(Chan == 2'b10)? 16'd30695 : //1,8 V (kinda real)
16'd36541; //3,3 V (kinda real)

assign DigitAddr = (Chan == 2'b00)? 7'h46 : //12 V
(Chan == 2'b01)? 7'h33 : //5 V
(Chan == 2'b10)? 7'h0D : //1,8 V
7'h21; //3,3 V

wire uDecimalPos = (Chan == 2'b00)? 1'd1 : 1'd0;
always @(posedge clk)
DecimalPos <= uDecimalPos;

endmodule

После этого Critical Warning исчез, и всё заработало как надо, как ни меняй константы.

Следующий на очереди - UART. Наша гордость, мега-компактный (всего 27 ЛЭ при 8-битном делителе частоты) модуль передатчика имел две недоработки.

Во-первых, выход txd всё-таки не надо делать комбинаторным, его нужно "защёлкнуть", чтобы асинхронный выход, идущий по плате на другую микросхему, не имел всяких нехороших "пичков". На малых частотах (вплоть до 576 кбод) нам это никоим образом не мешало - они все "проглатывались" так или иначе. Но на 921 600 бод оно наконец-то "сыграло" - не знаю точно, как именно устроен приёмник в этой микросхемке cp2102, но этот мусор ей явно не понравился.

И второе: мы определяли, во сколько раз делить тактовую частоту с помощью такой строки:

localparam Limit = CLKfreq / BAUDrate - 1;

В случае 80 МГц на входе и 921,6 кГц на выходе, у нас выходило частное 86,8, которое округлялось "вниз", хотя лучше было бы "вверх". В итоге, даже не считая "технологического разброса" по тактовой частоте, у нас выходила ошибка 8,6 кГц (0,94%), причём "вверх" - мы передавали шустрее, чем надо. Похоже, что это не устроило приёмник.

Сделаем чуточку получше:

localparam Limit = (CLKfreq + BAUDrate / 2) / BAUDrate - 1;

Теперь в результате деления мы получим 87, и ошибку в 2,06 кГц (0,22%), причём "вниз" - теперь мы чуточку медленнее. Теперь приёмник доволен!

Приведём код исправленного модуля:

`include "math.v"

module SimpleUARTtransmitter (input clk, input st, input [7:0] Data, output reg txd=1'b1, output ready);

parameter CLKfreq = 80_000_000;
parameter BAUDrate = 512_000;

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

reg [DividerBits - 1 : 0] FreqDivider;

reg [7:0] ShiftReg = 8'b1111_1111;

localparam sB1 = 4'b0000;
localparam sB2 = 4'b0001;
localparam sB3 = 4'b0010;
localparam sB4 = 4'b0011;
localparam sB5 = 4'b0100;
localparam sB6 = 4'b0101;
localparam sB7 = 4'b0110;
localparam sB8 = 4'b0111;
localparam sStop = 4'b1000;
localparam sIdle = 4'b1001;
localparam sStart = 4'b1111;

reg [3:0] State = sIdle;

wire isIdle = State[3]&(~State[1])&State[0];

wire ce = ((FreqDivider&Limit) == Limit);

wire i_txd = ~State[3]? ShiftReg[0] : ~State[2];

assign ready = State[3] & (~State[2]) & ce;

always @(posedge clk) begin
FreqDivider <= isIdle | ce | st? 1'b0: FreqDivider + 1'b1;

State <= st? sStart : ce? State + 1'b1 : State;

ShiftReg <= st? Data :
(~State[3] & ce)? (ShiftReg >> 1'b1) :
ShiftReg;
txd <= i_txd;
end

endmodule

После внесения этих доработок, мы получаем надёжную работу на скоростях до 921 600 бод включительно. Для передачи показаний АЦП нам это не шибко надо, но ведь на очереди - обработка изображений! Конечно, полноценное видео мы и по 921 600 передать не сможем, для этого нужен не COM-порт, а что-то куда более скоростное, но в отладочных целях он нам ещё как пригодится!

странные девайсы, ПЛИС, работа

Previous post Next post
Up