SystemVerilog IEEE 1800-2023. Обзор нововведений.
Вступление
Доброго времени суток, дорогие читатели! Не могу пройти мимо значительного события в сфере микроэлектроники, а уж в сфере её верификации и подавно.
28 февраля 2024 года была опубликована новая версия стандарта языка SystemVerilog 2023. Итак, что же стандарт 2023 года принесёт нового в существующий "уклад" инженеров? Давайте разбираться!
Данный пост представляет собой краткий авторский обзор нововведений. Обзор каждого нововведения будет содержать примеры, а также ссылки на соответствующие разделы нового стандарта. Приступаем!
1. Наследование функционального покрытия
В SystemVerilog существует такое понятие, как embedded covergroup
. Если переводить дословно, то "встроенная" covergroup
. Тип такой covergroup
объявляется внутри конкретного типа класса и становится "неразрывно" с ним связанным.
class base; enum {red, green, blue} color; covergroup g1; option.weight = 1; color_cp: coverpoint color; endgroup function new(); g1 = new; endfunction endclass
Такая covergroup
по умолчанию имеет доступ ко всем полям класса и должна создаваться в функции new()
этого класса.
В новой версии стандарта появилась возможность наследовать embedded covergroup
. Наследованная covergroup
"получает" доступ ко всем элементам (coverpoint
, cross
и т.д.) родительской, а также всем опциям.
Наследованная covergroup
может переопределять элементы и опции родительской, а также добавлять свои.
class derived extends base; bit d; covergroup extends g1; option.weight = 1; color_cp: coverpoint color { ignore_bins ignore = {blue}; } d_cp: coverpoint d; endgroup function new(); super.new(); endfunction endclass
В данном примере наследованная covegroup
(от covergroup cg1
из примера выше) переопределяет опцию weight
и точку покрытия color_cp
базовой, а также добавляет новую точку покрытия d_cp
.
Соответствующие разделы стандарта: 19.4.1.
2. Функция map()
распакованного массива
В новой версии стандарта появилась новая функция map()
поэлеметной работы с распакованным массивом.
Основная суть функции заключается в итерировании по массиву и применении к каждому элементу выражения, заключенного в сопутствующую конструкцию with()
.
int A [] = {1,2,3}, B [] = {2,3,5}, C [$]; // Add one to each element of an array A = A.map() with (item + 1'b1); // {2,3,4} // Add the elements of 2 arrays C = A.map(a) with (a + B[a.index]); // {4,6,9}
В данном примере при помощи функции map()
к каждому элементу массива A
сначала прибавляется 1, а после в очередь C
записывается результат поэлеметного сложения массивов A
и B
. Заметим, что функция map()
предоставляет пользователю ключевое слово item
для доступа к элементу массива и метод index
для доступа к его положению в массиве.
Также обратим внимание на важную особенность добавленной функции. Она может возвращать значение типа, отличающегося от типа обрабатываемого массива.
int A [] = {2,3,4}, B [] = {2,3,5}; // Element by element comparison bit C []; C = A.map(a) with (a == B[a.index]); // {1,1,0}
В данном примере происходит поэлементое сравнение массивов A
и B
. Обрабатываемый массив имеет тип int []
. Возвращаемый map()
результат имеет тип bit []
.
Соответствующие разделы стандарта: 7.12.5.
3. Множественные идентификаторы в `ifdef
В новой версии стандарта был дополнен синтаксис директивы компилятора `ifdef
. Ранее директива могла обрабатывать лишь одиночные идентификаторы, теперь могут обрабатываться логические выражения из множественных идентификаторов.
Пример-сравнение старой и новой версий стандарта.
`ifdef A `ifdef B // code for AND condition `endif `endif
`ifdef (A && B) // code for AND condition `endif
Обратите внимание, что в новой версии стандарта также актуальна и первая запись. А вообще - удобно! Доступные логические операторы: &&
, ||
, ->
, <->
.
Кстати, первые два нововведения (а на самом деле и все последующие после этого) относятся к несинтезируемому подмножеству. Чего не скажешь про`ifdef
, который может использоваться и в синтезируемом коде. Как думаете, когда "завезут" в ПО для синтеза? Делаем ставки, господа.
Соответствующие разделы стандарта: 22.6.
4. Multiline strings
В новой версии стандарта была добавлена возможность размещать текст типа string
на нескольких строках без использования специальных символов. Для этого используются тройные кавычки """
.
$display("Humpty Dumpty sat on a wall.\n\ Humpty Dumpty had a great fall.");
$display("""Humpty Dumpty sat on a wall. Humpty Dumpty had a great fall. """);
То есть элемент типа string
в тройных кавычках как бы автоматически "генерирует" необходимые символы для переноса на другую строку. Интересно также, что внутри тройных кавычек можно использовать одиночные "
без специальных символов.
$display("Humpty Dumpty sat on a \"wall\".\n\ Humpty Dumpty had a great fall.");
$display("""Humpty Dumpty sat on a "wall". Humpty Dumpty had a great fall. """);
Вообще мне введение такого форматирования очень напомнило Python, где оно также присутствует. И кстати, оно часто используется для документирования кода. И тут я задумался...
function int add(int a, int b); """ This function adds two integers. Arguments: int a - first integer to add; int b - second integer to add. """ return a + b; endfunction
Соответствующие разделы стандарта: 5.9.
5. Использование чисел с плавающей точкой в функциональном покрытии
В новой версии стандарта теперь возможно определение функционального покрытия для чисел с плавающей точкой (real
, shortreal
).
Рассмотрим одну из проблем определения функционального покрытия для чисел с плавающей точкой. Разделы покрытия представляют собой конечные наборы значений. Для целых чисел такой набор может быть однозначно определен.
int a; covergroup a_cg; a_cp: coverpoint a { bins b1 [] = {[0:9]}; // {0}, ..., {9} } endgroup
Числа же с плавающей точкой хранятся в памяти с некоторой точностью, что приводит к тому, что два разных числа могут быть представлены одним и тем же набором бит. Такая особенность может являться причиной дублирования значений в разделах покрытия, а также пересечения разделов покрытия.
real a; covergroup a_cg; a_cp: coverpoint a { bins b1 [] = {[0:9]}; // ??? } endgroup
В новой версии стандарта для поддержки использования чисел с плавающей точкой в функциональном покрытии была добавлена новая опция real_interval
, а также новый синтаксис вида +/-
и +%-
, которые призваны решить проблемы с точностью при помощи явного указания минимального шага между "соседними" числами. Давайте разбираться, как это работает.
real a; parameter real VALUE = 50.0; covergroup a_cg; option.real_interval = 0.01; a_cp: coverpoint a { // [49.9:50.1] bins b1 = {[VALUE+/-0.1]}; // [49.5:50.5] bins b2 = {[VALUE+%-1.0]}; // [0.75:0.76), [0.76:0.77), ... [0.84:0.85] bins a1 [] = {[0.75:0.85]}; } endgroup
В данном примере раздел b1
покрывает значения от 49.9 до 50.1. Синтаксис +/-
определяет отклонение от 50.0 на 0.1 (абсолютное значение) в обе стороны. Раздел b2
в свою очередь покрывает значения от 49.5 до 50.5. Синтаксис +%-
определяет отклонение от 50.0 на 1% (относительное значение) в обе стороны, то есть на 0.5 в абсолютном выражении.
Для массива разделов a1
количество элементов в нем определяется опцией real_interval
, которая задает шаг разбиения диапазона чисел с плавающей точкой. В данном примере шаг равен 0.01, так что интервал от 0.75 до 0.85 будет равномерно разбит на 10 интервалов: [0.85:0.86), [0.86:0.87), ..., [0.84:0.85]. Обратите внимание на включение границ в интервалы.
Соответствующие разделы стандарта: 19.5.1, 19.7.1.
6. Использование метода в качестве промежуточного результата
В большинстве симуляторов SystemVerilog в настоящее время поддерживается выполнение следующего примера:
module test; class my_class; function void print(); $display("Inside my_class!"); endfunction endclass function my_class get_my_class(); my_class cl; cl = new(); endfunction initial begin get_my_class().print(); end endmodule
Результат функции get_my_class()
используется в качестве промежуточного результата, который содержит указатель на объект типа my_class
, у которого вызывается метод print()
. В англоязычной литературе это называют "chaining of method calls".
Не трудно догадаться, что результатом выполнения будет:
Inside my_class!
Хоть данный пример и поддерживается многими симуляторами, в стандарте
явно не обозначались требования к соответствующему функционалу. В новой версии наконец-то появилось соответствующее описание. Из интересного: появилось правило разграничения иерархического обращения и промежуточного результата.
class A; int member=123; endclass module top; A a; function A F(int arg=0); int member; a = new(); return a; endfunction initial begin $display(F.member); $display(F().member); end endmodule
0 123
Новая версия стандарта явно указывает на то, что при использовании функции в качестве промежуточного результата всегда должны использоваться ()
, даже если у функции нет аргументов. В данном же примере выражение F.member
обращается к статической переменной member
функции F
, а F().member
обращается к полю member
объекта класса, который возвращается вызовом функции F
.
Соответствующие разделы стандарта: 13.4.1.
7. ref static
Для начала нам необходимо вспомнить, что переменная в SystemVerilog может быть автоматической. Если говорить простым языком, то это значит, что её присутствие в памяти на протяжении всего времени симуляции не гарантируется.
Для автоматических переменных стандартом накладываются определенные ограничения на их использование. Например, переменная не может стоять слева от < =
(быть целью Non-blocking assignment). Или, что более интересно, автоматическая переменная, объявленная вне блоков fork-join_any
и fork-join_none
не может быть использована внутри этих блоков.
initial begin for (int j = 1; j <= 3; ++j) begin fork begin automatic int k = j; #k $write("%0t %0d\n", $time(), k); end join_none end end
Такая конструкция абсолютно легальна. Выводом будет:
1 1 2 2 3 3
module test; function void print(ref logic arg); fork forever @(arg) $display("arg changed %0t", $time()); join_none endfunction logic C; initial begin print(C); #10; C = 1; #10; C = 2; end endmodule
Который запрещен стандартом, так как для компилятора при анализе функции print()
переменная arg
потенциально может быть автоматической, а её использование внутри fork-join_none
запрещено. Обратите внимание, что, даже если переменная, передаваемая в задачу будет статической, компилятор все равно сгенерирует ошибку.
Результатом запуска примера выше в QuestaSim 2021.2 будет:
vlog test.sv ** Error (suppressible): test.sv(4): (vlog-13300) The task or function 'print' with ref argument 'arg' must be automatic.
А вот в новой версии стандарта существует возможность передавать статическую переменную в подобную функцию по ссылке. Теперь аргумент можно будет объявить как ref static
.
module test; function void print(ref static logic arg); fork forever @(arg) $display("arg changed %0t", $time()); join_none endfunction logic C; initial begin print(C); #10; C = 1; #10; C = 2; end endmodule
Результат симуляции ожидается таким:
arg changed 10 arg changed 20
И знаете что. Симулятора, официально поддерживающего все нововведения SystemVerilog 2023 нет, а ожидаемый результат выполнения (ну почти) мне получить удалось. Спросите как?
Компиляцию первого примера в QuestaSim 2021.2 можно запустить с особыми флагами:
vlog test.sv -suppress 13300 -suppress 13219 -- Compiling module test Errors: 0, Warnings: 0, Suppressed Errors: 1
Что это за флаги - уже другая история. Но результат симуляции был следующим:
arg changed 10 arg changed 20
Кто-то заранее "подложил соломку"? Вопрос, скорее, риторический.
Соответствующие разделы стандарта: 9.3.2, 13.5.2.
Заключение
Вот и все, дорогие читатели. Только что мы с вами погрузились в новую версию стандарта SystemVerilog. Как думаете, хороши нововведения? Спасибо вам за уделенное время! Всего наилучшего и до новых встреч!
А больше подобных обзоров вы можете найти в Telegram-канале автора Verification For All.
Post Scriptum
Если вам известны иные нововведения версии стандарта SystemVerilog 2023 года, то не стесняйтесь написать об этом в комментариях или в личные сообщения автору в Telegram. Также автору было бы интересно узнать мнение читателей по поводу того, какого еще функционала, по их мнению, не хватает в теперь уже самом актуальном стандарте языка SystemVerilog IEEE 1800-2023.