nabbla (nabbla1) wrote,
nabbla
nabbla1

Category:

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

Часть 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%, чтобы уж наверняка.

Всё равно "неаккуратненько" - хотелось бы, чтобы и на симуляторе всё было правильно, так что как-нибудь мы всё-таки вставим сюда сумматор с ускоренным переносом. Но позже.
Tags: ПЛИС, работа, странные девайсы
Subscribe

  • Так есть ли толк в ковариационной матрице?

    Задался этим вопросом применительно к своему прибору чуть более 2 недель назад. Рыл носом землю с попеременным успехом ( раз, два, три, четыре),…

  • Big Data, чтоб их ... (4)

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

  • Потёмкинская деревня - 2

    В ноябре 2020 года нужно было сделать скриншот несуществующей программы рабочего места под несуществующий прибор, чтобы добавить его в документацию.…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments