10. Задачи(task) и функции (function) в Verilog HDL

Описывает синтаксис 2-х видов объявления функции(function) и задач (task) в Verilog HDL, правил использования и вызова, а их сравнение.
Содержание
Превью(обложка) видео для поста 10. Задачи(task) и функции (function) в Verilog HDL

Задачи(task) и функции(function) обеспечивают возможность выполнения общих процедур из нескольких разных мест описания. Они также позволяют разбить большие процедуры на более мелкие, чтобы облегчить чтение и отладку исходных описаний. В этом пункте обсуждаются различия между задачами(task) и функциями(function), описывается, как определять и вызывать задачи и функции, а также приводятся примеры каждой из них.

10.1 Различия между task и function

Следующие правила отличают задачи(task) от функций(function):

  • function должна выполняться за одну единицу времени моделирования. task может содержать операторы, управляющие временем.
  • function не может включать задачу. task может включать другие задачи и функции.
  • function должна иметь по крайней мере один аргумент типа input и не должна иметь аргументов типа output или inout. task может иметь 0 или более аргументов любого типа.
  • function должна возвращать одно значение. task не должна возвращать значение.

Цель функции — реагировать на входное значение, возвращая одно значение. Задача может поддерживать несколько целей и может вычислять несколько значений результата. Однако, только аргументы типа output или inout передают значения результатов обратно после вызова задачи.

function используется в качестве операнда в выражении. Значение этого операнда является значением, возвращаемым функцией.

Например:

Для переключения байтов в 16-битном слове можно определить либо задачу, либо функцию. Задача(task) возвращает переключенное слово в выходном аргументе. Поэтому исходный код для включения задачи switch_bytes может выглядеть следующим образом:
switch_bytes(old_word, new_word);

Задача switch_bytes возьмет байты в old_word, изменит их порядок и поместит измененные байты в new_word.

Функция(function) переключения слов возвращает переключенное слово в качестве возвращаемого значения функции. Таким образом, вызов функции switch_bytes может выглядеть следующим образом:
new_word = switch_bytes (old_word);

10.2 Задачи(task) и создание условий для выполнения задач

Задача(task) должна быть включена из оператора, который определяет значения аргументов, передаваемых задаче, и переменные, которые получают результаты. Управление должно быть передано обратно процессу включения после завершения задачи. Таким образом, если задача(task) имеет внутри себя временные элементы управления, то время включения задачи может отличаться от времени возврата управления. Задача может разрешать другие задачи, которые, в свою очередь, могут разрешать еще другие задачи, причем количество разрешенных задач не ограничено.

Независимо от того, сколько задач было включено, управление не возвращается до тех пор, пока все включенные задачи не завершатся.

10.2.1 Декларации задач(task)

Синтаксис для определения задач приведен в Синтаксис 10-1.

task_declaration ::= task [ automatic ] task_identifier ; { task_item_declaration } statement_or_null endtask | task [ automatic ] task_identifier ( [ task_port_list ] ) ; { block_item_declaration } statement_or_null endtask task_item_declaration ::= block_item_declaration | { attribute_instance } tf_ input_declaration ; | { attribute_instance } tf_output_declaration ; | { attribute_instance } tf_inout_declaration ; task_port_list ::= task_port_item { , task_port_item } task_port_item ::= { attribute_instance } tf_input_declaration | { attribute_instance } tf_output_declaration | { attribute_instance } tf_inout_declaration tf_input_declaration ::= input [ reg ] [ signed ] [ range ] list_of_port_identifiers | input task_port_type list_of_port_identifiers tf_output_declaration ::= output [ reg ] [ signed ] [ range ] list_of_port_identifiers | output task_port_type list_of_port_identifiers tf_inout_declaration ::= inout [ reg ] [ signed ] [ range ] list_of_port_identifiers | inout task_port_type list_of_port_identifiers task_port_type ::= integer | real | realtime | time block_item_declaration ::= { attribute_instance } reg [ signed ] [ range ] list_of_block_variable_identifiers ; | { attribute_instance } integer list_of_block_variable_identifiers ; | { attribute_instance } time list_of_block_variable_identifiers ; | { attribute_instance } real list_of_block_real_identifiers ; | { attribute_instance } realtime list_of_block_real_identifiers ; | { attribute_instance } event_declaration | { attribute_instance } local_parameter_declaration ; | { attribute_instance } parameter_declaration ; list_of_block_variable_identifiers ::= block_variable_type { , block_variable_type } list_of_block_real_identifiers ::= block_real_type { , block_real_type } block_variable_type ::= variable_identifier { dimension } block_real_type ::= real_identifier { dimension }
Синтаксис 10-1-Синтаксис объявления задачи

Существует два альтернативных синтаксиса объявления задач(task).

Первый синтаксис должен начинаться с ключевого слова task, за которым следует необязательное ключевое слово automatic, затем имя задачи и точка с запятой, и заканчиваться ключевым словом endtask. Ключевое слово automatic объявляет автоматическую задачу, которая является реентерабельной, при этом все объявления задач распределяются динамически для каждой параллельной записи задачи. В объявлениях элементов задачи(task) может быть указано следующее:

  • Входные(input) аргументы
  • Выходные(output) аргументы
  • Двунаправленные(inout) аргументы
  • Все типы данных, которые могут быть объявлены в процедурном блоке

Второй синтаксис должен начинаться с ключевого слова task, за которым следует имя задачи и заключенный в круглые скобки список task_port_list(список объявляемых портов). task_port_list должен состоять из нуля или более task_port_item(объявляемый порт), разделенных запятыми. После закрывающей круглой скобки должна быть точка с запятой. Далее следует тело задачи, а затем ключевое слово endtask.

В обоих синтаксисах объявления портов должны иметь тот же синтаксис, что и объявления tf_input_declaration, tf_output_declaration и tf_inout_declaration(т.е. объявления входных, выходных и двунаправленных портов), как описано в Синтаксисе 10-1 выше.

Задачи без необязательного ключевого слова automatic являются статическими задачами, в которых все объявленные элементы выделяются статически. Эти элементы должны быть общими для всех параллельно выполняющихся задач. Задачи с необязательным ключевым словом automatic — это автоматические задачи. Все элементы, объявленные внутри автоматических задач, выделяются динамически для каждого вызова. К элементам автоматических задач нельзя обращаться с помощью иерархических ссылок. Автоматические задачи могут быть вызваны с помощью их иерархического имени.

10.2.2 Вызов задач(task) и передача аргументов

Оператор вызова задач(task) должен передавать аргументы в виде списка выражений, разделенных запятыми и заключенных в круглые скобки. Формальный синтаксис оператора вызова задач(task) приведен в Синтаксисе 10-2.

task_enable ::= hierarchical_task_identifier [ ( expression { , expression } ) ] ;
Синтаксис 10-2-Синтаксис для заявления о разрешении задачи

Если объявление задачи(task) не имеет аргументов, список аргументов не должен быть представлен в вызове объявления задачи(task). В противном случае должен быть упорядоченный список выражений, длина и порядок которого соответствуют длине и порядку списка аргументов в объявлении задачи. Нулевое выражение не должно использоваться в качестве аргумента в утверждении вызова задачи(task).

Если аргумент в задаче объявлен как input, то соответствующее выражение может быть любым выражением. Порядок оценки выражений в списке аргументов не определен. Если аргумент объявлен как output или inout, то выражение должно быть ограничено выражением, которое допустимо в левой части процедурного присваивания (см. 9.2). Этому требованию удовлетворяют следующие элементы:

  • переменные reg, integer, real, realtime и time
  • Ссылки на память
  • Конкатенации переменных reg, integer и time
  • Конкатенации ссылок на память
  • Битовая выборка и частичная выборка reg, integer и time переменных

При выполнении оператора вызова задачи(task) входные значения из выражений, перечисленных в операторе вызова, передаются в аргументы, объявленной в задаче. Выполнение возврата из задачи(task) должно передать значения из аргументов типа output и inout задачи в соответствующие переменные в операторе вызова задачи. Все аргументы задачи(task) должны передаваться по значению, а не по ссылке (то есть указателю на значение).

Например:

Пример 1 — Следующий пример иллюстрирует основную структуру определения задачи(task) с пятью аргументами:
task my_task; input a, b; inout c; output d, e; begin . . . // объявления, которые выполняют работу задачи . . . c = foo1; // присваивания, инициализирующие результирующие регистры d = foo2; e = foo3; end endtask
Или, используя вторую форму объявления задачи, задача может быть определена следующим образом:
task my_task (input a, b, inout c, output d, e); begin . . . // объявления, которые выполняют работу задачи . . . c = foo1; // присваивания, инициализирующие результирующие регистры d = foo2; e = foo3; end endtask
Следующее объявления позволяет выполнить задание:
my_task (v, w, x, y, z);
Аргументы включения задачи (v, w, x, y и z) соответствуют аргументам (a, b, c, d и e), определенным задачей(task). В момент вызова задачи аргументы типа input и inout (a, b и c) получают значения, переданные в v, w и x. Таким образом, выполнение вызова задачи эффективно вызывает следующие назначения:
a = v; b = w; c = x;
В рамках обработки задачи определение задачи my_task помещает вычисленные значения результатов в c, d и e. Когда задача завершается, выполняются следующие назначения для возврата вычисленных значений вызывающему процессу:
x = c; y = d; z = e;
Пример 2 — Следующий пример иллюстрирует использование задач(task), описывая светофор:
module traffic_lights; reg clock, red, amber, green; parameter on = 1, off = 0, red_tics = 350, amber_tics = 30, green_tics = 200; // инициализация цветов initial red = off; initial amber = off; initial green = off; always begin // последовательность для управления цветами red = on; // включение красного света light(red, red_tics); // и ожидание green = on; // включение зеленого света light(green, green_tics); // и ожидание amber = on; // включение янтарный света light(amber, amber_tics); // и ожидание end // задание на ожидание положительных фронтов часов 'tics' // перед выключением "цветного" света. task light; output color; input [31:0] tics; begin repeat (tics) @ (posedge clock); color = off; // выключить свет end endtask always begin // генерация тактового сигнала #100 clock = 0; #100 clock = 1; end endmodule // traffic_lights.

10.2.3 Использование памяти задачи и одновременная активация

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

Переменные, объявленные в статических задачах(task), включая аргументы типа input, output и inout, должны сохранять свои значения между вызовами. Они должны быть инициализированы значением инициализации по умолчанию, как описано в п. 4.2.2.

Переменные, объявленные в автоматических задачах, включая аргументы типа output, должны инициализироваться значением инициализации по умолчанию всякий раз, когда выполнение входит в их область действия. Аргументы типа input и inout должны инициализироваться значениями, переданными из выражений, соответствующих этим аргументам, перечисленных в операторах вызова задачи(task).

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

  • Им не должны присваиваться значения с помощью неблокирующих присвоений или процедурных непрерывных присвоений.
  • На них нельзя ссылаться в процедурных непрерывных заданиях или процедурных объявлениях о силе.
  • На них нельзя ссылаться в элементах управления событиями внутри назначения в неблокирующих назначениях.
  • Они не должны отслеживаться с помощью системных задач, таких как $monitor и $dumpvars.

10.3 Завершение именованных блоков и задач

Оператор disable предоставляет возможность завершить действия, связанные с параллельно активными процедурами, сохраняя при этом структурированный характер процедурных описаний Verilog HDL. Оператор disable предоставляет механизм для завершения задачи(task) до выполнения всех ее операторов, прерывания циклического оператора или пропуска операторов для продолжения очередной итерации циклического оператора. Он полезен для обработки условий исключения, таких как аппаратные прерывания и глобальные сбросы.

Оператор disable имеет синтаксическую форму, показанную в Синтаксис 10-3.

disable_statement ::= disable hierarchical_task_identifier ; | disable hierarchical_block_identifier ;
Синтаксис 10-3-Синтаксис для оператора disable

Любая из форм оператора disable завершает выполнение задачи или именованного блока. Выполнение возобновляется в операторе, следующем за блоком или за оператором, вызывающее выполнение задачи. Все действия, вызванные в именованном блоке или задаче(task), также должны быть завершены. Если объявления вызова задач вложенные (то есть одна задача вызывает другую, а та вызывает еще одну), то отключение задачи(task) в цепочке приведет к отключению всех задач ниже по цепочке. Если задача вызвана более одного раза, то отключение такой задачи приведет к отключению всех активаций этой задачи.

Результаты следующих действий, которые могут быть инициированы заданием, не указываются, если задание отключено:

  • Результаты аргументов вывода и ввода
  • Запланированные, но не выполненные неблокируемые задания
  • Процедурные непрерывные назначения (объявления о assign и force)

Оператор disable может использоваться внутри блоков и задач(task) для отключения конкретного блока или задачи, содержащей оператор disable. Оператор disable можно использовать для отключения именованных блоков внутри функции(function), но нельзя использовать для отключения функций. В случаях, когда оператор disable внутри функции отключает блок или задачу, вызвавшую функцию, поведение не определено. Отключение автоматической задачи или блока внутри автоматической задачи происходит, как и для обычных задач, для всех параллельных выполнений задачи.

Например:

Пример 1 — Этот пример иллюстрирует, как блок отключает сам себя.
begin : block_name rega = regb; disable block_name; regc = rega; // это присвоение никогда не будет выполнено end
Пример 2 — В этом примере оператор disable используется внутри именованного блока аналогично прямому goto. Следующим после оператора disable будет выполняться оператор, следующий за именованным блоком.
begin : block_name ... if (a == 0) disable block_name; ... end // end of named block // continue with code following named block
Пример 3 — В этом примере оператор disable используется в качестве раннего возврата из задачи. Однако отключение задачи(task) с помощью оператора disable не является сокращением оператора return, используемого в языках программирования.
task proc_a; begin ... ... if (a == 0) disable proc_a; // return if true ... ... end endtask
Пример 4 — В этом примере оператор disable используется эквивалентно двум операторам continue и break в языке программирования Си. В примере показан управляющий код, который позволяет именованному блоку выполняться до тех пор, пока счетчик цикла не достигнет n итераций или пока переменная a не будет установлена в значение b. Именованный блок break содержит код, который выполняется до тех пор, пока a == b, после чего оператор disable break; завершает выполнение этого блока. Именованный блок continue содержит код, который выполняется на каждой итерации цикла for. Каждый раз, когда этот код выполняет оператор disable continue;. Блок continue завершается, и выполнение переходит к следующей итерации цикла for. Для каждой итерации блока continue выполняется набор операторов if (a != 0). Другой набор операторов выполняется, если (a! = b).
begin : break for (i = 0; i < n; i = i+1) begin : continue @clk if (a == 0) // "продолжить" цикл disable continue; statements statements @clk if (a == b) // "выход" из цикл disable break; statements statements end end
Пример 5 — Этот пример показывает использование оператора disable для одновременного отключения последовательности временных элементов управления и действия задачи(task) при возникновении события сброса. В примере показан блок fork-join, внутри которого находятся именованный последовательный блок (event_expr) и оператор disable, ожидающий наступления события сброса. Последовательный блок и ожидание сброса выполняются параллельно. Блок event_expr ожидает одного появления события ev1 и трех появлений события trig. Когда произойдут эти четыре события плюс задержка в d единиц времени, выполняется действие задачи. Когда происходит событие сброса, независимо от событий внутри последовательного блока, блок fork-join завершается, включая действие задачи.
fork begin : event_expr @ev1; repeat (3) @trig; #d action (areg, breg); end @reset disable event_expr; join
Пример 6 — Следующий пример представляет собой описание поведения моностабилизатора с ретриггером. Именованное событие retrig перезапускает период времени моностабильности. Если retrig продолжает происходить в течение 250 единиц времени, то q будет оставаться равным 1.
always begin : monostable #250 q = 0; end always @retrig begin disable monostable; q = 1; end

10.4 Функции и вызов функций

Цель функции — вернуть значение, которое будет использовано в выражении. Остальная часть этого параграфа объясняет, как объявляется и использовать функции.

10.4.1 Декларации функций

Синтаксис для определения функции приведен в Синтаксис 10-4.

function_declaration ::= function [ automatic ] [ function_range_or_type ] function_identifier ; function_item_declaration { function_item_declaration } function_statement endfunction | function [ automatic ] [ function_range_or_type ] function_identifier ( function_port_list ) ; { block_item_declaration } function_statement endfunction function_item_declaration ::= block_item_declaration | { attribute_instance } tf_input_declaration ; function_port_list ::= { attribute_instance } tf_input_declaration { , { attribute_instance }tf_input_declaration } tf_input_declaration ::= input [ reg ] [ signed ] [ range ] list_of_port_identifiers | input task_port_type list_of_port_identifiers function_range_or_type ::= [ signed ] [ range ] | integer | real | realtime | time block_item_declaration ::= { attribute_instance } reg [ signed ] [ range ] list_of_block_variable_identifiers ; | { attribute_instance } integer list_of_block_variable_identifiers ; | { attribute_instance } time list_of_block_variable_identifiers ; | { attribute_instance } real list_of_block_real_identifiers ; | { attribute_instance } realtime list_of_block_real_identifiers ; | { attribute_instance } event_declaration | { attribute_instance } local_parameter_declaration ; | { attribute_instance } parameter_declaration ; list_of_block_variable_identifiers ::= block_variable_type { , block_variable_type } list_of_block_real_identifiers ::= block_real_type { , block_real_type } block_variable_type ::= variable_identifier { dimension } block_real_type ::= real_identifier { dimension }
Синтаксис 10-4-Синтаксис объявления функции

Определение функции начинается с ключевого слова function, за которым следует необязательное ключевое слово automatic, за которым следует необязательное function_range_or_type возвращаемого значения функции, за которым следует имя функции, за которым следует либо точка с запятой, либо список портов функции, заключенный в круглые скобки, а затем точка с запятой, и заканчивается ключевым словом endfunction.

Использование function_range_or_type должно быть необязательным. Функция, указанная без function_range_or_type, по умолчанию использует скаляр для возвращаемого значения. Если используется, function_range_or_type должен указывать, что возвращаемое значение функции является real, integer, time, realtime или вектором (необязательно знаковым) с диапазоном [n:m] бит.

Функция должна иметь по крайней мере один объявленный вход.

Ключевое слово automatic объявляет автоматическую функцию, которая является реентерабельной. При этом все объявления функций выделяются динамически для каждого параллельного вызова функции. К элементам автоматических функций нельзя обращаться с помощью иерархических ссылок. Автоматические функции могут быть вызваны с помощью их иерархического имени.

Входы функций должны быть объявлены одним из двух способов. Первый способ должен содержать имя функции, за которым следует точка с запятой. После точки с запятой следует одно или несколько объявлений входов. По желанию смешанных с объявлениями элементов блока. После объявления элементов функции должно следовать заявление о поведении, а затем ключевое слово endfunction.

Второй метод должен содержать имя функции, за которым следует открытая скобка и одно или несколько объявлений ввода, разделенных запятыми. После всех входных деклараций должна быть закрытая скобка и точка с запятой. После точки с запятой должно быть ноль или более объявлений элементов блока, за которыми следует поведенченский оператор, а затем ключевое слово endfunction.

Например:

Следующий пример определяет функцию getbyte, используя спецификацию диапазона:
function [7:0] getbyte; input [15:0] address; begin // код для извлечения младшего байта из адресуемого слова . . . getbyte = result_expression; end endfunction
Или, используя вторую форму объявления функции(function). Функцию можно определить следующим образом:
function [7:0] getbyte (input [15:0] address); begin // code to extract low-order byte from addressed word . . . getbyte = result_expression; end endfunction

10.4.2 Возвращение значения из функции

Определение функции должно неявно объявлять внутреннюю для функции переменную с тем же именем, что функция. Эта переменная либо имеет значение по умолчанию 1-битный reg, либо имеет тот же тип, что и тип, указанный в объявлении функции. Определение функции инициализирует возвращаемое значение функции путем присвоения результата функции внутренней переменной с тем же именем, что и функция.

Объявление другого объекта с тем же именем, что и у функции, в области видимости, где объявлена функция(function), является незаконным. Внутри функции подразумевается переменная с именем функции, которая может использоваться в выражениях внутри функции(function). Поэтому также незаконно объявлять другой объект с тем же именем, что и функция, внутри области видимости функции(function).

Следующая строка из примера в разделе 10.4.1 иллюстрирует эту концепцию:
getbyte = result_expression;

10.4.3 Вызов функции

Вызов функции — это операнд внутри выражения. Вызов функции(function) имеет синтаксис, приведенный в разделе Синтаксис 10-5.

function_call ::= hierarchical_function_identifier{ attribute_instance } ( expression { , expression } )
Синтаксис 10-5-Синтаксис вызова функции

Порядок оценки аргументов вызова функции не определен. Например:

Следующий пример создает слово путем конкатенации результатов двух вызовов функции getbyte (определено в 10.4.1):
word = control ? {getbyte(msbyte), getbyte(lsbyte)}:0;

10.4.4 Правила функционирования

Функции(function) более ограничены, чем задачи(task). Их использование регулируется следующими правилами:

  1. Определение функции не должно содержать никаких объявлений с контролем времени. То есть любых объявлений, содержащих #, @ или wait.
  2. Функции не должны включать задачи.
  3. Определение функции должно содержать по крайней мере один входной аргумент.
  4. Определение функции не должно иметь ни одного аргумента, объявленного как output или inout.
  5. Функция не должна иметь никаких неблокирующих назначений или процедурных непрерывных назначений.
  6. Функция не должна иметь триггеров событий.

Например:

В этом примере определена функция factorial, которая возвращает целочисленное значение. Функция factorial вызывается итеративно, и результаты выводятся на печать.
module tryfact; // определить функцию function automatic integer factorial; input [31:0] operand; integer i; if (operand >= 2) factorial = factorial (operand - 1) * operand; else factorial = 1; endfunction // тестирование функции integer result; integer n; initial begin for (n = 0; n <= 7; n = n+1) begin result = factorial(n); $display("%0d factorial=%0d", n, result); end end endmodule // tryfact

Результаты моделирования следующие:

  1. factorial=1
  2. factorial=2
  3. factorial=6
  4. factorial=24
  5. factorial=120
  6. factorial=720
  7. factorial=5040
Перевод Официального Стандарта Verilog HDL

10.4.5 Использование константных функций(function)

Вызовы константных функций используются для поддержки построения сложных вычислений значений во время разработки (см. 12.8). Вызов константной функции должен быть вызовом функции константной функции, локальной для вызывающего модуля, где аргументы функции являются константными выражениями. Константные функции являются подмножеством обычных функций Verilog, которые должны удовлетворять следующим ограничениям:

  • Они не должны содержать иерархических ссылок.
  • Любая функция, вызываемая внутри константной функции, должна быть константной функцией, локальной для текущего модуля.
  • Должен быть законным вызов любой системной функции(function), которая разрешена в выражении constant_expression (см. пункт 5). Вызовы других системных функций должны быть запрещены.
  • Все системные задачи в рамках постоянной функции игнорируются.
  • Все значения параметров, используемые внутри функции, должны быть определены до использования вызывающей константной функции (т.е. любое использование параметра в оценке вызова константной функции представляет собой использование этого параметра в месте первоначального вызова константной функции).
  • Все идентификаторы, которые не являются параметрами или функциями, должны быть объявлены локально для текущей функции.
  • Если функции используют любое значение параметра, на которое прямо или косвенно влияет оператор defparam (см. 12.2.1), результат будет неопределенным. Это может привести к ошибке или константная функция может вернуть неопределенное значение.
  • Функции не должны быть объявлены внутри блока генерации (см. 12.4).
  • Функции не должны сами использовать константные функции в любом контексте, требующем константного выражения.

Вызовы константных функций оцениваются во время разработки. Их выполнение не влияет на начальные значения переменных, используемых как во время моделирования, так и при многократных вызовах функции во время разработки. В каждом из этих случаев переменные инициализируются так, как были бы инициализированы при обычном моделировании.

Например:

В этом примере определена функция clogb2, которая возвращает целое число со значением потолка логарифма по основанию 2.
module ram_model (address, write, chip_select, data); parameter data_width = 8; parameter ram_depth = 256; localparam addr_width = clogb2(ram_depth); input [addr_width - 1:0] address; input write, chip_select; inout [data_width - 1:0] data; // определить функцию clogb2 function integer clogb2; input [31:0] value; begin value = value - 1; for (clogb2 = 0; value > 0; clogb2 = clogb2 + 1) value = value >> 1; end endfunction reg [data_width - 1:0] data_store[0:ram_depth - 1];
Экземпляр этой модели ram_model с назначенными параметрами выглядит следующим образом:
ram_model #(32,421) ram_a0(a_addr,a_wr,a_cs,a_data);