Préambule

Objectifs

Vous savez comment écrire un module, que ce soit en terme d'assemblage d'objets plus petits (sous-modules, canaux) ou de fonctionnalité (processus).

Il faut néanmoins être capable de tester un modèle : de le simuler, le débugger. C'est l'objectif de ce chapitre, qui clôt cette formation à SystemC.

Une série d'exercices est donnée à la dernière section. Ces exercices sont obligatoires et seront notés.

Plan du chapitre

  1. Contrôle des simulations : comment lancer une simulation, l'arrêter
     
  2. Débug : ce n'est pas si simple qu'il y parait...
     
  3. Gros projets, petits soucis : les modèles SystemC sont longs à compiler. Et un projet de taille raisonnable se met vite à comporter un nombre déraisonnable de fichiers. Il vaut alors mieux utiliser des Makefile pour se faciliter la vie. Cette section s'adresse uniquement à ceux qui ne maîtrisent pas les Makefile.
     
  4. Si la théorie est belle, la pratique l'est encore plus. Des exercices sont prévus, pour préparer entre autres la série de TP sur les SoC qui vont suivre.
Back to top

La simulation

Nous ne parlerons pas ici de la façon dont on écrit un testbench, vous l'avez déjà vu dans les cours de Verilog et VHDL. Nous allons plutôt nous pencher sur la façon dont on contrôle le scheduler SystemC.

Les bibliothèques SystemC intègrent en standard un simulateur événementiel rapide, qui permet de se passer de simulateur tierce-partie. On peut toujours utiliser Modelsim ou autre, notamment pour cosimuler du SystemC et un autre langage, mais cela implique certains styles d'écriture (se reporter à la documentation de Modelsim pour cela).

Dans la première partie des TP sur les SoC, les simulations ne porteront que sur du SystemC seul. On pourra alors utiliser le simulateur intégré.

Résolution temporelle

Le simulateur natif SystemC est événementiel, et suit le comportement décrit dans le cours sur les HDL. Il procède par delta_cycles, en n'avançant le temps physique que lorsqu'il n'y a plus d'événements notifiés. Comme tous les simulateurs, il possède une résolution minimale : c'est la plus petite unité de temps comprise par le simulateur. Tous les temps spécifiés sont arrondis à des multiples de cette résolution. De même, le temps physique avance par multiple de cette résolution.

Par défaut, la résolution est de 1ps. Il est possible d'en changer, grâce à la fonction sc_set_time_resolution(), qui est un peu l'équivalent des `timescale de Verilog.

Cette fonction prend en argument un nombre (type double) et une unité (SC_PS, SC_NS, ...). Elle a des restrictions :

 

Exemple :


// spécifie une résolution de 10ps
sc_set_time_resolution(10, SC_PS);


...

wait(3.456, SC_NS); // sera arrondi à 3,46ns...

 

Déclaration des horloges

SystemC donne la possibilité de déclarer des horloges. C'est le scheduler qui se charge de les faire marcher. On utilise pour cela le type sc_clock, qui prend en argument :

 

Exemple :

// instancie une horloge de période 10ns
sc_clock clock1("clk100", 10, SC_NS);

sc_time t10(10, SC_NS);
sc_clock clock2("clk100", t10);

// instancie une horloge
// de 15 ns de période 15.3ns,
// de rapport cyclique 40%,
// démarrant au temps 45ms,
// et dont le premier état est haut
sc_time tt(45, SC_MS);
sc_clock clock2("clk2", 15.3, SC_NS, 0.4, tt, true);

 

Les horloges sont généralement instanciées au plus haut niveau, ou dans le générateur de vecteurs de test, et propagées tout le long de la hiérarchie des modules.

On peut déclarer une horloge dans un module, à la condition que : 

 

Hiérarchie, sc_main

Le plus haut niveau d'un système complet (module à tester + génération des vecteurs de test) n'est pas un module, c'est la procédure sc_main, qui est l'équivalent du main des programmes C.

Le véritable main est déjà implémenté dans les bibliothèques SystemC, et on ne doit pas le ré-implémenter. On utilise à la place sc_main, qui a le même prototype : int sc_main(int argc, char *argv[]);

On instancie directement au niveau du sc_main les modules de plus haut niveau, et les canaux qui vont les relier (pas de processus, ils n'existent qu'à l'intérieur des modules).

La génération des vecteurs de test peut se faire :

C'est principalement une affaire de goût et de bon sens...

 

Lancement et contrôle de la simulation

Quand tous les modules ont été instanciés et reliés entre eux, on peut lancer la simulation. Pour cela, on utilise la fonction sc_start() depuis le sc_main.

Cette fonction prend en argument un temps, qui est le temps pendant la simulation sera effectuée. Le scheduler SystemC est automatiquement initialisé (si besoin est) et lancé, et s'occupe de générer les horloges et lancer les processus.

Si on souhaite une simulation sans fin, il suffit d'appeler sc_start() sans arguments ou avec un argument négatif.
Si on passe SC_ZERO_TIME comme durée de simulation, alors la simulation dure un delta-cycle.

La simulation peut être arrêtée à n'importe quel moment depuis un processus, en appelant sc_stop().

On peut récupérer aussi à tout moment (depuis un processus) :

 

Remarques importantes :

 

Pour des exemples, se reporter au chapitre sur les exemples.

 

 

En résumé :

Le niveau le plus haut d'un système complet simulable est sc_main.

On peut instancier des horloges en instanciant des objets sc_clock.

La simulation se lance ainsi par sc_start() depuis sc_main, et être arrêtée par  sc_stop() depuis un processus. On peut aussi passer une durée à sc_start pour ne lancer la simulation que pendant un temps déterminé.

 

 

Dans les premiers temps, vos modèles risquent d'être buggés. Il faut alors les débugger. C'est l'objectif de la prochaine section.

 

 

Back to top

Le debug

Un système numérique écrit en SystemC n'étant après tout qu'un programme C++ standard, il est tout à fait possible d'utiliser les moyens habituels de debug des programmes C++ :

 

La génération de trace se fait en ouvrant un fichier de chronogramme en écriture. On peut en ouvrir autant qu'on veut, et chaque signal peut être tracé en même temps dans autant de fichiers qu'on le souhaite. Cela est fait par la commande sc_create_vcd_trace_file(), qui prend en argument un nom de fichier, et renvoie un file descriptor spécial, de type sc_trace_file*.

On spécifie les signaux à tracer par la fonction sc_trace(), qui prend en argument :

 

On peut préciser l'unité de temps qui sera utilisée dans le fichier de traces en utilisant la méthode set_time_unit() qui prend comme argument un sc_time.

 

Exemple:

  sc_signal<int> data;
  float b;
  
  sc_trace_file * trace_file;
trace_file = sc_create_vcd_trace_file("out.vcd"); sc_trace(trace_file, a, "mes_data"); sc_trace(trace_file, b, "B");

Les appels à sc_trace et sc_create_vcd_trace_file doivent être faits avant le début de la simulation. Par exemple dans sc_main, ou dans le constructeur d'un module...

A la fin de la simulation, il faut appeler sc_close_vcd_trace_file pour fermer le fichier de trace (et forcer le vidage des tampons).

 

 

Back to top

Gros projets, petits soucis : les Makefile

Cette section n'est pas détaillée, les Makefile ayant été étudiés en cours d'informatique.

Si, par hasard, cette partie des cours d'informatique n'avait pas été totalement comprise, je vous conseille de vous reporter à l'excellent cours de Fabrice Derepas, qui couvre C++, les Makefile ainsi que SystemC.

En attendant, je vous propose un Makefile type, que vous pourrez adapter à vos projets, qui devrait convenir aux machines du département COMELEC. Pour l'utiliser chez vous, pensez à modifier le chemin d'installation de SystemC et, éventuellement, à remplacer "linux64" par "linux".

Back to top

Exercices

Ces exercices sont à rendre, avant la fin de la semaine. Ils sont notés.

Les fichiers déposés sur Git doivent être indentés et commentés, en français ou anglais au choix.

PGCD

Le but de cet exercice est de construire un module calculant le PGCD de deux entiers non signés sur 8 bits.

Ce module sera appelé PGCD, et possède les ports suivants :

port direction largeur (bit) type description
clk in 1
sc_in<bool>
horloge du module
x in 8
sc_in<unsigned char>
premier entier
y in 8
sc_in<unsigned char>
deuxième entier
pgcd out 8 sc_out<unsigned char>
pgcd(x,y).
valide au moins pendant que done==1
start in 1
sc_in<bool>
enable est mis à 1 pendant un cycle.
Le module doit échantilonner x et y quand enable==1, et commencer à calculer leur PGCD
done out 1
sc_out<bool>
done vaut zéro en général, et 1 pendant un cycle quand le PGCD a été calculé.
Pendant que done est haut, le PGCD est présenté sur la sortie pgcd.

 

1) Écrire un modèle de haut niveau de ce module (proche de l'algorithme). Ce modèle doit doit en plus permettre de voir que le temps de calcul dépend des valeurs des données en entrée.

2) Écrire un modèle "synthétisable" de ce module. On l'implémentera sous forme d'une machine à état et d'un chemin de donnée.

3) Écrire un module de test de ce module, appelé test_PGCD. Le test doit être suffisamment précis, voire exhaustif si possible.

ps. Vous pouvez utiliser le modèle haut niveau comme réference pour tester le modèle synthétisable.

 

Généricité en SystemC

Vous connaissez les principes de la généricité, et comment elle est implémentée en Verilog et/ou VHDL.

Proposez deux moyens de l'implémenter en SystemC, et détaillez pour chaque moyen les avantages et inconvénients.

 

 

Back to top

Conclusion

Ce chapitre clôt la formation à SystemC.

Vous savez maintenant comment écrire un modèle complet, le simuler et le débugger. Si vous avez des problèmes, n'hésitez à consulter les enseignants (tous...), à vous reporter aux différents documents officiels de SystemC (voir chapitre introduction ou sur le site de SystemC), ou même à Google...

Enfin, toute remarque ou critique sur ce cours sera la bienvenue !


Back to top