La respuesta es sí y no a la vez. La instrucción existe, pero no se llama LDPC, sino JMP (del inglés JuMP, salto). Y es que el cargar el PC con un valor trae como consecuencia inmediata que la ejecución pasa (salta) a la dirección que hemos cargado. Sirva este ejemplo de bucle infinito para ver de lo que hablamos:
0x100 CC 00 00 ldd #0 0x103 C3 00 01 bucleinf: addd #1 0x106 7E 01 03 jmp bucleinf
El fragmento de código carga primero el registro D con 0 y, a continuación, se pone frenéticamente a incrementarlo en una unidad ad infinitum. Cuando D llega a 0xFFFF, pasa a 0x0000, se activa el bit de acarreo y vuelta a empezar.
Observad que, aunque lo que se carga en el PC es el valor de la
etiqueta bucleinf
y no el valor contenido en la dirección
de memoria apuntada por ella, no se pone el símbolo
#
delante de ella. Comparad con las instrucciones:
... 0x200 8E 01 03 ldx #bucleinf ; carga X con 0x103 0x203 9E 01 03 ldx bucleinf ; carga X con lo que hay en 0x103; ; mirando arriba el código ; ensamblado, X se carga con 0xC300 ...
No es, sin embargo, esta la instrucción de salto que más se usa. De su familia es la instrucción BRA (del inglés BRAnch, desvío o ramal). El bucle infinito anterior, desde el punto de vista del ensamblador es equivalente al que se muestra a continuación, pero prestad atención al código máquina:
0x100 CC 00 00 ldd #0 0x103 C3 00 01 bucleinf: addd #1 0x106 20 FB bra bucleinf 0x108 ...
El código de instrucción de BRA es 0x20, pero ¿De dónde viene el misterioso 0xFB que aparece a continuación? La respuesta es que se trata de un salto relativo. Dicha instrucción, en código máquina suma el siguiente byte, considerado con signo, al contenido del PC.
0xFB en complemento a dos es -(1+~0xFB) = -(1+0x04) = -5. Luego sumamos al PC -5 para encontrar el destino del salto: 0x108-5 = 0x103. Recordad que, cuando se ejecute la instrucción BRA, el PC ya está apuntando a la siguiente instrucción. Ni que decir tiene que el cálculo de estos desplazamientos los hace el ensamblador por nosotros.
El utilizar BRA en lugar de JMP tiene tres ventajas y un inconveniente:
Para solucionar el inconveniente último, existe una versión que permite desplazamientos de 16 bits: LBRA (Long BRAnch), aunque es más lenta que JMP y ocupa lo mismo.
N | Z | V | C | |
Flag activo | BMI | BEQ | BVS | BCS |
Flag inactivo | BPL | BNE | BVC | BCC |
El significado de las dos últimas columnas es directo: Branch if C Set, Branch if C Clear, ... En el caso del flag Z quedará claro más abajo por qué BEQ significa Branch if EQual y BNE significa Branch if Not Equal. El bit del signo N se activa cuando el resultado de una operación que le afecta es negativo (Branch if MInus) y se desactiva cuando dicho resultado es positivo o cero (Brach if PLus).
Cuando se usan estas instrucciones hay que tener muy presentes cuáles de las instrucciones precedentes han afectado a los flags. Es por ello que en los resúmenes de al final de cada sesión se pone los flags afectados por las instrucciones vistas.
Usemos nuestro nuevo conocimiento para simplificar un poco aquel galimatías de la sesión anterior en el que teníamos que sumar siete al registro B si este era mayor o igual que 10. Recordemos el código original:
std temp clra addb #246 adca #0 ; en A hay un 1 si la primera cifra es mayor o igual que 10 ldb #'A-'9-1 mul addb temp+1
Con lo que hemos visto, lo podemos simplificar bastante:
addb #246 bcc no_sumes addb #'A-'9-1 no_sumes: subb #246
El nuevo código es más rápido, ocupa menos y no necesita hacer uso de una variable temporal ni mancha el registro A. Pues aún lo tenemos que mejorar un poco más en lo que queda de sesión.
El ensamblador logra acercarse al significado de los saltos condicionales
mediante un sencillo truco. Supongamos que queremos comparar dos
números almacenados en las posiciones de memoria valor1
y valor2
. Lo que tenemos que
hacer es hallar la diferencia entre A y B. El ensamblador tiene una
serie de instrucciones que, fijándose en los flags que ha
dejado dicha resta, puede efectuar uno u otro tipo de salto condicional.
El primer ejemplo ya lo hemos visto. Supongamos que queremos saltar
a la etiqueta destino
si A es igual a B. Al hacer A-B,
el resultado nos da cero, por lo que el flag Z se activa. Si miramos
a la tabla de saltos de arriba, debemos codificar lo anterior así:
lda valor1 suba valor2 beq destino ; BEQ corresponde con saltar cuando Z=1
Esta es la razón por la que la instrucción que salta cuando Z vale 1 no se llama BZS (Branch if Z Set), sino BEQ (Branch if EQual): porque están preparadas para ejecutarse después de haber hecho una resta.
Como todos ya sabemos, un número puede ser mayor o menor que otro dependiendo de si los interpretamos como números con o sin signo. Así, en aritmética de 8 bits, 0xFF corresponde con 255, si se considera sin signo, o a -1, si se considera con signo. Si tomamos que A contiene 0x07 y B contiene 0xFF, A<B si consideramos los números sin signo o A>B, si los consideramos con signo. Por esta razón, existen para todas las posibilidades de salto dos versiones, con signo y sin signo y son las que se muestran a continuación:
Condición | Sin signo | Con signo |
Mayor que | BHI (higher) | BGT (greater than) |
Mayor o igual que | BHS (higher or same) | BGE (greater or equal) |
Igual que | BEQ (equal) | |
Diferente de | BNE (not equal) | |
Menor o igual que | BLS (lower or same) | BLE (less or equal) |
Menor que | BLO (lower) | BLT (less than) |
El ejemplo anterior en que sumamos 7 al registro B si vale 10 o más, tiene mucho más significado si se programa así:
subb #10 ; vamos a comparar con 10 blo no_sumes ; saltar si es menor addb #'A-'9-1 ; sumarle 7 no_sumes: addb #10 ; sumar 10 para compensar los 10 que le quitamos
El acabado profesional de este fragmento de código nos lo da el usar la instrucción CMP. Esta instrucción realiza la resta al igual que hace SUB, activa los flags correspondientes como hace SUB, pero a diferencia de SUB, descarta el resultado y no modifica el registro, por lo que el código anterior, si se usa CMP, quedaría:
cmpb #10 ; vamos a comparar con 10 blo no_sumes ; saltar si es menor addb #'A-'9-1 ; sumarle 7 no_sumes: ... ; continuar con lo que siga
Después de la visita al cirujano plástico, nuestro programa que imprime un número en hexa por la pantalla nos ha quedado bastante rejuvenecido:
.area PROG (ABS) ; definimos una constante fin .equ 0xFF01 pantalla .equ 0xFF00 .org 0x100 .globl programa programa: lda #28 ; pongamos este nUmero como prueba ; imprimamos 0x ldb #'0 stb pantalla ldb #'x stb pantalla ; primero imprimamos la primera cifra hexadecimal tfr a,b lsrb lsrb lsrb lsrb ; en B estA la primera cifra, de 0 a 15 cmpb #10 blo no1 addb #'A-'9-1 no1: addb #'0 stb pantalla ; ahora imprimimos la segunda cifra hexadecimal tfr a,b lslb lslb lslb lslb lsrb lsrb lsrb lsrb ; en B estA la segunda cifra, de 0 a 15 cmpb #10 blo no2 addb #'A-'9-1 no2: addb #'0 stb pantalla ; imprimamos un salto de lInea al final ldb #'\n stb pantalla ; el programa acaba clra sta fin .org 0xFFFE ; vector de RESET .word programa
.area PROG (ABS) ; definimos una constante fin .equ 0xFF01 pantalla .equ 0xFF00 .org 0x100 .globl programa programa: lda #137 ; un nUmero como cualquier otro, para probar ; primera cifra ldb #'0 cmpa #100 blo Menor100 suba #100 incb cmpa #100 blo Menor200 incb suba #100 Menor100: Menor200: stb pantalla ; segunda cifra. En A quedan las dos Ultimas cifras clrb cmpa #80 blo Menor80 incb suba #80 Menor80:lslb cmpa #40 blo Menor40 incb suba #40 Menor40:lslb cmpa #20 blo Menor20 incb suba #20 Menor20:lslb cmpa #10 blo Menor10 incb suba #10 Menor10:addb #'0 stb pantalla adda #'0 sta pantalla ; imprimimos un salto de lInea ldb #'\n stb pantalla ; el programa acaba clra sta fin .org 0xFFFE ; vector de RESET .word programaExpliquémoslo. El algoritmo consiste en ir desgajando al número, reduciéndolo, mientras se van imprimiendo sus cifras:
Está claro que si queremos que el registro B contenga la segunda cifra, esa cifra será un número del 0 al 9 y ocupará por tanto, 4 bits: del 00002 al 10012. ¿Qué números tendrán el bit más significativo de B activo? Los 80s y los 90s. Luego el número con el que debo comparar primero es 80. Al igual que arriba, si el número es mayor o igual que 80, activamos el cuarto bit de B (10002) y le quitamos 80 al número. Si no, no hacemos nada. El número que queda es ahora menor que 80. ¿Qué números menores que 80 tienen el tercer bit de la segunda cifra activado? Los 40s, los 50s, los 60s y los 70s, es decir, los mayores o iguales que 40. El siguiente número para comparar es 40, y así se va prosiguiendo comparando con 20 y con 10.
Como ya os habréis dado cuenta, los bits de B no se activan directamente, sino que se van haciendo desplazamientos a la izquierda e incrementos con el mismo resultado final.; segunda cifra. En A quedan las dos Ultimas cifras clrb cmpa #80 blo Menor80 incb suba #80 Menor80:lslb cmpa #40 blo Menor40 incb suba #40 Menor40:lslb cmpa #20 blo Menor20 incb suba #20 Menor20:lslb cmpa #10 blo Menor10 incb suba #10 Menor10:addb #'0
En este fragmento de código vemos claramente un patrón que se repite. Es un muy buen candidato para construir un bucle. Para ello, seguimos los siguientes pasos
lslb cmpa X blo etiqueta incb suba X etiqueta:
tfr d,x ; guardamos D en X temporalmente lda #80 ; valor inicial: variable=80 sta variable ; variable estarA definida al principio del programa tfr x,d ; recuperamos D bucle: lslb cmpa variable blo Menor incb suba variable Menor: lsr variable ; variaciOn de la variable entre iteraciones tfr d,x ; condiciOn de permanencia en el bucle lda variable cmpa #10 tfr x,d bhs bucle
.area PROG (ABS) ; definimos una constante fin .equ 0xFF01 pantalla .equ 0xFF00 .org 0x100 .globl programa variable: .byte 0 programa: lda #137 ; primera cifra ldb #'0 cmpa #100 blo Menor100 suba #100 incb cmpa #100 blo Menor200 incb suba #100 Menor100: Menor200: stb pantalla ; segunda cifra. En A quedan las dos Ultimas cifras ldb #80 stb variable clrb bucle: lslb cmpa variable blo Menor incb suba variable Menor: tfr d,x lda variable lsra sta variable cmpa #10 tfr x,d bhs bucle addb #'0 stb pantalla adda #'0 sta pantalla ; imprimimos un salto de lInea ldb #'\n stb pantalla ; el programa acaba clra sta fin .org 0xFFFE ; vector de RESET .word programa
A esta técnica se le denomina enrollamiento de bucles. A veces, se hace el proceso inverso: cuando se quiere interpretar un código que no se entiende se prueba a ver si desenrollando el bucle se ve la luz
=
si el ordenador acertó,
<
si el número que piensa es menor que el
del ordenador y >
, si es mayormayor
y menor
mayor=1000
y
menor=99
mayor
y menor
.
En nuestro caso, 549mayor
para que valgan: mayor=549
y
menor=99
menor
para que valgan: mayor=1000
y
menor=549
Ejemplo de una ejecución:
549 < 324 > 436 < 380 < 352 < 338 < 331 > 334 < 332 > 333 =
NOP
JMP x
Bxx y
LBCC, LBCS, LBNE, LBEQ, LBVC, LBVS, LBPL, LBMI,
LBHI, LBHS, LBLS, LBLO,
LBGT, LBGE, LBLE, LBLT, LBRA, LBRN
CMPx
TSTx
TST
ls
cd
rm
man
cat
echo $?