Модули в Verilog

Содержание

Сводка

Модуль — это иерархическая единица Verilog описания аппаратного обеспечения. Модуль объявляется в глобальном пространстве через ключевое слово «module» и заканчивается ключевым словом «endmodule«. В объявления модуля входит идентификатор, порты(необязательно) и параметры(необязательно). Объявление портов может содержать входные, выходные и двунаправленные порты. Объявление параметров может содержать параметры любого типа. Модуль может входить в другой модуль, но объявляется в качестве экземпляра. Различают 2 вида модулей:

  1. Модуль верхнего уровня — это модуль, который не входит в состав другого модуля проекта в качестве экземпляра. Т.е. имеет одно объявление в глобальной области и содержит в себе экземпляры других модулей, если таковы есть.
  2. Модуль низкого уровня — это модуль объявляемый в глобальном пространстве и в качестве экземпляра в другом модуле.

Объявления в глобальном пространстве модуля верхнего и низкого уровня ничем не отличаются.

Синтаксис

Объявления модуля в глобальном пространстве

Существует 4 вида объявления модуля Verilog(см. 12) в глобальном пространстве. Поэтому объявление параметров модуля может происходить в строке модуля, так и внутри блока модуля. Такая же ситуация обстоит и с портами. Если объявление портов происходит внутри блока, то строке модуля прописывается список портов.

Синтаксис объявления модуля outside в глобальном пространстве приведен в синтаксисе 1.

module #(/* объявление параметров */) identifier (/* объявление портов */); // другие объявления внутри блока endmodule
Синтаксис 1 — Объявление модуля outside в глобальном пространстве.

Синтаксис объявления модуля inside в глобальном пространстве приведен в синтаксисе 2.

module identifier(/* список портов */); /* объявление параметров */ /* объявление портов*/ // другие объявления внутри блока endmodule
Синтаксис 2 — Объявление модуля в глобальном пространстве.

Объявления экземпляра модуля

Экземпляр модуля — это объявление модуля внутри блока другого модуля. Объявление портов и параметров имеет два вида: простая и точечная нотация. Если используется простая нотация, необходимо присваивать цепи и регистры в той последовательности, в какой объявлены порты или параметры. Если используется точечная нотация, то присваивание можно объявлять в любом порядке.

Синтаксис экземпляра модуля с простой нотацией приведен в синтаксисе 3.

identifier_module #(/* значения параметра через запятую */) identifier_module_local(/* список портов через запятую */);
Синтаксис 3 — Объявление экземпляра модуля.

identifier_module — это идентификатор модуля в глобальном пространстве, а identifier_module_local — это новый идентификатор модуля в локальном пространстве объявления.

Синтаксис экземпляра модуля с точечной нотацией приведен в синтаксисе 4.

identifier_module #(.PARAM_NAME(VALUE_PARAM), .PARAM_NAME(VALUE_PARAM), /* и тд */) identifier_module_local (.PORT_NAME(WIRE_NAME), .PORT_NAME(WIRE_NAME), /* и тд */);
Синтаксис 4 — Объявление экземпляра модуля.

Примеры объявления модуля

Примеры модуля в глобальном пространстве

Пример 1 — Объявления модуля памяти, использующий примитив FPGA для хранения данных. Данный модуль не использует регистры, а примитив. В данном случае имеется виду ресурс заложенный в микрочип, отдельный от логических клеток.
module TWISE_PORTS_RAM #( parameter BITS_DATA = 8, // кол-во битов в линии данных parameter BITS_ADDR = 6 // кол-во битов в линии адреса ) ( input wire [(BITS_DATA - 1): 0] data, // линия данных input wire [(BITS_ADDR - 1): 0] addr_we, // линия адреса для записи input wire [(BITS_ADDR - 1): 0] addr_re, // линия адреса для чтения input wire we, // линия включения записи input wire clk, // тактовый сигнал output reg [(BITS_DATA - 1): 0] q // линия выхода ); // регистр, использующий примитив FPGA reg [(BITS_DATA - 1): 0] ram [((2**BITS_ADDR) - 1): 0]; always @(posedge clk) begin // запись if (we) ram[addr_we] <= data; // чтение q <= ram[addr_re]; end endmodule

В этом модуле реализован механизм простой двухпортовой однотактовой памяти(см. 4.9.3 Память). При приходе восходящего фронта в тактовом сигнале clk и высоком сигнале записи write, происходит запись в память по адресу addr_we. При такой же ситуации тактовым сигналом, то происходит чтение с памяти по адресу addr_re. RTL модель показана на рисунке 1.

RTL однотакотовой двухпортовой памяти
Рисунок 1 — RTL однотакотовой двухпортовой памяти.
Пример 2 — Модуль с таким же функционалом, но с другой формой объявления.
module TWISE_PORTS_RAM(data, addr_we, addr_re, we, clk, q); parameter BITS_DATA = 8; // кол-во битов в линии данных parameter BITS_ADDR = 6; // кол-во битов в линии адреса input wire [(BITS_DATA - 1): 0] data; // линия данных input wire [(BITS_ADDR - 1): 0] addr_we; // линия адреса для записи input wire [(BITS_ADDR - 1): 0] addr_re; // линия адреса для чтения input wire we; // линия включения записи input wire clk; // тактовый сигнал output reg [(BITS_DATA - 1): 0] q; // линия выхода // регистр, использующий примитив FPGA reg [(BITS_DATA - 1): 0] ram [((2**BITS_ADDR) - 1): 0]; always @(posedge clk) begin // запись if (we) ram[addr_we] <= data; // чтение q <= ram[addr_re]; end endmodule

Примеры объявления экземпляра модуля

Пример 3 — Объявление экземпляра модуля с двумя видами нотацией.
module tb2; wire [9:0] out_a, out_d; wire [4:0] out_b, out_c; reg [9:0] in_a, in_d; reg [4:0] in_b, in_c; reg clk; vdff #(.delay(10), .size(15)) mod_a (.out(out_a), .in(in_a), .clk(clk)); // тоже самое в другом формате // vdff #(15, 10) mod_a (out_a, in_a, clk); endmodule module vdff (out, in, clk); parameter size=5, delay=1; // Объявление внутри блока endmodule