Menu

Instantiasi Module Verilog: Menghubungkan Sub-Module

Cara membuat instance sebuah module di dalam module lain, perbedaan koneksi port bernama dan berdasarkan posisi, dan pola banyak instance yang akan kamu pakai untuk membangun desain sungguhan.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Module di Dalam Module

Desain Verilog adalah pohon module. Module tingkat atas (testbench-mu, atau wrapper tingkat atas sebuah chip) membuat instance module di tingkat bawahnya, yang membuat instance module di tingkat yang lebih bawah lagi, terus sampai ke primitif gerbang dari vendor. Instantiasi adalah sintaks untuk susunan bertingkat itu.

Kamu sudah pernah melihat bentuknya, karena kita memakainya di Module Pertamamu:

and_gate dut(.a(a), .b(b), .y(y));

Baris itu mencetak satu instance dari and_gate, menamainya dut, dan menghubungkan port-nya ke sinyal lokal. Mari kita uraikan setiap bagiannya.

Bentuk Sebuah Instantiasi

module_name instance_name (port_connections);
  • module_name harus cocok dengan nama dari sebuah deklarasi module di suatu tempat dalam proyekmu. Verilog peka huruf besar-kecil.
  • instance_name adalah label yang kamu pilih, biasanya menggambarkan peran instance ini. Kamu akan memakainya di path hierarkis dan tampilan waveform.
  • port_connections menghubungkan port instance ke sinyal lokal. Ada dua cara menuliskannya.

Koneksi Port Bernama (Pakai yang Ini)

Bentuk bernama terlihat seperti ini:

my_module instance_name(
    .clk    (clk),
    .reset  (reset_n),
    .data_in(in_bus),
    .data_out(out_bus),
    .valid  (out_valid)
);

Setiap pasangan .port(signal) berarti "hubungkan port instance ini yang bernama port ke sinyal lokal bernama signal". Urutannya tidak penting. Kalau kamu menambahkan port baru ke deklarasi module, instantiasi yang sudah ada tidak rusak, selama kamu memberi port baru itu nilai default atau memperbarui setiap tempat pemakaiannya.

Dua catatan praktis:

  • Nama port (di kiri tanda kurung) harus persis sama dengan deklarasi module.
  • Nama sinyal (di dalam tanda kurung) bersifat lokal di tempat instantiasi berada, biasanya module induknya.

Kalau sebuah port tidak dihubungkan, biarkan bagian dalamnya kosong: .optional_port(). Sinyalnya mengambang (z) di dalam instance. Beberapa tool sintesis memberi peringatan; kebanyakan menerimanya.

Koneksi Port Berdasarkan Posisi (Hindari yang Ini)

Bentuk yang lebih ringkas mencantumkan sinyal sesuai urutan daftar port:

my_module instance_name(clk, reset_n, in_bus, out_bus, out_valid);

Ini lebih pendek tapi rapuh. Ubah urutan daftar port module (refactor sungguhan yang memang terjadi), dan setiap instantiasi berdasarkan posisi diam-diam salah sambung. Jangan pakai gaya berdasarkan posisi kecuali daftar port hanya berisi satu atau dua anggota dan kemungkinan besar tidak akan pernah berubah.

Kapan gaya ini masih bisa diterima: module utilitas kecil di mana urutan port adalah bagian dari API-nya. Gerbang dua input boleh berdasarkan posisi. Kontroler memori dengan 30 port sama saja dengan mencari masalah.

Contoh Hierarki Lengkap

Itu adalah hierarki tiga tingkat yang sungguhan: test → full_adder → dua instance half_adder. Setiap instance punya salinan gerbangnya sendiri dari dalam half_adder; tool sintesis akan menghasilkan satu rangkaian untuk setiap instantiasi.

Banyak Instance dari Module yang Sama

Saat kamu membuat instance module yang sama beberapa kali, setiap instance adalah hardware yang independen. Mereka tidak berbagi state. Mereka tidak berbagi gerbang. Bayangkan setiap instance sebagai salinan baru yang dicetak dari desainnya.

adder add0(.a(a0), .b(b0), .sum(s0));
adder add1(.a(a1), .b(b1), .sum(s1));
adder add2(.a(a2), .b(b2), .sum(s2));
adder add3(.a(a3), .b(b3), .sum(s3));

Itu adalah empat adder terpisah yang berjalan paralel. Kalau adder berisi sebuah register, setiap instance akan punya salinan register itu sendiri, dengan state-nya sendiri.

Loop generate: Mencetak Hardware Berulang

Menulis empat instance secara manual masih tidak masalah. Menulis 64 itu melelahkan. Blok generate membiarkan elaborator mengetikkannya untukmu:

Tiga potong sintaks baru:

  • genvar i mendeklarasikan variabel loop yang bisa dipakai di generate. Ini bukan sinyal saat runtime; ia hanya ada saat elaborasi.
  • generate ... endgenerate membungkus loop-nya. Beberapa tool menerima loop generate tanpa kata kunci generate yang eksplisit, tapi menuliskannya membuat maksudmu jelas.
  • begin : invert_loop memberi label pada scope generate. Label itu menjadi bagian dari nama hierarkis setiap instance yang dihasilkan (dut.invert_loop[0].u_inv, dut.invert_loop[1].u_inv, dan seterusnya).

Synthesizer membuka loop itu dan menghasilkan WIDTH salinan bit_inverter. Setiap salinan adalah hardware yang independen.

Menimpa Parameter saat Instantiasi

Kalau module punya parameter, kamu bisa menimpanya dengan #(.PARAM(value)) di antara nama module dan nama instance:

counter #(.WIDTH(16)) c16 (.clk(clk), .count(out16));
counter #(.WIDTH(32)) c32 (.clk(clk), .count(out32));

Kedua instance memakai sumber counter yang sama tapi lebarnya berbeda. Sintaksnya sudah kita bahas di Parameter; ia masuk dengan rapi ke dalam instantiasi.

Nama Hierarkis

Begitu kamu punya hierarki, setiap sinyal punya path hierarkis:

test.dut.ha0.sum

Cara membacanya: di module test, di dalam instance dut, di dalam instance ha0, sinyal bernama sum. Kamu akan melihat path seperti ini di penampil waveform, pesan error, dan sesekali di panggilan $display yang mengintip jauh ke dalam sub-module dari testbench:

$display("internal carry1 = %b", dut.carry1);

Referensi hierarkis seperti itu hanya untuk testbench dan debug. RTL yang bisa disintesis tidak menjangkau ke dalam module lain.

Kesalahan yang Umum

Nama port tidak cocok. .clk_in(clk) menghubungkan clk lokal ke port bernama clk_in. Kalau port module sebenarnya bernama clk, parser akan memberi tahu (beberapa tool lebih jelas daripada yang lain).

Lebar port tidak cocok. Menghubungkan sinyal 4-bit ke port 8-bit diam-diam menambahkan nol di depan (zero-extend); kebalikannya diam-diam memotong bit. Kebanyakan tool memberi peringatan; kalau kamu tidak melihat peringatan, periksa lebih teliti.

Lupa # untuk parameter. counter (.WIDTH(8)) c(.clk(clk)) terlihat seperti penimpaan parameter, padahal bukan: parser mencoba memperlakukan (.WIDTH(8)) sebagai koneksi port dan gagal. Yang benar: counter #(.WIDTH(8)) c(.clk(clk)).

Memakai ulang nama instance. Dua instance tidak boleh punya nama yang sama dalam scope yang sama. Pesan error-nya biasanya jelas; godaan untuk copy-paste-lah yang menjebakmu.

Selanjutnya

Sekarang kamu bisa menghubungkan module-module menjadi hierarki sungguhan. Dokumen berikutnya, Penugasan Kontinu, melengkapi sisi struktural Verilog dan membahas lebih dalam pernyataan assign yang sudah kita pakai dengan bebas sejak bab pertama.

Pertanyaan yang sering diajukan

Bagaimana cara membuat instance module di Verilog?

Tulis nama module, lalu nama instance, lalu daftar koneksi port di dalam tanda kurung: my_module instance_name(.port(signal), ...);. Gaya yang paling umum memakai koneksi bernama (.port(signal)), yang dicocokkan berdasarkan nama port tanpa peduli urutan. Gaya berdasarkan posisi yang lebih ringkas (my_module instance(signal1, signal2)) bergantung pada urutan daftar port dan berbahaya untuk dirawat.

Apa perbedaan koneksi port bernama dan berdasarkan posisi?

Koneksi berdasarkan posisi mencantumkan sinyal dengan urutan yang sama seperti daftar port module: sinyal pertama terhubung ke port pertama, kedua ke kedua, dan seterusnya. Koneksi bernama memakai .port_name(signal_name) dan dicocokkan berdasarkan nama. Gaya bernama memang lebih panjang, tapi kebal terhadap perubahan urutan port dan langsung menjelaskan dirinya di tempat pemanggilan. Pakai gaya bernama untuk module dengan lebih dari dua atau tiga port.

Apakah module Verilog yang sama bisa dibuat instance-nya berkali-kali?

Bisa, dan justru itulah tujuannya. Setiap instance adalah hardware independen dengan state-nya sendiri. Kalau kamu punya module adder, kamu bisa membuat 64 instance-nya di unit SIMD, masing-masing dengan input berbeda. Loop generate adalah sintaks baku saat instance-instance itu mirip dan diberi indeks.

Apa itu blok generate di Verilog?

generate ... endgenerate adalah konstruksi saat compile yang mencetak hardware berulang. Loop for di dalam generate membuat N instance dari apa pun yang ada di isinya. generate berjalan saat elaborasi, sebelum simulasi dimulai: ini bukan loop saat runtime, melainkan generator kode untuk synthesizer.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai