Hiérarchie et modules
2023-2024
sc_module
Les modules sont les éléments principaux pour construire une description hiérarchique en SystemC.
Un module SystemC est une classe qui hérite de la classe
sc_module
struct mon_module : sc_module {
...
};
Un module ne peut être déclaré après le début de la simulation.
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:
...
};
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 :
(sc_module_name n):sc_module(n){ ... } mon_module
La méthode name()
permet de récupérer ce nom durant
l’exécution de la simulation.
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.
Deux macros sont définies pour simplifier cette déclaration :
SC_MODULE()
pour déclarer le moduleSC_CTOR()
pour définir le constructeurSC_MODULE(mon_module)
est équivalent à
struct mon_module : sc_module
.
SC_CTOR(mon_module)
est équivalent à
mon_module(sc_module_name n):sc_module(n)
On peut donc déclarant un module en écrivant :
(mon_module)
SC_MODULE{
// déclaration de méthodes et attributs
(mon_module)
SC_CTOR{
// Initialisation
}
};
Remarque : On verra par la suite que
SC_CTOR
ajoute une information supplémentaire.
Si un constructeur additionnel est ajouté il doit aussi appeler le
constructeur de la classe parent sc_module
Par exemple :
(sc_module_name n, int i):sc_module(n)
mon_module{
...
}
Dans ce cas, la macro SC_CTOR
ne peut plus être
utilisée.
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
.
Exemple d’utilisation de SC_HAS_PROCESS
(mon_module)
SC_MODULE{
// déclaration de méthodes et attributs
(sc_module_name n, int i):sc_module(n)
mon_module{
// Initialisation
}
(mon_module);
SC_HAS_PROCESS};
La déclaration des processus sera vue dans la suite du cours.
SystemC 3.0.0 exige au minimum C++17 et rend possible l’utilisation de macros variadiques et de simplifier l’écriture.
SC_CTOR
admet des paramètres additionnels,SC_HAS_PROCESS
devient inutile et ne doit plus être
utilisée.On peut donc avec SystemC 3.0.0 (IEEE 1666-2023) écrire:
(mon_module)
SC_MODULE{
// déclaration de méthodes et attributs
(mon_module, int i)
SC_CTOR{
// Initialisation
}
//\!/ DEPRECATED SC_HAS_PROCESS(mon_module);
};
sc_in
, sc_out
,
sc_inout
SystemC définit trois types de ports sous la forme de templates :
sc_in<T>
les entrées de type T
sc_out<T>
les sorties de type T
sc_inout<T>
les ports bidirectionnels de type
T
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.
À 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>
(foo)
SC_MODULE{
<bool> clk;
sc_in<bool> rst;
sc_in<sc_uint<8> > data;
sc_out
(foo):clk("clk"), data("data")
SC_CTOR{
cout<< "module : " << name() << endl
<< "clk : " << clk.name() << endl
<< "rst : " << rst.name() << endl
<< "data : " << data.name() << endl
;
}
};
int sc_main(int argc, char * argv[])
{
("foobar");
foo barreturn 0;
}
Ou en utilisant les constructions de C++11
:
#include <systemc.h>
(foo)
SC_MODULE{
<bool> clk {"clk"};
sc_in<bool> rst;
sc_in<sc_uint<8> > data {"data"};
sc_out
(foo)
SC_CTOR{
cout<< "module : " << name() << endl
<< "clk : " << clk.name() << endl
<< "rst : " << rst.name() << endl
<< "data : " << data.name() << endl
;
}
};
int sc_main(int argc, char * argv[])
{
("foobar");
foo barreturn 0;
}
Voir aussi la macro SC_NAMED
introduite avec
SystemC 2.3.3
et étendu en SystemC 3.0.0
.
Les ports SystemC doivent être connectés à des signaux avant de pouvoir les utiliser. Lire ou écrire dans un port non connecté entraine une erreur durant la simulation.
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.
Comme pour les signaux, il existe des ports pour les types résolus :
sc_in_resolved
, sc_out_resolved
et
sc_inout_resolved
sc_in_rv<N>
, sc_out_rv<N>
et
sc_inout_rv<N>
Ils doivent être connectés à des signaux du bon type.
sc_main
Les modules peuvent être instanciés dans la fonction
sc_main
.
Ils peuvent être connectés à des signaux déclarés au même niveau.
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>
(foo) {
SC_MODULE<bool> i {"i"};
sc_in(foo) {}
SC_CTOR};
(bar) {
SC_MODULE<bool> o {"o"};
sc_out(bar) {}
SC_CTOR};
int sc_main(int argc, char * argv[])
{
<bool> s("s");
sc_signal
("foo_i");
foo foo_i("bar_i");
bar bar_i
//foo_i.i(bar_i.o);// NON!
// connexion à travers le signal s
.i(s);
foo_i.o(s);
bar_i
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.
Les sous modules sont déclarés comme des attributs du module.
Les connexions se font alors dans le constructeur du module.
Un port peut être connecté soit à :
Exemple :
#include <systemc.h>
(foo) {
SC_MODULE<bool> i {"i"};
sc_in <bool> o {"o"};
sc_out(foo) { }
SC_CTOR};
(bar) {
SC_MODULE<bool> i {"i"};
sc_in <bool> o {"o"};
sc_out(bar) { }
SC_CTOR};
(foobar)
SC_MODULE{
// entrée/sortie
<bool> i {"i"};
sc_in <bool> o {"o"};
sc_out// interne
<bool> s;
sc_signal// sous modules
{"foo_i"};
foo foo_i {"bar_i"};
bar bar_i
(foobar)
SC_CTOR{
// connexions aux I/O
.i(i);
foo_i.o(o);
bar_i// connexion interne
.o(s);
foo_i.i(s);
bar_i}
};
int sc_main(int argc, char * argv[])
{
<bool> i("i");
sc_signal<bool> o("o");
sc_signal
("foobar");
foobar uut
.i(i);
uut.o(o);
uut
return 0;
}
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).
sc_export
SystemC 2.2 ajoute les export
qui permettent d’accéder à
un signal interne à partir de l’extérieur d’un module.
On peut connecter un export
directement à un
port
.
On a la garantie à la compilation qu’on propagera les même types.
Un sc_export
prend en argument de template, le type de
signal auquel il permettra d’accéder.
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.
Exemple :
Attention Pour complètement comprendre l’exemple, vous aurez besoin de lire le chapitre sur les processus.
#include <systemc.h>
(A)
SC_MODULE{
<bool> in;
sc_in
(A):in("in")
SC_CTOR{
(trigger);
SC_METHOD ();
dont_initialize<< in;
sensitive }
void trigger()
{
<< name() << ": triggered @" << sc_time_stamp() << endl;
cout }
};
(C)
SC_MODULE {
<sc_signal<bool> > inter;
sc_export
(C): inter("inter"), sig("sig")
SC_CTOR{
(sig);
inter(test);
SC_THREAD= false;
sig }
void test()
{
for(int i=0; i<9;i++) {
(15, SC_NS);
wait= !sig;
sig }
();
sc_stop}
private:
<bool> sig;
sc_signal
};
int sc_main(int argc, char* argv[])
{
("a");
A a("c");
C c
.in(c.inter);
a
cout<< "let's start..." << endl
<< "Initial: " << c.inter->read()
<< endl;
();
sc_start << "...done" << endl
cout << "Final: " << c.inter->read()
<< endl;
return 0;
}
© Copyright 2016-2024, Tarik Graba, Télécom Paris. | |
Le contenu de cette page est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International . | |
Ce document reprend des parties du cours en ligne sur SystemC d'Alexis Polti disponible ici. |