El Little Man Computer ( LMC ) es un modelo educativo de computadora creado por el Dr. Stuart Madnick en 1965. [1] El LMC se utiliza generalmente para enseñar a los estudiantes, porque modela una computadora de arquitectura von Neumann simple , que tiene todas las características básicas de una computadora moderna. Puede programarse en código de máquina (aunque en decimal en lugar de binario) o en código ensamblador. [2] [3] [4]
El modelo LMC se basa en el concepto de un hombrecito encerrado en una sala de correo cerrada (análogo a una computadora en este escenario). En un extremo de la sala, hay 100 buzones ( memoria ), numerados del 0 al 99, que pueden contener cada uno una instrucción o datos de 3 dígitos (que van del 000 al 999). Además, hay dos buzones en el otro extremo etiquetados como INBOX y OUTBOX que se utilizan para recibir y emitir datos. En el centro de la sala, hay un área de trabajo que contiene una calculadora simple de dos funciones (suma y resta) conocida como el acumulador y un contador reiniciable conocido como el contador de programa. El contador de programa contiene la dirección de la siguiente instrucción que llevará a cabo el hombrecito. Este contador de programa normalmente se incrementa en 1 después de que se ejecuta cada instrucción, lo que permite al hombrecito trabajar en un programa de forma secuencial. Las instrucciones de bifurcación permiten incorporar iteraciones (bucles) y estructuras de programación condicional en un programa. Esto último se logra configurando el contador de programa en una dirección de memoria no secuencial si se cumple una condición particular (normalmente, el valor almacenado en el acumulador es cero o positivo).
Como se especifica en la arquitectura de von Neumann , cualquier buzón (que significa una ubicación de memoria única) puede contener una instrucción o datos. Por lo tanto, se debe tener cuidado de evitar que el contador de programa alcance una dirección de memoria que contenga datos, o el Little Man intentará tratarlo como una instrucción. Se puede aprovechar esto escribiendo instrucciones en los buzones que están destinadas a ser interpretadas como código, para crear código que se automodifique. Para utilizar el LMC, el usuario carga datos en los buzones y luego le indica al Little Man que comience la ejecución, comenzando con la instrucción almacenada en la dirección de memoria cero. Restablecer el contador de programa a cero reinicia efectivamente el programa, aunque en un estado potencialmente diferente.
Para ejecutar un programa, el hombrecito realiza estos pasos:
Si bien el LMC refleja el funcionamiento real de los procesadores binarios , se eligió la simplicidad de los números decimales para minimizar la complejidad para los estudiantes que pueden no sentirse cómodos trabajando en binario/ hexadecimal .
Algunos simuladores LMC se programan directamente utilizando instrucciones numéricas de 3 dígitos y otros utilizan códigos mnemotécnicos y etiquetas de 3 letras. En ambos casos, el conjunto de instrucciones es deliberadamente muy limitado (normalmente unas diez instrucciones) para simplificar la comprensión. Si el LMC utiliza códigos mnemotécnicos y etiquetas, estos se convierten en instrucciones numéricas de 3 dígitos cuando se ensambla el programa.
La siguiente tabla muestra un conjunto de instrucciones numéricas típico y los códigos mnemotécnicos equivalentes.
Este programa (instrucción 901 a instrucción 000 ) está escrito utilizando únicamente códigos numéricos. El programa toma dos números como entrada y genera la diferencia. Observe que la ejecución comienza en el buzón 00 y finaliza en el buzón 07. A continuación se describen las desventajas de programar la LMC utilizando códigos de instrucciones numéricas.
El lenguaje ensamblador es un lenguaje de programación de bajo nivel que utiliza mnemónicos y etiquetas en lugar de códigos numéricos de instrucciones. Aunque el LMC solo utiliza un conjunto limitado de mnemónicos, la conveniencia de utilizar un mnemónico para cada instrucción se hace evidente en el lenguaje ensamblador del mismo programa que se muestra a continuación: el programador ya no necesita memorizar un conjunto de códigos numéricos anónimos y ahora puede programar con un conjunto de códigos mnemónicos más fáciles de recordar. Si el mnemónico es una instrucción que implica una dirección de memoria ( ya sea una instrucción de bifurcación o de carga/guardado de datos ), se utiliza una etiqueta para nombrar la dirección de memoria.
InfanciaSTA PRIMEROInfanciaSTA SEGUNDALDA PRIMEROSUB SEGUNDOAFUERAAlto NivelPRIMERA FECHASEGUNDA FECHA
Sin etiquetas, el programador debe calcular manualmente las direcciones de los buzones ( memoria ). En el ejemplo del código numérico , si se insertara una nueva instrucción antes de la instrucción HLT final, esa instrucción HLT se movería de la dirección 07 a la dirección 08 (el etiquetado de direcciones comienza en la ubicación de dirección 00). Supongamos que el usuario ingresó 600 como la primera entrada. La instrucción 308 significaría que este valor se almacenaría en la ubicación de dirección 08 y sobrescribiría la instrucción 000 (HLT). Dado que 600 significa "ramificar a la dirección de buzón 00", el programa, en lugar de detenerse, se quedaría atascado en un bucle sin fin.
Para evitar esta dificultad, la mayoría de los lenguajes ensambladores ( incluido el LMC ) combinan los mnemónicos con etiquetas . Una etiqueta es simplemente una palabra que se utiliza para nombrar una dirección de memoria donde se almacena una instrucción o datos, o para hacer referencia a esa dirección en una instrucción.
Cuando se ensambla un programa:
En el ejemplo de lenguaje ensamblador que utiliza mnemotecnia y etiquetas, si se insertaba una nueva instrucción antes de la instrucción HLT final, la ubicación de la dirección etiquetada FIRST ahora estaría en la ubicación de memoria 09 en lugar de 08 y la instrucción STA FIRST se convertiría en 309 (STA 09) en lugar de 308 (STA 08) cuando se ensamblara el programa.
Por tanto, las etiquetas se utilizan para:
El programa a continuación tomará la entrada del usuario y realizará una cuenta regresiva hasta cero.
Infancia OUT // Inicializar salidaLOOP BRZ QUIT // Etiqueta esta dirección de memoria como LOOP. Si el valor del acumulador es 0, salta a la dirección de memoria etiquetada // ABANDONAR SUB UNO // Resta el valor almacenado en la dirección UNO del acumulador AFUERA BRA LOOP // Salta (incondicionalmente) a la dirección de memoria etiquetada LOOPQUIT HLT // Etiqueta esta dirección de memoria como QUITONE DAT 1 // Almacena el valor 1 en esta dirección de memoria y etiquétalo como UNO (declaración de variable)
El programa que se muestra a continuación tomará una entrada del usuario, la elevará al cuadrado, mostrará la respuesta y luego repetirá la operación. Si se ingresa un cero, el programa finalizará.
( Nota: una entrada que dé como resultado un valor mayor a 999 tendrá un comportamiento indefinido debido al límite de 3 dígitos del LMC ).
INICIO LDA CERO // Inicializar para ejecución de múltiples programas RESULTADO STA CONTEO DE STA INP // Entrada proporcionada por el usuario BRZ END // Pasa al programa END si la entrada = 0 VALOR STA // Almacena la entrada como VALORLOOP LDA RESULT // Cargar el RESULTADO AGREGAR VALOR // Agrega VALOR, la entrada proporcionada por el usuario, al RESULTADO STA RESULT // Almacena el nuevo RESULTADO LDA COUNT // Cargar el COUNT AGREGAR UNO // Agregue UNO al CONTEO STA COUNT // Almacena el nuevo COUNT SUB VALOR // Resta el VALOR ingresado por el usuario de COUNT BRZ ENDLOOP // Si es cero (VALUE se ha sumado a RESULTADO VALUE veces), pasa a ENDLOOP BRA LOOP // Pasa a LOOP para continuar agregando VALOR al RESULTADORESULTADO LDA DE ENDLOOP // Cargar RESULTADO OUT // Salida RESULTADO BRA START // Pasa al INICIO para inicializar y obtener otro VALOR de entradaEND HLT // HALT - ¡se ingresó un cero, así que listo!RESULTADO DAT // Resultado calculado (predeterminado 0)COUNT DAT // Contador (predeterminado 0)ONE DAT 1 // Constante, valor 1VALOR DAT // Entrada proporcionada por el usuario, el valor que se elevará al cuadrado (predeterminado 0)ZERO DAT // Constante, valor 0 (predeterminado 0)
Nota: Si no hay datos después de una declaración DAT, el valor predeterminado 0 se almacena en la dirección de memoria.
En el ejemplo anterior, [BRZ ENDLOOP] depende de un comportamiento indefinido, ya que COUNT-VALUE puede ser negativo, después de lo cual el valor de ACCUMULATOR no está definido, lo que hace que BRZ se ramifique o no (ACCUMULATOR puede ser cero o estar en un bucle). Para que el código sea compatible con la especificación, reemplace:
... LDA COUNT // Cargar el COUNT AGREGAR UNO // Agregue UNO al CONTEO STA COUNT // Almacena el nuevo COUNT SUB VALOR // Resta el VALOR ingresado por el usuario de COUNT BRZ ENDLOOP // Si es cero (VALUE se ha sumado a RESULTADO VALUE veces), pasa a ENDLOOP ...
con la siguiente versión, que evalúa VALUE-COUNT en lugar de COUNT-VALUE, asegurándose de que el acumulador nunca se desborde:
... LDA COUNT // Cargar el COUNT AGREGAR UNO // Agregue UNO al CONTEO STA COUNT // Almacena el nuevo COUNT VALOR LDA // Cargar el VALOR SUB COUNT // Restar COUNT del VALOR de entrada proporcionado por el usuario BRZ ENDLOOP // Si es cero (VALUE se ha sumado a RESULTADO VALUE veces), pasa a ENDLOOP ...
Otro ejemplo es un quine , que imprime su propio código de máquina (la impresión del código fuente es imposible porque no se pueden imprimir letras):
LOAD LDA 0 // Carga la posición 0 en el acumulador. Esta línea se modificará en cada bucle para cargar las siguientes líneas en el acumulador. OUT // Muestra el valor del acumulador. El valor del acumulador será la línea que se acaba de cargar SUB ONE // Resta 1 del valor del acumulador. Esto es para que podamos hacer el BRZ en el siguiente paso para ver si estamos en la última línea del programa. BRZ UNO // Si la resta anterior ha dejado el acumulador a 0 (lo que significa que teníamos el valor 001 en el acumulador), entonces pasamos a la posición UNO LDA LOAD // Carga la posición LOAD en el acumulador, esto es en preparación para incrementar los dígitos de dirección para esta posición ADD ONE // Incrementa los dígitos de posición para la línea LOAD. El valor que se encuentra actualmente en el acumulador, si se lee como una instrucción, cargará la siguiente línea en el acumulador, en comparación con la última línea cargada STA LOAD // Almacena la línea LOAD recién incrementada nuevamente en la posición LOAD CARGA DEL SUJETADOR // Regresar al inicio del bucleONE DAT 1 // La variable ONE. Si se lee como instrucción, se interpretará como HLT/COB y finalizará el programa.
Esta quine funciona utilizando código automodificable . La posición 0 se incrementa en uno en cada iteración, generando el código de esa línea, hasta que el código que genera es 1, momento en el que se ramifica a la posición UNO. El valor en la posición UNO tiene 0 como código de operación, por lo que se interpreta como una instrucción HALT/COB.