nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Мучаем 5576ХС4Т - часть 'h28 - передатчик изображения PNG

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


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


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


Самый запутанный - модуль SendPNGImage. Ему мы задаём адрес, по которому в памяти начинается PNG-изображение, и запускаем его, подав "единичку" на start. Этот модуль "защёлкивает" исходный адрес и дожидается поступления символа, лежащего по этому адресу (на это уходит два такта).

И мы приходим в состояние sDecision ("выбор"), после чего возможен один из 3 вариантов:
- этот первый байт начинается с битов 10. Это похоже на заголовок PNG (там первый байт 0x89). Если бы это было начало одного из блоков (первый байт из четырёх, которые кодируют длину блока), это означало бы блок размером свыше 2 ГБ - у нас нет столько памяти, а даже если бы и было, я ни разу не встречал такой большой картинки, и не думаю, что хоть одна программа могла бы её открыть :) Так что с чистой совестью считаем, что нас "отправили" в самое начало, к заголовку. Передаём первые 8 байт, а вот за ними уже идёт один из блоков, начинающийся с 4 байт длины блока.

- первый байт начинается с битов 11. Это точно не заголовок PNG, и также не может быть началом блока, т.к означало бы его размер в 3 ГБ. Так что это наша специальная метка, чтобы прервать передачу. Это мы и делаем.

- первый байт начинается с бита 0. Тогда мы считаем, что нам подсунули начало очередного блока.

Передаём первые 4 байта (длина блока), а также заносим их в счётчик. "Обнуляем" модуль CRC, т.к длина блока в CRC не входит. И начинаем передавать данные - столько, сколько указано в длине, плюс ещё 4 (имя блока). Затем передаём 4 байта CRC, которых не было в памяти - их мы посчитали только что. После этого мы снова переходим в состояние sDecision - и круг замыкается.

Это даёт нам некоторую "гибкость" в работе с данным модулем. Если картинка маленькая, мы можем поместить её всю и за один раз передать, не забыв только после блока IEND поставить байт FF, который заставит нас завершить работу. (это проще, чем заставлять модуль прочитывать имя блока и только по IEND останавливаться).

Но если картинка большая, то можно сделать хитрее. Сначала мы отправляем только 8-байтный заголовок и блок IHDR (в нём указывается размер изображения, его цветность и пр.) - за ним ставим байт FF, чтобы прекратить работу. Затем мы получаем (то ли с камеры, то ли откуда-то ещё) первый кусочек изображения - "оборачиваем" его в блок IDAT, и запускаем его отправку. В конце блока снова стоит FF, и мы снова останавливаемся, формируем на том же месте новую область изображения, и снова запускаем наш модуль. И наконец, указываем адрес в памяти, где лежит финальный блок IEND и передаём его.

Немножко разберёмся в паутине проводов
Входы clk, start и InitAddr - всё понятно.
Char - символ, запрошенный из памяти.
OutAddr - адрес, который мы запрашиваем.
StartTx - сигнал для запуска передатчика UART.
ResetCRC - обнуляет (точнее инициализирует всеми единицами) CRC, это нужно сделать перед тем, как передавать имя блока и его данные.
ChooseCRC - управление мультиплексором, чтобы в передатчик UART пошли данные не из памяти, а из модуля CRC. Он же запрещает CRC принимать новые биты с выхода передатчика (ведь это наши собственные биты CRC и передаются, не нужно их друг с другом мешать!).
ShiftCRC - когда мы отправили один из байтов CRC, мы подаём сюда единичку на 24 тактовых импульса, чтобы данные внутри "провернулись", и на мультиплексор поступил следующий байт, в порядке, предусмотренном стандартом PNG.
DState - выход для отладки, состояние передатчика.
DLength - выход для отладки, сколько байт длины осталось принять, либо сколько байт CRC осталось передать.
DBytesLeft - выход для отладки, сколько байт данных осталось передать (но потом ещё 4 сверх того!)

Наконец, в модуле передатчика UART появился новый выход: DataOutCE. С него выходит единичный импульс каждый раз, когда на выходе передатчика появляется новый бит данных (стартовые и стоповые биты не считаются!). Как видно, это позволяет нам подключить модуль CRC прямо к выходу передатчика и брать последовательные биты именно оттуда. Но чтобы он игнорировал биты CRC, которые передаются отсюда же, пришлось добавить немножко примитивной логики, почему бы и нет :)

Ну а теперь посмотрим на потроха модуля SendPNGimage:
//rather complex logic of sending chunks of PNG image
//in memory we have full or partial PNG image, but WITHOUT any CRC (they are just skipped).
//we must compute them "on the fly".
//when starting, we can find png header (8 bytes) which violates overall structure of PNG. We must understand it
//and transmit it 'as is', after which return to our usual routine (finding length of chunk, transmitting it as a whole,
//then transmitting its CRC32 value, searching for next chunk (if there is any)

module SendPNGimage (input clk, input start, input [AddrWidth - 1 : 0] InitAddr, input [7:0] Char, input TxReady,
					output reg [AddrWidth - 1 : 0] OutAddr, output reg StartTx=1'b0, output ResetCRC, output ChooseCRC, output ShiftCRC,
					output [2:0] DState, output [3:0] DLength, output [AddrWidth - 1 : 0] DBytesLeft);
					
parameter AddrWidth = 12; //we can address 4096 bytes this way. Maximum we can expect on our FPGA internal memory is 96 kilobits, or 14 bits addr.
				
				
	reg [AddrWidth - 1 : 0] BytesLeft = 1'b0;
	wire NoBytesLeft = (BytesLeft == (1 << AddrWidth) - 3); //condition to move from Data to CRC, as long as last byte was transmitted
	
	reg [AddrWidth - 1 : 0] BytesLeftSR = 1'b0;
	
	wire [3:0] LengthCounter; //at beginning, it treats 8-byte header as 'unused length bytes' as well.	
	wire NoLengthBytesLeft;
				
				
//our state machine
	localparam sIdle     =	3'b000;
	localparam sFetch1   = 	3'b001;
	localparam sFetch2   = 	3'b010; //it seems we better use fully registered memory with 2 clocks of delay
	localparam sDecision = 	3'b011; //either return to Idle, or latch LengthCounter and proceed further.
	localparam sLenBytes =  3'b100;
	localparam sData     =  3'b101;
	localparam sCRC      =  3'b110;
	//111 - not used so far
	
	wire [2:0] State;
	
	assign DState = State;
	assign DLength = LengthCounter;
	assign DBytesLeft = BytesLeft;
	
	wire IsDecisionState = State[1] & State[0]; //little shortcut because 111 is not used
	wire IsCRCState = State[2] & State[1]; //yet another shortcut
	
	wire StateCountEn = start |   //to go from Idle to Fetch 1
			    (~State[2] & (State[1] | State[0])) | //to go from Fetch1 to Fetch2 to Decision to LenBytes each clock
			    TxReady & State[2] & (((~State[0])&NoLengthBytesLeft) | (State[0])&NoBytesLeft); //probably could be simplified if BytesLeft and LengthCount initialized properly...
						
	wire StateReset = IsDecisionState & Char[7] & Char[6]; //came up with end-of-file symbol
	
	wire StateSet = IsCRCState & TxReady & NoLengthBytesLeft; //transmitted all 4 CRC bytes
	
	lpm_counter	counter (
				.clock (clk),
				.cnt_en (StateCountEn),
				.sset (StateSet),
				.sclr (StateReset),
				.q (State) );
	defparam
		counter.lpm_direction = "UP",
		counter.lpm_port_updown = "PORT_UNUSED",
		counter.lpm_type = "LPM_COUNTER",
		counter.lpm_width = 3,
		counter.lpm_svalue = sDecision;
	
	wire [3:0] LengthInit = (State[1] & Char[7])? 4'd4 : 4'd12;
	
	lpm_counter	LengthCountUnit (
				.clock (clk),
				.cnt_en (TxReady),
				.sload (State[0]),
				.data (LengthInit),
				.q (LengthCounter),
				.cout (NoLengthBytesLeft) );
	defparam
		LengthCountUnit.lpm_direction = "UP",
		LengthCountUnit.lpm_port_updown = "PORT_UNUSED",
		LengthCountUnit.lpm_type = "LPM_COUNTER",
		LengthCountUnit.lpm_width = 4;
	
	
	always @(posedge clk) begin
	//doing our lovely RTL code, without state machines
		OutAddr <= start? InitAddr : (StartTx & (~IsCRCState))? OutAddr + 1'b1 : OutAddr;
		
		//actual only on sLenBytes (there we must shift left) and sData / sCRC (we must store 24)
		BytesLeftSR <= State[2] & (State[1] | State[0])? 5'd21 : TxReady? (BytesLeftSR << 8 | Char) : BytesLeftSR;
		
		//actual only on sData (should minus 1 on each byte transmitted) and CRC (should minus 1 on each clk, until it goes to 0, then wait TxReady and restore init value)
		BytesLeft <= (StartTx | (~State[1])) & (~State[0])? BytesLeftSR :
					((~NoBytesLeft) &(TxReady & (~IsCRCState) | IsCRCState))? BytesLeft - 1'b1 : BytesLeft;

		StartTx <= (State == sDecision)&(~(Char[7]&Char[6])) | (TxReady & (State != sIdle) & ((State != sCRC) | (~NoLengthBytesLeft))); 
		//so, it starts at particular moment, then just relays TxReady without problem, until we've transmitted everything!
		//also, blocked at idle (because we have the most bizzare UART transmitter, gotta change it eventually),
		//and also we don't like when in CRC mode we finally came to NoLengthBytesLeft...
	end
	
	//at last, doing some assigns

	assign ResetCRC = (State != sData) & (State != sCRC);
	
	//connect UART transmitter (or whatever byte transmitter we have) to CRC module output instead of memory
	assign ChooseCRC = IsCRCState;
	
	//makes CRC module shift data (to output all of 32 bits, 8 bits at a time) instead of computing it
	assign ShiftCRC = IsCRCState & (~NoBytesLeft);
endmodule


Поначалу я решил, что тут уже достаточно сложная логика работы, чтобы "честно" расписать конечный автомат, но когда этот конечный автомат "расползся" в 146 ЛЭ, решил всё-таки напрячься и сделать в своём любимом RTL-стиле (Register Transfer Level - тот уровень абстракции, в котором мы сейчас барахтаемся). Получилось 68 ЛЭ - самую малость поменьше.

Надо сказать, что такой выигрыш в 2-3 раза между исполнением "в лоб" через конечный автомат и "в RTL" - не сколько наша заслуга, сколько недоработка "компилятора" verilog. Я пробовал писать строки наподобие
BytesLeft <= 12'bxxxxxxxxxxxx;


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

Увы, компилятор это понимает не в смысле "мы можем здесь делать что угодно", а в смысле "мы можем присвоить ему произвольную константу". И выбирает, что это будет ноль. Не сильно помогает...

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


Снова мы сделали наш конечный автомат счётчиком, поскольку последовательность какая-никакая прослеживается. Мы используем 7 состояний:
- sIdle - режим ожидания
- sFetch1 - запросили в оперативной памяти байт по исходном адресу, ждём ответа
- sFetch2 - по-прежнему ждём ответа
- sDecision - дождались ответа, и либо возвращаемся в sIdle "несолоно хлебавши", либо запускаем передачу и идём дальше. Вариант с передачей 8-байтового заголовка удалось не выводить в обособленное состояние - мы просто сделали вид, что у нас не 4 байта длины, а 12 байт!
- sLenBytes - передаём байты длины блока (и, возможно 8-байтового заголовка), а также заносим байт за байтом в сдвиговый регистр, чтобы запомнить длину. Ничего страшного, если мы запихали 12 байт в 12-битный регистр - всё лишнее просто "перелезет через край" и исчезнет.
- sData - обнулив CRC, передаём имя блока (4 байта) и полезные данные
- sCRC - передаём 4 байта CRC.

Логика самую чуточку сложнее, чем раньше: кроме входа установки sset, который нужен, чтобы по окончании состояния sCRC перейти в sDecision, у нас также есть вход сброса sclr, чтобы из sDecision возвратиться в sIdle.

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

Понятное дело, одного лишь "конечного автомата" ("основного счётчика") нам недостаточно. Мы вводим ещё два счётчика:
- BytesLeft - имеет ту же ширину, что и адресная шина. Именно в него мы записываем длину блока, а затем при передаче каждого байта уменьшаем значение на единицу. Но признак NoBytesLeft формируется не по достижении нуля, как можно было подумать, а ещё спустя 4 байта, потому что имя блока (chunk) не входит в его длину, но передать надо и его тоже! Во время передачи CRC, BytesLeft "меняет профессию" - теперь он отсчитывает 24 такта, на время которых включается режим shiftCRC, чтобы подать на вход UART следующий из байтов CRC. По счастью, это не сильно усложнило логику работы.

- LengthCounter - отсчитывает, сколько байт занимает длина блока. Обычно 4, но если у нас есть 8-байтный заголовок, то "делаем вид", что это продолжение длины, так что 12. Поэтому ширина LengthCounter фиксирована - 4 бита. Во время передачи CRC, LengthCounter также подсчитывает переданные байты - их должно быть ровно 4 :) Благодаря этому логика серьёзно упрощается - в "чётных" состояниях sLenBytes и sCRC мы переходим в следующее состояние, когда LengthCounter досчитает до конца, а в "нечётном" sData - когда досчитает BytesLeft.

Кроме того, нам нужен счётчик OutAddr для хранения текущего адреса, и прибавления единички, когда мы передали очередной байт (но не CRC - они не в памяти лежат!), и сдвиговый регистр BytesLeftSR для "защёлкивания" длины блока, которая занимает больше одного байта. По идее, можно было "объединить" BytesLeftSR и BytesLeft, но полученный "монстр" больше не являлся бы счётчиком, и занял бы даже больше, у меня получилось около 40 ЛЭ при ширине всего 12 бит. В нынешнем виде (когда они разделены), получается лишь чуточку более 24 ЛЭ.

В кои-то веки мы сделали сигнал запуска передатчика StartTX регистровым, чтобы избежать длиннющих комбинаторных петель (StartTX напрямую зависит от TXReady, так что получается взад-вперёд), ну и так удобнее получается с точки зрения реализации.

Если кому-то захочется, могу разъяснить этот код строку за строкой, он на самом деле простой, только упоротый.

А в целом процесс настолько долгий, что передачу хотя бы даже первого блока (IHDR) уместить на одном экране симуляции нереально.

Вот начало процесса:


Наш долбанутый передатчик UART опять делает своё грязное дело - торжественно передаёт строку единичных бит (без стартового бита), что с точки зрения линии никак не проявляется, но под конец он докладывает - "работа выполнена" (TXReady)! Чтобы этот импульс (а также импульсы DataOutCE) не сбивал нас с толку, мы дождались окончания этого дела (оно происходит ровно один раз при инициализации ПЛИС), после чего подали наконец-то стартовый импульс.

Состояние State быстро перепрыгивает с нулевого (sIdle) через sFetch1, sFetch2, sDecision на четвёртое (sLenBytes). Во время sDecision мы увидели байт 0x89, и решили - давайте работать дальше, нужно прочитать 12 байт длины блока :) Поэтому Length (это отладочный сигнал, присоединённый к LengthCounter) установилась в 4, чтобы досчитать до 15. И тогда же, во время sDecision, мы выдали импульс запуска передачи - байт 0x89 пошёл в линию! А раз так, то счётчик адреса сразу увеличился на единицу, и на выходе оперативной памяти через пару тактов закрепился следующий байт - "P".

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

Прошло 8 байт заголовка, за ним пошли 4 байта длины блока IHDR: 00 00 00 0D.

В конце осциллограммы (той, что выше по тексту) мы видим, что мы запустили передачу байта 0D. Можно ещё понаблюдать, как себя ведёт счётчик BytesLeft - он вбирает в себя очередной байт, а прошлое своё содержимое сдвигает влево. Через него "прошёл" весь заголовок и "вылетел наружу", теперь мы заполнили его нулями, и вот к моменту передачи байта 0D в нём образовалось правильное значение - 13 байт! Тем временем счётчик Length дошёл до значения F, и на выходе cout появилась единичка, извещающая, что мы всё передали. Осталось лишь получить подтверждение от передатчика, что он "освободился".

Поехали дальше.


Сигнал TXReady (подтверждение, что последний байт "длины" был передан) пришёл, и мы перешли в состояние sData. В то же самое время прекратилась подача ResetCRC, так что с этого момента начался подсчёт контрольной суммы. Ну и сразу же мы запустили на передачу очередной байт, "I" из "IHDR". И снова всё пошло своим чередом, но теперь заработал счётчик BytesLeft, тогда как к LengthCounter мы потеряли всякий интерес :) А на самом деле, он уже "подготовился" к следующему этапу! Готовь сани летом!

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

Мы видим сначала 4 байта названия - IHDR, затем по 4 байта - ширина и высота изображения (и то, и другое: 00 00 04 00, что означает: 1024х1024).

Следующий слайд!


Продолжаем передавать IHDR: теперь идёт 1 байт - "глубина цвета" (Bit depth), 0x10, что означает - 16 бит на пиксель (на канал).
Следующий байт - цветность (Color Type), нулевое значение означает - градации серого.
И наконец, ещё три байта - это тип компрессии (поддерживается только значение 0, что означает применение ZLIB, алгоритма Deflate), затем тип фильтра (поддерживается только значение 0, что означает 5 типов фильтров - None, Sub, Up, Average, Paeth) и наконец, Interlacing, который в безграничной мудрости стоит перевести на русский как "прогрессивный режим", хотя казалось бы progressive и interlace должны быть антонимами! В общем, значение 0 соответствует отсутствию Interlacing, когда мы представляем изображение строку за строкой, слева направо сверху вниз. Ещё бывает значение 1 - это метод Adam7, когда сначала мы передаём каждый 8-й пиксель из каждой 8-й строки, образуя "превьюшку", затем добавляем в хитрой последовательности, чтобы по мере загрузки мы уже могли получить представление, что здесь будет.

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

Также замечаем, что BytesLeft давным-давно дошёл до нуля и "перевернулся", посчитав также FFF, FFE и FFD. "Переопределить ноль" для нас было легче, чем специально прибавлять 4 к тому числу, что мы прочитали! Последнее потребовало бы дополнительно 12 ЛЭ (возможно, 10, если синтезатор правильно сообразит, что младшие биты не участвуют), а так мы "ничего не теряем".

Итак, пришёл сигнал TXReady, подтверждающий передачу последнего байта IHDR, и мы переключаемся в состояние sCRC. На выходе ChooseCRC появляется единица, что заставляет мультиплексор подсоединить к входу UART выход CRC. Также ровно на 24 такта появляется сигнал ShiftCRC, и действительно, мы наблюдаем "мельтешение" значений на входе UART, которое затем останавливается - мы получили корректный следующий байт и ждём, пока закончится отправка предыдущего. Как только это происходит - мы отправляем текущий байт и в очередной раз производим циклический сдвиг на 24 позиции.

Наконец, передача 4 байт CRC заканчивается, и мы переходим в состояние 3 (sDecision), где видим нулевой байт на входе и понимаем - это пошёл следующий блок. Поэтому в этот раз в состоянии 4 (sLenBytes) мы передадим и "защёлкнем" в сдвиговый регистр лишь 4 байта, а не 12, как в прошлый раз. Также мы видим, что мы снова "непрерывно" сбрасываем CRC (не настало ещё его время), а также вернули ChooseCRC в нолик, т.е ко входу UART снова подключилась память.

Далее всё продолжается своим чередом - мы передаём особенно "толстый" блок IDAT, а за ним - самый короткий IEND.

Осталось посмотреть, как процесс завершается.


У IEND всегда получается один и тот же CRC: AE 42 60 82 (откройте любой png в редакторе и можете убедиться в этом!)
Мы их видим здесь, правда, первое значение AE появляется всего на один такт, поэтому здесь не видно, но оно появляется в конце, когда сдвиговый регистр делает "полный круг". Оно уже никуда не идёт.

А дальше мы видим в памяти значение FF (специально его поставили после окончания), которое заставляет нас из состояния sDecision вернуться в sIdle, не передавая больше ничего.

УРРА, товарищи!

Как всегда, нас интересует вопрос - "насколько жирно оно вышло?". Ответ: 151 ЛЭ, или 1,5% от логической ёмкости ПЛИС.

Из них 68 уходят на SendPNGimage (управляющий модуль), и уменьшить его уже сложно:
- 12 ЛЭ нужны для счётчика адреса (мы говорим про 12-битную адресацию сейчас. Меняя параметр ADDR_WIDTH, можно её поменять)
- 12 ЛЭ для счётчика оставшихся байт,
- 12 ЛЭ для сдвигового регистра, считывающего оставшиеся байты,
- 4 ЛЭ для счётчика байт заголовка и поля длины,
- 3 ЛЭ для счётчика "конечного автомата",
- 3 ЛЭ для компаратора BytesLeft == 'hFFD (поскольку в любом случае у нас 12 входов, а каждый ЛЭ берёт по 4),
- 4 ЛЭ для управляющих выходов: ShiftCRC, ChooseCRC, ResetCRC, StartTX
это уже 50. Оставшиеся - некая "связующая логика", которая действительно весьма сложна. Думаю, если очень хорошо закопаться, можно ещё "выцарапать" 1-2 ЛЭ. Оставим это читателям в качестве самостоятельного упражнения :)

Ещё 35 ЛЭ - на вычислитель CRC, 8 ЛЭ - на мультиплексор - уже получается 111.

Наш передатчик UART когда-то занимал 27 ЛЭ, но почему-то после добавления регистра на выход txd (чтобы комбинаторные "пички" не уходили по проводу, это мешало работать на высокой скорости), а также добавления нового выхода DataOutCE "раздулся" аж до 36 ЛЭ - Quartus опять потерял, где там счётчики... Видимо, придётся его переделать в итоге - задолбал немножко его "бзик" самопроизвольно запускаться при включении ПЛИС - так-то это никому не вредит, но каждый раз при симуляции приходится про это помнить...


Осталось за малым - прошить эту штуку в ПЛИС и посмотреть, что получается. А потом возьмёмся за формирование "с нуля" блока IDAT с потоком ZLIB, ну и оцифровкой собственно!
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