Niveaux d’abstraction et raffinement
2023-2024
En SystemC on peut partir d’une description fonctionnelle et aller jusqu’à une représentation RTL.
Comment utiliser les processus de SystemC pour faire cela.
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.
SC_THREAD
wait
SC_METHOD
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.
int f(int i, int j ...)
{
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
.
(tt) {
SC_MODULE<int> i;
sc_in<int> j;
sc_in...
<int> xx;
sc_out
void mthread()
{
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
= f(i,j,..)
xx // prévenir que la sortie est prête
}
()
SC_CTOR{
(mthread);
SC_THREAD...
}
}
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
(tt) {
SC_MODULE<int> i;
sc_in<int> j;
sc_in...
<int> xx;
sc_out
void mthread()
{
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
= f(i,j,..)
xx // Ajouter la latence
(xx,SC_NS);
wait// prévenir que la sortie est prête
}
()
SC_CTOR{
(mthread);
SC_THREAD...
}
}
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
(tt) {
SC_MODULE// L'horloge
<bool> clk;
sc_in// Les autres I/O
<int> i;
sc_in<int> j;
sc_in...
<int> xx;
sc_out
// Latence en nombre de cycles
static const unsigned int LAT = xxx;
void mthread()
{
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
= f(i,j,..)
xx // Ajouter la latence
for (int i=0, i<LAT, i++)
();
wait// prévenir que la sortie est prête
}
()
SC_CTOR{
(mthread);
SC_THREAD<< clk.pos();
sensitive ...
}
}
Par exemple avec un SC_CTHREAD
(tt) {
SC_MODULE// L'horloge
<bool> clk;
sc_in// Les autres I/O
<int> i;
sc_in<int> j;
sc_in...
<int> xx;
sc_out
// Latence en nombre de cycles
static const unsigned int LAT = xxx;
void mthread()
{
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
= f(i,j,..)
xx // Ajouter la latence
(LAT);
wait// prévenir que la sortie est prête
}
()
SC_CTOR{
(mthread, clk.pos());
SC_CTHREAD...
}
}
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
(tt) {
SC_MODULE// L'horloge
<bool> clk;
sc_in// Les autres I/O
<int> i;
sc_in<int> j;
sc_in...
<int> xx;
sc_out
int f(int i, intj, ...)
{
// étape 1
....
(x);
wait// étape 2
....
(y);
wait// ...
// étape n
....
(z);
waitreturn xx;
}
void mthread()
{
// Attendre que les entrées soient prêtes
...
// Exécuter la fonction
= f(i,j,..)
xx // prévenir que la sortie est prête
}
()
SC_CTOR{
(mthread, clk.pos());
SC_CTHREAD...
}
}
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.
Comme l’algorithme est itératif, nous allons l’implémenter en utilisant de la logique séquentielle.
Le module aura l’interface représentée dans le schéma suivant :
valid
passe à 1
, durant 1 cycle,
quand les entrées sont prêtes,ready
passe à 1
, durant 1 cycle,
quand le résultat est prêt,valid
passe à 1
durant le calcul, le
comportement est indéterminé.ready
est à 1
Travail à faire
valid
/ready
).
SC_CTHREAD
.sc_main
où vous testerez
exhaustivement le calcul.
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
Cette figure représente le chemin de donnée d’une implémentation possible de cet algorithme.
Travail à faire
SC_METHOD
proposez une
implémentation RTL du module calculant le PGCD.
© 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. |