Unidad 6 · Los códigos BCH · las raíces del localizador por prueba exhaustiva, encadenada por constantes: el procedimiento, el orden de las posiciones, el circuito, y lo que atrapa cada una de las dos comprobaciones de falla
Un decodificador de código BCH trabaja en tres etapas: calcula los síndromes, arma con ellos el polinomio localizador —por Peterson o por Berlekamp-Massey— y busca sus raíces. Esta nota es la tercera. No hay fórmula para las raíces de un polinomio de grado arbitrario sobre , pero el cuerpo es finito y se pueden probar todos los candidatos. R. T. Chien mostró en 1964 cómo hacer esa prueba con multiplicaciones por constantes.
Si el canal metió errores, con números localizadores , el localizador es
y sus raíces son los inversos . Un localizador es una potencia del elemento de orden del código, , y el exponente es la posición errada. Por eso alcanza con probar candidatos, las potencias con , y la traducción es directa:
y entonces la posición está errada, con tomado módulo : la raíz marca la posición 0. En un código primitivo y , que es el caso del resto de la nota hasta la sección 07.
El (15,5) del apunte, con tres errores en las posiciones 3, 5 y 12, da
Sustituyendo las quince potencias de , se anula en , y . Los localizadores son sus inversos, , y : las posiciones 12, 5 y 3.
Probar los candidatos uno por uno, con Horner, cuesta productos generales por punto, en total. Chien observó que cada evaluación sale de la anterior.
Paso 1: la evaluación, término por término. En ,
con para : el circuito se arma para errores y los coeficientes que sobran valen cero.
Paso 2: pasar a la posición siguiente es multiplicar por una constante. El término en es el de por :
Paso 3: guardar los términos. Se cargan en registros de bits. En cada paso se multiplica el registro por y se suma todo con la constante 1. Después de pasos el registro tiene , y la suma es . Si da cero, la posición está errada y se invierte.
La cuenta de productos no cambia: siguen siendo por posición. Lo que cambia es su tipo. Multiplicar por una constante conocida de antemano es una transformación lineal fija sobre los bits, o sea un puñado de XOR cableadas. En con , como ,
una sola XOR. Por son dos y por , tres: comprobado contra el producto del cuerpo en los dieciséis elementos. Un multiplicador general de tiene dos entradas variables y necesita compuertas AND además de las XOR.
La palabra recibida espera en un buffer mientras corren las tres etapas, y la búsqueda decide sobre cada bit en el momento en que sale. Así que importa qué posición examina cada paso.
El circuito multiplica antes de sumar. En el primer ciclo los registros pasan de a y la suma prueba , que corresponde a la posición . El ciclo prueba y la posición , y el último, , prueba y la posición 0. Las posiciones salen de mayor a menor, , que es el orden en que el buffer las entrega.
La regla para leer una traza: un error en la posición aparece en el paso . En el ejemplo de la sección 01, las posiciones 12, 5 y 3 dan sumas nulas en los pasos 3, 10 y 12. Y como la información de un código sistemático ocupa las posiciones de arriba, si sólo interesa corregir el mensaje alcanza con los primeros pasos.
Son registros de bits, cada uno realimentado a través de su multiplicador por ; un árbol de sumadores que junta los registros con la constante 1; y una compuerta NOR de entradas que da 1 cuando la suma es cero. Esa salida va a una XOR con el bit que sale del buffer: es la corrección. Un bit de decisión por ciclo, ciclos por palabra.
En el (15,7), que corrige dos errores, el circuito tiene dos registros de cuatro bits, uno realimentado por y el otro por : tres XOR entre los dos multiplicadores. En el (15,5) se suma un tercero, por . El costo por palabra es productos por constante: 45 en el (15,5), 510 en el (255,239) del apunte, que corrige dos.
Abajo, el circuito del (15,5) y el del (15,7). Elegís dónde caen los errores, y sale de los síndromes por el algoritmo iterativo binario. Arranca con los tres errores de la sección 01, sin ningún paso dado. Antes de avanzar, apostá en qué paso se anula la suma por primera vez.
Después de ver dónde caen las tres, mové un error y fijate cuánto se corre su paso. Sacá uno: el registro de queda en cero. Ponele un cuarto error al (15,5) y mirá qué pasa al final. Y en el (15,7) probá con errores en 0, 5 y 10.
El caso de la sección 01, paso por paso. Las columnas son los tres registros después de multiplicar, y la suma incluye la constante 1:
| l | posición | σ₁αˡ | σ₂α²ˡ | σ₃α³ˡ | suma |
|---|---|---|---|---|---|
| 1 | 14 | α | 0 | α⁸ | α⁵ |
| 2 | 13 | α² | 0 | α¹¹ | α⁷ |
| 3 | 12 | α³ | 0 | α¹⁴ | 0 |
| 4 | 11 | α⁴ | 0 | α² | α⁵ |
| 5 | 10 | α⁵ | 0 | α⁵ | 1 |
| 6 | 9 | α⁶ | 0 | α⁸ | α³ |
| 7 | 8 | α⁷ | 0 | α¹¹ | α² |
| 8 | 7 | α⁸ | 0 | α¹⁴ | α¹³ |
| 9 | 6 | α⁹ | 0 | α² | α¹² |
| 10 | 5 | α¹⁰ | 0 | α⁵ | 0 |
| 11 | 4 | α¹¹ | 0 | α⁸ | α⁹ |
| 12 | 3 | α¹² | 0 | α¹¹ | 0 |
| 13 | 2 | α¹³ | 0 | α¹⁴ | α⁸ |
| 14 | 1 | α¹⁴ | 0 | α² | α⁶ |
| 15 | 0 | 1 | 0 | α⁵ | α⁵ |
Tres cosas se leen sin hacer cuentas. El primer registro recorre las potencias de de a una, porque . El segundo no se mueve de cero: , y cero por cualquier constante es cero; es lo mismo que pasa con los registros que sobran cuando hay menos de errores. El tercero avanza de a tres y vuelve a empezar cada cinco pasos, porque tiene orden 5. Y la suma se anula en los pasos 3, 10 y 12, que son las posiciones 12, 5 y 3, como manda .
El ejemplo integral del apunte, con errores en 2, 7 y 13, da , y las sumas nulas caen en los pasos 2, 8 y 13. Está resuelto entero, de la palabra a la corrección, en el (15,5) de punta a punta.
Chien no decide nada: marca las posiciones donde la suma da cero. Antes de invertirlas, el decodificador comprueba dos cosas:
El porqué está en la definición. Un patrón de errores tiene un localizador de grado que se parte en factores lineales distintos, con todas sus raíces entre las potencias de . Un que no cumple eso no es el localizador de ningún patrón corregible, y el decodificador declara la falla: sabe que el bloque está dañado aunque no pueda repararlo. Y al revés: si pasa las dos, lo que sale es una palabra de código a distancia a lo sumo de lo recibido. Recorridos todos los patrones de y de errores en los cuatro códigos de la tabla de abajo y en el (21,12) de la sección 07, ninguno pasa las dos sin salir así.
Con un error más que se ve qué atrapa cada una. Enumerados todos los patrones con el decodificador completo —síndromes, algoritmo iterativo binario y Chien—, y mirando primero el grado:
| código | errores | patrones | grado > t | faltan raíces | pasan las dos |
|---|---|---|---|---|---|
| (15,7) | 3 | 455 | 35 | 240 | 180 |
| (15,5) | 4 | 1365 | 90 | 750 | 525 |
| (31,21) | 3 | 4495 | 155 | 2480 | 1860 |
| (31,16) | 4 | 31 465 | 930 | 25 110 | 5425 |
La que trabaja es la segunda. En el (15,5), de los 840 fallos, 750 los delata Chien y 90 el grado. Y en los dos códigos de largo 15 y en el (31,21), cuando faltan raíces no falta una: no hay ninguna. El localizador sale de grado y no se anula en ninguna de las posiciones, así que la búsqueda recorre la palabra entera sin marcar nada. En el (31,16) hay de los dos: de los 25 110, en 14 880 aparece una raíz sola. Los que pasan las dos se entregan como otra palabra sin aviso; cuántos son, y por qué, es el tema de pasado t, la cuenta la hacen las palabras livianas.
La primera comprobación tiene un costado menos obvio. Un de grado mayor que a veces tiene todas sus raíces. En el (15,7), con errores en 0, 5 y 10, el algoritmo entrega
que es el localizador verdadero: Chien encuentra las tres posiciones justas, y el decodificador igual declara la falla porque el grado es 3 y . Pasa en 5 de los 35 patrones de grado alto del (15,7), y en los 5 las raíces son los errores. ¿Por qué no aceptarlas? Porque en el (15,5) la misma situación sale mitad y mitad: de los 90 de grado 4, en 30 Chien encuentra cuatro raíces; en 15 son los cuatro errores y en los otros 15 son cuatro posiciones distintas, que junto con los errores completan una palabra de peso 8. Sin la primera comprobación, esos 15 serían otra palabra entregada sin aviso. El decodificador que corrige hasta y nada más —el de decodificación incompleta— renuncia a los 15 buenos para no entregar los 15 malos.
Los ciclos. El cálculo del síndrome y la búsqueda recorren la palabra a un bit por ciclo; el algoritmo iterativo binario tarda . Con , la búsqueda es el cuello de botella. Para examinar posiciones por ciclo se replica el circuito veces, cada réplica con las constantes de su posición, y la búsqueda se completa en ciclos a cambio de multiplicadores por constante. Con hay otra salida: las raíces de un polinomio de grado dos o tres se obtienen en forma directa, con un cambio de variable y una tabla, en pocos ciclos. Las dos están en el decodificador BCH en hardware.
Los códigos no primitivos. Cuando es un divisor propio de , la búsqueda prueba sólo las potencias de , no los elementos del cuerpo. En el (21,12) del apunte, sobre con , recorridos los 1330 patrones de tres errores: en 1092 faltan raíces, y en 420 de esos sí se anula en algún elemento del cuerpo, pero fuera de las 21 potencias de . Que Chien no las vea es lo correcto: una raíz así no corresponde a ninguna posición, y la segunda comprobación declara la falla igual.
El circuito con enteros de cuatro bits como registros, sobre con . Multiplica antes de probar, así que las posiciones salen de mayor a menor. Lo de los comentarios es la salida de correrlo.
# GF(2^4) con 1 + X + X^4: EXP[e] = α^e como entero de 4 bits, LOG al revés.
EXP, LOG = [1], {1: 0}
for e in range(1, 15):
x = EXP[-1] << 1
if x & 0b10000:
x ^= 0b10011
EXP.append(x)
LOG[x] = e
def mul(a, b):
return 0 if a == 0 or b == 0 else EXP[(LOG[a] + LOG[b]) % 15]
def alfa(x):
return '0' if x == 0 else '1' if LOG[x] == 0 else 'α' if LOG[x] == 1 else f'α^{LOG[x]}'
def chien(sigma, n=15, traza=False):
"""sigma = [1, σ1, ..., σt]. En el paso l se multiplica el registro i
por α^i y después se prueba 1 + Σ registros = 0: la raíz es α^l y la
posición errada, n − l."""
reg = sigma[1:]
const = [EXP[i] for i in range(1, len(reg) + 1)]
pos = []
for l in range(1, n + 1):
reg = [mul(r, c) for r, c in zip(reg, const)]
suma = 1
for r in reg:
suma ^= r
if suma == 0:
pos.append((n - l) % n)
if traza:
print(l, ' '.join(alfa(r) for r in reg), alfa(suma), '<- r_%d' % ((n - l) % n) if suma == 0 else '')
return pos
a = lambda e: EXP[e % 15]
print(chien([1, 1, 0, a(5)], traza=True)) # σ(X) = 1 + X + α^5 X^3
# 1 α 0 α^8 α^5
# 2 α^2 0 α^11 α^7
# 3 α^3 0 α^14 0 <- r_12
# ...
# 10 α^10 0 α^5 0 <- r_5
# ...
# 12 α^12 0 α^11 0 <- r_3
# ...
# 15 1 0 α^5 α^5
# [12, 5, 3]
print(chien([1, a(1), a(13), a(7)])) # el ejemplo integral
# [13, 7, 2]
print(chien([1, 0, 0, 1])) # σ(X) = 1 + X^3, grado 3
# [10, 5, 0]
print(chien([1, a(7), a(5)])) # grado 2, sin raíces
# []
Las filas omitidas son las de la tabla de la sección 05. La función devuelve las posiciones y nada más: las dos comprobaciones las hace quien la llama. El tercer caso es el de la sección 06, tres raíces justas que el decodificador del (15,7) rechaza por el grado; el cuarto, un de grado 2 que no se anula en ningún elemento de : es lo que el (15,7) obtiene con errores en 0, 1 y 3.