Guide de style de code SystemVerilog
Introduction
Le code est lu bien plus souvent qu'il n'est écrit. Maintenir une cohérence dans le style de codage au sein d'une équipe améliore significativement la lisibilité du code, ce qui est l'une des méthodes les plus efficaces et simples pour économiser du temps de développement.
Parmi les langages de programmation, Python est sans doute le plus élégant. Lire du code Python écrit par d'autres est très facile, même pour des logiques complexes. Plus important encore, le code écrit par des débutants et celui des développeurs principaux présentent un style très similaire. Cela s'explique principalement par le PEP8, ce guide de style Python, adopté par toute la communauté avec une énorme adhésion.
Ce guide s'inspire des succès et de la structure du PEP8, tout en intégrant les bonnes pratiques de la bibliothèque UVM pour éviter de recréer la roue.
Principe central du PEP8
L'essence d'un guide de style réside dans la cohérence. Il est important de respecter ce guide, plus encore la cohérence au sein d'un projet, et surtout la cohérence au sein d'un module ou d'une fonction.
Sachez toutefois quand faire abstraction des règles - parfois les recommandations du guide ne sont pas adaptées. En cas de doute, utilisez votre jugement.
Règles de mise en page du code
Règles d'indentation
Principe de base : Chaque niveau d'indentation utilise 4 espaces.
Méthode recommandée :
// Lorsqu'un paramètre est coupé en ligne, la deuxième ligne commence sous le nom de la fonction
foo = long_function_name(
var_one, var_two, var_three,
var_four);
// Ou aligné avec le premier paramètre (4 espaces d'indentation optionnels)
foo = long_function_name(var_one, var_two,
var_three, var_four);
// Ajouter un indent supplémentaire dans la déclaration de fonction pour distinguer le corps
function void long_function_name(var_one, var_two,
var_three, var_four);
int x;
// ...corps de la fonction...
endfunction: long_function_name
// Lorsqu'une expression conditionnelle est coupée en ligne, ajouter un indent
if (expr_one && expr_two &&
expr_three) begin
do_something();
end
Méthode non recommandée :
// La position du paramètre sur la deuxième ligne est incorrecte
foo = long_function_name(var_one, var_two,
var_three, var_four);
// L'indentation de la déclaration de fonction est floue, ce qui peut être confondu avec le corps
function void long_function_name(var_one, var_two,
var_three, var_four);
int x;
// ...
endfunction: long_function_name
Choix entre tabulations et espaces
Utiliser préférentiellement des espaces pour l'indentation. Les tabulations ne doivent être utilisées que lorsqu'elles sont nécessaires pour maintenir la compatibilité avec du code déjà indenté avec des tabulations.
Exemples de configuraton d'éditeur :
Configuration Vi/Vim :
" Ajouter le contenu suivant à ~/.vimrc
set tabstop=4
set shiftwidth=4
set expandtab
Configuraton Emacs :
; Ajouter le contenu suivant à ~/.emacs
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq indent-line-function 'insert-tab)
Limite de longueur des lignes
Recommandation : limiter toutes les lignes (y compris les commentaires) à 100 caractères maximum.
La recommandation traditionnelle était de 80 caractères, mais en considérant les définitions d' macros longues dans UVM :
`uvm_object_utils
`uvm_info(get_name(), "message détaillé ici", UVM_MEDIUM)
Dans ces déclarations, les macros et les noms de fonctions prennent environ 30 caractères. Si on respectait strictement la limite de 80 caractères, on aurait fréquemment des problèmes de coupure de ligne. Une limite de 100 caractères est donc plus pratique et raisonnable.
Avertissement important : S'assurer que les lignes coupées sont correctement indentées.
Blocs begin & end
begindoit être sur la même ligne que l'instruction associéeenddoit être sur une ligne séparée
Exemple recommandé :
always_ff @(posedge clk) begin
// code logique
end
if (big_endian == 1) begin
m_bits[count+i] = value[size-1-i];
end
else begin
m_bits[count+i] = value[i];
end
for (int i = 0; i < size; i++) begin
if (big_endian == 1) begin
m_bits[count+i] = value[size-1-i];
end
else begin
m_bits[count+i] = value[i];
end
end
Instructions if & else
else doit commencer sur une nouvelle ligne
Exemple recommandé :
if (big_endian == 1) begin
m_bits[count+i] = value[size-1-i];
end
else begin
m_bits[count+i] = value[i];
end
Exemple non recommandé :
if (big_endian == 1) begin
m_bits[count+i] = value[size-1-i];
end else begin
m_bits[count+i] = value[i];
end
Recommandation forte : Utiliser toujours begin/end avec les instructions conditionnelles. L'absence de accolades est source de bugs.
Code dangereux :
// Éviter cette forme
if (big_endian == 1)
m_bits[count+i] = value[size-1-i];
else
m_bits[count+i] = value[i];
// Particulièrement à éviter avec des imbriquations :
// Bien que le code fonctionne, d'autres peuvent ajouter du code après else
// en pensant qu'il s'exécutera dans le cas else, créant ainsi des bugs difficiles à détecter
for (int i = 0; i < size; i++)
if (big_endian == 1)
m_bits[count+i] = value[size-1-i];
else
m_bits[count+i] = value[i];
Règles d'utilisation des lignes vides
- Séparer les définitions de classes, fonctions et tâches par des lignes vides
- Éviter les lignes vides entre des lignes de code liées
- Utiliser prudemment les lignes vides pour distinguer des segments logiques dans les fonctions et tâches
Règles d'utilisation des espaces
Appels de fonctions et de tâches
Règle de base : Aucun espace entre le nom de la fonction et la parenthèse ouvrante, ni entre la parenthèse ouvrante et le premier paramètre.
Exemple recommandé :
function void foo(x, y, z);
foo(x, y, z);
Exemple non recommandé :
function void foo (x, y, z);
foo (x, y, z);
foo( x, y, z );
Gestion des paramètres par défaut : Aucun espace autour du signe égal.
Exemple recommandé :
function void foo(name="foo", x=1, y=20);
Exemple non recommandé :
function void foo(name = "foo", x = 1, y = 20);
Opérateurs d'affectation et de calcul
Principe de base : Ne pas ajouter d'espaces superflus autour des opérateurs d'affectation.
Exemple recommandé :
x = 1;
y = 2;
long_variable = 3;
Exemple non recommandé :
x = 1;
y = 2;
long_variable = 3;
Règles d'espace pour les opérateurs : Les opérateurs binaires suivants doivent toujours être entourés d'espaces :
- Opérateurs d'affectation :
= - Opérateurs d'affectation composée :
+=,-=,*=,/=etc. - Opérateurs de comparaison :
==,===,<,>,!=,!==,<=,>= - Opérateurs logiques :
&,&&,|,||
// Utilisation correcte des opérateurs
result = (a + b) * c;
if (count >= max_value && enable == 1'b1) begin
status += increment_value;
end
Boucles et instructions conditionnelles
Règles de base :
- Ajouter un espace entre les mots-clés
if,for,whileet la parenthèse ouvrante - Garder des espaces entre les parties d'une boucle for :
int i = 0; i < 10; i++
Exemple recommandé :
if (x == 10)
for (int ii = 0; ii < 20; ii++)
while (condition_true)
Exemple non recommandé :
if(x == 10)
if( x == 10 )
for(int ii=0;ii<20;ii++)
Instructions composées : Généralement, ne pas écrire plusieurs instructions sur une même ligne.
Exemple non recommandé :
if (foo == 1) $display("bar");
Format des blocs always
Exemple recommandé :
always_ff @(posedge clk) begin
// logique séquentielle
end
always_comb begin
// logique combinatoire
end
Règles de commentaires
Avertissement important sur les commentaires
Les commentaires contradictoires avec le code sont pires que l'absence de commentaires. Mettez à jour les commentaires en même temps que le code !
Règles de base des commentaires :
- Les commentaires doivent être des phrases complètes
- Si le commentaire est un mot ou une phrase, le premier mot doit être majuscule (sauf pour les identifiants en minuscules)
- Les commentaires courts peuvent omettre le point final
- Les commentaires blocs sont généralement composés de phrases complètes, chacune se terminant par un point
En-tête de copyright
Pour les en-têtes de licence/copyright, utiliser le format suivant :
/***********************************************************************
* Copyright 2007-2011 Mentor Graphics Corporation
* Copyright 2007-2010 Cadence Design Systems, Inc.
* Copyright 2010 Synopsys, Inc.
* Copyright 2013 NVIDIA Corporation
* All Rights Reserved Worldwide
*
* Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in
* writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing
* permissions and limitations under the License.
**********************************************************************/
Chaînes de documentation
Les chaînes de documentation (docstrings) sont des commentaires situés en haut du fichier, décrivant la fonctionnalité du code présent dans ce fichier. Les placer directement après l'en-tête de copyright. Ne pas les mélanger avec d'autres éléments. Utiliser le format suivant :
* Fin de l'en-tête de copyright
**********************************************************************/
/*
* Module `ABC`
*
* C'est le premier paragraphe. Séparez les paragraphes avec
* une ligne vide contenant uniquement le caractère ` *`.
*
* C'est le deuxième paragraphe. Ne pas encadrer la docstring
* par un masque de `*****`. Seul l'en-tête de copyright
* au-dessus de la docstring est encadré.
*/
Commentaires blocs
Les commentaires blocs s'appliquent généralement à certaines (ou toutes) les lignes de code suivantes, avec le même niveau d'indentation que ce code.
Chaque ligne d'un commentaire bloc commence par // et un espace (sauf dans le texte indenté). Les paragraphes dans les commentaires blocs sont séparés par une ligne contenant uniquement //.
On peut aussi utiliser la syntaxe de commentaire bloc /* */ :
// C'est la première ligne du commentaire bloc
// C'est la deuxième ligne
//
// C'est la deuxième partie du commentaire bloc
/*
* Ce commentaire décrit
* la fonctionnalité du
* code suivant
*/
foo = bar + 1;
Commentaires en ligne
Éviter les commentaires en ligne car ils rendent le code moins propre :
// Ne pas faire cela
x = x + 1; // Augmente le compteur de paquets
Conseils d'utilisation des commentaires
Les commentaires sont souvent négligés pour respecter les délais de projet. Mais lorsque vous revenez sur le code après un certain temps, vous regrettez toujours cette décision. Donc, passer un peu de temps maintenant pour écrire des commentaires évitera beaucoup de souffrances plus tard. Vous remercierez vous-même plus tard.
Éviter les barres de commentaires, par exemple :
/***********************///######################//////////////
Ces barres rendent le code désordonné et apportent peu de valeur. Un bon commentaire bloc suffit à séparer clairement le code. Seul l'en-tête de copyright doit utiliser le format de barre.
Conventions de nommage
Pour une compréhension uniforme, définissons d'abord quelques conventions de nommage courantes :
- PascalCase - La première lettre de chaque mot est majuscule
- camelCase - La première lettre de chaque mot (sauf le premier) est majuscule
- lowercase_with_underscores - Minuscules avec des soulignés
- UPPERCASE_WITH_UNDERSCORES - Majuscules avec des soulignés
Noms de fichiers
Les noms de fichiers doivent utiliser lowercase_with_underscores
crc_generator.sv
tb_defines.svh
module_specification.docx
input_message_buffer.sv
Classes et modules
Les noms de classes et de modules doivent utiliser lowercase_with_underscores. Si un seul classe ou module est présent dans le fichier, son nom doit être identique au nom du fichier.
class packet_parser_agent;
endclass: packet_parser_agent
module packet_parser_engine;
endmodule: packet_parser_engine
Les instances de classe doivent être traitées comme des variables et utiliser le format lowercase_with_underscores. Les instances de module doivent utiliser la notation camelCase sans soulignés.
// Classe
packet_parser_agent parser_agent;
parser_agent = new();
// Module
packet_parser_engine ppe0(.*);
packet_parser_engine packetParserEngine4a(.*);
packet_parser_engine packetParserEngine4b(.*);
Interfaces
- Les définitions d'interfaces utilisent
lowercase_with_underscoreset se terminent par "_io" - Les instances d'interfaces se terminent par "_if"
- Les blocs clocking utilisent
camelCase - Les modport sont généralement un seul mot
lowercase
interface bus_io(input bit clk);
logic vld;
logic [7:0] addr, data;
clocking ioDrv @posedge(clk);
input addr;
output vld;
output data;
endclocking: ioDrv
modport dut(input addr, output vld, data);
modport tb(clocking ioDrv);
endinterface: bus_io
module tb_top;
bus_io bus_if(clk);
endmodule: tb_top
Variables
Les noms des variables doivent toujours utiliser lowercase_with_underscores
ethernet_agent eth_agent;
int count_packets, count_errors;
logic [15:0] some_long_var;
Si nécessaire, utiliser des préfixes pour identifier et regrouper facilement les variables :
logic [31:0] pe_counter_0;
logic [31:0] pe_counter_1;
logic [31:0] pe_counter_2;
Structures, unions et énumérations
Définir toutes les structures, unions et énumérations avec typedef. Elles doivent utiliser la notation camelCase avec les distinctions suivantes :
- Les structures se terminent par
_s - Les unions se terminent par
_u - Les énumérations se terminent par
_e. De plus, les énumérations doivent utiliserUPPERCASE_WITH_UNDERSCORES
typedef struct packed {
logic [47:0] macda;
logic [47:0] macsa;
logic [15:0] etype;
} ethPacket_s;
typedef union packed {
logic [15:0] tx_count;
logic [15:0] rx_count;
} dataPacketCount_u;
typedef enum logic [1:0] {
IPV4_TCP,
IPV4_UDP,
IPV6_TCP,
IPV6_UDP
} packetType_e;
Noms de variables de type
Les noms de variables de type doivent être en UPPERCASE, idéalement un seul mot.
// Exemples suivants extraits du code UVM.
// Le chemin du fichier où ils peuvent être trouvés est également mentionné.
// tlm1/uvm_exports.svh
class uvm_get_peek_export #(type T=int);
class uvm_blocking_master_export #(type REQ=int, type RSP=REQ);
// base/uvm_traversal.svh
virtual class uvm_visitor_adapter #(type STRUCTURE=uvm_component,
VISITOR=uvm_visitor#(STRUCTURE)) extends uvm_object;
Normes de nommage des macros
Le nommage des macros doit être différent selon leur utilisation :
- Macros de fonctions/tâches : Utiliser UPPERCASE pour les noms de macros et lowercase pour les paramètres
- Macros de classes/fragments de code : Utiliser lowercase pour les noms de macros et UPPERCASE pour les paramètres
- Séparateurs de mots : Utiliser systématiquement des soulignés
// Macros de fonctions/tâches : NOMS_EN_MAJUSCULES + PARAMETRES_EN_MINUSCULES
`define PRINT_BYTES(arr, startbyte, numbytes) \
function print_bytes(logic[7:0] arr[], int startbyte, int numbytes); \
for (int ii=startbyte; ii<startbyte+numbytes; ii++) begin \
if ((ii != 0) && (ii % 16 == 0)) \
$display("\n"); \
$display("0x%x ", arr[ii]); \
end \
endfunction: print_bytes
// Macros de définition de classe : NOMS_EN_MINUSCULES + PARAMETRES_EN_MAJUSCULES
`define uvm_analysis_imp_decl(SFX) \
class uvm_analysis_imp``SFX #(type T=int, type IMP=int) \
extends uvm_port_base #(uvm_tlm_if_base #(T,T)); \
`UVM_IMP_COMMON(`UVM_TLM_ANALYSIS_MASK,`"uvm_analysis_imp``SFX`",IMP) \
function void write( input T t); \
m_imp.write``SFX( t); \
endfunction \
endclass
// Macros de fragments de code : NOMS_EN_MINUSCULES + PARAMETRES_EN_MAJUSCULES
`define uvm_create_on(SEQ_OR_ITEM, SEQR) \
begin \
uvm_object_wrapper w_; \
w_ = SEQ_OR_ITEM.get_type(); \
$cast(SEQ_OR_ITEM , create_item(w_, SEQR, `"SEQ_OR_ITEM`"));\
end
Lecture complémentaire
Pour en savoir plus sur l'utilisation avancée des macros SystemVerilog, consultez le guide détaillé sur l'utilisation des macros.
Identifiants de fin
Utiliser toujours des identifiants de fin là où c'est approprié :
endclass: driver_agent
endmodule: potato_block
endinterface: memory_io
endtask: cowboy_bebop
Conseils de pratique de programmation
Comme SystemVerilog couvre à la fois la conception et la vérification, il possède de nombreuses caractéristiques linguistiques. Pour les structures linguistiques non explicitement mentionnées dans ce guide, comme les assertions, la couverture, les contraintes, le contrôle temporel, etc., vous pouvez étendre les principes ci-dessus.
Conclusion
Écrire un code élégant n'est pas facile. Dans les délais serrés des projets et les lourdes charges de travail, il est difficile de consacrer du temps à revoir, refaire et optimiser le code rapidement produit. À ces moments, les paroles des anciens peuvent nous aider à maintenir notre engagement initial à écrire un code élégant :
Changeons notre attitude traditionnelle sur la construction de programmes : au lieu de nous imaginer que notre principle travail est d'indiquer à l'ordinateur ce qu'il doit faire, concentrons-nous sur l'explication à l'ordinateur de ce que nous souhaitons qu'il fasse.
— Donald Knuth
Ainsi, un code propre est clair, facile à lire et à comprendre ; son organisation, sa forme, son architecture et sa syntaxe déclarative révèlent l'intention. Chaque petite partie est cohérente, son objectif est unique, bien que toutes ces petites parties soient comme des morceaux d'un puzzle complexe, elles sont faciles à séparer lorsqu'un élément doit être modifié ou remplacé.
— Vikram Chandra, auteur de "Geek Sublime"
Références
- Code source UVM
- PEP8 - Guide de style Python
- PEP7 - Guide de style C
- "Geek Sublime" - Vikram Chandra
- "Code élégant" - Andy Oram, Greg Wilson
- Débat sur les espaces vides