Verilog module-instantiatie beheersen: Best practices voor hiërarchisch ontwerp
Zoals we zagen in een vorig artikel, worden grotere en complexere ontwerpen gebouwd door meerdere modules hiërarchisch te integreren. Modules kunnen geïnstantieerd worden binnen andere modules en poorten van deze instanties kunnen verbonden worden met andere signalen binnen de parent-module.
Deze poortverbindingen kunnen via een geordende lijst of bij naam gedaan worden.
Poortverbinding via geordende lijst
Eén methode om de verbinding tussen de poortexpressies in een module-instantiatie met de signalen in de parent-module te maken, is de geordende lijst.
mydesign is een module geïnstantieerd met naam d0 in een andere module tb_top. Poorten worden in een bepaalde volgorde verbonden, bepaald door de positie van die poort in de poortlijst van de module-declaratie. Bijvoorbeeld: b in de testbench is verbonden met y van het ontwerp, simpelweg omdat beide op de tweede positie in de poortlijst staan.
module mydesign ( input x, y, z, output o); endmodule
module tb_top;
wire [1:0] a; wire b, c;
mydesign d0 (a[0], b, a[1], c);
// a[0] pos 1 -> x
// b pos 2 -> y
// a[1] pos 3 -> z
// c pos 4 -> o
endmoduleVolgorde van poorten in het ontwerpmodule moet bekend zijn voor correcte verbinding.
Dit is ongemakkelijk omdat de volgorde kan veranderen als een nieuwe poort wordt toegevoegd of bij veel poorten.
Poortverbinding bij naam
Een betere manier is om poorten expliciet te koppelen met hun poortnaam.
De punt . duidt aan dat de poortnaam na de punt bij het ontwerp hoort. De signaalnaam waaraan de ontwerp-poort verbonden moet worden, volgt tussen haakjes ( ).
module design_top;
wire [1:0] a; wire b, c;
mydesign d0 (
.x (a[0]), // signaal "x" in mydesign verbonden met "a[0]" in design_top
.y (b), // signaal "y" in mydesign verbonden met "b" in design_top
.z (a[1]),
.o (c));
endmoduleHet wordt aanbevolen elke poortverbinding op een aparte regel te zetten, zodat compilatiefouten de juiste regelnummer aangeven. Dit is veel makkelijker te debuggen dan allemaal op één regel.
Omdat verbindingen bij naam gemaakt worden, is de volgorde irrelevant. Meerdere module-instantie poortverbindingen zijn niet toegestaan.
module design_top;
mydesign d0 (
.x (a[0]),
.z (a[1]), // z op tweede positie oké door expliciete verbinding
.y (a[1]),
.x (b), // illegaal - x al verbonden met a[0]
.o (c));
endmoduleNiet-verbonden / zwevende poorten
Poorten die niet verbonden zijn met een draad in de instantiërende module, krijgen een waarde van hoog-impedantie.
module design_top;
mydesign d0 (
// x is input en niet verbonden, dus a[0] zal Z zijn
.y (a[1]),
.z (a[1]),
.o ()); // o heeft geldige waarde in mydesign maar niet verbonden met "c" in design_top, dus c zal Z zijn
endmoduleVoorbeeld
Laten we het schuifregister-voorbeeld nemen en sommige poorten loslaten.
module shift_reg ( input d, input clk, input rstn, output q);
wire [2:0] q_net;
dff u0 (.d(d), .clk(clk), .rstn(rstn), .q(q_net[0]));
dff u1 (.d(q_net[0]), .clk(clk), .rstn(rstn), .q()); // Output q zwevend
dff u2 (.d(q_net[1]), .clk(clk), .rstn(rstn), .q()); // Output q zwevend
dff u3 (.d(q_net[2]), .clk(clk), .rstn(rstn), .q(q));
endmoduleIn het RTL-schema na synthetise zijn de uitgangen van instanties u1 en u2 niet verbonden. Omdat de invoer d van u2 en u3 nu verbonden zijn met nets die niet gedreven worden, worden ze gegrond.
In simulaties worden dergelijke niet-verbonden poorten aangeduid als hoog-impedantie ('hZ), typisch in golven als een oranje verticale lijn in het midden.
Alle poortdeclaraties zijn impliciet gedeclareerd als wire, dus de poortrichting is voldoende. Echter output poorten die waarden moeten opslaan, moeten als reg datatype gedeclareerd worden en kunnen in procedurale blokken als always en initial gebruikt worden.
Poorten van type input of inout kunnen niet als reg gedeclareerd worden omdat ze continu van buitenaf gedreven worden en geen waarden hoeven op te slaan, maar veranderingen in externe signalen zo snel mogelijk moeten weerspiegelen. Het is legaal om twee poorten met verschillende vectorformaten te verbinden, maar de kleinere vector prevaleert en de overige bits van de bredere poort worden genegeerd.
// Case #1 : Inputs zijn standaard impliciet type "wire"
module des0_1 (input wire clk ...); // wire hoeft niet gespecificeerd
module des0_2 (input clk, ...); // Standaard clk is wire
// Case #2 : Inputs kunnen niet van type reg zijn
module des1 (input reg clk, ...); // Illegaal: inputs kunnen niet reg zijn
// Case #3: Twee modules met variërende poortbreedtes
module des2 (output [3:0] data, ...); // 4-bit vector output
module des3 (input [7:0] data, ...); // 8-bit vector input
module top ( ... );
wire [7:0] net;
des2 u0 ( .data(net) ... ); // Bovenste 4-bits van net zijn ongedreven
des3 u1 ( .data(net) ... );
endmodule
// Case #4 : Outputs kunnen niet verbonden worden met reg in parent module
module top_0 ( ... );
reg [3:0] data_reg;
des2 ( .data(data) ...); // Illegaal: output poort verbonden met reg-type signaal "data_reg"
endmodule
Verilog
- Verilog beheersen: Complete tutorial voor digitale schakelingontwerp
- Verilog concatenatie beheersen: Een praktische gids
- Verilog Essentials: Een beknopt overzicht
- Verilog-toewijzingen begrijpen: types, syntax en best practices
- Verilog Blocking vs. Non-Blocking: belangrijkste verschillen uitgelegd
- Verilog-functies beheersen voor efficiënt RTL-ontwerp
- Verilog Tasks begrijpen: Functions vs Tasks, Syntax en Gebruik
- Verilog Hiërarchische Referentie Scope: Naamconflicten in Modules en Blokken vermijden
- Verilog Clock Generator: Ontwerp & Implementatie Gids
- Verilog Math Functions beheersen voor efficiënt Hardware-ontwerp
- Verilog $timeformat beheersen: Precisie en Weergave-controle