реклама на сайте
подробности

 
 
2 страниц V   1 2 >  
Reply to this topicStart new topic
> Необычное использование аппаратного умножителя
Rst7
сообщение May 7 2008, 17:14
Сообщение #1


Йа моск ;)
******

Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610



Вот, сижу, мастерю один проектик. Понадобилось сделать Хаффмана. В принципе, ничего сложного, однако работа с битовыми полями - это всегда узкое место процов с отсутствием комманд для обработки данных такого класса. Например, при упаковке по Хаффману необходима процедура, которая запишет в выходной поток n бит значения - вызывается примерно так:
Код
write_bits(huff->codes[val],huff->bits[val])

где первый параметр - код, а второй - собственно его размер в битах. Вот обычно эта функция - write_bits и несет основные затраты по времени выполнения. Почему так происходит - понятно, надо сдвигать входные биты в нужное положение, вычислять новое положение в буфере и т.д.

Посему, сразу кодить не стал, сел - подумал... biggrin.gif

И вот что надумал...

У нас есть аппаратный умножитель, который за 2 такта даст нам сдвинутый сразу в двух направлениях байт - и влево, и вправо. Конечно, в качестве множителя надо использовать маску - 0x01,0x02,...,0x80 и в результате, после выполнения комманды MUL в R0 будем иметь байт данных, сдвинутый влево, в R1 - сдвинутый вправо. Это уже отлично, одной коммандой мы готовим данные для OR с текущим байтом и для занесения следующих данных в накопитель. Однако, соответствующую текущему моменту маску еще надо получить. Она зависит как от текущей битовой позиции в выходном буфере, так и от размера битовых данных. Опять пришлось поразмышлять. Размышления натолкнули на идею хранить битовую позицию в буфере тоже в виде маски:
Код
0x80(биты 7...0 свободны),0x40(бит 7 с данными, биты 6...0 свободны),....,0x01(биты 7...1 с данными, бит 0 свободен)


Если размер символа в битах тоже задать в виде маски, то происходит чудо wink.gif - умножение (аппаратное, конечно) размера на битовую позицию даст новую позицию и она же является необходимым (точнее, в 2 раза меньше) множителем для сдвига символа. Причем, в зависимости, от того, какой байт результата, старший или младший, не равен 0 - то это и есть выбор двух альтернатив - новый символ требует перехода через границу байта или не требует.
Осталась маленькая тонкость, что новая позиция как множитель данных в 2 раза меньше (т.е. сдвинута вправо). Можно конечно сдвинуть при помощи LSL, но лучше использовать тот факт, что FMUL - есть Rs*Rd<<1, это поможет в оптимизации.

Букаф много (могут ниасилить), посему код:

Код
//Кластеризация вручную, при необходимости изготавливается нормальная структура
struct
{
  char bitstream_byte;
  char bitstream_bit;
  char *bitstream;
};

//Инициализация битового потока
//p - указатель на первый байт выходного буфера
void init_bitstream(char *p)
{
  bitstream_byte=0;
  bitstream_bit=0x80;
  bitstream=p;
}

//Финализация потока - слив неслитого
//bp - указатель на первый байт выходного буфера (для расчета размера)
//на выходе - размер потока в байтах
unsigned int finish_bitstream(char *bp)
{
  char *p=bitstream;
  if (bitstream_bit!=0x80) *p++=bitstream_byte;
  return p-bp;
}

void write_bits_by_mask(char sym, char msb);

//Запись n бит из символа sym, необходимо, чтобы неиспользуемые биты равнялись 0
void write_n_bits(char sym, char n)
{
  //Генерация обратной маски
  static __flash const char mask[]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
  write_bits_by_mask(sym,mask[n-1]);
}

//Записать в битовый поток биты из sym
//Биты записываются от 0 до бита, определенного установленным битом в msb в обратном порядке
//msb=0x80 sym=0000000a
//msb=0x40 sym=000000ab
//msb=0x20 sym=00000abc
//msb=0x10 sym=0000abcd
//msb=0x08 sym=000abcde
//msb=0x04 sym=00abcdef
//msb=0x02 sym=0abcdefg
//msb=0x01 sym=abcdefgh
//Запись идет от 7 к 0, например запись символов 000abcde, 0000xyzt будет лежать в байтах как
//[abcdexyz][t.......]
//Если есть возможность хранить msb, лучше применять эту функцию, меньше накладных расходов
void write_bits_by_mask(char sym, char msb)
{
  char b=bitstream_byte;
  char m=bitstream_bit;
  char *p=bitstream;
  //--> Если есть возможнось, эта часть инлайнится во внешний цикл
  unsigned short i;
  i=__multiply_unsigned(m,msb); //ih - множитель для sym, чтобы согласовать положение
  if (i>>8)
  {
    //Требуется сдвиг влево
    m=i>>8;
    i=__fractional_multiply_unsigned(sym,m);
    b|=i;
  }
  else
  {
    //Требуется сдвиг вправо
    m=i;
    i=__fractional_multiply_unsigned(sym,m);
    b|=i>>8;
    *p++=b;
    b=i;
  }
  //<-- Конец части для инлайна
  bitstream=p;
  bitstream_byte=b;
  bitstream_bit=m;
}


Компилятор - IAR 5.10

Ну и листинг, который особо радует глаз (приведу только внутренности функции write_bits_by_mask)
Код
     54          void write_bits_by_mask(char sym, char msb)
   \                     write_bits_by_mask:
     55          {
   \   00000000   2F5B               MOV     R21, R27
   \   00000002   2F6A               MOV     R22, R26
   \   00000004   2F20               MOV     R18, R16
     56            char b=bitstream_byte;
   \   00000006   ....               LDI     R30, LOW(_A_bitstream_byte)
   \   00000008   ....               LDI     R31, (_A_bitstream_byte) >> 8
   \   0000000A   8130               LD      R19, Z
     57            char m=bitstream_bit;
     58            char *p=bitstream;
   \   0000000C   81A2               LDD     R26, Z+2
   \   0000000E   81B3               LDD     R27, Z+3
   \   00000010   8101               LDD     R16, Z+1
     59            //--> Если есть возможнось, эта часть инлайнится во внешний цикл
     60            unsigned short i;
     61            i=__multiply_unsigned(m,msb); //ih - множитель для sym, чтобы согласовать положение
   \   00000012   9F01               MUL     R16, R17
     62            if (i>>8)
   \   00000014   2011               TST     R1
   \   00000016   F021               BREQ    ??write_bits_by_mask_0
     63            {
     64              //Требуется сдвиг влево
     65              m=i>>8;
   \   00000018   2D41               MOV     R20, R1
     66              i=__fractional_multiply_unsigned(sym,m);
     67              b|=i;
   \   0000001A   032C               FMUL    R18, R20
   \   0000001C   2930               OR      R19, R0
   \   0000001E   C005               RJMP    ??write_bits_by_mask_1
     68            }
     69            else
     70            {
     71              //Требуется сдвиг вправо
     72              m=i;
   \                     ??write_bits_by_mask_0:
   \   00000020   2D40               MOV     R20, R0
     73              i=__fractional_multiply_unsigned(sym,m);
   \   00000022   032C               FMUL    R18, R20
     74              b|=i>>8;
     75              *p++=b;
   \   00000024   2931               OR      R19, R1
   \   00000026   933D               ST      X+, R19
     76              b=i;
   \   00000028   2D30               MOV     R19, R0
     77            }
     78            //<-- Конец части для инлайна
     79            bitstream=p;
   \                     ??write_bits_by_mask_1:
   \   0000002A   83A2               STD     Z+2, R26
   \   0000002C   83B3               STD     Z+3, R27
     80            bitstream_byte=b;
   \   0000002E   8330               ST      Z, R19
     81            bitstream_bit=m;
   \   00000030   8341               STD     Z+1, R20
     82          }
   \   00000032   2FA6               MOV     R26, R22
   \   00000034   2FB5               MOV     R27, R21
   \   00000036   9508               RET


Конечно, если есть возможность, выжимку кода необходимо вставлять прямо в цикл генерации символов, чтобы меньше оверхеда было на загрузку/выгрузку. В этом случае добавление битового поля будет занимать 10 или 12 тактов, в зависимости от того, происходит переход через границу байта или не происходит.

Опять же, если есть возможность, надо хранить размеры символов в виде масок, а не в обычном виде - это уберет оверхед на функцию write_n_bits (7 тактов).

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

Вообще, подобный подход можно использовать там, где необходимо организовывать сдвиги на произвольное количество бит.


--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
Go to the top of the page
 
+Quote Post
SasaVitebsk
сообщение May 7 2008, 18:22
Сообщение #2


Гуру
******

Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521



Цитата(Rst7 @ May 7 2008, 21:14) *
Вообще, подобный подход можно использовать там, где необходимо организовывать сдвиги на произвольное количество бит.

Интересно! Я тоже в одном месте использовал нестандартно FMUL.

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

По такому принципу.
Код
maska =0x3;
if((X & 2)==0)
{
   maska = 0x30;
   Color <<= 4;
}
if((X & 1)==0)
{
   maska <<=2;
   Color <<=2;
}
Go to the top of the page
 
+Quote Post
Rst7
сообщение May 7 2008, 18:50
Сообщение #3


Йа моск ;)
******

Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610



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


В этом случае конечно да.

Ну а как Вам такой способ?
Код
//  110   char maska =0x3;
        LDI     R18, 3
//  111   if (X&1) maska=0x0C;
    SBRC    R16,0
        LDI     R18, 12
//  112   if (X&2) maska=__swap_nibbles(maska);
    SBRC    R16,1
        SWAP    R18
//  114   c=(Color*0x55)&maska;
        LDI     R16, 85
        MUL     R17, R16
        AND     R0, R18

На входе R16 - X, R17 - Color, на выходе R0 - цвет, R18 - маска. К сожалению, тут компилятор красоту с SBRC ниасилил, это руками писано.


--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
Go to the top of the page
 
+Quote Post
galjoen
сообщение May 7 2008, 19:49
Сообщение #4


Знающий
****

Группа: Свой
Сообщений: 841
Регистрация: 10-05-07
Из: Чебоксары (Россия)
Пользователь №: 27 640



А вот мне только-что мысль пришла - ещё не проверял. Так-что если глупость написал - не пинайте.
Мне кажется, что с помощью MUL можно узнать чётное или нечётное кол-во единиц в байте. Если байт, кол-во единичных битов которого на чётность проверяется, на FF умножить, то в b7 результа будет ТОЛЬКО от чётности зависеть. Я прав? Теоретически вроде так получается.
Go to the top of the page
 
+Quote Post
vet
сообщение May 8 2008, 04:22
Сообщение #5


Знающий
****

Группа: Свой
Сообщений: 550
Регистрация: 16-06-04
Из: Казань
Пользователь №: 32



galjoen
2*255 = 510 = 0x01FE, 7 бит=1
3*255 = 765 = 0x02FD, 7 бит=1


--------------------
Главная линия этого опуса ясна мне насквозь!
Go to the top of the page
 
+Quote Post
Rst7
сообщение May 8 2008, 07:24
Сообщение #6


Йа моск ;)
******

Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610



Цитата(galjoen @ May 7 2008, 22:49) *
А вот мне только-что мысль пришла - ещё не проверял. Так-что если глупость написал - не пинайте.
Мне кажется, что с помощью MUL можно узнать чётное или нечётное кол-во единиц в байте. Если байт, кол-во единичных битов которого на чётность проверяется, на FF умножить, то в b7 результа будет ТОЛЬКО от чётности зависеть. Я прав? Теоретически вроде так получается.


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

Код
;Вход R16 - байт для подсчета
;abcdefgh
;
    MOV    R18,R16
    LDI    R17,0x55
    AND    R16,R17    ;0b0d0f0h
    FMUL    R16,R17
;теперь в R0 суммы
;b0d0f0h0
;d0f0h000
;f0h00000
;h0000000
;как видим, перенос в седьмой бит отсутствует в любом случае
    MOV    R16,R0    ;Сохраняем результат по половине битов и повторяем для второй
    LSR    R18    ;0abcdefg
    AND    R18,R17    ;0a0c0e0g
    FMUL    R18,R17
;теперь в R0 суммы
;a0c0e0g0
;c0e0g000
;e0g00000
;g0000000
    EOR    R16,R0    ;Выход - бит 7 R16 - бит четности


--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
Go to the top of the page
 
+Quote Post
vet
сообщение May 8 2008, 07:54
Сообщение #7


Знающий
****

Группа: Свой
Сообщений: 550
Регистрация: 16-06-04
Из: Казань
Пользователь №: 32



сдвигами четность все равно шустрее выйдет:
Код
        MOV tmp,@0
        SWAP tmp
        EOR tmp,@0
        MOV @0,tmp
        LSR @0
        EOR @0,tmp
        SBRS @0,2
        SUBI @0,1; мл.бит регистра @0 равен 1, если в байте четное число единиц

UPD: убрал выдвигание мл.бита в C для немедленного ветвления


--------------------
Главная линия этого опуса ясна мне насквозь!
Go to the top of the page
 
+Quote Post
Rst7
сообщение May 8 2008, 08:00
Сообщение #8


Йа моск ;)
******

Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610



Цитата
сдвигами четность все равно шустрее выйдет:


Да, точно, я помнил, что коротенькая там процедура, но было лень восстанавливать по памяти, посему показалось, что с умножением быстрее.


--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
Go to the top of the page
 
+Quote Post
_Pasha
сообщение May 29 2008, 19:41
Сообщение #9


;
******

Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509



Америки не открою, и особой экзотики в использовании умножения не будет, но...
Самое быстрое деление 16-битного делимого на 8-битный делитель без остатка.
Методом поразрядного приближения.
Код
div16x8:
// yL = (zL:zH)/yH
// registers used yL,yH,zL,zH,r0,r1 (6)
            ldi    yL,0x80
            mul    yL,yH
            cp    zL,r0
            cpc    zH,r1
            brsh    dv00
            clr    yL
dv00:
            ori    yL,0x40
            mul    yL,yH
            cp    zL,r0
            cpc    zH,r1
            brsh    dv01
            andi    yL,0xbf
dv01:
            ori    yL,0x20
            mul    yL,yH
            cp    zL,r0
            cpc    zH,r1
            brsh    dv02
            andi    yL,0xdf
dv02:
            ori    yL,0x10
            mul    yL,yH
            cp    zL,r0
            cpc    zH,r1
            brsh    dv03
            andi    yL,0xef
dv03:
            ori    yL,0x08
            mul    yL,yH
            cp    zL,r0
            cpc    zH,r1
            brsh    dv04
            andi    yL,0xf7
dv04:
            ori    yL,0x04
            mul    yL,yH
            cp    zL,r0
            cpc    zH,r1
            brsh    dv05
            andi    yL,0xfb
dv05:
            ori    yL,0x02
            mul    yL,yH
            cp    zL,r0
            cpc    zH,r1
            brsh    dv06
            andi    yL,0xfd
dv06:
            ori    yL,0x01
            mul    yL,yH
            cp    zL,r0
            cpc    zH,r1
            brsh    dv07
            andi    yL,0xfe
dv07:
            ret // result = yL

// total clocks = 56 + call/ret

Может, кому пригодится smile.gif
Go to the top of the page
 
+Quote Post
ae_
сообщение May 31 2008, 11:55
Сообщение #10


Участник
***

Группа: Свой
Сообщений: 462
Регистрация: 2-04-07
Из: Иркутск
Пользователь №: 26 695



Как быть, когда результат (zL:zH)/yH > 255? проверять данные перед делением?
Go to the top of the page
 
+Quote Post
_Pasha
сообщение May 31 2008, 18:57
Сообщение #11


;
******

Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509



Цитата(ae_ @ May 31 2008, 14:55) *
Как быть, когда результат (zL:zH)/yH > 255?


Вычесть из делимого и повторить деление остатка.
Код
mul yL,yH
sub zL,r0
sbc zH,r1
brcc pc+2
ret
push yL
;repeat div
pop yh
add yL,yH
...etc


Ну, в данном случае такое не применимо.
А применимо оно в операциях вида N= M*256 / K, где M и K примерно равны. И там, где при этом надо очень быстро все считать.
Go to the top of the page
 
+Quote Post
Rst7
сообщение Dec 13 2008, 21:05
Сообщение #12


Йа моск ;)
******

Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610



Цитата(vet @ May 8 2008, 09:54) *
сдвигами четность все равно шустрее выйдет:
Код
        MOV tmp,@0
        SWAP tmp
        EOR tmp,@0
        MOV @0,tmp
        LSR @0
        EOR @0,tmp
        SBRS @0,2
        SUBI @0,1; мл.бит регистра @0 равен 1, если в байте четное число единиц

UPD: убрал выдвигание мл.бита в C для немедленного ветвления


Случайно пришла в голову мысль (не подумайте, что я тут полгода моск себе парил biggrin.gif ), что шестой бит выражения __multiply_unsigned((v^(v>>1))&0x55,0x55) будет соответствовать четности переменной v

Код
;R16 - вход
        LDI     R17, 85
        MOV     R18, R16
        LSR     R18
        EOR     R16, R18
        ANDI    R16, 0x55
        MUL     R16, R17
;6й бит R0 - четность


12 байт, 7 тактов smile.gif


--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
Go to the top of the page
 
+Quote Post
singlskv
сообщение Dec 14 2008, 19:11
Сообщение #13


дятел
*****

Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065



Цитата(Rst7 @ Dec 14 2008, 00:05) *
Случайно пришла в голову мысль что шестой бит выражения __multiply_unsigned((v^(v>>1))&0x55,0x55) будет соответствовать четности переменной v
Код
;6й бит R0 - четность
ИМХО, очень красиво... beer.gif
но я бы умножал на 0xAA для дальнейшего сдвига в С,
но это так в качестве дальнейшего улучшения...
Go to the top of the page
 
+Quote Post
Rst7
сообщение Dec 14 2008, 19:57
Сообщение #14


Йа моск ;)
******

Группа: Модераторы
Сообщений: 4 345
Регистрация: 7-07-05
Из: Kharkiv-city
Пользователь №: 6 610



Цитата(singlskv @ Dec 14 2008, 21:11) *
ИМХО, очень красиво... beer.gif
но я бы умножал на 0xAA для дальнейшего сдвига в С,
но это так в качестве дальнейшего улучшения...

Да вообщем пофиг, что в перенос двигать, что bst делать, что sbrs/sbrc.

Кстати, для ARM'а весьма красиво смотрится выражение типа if (((v^(v<<1))&0xAA)*0x55)&0x80) - конечно, в arm-режиме, в тумбе - так себе.


--------------------
"Практика выше (теоретического) познания, ибо она имеет не только достоинство всеобщности, но и непосредственной действительности." - В.И. Ленин
Go to the top of the page
 
+Quote Post
singlskv
сообщение Dec 14 2008, 20:08
Сообщение #15


дятел
*****

Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065



Цитата(Rst7 @ Dec 14 2008, 22:57) *
Да вообщем пофиг, что в перенос двигать, что bst делать, что sbrs/sbrc.
я в том смысле что есть выбор, толи в перенос толи sbrs/sbrc.
Цитата
Кстати, для ARM'а весьма красиво смотрится выражение типа if (((v^(v<<1))&0xAA)*0x55)&0x80) - конечно, в arm-режиме, в тумбе - так себе.
А v -8ми битный ?

Аа... Вы о том как это оттранслирует компилятор ?
Go to the top of the page
 
+Quote Post

2 страниц V   1 2 >
Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 26th June 2025 - 04:48
Рейтинг@Mail.ru


Страница сгенерированна за 0.01533 секунд с 7
ELECTRONIX ©2004-2016