Simulation à haut niveau d'un SoC

Modélisation du SoC et de son environnement:

Les Entrées/Sorties:

Nous voulons modéliser un système qui permettrait de traiter à la volée un flux vidéo.

Entrées-Sorties du SoC

Pour pouvoir simuler l'ensemble, nous avons besoin, en supplément du modèle du SoC vu précédemment, d'un environnement de simulation contenant les éléments suivants:

  • Un module "caméra" qui génère un flux vidéo au bon format.
  • Un module "affichage" qui permettra de visualiser le résultat du traitement à partir d'un flux vidéo sortant.

Pour cela, nous vous proposons deux modules SystemC, compatibles avec l'environnement SocLib.

Les transactions sur le bus:

En plus de ces deux module, nous vos proposons d'utiliser un module maître Whishbone générique. Ce module peut être instancié dans un module SystemC pour contrôler une interface maître. Il contient toute la logique nécessaire pour générer "simplement" des requêtes sur un bus Whishbone, de façon unitaire ou par bloc.

Les sources:

Les modules sont accessibles dans le dépôt soclib_hello_world, précédemment utilisé, dans la branche models

Les modules se trouvant dans le sous-dossier IPs.

Vous trouverez plus de détails sur les deux modules dans les pages suivantes.

Copyright:

La séquence d'images utilisée pour les tests, ainsi que certaines images présentes sur les pages suivantes, sont issues du film d'animation "Big Buck Bunny" distribué sous licence creative common.

Générateur de flux vidéo

Format des images:

Les images que vous aurez à manipuler ont le format suivant:

  • Les images sont en niveaux de gris
    • 8 bits soit 256 niveaux de gris
  • Le format des images est de 640x480 pixels

Le flux vidéo a les caractéristiques suivantes:

  • Une fréquence pixel de 25MHz
  • Une zone valide de 640x480
  • Une zone de "blanking" de 160 colonnes et de 40 lignes

Les trames vidéo ont une durée de 800x520x40ns ce qui donne une cadence vidéo de 60 trames par seconde.

Format du flux vidéo

Physiquement, l'interface vidéo est composée des trois signaux suivants:

  • pixel : les pixels sur 8 bits
  • line_valid : un signal indiquant que nous sommes dans une partie valide de la ligne.
  • frame_valid : un signal indiquant que la ligne actuelle est valide.

Le module de génération des images:

Ce module SystemC, destiné à la simulation, génère un flux vidéo au format décrit précédemment. Ce flux est généré à partir d'une série d'image au format PNG.

Le module SystemC (C++ class) correspondant est VideoGen.

Le code correspondant à ce module se trouve dans le dossier:

IPs/video_gen

Vous y trouverez:

  • Le fichier d'en-tête définissant le module
  • Le code SystemC
  • Le fichier de méta-données SocLib
  • Un exemple de configuration de l'environnement de compilation

Ce module utilise la bibliothèque libpng pour ouvrir les images, il faudra donc changer la configuration du compilateur pour la prendre en compte.

Constructeur du module:

 VideoGen(
           sc_module_name insname,            // nom du module SystemC
           const char *bn = "images/bbb/bbb", // nom de base et chemin vers images png
           const int w = 640,                 // largeur de la zone valide de l'image
           const int h = 480,                 // hauteur de la zone valide de l'image
           const int lsync = 160,             // largeur de la synchro. horizontale
           const int fsync = 40               // hauteur de la synchro. verticale
       );

Remarques:

  • Les paramètres du constructeur ont des valeurs par défaut correspondant au format vidéo utilisé.
  • Le paramètre bn correspond au nom de base des images utilisées et au chemin vers les fichiers PNG, relativement à l'executable de simulation.

Vous devriez aussi avoir une série d'image au bon format dans le dossier images/bbbb

L'interface SystemC du module:

            sc_in<bool>           clk;
            sc_in<bool>           reset_n;
            sc_out<bool>          line_valid;
            sc_out<bool>          frame_valid;
            sc_out<unsigned char> pixel_out;

En plus des signaux correspondant aux flux vidéo (tous en sortie) ce module nécessite les deux signaux suivants:

  • une horloge clk correspondant à l'horloge pixel.
  • un signale de remise à zéro reset_n actif sur niveau bas.

Configuration de l'environnement de compilation:

Ce module utilise la bibliothèque libpng il faut donc ajouter le chemin vers les fichiers d'en-tête pour la compilation et la bibliothèque pour l'édition de liens.

La commande libpng-config vous permet de trouver les paramètres adéquats.

# Pour obtenir les paramètres de compilation
libpng-config --cflags
# Pour obtenir les paramètres d'édition de liens
libpng-config --libs

Inspirez vous du fichier soclib.conf.example pour adapter la configuration des outils SocLib.

Afficheur de flux vidéo

Le module d'affichage:

Ce module SystemC est le complément du module VideoGen, il permet d'afficher à des images à partir d'un flux vidéo en utilisant la bibliothèque graphique SDL.

Le module SystemC (C++ class) correspondant est Display.

Le code correspondant à ce module se trouve dans le dossier:

IPs/display

Vous y trouverez:

  • Le fichier d'en-tête définissant le module
  • Le code SystemC
  • Le fichier de méta-données SocLib
  • Un exemple de configuration de l'environnement de compilation

Ce module utilise la bibliothèque graphique SDL pour l'affichage. Il faudra donc changer la configuration de l'outil de compilation pour la prendre en compte.

Constructeur du module:

 Display  (
           sc_module_name insname,            // nom du module SystemC
           const int w = 640,                 // largeur de la zone valide de l'image
           const int h = 480,                 // hauteur de la zone valide de l'image
           const int lsync = 160,             // largeur de la synchro. horizontale
           const int fsync = 40               // hauteur de la synchro. verticale
       );

Remarques:

  • Les paramètres du constructeur ont des valeurs par défaut correspondant au format vidéo utilisé.

L'interface SystemC du module:

            sc_in<bool>          clk;
            sc_in<bool>          reset_n;
            sc_in<bool>          line_valid;
            sc_in<bool>          frame_valid;
            sc_in<unsigned char> pixel_in;

En plus des signaux correspondant aux flux vidéo (tous en entrée) ce module nécessite les deux signaux suivants:

  • une horloge clk correspondant à l'horloge pixel.
  • un signale de remise à zéro reset_n actif sur niveau bas.

Configuration des variables de compilation:

Comme ce module utilise la bibliothèque SDL il faut ajouter le chemin vers les fichiers d'en-tête pour la compilation et la bibliothèque pour l'édition de liens.

La commande sdl-config peut vous aider à trouver les paramètres adéquats.

# Pour obtenir les paramètres de compilation
sdl-config --cflags
# Pour obtenir les paramètres d'édition de liens
sdl-config --libs

Inspirez vous du fichier soclib.conf.example pour adapter la configuration des outils SocLib.

Travail préliminaire

Comme première étape pour configurer l'environnement de simulation, vous allez tout d'abord, tester les deux modules vidéo en les connectant directement l'un à l'autre.

Liaison directe entre l'entrée vidéo et l'affichage

Voici les étapes par lesquelles vous devez passer:

  • Modifier le fichier de description de la plateforme pour ajouter les deux modules
  • Modifier le fichier C++ (top.cpp)
    • Ajouter les inclusions des fichiers d'en-tête
    • Déclarer un nouveau signal d'horloge pour l'horloge pixel
    • Déclarer les signaux permettant de connecter les deux modules
    • Instancier les deux modules
    • Connecter les modules directement l'un à l'autre
      • Ajouter les éventuellement aux traces

Testez l'ensemble et éventuellement vérifiez que les "timings" des signaux vidéo correspondent bien au format vidéo attendu.

Maître Wishbone générique

Pour faciliter le développement du modèle de votre SoC, nous vous proposons un module réutilisable permettant de générer des requêtes maitre sur le bus Wishbone.

Ce module peut être ajouté à un module SystemC pour contrôler une interface maitre Wishbone. Il contient ainsi la machine à état permettant de générer des requêtes unitaires ou par bloc de lecture et d'écriture.

Attention vous n'êtes pas obligés d'utiliser systématiquement ce module. Il est parfois plus simple de réécrire la séquence d'une requête Wishbone.

De plus ce module fait appel à des fonction bloquante de SystemC et ne peut donc être utilisé que dans des SC_THREAD.

Le code de ce module se trouve dans le dossier:

IPs/wb_master_fsm

Constructeur du module:

            WbMasterFsm (
                  sc_in<bool>        &p_clk,
                  WbMaster<wb_param> &p_wb
                  );

Les arguments passés au constructeur sont:

  • Un port Wishbone maitre p_wb
  • Un port d'entrée de type booléen p_clk correspondant ç l'horloge

Ce module module va manipuler les signaux connectés à un port Wishbone maitre, les arguments sont passé par référence pour pouvoir y accéder directement.

Ce module n'est pas un module SystemC, pour être utilisé, doit d'abord être instancié dans un module SystemC.

Utilisation du module:

Une instancié, vous pouvez initier des requêtes sur le bus en utilisant les méthode suivantes. Les Requêtes se font sur des mots de 32 bits.

Requête de lecture unitaire:

            uint32_t wb_read_at  (
                  uint32_t addr   // 32-bit address
                  );
  • Prend en entrée l'adresse 32 bits du mot à lire
  • Retourne un mot de 32 bits non signé

Requête d'écriture unitaire:

            void     wb_write_at (
                  uint32_t addr, // 32-bit address
                  uint8_t  mask, // 4-bit mask
                  uint32_t data  // 32-bit data
                  );
  • Prend en entrée:
    • l'adresse 32 bits de l'emplacement mémoire à modifier
    • le masque correspondant aux octets à modifier (seul les 4 bits de poids faible sont utilisés)
    • la donnée à écrire
  • La méthode ne retourne rien

Requête de lecture par bloc:

            void     wb_read_blk (
                  uint32_t saddr, // 32-bit start address
                  uint32_t num ,  // number of 32-bit transactions
                  uint8_t  *dest  // pointer to the destination byte buffer
                  );
  • Prend en entrée:
    • l'adresse 32 bits du début de l'emplacement du bloc à lire
    • le nombre de requêtes 32 bits consécutives qui seront faites
    • un pointeur vers un tampon pour stocker les données lues
  • La méthode ne retourne rien

ATTENTION: dest doit pointer vers une zone allouée au préalable. La taille de la zone allouée doit être de 4 fois le nombre de requêtes.

Pour être compatible avec l'endianness du LM32, les octets sont stockés dans l'ordre big-endian.

Requête d'écriture par bloc:

            void     wb_write_blk (
                  uint32_t saddr, // 32-bit start address
                  uint8_t  *mask, // pointer to the data masks
                  uint8_t  *data, // pointer data byte buffer
                  uint32_t num    // number of 32-bit transactions
                  );
  • Prend en entrée:
    • l'adresse 32 bits du début de l'emplacement mémoire où le bloc sera écrit
    • un pointeur vers un tampon contenant le masque des octets à modifier
    • un pointeur vers un tampon contenant les données à écrire
    • le nombre de requêtes 32 bits consécutives qui seront faites.
  • La méthode ne retourne rien

Remarque Si le pointeur de masque mask est égal NULL, tous les masques sont mis à 0xF. Tous les octets sont donc modifiés.

Exemple d'utilisation:

Un exemple d'utilisation est disponible dans le dossier:

IPs/dummy_wb_master

Voici les principales étapes pour utiliser le module dans l'exemple:

Dans le fichier d'en-tête dummy_wb_master.h nous avons:

  • Déclarer dans le module SystemC:
WbMasterFsm<wb_param> master0;

Dans le fichier d'implémentation dummy_wb_master.cpp nous avons:

  • Lier le module au port Wishbone et au port d'horloge:
DummyWbMaster (sc_module_name name ) : ...
      master0(p_clk,p_wb)
  • Faire des requêtes dans un SC_THREAD du module SystemC:
// Écrire 0xC01dCafe à l'adresse 0x40000000 avec le masque d'octets 0xF
master0.wb_write_at(0x40000000,0xf,0xC01dCafe);

// Lire la donnée se trouvant à l'adresse 0x40000000
val = master0.wb_read_at(0x40000000);

// Écrire consécutivement 20 mots de 32bits
// DATA pointe vers un buffer contenant au minimum 20x4 octets
// MASK  pointe vers les 20 masques correspondants
master0.wb_write_blk(0x40000000,MASK,DATA,20);

// Écrire consécutivement 20 mots de 32bits avec un masque égale à 0xF
master0.wb_write_blk(0x40000000,NULL,DATA,20);

// Lire 20 mots de 32 bits
// Le résultat est stocké dans la zone pointée par RDATA
// RDATA pointe vers une zone pré-allouée pouvant contenir 4x20 octets
// Les octets sont organisés dans l'ordre big-endian
master0.wb_read_blk(0x40000000,20,RDATA)

Pour tester l'ensemble, vous pouvez instancier le module DummyWbMaster dans votre modèle de SoC et observer, par exemple, les différences entre les requêtes simples et les requêtes par bloc.

Modules wishbone/vidéo

Modules wishbone/vidéo

Le module maître générique est utilisé pour modéliser les deux modules suivants:

  • WbVideoIn : qui récupère le flux vidéo et sauvegarde dans la mémoire du SoC en passant par le bus wishbone.
  • WbVideoOut : qui lit, en passant par le bus wishbone, une image se trouvant en mémoire et génère un flux vidéo en sortie.

Ces deux modules sont maîtres sur le bus mémoire et peuvent ainsi accéder (en lecture ou en écriture) à la mémoire RAM partagée du SoC. Ces deux modules sont des DMAs (Direct Memory Access) spécialisés.

Comme les flux vidéo ont un rythme bien précis, ces deux modules contiennent des tampons (buffers) pour absorber les variations de débit sur le bus wishbone. En effet, comme le bus est partagé entre plusieurs maîtres, il n'y a pas de garantie de disponibilité au moment ou un pixel arrive ou qu'il doit être généré. Sur le bus seul un débit moyen peut être garanti, les tampons servent à absorber les à-coups.

Pour exploiter au mieux le débit disponible, les accès sur le bus se font exclusivement par blocs. La taille des tampons internes est par conséquent multiple de la taille des blocs écrits sur le bus.

Comme ces modules doivent être contrôlables par les processeurs, ils possèdent aussi une interface esclave qui permet d'accéder à leur registres de configuration et de contrôle. En plus pour permettre la synchronisation de l'ensemble, des signaux d'interruption sont générés.

Le module WbVideoIn

La structure et fonctionnement

Le module contient un double buffer qui permet de stocker les pixels dès qu'il arrivent au niveau de l'entrée vidéo. Ces pixels arrivent à la fréquence de l'horloge vidéo. Les signaux accompagnant le flux vidéo permettent de savoir si ces pixels sont valides et de synchroniser l'acquisition sur le début d'une trame.

La figure suivante montre la structure interne de ce module.

Module pour la vidéo entrante

Une fois qu'un buffer est plein, il est écrit sur le bus wishbone par DMA. Les écritures se font par blocs de mots tant que le buffer n'est pas vide.

À la fin de l'écriture d'une image complète, un signal d'interruption est généré. Ce signal passe à 1 durant un cycle de l'horloge wishbone. (Ce signal correspond après l'écriture du dernier bloc de pixels en mémoire)

Un registre de contrôle permet de démarrer l'acquisition et l'enregistrement des pixels.

Les registres accessibles par l'interface esclave:

    @(base + 0) ctrl_reg               // seul le bit 0 est utilisé pour démarrer l'acqisition et les DMA
    @(base + 4) image_address_reg      // Registre contenant l'adresse à laquelle est enregistrée l'image entrante

nb. Le bit 0 du registre de contrôle agit comme un reset synchrone du module

Le constructeur:

         WbVideoIn (
               sc_module_name mod_name , // nom du module SystemC
               uint32_t image_address  , // adresse de l'image est enregistrée (valeur au reset)
               unsigned int buff_size  , // taille du tampon interne (en octets)
               unsigned int burst_size   // taille des blocs wishbone (en mots)
               ) ;

remarques:

  • buff_size est la taille d'un des doubles buffers
  • Les mots faisant 4 octets, buff_size doit être multiple de 4xburst_size

Le module WbVideoOut

La structure et e fonctionnement

Un double buffer permet de stocker les pixels avant de les présenter en sortie.

Le module lit les pixels à partir de la mémoire en passant par le bus wishbone. Les lectures DMA se font par bloc de mots tant que le buffer n'est pas plein. Quand le buffer est plein on passe au buffer suivant seulement s'il est vide.

La figure suivante montre la structure interne de ce module.

Module pour la vidéo sortante

Dès que le premier buffer est plein, le premier pixel est présenté en sortie en même temps que les signaux de synchronisation de la vidé. Les pixels en sortie sont produits à la fréquence de l'horloge vidéo et le buffer est vidé.

Une interruption est généré à la fin de la génération d'une image (à la fin de la zone visible). Ce signal passe à 1 durant un cycle de l'horloge wishbone.

Les registres accessibles par l'interface esclave:

    @(base + 0) ctrl_reg               // seul le bit 0 est utilisé pour démarrer les DMA
                                       // la génération de l'image se faisant une fois le premier
                                       // buffer plein
    @(base + 4) image_address_reg      // Registre contenant l'adresse à laquelle l'image est lue

nb. Le bit 0 du registre de contrôle agit comme un reset synchrone du module

Le constructeur:

         WbVideoOut (
               sc_module_name mod_name , // nom du module SystemC
               uint32_t image_address  , // adresse de l'image est enregistrée (valeur au reset)
               unsigned int buff_size  , // taille du tampon interne (en octets)
               unsigned int burst_size   // taille des blocs wishbone (en mots)
               ) ;

remarques:

  • buff_size est la taille d'un des doubles buffers
  • Les mots faisant 4 octets, buff_size doit être multiple de 4xburst_size

Analyse des transferts sur le bus:

Travail initial:

Pour cette étape du travail il faut d'abord compléter la structure du système en y ajoutant les modules DMA vidéo. Pour ce fait il faut:

  • Les instancier,
  • connecter les interfaces wishbone maitre et esclave au bus (en ajoutant les signaux nécessaires),
  • les relier aux flux vidéo,
  • relier les interruptions au processeur.

SoC avec modules pour la vidéo

Les modules DMA ne démarrent pas tant qu'ils n'ont pas été activé par le processeur. Il faudra veiller à modifier le code s'exécutant sur le processeur pour activer les deux modules. Les deux modules devront utiliser la même zone mémoire pour respectivement y stocker et y lire l'image. Pour les tests que vous avez à effectuer, les interruptions n'ont pas besoin d'être activées.

Une fois l'ensemble fonctionnel, vous devriez obtenir un modèle de système dans lequel des accès concurrents de et vers la mémoire sont régulièrement fait.

Travail à faire:

  • Comment se fait l'arbitrage? Combien de temps prend-il?
  • Quel est le débit des transferts sur le bus?
  • Modifiez les paramètres des modules DMA vidéo (taille des buffers, taille des blocs)
    • Quelle est la taille minimale des buffers utilisables sans perte de pixels?
    • Quelle relation avec la taille des blocs?
    • ...
  • Ajouter un maître qui fait des lecture/écritures concurrentes de blocs de données
    • Comment évoluent les performances précédemment mesurées

Cette étape est importante car elle permet de bien estimer les débits effectifs obtenus sur le bus et permettra de dimentionner la suite des éléments qui seront ajoutés au SoC.

Précision du modèle de contrôleur SDRAM:

Jusqu'ici nous avons utilisé le modèle de mémoire VCI générique fourni par SoCLib. Ce modèle a un comportement très simple celui d'une mémoire synchrone en lecture en écriture. Chaque accès entraine une latence d'un cycle sur le bus wishbone.

Pratiquement, les SDRAM n'ont pas ce comportement. Les accès entrainent des latences plus ou moins importantes en fonction de la vitesse à laquelle on les fait fonctionner. De plus, les SDRAM ont des modes d'accès "piplinés" qui permettent de lire (ou d'écrire) des blocs de données dont les adresses sont consécutives. Dans ce cas la latence n'est subite que pour le premier accès. Ajoutons à cela le contrôleur mémoire, qui fait l'interface entre le SoC et la SDRAM, qui peut éventuellement contenir des mémoires tempos et changer les latences d'accès.

Pour cela, nous proposons d'utiliser un autre modèle pour le couple SDRAM/contrôleur qui, bien que ne représentant pas exactement la réalité, est moins simpliste et pour lequel les latences sont réglables.

Le module WbRam:

Le module WbRam est un modèle de SDRAM+contrôleur avec les caractéristiques suivantes:

  • Une interface wishbone (pas besoin du convertisseur de protocole VCI->WB)
  • Latence nulle pour les requêtes d'écriture
  • Latence initiale configurable pour les requêtes de lecture par bloc

Ce module modélise le comportement d'un contrôleur de SDRAM avec une fifo pour les requêtes d'écriture (absorbant ainsi la latence) et faisant systématiquement des requêtes pipilinées en lecture.

Il comportement suivants ne sont par contre pas modélisés:

  • les rafraichissements réguliers de la sdram;
  • les latences aux changements de pages;

De plus, les requêtes en écriture sont systématiquement acceptées, comme si la fifo d'écriture était de taille infinie.

L'intérêt de ce module est de modéliser est de voir les pénalités en cas d'écritures isolées (non faites par bloc).

Pour avoir une estimation plus précise nous pouvons utilise un vrai modèle RTL. Nous verrons dans la suite comment co-simuler des modules Verilog avec notre plateforme.

Le constructeur du module est:

               WbRam (
                      sc_core::sc_module_name insname,         // Nom du module SystemC
                      const soclib::common::IntTab &index,     // Indice de la cible
                      const soclib::common::MappingTable &mt,  // Table d'adressage
                      uint32_t read_latency                    // Latence initiale pour une lecture par bloc
                      );

Ce module reprend les paramètre de la RAM VCI qui sont la table d'adressage et l'indice du segment à allouer.

La latence initiale pour les lectures est paramétrable avec une valeur par défaut de 4.

Travail à faire:

  • Remplacez la RAM VCI par ce modèle.
  • Modifiez la latence initiale de lecture et observez les performances.

Estimation des débits pour un maître supplémentaire:

Comme dans la suite nous serons amené à ajouter un maitre supplémentaire au bus (qui correspondra au module réalisant l'application de traitement d'image) il serait intéressant d'évaluer le débit réellement disponible sur le bus et la RAM (maintenant qu'une partie est utilisée pour les flux vidéo).

Pour cela vous pouvez utiliser le module maître générique (par exemple le DummyWbMaster) pour générer des requêtes de lecture et d'écriture sur de et vers la RAM.