Le rendu de TP se fera uniquement à travers l'interface web TPLab. Vous devrez fournir un unique fichier .c
contenant le code des fonctions demandées.
Vous pouvez utiliser le fichier Makefile
fournit pour faciliter la compilation.
Certaines questions appellent à une réponse que vous pouvez mettre en commentaire dans votre fichier principal.
Attention : les points suivants ne rapportent rien, mais ne pas les respecter pourra retrancher jusqu'à 10 points sur la note finale :
nom.c
",
-Wall -Werror -Wextra -pedantic -std=c99
,
Le but de ce TP est de comprendre le fonctionnement du code de Golay et d'en implémenter une version en C...
Nous utiliserons le type uint32_t
(mots 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
.
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
char
et leurs variantes,
short
et leurs variantes,
int
et leurs variantes,
long
et leurs variantes.
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 types du langage C grâce aux opérations bit-à-bit.
Les opérations sont :
0 & 0 = 0
0 & 1 = 1 & 0 = 0
1 & 1 = 1
0 | 0 = 0
0 | 1 = 1 | 0 = 1
1 | 1 = 1
0 ^ 0 = 0
0 ^ 1 = 1 ^ 0 = 1
1 ^ 1 = 0
~0 = 1
~1 = 0
Appliqués à des entiers (ou des char
), les opérateurs &
, |
, ^
et ~
agissent sur chaque bit indépendamment.
Deux autres opérations sont disponibles :
0
dans les bits de poids forts. Sur des entiers non signés, ça correspond à une division entière par une puissance de 2 :
00010000 >> 2 = 00000100
, c'est à dire 16>>2 = 4
en décimal, ou 0x10>>2 = 0x04
en hexadécimal,
01101100 >> 3 = 00001101
, c'est à dire 0x6a>>3 = 0x0d
en hexadécimal,
00010000 << 2 = 01000000
, c'est à dire 16<<2 = 64
en décimal, ou 0x10<<2 = 0x40
en hexadécimal,
char
) : 01101100 << 3 = 01100000
, c'est à dire 0x6a<<3 = 0x60
en hexadécimal.
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. Vous ne devrez modifier que le fichier harry-potter.c
.
Modifiez la macro
#define BIT(i,m) ((m) & 1)
qui donne le bit de poids faible d'un mot m
pour obtenir le bit numéro i
de m
. Le bit de poids faible aura le numéro 0
et le bit de poids fort aura le numéro 31
. Par exemple :
// test pour la question 1 mot m = 0x555; /* càd 00...000010101010101 en binaire */ int n = 3; printf("Le bit numéro %d de %x est %u et le bit %d est %u.\n", n, m, BIT(n,m), n+3, BIT(n+3,m));
devra afficher
Le bit numéro 3 de 555 est 0 et le bit 6 est 1
Servez-vous de cette macro pour écrire une fonction
void print_bin(mot u, int n) ;
qui affichera les n
bit de poids faible d'un mot. Par exemple:
m = 0xace; printf("le mot '%x' est en fait ",m); print_bin(m,12); printf("\n");
devra afficher
le mot 'ace' est en fait 101011001110
main
que vous ne devez pas modifier. Cette fonction permet de lancer le programme compilé depuis la ligne de commande. Une des options disponibles est -T
qui permet d'exécuter la fonction test_cmd
. Vous devrez donc utiliser cette fonction pour effectuer vos tests...
code_golay
, code_golay_etendu
, correction_golay
+decode_golay
).
Écrivez la fonction
int poids(mot m) ;
qui renvoie le nombre de 1
dans un mots.
Privilégiez les opérations bit à bit et essayer de minimiser le nombres d'instructions utilisées par votre fonction.
N'oubliez pas de tester votre fonction !
Une des matrices génératrices du code de Golay est [ I | A]
où 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 bits. Comme la partie gauche de
la matrice est la matrice identité, cela signifie que le codage de w
est
w.r
où r
contient les 11 bits de redondances ajoutés par le code. Le
décodage est donc trivial: il s'agit d'un décalage à droite.
Cette matrice signifie que :
100000000000
sont 10101110001
(première ligne),
010000000000
sont 11111001001
(deuxième ligne),
Les bits de redondance des autres mots sont codés par linéarité. Par exemple, pour 011000000000
on fait le XOR de la troisième et deuxième lignes de la matrice A
.
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 unsigned int
) la matrice A
doit être représentée par un tableau d'éléments du type mot
:
mot golay_A[12] = { 0x571, 0x7c9, 0x695, 0x63b, 0x66c, 0x336, 0,0,0,0,0,0}; };
Complétez la définition des 6 dernières lignes de cette matrice (variable globale) par les constantes appropriées.
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) ;
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. Le mot initial reste inchangé...
Privilégiez les opérations bit à bit dans votre fonction...
Vous pouvez tester la fonction code_golay
depuis la ligne de commande :
$ ./harry-potter -c 110100111 code_golay: 000110100111 => 00011010011111110000110
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é.
É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 :
$ ./harry-potter -C 110100111 code_golay_etendu: 000110100111 => 000110100111111100001100
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 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 est un mot correct. Si
le syndrome est différent de 000000000
, alors le mot 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 :
$ ./harry-potter -s 11010111010101111000110 syndrome_golay: 11010111010101111000110 => 01111011000
Le code de Golay permet de détecter 6 erreurs (7 pour la version étendue). Il permet donc de corriger 3 erreurs parmi les 23 bits reçus. La méthode de correction est basée sur les propriétés suivantes du code :
w
est un mot correct, alors toutes les permutations circulaires de w
sont également correctes.
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 00100100000010110
1
000
1
1
.
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.
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
.
La propriété précédente permet de corriger w'
en 000110001001
0
0
0
01101
0
10
.
Si on décale ce mot vers la gauche, en remettant le bit de poids fort à droite, on obtient
00110001001
0
0
0
01101
0
100
.
É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 */ unsigned 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
.
Programmez la fonction
/* * correction d'erreur pour le code de Golay en utilisant la méthode "error * trapping" + cyclicité */ mot correction_golay(mot m) ;
Testez votre fonction sur des exemples:
code_golay
,
00000000000001100001000
,
correction_golay
,
Par exemple :
$ ./harry-potter -d 01110110101110100000010 correction_golay: 01110110101110100000010 => 111101101011
Note : la fonction appelée grace à l'option -d
fait la correction d'erreur puis un décalage afin de ne donner que les bits de données corrigés.
Lorsqu'il n'y a qu'un bit d'erreur, la méthode précédente fonctionne toujours !
Expliquez en quelques mots pourquoi la méthode précédente ne parvient pas toujours à corriger deux ou trois erreurs.
Lorsque les bits d'erreurs sont trop éloignés, la méthode précédente ne fonctionne pas : aucun décalage circulaire n'amènera toutes les erreurs dans les 11 bits de poids faible du mot.
Donnez un exemple d'erreur de 2 bits que la fonction précédente ne peut pas corriger.
La méthode pour corriger toutes les erreurs de 2 ou 3 bits est donc un peu plus compliquée :
Attention, dans les étapes 3..24, comme il faut seulement 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
Écrivez la fonction
/* * décode un mot du code de golay un supprimant les bits de redondance */ mot decode_golay(mot m) ;
Écrivez une fonction qui lit des octets dans un fichier (fonction fread
), les code avec le code Golay étendu, et écrit le résultat dans un autre fichier (fwrite
).
Écrivez la fonction réciproque qui lit un fichier et corrige les erreurs avant d'écrire un fichier contenant le décodage correspondant.
Attention: un octet ne contient pas assez de bits pour être codé avec le code de Golay, mais deux octets contiennent trop de bits. Il faudra donc lire trois octets à la suite (24 bits) et les coder sur six octets (48 bits). (Et vice versa pour la fonction réciproque.)
Si le fichier en entrée ne contient pas assez d'octets, il faudra ajouter un "padding", qu'il ne faudra pas oublier d'enlever dans la fonction qui décode...
Lorsqu'un octet d'un fichier codé est modifié, il y a potentiellement 8 bits qui peuvent changer. Le code de Golay ne parviendra donc pas toujours à corriger un octet erroné.
Proposez une méthode qui permet de corriger plus facilement ce types d'erreurs (sur un octet entier), sans ajouter de bits de redondances supplémentaires.
Implémentez ce protocole dans vos fonctions de la question précédente.