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.