Мучаем 5576ХС4Т - часть 'h2B - ускоряем Adler32

Apr 26, 2019 16:34

Часть 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 - уравновешенный четверичный умножитель

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

А всё потому, что в части 2 мы очень здорово "успокоили" среду разработки, сказав ей, что собираемся "прошивать" эти модули в кристалл EPF10K200SRC240-1 - самый быстрый в линейке Flex10k. Тактовая частота 80 МГц для него - "раз плюнуть" - он вполне успеет протащить перенос через 32-битный сумматор, задействовать от него комбинаторную схему, и ещё немножко времени останется!

И до сих пор "мне везло" - схема, которая работала на симуляторе (не выдавала Critical Warning) - работала и в железе. Конечно, над "железом" я пока не шибко издевался - охлаждение хорошее, температура практически комнатная, напряжение ядра стабильное - чего бы ему и не работать как следует??

Но всё-таки это неправильно - если в документе ГПКФ.431262.003Д4 (Микросхемы интегральные 5576ХС3Т, 5576ХС4Т. Инструкция по программированию) написано, что по основным характеристикам разработанные микросхемы соответствуют EPF10K200SRC240-3, то её и надо было задать, и разрабатывать модули так, чтобы и в этом случае никаких warning'ов не появлялось!

Вот тогда у нас появится хороший "задел" - взяв любую наперёд заданную микросхему 5576ХС4Т (без отбраковки), подвергнув её дозе облучения (разрешённой по ТУ), запустив в предельно допустимых режимах, мы по-прежнему получим стабильную работу изделия!

Самый простой способ добиться устойчивой работы - снизить тактовую частоту с 80 МГц до 50 МГц - тогда все рассмотренные нами модули заработают "как есть". При использовании отладочной платы от LDM-systems, мы можем взять тактовую частоту от контроллера Ethernet - по умолчанию он выдаёт нам 4 МГц, но сконфигурировав контроллер по SPI, мы можем повысить эту частоту до 33,3 МГц, и комфортно работать уже на них.

Но можно и "ускорить" несколько модулей, которые не хотят работать - это вполне реализуемо.

Начнём с Adler32, который единственный из всех рассмотренных нами "не захотел работать" (выдал предупреждение) на кристалле EPF10K200SRC240-2!

Когда мы попытались синтезировать его для "медленного" кристалла EPF10K200SRC240-3, мы получили следующий ужас:




Как и говорилось - на 50 МГц мы бы заработали заведомо, а вот для 80 МГц у нас слишком длинные комбинаторные пути.

Мы попытались за один такт выбрать, что подавать на сумматор (либо новый байт со входа, либо число 15 для коррекции, либо 0, когда коррекция не нужна), затем найти сумму и положить её в регистр, но тут же мы хотели определить - нужна ли коррекция, что требует логики, задействующей все 16 выходов сумматора, и для кучи - выход переноса cout. Учитывая, что и сумматор даёт приличную задержку распространения до выхода переноса, мы и получили такой результат.

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

На следующем такте числа с двух регистров отправляются на сумматор - и тут же защёлкиваются в регистре - как 16-битный ответ, так и выход переноса!

Пошёл следующий такт - проверяем, нужна ли коррекция, и кладём ответ в регистр!

Следующий такт - забиваем в регистр значение 0 либо 15.

И только к следующему такту осуществляем корректирующее суммирование, и ещё спустя такт мы получаем правильное значение s1. Затем, используя s1 как вход для следующего каскада, мы таким же способом получаем s2.

Вот как оно выглядит:

//uses shortest combinatorial paths to allow stable work at 80 MHz with slow (flex10k speed grade -3) FPGAs
//unfortunately, this reduces input rate to 1 byte each 4 clocks...

module FasterAdler32 (input clk, input [7:0] A, input ce, input aclr, input shift, output [7:0] Q, output Busy);

//First stage
//control logic
localparam sIdle = 3'b000; //rA <= A
localparam sSum = 3'b100; //{r_cout1, s1} <= s1 + rA;
localparam sCheck = 3'b101; //NeedsCorrection1 <= f (r_cout, s1)
localparam sPrepare = 3'b110; //rA <= NeedsCorrection1? 15 : 0
localparam sCorrect = 3'b111; //s1 <= s1 + rA

wire [2:0] State1;
wire TC1; //terminal count
wire NotIdle1 = State1[2]; //simple as that
wire EnableS1 = (State1 == sSum) | (State1 == sCorrect); //all 3 bits used
wire EnableRA = State1[1] & (~State1[0]); //State1 == sPrepare

lpm_counter Stage1 (.clock (clk),
.cnt_en (NotIdle1),
.sset (ce),
.Q (State1),
.cout (TC1));
defparam
Stage1.lpm_direction = "UP",
Stage1.lpm_port_updown = "PORT_UNUSED",
Stage1.lpm_type = "LPM_COUNTER",
Stage1.lpm_width = 3,
Stage1.lpm_svalue = sSum;

//input reg (otherwise we have serious problems!)
//but also we use it instead of Mux
reg [7:0] rA = 1'b0;

//first adder
reg [15:0] s1 = 1'b0;
reg r_cout1 = 1'b0;
reg NeedsCorrection1 = 1'b0;

wire [15:0] sum1; //output of first adder
wire cout1; //carry-out of first adder

lpm_add_sub AdderS1 (
.dataa ({8'b0, rA}),
.datab (s1),
.cout (cout1),
.result (sum1) );
defparam
AdderS1.lpm_direction = "ADD",
AdderS1.lpm_hint = "ONE_INPUT_IS_CONSTANT=NO,CIN_USED=NO",
AdderS1.lpm_representation = "UNSIGNED",
AdderS1.lpm_type = "LPM_ADD_SUB",
AdderS1.lpm_width = 16;

//second stage
//control logic

wire [2:0] State2;
wire TC2; //terminal count
wire NotIdle2 = State2[2]; //simple as that
wire EnableS2 = (State2 == sSum) | (State2 == sCorrect); //all 3 bits used
wire EnableRB = State2[1] & (~State2[0]); //State2 == sPrepare

lpm_counter Stage2 (.clock (clk),
.cnt_en (NotIdle2),
.sset (TC1),
.Q (State2),
.cout (TC2));
defparam
Stage2.lpm_direction = "UP",
Stage2.lpm_port_updown = "PORT_UNUSED",
Stage2.lpm_type = "LPM_COUNTER",
Stage2.lpm_width = 3,
Stage2.lpm_svalue = sSum;
//input reg (otherwise we have serious problems!)
//but also we use it instead of Mux
reg [15:0] rB = 1'b0;

//second adder
reg [15:0] s2 = 1'b0;
reg r_cout2 = 1'b0;
reg NeedsCorrection2 = 1'b0;

wire [15:0] sum2; //output of second adder
wire cout2; //carry-out of second adder

lpm_add_sub AdderS2 (
.dataa (rB),
.datab (s2),
.cout (cout2),
.result (sum2) );
defparam
AdderS2.lpm_direction = "ADD",
AdderS2.lpm_hint = "ONE_INPUT_IS_CONSTANT=NO,CIN_USED=NO",
AdderS2.lpm_representation = "UNSIGNED",
AdderS2.lpm_type = "LPM_ADD_SUB",
AdderS2.lpm_width = 16;

always @(posedge clk, posedge aclr) begin
if (aclr) begin
s1 <= 16'b1; //by definition of Adler32
s2 <= 16'b0;
end
else begin
rA <= ce? A: (EnableRA & NeedsCorrection1)? 8'd15 : 8'd0;
r_cout1 <= cout1;
NeedsCorrection1 <= r_cout1 | (s1 >= 65521);
if (EnableS1 | shift) begin
s1 <= shift? s1 << 8 : sum1;
end

rB <= TC1? s1 : (EnableRB & NeedsCorrection2)? 16'd15 : 16'd0;
r_cout2 <= cout2;
NeedsCorrection2 <= r_cout2 | (s2 >= 65521);
if (EnableS2 | shift) begin
s2 <= shift? {s2[7:0], s1[15:8]} : sum2;
end
end
end

assign Q = s2[15:8];
assign Busy = NotIdle1 | NotIdle2;
endmodule

Опять пришлось вернуть состояния - уж больно их стало много, на сдвиговом регистре далеко не уедешь!
Итак, у нас есть две "ступени" - первая получает входной бит A и считает значение s1. Вторая запускается, когда мы обновили значение s1, и обновляет значение s2.

Мы могли бы их представить отдельными модулями, но при подаче сигнала shift эти два регистра должны действовать "сообща", перебрасывая s1 в s2, чтобы мы могли последовательно прочитать 4 байта результата.

Такая схема успешно проходит Timing Analysis, но запас совсем небольшой - лишь 0,4 нс.



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

Что удивительно, наш модуль почти что не "раздулся" - раньше он занимал 113 ЛЭ, теперь - 115 ЛЭ (или 116 с выходом busy) - вообще незначительно.

Хуже другое - если в старом модуле мы могли подавать новый байт на каждом втором такте, то теперь - лишь на каждом четвёртом! И как это исправить - не вполне понятно - чтобы начать вычислять следующее значение, мы просто ОБЯЗАНЫ досчитать предыдущее, хотя бы s1. По крайней мере, если действовать "в лоб". Но и тут мы не будем пока шибко усердствовать - для моих целей пока хватает и 4 тактов на байт, при тактовой частоте 80 МГц.

Задержка от поступления байта до полного обновления s1 / s2 составляет и вовсе 9 тактов. Это тоже надо учитывать, хотя особой проблемы вроде не представляет.

При запуске симуляции нас ждал новый сюрприз: симуляция показала ОШИБОЧНУЮ РАБОТУ модуля, если использовать кристалл -3:




Всё выглядит так, что единичка немножко припозднилась и не успела прийти в s2[10] до окончания "защёлкивания", поэтому просто исчезла. Все остальные 48 байт входных данных обработались правильно, и мы получили ответ, отличающийся от истинного как раз единицей в 10-м разряде:



Если "сменить" кристалл хотя бы на -2, то ответ получается правильный:



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

Так что Timing Analyzer и симулятор могут друг с другом тоже "не дружить" - один скажет, что всё в порядке, а другой смоделирует такие задержки, что мы получим неверный результат!

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

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

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

Previous post Next post
Up