L'interface d'un module
La liste de ports d'un module est son interface - tout ce que le monde extérieur peut voir et toucher. À l'intérieur du module, le corps calcule les sorties à partir des entrées. Les ports sont les wires qui franchissent la frontière.
La déclaration moderne style ANSI met direction, type et largeur inline :
module my_module(
input wire clk,
input wire reset,
input wire [7:0] data_in,
output reg [7:0] data_out,
output wire valid
);
// corps
endmodule
C'est toute la forme. Chaque port a :
- Une direction :
input,outputouinout. - Un type :
wireoureg(oulogicen SystemVerilog). - Une largeur : une plage comme
[7:0], ou 1 bit si omise. - Un nom : l'identifiant que tu utiliseras dans le corps.
Les trois directions
input - piloté depuis l'extérieur
Les entrées sont toujours des wire. Le pilote est à l'extérieur du module - soit un signal d'un module de plus haut niveau, soit un reg du testbench. À l'intérieur de ce module, tu peux lire les entrées dans les expressions mais jamais les assigner :
module reader(input wire [7:0] data);
initial $display("data = %h", data);
endmodule
Écrire data = ... à l'intérieur du module serait une erreur.
output - piloté depuis l'intérieur
Les sorties sont pilotées par quelque chose à l'intérieur de ce module. Elles peuvent être soit wire (pilotées par assign ou par la sortie d'un sous-module) soit reg (pilotées depuis always/initial).
module driver(
input wire a,
input wire b,
input wire clk,
output wire y, // wire - piloté par assign
output reg q // reg - piloté par always
);
assign y = a & b; // OK parce que y est wire
always @(posedge clk)
q <= a; // OK parce que q est reg
endmodule
Essayer d'assigner y à l'intérieur du bloc always, ou de piloter q avec un assign, serait une erreur de compilation. Le choix du mot-clé doit correspondre au pilote.
inout - bidirectionnel
Les ports inout sont des wires qui peuvent être pilotés alternativement par le module ou par ce qui est connecté à l'extérieur. Ils apparaissent aux frontières des puces - lignes SDA I²C, broches GPIO bidirectionnelles, bus de données partagés. À l'intérieur du module, tu contrôles la direction avec un motif tri-state :
module bidir_pin(
input wire data_out,
input wire output_enable,
output wire data_in,
inout wire pin
);
assign pin = output_enable ? data_out : 1'bz;
assign data_in = pin;
endmodule
pin est le wire partagé. Quand output_enable est haut, le module pilote pin vers data_out. Quand bas, il relâche la broche en haute impédance (z), laissant un pilote externe l'utiliser. data_in observe toujours ce qui est actuellement sur la broche.
inout est rare dans la logique purement interne. Si tu construis un contrôleur mémoire, un cœur CPU, un pipeline de traitement d'image, tu n'en auras peut-être jamais besoin. C'est une fonctionnalité pour l'anneau I/O aux frontières d'une puce.
Ports 1 bit vs multi-bits
La plage de largeur est optionnelle - omets-la pour un port 1 bit :
input wire valid, // 1 bit
input wire [7:0] data, // 8 bits
input wire [31:0] addr, // 32 bits
Tu peux utiliser des paramètres pour les largeurs, c'est comme ça que fonctionnent les modules paramétrés :
module bus #(
parameter WIDTH = 32
)(
input wire [WIDTH-1:0] in,
output wire [WIDTH-1:0] out
);
assign out = in;
endmodule
Styles ANSI vs Verilog-1995
Tu verras occasionnellement du code plus ancien qui sépare la liste de ports et les déclarations :
// Ancien style Verilog-1995 - NE FAIS PAS ça dans le code récent
module foo(clk, data_in, data_out);
input clk;
input [7:0] data_in;
output reg [7:0] data_out;
// ...
endmodule
La liste de ports ne contient que des noms ; chaque port est ensuite déclaré séparément dans le corps. C'est verbeux, sujet aux erreurs « nommé dans la liste de ports mais jamais déclaré » et deux fois plus à taper. Le style ANSI-2001 (celui montré tout au long de cette doc) remplace les deux :
module foo(
input wire clk,
input wire [7:0] data_in,
output reg [7:0] data_out
);
// ...
endmodule
Utilise le style ANSI. Les outils le supportent depuis 2001.
Un exemple complet
Ce module unique a :
- Un paramètre (
WIDTH). - Des entrées pour horloge, reset, load enable, data et shift enable.
- Une sortie
reg(data_out) pilotée à l'intérieur d'un blocalways. - Une sortie
wire(msb_out) pilotée par une assignation continue. - Une déclaration ANSI complète qui liste la direction, le type et la largeur de chaque port inline.
C'est la forme de presque chaque module synthétisable que tu écriras.
La suite
Le prochain document - Instantiation de module - montre comment prendre ce module et l'utiliser à l'intérieur d'un design plus large. Le shifter que tu viens d'écrire n'est pas utile seul ; il gagne sa place quand quelque chose l'instancie et le câble dans un système.
Questions fréquentes
Quelles sont les trois directions de ports en Verilog ?
input, output et inout. input est piloté depuis l'extérieur du module. output est piloté depuis l'intérieur du module. inout est bidirectionnel - utile pour les bus tri-state où le module pilote et lit la même broche. La vaste majorité des ports internes sont input ou output ; inout n'apparaît qu'aux broches de puces.
Quelle est la différence entre output wire et output reg ?
output wire veut dire que la sortie est pilotée par une assignation continue ou par la sortie d'un sous-module - tout ce qui est en dehors d'un bloc always. output reg veut dire qu'elle est pilotée depuis un bloc procédural (initial ou always). Le mot-clé contrôle si tu peux cibler le signal depuis un always, pas si le matériel synthétisé contient un flip-flop.
Qu'est-ce que le style ANSI pour les ports Verilog ?
Le style ANSI déclare la direction et le type de chaque port inline dans la liste de ports : module foo(input wire [7:0] data, output reg [7:0] result);. L'ancien style Verilog-1995 ne listait que les noms dans la liste de ports et les redéclarait à l'intérieur du module. Utilise toujours le style ANSI dans le code récent - c'est plus court, moins source d'erreurs et standard partout.
Peut-on avoir plusieurs entrées et sorties dans un module Verilog ?
Oui - un module peut avoir autant de ports que nécessaire, dans n'importe quelle combinaison de directions. Sépare-les par des virgules dans la liste de ports. L'ordre dans la liste de ports détermine l'ordre des connexions positionnelles à l'instantiation, mais tu utiliseras presque toujours des connexions nommées (.port(signal)) pour éviter de dépendre de l'ordre.