2025-06-12
6~ minutes
Esto es un intento de “apuntes”. Es un frankestein de fragmentos sacados del libro Tanenbaum - Organización del Computador, un enfoque estructurado y Linda Null - Essentials of Computer Organization and Architecture, siguiendo como guía los PDF de las clases.
Los canales de comunicación de datos son más propensos a errores y son más tolerantes a errores. En comunicación de datos, es suficiente con detectar errores. Si una comunicación se detecto que hubo un error, solo se debe pedir la retransmisión de datos. Dispositivos de almacenamiento y la memoria no tienen este lujo. Si un disco puede ser el único acceso a datos sensibles que se requieren en tiempo real. Los dispositivos de almacenamiento y la memoria entonces deben tener forma de detectar y corregir los errores.
El código Hamming es una adaptación del concepto de paridad, donde la detección y corrección de errores dependen de la proporción de bits de paridad añadidos a la información. Hamming es utilizado cuando se espera que ocurran errores aleatorios.
Hamming utiliza bits de paridad, también llamados bits de verificación o bits redundantes. La palabra consiste de m bits, y se añaden r bits redundantes para la detección y corrección de errores. Entonces, la palabra final, llamada palabra código, es una palabra de n bits que contiene m bits más r bits de verificación.
La cantidad de bits en la que difieren dos palabras de código se llama distancia de Hamming. Por ejemplo, si tenemos las siguientes dos palabras de código:
1 0 0 0 1 0 0 1
1 0 1 1 0 0 0 1
* * *
vemos que hay 3 bits que difieren (marcados con *), por lo que la distancia de Hamming es de 3.
El algoritmo de Hamming provee una forma sencilla de construir códigos para corregir errores de un solo bit. Para construir códigos de palabras, se siguen estos pasos:
Queremos obtener el código Hamming del siguiente dato: 0110101
Podemos armar una tabla de la siguiente forma:
Posición | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|
0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | |
Label | P1 | P2 | D1 | P3 | D2 | D3 | D4 | P4 | D5 | D6 | D7 |
Dato | 0 | 1 | 1 | 0 | 1 | 0 | 1 | ||||
1 | 0 | 1 | 0 | 1 | 1 | ||||||
0 | 0 | 1 | 0 | 0 | 1 | ||||||
0 | 1 | 1 | 0 | ||||||||
0 | 1 | 0 | 1 | ||||||||
Código | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 |
Cada bit de paridad se verifica con los bits de datos a su derecha. Si el valor existe, se “baja” el dato. Al finalizar la fila, se cuentan los 1 y se añade al bit de paridad 1 o 0, para cumplir la paridad de la fila.
Por lo que la palabra código de 0110101 es 10001100101.
Queremos comprobar si hubo un error. Si la palabra codificada es correcta, la paridad de cada grupo de bits incluyendo el bit de paridad tiene que ser par (0).
Supongamos que el código anterior debido a un error, el tercer bit de la derecha cambia de 1 a 0. Por lo que nuestra palabra codificada es la siguiente:
10001100101 -> 10001100001
Podemos armar la tabla nuevamente y comprobar si hubo errores:
Posición | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 | 1000 | 1001 | 1010 | 1011 | ||
Label | P1 | P2 | D1 | P3 | D2 | D3 | D4 | P4 | D5 | D6 | D7 | |
Código | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | |
1 | 0 | 1 | 0 | 0 | 1 | 1 | ||||||
0 | 0 | 1 | 0 | 0 | 1 | 0 | ||||||
0 | 1 | 1 | 0 | 0 | 0 | |||||||
0 | 0 | 0 | 1 | 1 |
Leyendo los bits de la columna extra, de abajo para arriba, obtenemos el número 1001, que corresponde a la posición 9, donde estaba el error. Por lo tanto, podemos corregirlo.
Un lenguaje ensamblador es un lenguaje en el que cada enunciado produce exactamente una instrucción de máquina. Un programa de n líneas escrito en ensamblador producirá un programa de n líneas en lenguaje de máquina.
La razón de utilizar este lenguaje en vez de lenguaje de máquina (en hexadecimal) es que es mucho más fácil programar utilizando nombres simbólicos que números. Lo mismo aplica para las direcciones. Se pueden asignar nombres simbólicos a las direcciones de memoria y el ensamblador se encarga de traducirlo correctamente.
Una característica que distingue al lenguaje ensamblador a los lenguajes de alto nivel, es que se tiene acceso a todas las características e instrucciones disponibles en la máquina objetivo. Por lo que todo lo que puede hacerse en lenguaje de máquina se puede hacer en lenguaje ensamblador, a diferencia de un lenguaje de alto nivel, por ejemplo Java.
Otra diferencia es que el lenguaje ensamblador solo puede ejecutarse en una familia de máquinas, mientras que un programa escrito en un lenguaje de alto nivel puede ser multiplataforma.
Para poder convertir el lenguaje ensamblador (assembly) a lenguaje de máquina se utilizan programas llamados traductores. Un traductor se encarga de tomar el programa escrito en el lenguaje fuente y traducirlo al lenguaje objetivo.
Se utiliza traducción cuando se cuenta con un procesador para el lenguaje objetivo, pero no para el lenguaje fuente. Si se cuenta con un procesador capaz de ejecutar programas en el lenguaje fuente, no habría necesidad de traducir el programa.
Los traductores se dividen en 2 grupos. Si el lenguaje fuente es una representación simbólica del lenguaje de máquina, entonces el traductor se llama ensamblador (assembler). Si el lenguaje fuente es uno de alto nivel, como C o Java, y el lenguaje objetivo es el lenguaje de máquina, el traductor se llama compilador.
El trabajo, entonces, del assembler es tomar un programa en lenguaje ensamblador, que es una representación simbólica de números binarios, y convertirlo en instrucciones binarias que la máquina pueda entender. El assembler lee el archivo fuente (el programa ensamblador) y produce un archivo objeto (el código máquina).