Класс  Environment  описан в файле  Environment .sv. В нем описываются методы, определяющие архитектуру тестового окружения и управляющие процессом симуляции:

  • new() - конструктор, в котором виртуальные интерфейсы, переданные из программного блока, присваиваются виртуальным интерфейсам, объявленным в классе  Environment . Сам по себе виртуальный интерфейс является неким абстрактным дескриптором физического интерфейса (указатель на физический интерфейс). Напомню, что физические интерфейсы объявлены в модуле tb_top. Метод new() связывает виртуальный интерфейс с физическим (см. рис. 10).
  • Рис. 10. Привязка виртуального интерфейса к физическому

    Рис. 10. Привязка виртуального интерфейса к физическому

  • build() - в этом методе создаются компоненты тестового окружения, а точнее экземпляры классов Driver, Scoreboard и по 2 экземпляра почтового ящика mailbox и класса Receiver.
  • reset() - этот метод посылает сигнал сброса на тестируемое устройство. В схеме вычисления адресов операндов этого сигнала нет, поэтому этот метод просто устанавливает начальное состояние всех входных сигналов в 0.
  • cfg_dut() - с помощью этого метода конфигурируются тестируемые устройства. Для рассматриваемой схемы это не требуется.
  • start() - этот метод активирует работу компонентов тестового окружения. В блоке fork-join_any начинают параллельно выполняться 4 метода start() созданных компонентов тестового окружения. Выход из блока осуществляется при завершении любого из этих методов.
  • wait_for_end() - этот метод вносит дополнительную задержку для корректного завершения симуляции. Метод start() завершает свою работу при остановке любого компонента тестового окружения, а метод wait_for_end() ждет, пока остальные компоненты завершат свою работу.
  • report() - этот метод выводит информацию о количестве ошибок, возникших при симуляции. Для этого анализируется содержимое вектора ошибок errors, объявленного в классе Transaction.
  • run() - в этом методе в определенном порядке вызываются все описанные выше методы (за исключением метода new(), который вызывается в программном блоке testcase) (см. рис. 11).
  • Рис. 11. Порядок вызова методов класса Environment

    Рис. 11. Порядок вызова методов класса  Environment 

При неизменном составе тестового окружения от тестируемых схем зависит только содержимое методов reset() и cfg_dut().
Перед объявлением класса  Environment  в него включаются файлы, в которых описаны компоненты тестового окружения:

`include "Transaction.sv"
`include "Coverage.sv"
`include "Driver.sv"
`include "Receiver.sv"
`include "Scoreboard.sv"					

Это делает класс  Environment  независимым от порядка компиляции.
В начале описания класса  Environment  объявляются виртуальные интерфейсы:

virtual input_interface.input_prt input_intf ;
virtual output_interface.output_prt output_intf[2] ;			

, к которым потом в конструкторе подсоединяются интерфейсы, переданные из программного блока testcase:

function new(virtual input_interface.input_prt input_intf_new ,
              virtual output_interface.output_prt output_intf_new[2] );

    this.input_intf = input_intf_new ;
    this.output_intf = output_intf_new ;
    ...			

Далее объявляются компоненты тестового окружения:

Driver                 drvr;
Receiver               rcvr[2];
Scoreboard             sb;
mailbox #(Transaction) rcvr2sb[2];			

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

Примечание

В SystemVerilog есть обычные почтовые ящики:

					
mailbox rcvr2sb[2];
	

и параметризированные:

					
mailbox #(Transaction) rcvr2sb[2];
	

Обычные почтовые ящики позволяют передавать данные различных типов, а параметризированные – только одного (в нашем случае типа Transaction). Симулятор VCS разрешает использование почтовых ящиков любого типа, а вот ModelSim – только параметризированные. Во многих случаях использование параметризированных почтовых ящиков спасает от ошибки несоответствия типов (type mismatch). Для того, чтобы наше IP-ядро не было чувствительно к симулятору, будем использовать только параметризированные почтовые ящики.

Рассмотрим подробнее метод build():
1) В цикле foreach создаются почтовые ящики:

					
foreach(rcvr2sb[i])
    rcvr2sb[i] = new();

2) Создается драйвер:

					
drvr= new(input_intf);

, которому передается входной интерфейс.
3) Создается блок сравнения:

					
sb = new(rcvr2sb);

Несмотря на то, что он подключен к двум почтовым ящикам, в конструктор передается только одно название без индекса.
4) В цикле foreach создается 2 приемника:

					
foreach(rcvr[i])
    rcvr[i] = new(output_intf[i], rcvr2sb[i]);

Каждому приемнику передается свой интерфейс и почтовый ящик.
Исходный код класса  Environment :

					
`ifndef _ ENVIRONMENT _
`define _ ENVIRONMENT _

`include "Transaction.sv"
`include "Coverage.sv"
`include "Driver.sv"
`include "Receiver.sv"
`include "Scoreboard.sv"

class  Environment  ;

virtual input_interface.input_prt input_intf ;
virtual output_interface.output_prt output_intf[2] ;

Driver                 drvr;
Receiver               rcvr[2];
Scoreboard             sb;
mailbox #(Transaction) rcvr2sb[2];

function new(virtual input_interface.input_prt input_intf_new ,
              virtual output_interface.output_prt output_intf_new[2] );

    this.input_intf = input_intf_new ;
    this.output_intf = output_intf_new ;

    $display(" %0d :  Environment  : constructor created env object",$time);
endfunction : new

function void build();
    $display(" %0d :  Environment  : start of build() method",$time);
    foreach(rcvr2sb[i])
        rcvr2sb[i] = new();
    drvr = new(input_intf);
    sb = new(rcvr2sb);
    foreach(rcvr[i])
        rcvr[i]= new(output_intf[i], rcvr2sb[i]);
    $display(" %0d :  Environment  : end of build() method",$time);
endfunction : build

task reset();
    $display(" %0d :  Environment  : start of reset() method",$time);
    // Drive all DUT inputs to a known state
    input_intf.cb.a <= 0;
    input_intf.cb.b <= 0;
    input_intf.cb.c <= 0;
    input_intf.cb.d <= 0;

    /*// Reset the DUT
    input_intf.reset <= 1;
    repeat (4) @input_intf.clock;
    input_intf.reset <= 0;
    */
    $display(" %0d :  Environment  : end of reset() method",$time);
endtask : reset
task cfg_dut();
    $display(" %0d :  Environment  : start of cfg_dut() method",$time);
    //empty
    $display(" %0d :  Environment  : end of cfg_dut() method",$time);
endtask : cfg_dut

task start();
    $display(" %0d :  Environment  : start of start() method",$time);
    fork
        drvr.start();
        rcvr[0].start();
        rcvr[1].start();
        sb.start();
    join_any
    $display(" %0d :  Environment  : end of start() method",$time);
endtask : start

task wait_for_end();
    $display(" %0d :  Environment  : start of wait_for_end() method",$time);
    repeat(100) @(input_intf.clock);
    $display(" %0d :  Environment  : end of wait_for_end() method",$time);
endtask : wait_for_end

task report();

Transaction trans;

    $display(" %0d :  Environment  : start of report() method",$time);

    $display("=====================================");
    if (trans.errors == 1)
        $display(" Test completed with 1 error");
    else if (trans.errors)
        $display(" Test completed with %d errors", trans.errors);
    else
        $display(" Test completed without errors");
    $display("=====================================");

    $display(" %0d :  Environment  : end of report() method",$time);
endtask : report

task run();
    $display(" %0d :  Environment  : start of run() method",$time);
    build();
    reset();
    cfg_dut();
    start();
    wait_for_end();
    report();
    $display(" %0d :  Environment  : end of run() method",$time);
endtask : run

endclass

`endif