Full
Full
Mini-projet
Tarik Graba
2020-2021
1 Historique et concepts 6
Présentation de l’UE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Objectifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Organisation et évaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Pourquoi SystemC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
À quel niveau d’abstraction ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Que veut dire niveau de représentation ? . . . . . . . . . . . . . . . . . . . . . . . . . 8
Que veut dire niveau de représentation ? . . . . . . . . . . . . . . . . . . . . . . . . . 9
SystemC : Un langage pour tous les modèles . . . . . . . . . . . . . . . . . . . . . . . 11
SystemC : Un langage pour tous les modèles . . . . . . . . . . . . . . . . . . . . . . . 11
SystemC : Un langage pour presque tous les modèles . . . . . . . . . . . . . . . . . . 11
SystemC : Un langage pour presque tous les modèles . . . . . . . . . . . . . . . . . . 12
SystemC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
La bibliothèque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Structure de la bibliothèque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Comment récupérer et installer SystemC . . . . . . . . . . . . . . . . . . . . . . . . . 13
Premier exemple Hello World . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Que doit-on compiler ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Première compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2 Les types 19
Le type logique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Le type logique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Les vecteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Les bit vectors sc_bv<N> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Les logic vectors sc_lv<N> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Les entiers de taille arbitraire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Remarque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
sc_int<N> et sc_uint<N> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
sc_bigint<N> et sc_biguint<N> . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Les nombre en virgule fixe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Les types définis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Les sc_fixed et sc_ufixed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2
Comment les utiliser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Les sc_fix et sc_ufix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Alternatives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Le temps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Le type sc_time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3 Simulation et signaux 30
Le simulateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Le simulateur événementiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Faire avancer la simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Visualisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Traces VCD en SystemC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Les signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Au-delà de simples signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Le type sc_signal<T> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
L’implémentation sc_signal<T> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Quels types peut-on transporter ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Un signal d’horloge avec sc_clock . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Résolution de conflits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4 Hiérarchie et modules 40
Les modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
sc_module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Le constructeur d’un module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Les macros prédéfinies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Constructeurs additionnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Les ports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
sc_in, sc_out, sc_inout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Comment utiliser les ports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Types résolus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Instances et connexions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Dans le sc_main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Dans un module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Les exports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
sc_export . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Sûr et transparent . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5 Évènements et processus 51
Les évènements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Les sc_event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
La notification des sc_event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Les évènements pour un sc_signal . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Les processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Les processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Déclaration d’un processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3
Les SC_THREAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Déclarer un SC_THREAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
La mise en veille d’un SC_THREAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
La liste de sensibilité d’un SC_THREAD . . . . . . . . . . . . . . . . . . . . . . . . . . 55
La remise à zéro d’un SC_THREAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Spécialisation : les SC_CTHREAD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Spécialisation : wait dans un SC_CTHREAD . . . . . . . . . . . . . . . . . . . . . . . . 59
Les SC_METHOD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
La liste de sensibilité d’une SC_METHOD . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Gestion de la remise à zéro dans une SC_METHOD . . . . . . . . . . . . . . . . . . . . 61
La méthode dont_initialize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Résumons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Travail à faire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Modéliser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
7 La Co-simulation 75
Mixer les langages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
La co-simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Testbench en SystemC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Mise en pratique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Deux approches . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Modelsim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Module “étranger” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Exemple (HDL) : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Exemple (Wrapper) : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Exemple (Instanciation) : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Workflow Modelsim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Verilator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Convertir le Verilog en C++/SystemC . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Conversion automatique du RTL grâce à Verilator . . . . . . . . . . . . . . . . . . . . . 82
Workflow Verilator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Exemple (Conversion) : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Travail à faire : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
4
8 Les canaux de communication 88
Au-delà des simples signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Interface et canal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
sc_interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
sc_prim_channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
sc_port . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Exemple d’utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Et pour les sc_signal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
L’interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Les ports sc_in, sc_out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Le sc_signal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Des canaux standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Les sc_buffer<T> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Les sc_fifo<T> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
Les sc_mutex et sc_semaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
sc_channel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
9 Mini-projet 99
Objectifs : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Plan du projet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Contexte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Module d’entrée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Au travail : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
module de sortie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Effets spéciaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5
1 Historique et concepts
Présentation de l’UE
Objectifs
• Présenter un nouveau langage de description du matériel et de modélisation SystemC
• Présenter des concepts de modélisation à différents niveaux d’abstraction
• Regarder sous le capot (SystemC est opensource)
Organisation et évaluation
• Cours suivi de mise en pratique
• Travaux à faire avant la séance suivante
• QCM à la fin
Introduction
Généralités
• SystemC est un “langage” de modélisation et de description du matériel.
• Une implémentation de référence, libre (open source) est distribuée par Accellera.
• Le standard est distribué sous la forme d’un manuel de référence (Language Reference Manual) et
est disponible gratuitement sur le site de l’IEEE (Institute of Electrical and Electronics Engineers). Il
peut être téléchargé sur cette page du site de l’IEEE.
• Accellera System Initiative est une organisation à but non lucratif dont l’objectif est de développer et de
promouvoir des standards industriels pour la conception et la modélisation de systèmes électroniques.
La liste des standards soutenus par cette organisation se trouve ici.
• L’implémentation de référence de SystemC est accessible librement (après acceptation de la licence
d’utilisation) sur la page dédié du site d’Accellera. Pour ce cours nous utiliserons la version 2.3 du
6
standard (la version 2.3.2 propose quelques fonctionnalités en avance par rapport à la norme qui
sont désactivées par défaut ainsi que la compatibilité avec des compilateurs récents).
• Sur les machines de l’école, la bibliothèque est accessible dans le répertoire /comelec/softs/opt/systemc/current.
Le répertoire contient le source ainsi qu’une version compilée pour Linux x86_64.
Historique
• 1998, Synopsys “ouvre” son outil Scenic et crée SystemC 0.9
• 2000, Standard OSCI (Open SystemC Initiative)
– Avec des contributions de Frontier Design et CoWare
• Standard IEEE en 2005 avec la version 2.0
– SystemC V2.2 correspond au standard IEEE 1666-2005
• Fusion OSCI/Accellera en 2011 et mise à jour du standard
– SystemC V2.3 correspond au standard IEEE 1666-2011
• Nov. 2016 V2.3.1, passage en licence Apache
• Version actuelle V2.3.3
À la base, c’est des contributions de plusieurs entreprises qui ont été regroupées pour devenir un standard.
La première version de SystemC apportait déjà tous les éléments nécessaires pour la description du
matériel :
• Un simulateur évènementiel
• Des processus
• Des signaux
La version 2 de SystemC a apporté des mécanismes de modélisation plus haut niveau dits transactionnels.
D’abord sous la forme d’une extension (TLM : Transaction Level Modeling) qui a été ensuite intégrée au
standard. La version 2.3 de SystemC apporte la 2e version de cette extension (TLM 2.0).
CoWare et Frontier Design, n’existent plus. Elles ont disparu dans les multiples fusions et rachats entre les
différents acteurs du marché de l’EDA.
Pourquoi SystemC
• Pourquoi utiliser C++ pour décrire du matériel ?
7
Dans un système sur puce (SoC : System On Chip) il y a du matériel :
• des processeurs,
• des accélérateurs (graphiques, audio…),
• des interfaces de communication
• Concevoir le système :
– développer les algorithmes,
– définir ce qui est fait en logiciel et ce qui est fait en matériel (partitionnement),
• Simuler le système :
– vérifier les fonctionnalités,
– avoir un modèle de référence.
8
Pour la fonctionnalité :
Pour décrire un algorithme, on n’a besoin de décrire que la succession d’opérations menant au résultat.
Dans cette description fonctionnelle le temps nécessaire à l’exécution de ces différentes étapes n’a pas
besoin d’apparaître. Ce type de modèle est en général un point de départ pour définir les différentes
fonctionnalités que notre système devra réaliser. On parle de modèle Untimed Functionnal (UTF).
Dès qu’on veut évaluer les performances d’une implémentation pour connaître, par exemple :
On doit donc avoir au moins, une description fonctionnelle avec une information de temps sur chaque étape.
On parle de modèle Timed Functionnal (TF).
Pour l’interface :
Pour décrire des échanges de données dans le système, il peut suffire de les décrire en termes de
transactions. Des lectures ou des écritures entre différents éléments du système (un processeur, une
mémoire…). S’il y a plusieurs transactions en parallèle, il faut pouvoir garantir l’ordre dans lequel elles ont
lieu.
Si en plus, on veut pouvoir vérifier que ces transactions respectent un certain protocole de bus, il faut que
le modèle décrive précisément tous les signaux de contrôle.
Dans la conception d’un système sur puce (SoC) on passe par plusieurs étapes. À partir d’un modèle
fonctionnel, on sépare l’application en partie logicielle et partie matérielle. On parle de partitionnement.
Pour la partie logicielle, on va utiliser des modèles du support d’exécution de plus en plus précis en :
Pour la partie matérielle, on va également passer par plusieurs étapes. À partir d’un modèle architectural,
dans lequel on définit les différents blocs utilisés, on passe à des modèles transactionnels faisant apparaître
les différents transferts de données puis les temps nécessaires pour les traitements et les transferts.
9
Fig. 1.1: Niveaux d’abstraction dans un flot de conception
10
Ces modèles deviennent de plus en plus précis au cours du développement jusqu’à obtenir un modèle
précis au cycle et au bit près. Ce modèle est dit CABA (Cycle Accurate Bit Accurate) qui peut servir de
référence à l’écriture d’un modèle RTL synthétisable.
Tout au long de ce flot de développement on doit aussi conserver un modèle de référence pour vérifier la
conformité avec l’application de départ.
Remarque :
Le modèle RTL est un modèle CABA dans lequel, en plus d’être précis au cycle près et au bit près, on doit
respecter un style de codage particulier.
Avantages :
Dans l’industrie, SystemC est principalement utilisé comme langage de modélisation. Il est rarement utilisé
pour produire des modèles fonctionnels haut niveau ni pour produire des modèles au niveau RTL.
Actuellement, SystemC est utilisé pour la conception de modèles transactionnels permettant de simuler
efficacement des systèmes sur puce. Par exemple des modèles de :
• processeurs,
• contrôleurs mémoire,
11
• interconnexion…
L’utilisation de SystemC permet de concevoir des modèles dont on peut faire évoluer la précision jusqu’à
des modèles CABA. Ces modèles peuvent alors être interfacés avec des modules écrits dans d’autres
langages de description tels que SystemVerilog ou VHDL.
• Il existe des langages spécialisés dans certains domaines (Matlab…) qui facilitent la vie des déve-
loppeurs.
• Tout le monde ne veut pas apprendre le C++.
• Il a y du code qui existe déjà !
Les outils
• Les outils de synthèse RTL pour SystemC n’ont jamais été développés (ça peut changer)
• Le passage d’un niveau à l’autre n’est pas vraiment automatique.
SystemC
La bibliothèque
SystemC est une bibliothèque logicielle écrite en C++ qui contient les éléments nécessaires à la modélisation
d’un système matériel numérique.
• la modélisation transactionnelle,
• la vérification…
Il faut noter qu’il existe depuis 2010 une extension AMS (Analog/Mixed-signal) de SystemC pour la modéli-
sation des systèmes analogiques et mixtes. Cette extension utilise un moteur de simulation analogique (à
temps continu) différent du moteur événementiel et des concepts propres à la simulation analogique qui ne
seront pas abordés ici.
12
Structure de la bibliothèque
SystemC est une bibliothèque construite au-dessus de C++ donc tout ce qui est disponible en C++ l’est
aussi en SystemC. Ceci est important pour pouvoir réutiliser des bibliothèques logicielles existantes qu’elles
aient été écrites en C ou en C++.
Au-dessus de C++, un moteur de simulation événementiel est implémenté. Ce moteur permet de gérer les
processus et les évènements comme dans d’autres langages HDL (Verilog ou VHDL).
L’implémentation open source de référence distribuée par Accellera contient aussi le moteur de simulation
implémenté en C++.
SystemC définit un certain nombre de types utiles à la modélisation du matériel, parmi lesquels :
Sont aussi définis les éléments nécessaires à la description hiérarchique : modules, ports et canaux.
En SystemC, les canaux de communication vont au-delà des simples signaux pour permettre des niveaux
d’abstraction plus élevés. Un certain nombre de canaux primaires existe dans la bibliothèque (signaux,
Fifo…) mais des canaux supplémentaires peuvent être définis si besoin.
Au-dessus de tout ça des bibliothèques supplémentaires sont implémentées. Certaines sont fournies
avec l’implémentation de référence telles que les bibliothèques pour la modélisation transactionnelle
(TLM/TLM2.0) d’autres sont indépendantes comme la bibliothèque pour la vérification (SVC).
D’autres bibliothèques basées sur SystemC peuvent être fournies par des tiers (vendeur d’outils, d’IP…).
http ://[Link]/downloads/standards/systemc
Peut-être installée sur Linux, Mac OSX et Windows et peut être compilée au moins avec g++, clang++ ou
Visual C++.
Est déjà installée sur les machines de l’école.
Pour les distributions Linux récente, la bibliothèque est disponible à travers le système de distribution de
paquets standard.
13
Fig. 1.2: Organisation de la bibliothèque SystemC
14
Sur Debian (version 10 minimum) ou Ubuntu (19.x minimum) elle peut être installée comme suit :
La procédure d’installation est décrite en détail dans le fichier INSTALL dans l’archive de la bibliothèque.
Le plus simple sur une machine Linux est de l’installer dans /usr/local/ pour que les fichiers d’en-tête
et la bibliothèque soit automatiquement trouvé à la compilation et durant l’exécution de vos programmes.
Pour cela, une fois l’archive décompressée, dans le répertoire que vous obtiendrez, il suffit de faire :
o Microsoft Windows
(Windows Server 2008, Windows 10 Enterprise)
- Microsoft Visual Studio 2010 (10.0) (Win32 and x64)
15
- Microsoft Visual Studio 2013 (12.0) (Win32 and x64)
- Microsoft Visual Studio 2015 (14.0) (Win32 and x64)
- Microsoft Visual Studio 2017 (14.1) (Win32 and x64)
This release has not yet been tested or is known not to work as expected
on the following formerly supported platforms :
Aussi, sachez que la version en cours de développement est accessible publiquement sur GitHub.
https ://[Link]/accellera-official/systemc
#include <systemc.h>
return 0;
}
La fonction principale n’est pas main mais sc_main.
16
Fig. 1.3: Flot de compilation
La fonction main existe quand même, elle est fournie par la bibliothèque SystemC. Elle s’occupe de
configurer l’environnement de simulation avant d’appeler la fonction sc_main en lui passant les arguments
qu’elle a reçus.
Le fichier d’en-tête systemc.h contient les définitions des éléments de la bibliothèque. En plus,
• il inclut d’autres fichiers d’en-tête de la bibliothèque standard C++, par exemple iostream
• et permet d’utiliser directement certains espaces de noms (namespace) relatifs à SystemC et à la
bibliothèque standard (std).
Si on a besoin de gérer précisément les en-têtes et les espace de noms on peut préférer inclure systemc.
Les sources C++ et C sont compilés puis l’édition de liens est faite pour générer un exécutable. Parmi ces
sources, doit figurer la définition de la fonction sc_main.
La bibliothèque SystemC doit être fournie pour l’édition de liens. Elle apporte les éléments suivants :
• la fonction main,
• le simulateur événementiel,
17
• les autres objets de la bibliothèque SystemC.
Comme, il s’agit d’un flot de compilation standard pour du C++, on peut faire appel à d’autres bibliothèques
du système.
Première compilation
Makefile
SYSTEMC ?= /comelec/softs/opt/systemc/current
ARCH = linux64
• On précise les chemins de recherche pour les fichiers d’en-tête et les bibliothèques précompilées.
• On ajoute la bibliothèque systemc au moment de l’édition de liens.
Si la bibliothèque est installée dans des répertoires UNIX standards, seule la définition de la variable LDLIBS
est vraiment nécessaire. Malgré cela, gardez la définition des autres variables pour pouvoir compiler votre
code sur les machines de l’école.
L’exécution donne :
hello world
Le message indiquant la version de SystemC est issu de la bibliothèque et montre bien que des choses
sont faites avant l’appel à sc_main.
18
2 Les types
Le type logique
Le type logique
En C++ le type bool permet de représenter une valeur logique
• (true, false).
• (0, 1, Z, X).
sc_logic u;
• 0 : faux
• 1 : vrai
• Z : haute impédance
• X : Non défini, inconnu, conflit
enum sc_logic_value_t {
Log_0=0;
Log_1,
Log_Z,
Log_X
};
Notez que ces valeurs ne sont pas directement utilisable. Le standard définit les constantes
suivantes que l’on peut utiliser :
• SC_LOGIC_0
19
• SC_LOGIC_1
• SC_LOGIC_Z
• SC_LOGIC_X
En plus du constructeur par défaut et du constructeur de copie, un sc_logic peut être construit à partir :
• d’un booléen
– false -> 0 et true-> 1
• d’un entier
– seules les valeurs entières entre 0 et 3 sont autorisées
• d’un caractère
– les caractères '0', '1', 'x', 'z', 'X', 'Z' peuvent alors être utilisés
– tous les autres sont équivalents à x
Ces constructeurs sont définis comme explicit et ne peuvent donc pas être utilisés pour les conversions
implicites.
Par contre, les opérateurs binaires ainsi que les opérateurs d’affectation sont définis pour pouvoir utiliser
les sc_logic avec des booléens, des caractères ou des entiers.
Il existe aussi des méthodes pour convertir ou tester ces valeurs (to_bool, to_char, is_01) qui peuvent
être appelées si besoin.
Pour imprimer la valeur d’une variable de type sc_logic l’opérateur de flux « a été surchargé. En général,
l’opérateur de flux est surchargé pour tous les types définis dans SystemC.
Remarque Il existe aussi un type sc_bit ne pouvant prendre que les 2 valeurs (0, 1) mais il est déprécié
depuis la version 2.2 ce SystemC. Dans ce cas, on lui préférera le type bool de C++.
Les vecteurs
Les bit vectors sc_bv<N>
Vecteur d’éléments binaires (bool).
sc_bv<8> x;
20
Le paramètre de template est un entier qui permet de définir la taille du vecteur.
sc_lv<8> x;
Le paramètre de template est un entier qui permet de définir la taille du vecteur.
Les types sc_lv et sc_bv sont des vecteurs. Ils ont été conçus pour permettre l’accès efficace à leurs
différents éléments. Par contre, ils ne permettent de faire des opérations arithmétiques.
Pour l’arithmétique, nous verrons par la suite des types plus adaptés.
Les vecteurs peuvent être comparés et initialisés à partir d’une chaîne de caractère. Par exemple :
if (x == ”0000”)
...
Ici chaque caractère représente un des éléments logiques.
On peut aussi initialiser un vecteur à partir d’un bool ou d’un char. Dans ce cas tous les éléments auront
la même valeur. Par exemple :
//
sc_bv<8> X;
X = 44;
// Imprimera 44
cout « ”x---> ” « X « ”(” « X.to_uint() « ”)” « endl;
21
Les opérateurs binaires (~, &, |, ^) ainsi que les opérateurs de décalage (», «) sont supportés.
De plus, certains opérateurs ont été surchargés pour permettre la sélection d’un ou plusieurs éléments. Ils
peuvent être utilisés des deux cotés d’une affectation.
sc_bv<8> X;
X[0] = false;
sc_bit b = X[1];
X(7,4) = ”1010”;
X(3,0) = X(7,4);
La méthode range peut aussi être utilisée pour la sélection de plusieurs éléments (i.e. X(n1,n2) est
équivalent à [Link](n1,n2)).
La concaténation de deux vecteurs se fait en les mettant entre parenthèses. C’est l’opérateur ',' qui est
surchargé.
sc_bv<8> X = ”11110000”;
sc_bv<4> a;
sc_bv<4> b;
(a,b) = X;
X = (b,a);
La fonction concat peut aussi être utilisée pour la concaténation de deux éléments (i.e. (n1,n2) est
équivalent à concat(n1,n2)).
22
Dans une fonction sc_main :
- déclarez plusieurs variables de ces deux types,
- testez différentes initialisations,
- testez différents opérateurs binaires,
- testez différentes conversions entre les deux types,
- imprimez systématiquement les résultats.
Pour cela, la bibliothèque SystemC fournit des types permettant de manipuler des entiers de taille arbitraire.
sc_int<N> et sc_uint<N>
SystemC définit ces deux classes pour les entiers de taille inférieure ou égale à 64 bits.
Ces types supportent tous les opérateurs arithmétiques et logiques supportés par les entiers en C++. En
plus, comme pour les types vecteurs, ils supportent les opérateurs de sélection de bits ou de plages de bits,
de concaténation, ainsi que les opérateurs de réduction.
• un entier,
• un sc_lv ou un sc_bv ce qui est utile pour pouvoir faire des opérations arithmétiques sur ces types.
En pratique, il faut privilégier les types C++ natifs. Si l’on a besoin d’une taille arbitraire qui ne correspond
pas à un type natif, on utilise alors les sc_int et sc_uint. Ce n’est que si l’on a besoin de modéliser un
bus 3 états (Z) qu’on utilise les sc_lv.
23
- testez différents conversions entre ces types et des sc_lv,
- imprimez systématiquement les résultats.
sc_bigint<N> et sc_biguint<N>
Si l’on a besoin de représenter des entiers de taille arbitraire supérieure à 64 bits, il faut utiliser ces types.
Ils sont prévus pour permettre des calculs sur une taille illimitée mais pour des raisons pratiques, l’im-
plémentation de référence limite leur taille à SC_MAX_NBITS. Cette valeur est définie dans le fichier
kernel/sc_constants.h et peut être modifiée si nécessaire.
• Les sc_fixed et sc_ufixed pour lesquels les paramètres sont statiques (définis par des templates)
• Les sc_fix et sc_ufix pour lesquels les paramètres peuvent être définis dynamiquement
En plus, des versions _fast sont définies. Ces types utilisent en interne les doubles natifs de C++ pour
être plus efficaces mais sont limités à la taille de ceux-ci.
• wl la taille totale
• iwl la taille de la partie entière
• q_mode le mode de quantification
• o_mode le mode de saturation
• n_bits le nombre de bits qui saturent
Les paramètres wl etiwl doivent être définis à l’instanciaton. Les autres paramètres ont par défaut les
valeurs suivantes :
24
• q_mode : SC_TRN // troncature vers le bas
• o_mode : SC_WRAP // modulo
• n_bits : 0 // aucun bit ne sature
La définition exacte de ces paramètres et des comportements associés se trouvent dans la section 7.10 du
standard.
Remarque
Par défaut, les types permettant de représenter des nombres en virgule fixe, ne sont pas disponibles. Pour
les utiliser il faut définir la macro SC_INCLUDE_FX avant d’inclure systemc.h.
Toutes les opérations arithmétiques et logique sont faites en fonction des paramètres sur lesquels ils ont
été définis.
Ces types peuvent, par exemple, être utilisés pour tester simplement une implémentation en virgule fixe et
comparer la précision à une implémentation flottante.
Par exemple :
#define SC_INCLUDE_FX
#include <systemc.h>
double d1 = 0.1;
double d2 = 0.9;
double ds = d1 + d2;
sc_fixed<WL,IWL> f1(d1);
sc_fixed<WL,IWL> f2 = d2;
sc_fixed<WL,IWL> fs = f1 + f2;
double e = ds - double(fs);
25
cout
« ”Double ” « ds « endl
« ”Fixed ” « fs « endl
« ”Error ” « e « endl;
return 0;
}
Exemple d’utilisation :
#define SC_INCLUDE_FX
#include <systemc.h>
return ds - fs;
}
double d1 = 0.1;
double d2 = 0.9;
26
sc_fxtype_params param1(3,1);
// 1 bit pour la partie entière et 4 bits après la virgule
sc_fxtype_params param2(5,1);
// 1 bit pour la partie entière et 6 bits après la virgule
sc_fxtype_params param3(7,1);
sc_fxtype_context ctxt1(param1);
cout
« ”The error with ” « ctxt1.default_value()
« ” format is ” « compare(d1,d2) « endl;
sc_fxtype_context ctxt2(param2);
cout
« ”The error with ” « ctxt1.default_value()
« ” format is ” « compare(d1,d2) « endl;
sc_fxtype_context ctxt3(param3);
cout
« ”The error with ” « ctxt1.default_value()
« ” format is ” « compare(d1,d2) « endl;
return 0;
}
De façon analogue aux sc_fixed, le type sc_fxtype_params prend, entre autres, comme paramètres
de construction la taille totale de la représentation ainsi que la taille de la partie entière.
Alternatives
Il existe une autre bibliothèque C++ libre, indépendante de SystemC, pour modéliser des calculs matériels.
Il s’agit de la Algorithmic C Datatype (AC Datatypes) library. Elle est compatible avec SystemC mais peut
aussi être utilisée indépendamment en pure C++.
Elle est distribuée sous les termes de la licence libre Apache 2.0 et est accessible sur https ://[Link]/
27
Le temps
Le type sc_time
SystemC définit un type pour représenter le temps de simulation ou des intervalles de temps.
Un sc_time est construit à partir d’un double et d’une unité. L’unité est une des valeurs du type énuméré
sc_time_unit
sc_time periode(17.22, SC_NS);
enum sc_time_unit {
SC_FS = 0, // femtosecondes
SC_PS, // picosecondes
SC_NS, // nanosecondes
SC_US, // microsecondes
SC_MS, // millisecondes
SC_SEC // secondes
};
En interne, le temps est stocké sous la forme d’un entier 64 bits qui est un multiple de la résolution temporelle.
Cette valeur interne peut être récupérée en utilisant la méthode value.
La résolution temporelle est globale à une simulation. Par défaut elle est égale à 1 picoseconde mais peut
être modifiée en utilisant la fonction sc_set_time_resolution.
Quelle que soit la résolution, la constante SC_ZERO_TIME représente toujours un temps nul.
Le type sc_time supporte des opérateurs de comparaison ainsi que certains opérateurs arithmétiques. On
peut ainsi additionner deux temps ou les multiplier par un double.
Exemple d’utilisation :
#include <systemc.h>
sc_time t2 = 2*t1;
cout « ”---> ” « [Link]() « ”, ” « t2 « endl;
28
else
cout « ”t2”;
cout « ” est plus grande” « endl;
return 0;
}
29
3 Simulation et signaux
Le simulateur
Le simulateur événementiel
Le simulateur événementiel fait partie de la bibliothèque SystemC.
L’implémentation de référence fournie contient donc tout le nécessaire pour faire des simulations.
• Elle ne peut être appelée qu’à partir de sc_main (ou dans une fonction appelée dans sc_main)
• Elle peut prendre en argument un temps
– dans ce cas, la simulation avance du temps indiqué
• Sans argument, la simulation est lancée indéfiniment
– tant qu’il y a de l’activité,
– tant que sc_stop n’a pas été appelé.
• un sc_time,
• un couple double plus unité de temps
Pour faire avancer la simulation d’un delta (un cycle de simulation) sans faire avancer le temps, il suffit de
passer à sc_start la constante SC_ZERO_TIME.
#include <systemc.h>
sc_start(T);
30
cout « ” ... ” « sc_time_stamp() « ” ” « sc_delta_count() « endl;
sc_start(0.333,SC_NS);
cout « ” ... ” « sc_time_stamp() « ” ” « sc_delta_count() « endl;
sc_start(SC_ZERO_TIME);
cout « ” ... ” « sc_time_stamp() « ” ” « sc_delta_count() « endl;
sc_start();
cout « ” ... ” « sc_time_stamp() « ” ” « sc_delta_count() « endl;
return 0;
}
Notes :
Dans cet exemple, le nombre de cycles de simulation n’avance pas car il n’y a aucun processus. Les
processus seront présentés plus tard dans ce cours.
Visualisation
Il est souvent intéressant de visualiser l’état de la simulation au cours du temps. On peut toujours utiliser
les fonctions d’impression standard de C++ car les objets de la bibliothèque SystemC les supportent.
Pour une visualisation graphique, SystemC fournit les fonctions nécessaires à la génération de chrono-
grammes au format VCD.
Le format Value Change Dump (VCD) est un format standard pour enregistrer au format ASCII les change-
ments d’états dans une simulation. Il fait partie du standard Verilog/SystemVerilog.
Dans un fichier VCD ne sont sauvegardés que les changements d’états des signaux et des variables. On
parle généralement de fichier de trace.
31
• La fonction sc_trace pour ajouter un objet aux objets à tracer
• La fonction sc_close_vcd_trace_file pour fermer le fichier
La génération des traces se fait ensuite automatiquement durant une simulation. On peut ensuite les voir
avec un outil dédié.
Par défaut, la fonction sc_trace permet de tracer les types standards de C++ ainsi que les types SystemC.
Nous verrons par la suite qu’elle permet de tracer d’autres objets définis dans SystemC et qu’on peut la
surcharger pour suivre des types personnalisés.
#include <systemc.h>
bool t = false;
// La simulation
sc_start(10,SC_NS);
t = !t;
sc_start(10,SC_NS);
t = !t;
sc_start(10,SC_NS);
t = !t;
sc_start(10,SC_NS);
32
// Ferme le fichier de trace
// ne peut êter fait qu'à la fin de la simulation
sc_close_vcd_trace_file(trace_f);
return 0;
}
À la fin de l’exécution de la simulation, un fichier nommé my_simu_trace.vcd est créé.
Il existe plusieurs outils permettant de visualiser des traces au format VCD parmi lesquels l’outil opensource
gtkwave. Il fait généralement partie des paquets disponibles dans la majorité des distributions GNU-Linux.
Travail à faire
Modifier le code de l’exemple pour générer les traces d’une variable entière positive qui s’incrémente de 0 à
N toutes les 10ns. Le maximum, N, doit pouvoir être modifié sans recompiler l’exécutable et ne dépassera
jamais 255.
Les signaux
Au-delà de simples signaux
Dans une simulation événementielle, pour garantir le déterminisme, nous avons besoin de signaux. Les
signaux permettent les affectations différées.
• la valeur actuelle,
• la valeur à la fin du cycle de simulation.
Le type sc_signal<T>
SystemC définit le type templaté sc_signal. Le paramètre de template est un type, permettant ainsi de
créer des signaux transportant tout type de donnée.
L’implémentation sc_signal<T>
• 2 valeurs : courante et future
• 3 méthodes read, write et update
33
Fig. 3.1: Un sc_signal
C’est une classe C++ templatée par un type T et contenant deux variables de ce type :
template<typename T>
class simple_sc_signal
{
T cur_val;
T new_val;
public:
34
ATTENTION C’est une version très simplifiée.
De plus, l’opérateur d’affectation = est surchargé pour que toute affectation vers un sc_signal appelle la
méthode write. Et toute référence à un sc_signal appelle la méthode read.
On peut générer des traces de signaux, tant que le type transporté le supporte.
L’exemple suivant montre comment évolue un signal dans une simulation SystemC. Notez aussi la différence
par rapport à une variable standard.
#include <systemc.h>
sc_signal<int> i;
int j = 0;
i = 33;
j = 33;
cout « ”--> @ ” « sc_time_stamp() « ” j = ” « j «” et i = ” « i « endl;
sc_start(T);
cout « ”--> @ ” « sc_time_stamp() « ” j = ” « j «” et i = ” « i « endl;
i = 44;
j = 44;
cout « ”--> @ ” « sc_time_stamp() « ” j = ” « j «” et i = ” « i « endl;
sc_start(SC_ZERO_TIME);
cout « ”--> @ ” « sc_time_stamp() « ” j = ” « j «” et i = ” « i « endl;
return 0;
}
De plus, un sc_signal peut être nommé en lui passant une chaîne de caractère comme argument
de constructeur. Si cet argument n’est pas donné, un nom générique est créé automatiquement. Des
mécanismes existent pour garantir l’unicité de ce nom. Ce nom peut être récupéré en appelant la méthode
name
#include <systemc.h>
35
sc_signal<int> i(”i”);
sc_signal<int> j(”je m'appelle j”);
sc_signal<int> x;
return 0;
}
Remarque Par défaut, un signal ne supporte qu’un seul écrivain. C’est le cas ici, puisque qu’on ne modifie
le signal que dans sc_main.
Pour qu’une classe puise être utilisée comme template d’un sc_signal il faut qu’elle remplisse les conditions
suivantes :
Exemple :
#include <systemc.h>
// un type utilisateur
struct pt_t {
int i;
int j;
// un constructeur particulier avec des valeurs par défaut
pt_t( int _i=0, int _j=1): i(_i), j(_j) { }
36
bool operator == (const pt_t &other) const {
return (i == other.i) && (j == other.j);
}
// On doit pouvoir imprimer la valeur d'un objet de ce type
// l'opérateur « est un opérateur de la classe std::ostream
friend ostream& operator « ( ostream& o, const pt_t& P ) {
o « ”{” « P.i « ”,” « P.j « ”}” ;
return o;
}
};
// Le test
int sc_main (int argc, char * argv[])
{
sc_signal<pt_t> P;
cout « ”--> @ ” « sc_time_stamp() « ” P = ” « P « endl;
// affectation au signal
P = pt_t(33,22);
cout « ”--> @ ” « sc_time_stamp() « ” P = ” « P « endl;
sc_start(1,SC_NS);
cout « ”--> @ ” « sc_time_stamp() « ” P = ” « P « endl;
return 0;
}
Travail à faire
• Écrivez une structure Pixel qui encapsule trois composantes nonn signées (R,G,B) au format
(5 :6 :5).
• Faites évoluer un signal transportant un Pixel en incrémentant successivement chaque composante.
• Générez-les traces montrant cette évolution
37
Un signal d’horloge avec sc_clock
Le type sc_clock est une spécialisation de sc_signal<bool>. Quand la simulation avance, la valeur
d’une sc_clock change cycliquement en fonction des paramètres qui lui ont été passés.
• un nom,
• une période,
• un rapport cyclique,
• l’instant du premier front,
• le sens du premier front.
Les temps peuvent être donnés sous forme de sc_time ou d’un double plus une unité.
#include <systemc.h>
sc_time T(25,SC_NS);
// une horloge de période T
sc_clock ck2(”ck2”,T);
double D = 0.4;
sc_time ST(200,SC_NS);
bool first_edge = true;
// une horloge de période T avec un rapport cyclique D
// qui commence à l'instant ST par un front montant
sc_clock ck4(”ck4”,T,D,ST,first_edge);
return 0;
}
Travail à faire
38
Afficher les traces de ces différents signaux d’horloge.
Résolution de conflits
Pour modéliser un bus 3 états on doit utiliser des signaux particuliers :
La fonction de résolution permet de calculer la valeur du signal si 2 écrivains modifient en même temps la
valeur du signal.
0 1 Z X
0 0 X 0 X
1 X 1 1 X
Z 0 1 Z X
X X X X X
39
4 Hiérarchie et modules
Les modules
sc_module
Les modules sont les éléments principaux pour construire une description hiérarchique en SystemC.
Un module SystemC est une classe C++ dans laquelle tous les champs sont publics par défaut.
Il peut donc avoir des attributs et des méthodes comme toute classe C++. Les attributs peuvent bien sur
être tout objet C++ ou SystemC, même un autre module.
De plus, on peut séparer la déclaration de la classe de la définition des méthodes dans deux fichiers (.h et
.cpp).
rappel
En C++ :
struct X {
...
};
est équivalent à :
class X {
public:
...
};
40
Le constructeur d’un module
Un module doit avoir un nom à l’instanciation. Ce nom est passé comme argument de son constructeur et
doit être transmis à la classe parent sc_module.
Par exemple :
La classe sc_module_name est une classe intermédiaire qui est utilisée pour définir un nom de module à
partir d’une chaine de caractères. À l’usage elle est transparente.
SC_MODULE(mon_module)
{
// déclaration de méthodes et attributs
SC_CTOR(mon_module)
{
// Initialisation
}
};
Remarque : On verra par la suite que SC_CTOR ajoute une information supplémentaire.
Constructeurs additionnels
Si un constructeur additionnel est ajouté il doit aussi appeler le constructeur de la classe parent sc_module
41
Par exemple :
Dans le cas où le constructeur n’est pas déclaré avec SC_CTOR, alors, si le module contient des processus,
on doit utiliser la macro SC_HAS_PROCESS.
SC_MODULE(mon_module)
{
// déclaration de méthodes et attributs
SC_HAS_PROCESS(mon_module);
};
La déclaration des processus sera vue dans la suite du cours.
Les ports
sc_in, sc_out, sc_inout
SystemC définit trois types de ports sous la forme de templates :
Comme pour les signaux, le tmplate T peut être tout SystemC ou C++. De plus, la surcharge de la fonction
sc_trace pour ce type est obligatoire.
Les trois types de ports héritent d’une classe primaire sc_port. Cette classe primaire permet d’étendre le
concept de port.
42
À la construction, on peut donner un nom au port. Ce nom, est utile pour identifier certains problèmes à la
simulation.
Si un nom n’est pas donné, un nom générique sera créé. Dans tous les cas, ce nom sera hiérarchique et
dépendra du nom du module.
Exemple :
#include <systemc.h>
SC_MODULE(foo)
{
sc_in<bool> clk;
sc_in<bool> rst;
sc_out<sc_uint<8> > data;
SC_CTOR(foo):clk(”clk”), data(”data”)
{
cout
« ”module : ” « name() « endl
« ”clk : ” « [Link]() « endl
« ”rst : ” « [Link]() « endl
« ”data : ” « [Link]() « endl
;
}
};
#include <systemc.h>
SC_MODULE(foo)
{
sc_in<bool> clk {”clk”};
sc_in<bool> rst;
sc_out<sc_uint<8> > data {”data”};
SC_CTOR(foo)
{
cout
43
Fig. 4.1: Ports et signaux
44
En fonction du type de ports, ils implémentent les méthodes :
• read()
• write()
Les opérateurs d’affectation sont surchargés pour que l’appel à ces méthodes soit transparent.
Ces méthodes ne font rien à part appeler les méthodes équivalentes des signaux connectés aux ports. Ce
sont en réalité des coquilles vide. Quand on lit ou qu’on écrit dans un port, c’est en réalité au signal qui lui
est connecté qu’on accède.
Donc, lire ou écrire sur un port non connecté ne peut pas fonctionner. Pour éviter cela, durant la phase
d’élaboration, avant le début de la simulation, des vérifications sont faites pour garantir que tous les ports
sont connectés (pour s’en rendre compte, il faut appeler sc_start).
On est parfois amené à appeler explicitement les méthodes read et write particulièrement quand les
transtipages automatiques ne fonctionnent pas.
Types résolus
Comme pour les signaux, il existe des ports pour les types résolus :
Instances et connexions
Dans le sc_main
Les modules peuvent être instanciés dans la fonction sc_main.
L’opérateur () est surchargé pour connecter les ports d’un module à des signaux. En interne, la méthode
bind du port est appelée pour enregistrer cette connexion. Des vérifications sont faites avant le début de la
simulation pour garantir que tous les ports sont connectés.
Exemple :
#include <systemc.h>
SC_MODULE(foo) {
sc_in<bool> i {”i”};
45
SC_CTOR(foo) {}
};
SC_MODULE(bar) {
sc_out<bool> o {”o”};
SC_CTOR(bar) {}
};
foo foo_i(”foo_i”);
bar bar_i(”bar_i”);
//foo_i.i(bar_i.o);// NON!
// connexion à travers le signal s
foo_i.i(s);
bar_i.o(s);
return 0;
}
Notez que la connexion du port au signal se fait après la création du module. Il n’est donc pas possible
d’accéder à un port (le lire ou y écrire) dans le constructeur du module.
Dans un module
Les sous modules sont déclarés comme des attributs du module.
• un autre port
• un signal interne
Exemple :
#include <systemc.h>
SC_MODULE(foo) {
sc_in <bool> i {”i”};
46
sc_out<bool> o {”o”};
SC_CTOR(foo) { }
};
SC_MODULE(bar) {
sc_in <bool> i {”i”};
sc_out<bool> o {”o”};
SC_CTOR(bar) { }
};
SC_MODULE(foobar)
{
// entrée/sortie
sc_in <bool> i {”i”};
sc_out<bool> o {”o”};
// interne
sc_signal<bool> s;
// sous modules
foo foo_i {”foo_i”};
bar bar_i {”bar_i”};
SC_CTOR(foobar)
{
// connexions aux I/O
foo_i.i(i);
bar_i.o(o);
// connexion interne
foo_i.o(s);
bar_i.i(s);
}
};
foobar uut(”foobar”);
uut.i(i);
uut.o(o);
return 0;
}
47
Fig. 4.2: Exports et signaux
Travail à faire
Ajoutez au sc_main le nécessaire pour relier l’entrée du module foobar à un signal d’horloge périodique.
Tracez ensuite l’état de tous les signaux (internes et externes).
Les exports
sc_export
SystemC 2.2 ajoute les export qui permettent d’accéder à un signal interne à partir de l’extérieur d’un
module.
Sûr et transparent
On peut connecter un export directement à un port.
Pour appeler directement les méthodes du signal (read, write) à travers un sc_export, on doit utiliser
l’opérateur ->.
Un sc_export peut aussi être connecté à un sc_port. Le port est alors connecté (bind) au signal exporté.
La lecture ou l’écriture dans le port appellera donc directement les méthodes définies dans le signal.
48
Fig. 4.3: Exports et Ports
Exemple :
Attention Pour complètement comprendre l’exemple, vous aurez besoin de lire le chapitre sur les processus.
#include <systemc.h>
SC_MODULE(A)
{
sc_in<bool> in;
SC_CTOR(A):in(”in”)
{
SC_METHOD (trigger);
dont_initialize();
sensitive « in;
}
void trigger()
{
cout « name() « ”: triggered @” « sc_time_stamp() « endl;
}
};
SC_MODULE (C)
{
sc_export <sc_signal<bool> > inter;
49
inter(sig);
SC_THREAD(test);
sig = false;
}
void test()
{
for(int i=0; i<9;i++) {
wait(15, SC_NS);
sig = !sig;
}
sc_stop();
}
private:
sc_signal <bool> sig;
};
[Link]([Link]);
cout
« ”let's start...” « endl
« ”Initial: ” « [Link]->read()
« endl;
sc_start ();
cout « ”...done” « endl
« ”Final: ” « [Link]->read()
« endl;
return 0;
}
50
5 Évènements et processus
Les évènements
Les sc_event
En SystemC, les évènements sont des objets de type sc_event.
Quand un évènement est instancié, il s’ajoute à la liste des évènements “surveillés” par le simulateur.
On a rarement besoin de les instancier directement dans le code. Ils sont généralement instanciés par les
objets qui en ont besoin.
void notify();
void notify( const sc_time& );
void notify( double, sc_time_unit );
Sinon, la notification est programmée (schedule) pour avoir lieu plus tard. L’argument correspondant alors
au délai par rapport au temps actuel de la simulation.
Si le temps fourni en argument est nul (ou égal à SC_ZERO_TIME) la notification se fera à la fin du delta.
51
À l’élaboration, si besoin, il est alloué et ajouté à la liste des évènements du simulateur.
Quand le signal change de valeur, l’évènement est notifié pour la fin du delta.
template<typename T>
class simple_sc_signal
{
T cur_val;
T new_val;
// L'évènement est initialisé à NULL dans le constructeur
// Si besoin durant la phase d'élaboration il sera alloué
sc_event * m_event;
public:
Pour un signal on peut récupérer une référence vers l’évènement en utilisant l’une des deux méthodes :
• value_changed_event()
• default_event()
Pour un sc_signal<bool> en plus des évènements génériques, on peut récupérer des évènements parti-
culiers en cas de fronts montants ou descendants. Les méthodes permettant d’accéder à ces évènements
sont :
52
• posedge_event()
• negedge_event()
Les processus
Les processus
En SystemC il existe deux types principaux de processus :
• Les SC_THREAD
• Les SC_METHOD
Ces méthodes particulières doivent enregistrées en utilisant l’une des macros suivantes dans le constructeur
du module :
• SC_THREAD
• SC_CTHREAD (cas particulier de SC_THREAD)
• SC_METHOD
Pour pouvoir enregistrer une méthode d’un sc_module comme processus, elle :
Les SC_THREAD
• Les SC_THREAD sont des threads indépendants qui sont lancés au début de la simulation (au premier
sc_start()) dans le contexte du simulateur.
• C’est du multitâche collaboratif, les SC_THREAD doivent se mettre en veille pour permettre à la
simulation d’avancer.
• En veille, ils peuvent être réveillés par la notification d’un évènement.
Attention
Les SC_THREAD ne sont pas des threads du système. Ce sont des threads exécutés dans le contexte du
simulateur qui lui reste vu comme un processus unique.
53
Quand un SC_THREAD se termine (on arrive à la fin de la fonction, ou à un return) il n’est plus relancé. Si
on veut maintenir en vie un SC_THREAD on doit l’implémenter sous la forme d’une boucle infinie.
Déclarer un SC_THREAD
La macro SC_THREAD permet de faire cela.
Elle doit être utilisée dans le constructeur du sc_module. Elle prend comme unique argument la méthode
associée.
SC_MODULE(foo)
{
// La déclaration d'une méthode interne du module
void bar();
// Le constructeur du module
SC_CTOR(foo)
{
// Enregistrement de la méthode 'bar' comme sc_thread
SC_THREAD(bar);
}
};
• Si l’argument passé à wait est un temps (sous la forme d’un sc_time ou d’un couple double
plus unité) alors le processus est mis en veille immédiatement et son reveil est programmé dans
l’échéancier.
• Si l’argument est un évènement, le SC_THREAD est mis en veille jusqu’à la notification de ce processus.
• Si wait est appelé sans argument, le réveil se fera sur un des évènements de la liste de sensibilité
du SC_THREAD (voir section suivante).
54
Exemples
sc_time T(133,SC_NS);
SC_MODULE(foo)
{
sc_in<bool> a;
sc_in<int> b;
void bar()
{
// On attend un temps
wait(254,SC_NS);
...
// Ici aussi, on attend un temps
wait(T);
...
// On attend un front montant de a
wait(a.posedge_event());
...
// On attend que b change (si le port est connecté à un signal)
wait(b.default_event());
...
}
SC_CTOR(foo)
{
SC_THREAD(bar);
}
};
sensitive « e1 « e2 ;
sensitive est un champ des sc_module qui permet de définir la liste de sensibilité d’un processus.
L’opérateur de flux « a été surchargé pour ajouter un élément à la liste de sensibilité.
Exemple
55
SC_MODULE(foo)
{
sc_in<bool> a;
sc_in<int> b;
sc_in<int> c;
SC_CTOR(foo)
{
// Le thread bar est sensible aux évènements sur a, b et c
SC_THREAD(bar);
sensitive « a « b ;
sensitive « c ;
// Le thread lab est sensible à l'évènement ”front montant” de a
SC_THREAD(lab);
sensitive « [Link]();
}
void bar()
{
...
// attendre un évènement sur a, b ou c
wait();
...
}
void lab()
{
...
// attendre un front montant de ”a”
wait();
...
}
};
Une fois la liste de sensibilité définie, on peut utiliser la méthode wait() sans argument. Le SC_THREAD
est mis en veille en attente d’un des évènements de sa liste de sensibilité.
La liste de sensibilité peut contenir des ports en entrée ou des signaux internes du module. C’est l’évènement
par défaut (default_event()) du signal (ou du signal connecté au port) qui sera enregistré dans la liste
de sensibilité du processus.
Cas particulier
Pour les ports de types sc_in<bool>, on peut vouloir restreindre les évènements qui déclenchent le
processus aux fronts montants ou ascendants. Dans ce cas, on peut explicitement y faire référence en
utilisant les méthodes suivantes :
56
...
sc_in<bool> c;
...
SC_THREAD(...)
sensitive « [Link](); // sensible aux fronts montants sur c
sensitive « [Link](); // sensible aux fronts descendants sur c
• reset_signal_is
• async_reset_signal_is
Le signal utilisé pour la remise à zéro doit être de type bool. Il peut être déclaré en interne ou être connecté
à un port du module.
Elles prennent deux arguments, le signal et la polarité (positive ou négative) pour laquelle le reset est actif
sous la forme d’un booléen (true,false).
Un signal de remise à zéro asynchrone fait automatiquement partie de la liste de sensibilité. S’il change
d’état son effet est immédiat.
Un signal de remise à zéro synchrone ne fait pas partie de la liste de sensibilité. Son effet ne sera visible
que quand le processus sera réactivé.
L’utilisation des signaux de remise à zéro sous-entend un certain style de codage comme le montre l’exemple
suivant :
57
SC_MODULE(foo)
{
sc_in<bool> c;
sc_in<bool> r;
void bar()
{
// Ce qu'on fait au début et à chaque fois que r passe à true
...
// Fonctionnement normal dans une boucle infinie
for(;;)
{
// ce qu'on fait à chaque front montant de ”c”
...
}
}
SC_CTOR(foo)
{
SC_THREAD(bar);
sensitive « [Link]();
async_reset_signal_is(r,true);
}
};
La première partie du processus est exécutée au démarrage initial du processus puis à chaque fois que
le signal de remise à zéro est actif. En suite, le processus entre dans une boucle infinie dans laquelle on
retrouve le fonctionnement “normal”.
Attention, si le processus se termine (fin de la fonction, return…) il ne pourra plus être redémarré même si
un signal de remise à zéro a été défini.
On doit y associer un signal d’horloge qui ferra automatiquement partie de sa liste de sensibilité.
Exemple
SC_MODULE(foo)
{
sc_in<bool> clk;
58
// La déclaration d'une méthode interne du module
void bar();
// Le constructeur du module
SC_CTOR(foo)
{
// Enregistrement de la méthode 'bar' comme sc_cthread
// Elle sera déclanchée sur les fronts montants de clk
SC_CTHREAD(bar, [Link]());
}
};
Exemple
SC_MODULE(foo)
{
sc_in<bool> clk;
int lat;
void bar()
{
// On un cycle d'horloge
wait();
...
// Ici aussi
wait();
...
// On attend 33 cycles d'horloge
wait(33);
...
// On attend ”lat” cycles
wait(lat);
...
}
59
SC_CTOR(foo)
{
SC_CTHREAD(bar, [Link]());
}
};
Pour le reste le comportement d’un SC_CTHREAD est équivalent à celui du SC_THREAD.
Les SC_METHOD
Second type de processus, les SC_METHOD sont équivalentes aux processus des autres HDL. Elles per-
mettent de faire d’écrire des représentations “RTL”.
Contrairement aux SC_THREAD les SC_METHOD ne sont pas autonomes. Elles sont exécutées par le scheduler
quand un évènement de leur liste de sensibilité est notifié.
Elles doivent s’exécuter entièrement et rendre la main au scheduler sans quoi la simulation est bloquée.
Appeler la méthode wait() dans une SC_METHOD est donc interdit.
Exemple
SC_MODULE(foo)
{
// La déclaration d'une méthode interne du module
void bar();
// Le constructeur du module
SC_CTOR(foo)
{
// Enregistrement de la méthode 'bar' comme sc_method
SC_METHOD(bar);
}
};
60
Exemple
SC_MODULE(foo)
{
sc_in<int> a;
sc_in<int> b;
sc_out<int> c;
SC_CTOR(foo)
{
// La méthode bar est sensible aux évènement sur a et b
SC_METHOD(bar);
sensitive « a « b ;
}
Les SC_METHOD sont toujours exécutées du début à la fin. Le teste de la condition de reset se fait alors
dans le code de la méthode.
Exemple
SC_MODULE(foo)
{
sc_in<bool> c;
sc_in<bool> r;
void bar()
{
61
// Ce qu'on fait au début et à chaque fois que r passe à true
if (r)
{
...
}
// ce qu'on fait à chaque front montant de ”c”
else
{
...
}
}
SC_CTOR(foo)
{
SC_METHOD(bar);
sensitive « [Link]();
reset_signal_is(r,true);
}
};
La méthode dont_initialize
Par défaut, tous les processus sont exécutés au début de la simulation avant le moindre évènement. Si ce
comportement n’est pas désiré, il faut appeler la méthode dont_initialize() après l’enregistrement du
processus.
Pour avoir un comportement équivalent à celui d’une simulation Verilog/SystemVerilog, il faut appeler
dont_initialize() pour toutes les SC_METHOD.
SC_MODULE(foo)
{
sc_in<int> a;
sc_in<int> b;
sc_out<int> c;
SC_CTOR(foo)
{
// La méthode bar est sensible aux évènement sur a et b
62
SC_METHOD(bar);
sensitive « a « b ;
// attendre le premier évènement avant de faire le premier calcul
dont_initialize();
}
SC_CTOR(foo)
{
SC_THREAD(bar);
dont_initialize();
}
Résumons
63
Fig. 5.1: Les processus
64
Travail à faire
Modéliser
En reprenant le classe Pixel définie dans la section sur les signaux écrivez les modèles suivants :
Intégrez ces modules dans un sc_main permettant de vérifier le fonctionnement et générer les chrono-
grammes.
65
6 Niveaux d’abstraction et raffinement
Les SC_METHOD correspondent aux processus qu’on trouve dans les autres langages HDL. Elles permettent
donc de modéliser au niveau RTL.
Pour des modélisations plus haut niveau, les SC_THREAD doivent être utilisés. Ils permettent de passer
d’une modélisation fonctionnelle à une modélisation précise au bit et cycle près (CABA). Les SC_THREAD
permettent de faire ces modifications avec le minimum de modification.
Étapes de raffinement
1. Décrire l’algorithme
2. Écrire une version fonctionnelle
3. Définir l’interface du module
• les entrées/sortie
• un éventuel protocole
4. Encapsuler la fonction dans un SC_THREAD
• la version fonctionnelle servant de référence
5. Ajouter une information sur le temps en ajoutant des wait
• en nombre de cycles ou en temps absolu
6. Refaire une version RTL… en utilisant des SC_METHOD
• la version précédente servant de référence
Une fonction logicielle prend des arguments et renvoie un résultat. Un module matériel a des entrées et des
sorties. En plus, un protocole particulier peut être utilisé pour indiquer que les entrées/sorties sont prêtes.
66
return xx;
}
La première chose à faire est donc de définir un sc_module dont les entrées/sortie correspondent au
protocole qui sera utilisé. En suite, la fonction peut être encapsulée dans un SC_THREAD.
SC_MODULE(tt) {
sc_in<int> i;
sc_in<int> j;
...
sc_out<int> xx;
void mthread()
{
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
xx = f(i,j,..)
// prévenir que la sortie est prête
}
SC_CTOR()
{
SC_THREAD(mthread);
...
}
}
Dans ce SC_THREAD on attend que les entrées soient prêtes, puis on appelle juste la fonction que nous
voulons modéliser. Ceci permet d’avoir un premier modèle fonctionnel et respectant le protocole prévu. Par
contre, aucune notion de temps n’existe pour l’instant.
Dans un vrai module matériel, l’exécution de la fonction prend du temps (nombre de cycles dans une
implémentation séquentielle, par exemple). Si on veut ajouter une notion de temps à notre modèle, on peut,
dans un SC_THREAD appeler la méthode wait() entre l’appel à de la fonction et le renvoie du résultat. Le
SC_THREAD se mettra alors en veille et le résultat ne sera disponible qu’au bout du temps précisé.
Par exemple
SC_MODULE(tt) {
sc_in<int> i;
sc_in<int> j;
...
sc_out<int> xx;
void mthread()
{
67
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
xx = f(i,j,..)
// Ajouter la latence
wait(xx,SC_NS);
// prévenir que la sortie est prête
}
SC_CTOR()
{
SC_THREAD(mthread);
...
}
}
Pour la logique séquentielle, les temps de mise en veille du processus doivent être des multiples de la
période d’horloge. De plus, pour garantir le synchronisme, ils doivent dépendre de l’activité d’un signal
d’horloge. Dans ce cas, les SC_CTHREAD peuvent simplifier certaines écritures.
Par exemple
SC_MODULE(tt) {
// L'horloge
sc_in<bool> clk;
// Les autres I/O
sc_in<int> i;
sc_in<int> j;
...
sc_out<int> xx;
void mthread()
{
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
xx = f(i,j,..)
// Ajouter la latence
for (int i=0, i<LAT, i++)
wait();
// prévenir que la sortie est prête
}
68
SC_CTOR()
{
SC_THREAD(mthread);
sensitive « [Link]();
...
}
}
Par exemple avec un SC_CTHREAD
SC_MODULE(tt) {
// L'horloge
sc_in<bool> clk;
// Les autres I/O
sc_in<int> i;
sc_in<int> j;
...
sc_out<int> xx;
void mthread()
{
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
xx = f(i,j,..)
// Ajouter la latence
wait(LAT);
// prévenir que la sortie est prête
}
SC_CTOR()
{
SC_CTHREAD(mthread, [Link]());
...
}
}
Pour augmenter la précision du modèle, il faut pouvoir tracer temporellement l’état des variables internes
de la fonction modélisée. Pour cela, il faut pouvoir insérer des attentes (wait) dans le corps de la fonction
elle-même. Il suffit pour cela que la fonction soit elle-même une méthode du sc_module
Par exemple
69
SC_MODULE(tt) {
// L'horloge
sc_in<bool> clk;
// Les autres I/O
sc_in<int> i;
sc_in<int> j;
...
sc_out<int> xx;
SC_CTOR()
{
SC_CTHREAD(mthread, [Link]());
...
}
}
70
Exemple pratique, Calculer le PGCD
Choix de l’algorithme
Nous voulons modéliser un module matériel calculant le PGCD de deux nombres entiers.
L’algorithme d’Euclide peut être utilisé pour cela. Il existe deux variantes de cet algorithme utilisant :
Pour une implémentation matérielle, nous utiliserons la variante avec des soustractions car elle utilisera
moins de ressources.
Travail à faire :
Écrire une fonction qui prend deux entiers non signés et qui renvoie leur PGCD en utilisant la variante en
soustraction successive de l’algorithme d’Euclide.
• Les données sont des données non signées représentées sur 8 bits.
• Des signaux de contrôle sont ajoutés :
– le signal valid passe à 1, durant 1 cycle, quand les entrées sont prêtes,
– le signal ready passe à 1, durant 1 cycle, quand le résultat est prêt,
– si valid passe à 1 durant le calcul, le comportement est indéterminé.
– le résultat n’a pas à être maintenu au-delà du cycle durant lequel ready est à 1
Travail à faire
71
Fig. 6.1: Interface du module PGCD
Modèle temporel
Modifiez le modèle pour avoir un comportement temporel réaliste.
Le nombre d’étapes de calcul pour calculer le PGCD en utilisant l’algorithme d’Euclide dépend des données
en entrée.
Dans une implémentation séquentielle simple, chaque étape de calcul peut se faire en un cycle d’horloge.
Travail à faire
1. Modifiez le modèle précédent pour que le comportement temporel soit plus réaliste.
• Nous voulons obtenir un modèle CABA.
2. Le test précédent devrait fonctionner sans modification.
3. Générez les traces.
Modèle RTL
Cette figure représente le chemin de donnée d’une implémentation possible de cet algorithme.
Travail à faire
72
Fig. 6.2: Datapath du PGCD
73
1. En utilisant exclusivement des SC_METHOD proposez une implémentation RTL du module calculant
le PGCD.
• Séparez le chemin de donnée du contrôle
• Attention, il ne faut pas produire un modèle structurel
2. Ce module doit avoir une interface compatible avec les versions précédentes.
• Vous devriez pouvoir tester en parallèle la CABA
• Observez-vous de différences temporelles ? Expliquez-les.
3. Générez les traces.
74
7 La Co-simulation
Utiliser SyetemC pour la simulation et la vérification de modèle RTL écrits dans d’autres langages.
Les modèles RTL sont généralement écrits en utilisant les sous-ensembles synthétisables des langages
HDL communs (VHDL, Verilog/SystemVerilog)
Même dans ce cas, SystemC peut être utilisé pour concevoir un environnement de simulation pour vérifier
et valider le modèle RTL.
• C++ est un langage complexe pas forcément maitrisé par les concepteurs HDL,
• il existe des alternatives (DPI/VPI) pour utiliser des bibliothèques système dans un environnement
de simulation en SystemVerilog,
• certaines méthodologies de vérification font référence dans l’industrie et elle n’utilisent pas forcement
SyetemC.
• les concepts manipulés doivent pouvoir s’exprimer dans les deux langages,
• l’outil (simulateur) doit les supporter et permettre de passer de l’un à l’autre.
75
Si on doit faire une simulation évènementielle, les langages utilisés doivent prévoir les concepts :
• d’évènements,
• de processus.
De plus, on doit pouvoir représenter la structure des modules simulés dans les deux langages.
Les langages RTL communément utilisés (VHDL, Verilog/SystemVerilog) ont été conçus autour de ces
concepts. SystemC ajoute ces concepts à C++.
De plus, il faut pouvoir exprimer les données manipulées avec la bonne granularité (entier, bits, vec-
teurs/bus…) dans les deux langages. Il n’est généralement pas possible de faire passer d’un langage vers
l’autre des structures de données plus complexes (interfaces SystemVerilog, des structures…).
Testbench en SystemC
Ce que nous allons faire ici c’est simuler un design HDL décrit en Verilog/SystemVerilog dans un environne-
ment en SystemC.
Cet environnement contiendra de quoi générer des stimuli et vérifier les réponses du bloc HDL (DUT :
Design Under Test).
Il serait aussi envisageable d’insérer un module SystemC dans un banc de test (testbench) écrit dans un
autre langage, mais ce ne sera pas présenté dans ce cours.
Mise en pratique
Deux approches
Nous allons voir deux exemples de simulation d’un module RTL (Verilog/SystemVerilog) dans un testbench
écrit en SyetemC.
Deux approches :
76
Fig. 7.1: Co-simulation
77
Fig. 7.2: Foreign Module
1. En utilisant Modelsim, un simulateur commercial qui supporte l’utilisation de plusieurs langages HDL,
nous pouvons compiler séparément les différents fichiers sources. La seule contrainte viendra du
fait que le code SystemC étant du C++ il faudra d’une que les modules HDL soient définis d’une
scertaine façon au moment de la compilation des fichiers C++ qui y font référence. Le simulateur
s’occupera par la suite de faire communiquer l’ensemble de façon transparente.
2. En utilisant Verilator un outil qui permet de générer des modèles C++ et SystemC à partir de modèles
en Verilog/SystemVerilog. C’est un outil libre (OpenSource) destiné à générer des modèles de
simulation à partir de modèles RTL synthétisables. Le tout est ensuite compilé et le simulateur intégré
à la bibliothèque SystemC peut alors être utilisé.
Modelsim
Module “étranger”
Avec Modelsim on doit utiliser un module wrapper SystemC qui est compilé avec le testbench. Ce module
est une coquille vide qui ne contient pas de code comportemental, mais doit :
78
• reprendre l’interface du module HDL (les entrées/sorties) pour permettre de faire les connexions,
• utiliser un mécanisme (foreign module) du simulateur pour le lier au module HDL pour mettre en
relation le wrapper avec le module HDL durant la phase d’élaboration.
Ce wrapper peut être écrit à la main ou généré automatiquement après compilation du module HDL
(scgenmod --help pour plus d’informations.)
Exemple (HDL) :
module counter ( input clk, nrst, enable,
output logic[7:0] Q
);
// ...
endmodule
Exemple (Wrapper) :
#ifndef _SCGENMOD_counter_
#define _SCGENMOD_counter_
#include ”systemc.h”
79
~counter()
{}
};
#endif
Contrairement à un sc_module, ici on hérite d’un sc_foreign_module qui est une classe propre à
Modelsim.
L’interface de ce module (ses entrées/sorties) correspondent à celles du module HDL. Les types qui existent
dans les deux langages ne sont pas les même, Modelsim (se référer à la documentation de l’outil) prévoit
plusieurs possibilités. Dans cet exemple, on fait correspondre aux bits Verilog des bool et aux vecteurs des
sc_uint<> mais nous aurions pu utiliser respectivement des sc_logic et des sc_lv.
Le constructeur de ce module prend 2 arguments, l’un correspondant au nom de l’instance
(sc_module_name) au sens SystemC et l’autre correspondant au nom du module HDL auquel il
doit être lié. Sinon, le constructeur ne fait qu’appeler une méthode elaborate_foreign_module qui
déclenchera un mécanisme interne du simulateur pour l’élaboration du module RTL.
Exemple (Instanciation) :
#include <systemc.h>
#include ”counter.h”
Ensuite, le module wrapper peut être instancier et utilisé comme un module SystemC standard.
80
Fig. 7.3: Foreign Module
Workflow Modelsim
La génération du module wrapper peut être automatisée en utilisant la commande scgenmod de Modelsim
après compilation du module HDL. scgenmod possède aussi des options pour préciser les correspondances
entre les types HDL et SystemC.
La commande de Modelsim pour compiler des fichiers SystemC est sccom. Elle utilise en interne une
version de g++ fournie avec l’outil. Aussi, sccom --link doit être appelé après la compilation pour l’édition
de liens.
sccom définit la macro MTI_SYSTEMC qui peut être testée pour écrire du code spécifique pour Modelsim.
81
Verilator
Convertir le Verilog en C++/SystemC
Utiliser un “convertisseur” qui converti le Verilog en C++/SystemC :
Verilator est un outil qui analyse du code HDL (seulement Verilog ou SystemVerilog) et qui génère un AST
(Abstract Syntaxic Tree).
• générer un modèle SystemC ou C++ (qui utilise son propre modèle de simulation),
• mettre en œuvre un certain nombre d’optimisations :
– simplification de logique combinatoire,
– propagation de constantes,
– faire les calculs sur des tailles adaptées aux processeurs…
Il a été développé pour générer des simulateurs rapides à partir de modèles RTL.
C’est un outil libre dont le code source est distribué sous les termes de la licence LGPLv3. Le code source
est disponible ici et des versions binaires sont disponibles dans la majorité des distributions Linux.
Verilator permet donc de générer un modèle de simulation SystemC à partir d’un module Verilog ou
SystemVerilog.
Le module généré peut alors être intégré dans une simulation SystemC standard.
Workflow Verilator
1. On utilise Verilator pour générer des modèles SystemC à partir des modules Verilog/SystemVerilog.
2. L’ensemble des sources est compilé en utilisant le compilateur C++ natif.
3. En plus de la bibliothèque SystemC, l’édition de liens doit utiliser certaines bibliothèques fournies
avec l’outil.
82
Fig. 7.4: Convertir le Verilog en SystemC
83
Exemple (Conversion) :
Pour le module Verilog précédent (counter) on obtient le module SystemC Vcounter :
#ifndef _Vcounter_H_
#define _Vcounter_H_
#include ”systemc.h”
#include ”verilated_sc.h”
#include ”verilated.h”
SC_MODULE(Vcounter) {
public:
sc_in<bool> clk;
sc_in<bool> nrst;
sc_in<bool> enable;
sc_out<sc_uint<8> > Q;
....
}
#endif
Ce modèle peut être instancié directement dans un autre module SystemC ou directement dans le sc_main.
Pour obtenir ce module verilator est utilisé avec les options suivantes :
En réalité, le module généré est un peu plus complexe et contient des références à des objets internes de
l’outil.
#ifndef _Vcounter_H_
#define _Vcounter_H_
84
#include ”systemc.h”
#include ”verilated_sc.h”
#include ”verilated.h”
class Vcounter__Syms;
//----------
SC_MODULE(Vcounter) {
public:
// PORTS
// The application code writes and reads these signals to
// propagate new values into/out from the Verilated model.
sc_in<bool> clk;
sc_in<bool> nrst;
sc_in<bool> enable;
sc_out<sc_uint<8> > Q;
// LOCAL SIGNALS
// Internals; generally not touched by application code
// LOCAL VARIABLES
// Internals; generally not touched by application code
VL_SIG8(__Vcellout__counter__Q,7,0);
// INTERNAL VARIABLES
// Internals; generally not touched by application code
Vcounter__Syms* __VlSymsp; // Symbol table
// PARAMETERS
// Parameters marked /*verilator public*/ for use by application code
// CONSTRUCTORS
private:
VL_UNCOPYABLE(Vcounter); ///< Copying not allowed
public:
SC_CTOR(Vcounter);
virtual ~Vcounter();
// API METHODS
private:
void eval();
85
public:
void final();
// INTERNAL METHODS
private:
static void _eval_initial_loop(Vcounter__Syms* __restrict vlSymsp);
public:
void __Vconfigure(Vcounter__Syms* symsp, bool first);
private:
static QData _change_request(Vcounter__Syms* __restrict vlSymsp);
void _ctor_var_reset();
public:
static void _eval(Vcounter__Syms* __restrict vlSymsp);
private:
#ifdef VL_DEBUG
void _eval_debug_assertions();
#endif // VL_DEBUG
public:
static void _eval_initial(Vcounter__Syms* __restrict vlSymsp);
static void _eval_settle(Vcounter__Syms* __restrict vlSymsp);
static void _settle__TOP__1(Vcounter__Syms* __restrict vlSymsp);
} VL_ATTR_ALIGNED(128);
#endif // guard
Par défaut, ce fichier d’en-tête, ainsi qu’un fichier C++, sont générés dans un sous-répertoire obj_dir. La
méthode eval est l’unique méthode SystemC (SC_METHOD) dans laquelle se trouve tout le comportement du
module RTL. L’observation des signaux interne n’est pas donc possible par défaut pour obtenir la meilleure
performance.
On peut aussi demander à Verilator de générer un Makefile pour automatiser la compilation. Par exemple :
verilator --help
Travail à faire :
Reprendre l’exemple du PGCD.
86
Voici le code SystemVerilog d’un module RTL calculant le PGCD de deux nombres codés sur 8 bits. Il
possède la même interface et le même comportement que le module demandé dans le chapitre “Niveaux
d’abstraction et raffinement”.
module PGCD (
input clk,
input valid,
input [7:0] a,
input [7:0] b,
output ready,
output [7:0] pgcd
);
endmodule
• Le sc_main doit instancier un module testeur qui génère des séquences de test et vérifie la validité
du résultat renvoyé.
• Le code du sc_main doit être compatible avec les deux outils.
• Bonus écrire un Makefile qui automatise la compilation dans les deux cas.
87
8 Les canaux de communication
L’interface de ces canaux est séparée de la définition de leur comportement pour permettre de faire évoluer
les canaux de communication sans modifier ce qui se trouve des deux cotés du canal.
La séparation entre la définition de l’interface et son comportement se fait en utilisant le concept d’interfaces
de la programmation orientée objet.
• On définit dans l’interface la liste des méthodes (fonctions) que doit implémenter le canal
• On implémente dans le canal les méthodes de l’interface.
En C++, le concept d’interface utilise ce qu’on appelle des classes abstraites (virtuelles pures).
Exemple
#include <iostream>
using namespace std;
class MyInterface {
public:
// ”=0” veut dire implémentation obligatoire
virtual void hello() = 0;
};
88
class CplxImpl : virtual public MyInterface {
public:
virtual void hello() override
{
cout « ”Hi ” « message « endl;
}
CplxImpl(string s): message(s) {}
private:
const string message;
};
int main()
{
// MyInterface x; // ceci est une erreur car la classe est abstraite
MyInterface * o[2];
return 0;
}
Pour garder l’isolation entre les éléments d’une hiérarchie, les ports doivent permettre de rendre accessible
les méthodes des interfaces dans un module.
• sc_interface.
• sc_prim_channel
• sc_port
sc_interface
Définit le minimum de méthodes à implémenter pour fonctionner avec le simulateur :
Toute interface doit hériter de ce type pour pouvoir s’intégrer dans une simulation.
89
sc_prim_channel
Définit en plus les méthodes permettant l’interaction avec le moteur de simulation :
sc_port
Permet de déclarer un port pour une interface particulière. C’est une classe template dont l’un des paramètres
est l’interface utilisée. Les autres paramètres correspondent au nombre de canaux qu’on peut y connecter
(par défaut exactement 1).
Exemple d’utilisation
Prenons le temps de regarder le code de l’exemple.
#include <systemc.h>
90
public:
SC_CTOR(tagged_bus) {
tag = 0;
cur_d = 0;
new_d = 0;
}
// dans tag_write_if
virtual void write(sc_uint<8> i) {
new_d = i;
// on demande à ce qu'update soit appelé
request_update();
}
// dans tag_read_if
virtual sc_uint<16> read() {
return (tag,cur_d);
}
// dans sc_interface
virtual const sc_event& default_event() const {
return m_ev;
}
// dans sc_prim_channel
virtual void update() {
if (cur_d != new_d) {
cur_d = new_d;
tag++;
m_ev.notify(SC_ZERO_TIME);
}
}
};
SC_MODULE(W) {
// un port en sortie n'implémente que l'interface d'écriture
sc_port<tag_write_if> p_o;
SC_CTOR(W) {
SC_THREAD(loop);
}
void loop () {
sc_uint<8> v = 1;
while(v) {
// on appelle la méthode write de l'interface
p_o->write(v);
v = v«1;
wait(10, SC_NS);
91
}
}
};
SC_MODULE(R) {
// un port en entrée n'implémente que l'interface de lecture
sc_port<tag_read_if> p_i;
SC_CTOR(R) {
SC_METHOD(loop);
// Utilise le default_event
sensitive « p_i;
}
void loop () {
// on appelle la méthode read de l'interface
sc_uint<16> t = p_i->read();
cout « name() «” --> tag: ” « t(15,8) « ” val: ” « sc_bv<8>(t(7,0))« endl;
}
};
tagged_bus b(”t_bus”);
W w(”writer”);
w.p_o(b);
R r(”reader”);
r.p_i(b);
sc_start();
return 0;
}
92
L’interface
Pour les signaux les deux interfaces suivantes sont définies :
Ces deux interfaces sont définies comme des classes template, où le template T est le type des données
transporté.
• sc_signal_in_if<T> : définit la méthode read() ainsi que les méthodes renvoyant les évène-
ments.
• sc_signal_inout_if<T> : la complète en ajoutant entre autres la méthode write()
Des spécialisations de ces interfaces existent pour les types bool et sc_logic. Elles ajoutent ce qu’il faut
pour les évènements sur front.
Ces ports spécialisés ajoutent des surcharges d’opérateurs pour permettre simplement d’écrire et de lire
dans le signal. Aussi, on ne peut y connecter qu’un seul signal.
Le sc_signal
Est défini comme :
template <class T,
sc_writer_policy WRITER_POLICY = SC_ONE_WRITER>
class sc_signal
: public sc_signal_inout_if<T>, public sc_prim_channel
{ ... };
93
Des canaux standards
En plus des sc_signal, dans la bibliothèque, un certain nombre de canaux standards sont définis.
Les sc_buffer<T>
Un sc_buffer est équivalent à un sc_signal et implémente la même interface.
La seule différence, vient du fait que pour un sc_buffer il y a un évènement notifié à chaque écriture alors
que pour un sc_signa il faut que la valeur change.
Travail à faire :
Les sc_fifo<T>
Permet d’instances des fifos dont la taille est définie à l’instanciation.
• sc_fifo_in_if<T>
• sc_fifo_out_if<T>
• sc_fifo_in
• sc_fifo_out
• bool nb_read(&T) : lecture non bloquante qui renvoie true si elle réussit.
• void read(&T) et T read() : lecture bloquante (appelle wait() si la fifo est vide).
• int num_available() : qui renvoie le nombre d’éléments disponibles dans la fifo.
• sc_event& data_written_event() : renvoie une référence vers évènement notifié en cas d’écri-
ture.
• bool nb_write(&T) : écriture non bloquante qui renvoie true si elle réussit.
• void write(&T) : écriture bloquante (appelle wait() si la fifo est pleine)
• int num_free() : renvoie le nombre d’éléments pouvant être écrits dans la fifo.
• sc_event & data_read_event() : une référence vers un évènement notifié en cas de lecture.
94
Attention comme les méthodes bloquantes de lecture et d’écriture font appel à wait() pour suspendre le
processus qui les appelle, il faut donc prendre des précautions quand on les utilise dans des SC_METHOD.
Le constructeur d’une sc_fifo<T> prend en argument la taille de la fifo. La taille par défaut est de 16. De
plus l’opérateur d’affectation a été surchargé pour appeler les méthodes bloquantes de lecture et d’écriture.
Exemple :
#include <systemc.h>
SC_MODULE(A) {
sc_fifo_out<int> out;
void loop() {
int i = 0;
for(;;){
// write est obligatoire
[Link](i);
i++;
wait(33, SC_NS);
}
}
SC_CTOR(A) { SC_THREAD(loop); }
};
SC_MODULE(B) {
sc_fifo_in<int> in;
void loop() {
wait(300, SC_NS);
for(;;){
// [Link]() appelle in->read()
cout « ”Lecture 1 : ” « in->read() « ”@ ” « sc_time_stamp() « endl;
cout « ”Lecture 2 : ” « [Link]() « ”@ ” « sc_time_stamp() « endl;
95
wait(55, SC_NS);
}
}
SC_CTOR(B) { SC_THREAD(loop); }
};
A a(”modA”);
[Link](fifo);
B b(”modB”);
[Link](fifo);
sc_start(300,SC_NS);
cout
« ”contenu de la fifo @” « sc_time_stamp() « endl
« fifo « endl;
sc_start(1,SC_US);
cout
« ”contenu de la fifo @” « sc_time_stamp() « endl
« fifo « endl;
return 0;
}
Travail à faire :
Écrire le code de deux modules s’échangeant des Pixels à travers une fifo. Les modules devront fonctionner
à des cadences différentes tout en étant synchrones à une horloge clk générée au niveau supérieur. Par
exemple :
• Producteur : envoie, une à une, 100 données en attendant un cycle entre chaque envoi puis s’arrête
pendant un nombre aléatoire de cycles compris entre 0 et 32.
• Consommateur : lit une donnée puis attend 2 cycles si la somme des composantes du pixel lu est
paire ou 3 cycles elle est impaire.
• Observez le comportement pour différentes profondeurs de fifo.
Faites deux implémentations du producteur, l’une utilisant des SC_THREAD ou SC_CTHREAD et l’autre utilisant
des SC_METHOD.
96
Les sc_mutex et sc_semaphore
La bibliothèque définit d’autres canaux standards permettant la synchronisation de SC_THREAD pour l’accès
à des ressources partagées.
• int lock() : pour verrouiller le mutex. S’il est déjà verrouillé, on est mis en attente (wait() est
appelée). Cette méthode renvoie toujours 0.
• int trylock() : pour essayer de verrouiller le mutex. Cette méthode renvoie 0 en cas de succès,
-1 sinon.
• int unlock() : pour libérer le mutex. Cette méthode renvoie 0 en cas de succès, -1 sinon (i.e. le
mutex n’est pas verrouillé ou il appartient à un autre thread).
Attention
Il n’y a pas de ports dédiés à ces deux types de canaux. Si vous voulez utiliser ces canaux entre deux
modules, il faudra passer par un sc_port générique exposant l’interface de ces deux type de canaux
(respectivement sc_mutex_if et sc_semaphore_if).
Travail à faire :
Écrire le code d’un module dans lequel deux SC_THREAD concurrents s’exécutent à tour de rôle en utilisant
un sc_mutex.
sc_channel
SystemC définit la notion de canal hiérarchique. Un canal hiérarchique n’est en réalité qu’un sc_module qui,
en utilisant entre autres des sc_export, peut se présenter comme un canal de communication complexe.
On peut donc y trouver des processus et modéliser les comportements du canal de communication.
97
Pour définir explicitement un canal hiérarchique on utilisera la classe sc_channel qui n’est qu’un alias de
la classe sc_module.
Travail à faire :
En utilisant la classe sc_channel écrire le code d’un générateur d’horloge. Ce canal contiendra un
sc_signal qui pourra être connecté à l’entrée d’un module de test à travers un sc_export.
98
9 Mini-projet
Objectifs :
Le but de ce mini-projet est de vous faire modéliser un système de traitement vidéo, qui prend en entrée un
flux vidéo et qui lui applique au vol des transformées basiques.
On cherchera dans ce projet à être le plus efficace possible, en termes de ressources pour la simulation, de
types de variables/signaux, de complexité et de lisibilité du code.
Plan du projet
Le travail demandé pour ce mini-projet se décompose en trois phases :
Contexte
On cherche à modéliser et simuler un système de traitement vidéo, qui reçoit un flux vidéo en entrée, et
qui restitue en sortie un flux transformé au même format. Les transformations appliquées peuvent être
multiples :
Le format de flux vidéo choisi est une version simplifiée CCIR 601. En voici les grandes lignes (mais le web
regorge d’information à ce sujet) :
99
• Une image se compose d’environ 625 lignes, dont exactement 576 actives (les autres correspondent
au temps de retour trame).
Pendant la transmission de pixels actifs d’une ligne, un signal de synchronisation, HREF, est mis à 1.
Pendant le reste du temps, HREF vaut 0.
Au début d’une trame, un signal de synchronisation verticale, VREF, est mis à 1 pendant 3 lignes. Le front
montant de VREF coïncide avec celui de HREF.
Les pixels sont transmis à la cadence de 13.5Mhz, synchrones sur front montant de l’horloge.
Module d’entrée
Pour tester votre système, vous allez avoir besoin de modules d’entrée et de sortie vidéo :
• un module doit permettre de générer un flux vidéo à partir d’une séquence d’images,
• un autre module, doit permettre de générer des images à partir d’un flux pour vérifier visuellement le
résultat.
Pour cela, nous vous proposons d’utiliser une série d’images au format PNG. Les fonctions de la bibliothèque
libpng, une bibliothèque libre, permettront de lire et d’écrire simplement des images au format PNG.
On aurait pu utiliser d’autres bibliothèques pour lire directement des flux vidéo, mais cela aurait été un peu
plus compliqué.
Le module de génération de flux vidéo vous est fourni. Il est disponible dans le dépôt Git suivant :
git@[Link] :se207/[Link]
Vous pouvez, cloner le dépôt séparément et copier manuellement les fichiers dans votre dépôt personnel
ou le fusionner (merger) directement avec votre dépôt personnel comme suit :
Pour des raisons de simplicité on se limite à des images en niveaux de gris. Les pixels venant du générateur
de flux ne sont donc codés que sur 8bits.
100
Fig. 9.1: Module générant le flux
Vous pouvez simuler le système tel quel et observer les traces générées (simulation_trace.vcd). Prenez
le temps d’analyser ces chronogrammes et de vous familiariser avec le format pseudo-CCIR601.
Au travail :
module de sortie
Maintenant que vous avez un module qui vous fournit un flux vidéo, il serait sympa d’avoir un module qui
fait l’inverse, pour vérifier visuellement les transformations vidéo.
C’est le rôle d’un module que vous allez concevoir et vous appellerez video_out.
Le travail à faire :
• écrire un module de récupération du flux vidéo, qui crée des images PNG à partir du flux vidéo.
– Le nom du fichier généré doit dériver du nom du module une fois instancié.
• instancier dans votre environnement de test les deux modules video_in et video_out
• relier les deux modules directement entre eux et simuler le tout.
Les images de sorties doivent être les mêmes que celles d’entrée. Vous pouvez le vérifier en les affichant
successivement. Par exemple :
101
Fig. 9.2: Module récupérant le flux
102
Fig. 9.4: Testbench d’un module de filtrage
Attention :
Votre module video_out ne doit pas compter sur le fait que les trames font 625 lignes ni 864 pixels ! C’est
à lui de se synchroniser (à l’aide des signaux HREF et VREF) sur le flux entrant. Il peut par contre compter
sur le fait que dans chaque trame il y aura exactement 720×576 pixels valides.
Effets spéciaux
Le but du jeu est maintenant de construire un système qui va opérer, à la volée, sur un flux sortant du
premier module.
Vous avez d’ores-et-déjà un module qui fournit des flux vidéos et un module qui en génère des PNG. Reste
à écrire ce qui va s’interposer entre les deux.
• filtre moyenneur sur 8 pixels adjacents (pixel central + ses 8 voisins) : dans la mesure du possible,
ce filtre doit effectuer son traitement au vol.
• zoom ×2 : on fera un zoom centré, en dupliquant chaque pixel. Un traitement au vol est-il possible
et/ou adapté ?
Vérifiez visuellement que tout marche bien et que les signaux de synchronisation HREF et VREF respectent
bien le format initial.
Si tout fonctionne correctement, vous devriez pouvoir chainer les différents modules en les instanciant les
uns après les autres.
• Bonus : Le modèle du filtre moyenneur pourrait être utilisé pour tous les filtres agissant sur le même
type de voisinage 3×3 (Sobel, Gaussien…). Modifiez le filtre moyenneur pour pouvoir préciser les
paramètres du filtre à la construction du module.
103
Fig. 9.5: Testbench avec plusieurs modules de filtrage
104