SystemVerilog и виртуальный интерфейс. Связь статического и динамического мира симуляции.
Вместе с большим количеством нововведений несинтезируемого подмножества (class
, fork-join
, randomize()
, queue
, ...) SystemVerilog "порадовал" инженеров новыми синтезируемыми конструкциями. Одной из них являлся интерфейс (interface
).
Задумка здесь очень простая. Интерфейс по своей сути является сгруппированным набором сигналов. Например, для простейшего AXI-Stream этот набор может быть объявлен следующим образом:
interface axi_s_intf (input logic clk);
// Simple AXI-Stream interface
// TSTRB, TKEEP, TID, TDEST, TUSER
// are not used for this impl.
logic tvalid;
logic tready;
logic [31:0] tdata;
logic tlast;
endinterface
Интерфейс обладает статической природой (как, например, и модуль). Его экземпляр создается в нулевой момент времени симуляции и не может быть удален до ее завершения.
module testbench;
// Тактовый сигнал и сигнал сброса
logic clk;
logic aresetn;
// Экземпляры AXI-Stream интерфейса
axi_s_intf intf_1 (clk);
axi_s_intf intf_2 (clk);
// Дизайн для верификации
my_design DUT (
.clk ( clk ),
.aresetn ( aresetn ),
// AXI-Stream 1
.tvalid_1 ( intf_1.tvalid ),
.tready_1 ( intf_1.tready ),
.tdata_1 ( intf_1.tdata ),
// AXI-Stream 2
.tvalid_2 ( intf_2.tvalid ),
.tready_2 ( intf_2.tready ),
.tdata_2 ( intf_2.tdata ),
.tlast_2 ( intf_2.tlast )
);
Как правило, экземпляры интерфейсов объявляются в главном модуле симуляции и подключаются к интерфейсам или портам тестируемого устройства.
Заметим, что не все провода интерфейса обязательно должны быть использованы при его подключении. Например, в примере выше провод tlast
для интерфейса intf_1
не подключается.
Структурно и в системной памяти имеем следующую картину:
Статические элементы в начале симуляции занимают место в памяти и существуют до завершения симуляции.
Так как в SystemVerilog появились динамические объекты (class
), то возникла также необходимость в получении доступа из динамических объектов к статическим элементам верификационного окружения. Для этих целей и существует виртуальный интерфейс.
Виртуальный интерфейс - указатель на статический экземпляр интерфейса. В ходе симуляции виртуальный интерфейс может указывать на различные экземпляры.
module testbench;
// Тактовый сигнал и сигнал сброса
logic clk;
logic aresetn;
// Экземпляры AXI-Stream интерфейса
axi_s_intf intf_1 (clk);
axi_s_intf intf_2 (clk);
...
// Виртуальный интерфейс AXI-Stream
virtual axi_s_intf vif;
initial begin
$display(vif);
vif = intf_1;
$display(vif);
end
endmodule
Результатом запуска симуляции в таком случае будет:
# null
# /testbench/intf_1
В начале виртуальный интерфейс не указывает ни на какой из статических интерфейсов, то есть проинициализирован null
(нулевым указателем). Строка vif = intf_1
определяет для виртуального интерфейса статический экземпляр интерфейса, на который он будет указывать.
При помощи виртуального интерфейса пользователь может взаимодействовать с сигналами статического интерфейса. В ходе симуляции виртуальный интерфейс может использоваться для изменения сигналов различных статических интерфейсов.
module testbench;
// Тактовый сигнал и сигнал сброса
logic clk;
logic aresetn;
// Экземпляры AXI-Stream интерфейса
axi_s_intf intf_1 (clk);
axi_s_intf intf_2 (clk);
initial begin
clk <= 0;
forever begin
#5 clk <= ~clk;
end
end
...
// Виртуальный интерфейс AXI-Stream
virtual axi_s_intf vif;
initial begin
vif = intf_1;
$display(vif);
@(posedge clk);
vif.tvalid <= 1;
vif.tdata <= 10;
@(posedge clk);
vif = null;
@(posedge clk);
vif = intf_2;
$display (vif);
@(posedge clk);
vif.tvalid <= 1;
vif.tdata <= 10;
vif.tlast <= 1;
@(posedge clk);
vif = null;
end
endmodule
Результаты симуляции:
# /testbench/intf_1
# /testbench/intf_2
Заметим, что в промежутке между присвоениями указателей на статические интерфейсы виртуальный "успел побывать" и в нулевом (null
) значении. В ходе симуляции указатель, содержащийся в виртуальном интерфейсе, может динамически создаваться и уничтожаться.
Изображение ниже демонстрирует процесс создания и удаления указателя на статические интерфейсы (пример кода выше). Изначально виртуальный интерфейс указывал на intf_1
(отмечено -->
), после чего был проинициализирован null
, а после стал указывать на intf_2
(отмечено ->
).
Виртуальные интерфейсы в большинстве случаев используются для передачи в объекты классов, в которых определен набор задач для манипулирования сигналами интерфейса.
module testbench;
// Тактовый сигнал и сигнал сброса
logic clk;
logic aresetn;
// Экземпляры AXI-Stream интерфейса
axi_s_intf intf_1 (clk);
axi_s_intf intf_2 (clk);
...
class my_design_driver;
virtual axi_s_intf vif;
function new(virtual axi_s_intf vif);
this.vif = vif;
endfunction
task drive();
@(posedge vif.clk);
vif.tvalid <= 1;
vif.tdata <= $random();
@(posedge vif.clk);
vif.tvalid <= 0;
endtask
endclass
my_design_driver driver;
initial begin
driver = new(intf_1);
repeat(10) driver.drive();
end
endmodule
Результат симуляции кода:
В данном примере в конструктор класса my_design_driver
передается статический интерфейс. Однако в аргументе конструктора тип аргумента объявлен как virtual axi_s_intf
, то есть происходит присвоение virtual axi_s_intf vif = axi_s_intf intf_1
, что является абсолютно легальным в SystemVerilog (было разобрано в примерах выше).
Как видите, ничего сложного! Больше заметок вы можете найти в Telegram-канале автора Verification For All.
Хорошего тебе дня, читатель, и до новых встреч!