nabbla (nabbla1) wrote,
nabbla
nabbla1

Categories:

Дурацкое сжатие ч/б картинки

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

И тут очередная дилемма на ровном месте. Нужно выбрать:

- либо на основе полученного изображения сформировать файл verilog testbench (несинтезируемый, чисто для симуляции), но вот незадача: Quartus не позволяет выбрать файл верилога для симуляции, только Vector Waveform в разных форматах. Значит, если идти по этому пути, надо где-то в другом месте проводить симуляцию. Возможно, скачать более свежий ModelSim, он вроде с testbench на верилоге дружит, вопрос только, воспримет ли он как надо библиотеки для нашего древнего Flex10k, все эти lpm_counter, блоки памяти и пр. - есть у них нехорошая привычка в новых версиях потихоньку избавляться от поддержики старых устройств

- на основе полученного изображения формировать тот самый Vector Waveform File. Тоже вроде штука не самая сложная, хотя всевозможных секций в нём дохрена. "Создавать его с нуля" - дело малоприятное.

- "Зашить" изображение в самый обычный, синтезируемый модуль, только при этом уместиться в несколько килобайт памяти. Всего у нас на ПЛИС 5576ХС4Т имеется 12 килобайт памяти, но из них 1,5 килобайта уже занято процессором :)

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

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


Всё изображение будет храниться как пары (X,Y), где X это 10-битное значение (от 0 до 1023), Y - 6-битное (от 0 до 63), они выражают ближайшую точку, на которой надо будет поменять "цвет", то бишь сменить чёрный на белый или наоборот.

Значение X задаётся "как есть", значение Y - как инкремент к текущему значению.

Для нашего изображения получается 1836 таких пар, что означает: нужно 8 блоков внутренней памяти (БВП, они же EAB) из доступных 21. Как бы картинка ужалась до 3 672 байта, что, как ни странно, МЕНЬШЕ, чем занимает PNG-файл, в котором всего два цвета, чёрный (0) и белый (255), но выставлен режим GRAYSCALE и глубина 8 бит. (сохранение делал фотошоп). Он у меня весит 4 728 байт.

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

Так выглядят наши пары координат:

В начале 33 строки совершенно пустые, поэтому заполняем их чёрным, пока не дойдём до (360;34), там начинаем заполнение белым. Но уже на той же строке (о чём свидетельствует Y=0), на X=368, снова переключаемся на чёрный, затем на 369 - на белый, и т.д.

Нам повезло, что монотонно заполненных областей длиннее 63 строк нигде не нашлось, поэтому такой подход работает вообще без нареканий. В противном случае можно объявить, что дважды упомянутая одна и та же точка как бы ДВАЖДЫ переключает нам цвет, из-за чего мы продолжаем рисовать тем же цветом.

Сейчас у нас был такой вот "генератор тестового изображения":


Надо будет лишь заменить модуль памяти GPU_test_image на чуточку более хитрый модуль. Добавим разве что ещё и VSync туда на вход, чтобы он по прошествии кадра возвращался в исходное состояние, и координату Y заменим на HSync, который будет "вспыхивать" только при переходе на ПОЛЕЗНУЮ строку.

Вот код нового модуля, сделанный "с нахрапу":
module PackedTestImage (input clk, input [9:0] X, input HSync, input VSync, 
			output [7:0] Q);

parameter MemWidth = 11;

wire [MemWidth-1:0] Adr;

wire [9:0] MX;
wire [5:0] MY;
wire [5:0] CurY;

wire ReachedPoint = (X == MX) & (CurY == MY);

	lpm_counter	AdrCnt (
				.clock (clk),
				.cnt_en (ReachedPoint),				
				.sclr (VSync),
				.Q (Adr) );
	defparam
		AdrCnt.lpm_direction = "UP",
		AdrCnt.lpm_port_updown = "PORT_UNUSED",
		AdrCnt.lpm_type = "LPM_COUNTER",
		AdrCnt.lpm_width = MemWidth;
		
	lpm_counter YCnt (
				.clock (clk),
				.cnt_en (HSync),
				.sclr (VSync | ReachedPoint),
				.Q (CurY) );
	defparam
		YCnt.lpm_direction = "UP",
		YCnt.lpm_port_updown = "PORT_UNUSED",
		YCnt.lpm_type = "LPM_COUNTER",
		YCnt.lpm_width = 6;

	lpm_rom	lpm_rom_component (
//				.outclock (clk),
				.address (Adr),
//				.inclock (clk),
				.q ({MY,MX}),
				.memenab (1'b1));
	defparam
		lpm_rom_component.intended_device_family = "FLEX10KE",
		lpm_rom_component.lpm_address_control = "UNREGISTERED",
		lpm_rom_component.lpm_file = "PackedImage.mif",
		lpm_rom_component.lpm_outdata = "UNREGISTERED",
		lpm_rom_component.lpm_type = "LPM_ROM",
		lpm_rom_component.lpm_width = 16,
		lpm_rom_component.lpm_widthad = MemWidth;

reg Color = 1'b0;

always @(posedge clk)
	Color <= VSync? 1'b0 : Color^ReachedPoint;
	
assign Q = {8{Color}};

endmodule


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

Второй - счётчик строк, прошедших с предыдущего отрезка.

Память сделали "бестактной", в надежде, что тайминги мы пройдём. Такая реализация - самая простая, т.к если где-то есть отрезки всего в 1 пиксель (а такие ЕСТЬ), то к следующему такту у нас как раз успевает увеличиться на единичку адрес (в счётчике), и на этом же такте мы уже получаем новую пару (X,Y) и даже успеваем её проверить, так что можем переключиться и ещё раз.

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

Синтезируется оно в 29 ЛЭ, предельная частота 34 МГц, вроде бы неплохо.

Всунем модуль на своё место:


И ещё мы в кои-то веки заменили модуль FastFreqDiv ("быстрый делитель частоты", описан здесь) для выдачи кадровых синхроимпульсов на новый модуль VSyncCounter. Вообще, у нас был ещё FastCounter ("быстрый счётчик", описывал его здесь), и был библиотечный счётчик lpm_counter, но ничего из этого нас не устраивало!

У этого FastFreqDiv очень неприятная для симуляции "багофича", что первый синхроимпульс он выдаёт не через свои положенные 40 мс, а вообще через 54 мс, т.к он в досчитывает не до 754 (общее количество строк, включая "верхние" и "нижние", не отображающиеся на экране), а до 1024, и только после этого выходит на правильную работу.

Но и 40 мс ждать совсем не хочется - "в железе"-то почему бы и нет, а на симуляции это очень тяжело. Поэтому добавили вход VSyncReq, при подаче на которого единички у нас сразу же начнётся новый кадр. У моего FastCounter был вход сброса, но после его задействования как раз-таки придётся ждать 40 мс, а нам-то хочется почти мгновенно. Вот и написал новый модулёк:

`include "math.v"

module VSyncCounter (	input ce,  	//clock enable
			input clk, 	//clock
			input Req,
			output VSync); //clock enable - output

	parameter RowNum = 10;

	localparam DivideBy = RowNum;
	localparam IntWidth = `CLOG2((DivideBy - 1)); //here we don't have to honestly show integer 0 up to DivideBy-1, so we can cheat a little :)
	localparam InitVal = DivideBy - 2; 
	
	wire combTC;	
	reg TC = 1'b0;

	lpm_counter	lpm_counter_component (
				.clock (clk),
				.cnt_en (ce),
				.sset (VSync),
				.sclr (Req),
				.cout (combTC));
	defparam
		lpm_counter_component.lpm_direction = "DOWN",
		lpm_counter_component.lpm_port_updown = "PORT_UNUSED",
		lpm_counter_component.lpm_type = "LPM_COUNTER",
		lpm_counter_component.lpm_width = IntWidth,
		lpm_counter_component.lpm_svalue = InitVal;

	
	always @(posedge clk) if (ce)
		if (DivideBy == 1)
			TC <= 1'b1;
		else
			TC <= combTC;
		
	assign VSync = TC & ce;
	
endmodule
				


За основу взят FastFreqDivider, но убраны лишние параметры, т.е Fin и Fout заменены на один RowNum (число строк изображения). И направление счёта теперь не "снизу вверх", а "сверху вниз". Разница в том, что регистры в счётчиках всегда инициализируются нулями, поэтому тут мы сразу же получаем синхроимпульс. И подача единички на вход сброса заставляет этот импульс повторить сразу же, как придёт ce (count enable).

Что ж, давайте попробуем теперь всё это промоделировать. Модуль PackedImageGen (та самая "схема", изображённая выше), синтезируется в 114 ЛЭ и 32768 бита внутренней памяти (33% от ресурсов данной ПЛИС), предельная частота 37 МГц - нормально. Посмотрим его по отдельности:



А ведь работает!


Вот теперь этот злостный баг от меня не скроется!
Tags: ПЛИС, программки, работа, странные девайсы
Subscribe

  • Ковыряемся с сантехникой

    Наконец-то закрыл сколько-нибудь пристойно трубы, подводящие к смесителю, в квартире в Москве: А в воскресенье побывал на даче, там очередная…

  • Крещенская резня бензопилой

    Вчера наконец-то доехал на лыжах до Гремячего и расчистил немного эту лыжню. Ничего фатального не было, но стало поприятнее. А оттуда приехал на…

  • Зимний водопровод, динамо и макет

    В эти выходные так и не открыл лыжный сезон, хотя мог. Зато впервые наладил холодную и горячую воду на даче ЗИМОЙ, в минусовую температуру! И…

  • Дачное электро-сантехническое

    Чуть ли не 10 лет назад купил SMS-розетку под брендом "Мегафон", чтобы дистанционно включать отопление на даче. Сначала хотел что-то своё хитрое…

  • Рекордный лыжный мостик

    В субботу ездил в Герасимиху, находящуюся в средоточении наших лыжных маршрутов. Откуда не стартуй - Радонеж, Абрамцево, Калистово, Морозки - так или…

  • Приключения итальянцев на мосводоканале

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

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments