Placement & Routage de Cordic
Contents
Avertissement
L'interface entre la partie de contrôle et le chemin de données n'ont pas été finalisées et ne correspondent donc pas exactement. Pour pouvoir faire l'exercice nous nous concentrerons sur le placement/routage de la du chemin de données seul.
Introduction
L'objectif de ce tme est de réaliser l'implantation physique du circuit Cordic, c'est à dire son dessin des masques ou layout. Nous le réaliserons en utilisant deux approches différentes pour en effectuer les comparaisons.
- Synthèse directe du circuit.
- Découpage explicite en parties contrôle et opératives (ou chemin de données datapath). La partie contrôle en synthétisée, mais le chemin de données sera décrit à l'aide du langage de saisie de netlist Stratus.
En première approche, la comparaison se limitera à la surface totale du circuit ainsi que la longueur totale de fil.
Synthèse logique avec Yosys
Yosys est une alternative beaucoup plus performante à l'ensemble boom, boog & loon.
La première étape consiste à transformer notre description vhdl en une netlist de portes logiques. Nous utiliserons pour cela le logiciel Yosys. Malheureusement, Yosys ne prend en entrée que du Verilog et ne génère en sortie que du Blif (Berkeley Logic Interchange Format). Il nous faut donc effectuer une traduction avant Yosys puis une autre après. Ces différentes étapes sont présentées dans le diagramme suivant:
Pour automatiser l'ensemble des commandes à donner à Yosys pour effectuer la synthèse, on passe par un petit script en langage tcl, yosys.tcl, dont un exemple est donné ci dessous:
set design cordic_cor set verilog_file $design.v set verilog_top $design set liberty_file /soc/alliance/cells/sxlib/sxlib.lib yosys read_verilog $verilog_file yosys hierarchy -check -top $verilog_top yosys synth -top $verilog_top yosys dfflibmap -liberty $liberty_file yosys abc -liberty $liberty_file yosys clean yosys write_blif $design.blif
Pour l'adapter à vos besoins, simplement éditer la première ligne et changer le nom du design.
Note
Nom de fichier et nom du design: le script est écrit de telle façon que le nom du design (de l'ENTITY vhdl) soit le même que le nom du fichier qui le contient. Autrement dit, le fichier cordic_cor.vhd doit définir l'ENTITY cordic_cor.
Placement & routage du bloc avec Coriolis
Avant de pouvoir utiliser Coriolis, il est nécessaire d'initialiser l'environnement unix:
student@pc:~> eval `/soc/coriolis2/etc/coriolis2/coriolisEnv.py` Using SoC network-wide Coriolis 2 (/soc/coriolis2) Switching to Coriolis 2.x (Release.Shared) student@pc:~>
Vous pouvez ensuite lancer l'interface graphique cgt, en indiquant le nom de la netlist (i.e. cell) à charger:
student@pc:~> cgt -V --cell=cordic_cor
Pour effectuer le placement, sélectionnez dans les menus:
P&R???Place Block
Pour effectuer le routage, sélectionnez dans les menus:
P&R???Route
Dans le terminal d'ou vous avez lancé l'interface graphique, le placeur puis le routeur auront affiché toute une série d'informations intéressantes, en particuler les dimensions du circuit ainsi que la longueur totale de fils. La taille du circuit, en λ, est donnée sur la ligne <Box 0l 0l 1850l 1850l>.
o Configuration of ToolEngine<Etesian> for Cell <cordic_cor> - Cell Gauge ...................................................... <sxlib> - Place Effort .......................................................... 2 - Update Conf ........................................................... 2 - Spreading Conf ........................................................ 2 - Routing driven .................................................... false - Space Margin ......................................................... 5% - Aspect Ratio ....................................................... 100% - Bloat model .................................................... disabled o Creating abutment box (margin:5% aspect ratio:100% g-length:1290.6) - Bloat space margin: 0%. - <Box 0l 0l 1850l 1850l> - GCell grid: [37x37]
La lngueur de fil totale est indiquée ici, c'est le Wire length completion ratio, exprimé lui aussi en λ.
o Running Negociate Algorithm o Negociation Stage. <queue:00012257> <event:00023112 remains:00000000> o Repair Stage. <repair.queue:00000000> o Computing statistics. - Processeds Events Total ........................................... 23113 - Unique Events Total ............................................... 12811 - # of GCells ........................................................ 1369 - Track Segment Completion Ratio ........................... 100% [12811+0] - Wire Length Completion Ratio ............................ 100% [596946+0] - Wire Length Expand Ratio ............................. 4.92% [min:568971] - Done in ................................................... 1.82s, 11.4Mb - Raw measurements ............................. 1.81689s, +11660Kb/974.3Mb
Réalisation avec un chemin de données
Pour décrire le chemin de données (datapath), nous allons utiliser le langage de saisie de netlist Stratus, qui est fourni avec Coriolis.
La documentation se décompose en deux parties, celle sur Stratus, le langage lui-même et celle sur DpGen, les opérateurs de chemin de données.
Le langage Stratus est un jeu de classes Python, il est donc possible et même recommandé d'utiliser toutes les fonctionalités proposées par ce langage.
Note
Stratus n'est absolument pas limité à la description de chemin de données. Il peut être utilisé pour tout type de netlists.
Votre travail va consister, en partant de la description vhdl et du schéma du chemin de données, à mettre en correspondance chaque élement avec un opérateur donné dans DpGen. C'est un travail similaire à celui que fait le synthétiseur logique Yosys.
Convention de nommage pour les signaux (ou Net)
Il faut bien distinguer le nom du signal, soit la chaîne de caratère 'x' passée en argument à la fonction Signal() et la variable Python contenant l'objet représentant le signal x. Pour des raisons de clarté du code, on donne à la variable le même nom que celui du signal (la chaîne de caractère). Comme on pourra le voir dans l'exemple 2, la variable Python admet la notation en tranche (ou slice) qui permet de sélectionner tout ou partie des bits composant un signal (x[15] ou x[7:4]).
x = Signal( 'x' , 16 ) x_sra_1 = Signal( 'x_sra_1', 16 )
Exemple 1: Multiplexeur 8 vers 1.
La bibliothèque d'opérateur n'offre pas de multiplexeur 8 vers 1, il faut donc en construire un à partir de multiplexeurs 2 vers 1.
Remarques:
- Comme nous sommes en logique dual-cmos, les portes complémentées sont plus petites que les celles non-complémentées. On utilisera donc, dans autant de couches que possible, les opérateurs nmux2.
- La commande du multiplexeur est encodée, c'est à dire que pour huit entrées, il nous faut une commande de trois bits. La façon dont la partie opérative est construite impose des contraintes sur la partie de contrôle qui doit être capable de fournir les commandes dans le format approprié.
Exemple 2: Réorganisation des différents bits d'un signal
Obtenir un signal aux bits décalés par rapport à un autre ne correspond pas réellement à du matériel, cela correspond juste à un câblage particulier des fils. Pour l'indiquer au langage Stratus nous procédons de la façon suivante:
- Un second signal x_sra_1 est déclaré.
- La fonction Cat() permet d'assembler un vecteur de bits à partir de bits ou de portion d'autre signaux vectorisés.
- Enfin, la méthode Alias() de l'objet signal permet de dire que celui-çi n'est pas un net indépendant, mais simplement un synonyme ou alias d'un autre (utilisation de la notation en tranche).
Base du fichier Stratus du chemin de données
Le script donné ci après vous fourni la base de départ pour décrire le chemin de données. Quelques remarques concernant l'organisation de ce script:
Nous définissons une classse dérivée de Model (classe de base fournie par Stratus). Nous devons surcharger les méthodes Interface(), Netlist() et optionellement Layout().
Comme indiqué précedemment, par convention, les objets signal (ou net) sont stockés dans des variables au nom identique.
Pour les instances nous utiliseront un dictionnaire membre de la classe (self.instances) pour pouvoir retrouver facilement les instances, qui vont être créé dans Netlist() par leur nom dans Layout().
Nous utiliserons, pour le nommage des instances, une convention similaire à celle des signaux : le nom (et sa clé dans le tableau) seront identique à celle du signal qu'elle génère.
Dans la méthode Layout(), on se restreindra à un placement sur une seule ligne des opérateurs. On essayera d'optimiser le placement des opérateurs, ce qui, dans notre cas, se résume a changer leur ordonnancement.
Ce script peut être exécuté des différentes façons suivantes:
Directement, après l'avoir rendu exécutable (point d'entrée: __main__):
etudiant@pc:~> chmod u+x cordic_dp.py etudiant@pc:~> ./cordic_dp.py
Directement, en passant par l'interpréteur Python (point d'entrée: __main__):
etudiant@pc:~> python cordic_dp.py
En utilisant l'argument script de cgt en mode texte (point d'entrée: ScriptMain()):
etudiant@pc:~> cgt -V --text --script=cordic_dp
Par le menu Tools???Python Script de cgt en mode graphique (point d'entrée: ScriptMain()).
#!/usr/bin/env python import sys from stratus import * class Cordic_DP ( Model ): def Interface ( self ): print 'Cordic_DP.Interface()' self.x_p = SignalIn ( 'x_p' , 8 ) self.y_p = SignalIn ( 'y_p' , 8 ) # Interface a completer. # ... self.ck = CkIn ( 'ck' ) self.vdd = VddIn ( 'vdd' ) self.vss = VddIn ( 'vss' ) return def Netlist ( self ): print 'Cordic_DP.Netlist()' Generate( 'DpgenNmux2' , 'nmux2_16b', param={'nbit':16,'behavioral':True,'physical':True,'flags':0} ) Generate( 'DpgenMux2' , 'mux2_16b', param={'nbit':16,'behavioral':True,'physical':True,'flags':0} ) self.instances = {} zero_16b = Signal( 'zero_16b', 16 ) self.instances['zero_16b'] = Inst( 'zero_16b', 'zero_16b' , map = { 'q' : zero_16b , 'vdd' : self.vdd , 'vss' : self.vss } ) x = Signal( 'x', 16 ) self.instances['x_sra_0_0'] = Inst( 'nmux2_16b', 'x_sra_0_0' , map = { 'cmd' : self.i_p[0] , 'i0' : x , 'i1' : x_sra_1 , 'nq' : x_sra_0_0 , 'vdd' : self.vdd , 'vss' : self.vss } ) # Netlist a completer. # ... return def Layout ( self ): print 'Cordic_DP.Layout()' # X processing. Place ( self.instances['zero_16b' ], NOSYM, XY( 0, 0 ) ) PlaceRight( self.instances['x_sra_0_0' ], NOSYM ) # Layout a completer. # ... return def ScriptMain ( **kw ): if kw.has_key('editor') and kw['editor']: setEditor( kw['editor'] ) cordic_dp = Cordic_DP( "cordic_dp" ) cordic_dp.Interface() cordic_dp.Netlist () cordic_dp.Layout () cordic_dp.Save (LOGICAL|PHYSICAL) return 1 if __name__ == "__main__" : kw = {} success = ScriptMain( **kw ) if not success: shellSuccess = 1 sys.exit( shellSuccess )
Assemblage du contrôle et du chemin de données
Ce script effectue les différentes tâches suivantes:
- Effacer les fichiers vbe, vst et ap issus d'une exécution précédente.
- Charger le .blif de la partie contrôle et la convertir au format vst (c'est à dire ce que fait blif2vst).
- Appeler le générateur de chemin de données écrit précedemment automatiquement.
- Créer un layout de l'ensemble avec suffisament d'espace vide pour que le placer puisse y mettre les cellules de la partie contrôle.
Note
Ce fichier génére un équivalent exact à cordic_net.vhd, mais directement au format vst.
#!/usr/bin/env python import sys import symbolic.cmos from Hurricane import DbU import CRL import plugins.RSavePlugin from stratus import * class Cordic_Net ( Model ): def Interface ( self ): print 'Cordic_Net.Interface()' self.raz = SignalIn ( 'raz' , 1 ) self.wr_axy_p = SignalIn ( 'wr_axy_p' , 1 ) self.a_p = SignalIn ( 'a_p' , 10 ) self.x_p = SignalIn ( 'x_p' , 8 ) self.y_p = SignalIn ( 'y_p' , 8 ) self.wok_axy_p = SignalOut( 'wok_axy_p' , 1 ) self.rd_nxy_p = SignalIn ( 'rd_nxy_p' , 1 ) self.nx_p = SignalOut( 'nx_p' , 8 ) self.ny_p = SignalOut( 'ny_p' , 8 ) self.rok_nxy_p = SignalOut( 'rok_nxy_p' , 1 ) self.ck = CkIn ( 'ck' ) self.vdd = VddIn ( 'vdd' ) self.vss = VddIn ( 'vss' ) return def Netlist ( self ): print 'Cordic_Net.Netlist()' get = Signal( 'get' , 1 ) calc = Signal( 'calc' , 1 ) mkc = Signal( 'mkc' , 1 ) place = Signal( 'place' , 1 ) a_lt_0 = Signal( 'a_lt_0' , 1 ) quadrant = Signal( 'quadrant', 2 ) i = Signal( 'i' , 3 ) self.instances = {} self.instances['ctl'] = Inst( 'cordic_ctl', 'ctl' , map = { 'ck' : self.ck , 'raz' : self.raz , 'wr_axy_p' : self.wr_axy_p , 'a_p' : self.a_p , 'wok_axy_p' : self.wok_axy_p , 'rd_nxy_p' : self.rd_nxy_p , 'rok_nxy_p' : self.rok_nxy_p , 'calc_p' : calc , 'mkc_p' : mkc , 'place_p' : place , 'a_lt_0_p' : a_lt_0 , 'i_p' : i , 'quadrant_p' : quadrant , 'vdd' : self.vdd , 'vss' : self.vss } ) self.instances['dp'] = Inst( 'cordic_dp', 'dp' , map = { 'ck' : self.ck , 'x_p' : self.x_p , 'y_p' : self.y_p , 'nx_p' : self.nx_p , 'ny_p' : self.ny_p , 'get_p' : get , 'calc_p' : calc , 'mkc_p' : mkc , 'place_p' : place , 'i_p' : i , 'quadrant_p' : quadrant , 'a_lt_0_p' : a_lt_0 , 'n_xy_cmd' : i # THIS IS WRONG ! , 'vdd' : self.vdd , 'vss' : self.vss } ) return def Layout ( self ): print 'Cordic_Net.Layout()' Place( self.instances['dp'], NOSYM, XY( 0, 0 ) ) ResizeAb( 0, 0, 0, DbU.fromLambda( 100.0 ) ) return def ScriptMain ( **kw ): for entry in os.listdir('.'): if os.path.isfile(entry): if entry.endswith('.vst') \ or entry.endswith('.vbe') \ or entry.endswith('.ap'): print 'Removing previous run file "%s".' % entry os.unlink( entry ) views = CRL.Catalog.State.Logical|CRL.Catalog.State.VstUseConcat ctlCell = CRL.Blif.load( 'cordic_ctl', CRL.Catalog.State.Logical|CRL.Catalog.State.VstUseConcat ) ctlKw = {'cell':ctlCell, 'views':views} plugins.RSavePlugin.ScriptMain( **ctlKw ) if kw.has_key('editor') and kw['editor']: setEditor( kw['editor'] ) buildModel( 'cordic_dp', DoNetlist|DoLayout, className='Cordic_DP' ) cordic_net = Cordic_Net( "cordic_net" ) cordic_net.Interface() cordic_net.Netlist () cordic_net.Layout () cordic_net.Save (LOGICAL|PHYSICAL) return 1 if __name__ == "__main__" : kw = {} success = ScriptMain( **kw ) shellSuccess = 0 if not success: shellSuccess = 1 sys.exit( shellSuccess )
Travail demandé
Effectuer les placements & routages par synthèse complète puis avec un chemin de données, comparer les résultats. Le travail à rendre est le fichier Python de génération du chemin de données ainsi qu'une page expliquant vos conclusions.