Les canaux de communication
2020-2021
En SystemC on peut définir des canaux de communication complexes.
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.
En C++, le concept d’interface utilise ce qu’on appelle des classes abstraites (virtuelles pures).
Exemple
Comment définir des interfaces en C++ ?
#include <iostream>
using namespace std;
class MyInterface {
public:
// "=0" veut dire implémentation obligatoire
virtual void hello() = 0;
};
class SimpleImpl : virtual public MyInterface {
public:
virtual void hello() override
{"Hi" << endl;
cout <<
}
};
class CplxImpl : virtual public MyInterface {
public:
virtual void hello() override
{"Hi " << message << endl;
cout <<
}
CplxImpl(string s): message(s) {}
private:
const string message;
};
int main()
{// MyInterface x; // ceci est une erreur car la classe est abstraite
2];
MyInterface * o[
0] = new SimpleImpl();
o[1] = new CplxImpl("folks");
o[
for (int i=0; i<2; i++)
o[i]->hello();
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.
Pour avoir un minimum d’interopérabilité, SystemC définit plusieurs types :
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.
sc_prim_channel
Définit en plus les méthodes permettant l’interaction avec le moteur de simulation :
wait()
…)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).
Prenons le temps de regarder le code de l’exemple.
#include <systemc.h>
// Une interface pour l'écriture
class tag_write_if : virtual public sc_interface
{public:
virtual void write(sc_uint<8> i) =0;
};
// Une interface pour la lecture
class tag_read_if : virtual public sc_interface
{public:
virtual sc_uint<16> read() =0;
};
// notre canal personnalisé, implémente les deux interfaces
// ce canal est semblable au sc_signal avec:
// - des données de taille fixe
// - un tag ajouté pour chaque nouvelle donnée
class tagged_bus : public tag_write_if, public tag_read_if, public sc_prim_channel
{8> tag;
sc_uint<8> cur_d, new_d;
sc_uint<m_ev;
sc_event
public:
SC_CTOR(tagged_bus) {0;
tag = 0;
cur_d = 0;
new_d =
}// 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 () {
8> v = 1;
sc_uint<while(v) {
// on appelle la méthode write de l'interface
p_o->write(v);1;
v = v<<10, SC_NS);
wait(
}
}
};
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
16> t = p_i->read();
sc_uint<" --> tag: " << t(15,8) << " val: " << sc_bv<8>(t(7,0))<< endl;
cout << name() <<
}
};
int sc_main(int argc, char * argv[])
{
"t_bus");
tagged_bus b(
"writer");
W w(
w.p_o(b);
"reader");
R r(
r.p_i(b);
sc_start();
return 0;
}
sc_signal
Les sc_signal
utilisent ces mêmes notions d’interfaces et canaux et de ports.
Pour les signaux les deux interfaces suivantes sont définies :
sc_signal_in_if<T>
: interface en lecturesc_signal_inout_if<T>
: interface en écritureCes 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ènements.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.
sc_in
, sc_out
sc_in
est équivalent à un sc_port<sc_signal_in_if<T>,1>
sc_out
et un sc_inout
sont équivalents à un sc_port<sc_signal_inout_if<T>,1>
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.
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
: { ... };
T
est le type transportéWRITER_POLICY
détermine le comportement du sc_signal
s’il est modifié par plusieurs processus. Par défaut, c’est interdit.En plus des sc_signal
, dans la bibliothèque, un certain nombre de canaux standards sont définis.
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 :
Écrire un exemple de code mettant en évidence la différence de comportement entre sc_signal
et sc_buffer
sc_fifo<T>
Permet d’instances des fifos dont la taille est définie à l’instanciation.
Elles implémentent les interfaces :
sc_fifo_in_if<T>
sc_fifo_out_if<T>
Des ports spéciaux sont aussi prévus :
sc_fifo_in
sc_fifo_out
L’interface sc_fifo_in_if<T>
fournit :
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’écriture.L’interface sc_fifo_out_if<T>
fournit :
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.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 :
// une fifo de 16 éléments
int> A;
sc_fifo<// une fifo de 32 éléments
int> B(32);
sc_fifo<
// deux écritures bloquantes
1);
A.write(1;
B =
// deux lectures bloquantes
int x;
x = A; x = B.read();
Exemple plus complet :
#include <systemc.h>
SC_MODULE(A) {int> out;
sc_fifo_out<void loop() {
int i = 0;
for(;;){
// write est obligatoire
out.write(i);
i++;33, SC_NS);
wait(
}
}
SC_CTOR(A) { SC_THREAD(loop); }
};
SC_MODULE(B) {int> in;
sc_fifo_in<void loop() {
300, SC_NS);
wait(for(;;){
// in.read() appelle in->read()
"Lecture 1 : " << in->read() << "@ " << sc_time_stamp() << endl;
cout << "Lecture 2 : " << in.read() << "@ " << sc_time_stamp() << endl;
cout << 55, SC_NS);
wait(
}
}
SC_CTOR(B) { SC_THREAD(loop); }
};
int sc_main(int, char **) {
// une fifo de 10 entiers
int> fifo(10);
sc_fifo<
"modA");
A a(
a.out(fifo);
"modB");
B b(
b.in(fifo);
300,SC_NS);
sc_start(
cout "contenu de la fifo @" << sc_time_stamp() << endl
<<
<< fifo << endl;
1,SC_US);
sc_start(
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 :
Faites deux implémentations du producteur, l’une utilisant des SC_THREAD
ou SC_CTHREAD
et l’autre utilisant des SC_METHOD
.
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.
Le sc_mutex
fournit les méthodes :
int lock()
: pour verrouiller le mutex. S’il est déjà verrouillé, on est mis en attente (wait()
est appelé). Cette méthode renvoie toujours 0.int trylock()
: pour essayer 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).Le sc_semaphore
prend en argument de constructeur le nombre de sémaphores qu’on peut prendre. Si ce nombre est égal à un, son comportement est équivalent à un sc_mutex
.
Le sc_semaphore
fournit les méthodes :
int wait()
: si le nombre de sémaphores restants est supérieur à 0, le décrémenter et retourner, sinon, le processus est suspendu (wait()
) Cette méthode renvoie toujours 0.int trywait()
: si le nombre de sémaphores restants est supérieur à 0, le décrémenter et retourner 0, sinon, retourne -1.int post()
: incrémente le nombre de sémaphores. Cette méthode renvoie toujours 0.int get_value()
: renvoie le nombre de sémaphores disponibles.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
.
Un sc_mutex
suffirait-il si on avait trois processus ?
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.
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
.
© Copyright 2016-2021, 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 disponnible ici. |