Finalmente llegamos a las
instrucciones DSP.Largo camino recorrido. Mucha lectura y algunos ejemplos, todos fueron probados y simulados.
Hoy vamos a ver una introduccion, tal ves demasiado breve/extenso para algunas personas. No creo que sea necesario explicar parte por parte el modulo DSP, si no que voy a realizar unos ejemplos y algunas aclaraciones que nunca estan por demas.
Ademas de los registros de trabajo W existen otros registros del nucleo tales como
PC - Program Counter
SR - Status register ALU + DSP + algunos bits mas de indicacion
SPLIM - Stack Pointer Limit , que ya se nombro antes
PSVPAG y TBLPAG - Tambien se explicaron que es
RCOUNT - Este es un contador para la instruccion REPEAT
DCOUNT, DOSTART, DOEND - Como su nombre lo dice para la instruccion DO
CORCON - Registro que controla el nucleo / motor dsp / y para saber que anidado de DO tenemos
Finalmente encontramos 2 registros extra largos.
ACCA y ACCB
Esto registros son los Acumuladores para el DSP, poseen 40 bits de largo, lo cual eso quiere decir que el motor DSP deberia tener una ALU capaz de sumar valores de ese largo. Como son tan largos lo dividen de la siguiente forma:
ACCA = ACCAU:ACCAH:ACCAL
Todos son de 16 bits menos ACCAU que es de 8 bits.
El motor DSP puede funcionar con 2 tipos de datos. En el cual son enteros o fraccionarios.
En los registros W que son de 16 bits se cargan con enteros o con fraccionarios punto fijo Q15, es decir el Msb es el signo y lo demas la fraccion ( pongo el punto para que se note donde es)
0.5 = 0.100 0000 0000 0000
-0.5 = 1.100 0000 0000 0000
0.25 = 0.010 0000 0000 0000
El acumulador tiene otra forma, para los enteros se tiene que los limites maximos son para cada modo:
-549,755,813,888 < x < 549,755,813,887
-256.0 < x < 256.0 - 2^-31 (El punto se ubica a la derecha del bit 31 - Q.31)
Maxima resolucion en Q.31 = 4.567 * 10^-10
Lo primero a notar es que no se usan completamente los 40 bits a pesar de ser punto fijo. ¿Por que? Mas delante se dira
Para no complicarnos demasiado la existencia no vamos a entrar en la parte de los fraccionarios y solo vamos a quedarnos con los enteros.
Como vemos hay valores negativos. ¿Esto quiere decir que podemos Multiplicar valores con y sin signo en una operacion?, por ejemplo 2 * -5
Si se puede realizar esto. Antes de comenzar a trabajar con las instrucciones debemos indicarle a nuestro nucleo que tipo de datos va a recibir. Por que 0xFFFF, puede ser tanto como -1 o como 65535.
Para eso tenemos un bit en el registro CORCON llamado US. Este bit no afecta a las otras instrucciones que son del MCU, es decir por ejemplo MUL.UU
Pero solo hay que remarcar una diferencia. En las instrucciones de MCU podiamos hacer MUL.US, en el que una de los datos era una representacion binaria sin signo y la otra con signo. En las instrucciones de DSP esto no es posible, o son las dos representadas con signo o las 2 sin signo.
Bit de Status ( SR )Volviendo al tema de los acumuladores, estos tienen bits de STATUS tambien, pero no son como los de la ALU que siempre usamos ( Z,C,DC,etc). Sino que avisan "otros" problemas. Entre los cuales se encuentran algunas soluciones para una pregunta que se dio unos parrafos atras.
Los bits de Status indican Overflow y Saturacion "sticky" (pegajoso o persistente)
¿Que es este overflow?Estamos acostumbrados a micros de 8 bits, en el que la cantidad de bits esta limitada por este tamaño, cuando sumamos un valor a este registro y el resultado excede lo previsto normalmente se activa nuestro flag de Carry, que es el indicador de overflow. En el DSP ocurre exactamente lo mismo, pero comprobar el overflow a cada rato es algo ineficiente algunas veces. En 8 bits si seguimos sumando y no hacemos algo con el carry se pierde ese valor y no podemos saber cuantos "overflows" sucedieron.
El motor DSP tiene una solucion para esto,que es esa parte alta de 8 bits del acumulador ( ACCxU ). Cuando se produce multiples sumas/restas, la ultima suma/resta es la que modifica el bit de overflow ( igual que el flag carry) pero esta ves ya no nos importa ver si realmente ocurrio o no, por que todo overflow va a ir a sumarse en el ACCxU. Se podria decir que este registro son unos bits de guarda. Y tenemos 8 bits!
Nuevamente con las analogias. Recordamos que el flag de carry si seguiamos operando se borraba, ejemplo en esta suma:
W = 0xFF
W = W + 0x10 -> C=1
W = W + 0xFF -> C=1
W = W + 1 -> C=0Es imposible notar cuantas veces ocurrio el overflow, asi que hacer esto es un error que no se puede recuperar
En el DSP ocurre algo parecido, pero mejor, ya no es catastrofico como antes
ACCA = 0x00.FFFF.FFFF
ACCA += 0x10 -> ACCA = 0x01.0000.000F , la cual ACCAU = 0x01 , y el bit de overflow OA = 1
ACCA += 0x00.FFFF.FFFF -> ACCA = 0x02.0000.000E, ACCAU = 0x02 , OA = 1
ACCA += 0x1 -> ACCA = 0x02.0000.000F, ACCAU = 0x02 , OA = 0Espero haber sido claro con el ejemplo, son muchos bits asi que separo en 16 bits de esa forma es mas simple de leer.
Con este flag tambien es posible generar una interrupcion ( Arithmetic Error ). La cual nos va a permitir arreglar si deseamos corregir las cosas para que sigan su camino, si es deseado obviamente.
Curiosidad, si por alguna razon desean saber si alguno de los acumuladores, el A o B ocurrio un overflow en su ultima operacion, tiene un bit que es la OR de ambos flag de cada acumulador.
SaturacionAlgo que asumimos cuando hablamos de overflow es que siempre se tratan de numeros sin signos ( unsigned ) lo cual es menos evidente que realmente terminemos con los 40 bits "llenos" 0xFF.FFFF.FFFF . Esto ocurre tambien con numeros unsigned, pero con numeros con signos se hace un poco mas simple de entenderlo. Asi que vamos a verlo por ahi.
El modo de saturacion es un accesorio mas que poseen los DSP. En este caso el dsPIC ofrece un modo que puede ser de 39bits o de 31bits.
En 39 bits el maximo a representar seria, 1 bit de signo y 39 bits de "fracciones", el de 31 bits es igual asi que solo voy a explicar uno solo.
0x7F.FFFF.FFFF - Maximo positivo
0x80.0000.0000 - Minimo negativo
En el caso de que llegar a 0x7F.FFFF.FFFF y producirse una suma, no solo se produce un overflow sino que ademas destruimos el bit de signo, si sumamos 1 a ese valor lo representado paso de ser el maximo positivo al maximo negativo. En 31 bits con signo esos 8 bits de guarda que teniamos SON el signo extendido, y no nos sirve mas el valor:
0x00.7FFF.FFFF
0xFF.8000.0000
Entonces, como se proteje uno de estos problemas? Para eso esta la saturacion Lo que se hace es si por ejemplo teniamos 0x7F.FFFF.FFFF y sumamos lo que sea, es que quede en 0x7F.FFFF.FFFF, es decir esta saturado, en pocas palabras limitamos a nuestro valores entre un rango.
Pero esto no es una operacion matematica valida, ejemplo si limito a 100 y hago 60 + 43, deberia ser 103 y no 100, pero esto es importante que exista un limite en el procesamiento digital de señales.
¿Que es peor? o ¿Cual esta mas lejos del valor real?. Saturacion o no saturacion
Sin saturacion: 0x7F.FFFF.FFFF + 0x1 = 0x80.0000.0000 = -549,755,813,888
Con saturacion: 0x7F.FFFF.FFFF + 0x1 = 0x7F.FFFF.FFFF = 549,755,813,887
Clara la diferencia... El registro de status posee bits para indicar esto, asi como una OR de ambos acumuladores, la diferencia con el bit de overflow, es que es persistente y solo se pone a 0 por orden del usuario.
EJEMPLOCreo que ya se vio demasiado texto, procedamos a un ejemplo de uso. No vamos a tocar la saturacion, ni nada por el estilo, solo vamos a activar nuestro modulo, activar nuestros generadores y probar un par de funciones. Para eso hice este codigo
.global _main ; De donde viene del startup.
.global __DefaultInterrupt
;..............................................................................
;Iniciacion de variables
;..............................................................................
.section *,data,xmemory
BuffA: .fixed 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9
BuffiA: .hword 1, 2, 3, 4, 5, 6, 7, 8, 9
.section *,data,ymemory,address(0xC00)
BuffB: .fixed 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9
BuffiB: .hword 1, 2, 3, 4, 5, 6, 7, 8, 9
.text ;Seccion codigo
;..............................................................................
;Configuracion de pines
;..............................................................................
_main:
;Configuracion de Puertos
mov #0x0000, W0
mov W0, LATB ; Limpio los valores de los latch
mov W0, TRISB ; Ponemos todos los pines de salida RB0-15
mov W0, ODCB ; Se configuran los pines para no trabajar como
; como colector abierto.
;..............................................................................
;Programa Principal
;..............................................................................
MainLoop:
;Seteado
BSET CORCON,#IF ; Activo modo entero
BSET CORCON,#US ; Multiplicacion sin signo
MOV #0x1212,W0 ; W0 = 0x1212
LAC W0,#0,B ; ACCB = W0 << 0 = 0x00.1212.0000
MOV #BuffiA,W0 ; Cargo direccion del Buffer de X en X
MOV W0,W8 ; Direccion de inicio
MOV W0,XMODSRT ; En ambos lados.
MOV (#BuffiA+18-1),W0
MOV W0,XMODEND ; Direccion final
MOV #BuffiB,W0 ; Cargo direccion del Buffer de Y en Y
MOV W0,W10 ; Direccion de inicio
MOV W0,YMODSRT ; En ambos lados.
MOV (#BuffiB+18-1),W0
MOV W0,YMODEND ; Direccion final
MOV #0xFFA8,W0 ; Activo ambos
MOV W0,MODCON ; Habilitamos el modulo, y que W1 sirva de puntero, W0 va a tener el valor de llenad
; Borro el accumulador y cargo los primeros valores
CLR A, [W8]+=2, W4, [W10]+=2, W5
; MAC W4*W5, A, [W8]+=2, W4, [W10]+=2, W5
; ACCA = ACCA + W4*W5
; [W8] -> W4
; [W10] -> W5
; W8+=2, W10+=2
; redondeado(ACCA) -> W13
REPEAT #8
a: MAC W4*W5, A, [W8]+=2, W4, [W10]+=2, W5
NOP
; Preparamos registros
; Debo cargar el primer caso en W4 y acomodar los punteros como para el segundo caso
; Primer caso:
; W4 tiene ya el Primer dato
; W4 = Primero - Ultimo
; W8 debe apuntar al 2do dato, ya esta apuntando ahi
; W10 debe apuntar al Penultimo dato
BCLR CORCON,#US ; Multiplicacion sin signo
CLR A, [W10]-=4, W5 ;W10 apunta al ultimo, A=0
SUB W4, [W10--], W4 ; W4 = [W10] - W4 -> W10-=2
; ACCA = (X - Y)^2
; ED W4*W4, A, [W8]+=2, W4, [W10]+=2, W4
; ACCA = W4*W4
; [W8] - [W10] -> W4
; W8 += 2 , W10 -=2
REPEAT #8
b: ED W4*W4, A, [W8]+=2, [W10]-=2, W4
NOP
; ACCA = ACCA + (X - Y)^2
; ED W4*W4, A, [W8]+=2, W4, [W10]+=2, W4
; ACCA = W4*W4
; [W8] - [W10] -> W4
; W8 += 2 , W10 +=2
CLR A
REPEAT #8
c: EDAC W4*W4, A, [W8]+=2, [W10]-=2, W4
NOP
BRA $ ; Loop Infinito
;----------------- Fin del codigo ---------------------------------------------
__DefaultInterrupt:
BRA $
.end
A pesar que esta explicado en los comentarios hay algunas limitaciones de las instrucciones que ahi no nombre, por ejemplo la primera que es MAC ( multiplicar y acumular )
MAC W4*W5, A, [W8]+=2, W4, [W10]+=2, W5 , W13
Esta instruccion la podemos separar en partes:
1: MAC W4*W5, A,
2: [W8]+=2, W4,
3: [W10]+=2, W5 ,
4: W13
1 - Lo que se va a multiplicar si o si tiene que estar en W4 a W7, es decir W4*W5, W4*W7, y se deposita en el acumulador A.
A partir de aca son opcionales
2 - Primero acceso indirecto, terminado lo de 1, procede a tomar el dato que apunta [W8], lo guarda en W4 y procede a aumentar W8 en 2. Aqui implicitamente cargamos nuestro proximo dato en W4, y ya apuntamos al otro dato. Las limitaciones son: Puntero solo W8 o W9, y el post incremento/decremento puede ser de +=2,4,6 y -=2,4,6 o [W9 + W12], y solo depositarlos en W4 a W7
3 - Nuestro segundo puntero! A nuestro segundo dato, igual que el anterior caso. pero solo acepta W10 y W11 como punteros, y tambien se puede extender con [W11 + W12]
4 - Por ultimo tenemos nuestro Write-back en el que se toma lo del otro acumulador, se redondea y se envia a W13 ( 40bits en 16bits mmmmmm )
Y todo es magicamente en un solo ciclo. El ejemplo es "simple", pocas instrucciones muchas cosas suceden asi que no es tan simple, usa 3 instrucciones de DSP, se crearon 2 array con variables, uno en el espacio X y otro en el espacio Y, se configuran las AGU para no tener que recargar mas los punteros, y directamente se procede a ejecutarlas.
Resultados:Antes de continuar y dar los resultados se le hace notar al lector que se pusieron unas banderas para saber de que instruccion estamos hablando.
Instruccion "a", MACEstamos haciendo una multiplicacion con acumulacion de los cuadrados de los primeros 9 numeros decimales, sin el cero, es decir 1² + 2² + 3² + .... + 9²
Esto lo podriamos haber realizado de 2 formas, o podriamos haber hecho:
MAC W4*W4, A, [W8]+=2, W4
Sin ningun problema y solo tomariamos los valores del primer puntero(buffer), pero para demostrar que se puede acceder a 2 lados a la misma ves decidi ir por lo mas completo.
Registros iniciales (gracias al CLR el cual me preparo todo para comenzar):
W4=W5=0x01 , W8=0x816 , W10=0xC16 , A=0x0 , W13=0x0 , B= 0x00.1212.0000 ( Inicial )
Cargue B solo para ver el Write-Back de la funcion, y ver como funciona su "redondeo" (Si recuerdan: 40bits en 16bits).
Resultados al final de cada repeticion de la instruccion :
1. W4=W5=0x2 , W8=0x818 , W10=0xC18 , A=0x1 , W13=0x1212 ( Luego del 1*1 )
2. W4=W5=0x3 , W8=0x81A , W10=0xC1A , A=0x5 (Luego del 2*2)
3. W4=W5=0x4 , W8=0x81C , W10=0xC1C , A=0xE
4. W4=W5=0x5 , W8=0x81E , W10=0xC1E , A=0x1E
5. W4=W5=0x6 , W8=0x820 , W10=0xC20 , A=0x37
6. W4=W5=0x7 , W8=0x822 , W10=0xC22 , A=0x5B
7. W4=W5=0x8 , W8=0x824 , W10=0xC24 , A=0x8C
8. W4=W5=0x9 , W8=0x814 , W10=0xC14 , A=0xCC
9. W4=W5=0x1 , W8=0x816 , W10=0xC16 , A=0x11D (Luego del 9*9 y valores finales de los registros )0x11D = 285 Resultado que obtuve con la calculadora tambien, asi que puedo asegura que es correcto.
En fin hicimos todo eso en 1 sola instruccion y ademas 1 ciclo cada una de esas repeticiones
Una cosa a observar que se puede notar aqui. Debe hacer una precarga antes de iniciar. Muy facil de realizar al comienzo. De no haber realizado la precarga ( es decir cargar en W4 y W5 los datos y apuntar a los datos que siguen) tendria que haber realizado una repeticion mas, y ademas eso no me asegura los valores que tenga W4,W5 y el acumulador. Lo que me hubiera llevado mas instrucciones.
Finalmente se observa la utilidad de las AGU, que volvieron los punteros a los lugares que corresponden y estan listos para la proxima instruccion.
Si hubieramos usado otra instruccion MAC nos hubiera sido espetacular todo el tema de las AGU, si observan los punteros y registros son los mismos que estaban antes de iniciar la MAC. Pero....
Instruccion "b", ED o Euclidean Distance, (x-y)² Esta no se acumula, solo calcula eso, lo hice ciclico tambien por que no me costaba realmente nada! Este caso si hacia lo mismo que antes iba a dar 0 en todos los resultados ya que como los valores son iguales la resta va a ser 0 en W4. Asi que para realmente ver el cambio, decidi cambiar el orden de ejecucion de los buffers, esta ves W10 va a ir hacia atras.
ED W4*W4, A, [W8]+=2, [W10]-=2, W4
Ocurre en este orden:
A = W4 * W4
W4 = [W8] - [W10]
W8 += 2 , W10 -=2
Antes de comenzar como cambiamos todo el orden de direccion de los buffers tuvimos que cargar los valores del primer caso en W4. Y acomodar el orden de los punteros, bastante simple fue que con 2 instrucciones ya lo tenia listo. Ademas esta ves los numeros iban a ser con signo. Esto es obvio, si hacia 1-9 me iba a dar en hexa 0xFFF8 , eso al cuadrado como sin signo es: 0xFFF0.0040 = 4.293.918.784
Como que no tiene sentido si era asi, para nadie...
Veamos los valores:
W4=0xFFF8 (-8) , W8=0x816 , W10=0xC22 , A=0x0 ( Inicial - Borrado el Acumulador, preparado los punteros y preparado W4 con el primer valor )
Resultados al final de cada repeticion de la instruccion :
1. W4=0x2 , W8=0x818 , W10=0xC20 , A=0x40 ( Luego del (1-9)² = 64 )
2. W4=0x3 , W8=0x81A , W10=0xC1E , A=0x24 ( Luego del (2-8)² = 36 )
3. W4=0x4 , W8=0x81C , W10=0xC1C , A=0x10
4. W4=0x5 , W8=0x81E , W10=0xC1A , A=0x4
5. W4=0x6 , W8=0x820 , W10=0xC18 , A=0x0 ( Luego del (5-5)² )
6. W4=0x7 , W8=0x822 , W10=0xC16 , A=0x4
7. W4=0x8 , W8=0x824 , W10=0xC14 , A=0x10
8. W4=0x9 , W8=0x814 , W10=0xC24 , A=0x24
9. W4=0x1 , W8=0x816 , W10=0xC22 , A=0x40 ( (9-1)² = 64 )Nuevamente vemos que se cumplio todo, los punteros a nuestros Buffers estan ya para otra ronda. El valor cargado en W tambien, vimos una operacion con numeros signados y vimos que se cumple.
Algo que tal ves paso desapercibido es que no tuve que tocar nada de las AGU para que si voy en el otro sentido se acomode solo. G E N I A L
Instruccion 'c' EDAC :Por ultimo solo para jugar nomas hice lo mismo que antes, pero en ves de solo sobreescribir el acumulador, hago que lo sume. Valores iniciales igual que los anteriores, dejo los resultados
1. W4=0x2 , W8=0x818 , W10=0xC20 , A=0x40
2. W4=0x3 , W8=0x81A , W10=0xC1E , A=0x64
3. W4=0x4 , W8=0x81C , W10=0xC1C , A=0x74
4. W4=0x5 , W8=0x81E , W10=0xC1A , A=0x78
5. W4=0x6 , W8=0x820 , W10=0xC18 , A=0x78
6. W4=0x7 , W8=0x822 , W10=0xC16 , A=0x7C
7. W4=0x8 , W8=0x824 , W10=0xC14 , A=0x8C
8. W4=0x9 , W8=0x814 , W10=0xC24 , A=0xB0
9. W4=0x1 , W8=0x816 , W10=0xC22 , A=0xF0 Aqui es donde termino yo con el tema de dsPIC y ASM. Tal ves agegue un par de cositas mas como la integracion de ASM y C, Stack como se maneja, pero creo que ya estariamos casi que completos.