Видеообработчик: нахождение "второй" суммы

Jun 05, 2020 16:34

Вот этой суммы:


До сих пор не знаю, как её правильно обозвать.

Ничего сложного, соответствующий блок уже был готов:




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

Дальше видим ПЗУ, lpm_rom0. Это чисто для отладки: вместо того, чтобы принимать "реальные пиксели" откуда-то извне, мы их пока берём из памяти. Там сейчас записана одна строка в 32 пикселя, это строка под номером 4 из нашего "тестового изображения".

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

И наконец, совсем справа стоит модуль PixelAdder, который только находит сумму, но подключён он к выходу PixelSumMax, т.е суммирует "частичные суммы". Сейчас я его чуточку переделал, тоже заменил плюс на минус, чтобы "отрицательные значения", идущие на вход, снова стали положительными. Вообще, разницы быть не должно, но так оно приятнее немножко:

module PixelAdder (input clk, input sclr, input [InputWidth-1:0] Pixel, output [OutputWidth-1:0] Q);

parameter InputWidth = 12;
parameter OutputWidth = 20;

localparam CounterWidth = OutputWidth - InputWidth;

wire [InputWidth-1:0] Sum;
reg [InputWidth-1:0] LowerQ = 1'h0;
wire PlusOne;
lpm_add_sub Adder ( .dataa (LowerQ),
.datab (Pixel),
.cin (1'b1),
.result (Sum),
.cout (PlusOne));
defparam
Adder.lpm_direction = "SUB",
Adder.lpm_hint = "ONE_INPUT_IS_CONSTANT=NO,CIN_USED=YES",
Adder.lpm_representation = "UNSIGNED",
Adder.lpm_type = "LPM_ADD_SUB",
Adder.lpm_width = InputWidth;

always @(posedge clk)
LowerQ <= sclr? 1'h0 : Sum;

wire [CounterWidth-1:0] CounterOut;
lpm_counter UpperBits ( .clock (clk),
.cnt_en (~PlusOne),
.sclr (sclr),
.Q (CounterOut));
defparam
UpperBits.lpm_direction = "UP",
UpperBits.lpm_port_updown = "PORT_UNUSED",
UpperBits.lpm_type = "LPM_COUNTER",
UpperBits.lpm_width = CounterWidth;

assign Q = {CounterOut, LowerQ};
endmodule

И параметры переименовал: были PixelWidth и PixSumWidth, а стали более "нейтральными" InputWidth и OutputWidth. Иначе на "общей схеме" получался конфликт: Quartus, находя модуль, вход которого имеет ширину PixelWidth, подставлял туда "глобальный" параметр, а не "внутренний", которые назывались одинаково, но имели разные значения. Теперь не конфликтует.

В итоге, вся эта штуковина (что на схеме) синтезируется в 106 ЛЭ. Из них примерно 8 - для "горизонтального счётчика", 49 - для модуля суммы/максимума, а PixelAdder получился покрупнее из-за большей разрядности. 20 бит на входе - это 40 ЛЭ на сумматоре и "нижнем" регистре, и ещё 6 ЛЭ на "верхнем" регистре точнее, счётчике), итого как минимум 46 ЛЭ. Всё складываем - выходит 103 ЛЭ, ну ещё 3 ЛЭ где-то "на логику". Нормально.

Максимально допустимая частота: 40,16 МГц, главный "тормоз" - обращение к памяти. Но нас пока всё устраивает, QuatCore и вовсе на 25 МГц работает, так что пускай.

И запустим симуляцию:



Увы, что-то не то... Всё-таки "вляпались" в заморочки дополнительного кода, как обычно. С одной стороны, мы заявили: у нас число БЕЗ ЗНАКА, ВСЕГДА ОТРИЦАТЕЛЬНОЕ. Но с другой, ноль попытались оставить нулём. Похоже, в этом и дело: когда мы с 20 бит "расширяемся" до 26 бит, и при этом хотим, чтобы ноль был нулём, то нужно делать "расширение знака": ноль расширяется до нуля (6 дополнительных бит тоже нули), а вот все остальные числа должны дополняться 6 единицами!

Ох, неудобно...

Сдаётся мне, в этой ситуации лучше всего подойдёт "обратный код", вместо "дополнительного". Они отличаются на единичку. Так, все единицы будут выражать ноль, 1111_1110 (в случае 8 бит) будет выражать -1, 1111_1101 будет выражать -2, и так далее. Чтобы оно так вышло, нужно PixelSumMax инициализировать не нулями, а всеми единицами. Раздражает, что при нахождении максимума его надо инициализировать нулями, что ещё усложняет реализацию. Теоретически, число ЛЭ возрасти не должно, а там кто его знает...

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

module PixelSumMax (input clk, input sclr, input isSum, input [PixelWidth-1:0] Pixel, input [XregWidth-1:0] X, output [PixSumWidth-1:0] Q);

parameter PixelWidth = 12;
parameter PixSumWidth = 20;
parameter XregWidth = 8;

localparam CounterWidth = PixSumWidth - PixelWidth;

wire [PixelWidth-1:0] Sum;
reg [PixelWidth-1:0] LowerQ = 1'h0;
wire PlusOne;
lpm_add_sub Adder ( .dataa (LowerQ),
.datab (Pixel),
.cin (1'b0),
.result (Sum),
.cout (PlusOne));
defparam
Adder.lpm_direction = "ADD",
Adder.lpm_hint = "ONE_INPUT_IS_CONSTANT=NO,CIN_USED=NO",
Adder.lpm_representation = "UNSIGNED",
Adder.lpm_type = "LPM_ADD_SUB",
Adder.lpm_width = PixelWidth;

always @(posedge clk) if (isSum | PlusOne | sclr)
LowerQ <= sclr? {PixelWidth{~isSum}} : isSum? Sum : ~Pixel;

wire [CounterWidth-1:0] CounterOut;
lpm_counter UpperBits ( .clock (clk),
.cnt_en (PlusOne&isSum),
.sclr (sclr),
.data (X),
.sload (PlusOne&(~isSum)),
.Q (CounterOut));
defparam
UpperBits.lpm_direction = "DOWN",
UpperBits.lpm_port_updown = "PORT_UNUSED",
UpperBits.lpm_type = "LPM_COUNTER",
UpperBits.lpm_width = CounterWidth;

assign Q = {CounterOut, LowerQ};
endmodule

По счастью, этот модуль по-прежнему синтезируется в 49 ЛЭ, т.е он "не усложнился".

Ну и PixelAdder возвращаем "как было", со сложением вместо вычитания. И пробуем ещё разок:



Вот теперь всё как надо. Видим, как в сумму попало первый ненулевой пиксель, 19. В этот же момент "вторая сумма" (на схеме мы её обозначили Mul, это ещё со времён того упоротого умножителя повелось) нулевая. Это как бы p0*0.

На следующем такте мы взяли пиксель 170, сумма вышла 19+170=189, а вторая сумма: 19*1+170*0=19. Всё верно.

Далее, у нас пиксель 0, сумма остаётся 189, а вторая сумма: 19*2+170*1+0*0=208 - так и есть.
Ещё один пиксель 0, сумма 189, вторая сумма 19*3+170*2+0*1+0*0=397 - оно самое.
Ещё один пиксель 0, сумма 189, вторая сумма 19*4+170*3+0*2+0*1+0*0=586.
Ещё один пиксель 0, сумма 189, вторая сумма 19*5+170*4+0*3+0*2+0*1+0*0=775.
Следующий пиксель 114, сумма 19+170+114=303 - всё верно. Вторая сумма: 19*6+170*5+0*4+0*3+0*2+0*1+114*0=964.
Следующий пиксель 437, сумма 19+170+114+437=740. Вторая сумма: 19*7+170*6+0*5+0*4+0*3+0*2+114*1+437*0=1267.

Работает хреновина! Великая вещь школьная математика!

Осталось убедиться, что и нахождение максимумов мы не поломали:



Всё работает: старшие две шестнадцатеричные цифры в SUM показывают координату самого яркого (на данный момент) пикселя, а три младших - эту самую яркость, но в инверсии. Так, 013 = 0000_0001_0011 превратился в FEC = 1111_1110_1100, 0AA = 0000_1010_1010 превратился в F55 = 1111_0101_0101, и так далее. Чуть-чуть необычно, но в целом жить можно. Может, её в таком виде и оставить для QuatCore, его же "абсолютные величины" и не интересуют практически. На выходе Mul образуется некий "мусор" - координата максимума и сам максимум (инвертированный) воспринимаются как одно число, и суммируются. Ну и пускай суммируются - мы просто этот выход будем игнорировать в режиме захвата.

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

странные девайсы, математика, ПЛИС, программки, работа

Previous post Next post
Up