Consignes

Le rendu de TP se fera uniquement à travers l'interface web TPLab. Vous devrez fournir un fichier .c contenant le code des fonctions demandées et éventuellement des fichiers annexes pour la question 12.

Vous pouvez utiliser le fichier Makefile fournit pour faciliter la compilation.

Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :

Le seul fait que votre programme fonctionne ne suffit pas pour avoir une bonne note. Les points suivants seront pris en compte :

  1. l'architecture de votre programme (code découpé en fonctions etc.),

  2. la lisibilité de votre programme (choix pertinent pour les noms de variables etc.),

  3. la présence de commentaires aux endroits appropriés,

  4. la présence de documentation pour vos fonctions.

Liens utiles

Suite aux demandes répétées de nombreux étudiants, voici un petit tutoriel sur les fichiers Makefile (lien local : Makefile).

Je vous conseille de lire le fichier et de suivre les étapes décrites à l'intérieur. (À un autre moment que pendant le TP !)

1. Préliminaires

Le but de ce TP est de comprendre le fonctionnement du code de Golay et d'en implémenter une version en C.

Utilisation du code

  1. Le code fourni contient une fonction main dans un fichier main.c que vous ne devez pas modifier. Cette fonction permet de lancer le programme compilé depuis la ligne de commande.

  2. Votre code devra être écrit dans le fichier harry-potter.c. (Sauf les tests, qui seront dans tests-harry-potter.c.)

  3. Une des options disponibles est -T qui permet d'exécuter la fonction perform_tests du fichier test-harry-potter.c. La fonction fournie gère un ou deux tests. À vous de l'étendre de manière pertinente.

  4. Le code définit une fonction DEBUG qui s'utilise comme la fonction printf. Elle a simplement un premier argument supplémentaire qui permet de contrôler l'affichage :

    • si cet argument est à 0, le message est toujours affiché,

    • si cet argument est à 1, le message est seulement affiché si le programme est lancé avec l'option -v,

    • si cet argument est à 2, le message est seulement affiché si le programme est lancé avec l'option -vv,

    • etc.

  5. L'exécutable propose d'autres options qui permettent de tester directement certaines fonctions (code_golay, code_golay_etendu, correction_golay+decode_golay).

Voila un exemple d'exécution :

$ make clean
rm -f *.o a.out
$ make
gcc -Wall -std=c99 -Wextra -pedantic -Werror -Wno-unused-parameter -Wno-unused-function -c -o main.o main.c
gcc -Wall -std=c99 -Wextra -pedantic -Werror -Wno-unused-parameter -Wno-unused-function -c -o correction.o correction.c
gcc -Wall -std=c99 -Wextra -pedantic -Werror -Wno-unused-parameter -Wno-unused-function -c -o tests-correction.o tests-correction.c
gcc -Wall -std=c99 -Wextra -pedantic -Werror -Wno-unused-parameter -Wno-unused-function main.o correction.o tests-correction.o -o golay
$ 
$ ./golay -h
Utilisation :
  ./golay options
actions sur un mot :
    -c MOT  code le mot de 12 bits sur 23 bits
    -C MOT  code le mot de 12 bits sur 24 bits
    -s MOT  calcule le syndrome du mot 23 bits
    -d MOT  décode (avec correction d'erreurs) le mot de 23 bits sur 12 bits
    -D MOT  décode (avec correction d'erreurs) le mot de 24 bits sur 12 bits
    -e MOT  décode (avec correction d'erreurs exhaustive) le mot de 23 bits sur 12 bits
    -E MOT  décode (avec correction d'erreurs exhaustive) le mot de 24 bits sur 12 bits

tests aléatoires :
    -n NB   nombre de mots à tester
    -p X    probabilité d'erreur sur chaque bit

divers :
    -x      utilise la base hexadécimal plutôt que la base 2
    -v      augmente le niveau des affichages de débogage
    -T ARG  lance la fonction de test
    -h      ce message
$ 
$ ./golay -T binaire 12648430
en binaire, le mot 0x00c0ffee s'écrit 0b00000000110000001111111111101110
$ 

1.1. Opérations bit à bit

Nous utiliserons le type uint32_t (entiers non signés, sur 4 octets, défini dans stdint.h) pour représenter en interne les suites de 12 bits et 24 bits :

typedef uint32_t mot ;

(Cette définition se trouve déjà dans le fichier tp1.h.)

Par exemple, la suite de 12 bits 110100001011 sera en fait représentée par 00000000000000000000110100001011, c'est à dire 0xd0b (ou 3339 en décimal).

La norme du C ne spécifie pas la taille des entiers. Les int (ou unsigned) peuvent être stockés sur 2 octets (16 bits). Les long ou unsigned long sont toujours stockés sur 4 octets (32 bits) ou plus.

En pratique, les compilateurs modernes utilisent

Le type uint32_t est apparu dans la norme C99 et représente les entiers non-signés sur 32 bits. On pourrait également utiliser les types uint_least32_t ou uint_fast32_t qui ont au moins 32 bits.

Il est possible de manipuler directement les bits des entiers du langage C grâce aux opérations bit-à-bit.

Les opérations sont le « et », le « ou» et le « ou-exclusif » :

Appliqués à des entiers (ou des char), les opérateurs &, |, ^ et ~ agissent sur chaque bit indépendamment.

attention, les opérateurs && et || ne sont pas des opérateurs bit à bit !

Deux autres opérations sont disponibles :

Sur les entiers signés, les décalages agissent exactement comme des multiplications / divisions par des puissances de deux. Cela signifie que le bit de poids fort (le bit de signe) est conservé par ces opérations et que le décalage ne concerne que les autres bits. On parle de décalage arithmétique par opposition au décalage logique des entiers non signés.

Téléchargez l'archive contenant les fichiers du TP.

Définissez la fonction

int bit(int i, mot m);

qui donne le bit (0 ou 1) numéro i du mot m. Le bit de poids faible aura le numéro 0 et le bit de poids fort aura le numéro 31. Servez-vous de cette fonction pour écrire une fonction

void print_bin(mot u, int n) ;

qui affichera les n bit de poids faible d'un mot sans ajouter de saut de ligne en fin d'affichage. Par exemple:

$ ./golay -T binaire 12648430
en binaire, le mot 0x00c0ffee s'écrit 0b00000000110000001111111111101110

1.2. Poids d'un mot

Écrivez la fonction

int poids(mot m);

qui renvoie le nombre de 1 dans un mots.

N'oubliez pas de tester votre fonction !

La lecture suivantes est réservée aux hackers qui se demandent comment minimiser le nombre d'opérations de la fonction poids : https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation.

Aucun point supplémentaire ne sera donné si vous choisissez une des versions de Wikipedia, mais vous risquez de perdre un point si vous n'êtes pas capable d'expliquer le fonctionnement de votre code !

2. Le code de Golay

2.1. Codage

Une des matrices génératrices du code de Golay est [ I | A ]I est la matrice identité sur 12 lignes / 12 colonnes et A est la matrice sur 12 lignes / 11 colonnes suivante :

1 0 1 0 1 1 1 0 0 0 1
1 1 1 1 1 0 0 1 0 0 1
1 1 0 1 0 0 1 0 1 0 1
1 1 0 0 0 1 1 1 0 1 1
1 1 0 0 1 1 0 1 1 0 0
0 1 1 0 0 1 1 0 1 1 0
0 0 1 1 0 0 1 1 0 1 1
1 0 1 1 0 1 1 1 1 0 0
0 1 0 1 1 0 1 1 1 1 0
0 0 1 0 1 1 0 1 1 1 1
1 0 1 1 1 0 0 0 1 1 0
0 1 0 1 1 1 0 0 0 1 1

On code donc un mot de 12 bits en un mot de 23 (12+11) bits. Comme la partie gauche de la matrice est la matrice identité, cela signifie que le codage de w commence par w lui même et est suivi de 11 bits de redondance. Le décodage est trivial: il s'agit d'un décalage à droite.

Cette matrice signifie que :

Les matrices habituellement données pour le code de Golay sont différentes et plus "symétriques". La matrice donnée ici correspond à une présentation du code de Golay comme code cyclique engendré par le polynôme X11 + X9 + X7 + X6 + X5 + X + 1 dans F2[X] / (X23-1).

Comme les mots de bits sont représentée par des éléments du type mot (synonyme du type uint32_t) la matrice A doit être représentée par un tableau d'éléments du type mot:

mot golay_A[12] = { 0x571, ... };
  1. Complétez la définition des dernières lignes de cette matrice (variable globale) par les constantes appropriées.

  2. Vous pouvez maintenant écrire la fonction de codage, qui multiplie un mot par la matrice définie précédemment:

    /*
     * code une suite de 12 bits (bits de poids faible d'un mot) en ajoutant
     * la redondance donnée par la matrice A
     * Le résultat (23 bits) est stocké dans les 23 bits de poids faible du
     * mot renvoyé.
     */
    mot code_golay(mot entree) ;
    

    Privilégiez les opérations bit à bit dans votre fonction...

Attention : c'est le bit de poids fort qui correspond à la ligne 0 de la matrice, et le bit de poids faible qui correspond à la ligne 11 de la matrice...

Note: par rapport à ce qui a été vu en cours, seuls les bits de redondance sont obtenus par multiplication de matrice. Il faudra donc coller ces bits de redondance derrière le mot initial pour obtenir le codage d'un mot.

Vous pouvez tester la fonction code_golay depuis la ligne de commande :

$ ./golay -c 110100111
code_golay: 000110100111  =>  00011010011111110000110

Écrivez la fonction

/*
 * décode un mot du code de golay un supprimant les bits de redondance
 */
mot decode_golay(mot m) ;

Cette fonction est très simple car il suffit de supprimer les bits de redondance avec un décalage !

Le code de Golay étendu code une suite de 12 bits en une suite de 24 bits : il s'agit du code de Golay avec un bit de parité supplémentaire ajouté à droite.

Écrivez la fonction correspondante :

/*
 * code de Golay étendu: 23 bit du code de Golay, avec un bit de parité
 */
mot code_golay_etendu(mot entree) ;

Vous pouvez tester la fonction code_golay depuis la ligne de commande :

$ ./golay -C 110100110
code_golay_etendu: 000110100110  =>  000110100110101011001011

2.2. Vérification

Pour vérifier si un mot de 23 bits est correct, il faut multiplier ce mot par une matrice de parité. La matrice de parité correspondant à la matrice génératrice utilisé est obtenue en collant une matrice identité 11 lignes / 11 colonnes sous la matrice A précédente :

1 0 1 0 1 1 1 0 0 0 1
1 1 1 1 1 0 0 1 0 0 1
1 1 0 1 0 0 1 0 1 0 1
1 1 0 0 0 1 1 1 0 1 1
1 1 0 0 1 1 0 1 1 0 0
0 1 1 0 0 1 1 0 1 1 0
0 0 1 1 0 0 1 1 0 1 1
1 0 1 1 0 1 1 1 1 0 0
0 1 0 1 1 0 1 1 1 1 0
0 0 1 0 1 1 0 1 1 1 1
1 0 1 1 1 0 0 0 1 1 0
0 1 0 1 1 1 0 0 0 1 1
- - - - - - - - - - -
1 0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0 1

Le résultat de cette multiplication est appelé le syndrome d'un mot. Si le syndrome (sur 11 bits) est 00000000000, alors le mot initial est correct. Si le syndrome est différent de 000000000, alors le mot initial n'est pas correct.

Définissez la matrice de parité

/*
 * matrice de parité pour le code de Golay
 */
unsigned golay_H[23] = {
    ...,
    ...,
};

puis écrivez la fonction qui calcule le syndrome (11 bits) d'un mot

/*
 * syndrome d'un mot du code de Golay (sans bit de parité)
 */
mot syndrome_golay(mot m) ;

Par exemple :

$ ./golay -s 11010111010101111000110
syndrome_golay: 11010111010101111000110  =>  01111011000

2.3. Correction des erreur -- 1

Le code de Golay permet de détecter 6 erreurs (7 pour la version étendue) et permet donc de corriger 3 erreurs parmi les 23 bits reçus.

Nous allons implémenter une méthode de correction basée sur les propriétés suivantes du code.

Propriété 1: si le syndrome a un poids de 3 ou moins (càd a moins de trois 1) alors le syndrome contient exactement l'erreur.

Par exemple, w = 00100100000010110000001 a pour syndrome 00000100010.

Comme le syndrome ne contient que deux 1, cela signifie que le mot w a deux erreurs :

La correction de w est donc 00100100000010110100011.

Ce type de correction n'est pas très intéressant car seuls les bits de redondance (les 11 bits de poids faible) peuvent être corrigés de cette manière.

Cette propriété vient du fait que la matrice génératrice utilisée est sous forme canonique (avec la matrice identité à gauche).

Propriété 2: pour corriger un mot, il suffit de corriger une permutation circulaire (puis de la permuter circulairement à l'envers).

Par exemple, le mot w = 00110001001101011011100 a pour syndrome 00011101011. La propriété précédente ne permet donc pas de le corriger.

Si on décale le mot w vers la droite, en remettant le bit de poids faible à gauche, on obtient w' = 00011000100110101101110. Le syndrome de ce mot est 10100000100 qui ne contient que trois 1.

On peut donc corriger w' en 00011000100100001101010.

Si on décale ce mot vers la gauche (pour annuler le décalage vers la droite précédent), en remettant le bit de poids fort à droite, on obtient 00110001001000011010100 qui est la correction du mot w initial.

Cette propriété vient du fait que le code de Golay est cyclique.

Écrivez la fonction qui prend un mot m de n bits en argument et fait le décalage de p bits vers la droite, en remettant les p bits de poids faible à gauche.

/*
* renvoie le décalage circulaire (à droite) de p bits sur un mot m de n bits
*/
mot decalage_circulaire(mot m, int n, int p) ;

Pour notre application, la valeur de n sera toujours 23.

Privilégiez les opérations bit à bit pour cette fonction.

Par exemple, sur le mot m = 11010101110 avec n=11 bits, le décalage vers la droite de 3 bits donne 11011010101.

Attention, pour éviter des problèmes dans la suite, faites en sorte de toujours renvoyer des mots de n bits : les bits de poids fort en dehors du résultat doivent être égaux a 0.

Programmez la fonction

/*
 * correction d'erreur pour le code de Golay en utilisant la méthode "error
 * trapping" + cyclicité
 */
mot correction_golay(mot m) ;

Attention, cette fonction doit renvoyer un mot de Golay correct sur 23 bits. (C'est la fonction main qui décode le résultat en enlevant les 11 bits de redondance.)

Testez votre fonction sur des exemples:

  1. choisissez un mot sur 12 bits,

  2. codez le avec la fonction code_golay,

  3. ajoutez (avec un XOR) une erreur choisie, comme par exemple 00000000000001100001000,

  4. essayez de la corriger avec la fonction correction_golay,

  5. comparer avec le mot du code sans erreur.

Par exemple :

$ ./golay -d 01110110101110100000010
correction_golay: 01110110101110100000010  =>  111101101011

Note : la fonction appelée grâce à l'option -d fait la correction d'erreur puis un décalage pour supprimer les bits de redondance.

2.4. Correction des erreur -- 2

Lorsqu'il n'y a qu'un bit d'erreur, la méthode précédente fonctionne toujours.

La méthode pour corriger toutes les erreurs de 2 ou 3 bits est donc un peu plus compliquée :

  1. on essaie la méthode précédente

  2. si ça ne fonctionne pas, on reprend le mot initial (incorrect) et on suppose que le premier bit est faux. On le change et on essaie de corriger en utilisant la méthode précédente.

  3. si ça ne fonctionne pas, on reprend le mot initial (incorrect) et on suppose que le deuxième bit est faux. On le change et on essaie de corriger en utilisant la méthode précédente.

  4. ...

Attention, dans les étapes 2, 3, 4, ... on essaie de corriger 2 bits. Il faut vérifier que le poids du syndrome est inférieur à 2 au lieu de 3.

Parce que le code de Golay est parfait, cette méthode trouvera toujours une correction pour le mot.

Modifiez votre fonction correction_golay pour pouvoir corriger toutes les erreurs de 2 ou 3 bits.

./correction -d 01110110101110100010010
correction_golay: 01110110101110100010010  =>  010100101011

2.5. Correction d'erreurs : recherche exhaustive

Une autre méthode de correction d'erreurs est la recherche exhaustive : on parcourt tous les mots corrects en les comparant au mot à corriger. Le résultat est le mot correct le plus proche (au sens de la distance de Hamming) du mot à corriger.

  1. Programmez la fonction correction_exhaustive_golay,

  2. et comparez les temps d'exécution de correction_exhaustive_golay et correction_golay en détaillant votre méthodologie.

Vous pouvez tester cette fonction avec l'option -e :

./correction -e 01110110101110100010010
correction_golay_exhaustive: 01110110101110100010010  =>  010100101011

Attention, la fonction correction_exhaustive_golay doit renvoyer un mot du code de Golay, c'est à dire un mot de 23 bits.

2.6. Statistiques sur la correction

Le code de Golay a une capacité de 12/23 ≈ 0.522 : il envoie en moyenne 52.2% d'information et 47.8% de redondance.

On peut démontrer qu'avec une telle capacité, le meilleur taux d'erreur possible en sortie (en fonction du taux d'erreur en entrée) suit la courbe suivante :

./TP-Golay/graphique-small.png ./TP-Golay/graphique-zoom-small.png
données données
(fichier gnuplot)

Les options -p et -n de l'exécutable golay permettent de simuler des transmissions sur un canal binaire symétrique bruité.

$ ./golay -n 10000 -p 3
Tests sur un canal symétrique binaire avec taux d'erreur de 3.00%.
10000 mots ont été codés et envoyés
5202 mots ont étés modifiés (52.0200%)
7216 bits ont étés modifiés (3.1374%)
39 mots n'ont pas étés corrigés correctement (0.3900%)
132 bits n'ont pas étés corrigés correctement (0.1100%)

Utilisez ces options pour ajoutez des points sur les graphiques ci dessus et interprétez le résultat attentivement. (Vous pouvez aussi utiliser la fonction statistiques directement dans votre fichier de tests.)

N'oubliez pas d'ajouter les fichiers pertinents lors de votre soumission.