background image

Ćwiczenie D4:

P

ODSTAWOWE

 

OPERACJE

 

MATEMATYCZNE

 

NA

 

LICZBACH

 

POJEDYNCZEJ

 

I

 

WIELOKROTNEJ

 

PRECYZJI

 

PRZY

 

ZASTOSOWANIU

 

PROCESORÓW

 

SYGNAŁOWYCH

 TMS320

opracował:

mgr inż. Tadeusz Białoń

Tadeusz.Bialon@polsl.pl

Cele ćwiczenia:

zapoznanie się z kodem uzupełnienia do dwóch

zapoznanie   się   z   metodami   realizacji   operacji   dodawania 
i mnożenia stałoprzecinkowych liczb wielokrotnej i pojedyn-
czej precyzji

zapoznanie się ze skalowaniem typu Q

Wstęp

Kodu uzupełnienia do dwóch

Procesory należące do rodziny C2xxx wykorzystują stałoprzecinkową arytmetykę binarną w kodzie  
uzupełnienia do dwóch (ang. two’s complement). Kod ten, wykorzystywany przez większość obecnie  
produkowanych   mikroprocesorów   stałoprzecinkowych,   swoją   popularność   zawdzięcza   przede 
wszystkim możliwości wyrażania nie tylko liczb dodatnich lecz również liczb ujemnych.   Kolejną  
bardzo  ważną     jego   zaletą   jest  możliwość  realizacji   dodawania  i   odejmowania   za  pomocą  tego  
samego   algorytmu,  co   jest   największą   przewagą   tego   kodu   nad   kodem   znak  –  moduł  (sign   and  
magnitude), wymagającym osobnych algorytmów do przeprowadzenia tych działań.  
Aby liczbę zamienić na liczbę do niej przeciwną należy znaleźć jej dopełnienie logiczne a następnie  
dodać do niego 1.

Przykład 1:
Należy   zamienić   liczbę   16-bitową:  
0000   0000   0000   0110b,   zapisaną   w   binarnym   kodzie 

uzupełnienia do dwóch (w kodzie dziesiętnym 6) na liczbę ujemną o tym samym module.
Najpierw należy wykonać operację negacji: 

        NOT( 0000 0000 0000 0110 b ) = 1111 1111 1111 1001b

następnie dodać 1:

                            1  <= wiersz przeniesienia
           1111 1111 1111 1001b
        +  0000 0000 0000 0001b
        -----------------------
           1111 1111 1111 1010b

Przy okazji zależy zauważyć ważną cechę kodu uzupełnienia do dwóch. O ile przed pierwszą cyfrą  
znaczącą liczby dodatniej można dopisywać zera nie zmieniając jej wartości, np.:

        0000 0000 0000 0110b = 110b (6 w kodzie dziesiętnym)

background image

to przed liczbą ujemną można dopisywać jedynki:

        1111 1111 1111 1010b = 1010b (-6 w kodzie dziesiętnym)

Zamiana z liczby ujemnej na dodatnią przebiega dokładnie tak samo jak z dodatniej na ujemną. 
Jedynym   wyjątkiem   jest   najmniejsza   liczba   ujemna,   czyli   w   16-bitowym   kodzie   uzupełnienia   do  
dwóch 
1000 0000 0000 0000b = -32 768. Liczba ta nie ma liczby do siebie przeciwnej w  

kodzie 16-bitowym, gdyż największa liczba dodatnia to 0111 1111 1111 1111b = 32 767

Przykład 2:
Należy zamienić liczbę 16-bitową 
1111 0001 0101 0000b w binarnym kodzie uzupełnienia do  

dwóch (w kodzie dziesiętnym -3 760) na liczbę dodatnią o tym samym module.
Najpierw,  jak poprzednio, należy wykonać operację negacji: 

        NOT( 1111 0001 0101 0000b ) = 0000 1110 1010 1111b

następnie dodać 1:

                        1 111  <= wiersz przeniesienia
           0000 1110 1010 1111b
        +  0000 0000 0000 0001b
        -----------------------
           0000 1110 1011 0000b

Powyższe przykłady ukazują jeszcze jedną, bardzo ważną zaletę kodu uzupełnienia do dwóch: każdy  
bit wielobitowej liczby jest traktowany jednakowo podczas operacji arytmetycznych. W przypadku  
liczby zapisanej w kodzie znak – moduł, najpierw trzeba od tej liczby oddzielić najbardziej znaczący 
(pierwszy   z   lewej   strony)   bit   a   dalsze   operacje   wykonywać   na   pozostałych   bitach   tej   liczby,  
odpowiednio dobierając algorytm w zależności od tego czy najbardziej znaczący bit był jedynką czy  
zerem (czy liczba jest dodatnia czy ujemna).
Powyżej pokazano, że przed liczbą ujemną można w nieskończoność dopisywać jedynki bez zmiany 
wartości tej liczby. Właściwość ta bywa w pewnych przypadkach źródłem problemów, na przykład  
gdy  wynik   dodawania  dwóch   liczb,   z  których   co   naj-mniej   jedna   jest   liczbą  ujemną,  jest   liczbą  
dodatnią.

Przykład 3:
Należy w 16-bitowym kodzie uzupełnienia do dwóch wykonać działanie 6 + (-5).
Najpierw należy zmienić liczby dziesiętne na odpowiadające im liczby w kodzie uzupełnienia do  
dwóch:

         6 = 0000 0000 0000 0110b
        -5 = 1111 1111 1111 1011b
 
następnie wykonać dodawanie: 

         1 1111 1111 1111 11   <= wiersz przeniesienia
           0000 0000 0000 0110b
        +  1111 1111 1111 1011b
        -----------------------
         1 0000 0000 0000 0001b = 65 537

Jak widać, otrzymany wynik jest ewidentnie niepoprawny.
Zauważmy jednak, że zgodnie z zasadą dopisywania jedynek i zer powyższe działanie może wyglądać  
również tak (w kodzie 32-bitowym):

         1 1111 1111 1111 1111 1111 1111 1111 11   <= wiersz 
           0000 0000 0000 0000 0000 0000 0000 0110b   przeniesienia
        +  1111 1111 1111 1111 1111 1111 1111 1011b
        -------------------------------------------

background image

         1 0000 0000 0000 0000 0000 0000 0000 0001b => 42 949 67 297

Wynika stąd, że teoretycznie powinno się w takich przypadkach dopisać nieskończenie wiele jedynek  
by uzyskać wynik poprawny (zaznaczona na szaro jedynka w ogóle się wtedy nie pojawi). W praktyce 
zaznaczona na szaro jedynka zostanie obcięta przez akumulator mikroprocesora, gdyż znajduje się  
ona poza zakresem jego pojemności – w przykładzie 3 na miejscu dziewiątym gdy mikroprocesor 
operuje w arytmetyce 8-bitowej. W przypadku takim, rdzeń procesora 2xxx nie zgłosi że nastąpiło 
przepełnienie akumulatora. Przepełnienie natomiast zostanie zgłoszone gdy do liczby   
0111 1111 
1111 1111 1111 1111 1111 1111b  dodamy  0000 0000 0000 0000 0000 0000 
0000 0001b, lub do liczby  1000 0000 0000 0000 0000 0000 0000 0000b  dodamy 
1111   1111   1111   1111   1111   1111   1111   1111b  (należy   zauważyć,   że   akumulator 

procesorów serii C24xx i C5x jest 32-bitowy)

Skalowanie typu Q

Wszystkie   liczby   w   poprzednich   przykładach   były   liczbami   całkowitymi.   Zwykle   jednak   zachodzi  
konieczność  wykonywania  obliczeń również na  ułamkach.  Wtedy, aby liczenie na  ułamkach  było  
możliwe, należy zastosować odpowiednie skalowanie. Najczęściej stosowane są skalowania typu Q,  
czyli na  przykład  Q15, Q6, Q10 itd. Występująca  w nazwie skalowania  liczba  określa położenie 
przecinka oddzielającego część całkowitą liczby od części ułamkowej.
Wszystkie liczby całkowite występujące w przykładach 1, 2 i 3 są jednocześnie liczbami w skalowaniu  
Q0 – przecinek znajduje sie na pozycji zerowej czyli przy pierwszej cyfrze binarnej od prawej.

Przykład 4:
Należy zapisać liczby 13 i -27 w 16-bitowym kodzie uzupełnienia do dwóch i skalowaniu Q9.
Liczba 16-bitowa w skalowaniu Q9 ma postać (gdzie 
x oznacza 0 lub 1):

        xxxx xxx,x xxxx xxxxb 

Obie liczby mają część ułamkową równą zeru więc obie liczby po przecinku będą miały tylko bity  
nieaktywne   czyli   zera   w   przypadku   liczby   dodatniej   i   jedynki   w   przypadku   liczby   ujemnej   (w  
przypadku liczby -27 najpierw zakodujemy liczbę przeciwną czyli 27, a potem zmienimy znak liczby  
tak samo jak w przykładzie 1). Otrzymujemy więc:

        13 =  1101b = 0001 101,0 0000 0000b = 0001 1010 0000 0000b

Q9

        27 = 11011b = 0011 011,0 0000 0000b = 0011 0110 0000 0000b

Q9

Na koniec zmieniamy znak liczby 27 na przeciwny:

        -27 = 1100 1011 1111 1111b

Q9

Aby zakodować dowolną liczbę dziesiętną należy wymnożyć tą liczbę przez 2 podniesione do potęgi  
równej   numerowi   pozycji   przecinka,   następnie   zaokrąglić   wynik   pomijając   część   ułamkową   a  
otrzymaną w ten sposób liczbę zakodować binarnie w kodzie uzupełnienia do dwóch.

Przykład 5:
Należy   zapisać   liczby   3,1415,   0,0076   i   1,8463   w   16-bitowym   kodzie   uzupełnienia   do   dwóch   i  
skalowaniu Q14.
Najpierw mnożymy kodowane liczby przez wagę wynikającą z rodzaju skalowania i zaokrąglamy:

        3.1415 * 2

14

 = 3,1415 * 16384 = 51470,336 = 51570

        0.0076 * 2

14

 = 0,0076 * 16384 = 124,51840 = 124

        1.8463 * 2

14

 = 1,8463 * 16384 = 30249,779 = 30250

Na koniec kodujemy otrzymane liczby w 16-bitowym kodzie uzupełnienia do dwóch i dopisujemy  
przecinek na pozycji czternastej:

        51570 => 0 1100 1001 0111 0010b

Q14

  <- kodowanie niemożliwe!

        124   =>   0000 0000 0111 1100b

Q14

 

        30250 =>   0111 0110 0010 1010b

Q14

 

background image

Okazuje się, że liczby 3,1415 nie da sie zakodować! Liczba dodatnia w kodzie uzupełnienia do dwóch  
musi się zaczynać cyfrą 
0 a tutaj zero to pojawia się na pozycji siedemnastej, czyli wykracza poza  

założony zakres. 
Przedział liczb możliwych do zakodowania w kodzie uzupełnienia do dwóch zależy od ilości bitów  
liczby oraz od ustalonego położenia przecinka. Dany jest on wzorem:

−2

b

q−1

2

b

q−1

−2

q

gdzie: b – liczba bitów liczby stałoprzecinkowej, q – pozycja przecinka. 

Przykład 6:
Należy określić przedziały liczb kodowanych za pomocą kodów uzupełnienia do dwóch: 16-bitowego  
Q14, 32-bitowego Q31 i 32-bitowego Q24.
Korzystając z powyższego wzoru otrzymujemy:

        16bit Q14  =>  b=16 q=14  =>  <-2;2-2

-14

>  

        32bit Q31  =>  b=32 q=31  =>  <-1;1-2

-31

>

        32bit Q24  =>  b=32 q=24  =>  <-2

7

;2

7

-2

-24

>

Aby   odczytać   wartość   dziesiętną   liczby   binarnej   w   kodzie   uzupełnienia   do   dwóch   i   dowolnym  
skalowaniu Q należy kolejno: jeżeli jest to liczba ujemna należy zamienić ją na liczbę przeciwną,  
następnie   zapisać   ją   w   postaci   dziesiętnej,   po   czym   podzielić   otrzymaną   wartość   przez   dwa  
podniesione do potęgi równej pozycji przecinka, na koniec uwzględnić ewentualną zmianę znaku. 
Po porównaniu przykładów 5 i 6 staje się jasne dlaczego liczby 3,1415 nie da się zakodować w 16-
bitowym kodzie uzupełnienia do dwóch i skalowaniu Q14.

Przykład 7:
Należy odczytać wartości liczb w 16-bitowym kodzie uzupełnienia do dwóch:  
0010 1101 0111 
0010b

Q6

1111 1100 0100 0110b

Q3

1100 0000 0000 0000b

Q15

.

Najpierw należy zamienić znaki liczby drugiej i trzeciej na przeciwne, gdyż są one liczbami ujemnymi  
– zaczynają się od jedynki:

        - ( 1111 1100 0100 0110b

Q3 

 ) = 0000 0011 1011 1010b

Q3

        - ( 1100 0000 0000 0000b

Q15

 ) = 0100 0000 0000 0000b

Q15

Następnie  zapisujemy wszystkie  liczby  w postaci   dziesiętnej  i  dzielimy  przez  wagi  wynikające  ze  
sposobu skalowania:

        0010 1101 0111 0010b = 11634  =>  11634 / 2

6  

 = 181,78125

        0000 0011 1011 1010b = 954    =>  954 / 2

3  

   = 119,95

        0100 0000 0000 0000b = 16384  =>  16384 / 2

15 

 = 0,5

Na koniec uwzględniamy wcześniejsze zmiany znaków i otrzymujemy:

        0010 1101 0111 0010b

Q6 

 =  181,78125

        1111 1100 0100 0110b

Q3 

 = -119,95

        1100 0000 0000 0000b

Q15

 = -0,5

Przebieg ćwiczenia

Ćwiczenie   polega   na   uruchamianiu   i   testowaniu   działania   gotowych   procedur   arytmetycznych 
realizujących   mnożenie   i   dodawanie   liczb   w   kodzie   uzupełnienia   do   dwóch,   pojedynczej   i 
dwukrotnej precyzji. Procedury te, napisane w asemblerze, oraz całe środowisko programistyczne 
zostaną przygotowane przez prowadzącego ćwiczenie. W trakcie ćwiczenia studenci przy pomocy 
tych procedur wykonują przykładowe obliczenia zadane przez prowadzącego ćwiczenie.

background image

Sprawozdanie z ćwiczenia

Sprawozdanie   powinno   zawierać   przykłady   obliczeniowe   przerobione   w   trakcie   ćwiczenia 
opatrzone  odpowiednimi   komentarzami  dotyczącymi  sposobu  obliczeń oraz  działania  badanych 
procedur arytmetycznych. Przykłady te powinny być opracowane w sposób podobny jak przykłady 
zamieszczone w tej instrukcji.