- 12. Иерархические структуры
- 12.1 Модули
- 12.1.1 Модули верхнего уровня
- 12.1.2 Объявление экземпляра модуля
- 12.2 Переопределение значений параметров модуля
- 12.2.1 Объявление defparam
- 12.2.2 Присвоение значения параметра экземпляра модуля
- 12.2.2.1 Присвоение значения параметра по упорядоченному списку
- 12.2.2.2 Присвоение значения параметра по имени
- 12.2.3 Зависимость параметров

12. Иерархические структуры
Verilog HDL поддерживает иерархическую структуру описания аппаратного обеспечения, позволяя модулям встраиваться в другие модули. Модули более высокого уровня создают экземпляры модулей более низкого уровня и взаимодействуют с ними через порты ввода, вывода и двунаправленные порты. Эти порты ввода/вывода (I/O) модулей могут быть скалярными или векторными.
В качестве примера иерархии модулей рассмотрим систему, состоящую из печатных плат (PCB). Система будет представлена как модуль верхнего уровня и будет создавать экземпляры модулей, представляющих платы. Модули плат, в свою очередь, создают экземпляры модулей, представляющих интегральные схемы (ИС). ИС, в свою очередь, могут создавать экземпляры таких модулей, как fip-flop, мультиплексоры и АЛУ.
Чтобы описать иерархию модулей, пользователь предоставляет текстовые определения различных модулей. Каждое определение модуля стоит отдельно. Определения не вложены друг в друга. Объявления внутри определений модулей создают экземпляры других модулей, таким образом описывая иерархию.
12.1 Модули
В этом подпункте приводится формальный синтаксис определения модуля, а затем синтаксис экземпляра модуля, а также пример определения модуля и экземпляра модуля.
Определение модуля должно быть заключено между ключевыми словами module и endmodule. Идентификатор, следующий за ключевым словом module, должен быть именем определяемого модуля. Необязательный список определений параметров должен содержать упорядоченный список параметров модуля. Необязательный список портов или деклараций портов должен определять упорядоченный список портов для модуля. Порядок, используемый при определении списка параметров в списке module_parameter_port_list и в списке портов, может иметь значение при объявлении экземпляра модуля (см. 12.2.2.1 и 12.3.5). Идентификаторы в этом списке должны быть объявлены в операторах input, output и inout определении модуля. Порты, объявленные в списке объявлений портов, не должны быть повторно объявлены в теле модуля. Элементы модуля определяют, что представляет собой модуль, и включают множество различных типов объявлений и определений, многие из которых уже были представлены.
Ключевое слово macromodule может использоваться как взаимозаменяемое с ключевым словом module для определения модуля. Реализация может по-разному относиться к определениям модулей, начинающимся с ключевого слова macromodule.
module_declaration ::=
{ attribute_instance } module_keyword module_identifier [ module_parameter_port_list ]
list_of_ports ; { module_item }
endmodule
| { attribute_instance } module_keyword module_identifier [ module_parameter_port_list ]
[ list_of_port_declarations ] ; { non_port_module_item }
endmodule
module_keyword ::= module | macromodule
module_parameter_port_list ::=
# ( parameter_declaration { , parameter_declaration } )
list_of_ports ::= ( port { , port } )
list_of_port_declarations ::= ( port_declaration { , port_declaration } ) | ( )
port ::= [ port_expression ] | . port_identifier ( [ port_expression ] )
port_expression ::= port_reference | { port_reference { , port_reference } }
port_reference ::= port_identifier [ [ constant_range_expression ] ]
port_declaration ::= {attribute_instance} inout_declaration
| {attribute_instance} input_declaration
| {attribute_instance} output_declaration
module_item ::=
port_declaration ;
| non_port_module_item
module_or_generate_item ::=
{ attribute_instance } module_or_generate_item_declaration
| { attribute_instance } local_parameter_declaration ;
| { attribute_instance } parameter_override
| { attribute_instance } continuous_assign
| { attribute_instance } gate_instantiation
| { attribute_instance } udp_instantiation
| { attribute_instance } module_instantiation
| { attribute_instance } initial_construct
| { attribute_instance } always_construct
| { attribute_instance } loop_generate_construct
| { attribute_instance } conditional_generate_construct
module_or_generate_item_declaration ::=
net_declaration
| reg_declaration
| integer_declaration
| real_declaration
| time_declaration
| realtime_declaration
| event_declaration
| genvar_declaration
| task_declaration
| function_declaration
non_port_module_item ::=
module_or_generate_item
| generate_region
| specify_block
| { attribute_instance } parameter_declaration ;
| { attribute_instance } specparam_declaration
parameter_override ::= defparam list_of_defparam_assignments ;
Определения портов см. в разделе 12.3.
12.1.1 Модули верхнего уровня
Модули верхнего уровня — это модули, которые включены в исходный текст, но не появляются ни в одном объявлении экземпляра модуля, как описано в 12.1.2. Это применимо, даже если объявление экземпляра модуля появляется в блоке generate, который сам не объявляется экземпляр (см. 12.4). Модель должна содержать по крайней мере один модуль верхнего уровня.
12.1.2 Объявление экземпляра модуля
Объявление экземпляра позволяет одному модулю включать в себя копию другого модуля. Определения модулей не вложены друг в друга. Другими словами, одно определение модуля не должно содержать текст другого определения модуля в своей паре ключевых слов module-endmodule. Определение модуля создает место для другого модуля путем его объявления экземпляра. Оператор объявления экземпляра модуля создает один или несколько именованных экземпляров определенного модуля.
Например, модуль счетчика может объявить модуль D-flip-flop для создания нескольких экземпляров flip-flop.
В синтаксисе 12-2 приведен синтаксис для указания объявления экземпляра модулей.
module_instantiation ::=
module_identifier [ parameter_value_assignment ]
module_instance { , module_instance } ;
parameter_value_assignment ::=
# ( list_of_parameter_assignments )
list_of_parameter_assignments ::=
ordered_parameter_assignment { , ordered_parameter_assignment }
| named_parameter_assignment { , named_parameter_assignment }
ordered_parameter_assignment ::=
expression
named_parameter_assignment ::=
. parameter_identifier ( [ mintypmax_expression ] )
module_instance ::=
name_of_module_instance ( [ list_of_port_connections ] )
name_of_module_instance ::=
module_instance_identifier [ range ]
list_of_port_connections ::=
ordered_port_connection { , ordered_port_connection }
| named_port_connection { , named_port_connection }
ordered_port_connection ::=
{ attribute_instance } [ expression ]
named_port_connection ::=
{ attribute_instance } . port_identifier ( [ expression ] )
Объявление экземпляров модулей может содержать спецификацию диапазона. Это позволяет создать массив экземпляров. Массив экземпляров описан в разделе 7.1. Синтаксис и семантика массивов экземпляров, определенные для вентилей и примитивов, применимы и к модулям.
Один или несколько экземпляров модуля (идентичные копии модуля) могут быть объявлены одном операторе экземпляре модуля.
Список соединений портов должен быть предоставлен только для модулей, определенных с портами. Круглые скобки, однако, всегда обязательны. Когда список соединений портов приводится с использованием метода упорядоченного соединения портов, первый элемент списка должен соединяться с первым портом, объявленным в модуле, второй — со вторым портом и так далее. Более подробное обсуждение портов и правил подключения портов см. в разделе 12.3.
Соединение может быть простой ссылкой на переменную или идентификатор сети, выражением или пробелом. Выражение может использоваться для подачи значения на входной порт модуля. Пустое соединение порта должно представлять ситуацию, когда порт не должен быть соединен.
При подключении портов по имени, неподключенный порт можно указать либо пропуская его в списке портов, либо не указывая выражения в круглых скобках [т.е. .port_name ()].
Например:
// Модуль нижнего уровня:
// модульное описание схемы nand flip-flop
module ffnand (q, qbar, preset, clear);
output q, qbar; //объявляет 2 выходные сети схемы
input preset, clear; //объявляет 2 входные сети схемы
// объявление двух nand-гейтов и их взаимосвязей
nand g1 (q, qbar, preset),
g2 (qbar, q, clear);
endmodule
// Модуль более высокого уровня:
// описание формы сигнала для флип-флопа nand
module ffnand_wave;
wire out1, out2; //выходы из схемы
reg in1, in2; //переменные для управления схемой
parameter d = 10;
// инстанцируйте цепь ffnand, назовите ее "ff",
// и укажите взаимосвязи портов ввода-вывода ffnand
ffnand ff(out1, out2, in1, in2);
// определить форму волны для стимулирования схемы
initial begin
#d in1 = 0; in2 = 1;
#d in1 = 1;
#d in2 = 0;
#d in2 = 1;
end
endmodule
// описание формы волны для тестирования
// nand flip-flop, без выходных портов
module ffnand_wave;
reg in1, in2; //переменные для управления схемой
parameter d = 10;
// сделайте две копии схемы ffnand
// ff1 имеет qbar неподключенных, ff2 имеет q неподключенных
ffnand ff1(out1, , in1, in2),
ff2(.qbar(out2), .clear(in2), .preset(in1), .q());
// ff3(.q(out3),.clear(in1),,,); является неправльным
// определить форму волны для стимулирования схемы
initial begin
#d in1 = 0; in2 = 1;
#d in1 = 1;
#d in2 = 0;
#d in2 = 1;
end
endmodule
12.2 Переопределение значений параметров модуля
Существует два различных способа определения параметров. Первый — это module_parameter_port_list(список параметров порта) (см. 12.1), а второй — как module_item(элемент модуля) (см. 4.10). Объявление модуля может содержать определения параметров одного или обоих типов или не содержать определений параметров.
Параметр модуля может иметь спецификацию типа и спецификацию диапазона. Влияние переопределения параметров на тип и диапазон параметра должно соответствовать следующим правилам:
- Объявление параметра без указания типа или диапазона должно по умолчанию соответствовать типу и диапазону окончательного переопределяемого значения, присвоенного параметру.
- Параметр со спецификацией диапазона, но без спецификации типа, должен соответствовать диапазону объявления параметра и быть беззнаковым. Значение переопределения должно быть преобразовано к типу и диапазону параметра.
- Параметр со спецификацией типа, но без спецификации диапазона, должен иметь указанный тип. Переопределенное значение должно быть преобразовано к типу параметра. Подписанный параметр должен по умолчанию соответствовать диапазону конечного значения переопределения, присвоенного параметру.
- Параметр с подписанной спецификацией типа и спецификацией диапазона должен быть подписан и должен быть диапазоном его объявления. Значение переопределения должно быть преобразовано к типу и диапазону параметра.
module generic_fifo
#(parameter MSB=3, LSB=0, DEPTH=4)
//Эти параметры могут быть переопределены
(input [MSB:LSB] in,
input clk, read, write, reset,
output [MSB:LSB] out,
output full, empty );
localparam FIFO_MSB = DEPTH*MSB;
localparam FIFO_LSB = LSB;
// Эти параметры являются локальными и не могут быть переопределены.
// На них можно повлиять, изменив публичные параметры
// выше, и модуль будет работать правильно.
reg [FIFO_MSB:FIFO_LSB] fifo;
reg [LOG2(DEPTH):0] depth;
always @(posedge clk or reset) begin
casex ({read,write,reset})
// реализация fifo
endcase
end
endmodule
Существует два способа изменения нелокальных значений параметров: оператор defparam, который позволяет присваивать параметры, используя их иерархические имена, и присвоение значения параметру экземпляра модуля, которое позволяет присваивать значения в строке во время объявления экземпляра модуля. Если назначение defparam конфликтует с параметром экземпляра модуля, то параметр в модуле примет значение, указанное в defparam. Назначение значения параметра экземпляра модуля происходит в двух формах: по упорядоченному списку или по имени. Следующие два подраздела описывают эти два метода.
Существует два вида объявления параметров. Первый вид объявления параметров имеет спецификацию типа и/или диапазона, а второй — нет. Когда значение параметра без указания типа и диапазона переопределяется, параметр принимает размер и тип переопределяемого параметра.
Когда переопределяется типизированный и/или диапазонный параметр, новое значение преобразуется к типу и размеру назначения и присваивается этому параметру.
module foo(a,b);
real r1,r2;
parameter [2:0] A = 3'h2;
parameter B = 3'h2;
initial begin
r1 = A;
r2 = B;
$display("r1 is %f r2 is %f",r1,r2);
end
endmodule // foo
module bar;
wire a,b;
defparam f1.A = 3.1415;
defparam f1.B = 3.1415;
foo f1(a,b);
endmodule // bar
Параметр A является типизированным и/или ранжированным параметром. Поэтому при переопределении его значения параметр сохраняет свой исходный тип и знак. Поэтому defparam параметра f1. A со значением 3.1415 выполняется путем преобразования числа с плавающей точкой 3.1415 в число с фиксированной точкой 3, а затем младшие 3 бита числа 3 присваиваются параметру A.
Параметр B не является типизированным и/или диапазоном. Поэтому, когда его значение переопределяется, тип и диапазон параметра принимают тип и диапазон нового значения. Таким образом, defparam f1. B со значением 3.1415 заменяет текущее значение B 3’h2 на число с плавающей точкой 3.1415.
12.2.1 Объявление defparam
С помощью оператора defparam значения параметров могут быть изменены в любом экземпляре модуля во всем проекте с использованием иерархического имени параметра. Иерархические имена см. в разделе 12.5.
Однако оператор defparam в иерархии в или под экземпляром генерирующего блока (см. 12.4) или массивом экземпляров (см. 7.1 и 12.1.2) не должен изменять значение параметра вне этой иерархии.
genvar i;
generate
for (i = 0; i < 8; i = i + 1) begin : somename
flop my_flop(in[i], in1[i], out1[i]);
defparam somename[i+1].my_flop.xyz = i ;
end
endgenerate
Аналогично, оператор defparam в одном экземпляре массива экземпляров не может быть адресован параметру в другом экземпляре массива.
Выражение в правой части присваивания defparam должно быть константным выражением, включающим только числа и ссылки на параметры. Ссылающиеся параметры (в правой части defparam) должны быть объявлены в том же модуле, что и оператор defparam.
Оператор defparam особенно полезен для группировки всех назначений переопределения значений параметров в одном модуле.
В случае нескольких defparam для одного параметра, параметр принимает значение последнего оператора defparam, встретившегося в исходном тексте. Если defparam встречается в нескольких исходных файлах, например, при поиске в библиотеке, то defparam, из которого параметр принимает свое значение, не определяется.
module top;
reg clk;
reg [0:4] in1;
reg [0:9] in2;
wire [0:4] o1;
wire [0:9] o2;
vdff m1 (o1, in1, clk);
vdff m2 (o2, in2, clk);
endmodule
module vdff (out, in, clk);
parameter size = 1, delay = 1;
input [0:size-1] in;
input clk;
output [0:size-1] out;
reg [0:size-1] out;
always @(posedge clk)
# delay out = in;
endmodule
module annotate;
defparam
top.m1.size = 5,
top.m1.delay = 10,
top.m2.size = 10,
top.m2.delay = 20;
endmodule
Модуль annotate имеет оператор defparam, который переопределяет значения параметров size и delay для экземпляров m1 и m2 в модуле верхнего уровня top. Модули top и annotate считаются модулями верхнего уровня.
12.2.2 Присвоение значения параметра экземпляра модуля
Альтернативным методом присвоения значений параметрам внутри экземпляров модуля является использование одной из двух форм присвоения значения параметра экземпляра модуля. Это присвоение по упорядоченному списку и присвоение по имени. Эти два типа присвоения значений параметров экземпляра модуля не должны смешиваться. Присвоение параметров конкретному экземпляру модуля должно быть полностью по порядку или полностью по имени.
Назначение значений параметров экземпляра модуля упорядоченным списком похоже по виду на назначение значений задержки экземплярам строба, а назначение по имени похоже на подключение портов модуля по имени. Он присваивает значения для конкретных экземпляров модуля любым параметрам, которые были в определении этого модуля.
Параметр, объявленный именованном блоке, задаче или функции, может быть переопределен только с помощью оператора defparam. Однако, если значение параметра зависит от второго параметра, то переопределение второго параметра приведет к обновлению значения и первого параметра (см. 12.2.3).
12.2.2.1 Присвоение значения параметра по упорядоченному списку
Порядок присвоения значений параметров экземпляра модуля по упорядоченному списку должен соответствовать порядку объявления параметров модуле. При использовании этого метода нет необходимости присваивать значения всем параметрам в модуле. Однако, пропустить какой-либо параметр невозможно. Поэтому, чтобы присвоить значения подмножеству параметров, объявленных в модуле, объявления параметров, составляющих это подмножество, должны предшествовать объявлениям остальных параметров. Альтернативой является присвоение значений всем параметрам, но использование значения по умолчанию (того же значения, которое было присвоено при объявлении параметра в определении модуля) для тех параметров, которые не нуждаются в новых значениях.
Например:
module tb1;
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 с присвоением значений параметров
// по упорядоченному списку
// mod_a имеет новые значения параметров size=10 и delay=15
// mod_b имеет параметры по умолчанию (size=5, delay=1)
// mod_c имеет один стандартный size=5 и один новый delay=12
// Для того, чтобы изменить значение задержки,
// необходимо также указать значение размера (по умолчанию).
// mod_d имеет новое значение параметра size=10.
// задержка сохраняет свое значение по умолчанию
vdff #(10,15) mod_a (.out(out_a), .in(in_a), .clk(clk));
vdff mod_b (.out(out_b), .in(in_b), .clk(clk));
vdff #( 5,12) mod_c (.out(out_c), .in(in_c), .clk(clk));
vdff #(10) mod_d (.out(out_d), .in(in_d), .clk(clk));
endmodule
module vdff (out, in, clk);
parameter size=5, delay=1;
output [size-1:0] out;
input [size-1:0] in;
input clk;
reg [size-1:0] out;
always @(posedge clk)
#delay out = in;
endmodule
module my_mem (addr, data);
parameter addr_width = 16;
localparam mem_size = 1 << addr_width;
parameter data_width = 8;
...
endmodule
module top;
...
my_mem #(12, 16) m(addr,data);
endmodule
12.2.2.2 Присвоение значения параметра по имени
Присвоение параметра по имени заключается в явном связывании имени параметра и его нового значения. Имя параметра должно быть именем, указанным в объявленном экземпляре модуля в его определении.
При использовании этого метода нет необходимости присваивать значения всем параметрам в модуле. Необходимо указать только те параметры, которым присваиваются новые значения.
Выражение параметра является необязательным, чтобы экземпляр модуля мог документировать существование параметра, ничего ему не присваивая. Круглые скобки обязательны, и в этом случае параметр сохраняет свое значение по умолчанию. После того, как параметру присвоено значение, другое присвоение этому имени параметра не допускается.
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 с присвоением значений параметров по имени
// mod_a имеет новые значения параметров size=10 и delay=15
// mod_b имеет параметры по умолчанию (size=5, delay=1)
// mod_c имеет один стандартный size=5 и один новый delay=12
// mod_d имеет новое значение параметра size=10.
//задержка сохраняет свое значение по умолчанию
vdff #(.size(10),.delay(15)) mod_a (.out(out_a),.in(in_a),.clk(clk));
vdff mod_b (.out(out_b),.in(in_b),.clk(clk));
vdff #(.delay(12)) mod_c (.out(out_c),.in(in_c),.clk(clk));
vdff #(.delay( ),.size(10) ) mod_d (.out(out_d),.in(in_d),.clk(clk));
endmodule
module vdff (out, in, clk);
parameter size=5, delay=1;
output [size-1:0] out;
input [size-1:0] in;
input clk;
reg [size-1:0] out;
always @(posedge clk)
#delay out = in;
endmodule
module tb3;
// декларации и код
// легальная смесь экземпляра с позиционными параметрами и
// другой экземпляр с именованными параметрами
vdff #(10, 15) mod_a (.out(out_a), .in(in_a), .clk(clk));
vdff mod_b (.out(out_b), .in(in_b), .clk(clk));
vdff #(.delay(12)) mod_c (.out(out_c), .in(in_c), .clk(clk));
endmodule
// экземпляр mod_a с НЕПРАВИЛЬНОЙ смесью назначений параметров vdff
vdff #(10, .delay(15)) mod_a (.out(out_a), .in(in_a), .clk(clk));
12.2.3 Зависимость параметров
parameter
word_size = 32,
memory_size = word_size * 4096;