nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Мучаем 5576ХС4Т - часть 'h2C - формирователь потока Zlib

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


Продолжаем "подготовительную работу" для отправки изображения с ПЛИС на компьютер. В этой части мы делаем модуль, который принимает несжатые данные, по одному байту за раз, и заносит в память корректный поток Zlib. Правда, на данный момент никакого сжатия не происходит, наоборот, добавляется немножко заголовков.

Замысел состоит в том, что в дальнейшем мы сможем спроектировать новый модуль, с тем же самым интерфейсом, но он уже будет проводить сжатие. Но наличие даже такого модуля, как сейчас, позволяет из "сырых данных" получить изображение PNG со всеми необходимыми "обёртками", с корректными контрольными суммами.



Запишем заголовок нашего модуля:
module ZLIB_wrapper (	input clk, input [AddrWidth - 1: 0] StartAddr, input ResetAddr, input [7:0] NextByte,
			input ByteCE, input IsFirstBlock, input IsLastBlock, input StartNewBlock,
			output [AddrWidth - 1:0] WR_Addr, output [7:0] WR_Data, output WR_EN, output ready_for_data);

parameter AddrWidth = 14;
parameter BlockLength = 14'd11536;


В этот раз мы отдельно управляем адресом в памяти с помощью входов StartAddr и ResetAddr. Предполагается, что в памяти уже лежит заголовок IDAT, и теперь мы должны "всунуть" непосредственные данные посередине. Необходимый начальный адрес мы заносим в StartAddr и подаём единичный импульс на ResetAddr. Как только мы займём всю доступную память - надо будет вернуться в начало блока и начать его заполнять по-новой, но не быстрее, чего его опустошает модуль SendPngImage. Пусть он передаст текущий блок, а мы тем временем наполним содержимым следующий по счёту.

NextByte - очередной байт "сырых данных", ByteCE - единичный импульс о том, что его можно забирать.

IsFirstBlock подсказывает модулю, что начать нужно с 2-байтового заголовка Zlib, поскольку это самый первый блок. Затем последуют блоки Deflate, как обычно.

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

StartNewBlock непосредственно запускает процесс - как только здесь появляется единичка, как мы передаём заголовок Zlib (если надо), заголовок нового блока Deflate, и сразу после этого выдаём сигнал ready_for_data - по нему может включиться наш передатчик "сырых данных" и начать подавать NextByte, не забывая сопровождать их единичными импульсами по ByteCE.

Выходы WR_Addr, WR_Data и WR_EN подключаются к соответствующим входам оперативной памяти - адрес для записи, данные для записи и разрешение записи. Здесь мы имеем в виду внутреннюю память ПЛИС, работать с которой - одно удовольствие. Указал адрес, указал данные, "мигнул" WR_EN - и они заносятся куда надо.

Наконец, мы видим два параметра - ширину адресной шины и размер блока. Это некоторое упрощение, что блок мы считаем одной длины, но мы всё-таки ориентируемся на конкретное приложение - передачу изображения по несколько строк за раз. Как правило, удаётся поделить изображение на равные блоки, и при использования несжатых блоков их размер оказывается известным заранее.

Когда мы сделаем модуль, реально осуществляющий сжатие (впрочем, это будет ещё нескоро - дело это довольно сложное, народ умудряется только на это все 10 000 ЛЭ истратить), логика всё-таки усложнится - скорее всего параметр "размер блока" сохранится - это будет максимальный объём в памяти, который мы можем выделить (за вычетом заголовков PNG, IHDR, IDAT, IEND и пр.). Именно в такой объём мы будем "набивать" данные и отправлять забитые под завязку блоки IDAT, пока данные совсем не закончатся. Тогда блок завершится раньше времени, и нам надо будет переправить его длину, как того требует спецификация PNG. Но не будем заглядывать так далеко, нам бы с этим модулем разобраться...

Заголовки для Zlib и блоков Deflate "хранятся" в отдельном модуле
module ZLIB_header_bytes (input [2:0] Addr, input IsLast, output [7:0] Q);

parameter Block_length = 16'd11_536;

	wire [15:0] BlockLen = Block_length;

	assign Q = 	(Addr == 6)? 	8'h78 : //window size 32k, deflate
			(Addr == 5)? 	8'h01 : //no preset dictionary, fastest compression (worth to recompress it later)
			(Addr == 4)? 	{7'b0, IsLast}: //"no compression" block, with "Last" flag
			(Addr == 3)? 	BlockLen[7:0]: //least sign. byte of block length
			(Addr == 2)? 	BlockLen[15:8]:  //senior byte
			(Addr == 1)? 	~BlockLen[7:0]: //now it's inverted
					~BlockLen[15:8];
endmodule


Для адресации мы будем использовать младшие биты счётчика BytesLeft (сколько байт осталось передать), поэтому здесь у нас "обратный отсчёт": по адресам 6, 5 хранится заголовок Zlib (78 01), мы не стали шибко мудрить.

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

И наконец, у нас идёт 2 байта длины блока, сначала в нормальном виде, потом в инвертированном. Не забываем, что в Deflate принято все большие числа передавать начиная с младших байтов! (А в ZLib, т.е в его заголовке и Adler32 - всё наоборот, чтобы никто не догадался!)

Такое хранение констант - довольно экономично, мы уместились в 6 ЛЭ (как оказалось, из 8 выходных бит несколько повторяются. Всё зависит от параметра Block_length). В конце концов, каждый ГФ (LUT) по сути хранит в себе 16 бит информации.

Наконец, приведём код модуля ZLIB_wrapper:
module ZLIB_wrapper (	input clk, input [AddrWidth - 1: 0] StartAddr, input ResetAddr, input [7:0] NextByte,
			input ByteCE, input IsFirstBlock, input IsLastBlock, input StartNewBlock,
			output [AddrWidth - 1:0] WR_Addr, output [7:0] WR_Data, output WR_EN, output ready_for_data);
						
parameter AddrWidth = 14;
parameter BlockLength = 14'd11536;
						
//state machine
	localparam sIdle = 	2'b00;
	localparam sHeader =	2'b01;
	localparam sData = 	2'b10;
	localparam sAdler = 	2'b11;
	
	wire [1:0] State;
	
	lpm_counter StateCounter (	.clock (clk),
					.cnt_en (StartNewBlock | (NoBytesLeft & (State != sIdle))),
					.sclr (NoBytesLeft & State[1] & (~IsLastBlock) ), ),
					.q (State));
	defparam
	StateCounter.lpm_direction = "UP",
	StateCounter.lpm_port_updown = "PORT_UNUSED",
	StateCounter.lpm_type = "LPM_COUNTER",
	StateCounter.lpm_width = 2;
						
//byte counter
	wire [AddrWidth - 1 : 0] BytesLeft;
	
	wire NoBytesLeft;

	wire [AddrWidth - 1 : 0] BytesCount = 	(State == sIdle)? (IsFirstBlock? 3'b110 : 3'b100) : //7 or 5 bytes of header
						(State == sHeader)? BlockLength : 3'b011; //4 bytes of Adler32
	
	lpm_counter ByteCounter (.clock (clk),
				.cnt_en (WR_EN),
				.sload (StartNewBlock | NoBytesLeft),
				.data (BytesCount),
				.q (BytesLeft),
				.cout (NoBytesLeft));
	defparam
	ByteCounter.lpm_direction = "DOWN",
	ByteCounter.lpm_port_updown = "PORT_UNUSED",
	ByteCounter.lpm_type = "LPM_COUNTER",
	ByteCounter.lpm_width = AddrWidth;
						
//address counter

	//can be 'reset' to default at ResetAddr,
	//plus 1 each time we write 1 byte to memory
	lpm_counter AddrCounter (	.clock (clk),
					.cnt_en (WR_EN),
  				        .sload (ResetAddr),
					.data (StartAddr),
					.q (WR_Addr));
	defparam
		AddrCounter.lpm_direction = "UP",
		AddrCounter.lpm_port_updown = "PORT_UNUSED",
		AddrCounter.lpm_type = "LPM_COUNTER",
		AddrCounter.lpm_width = AddrWidth;

//our 'header' block
	wire [7:0] HeaderByte;

	ZLIB_header_bytes Header (	.Addr (BytesLeft[2:0]),
					.IsLast (IsLastBlock),
					.Q (HeaderByte));
	defparam
		Header.Block_length = BlockLength;

//our Adler32 block

wire [7:0] AdlerByte;
wire Busy;

	FasterAdler32 adler_block (	.clk (clk),
					.A (NextByte),
					.ce (ByteCE),
					.aclr (IsFirstBlock & StartNewBlock),
					.shift ((State == sAdler)&(~Busy)), //why not, it's not latch
					.Q (AdlerByte),
					.Busy (Busy));


//outputs (WR_Addr is connected already)
assign WR_Data = 	(State == sHeader)? HeaderByte :
			(State == sData)?	NextByte : 
						AdlerByte;
										
assign WR_EN = ByteCE | (State[0] & ~Busy); //always on at sHeader (Adler should be cleared and not working) and sAdler (but first wait it to finish processing),
											//forwards ByteCE at sData
											
assign ready_for_data = (State == sHeader) & NoBytesLeft;

endmodule


В этот раз вообще получилось без always-блока: всё набрали из счётчиков и пары других модулей!

Мы используем 2-битный счётчик "состояния" - сначала находимся в sIdle (режим ожидания), но по импульсу StartNewBlock нам разрешается счёт, поэтому мы переходим в следующее состояние sHeader (передача заголовка).

Там мы "застреваем", пока не передадим все байты заголовка, о чём скажет признак NoBytesLeft - по сути бит переноса счётчика BytesLeft, работающего "вниз".

Затем мы переходим в состояние sData (передача данных). В нём мы опять "застреваем", пока не передадим все данные. Далее, нам будет разрешён счёт до следующего состояния, sAdler (передача контрольного слова Adler32), но если блок не является последним, то будет также разрешён синхронный сброс в sIdle - и он имеет приоритет перед счётом. Поэтому только по окончанию последнего блока будет передана контрольная сумма Adler32.

Наконец, когда будет передан последний байт Adler32, снова сработает признак NoBytesLeft - и мы снова прибавим единичку к состоянию, вернувшись к sIdle, и как всегда, застрянем там до поступления следующего импульса StartNewBlock.

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

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

Счётчик адреса AddrCounter в этот раз оказался сам по себе, свой собственный. По импульсу ResetAddr загружает значение, лежащее в StartAddr, при передаче каждого байта (что сопровождается импульсом WR_EN) - прибавляет единичку, вот и всё.

Модуль HeaderByte (хранит в себе байты заголовка) подсоединён вполне очевидным способом - в качестве адреса берутся 3 младшие бита BytesLeft (а нам больше и не надо), туда же поступает входной "провод" IsLastBlock (чтобы правильно поставить "флажок", признак последнего блока), а искомый байт появляется на проводе HeaderByte.

Чуть хитрее работает модуль Adler32 (мы его мучали в прошлых 2 частях) - мы устраиваем ему сброс только при поступлении первого блока, направляем в него "сырые данные" (именно так предписывает стандарт Zlib - контрольная сумма берётся от "сырых" данных!), а вот команда на сдвиг подаётся лишь после того, как Adler32 дообработает последний байт. Ведь как только мы получили последний байт, мы тут же переходим в состояние sAdler, и хотели бы тут же вытащить оттуда 4 байта контрольной суммы, но нам нужно подождать немножко.

Остаются комбинаторные схемы, формирующие нам выходы.

Выход данных WR_Data - по сути мультиплексор. В зависимости от режима, мы либо подаём байт заголовка, либо байт данных (прошедший "насквозь"), либо байт контрольной суммы.

Выход разрешения записи WR_EN оказывается всегда единичным в состоянии sHeader (поскольку байты следуют друг за другом без остановки), затем повторяет импульсы ByteCE (приход очередного байта на вход) в состоянии sData, и потом остаётся нулевым в состоянии sAdler, до тех пор, пока Adler32 не обработает последний байт и начнёт выдавать свои байты, по одному за такт. В состоянии sIdle WR_EN всегда нулевой.

Для проверки этого модуля мы в кои-то веки написали примитивнейший генератор тестового сигнала - того самого "полосатого" изображения 48х1. Мне хотелось, чтобы в тестовом примере происходило "переполнение" контрольной суммы Adler32, и мы бы убедились в корректной работе "схемы коррекции", которая счёт по модулю 65536 (обычная целочисленная арифметика без насыщения) превращает в счёт по модулю 65521. Для этого хватило бы последовательности из 23 байт FF, но хитрый фотошоп догадался при сохранении чёрного прямоугольника 23х1 поставить фильтр Sub, поэтому там был лишь один байт FF, за которым следовало 22 нуля. Поэтому в итоге я взял "полосатую" картинку, которую просто так не "отфильтруешь"! Но поскольку байтов FF надо было хотя бы 23, то для ровного счёта постановил размер 48х1.

Вот он, "генератор":
module ZLIB_fast_test_data_gen (input clk, input start, output reg [7:0] Q = 8'b0, output reg ceo = 1'b0);

wire NoBytesLeft;

reg t = 1'b0;

wire raw_ceo;

CustomCounter counter (.clk (clk),
					   .ce (~NoBytesLeft),
					   .ceo (raw_ceo));
defparam
	counter.Fin = 80_000_000,
	counter.Fout= 20_000_000;
	
	
lpm_counter ByteCounter (.clock (clk),
						 .cnt_en (raw_ceo),
						 .sset (start),
						 .cout (NoBytesLeft));
defparam
	ByteCounter.lpm_direction = "DOWN",
	ByteCounter.lpm_port_updown = "PORT_UNUSED",
	ByteCounter.lpm_type = "LPM_COUNTER",
	ByteCounter.lpm_width = 6,
	ByteCounter.lpm_svalue = 6'd49;
						   
always @(posedge clk) begin
	if (raw_ceo)
		t <= ~t;
	ceo <= raw_ceo;
	Q <= t? 8'hFF : 8'h00;
end
	
//assign Q = t? 8'hFF : 8'h00;

endmodule


Поначалу он "молчит", но при подаче сигнала start начинает выдавать 49 байт (первый байт - фильтр 00, затем 48 пикселей изображения) - перемежающиеся 00 и FF, причём между каждой посылкой делается пауза в 3 такта, чтобы наш несчастный Adler32 успевал обновлять контрольную сумму... Если использовать более "старую" реализацию Adler32, и сколько-нибудь шустрый кристалл, то пауза могла бы быть всего в 1 такт. Нас это вполне устраивает - ведь сейчас данные поступают на вход на скорости 20 МБайт / с = 160 МБит/с (чистыми) - у нас пока не подключено столь шустрой периферии, которая могла бы передать эти данные или сохранить их!

Наконец, вот полная схема для проверки работы нашего модуля:


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

Как водится, несколько "внутренних" цепей мы вывели наружу с префиксом D (debug), чтобы лучше понять, что же там происходит.

Первая часть "осциллограммы" приведена в начале поста. Когда мы запускаем формирователь, он переходит в состояние 1 (sHeader) и передаёт 7-байтный заголовок (2 байта Zlib + 5 байт Deflate), затем переходит в состояние 2 (sData) и ждёт поступления данных на вход. Они передаются "насквозь", только прибавляется адрес и уменьшается количество оставшихся байт.

Так продолжается некоторое время, пока все данные не закончатся:


Тогда модуль переходит в режим sAdler, но не торопится выдавать новые данные - ждёт, когда Adler32 закончит свою работу. И действительно, мы видим, что данные установились далеко не сразу, а это ещё и коррекция у нас прошла "вхолостую" - иначе правильный ответ появился бы ещё на пару тактов позже. Собственно, там, где мы его и выдаём. Зато дальше дело происходит очень быстро - каждый такт выдаём по байту, уже знакомую нам последовательность, которую мы увидели и в PNG-изображении, которое сгенерил "фотошоп".

И наконец, модуль возвращается в состояние sIdle, и сидит там, пока мы не запустим его вновь.

Модуль ZLIB_wrapper, в который входит и Adler32, и ZLIB_header_bytes, целиком занимает 175 ЛЭ, если используется 14-битная адресация и размер блока 11536 байт (16 * 721, т.е 16 строк по 720 пикселей, плюс байт в начале каждой из них, чтобы выбрать фильтр). Честно говоря, многовато на мой вкус. 175 ЛЭ здесь, ещё 151 ЛЭ на передатчик PNG - и уже выходит 326 ЛЭ, или 3,3% от общего числа логических элементов в этой ПЛИС. Возможно, если у нас будет процессорное ядро, то часть этих операций стоило бы выполнять "программно", но не уверен.

Adler32 оказался на удивление "мерзопакостным" в реализации! И эти пляски с порядком байтов всё время оказываются нам "в минус". Сейчас, например, мы видим, как сначала обновляются "младшие" 2 байта (s1), а только потом - "старшие" (s2). Разумеется, в стандарте прописали, что передавать надо именно старшими байтами вперёд! Ну чего бы вам не сделать наоборот, заодно внеся единообразие между Zlib и Deflate - и тогда мы бы передавали s1, в это время досчитывая s2. И с CRC32 тогда всё задом наперёд вышло.


Фух, с "обёртками" вроде всё - можно теперь эти картинки генерить на лету. Этим мы и займёмся в следующий раз.
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 

  • 2 comments