SV experiment -- switch1

I. Introduction

We will verify the Switch RTL core. Here are the steps to verify the Switch RTL core.
1) Understanding specification s
2) Develop validation plan
3) Establish the verification environment. We'll build the environment in multiple phases, so you can easily learn step-by-step.

Phase 1) we will develop test cases and interfaces and integrate them with the DUT in the top module.
Phase 2) we will develop environment classes (insert more components into the same environment, so it makes validation more flexible and extensible).
Phase 3) we will develop the reset and configuration methods in the Environment class. Then using these methods, we will reset the DUT and configure the port address.
Phase 4) we will develop a package category based on the stimulus plan. We will also write a small piece of code to test the implementation of the package class.
Phase 5) we will develop a driver class. The package is generated and sent to dut using the driver.
Phase 6) we will develop the receiver class. The receiver collects packets from the DUT output port.
Stage 7) we will develop a scoreboard class that compares the expected packets with the actual packets received from DUT.
Phase 8) we will develop the coverage class based on the coverage plan.
Phase 9) in this phase, we will write test cases and analyze coverage reports.

2, Switch Specification

This is a simple switch. Switch is a protocol based package. Switch drives the input packet from the input port to the output port according to the address contained in the packet.
The Switch has an input port from which packets enter. It has four output ports on which packets are output.

3, Packet Format

The Packet contains three parts. They are header, data, and frame check sequences. The width of the Packet is 8 bits, and the length of the Packet can be between 4 bytes and 259 bytes.

Packet Header: the Packet Header contains three fields DA, SA and length.

DA: the destination address of the packet is 8 bits. Switch drives the packet to each port according to the destination address of the packet. Each output port has an 8-bit unique port address. If the destination address of the packet matches the port address, the switch drives the packet to the output port.

SA: source address of the packet. It's 8 bits.

Length: the length of the data is 8 bits and the range is 0-255. The length of the data is in bytes.
If Length = 0, the data length is 0 bytes;
If Length = 1, the data length is 1 byte;
If Length = 2, the data length is 2 bytes;
If Length = 255, the data length is 255 bytes;

Data: data should be in bytes and can contain anything.

FCS: frame check sequence: this field contains the security check of the packet.

4, Configuration

The switch has four output ports. These output port addresses must be configured as unique addresses. The switch matches the DA field of the packet with the port address of this configuration and sends the packet to that port. Switch contains a memory. The memory has four positions, each of which can store 8 bits. To configure the switch port address, you must use the memory interface to write to the memory. The memory address (0,1,2,3) respectively contains the address of the port (0,1,2,3).

5, Interface Specification:

The switch can be configured with an input interface (from the interface to the packet) and four output interfaces (from the interface to the packet) and a storage interface through the port address. The switch also has a clock and an asynchronous reset signal.

6, Memory interface

Configure the output port address through the memory interface. It accepts 8 bits of data to be written to memory. It has 8-bit address input. Address 0, 1, 2 and 3 respectively contain the addresses of ports 0, 1, 2 and 3.

The memory interface has four input signals. They are:

input mem_en;
input mem_rd_wr;
input [1:0] mem_add;//Port number
input [7:0] mem_data;//Eight bit port address

All signals are effective at high level and synchronized with the rising edge of the clock signal. Configure port address:
1. Declare the MEM? En signal.
2. Declare the MEM Rd WR signal.
3. Drive the port number (0 or 1 or 2 or 3) on the MEM? Add signal
4. Drive the 8-bit port address to the MEM? Data signal.

From the oscillogram, we can see that:
When the port number is 00, the port address is 05, i.e. 0000_;
When the port number is 01, the port address is 03, i.e. 0000_;
When the port number is 10, the port address is 05, i.e. 0000_; input port

7, Input Port

Packets are sent to the switch using the input port. All signals are effective at high level and synchronized with the rising edge of the clock signal.
The input port has two input signals. They are:

input [7:0] data;
input data_status;

To send packets to a switch:
1. Declare the data status signal.
2. Send packets byte by byte on the data signal.
3. After sending all data bytes, set the data status signal low.
4. There should be at least three clock cycles difference between packets.

8, Output Port

Switches use output ports to send packets. There are four ports, each with data, prepare signal and read signal. All signals are effective at high level and synchronized with the rising edge of the clock signal.

The signal list is:

output [7:0] port0;
output [7:0] port1;
output [7:0] port2;
output [7:0] port3;
output ready_0;
output ready_1;
output ready_2;
output ready_3;
input read_0;
input read_1;
input read_2;
input read_3;

When ready to send data from the port, the switch sets the ready * signal to the high level, indicating that the data is ready to be sent. If the read * signal is valid, then the read * is high power level, and the data is output from the port * signal after one clock cycle.

Nine.RTL code

Verification plan

1, Overview

This document describes the validation plan for the switch. The verification plan is based on the system Verilog hardware verification language. The method used for verification is constraint random coverage driven verification.

2, Feature extraction

This section contains a list of all the features to verify.
1)
ID: Configuration
Description: Configure all the 4 port address with unique values.
2)
ID: Packet DA
Description: DA field of packet should be any of the port address. All the 4 port address should be used.
3)
ID : Packet payload
Description: Length can be from 0 to 255. Send packets with all the lengths.
4)
ID: Length
Description:
Length field contains length of the payload.
Send Packet with correct length field and incorrect length fields.
5)
ID: FCS
Description:
Good FCS: Send packet with good FCS.
Bad FCS: Send packet with corrupted FCS.

3, Incentive generation plan

  1. Packet DA: Generate packet DA with the configured address
  2. Payload length: generate payload length ranging from 2 to 255
  3. Correct or Incorrect Length field
  4. Generate good and bad FCS

4, Coverage Plan

  1. Cover all the port address configurations.
  2. Cover all the packet lengths.
  3. Cover all correct and incorrect length fields.
  4. Cover good and bad FCS.
  5. Cover all the above combinations.

5, Verification environment

First stage TOP

In the first phase

1) We will write the system Verilog interface for input port, output port and memory port.
2) We will write the Top module to complete the test case and DUT instance.
3) The DUT and Te stBench interfaces are connected in the top module.
4) The clock is the generator in the top module.
Note: in each file, you will see the syntax:

`ifndef GUARD_*
`endif GUARD_*

1, Interfaces

In the interface.sv file, declare three interfaces as follows.
[1] All interfaces have a clock as input.
[2] All signals in the interface are of logical type.
[3] Except for clock block reset, all signals are synchronized with the clock.
[4] Testbench signal orientation is specified through modport.

1.1 memory interface declaration

`ifndef GUARD_INTERFACE
`define GUARD_INTERFACE

interface mem_interface(input bit clock);
logic [7:0] mem_data;
logic [1:0] mem_add;
logic mem_en;
logic mem_rd_wr;
//The blocking block is to declare the timing attribute of sampling or driving signals in the module/interface/checker/program
clocking cb@(posedge clock);
default input #1 output #1;
output mem_data;
output mem_add;
output mem_en;
output mem_rd_wr;
endclocking

modport MEM(clocking cb,input clock);

endinterface

1.2 input interface declaration

interface input_interface(input bit clock);
logic data_status;
logic [7:0] data_in;
logic reset;

clocking cb@(posedge clock);
default input #1 output #1;
output data_status;
output data_in;
endclocking

modport IP(clocking cb,output reset,input clock);

endinterface

1.3 declaration of output interface

interface output_interface(input bit clock);
logic [7:0] data_out;
logic ready;
logic read;

clocking cb@(posedge clock);
default input #1 output #1;
input data_out;
input ready;
output read;
endclocking

modport OP(clocking cb,input clock);

endinterface

2, Testcase test case

testcase is a block that provides an entry for testing and creates a scope to encapsulate program wide data. Currently, this is an empty test case, and the simulation ends only after 100 time units. The block contains all the interfaces declared above as parameters. The test case has initial and final blocks.

`ifndef GUARD_TESTCASE
`define GUARD_TESTCASE

program testcase(mem_interface.MEM mem_intf,input_interface.IP input_intf,output_interface.OP output_intf[4]);

initial
begin
$display(" ******************* Start of testcase ****************");

#1000;
end

final
$display(" ******************** End of testcase *****************");

endprogram
`endif

3, Top Module

The modules contained in the source text but not instantiated are called top modules. This module is the highest range of modules. Usually this module is named "top" and referred to as "top module". The module name can be anything.
Do the following in the top module:
1) Generate clock signal;

bit CLock;

initial
forever #10 Clock=~Clock;

2) Example memory interface

mem_interface mem_intf(Clock);

3) Example input interface

input_interface input_intf(Clock);

4) There are four output ports. The same is true for the four output U interface instances.

output_interface output_intf[4](Clock);

5) Execute the testcase instance and pass all the interfaces declared above.

testcase TC (mem_intf,input_intf,output_intf);

6) Instantiation of DUT

switch DUT (.

7) Connect all interfaces and DUTs. Our design is verilog. Therefore, Verilog DUT instances are connected signal by signal.

switch DUT (.clk(Clock),
.reset(input_intf.reset),
.data_status(input_intf.data_status),
.data(input_intf.data_in),
.port0(output_intf[0].data_out),
.port1(output_intf[1].data_out),
.port2(output_intf[2].data_out),
.port3(output_intf[3].data_out),
.ready_0(output_intf[0].ready),
.ready_1(output_intf[1].ready),
.ready_2(output_intf[2].ready),
.ready_3(output_intf[3].ready),
.read_0(output_intf[0].read),
.read_1(output_intf[1].read),
.read_2(output_intf[2].read),
.read_3(output_intf[3].read),
.mem_en(mem_intf.mem_en),
.mem_rd_wr(mem_intf.mem_rd_wr),
.mem_add(mem_intf.mem_add),
.mem_data(mem_intf.mem_data));

4, top Module Source Code:

`ifndef GUARD_TOP
`define GUARD_TOP

module top();

/////////////////////////////////////////////////////
// Clock Declaration and Generation //
/////////////////////////////////////////////////////
bit Clock;

initial
forever #10 Clock = ~Clock;

/////////////////////////////////////////////////////
// Memory interface instance //
/////////////////////////////////////////////////////

mem_interface mem_intf(Clock);

/////////////////////////////////////////////////////
// Input interface instance //
/////////////////////////////////////////////////////

input_interface input_intf(Clock);

/////////////////////////////////////////////////////
// output interface instance //
/////////////////////////////////////////////////////

output_interface output_intf[4](Clock);

/////////////////////////////////////////////////////
// Program block Testcase instance //
/////////////////////////////////////////////////////

testcase TC (mem_intf,input_intf,output_intf);

/////////////////////////////////////////////////////
// DUT instance and signal connection //
/////////////////////////////////////////////////////

switch DUT (.clk(Clock),
.reset(input_intf.reset),
.data_status(input_intf.data_status),
.data(input_intf.data_in),
.port0(output_intf[0].data_out),
.port1(output_intf[1].data_out),
.port2(output_intf[2].data_out),
.port3(output_intf[3].data_out),
.ready_0(output_intf[0].ready),
.ready_1(output_intf[1].ready),
.ready_2(output_intf[2].ready),
.ready_3(output_intf[3].ready),
.read_0(output_intf[0].read),
.read_1(output_intf[1].read),
.read_2(output_intf[2].read),
.read_3(output_intf[3].read),
.mem_en(mem_intf.mem_en),
.mem_rd_wr(mem_intf.mem_rd_wr),
.mem_add(mem_intf.mem_add),
.mem_data(mem_intf.mem_data));


endmodule


`endif

Phase II environment

At this stage, we will prepare:
[1] Environment;
[2] Virtual interface declaration;
[3] Define the constructor of environment class;
[4] Define the methods required for execution. Currently, these methods will not be implemented at this stage.

All of the above operations are done in the Environment.sv file. We will write a test case in the testcase.sv file using the above definition environment class.

1, Environment Class

This class is the base class for implementing the validation environment. Testcase contains instances of environment classes and has access to all public declarations of environment classes. All methods are declared virtual methods. In the environment class, we will use virtual methods to formalize simulation steps. These methods are used to control the execution of simulation. The following are the methods that will be defined in the environment class:
1) new(): in the constructor method, we connect the virtual interface passed to the virtual interface as a parameter to the virtual interface declared in the environment class;
2) build(): in this method, all objects will be constructed, such as driver, monitor, etc. At present, this method is empty because we have not developed any other components;
3) reset(): in this method, we will reset DUT;
4) cfg_dut(): in this method, we will configure the DUT output port address;
5) start(): in this method, we will call methods declared in other components (such as drivers and monitors);
6) wait for end(): this method is used to wait for the simulation to end. Wait until all operations required in other components are completed;
7) report(): this method is used to print the simulated TestPass and TestFail status according to the error count;
8)run(): this method calls all the declared methods in sequence. testcase calls this method to start the simulation;
At this stage, we will not implement the build(), reset(), cfg_dut(), strat() and report() methods.

Connect the virtual interface of the environment class to the physical interface of the top-level module.

The validation environment contains the declaration of the virtual interface. A virtual interface is just a handle, such as a pointer. When a virtual interface is declared, it creates only one handle. It doesn't create a real interface.

Constructor methods should be declared with virtual interfaces as parameters so that when an object is created in testcase, the new() method can pass the interfaces to the environment class and assign them to the local virtual interface handle. In this way, the environment class virtual interface is pointed to the physical interface declared in the top module.

How the top-level interface connects to the virtual interface in the class.

The above figure describes the method of connecting the virtual interface of the environment class to the physical interface of the top-level module. The MEM interface created in the Top.sv file is passed to the environment class as a parameter of the new() function in Testcase.v, where this.men'intf represents the environment class, the virtual MEM interface, and mem'intf'new represents the physical interface in the top-level file. This. Menu? INTF = MEM? INTF? New implements the link between the environment class virtual interface and the top-level module physical interface.

Declare the virtual interface in the Environment class.

virtual mem_interface.MEM mem_intf ;//Physical interface in top module: MEM? INTF
virtual input_interface.IP input_intf ;//Physical interface in the top module: input ﹣ INTF
virtual output_interface.OP output_intf[4] ;//Physical interface in the top-level module: output ﹣ INTF [4]

The construction of environment class is declared with virtual interface as parameter.

function new(virtual mem_interface.MEM mem_intf_new ,
virtual input_interface.IP input_intf_new ,
virtual output_interface.OP output_intf_new[4] );

In the constructor method, the interface as a parameter is connected to the virtual interface of the environment class.

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

2, Run

The run () method is invoked from the test case (Testcase) to start the simulation. The run () method calls all methods defined in the Environment class.

task run();
$display(" %0d : Environment : start of run() method",$time);
build();//In this method, all objects are constructed, such as driver, monitor, etc
reset();//In this method, we will reset DUT;
cfg_dut();//In this method, we will configure the dut output port address;
start();//In this method, we will call methods declared in other components (such as drivers and monitors);
wait_for_end();//This method is used to wait for the end of simulation
report();//This method is used to print the simulated TestPass and TestFail states according to the error count;
$display(" %0d : Environment : end of run() method",$time);
endtask : run

3, Environment class source code

`ifndef GUARD_ENV
`define GUARD_ENV

class Environment ;
//Declaration of environment class
virtual mem_interface.MEM mem_intf ;
virtual input_interface.IP input_intf ;
virtual output_interface.OP output_intf[4] ;

//Construction of environment class
function new(virtual mem_interface.MEM mem_intf_new ,
virtual input_interface.IP input_intf_new ,
virtual output_interface.OP output_intf_new[4] );
//Connection between virtual interface of environment class and top-level physical interface
this.mem_intf = mem_intf_new ;
this.input_intf = input_intf_new ;
this.output_intf = output_intf_new ;
$display(" %0d : Environment : created env object",$time);
endfunction : new


function void build();
$display(" %0d : Environment : start of build() method",$time);
$display(" %0d : Environment : end of build() method",$time);
endfunction :build


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

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

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

task wait_for_end();
$display(" %0d : Environment : start of wait_for_end() method",$time);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end
//number
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

task report();
endtask : report

endclass

`endif

We will create a file Global.sv for the global requirements. In this file, define all port addresses as macros in the file. Define variable errors as integers to track the number of errors that occur during the simulation.

`ifndef GUARD_GLOBALS
`define GUARD_GLOBALS

`define P0 8'h00
`define P1 8'h11
`define P2 8'h22
`define P3 8'h33

int error = 0;
int num_of_pkts = 10;

`endif

Now we will update testcase. Take the instance of environment class as an example, call the run method of environment class.

`ifndef GUARD_TESTCASE
`define GUARD_TESTCASE

program testcase(mem_interface.MEM mem_intf,input_interface.IP input_intf,output_interface.OP output_intf[4]);

Environment env;

initial
begin
$display(" ******************* Start of testcase ****************");

env = new(mem_intf,input_intf,output_intf);
env.run();

#1000;
end

final
$display(" ******************** End of testcase *****************");

endprogram
`endif

4, Source location

/home/zwj/wendang/systemverilog/SV_code/switch_2/top.sv

5, Run simulation

vcs -sverilog -f filelist -R -ntb_opts dtm
 Note:
[1] - f filename specifies the file name of the file list;
[2] - R executes the executable immediately after compilation;
[3] - ntb? Opts is energy ntb

6, Operation results

******************* Start of testcase ****************
 0 : Environemnt : created env object
 0 : Environemnt : start of run() method
 0 : Environemnt : start of build() method
 0 : Environemnt : end of build() method
 0 : Environemnt : start of reset() method
 0 : Environemnt : end of reset() method
 0 : Environemnt : start of cfg_dut() method
 0 : Environemnt : end of cfg_dut() method
 0 : Environemnt : start of start() method
 0 : Environemnt : end of start() method
 0 : Environemnt : start of wait_for_end() method
 0 : Environemnt : end of wait_for_end() method
 0 : Environemnt : end of run() method
 ******************** End of testcase *****************

Phase 3 RESET

At this stage, we will reset and configure the DUT.
The Environment class has the reset() method and the cfg_dut() method, the reset() method contains the logic to reset the DUT, and the cfg_dut() method contains the logic to configure the DUT port address.

Note: clock block signals can only be driven by non blocking assignment.
Define the reset() method:
1) Set all DUT input signals to known status;

mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 0;

2) Reset DUT;

input_intf.reset <= 1;
repeat (4) @ input_intf.clock;
input_intf.reset <= 0;

3) Update the cfg_dut method; / / configure the output as the address of the port

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

mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
$display(" %0d : Environment : Port 0 Address %h ",$time,`P0);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
$display(" %0d : Environment : Port 1 Address %h ",$time,`P1);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
$display(" %0d : Environment : Port 2 Address %h ",$time,`P2);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
$display(" %0d : Environment : Port 3 Address %h ",$time,`P3);

@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;


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

4) In the wait for end method, wait for some clock cycles;

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

1, Path to source file

/home/zwj/wendang/systemverilog/SV_code/switch_3

Simulation command:

vcs -sverilog -f filelist -R -ntb_opts dtm

Simulation results:

******************* Start of testcase ****************
 0 : Environemnt : created env object
 0 : Environemnt : start of run() method
 0 : Environemnt : start of build() method
 0 : Environemnt : end of build() method
 0 : Environemnt : start of reset() method
 40 : Environemnt : end of reset() method
 40 : Environemnt : start of cfg_dut() method
 70 : Environemnt : Port 0 Address 00 
 90 : Environemnt : Port 1 Address 11 
 110 : Environemnt : Port 2 Address 22 
 130 : Environemnt : Port 3 Address 33 
 150 : Environemnt : end of cfg_dut() method
 150 : Environemnt : start of start() method
 150 : Environemnt : end of start() method
 150 : Environemnt : start of wait_for_end() method
 100150 : Environemnt : end of wait_for_end() method
 100150 : Environemnt : end of run() method
 ******************** End of testcase *****************

PHASE 4 PACKET

In this phase, we will define a packet and test it to see if the expected packet is generated. Packages are modeled using classes. The packet category shall be able to randomly generate all possible packet types. The package class should also implement the necessary methods, such as packing () unpacking (), compare () and display () methods. We write the packet class to the packet.sv file. Grouped variables and constraints have been derived from the incentive generation plan.
Revisit the incentive generation plan:
1) Packet DA: generate packet DA using the configured address;
2) Payload length: the generated payload length ranges from 2 to 255;
3) Correct or incorrect length field;
4) Generate good or bad FCS;

1) Declare the FCS type as an enumerated data type. Name the members good FCS and bad FCS;

typedef enum { GOOD_FCS, BAD_FCS } fcs_kind_t; //Create an enumeration data structure. The data type is FCS ﹐ kind ﹐ T, which contains two members

2) Declares the length type as an enumeration data type. Name the members good & long and bad & long;

typedef enum { GOOD_LENGTH, BAD_LENGTH } length_kind_t;

3) Declare the length type and fcs type variables as rand (randomization);

rand fcs_kind_t fcs_kind;//In this sentence, the variable of the type of random variable FCS Kin t is called FCS Kin random
rand length_kind_t length_kind;

4) Declare the package field as rand. All fields are bit data types. All fields are 8-bit packet arrays. Declare the payload as a dynamic array;

rand bit [7:0] length;
rand bit [7:0] da;
rand bit [7:0] sa;
rand byte data[];
rand byte fcs;,

5) Limit the DA field to any of the configured addresses;

constraint address_c { da inside {`P0,`P1,`P2,`P3} ; }

6) Limit the payload dynamic array size to 1 to 255;

constraint payload_size_c { data.size inside { [1 : 255]};}

7) Constrain the payload length to the length field based on the length type;

constraint length_kind_c {
(length_kind == GOOD_LENGTH) -> length == data.size;
(length_kind == BAD_LENGTH) -> length == data.size + 2 ; }

8) Using solve before direct randomization first generates the payload dynamic array size, then randomizes the length field.

constraint solve_size_length { solve data.size before length; }

9) Constrain the initial value of FCS field according to FCS category field;

constraint fcs_kind_c {
(fcs_kind == GOOD_FCS) -> fcs == 8'b0;
(fcs_kind == BAD_FCS) -> fcs == 8'b1; }

10) Define FCS methods;

function byte cal_fcs;
integer i;
byte result ;
result = 0;
result = result ^ da;
result = result ^ sa;
result = result ^ length;
for (i = 0;i< data.size;i++)
result = result ^ data[i];
result = fcs ^ result;
return result;
endfunction : cal_fcs

11) Define the display() method:

The display() method displays the current value of the packet field to standard output.

virtual function void display();
$display("\n---------------------- PACKET KIND ------------------------- ");
$display(" fcs_kind : %s ",fcs_kind.name() );
$display(" length_kind : %s ",length_kind.name() );
$display("-------- PACKET ---------- ");
$display(" 0 : %h ",da);
$display(" 1 : %h ",sa);
$display(" 2 : %h ",length);
foreach(data[i])
$write("%3d : %0h ",i + 3,data[i]);
$display("\n %2d : %h ",data.size() + 3 , cal_fcs);
$display("----------------------------------------------------------- \n");
endfunction : display

12) Define the pack method

Packages are often used to convert high-level data into low-level data that can be applied to DUTs. In the group category, various fields are generated. The required fields are connected to form a byte stream which can be easily driven to the DUT interface by the driver.

virtual function int unsigned byte_pack(ref logic [7:0] bytes[]);//Define 8-bit-wide dynamic array bytes [], for example, byte[0] is 8bits,byte[1] is 8bits
bytes = new[data.size + 4];//The function of dynamic array: to store data data+da+sa+length, the dynamic array size (data.size+da+sa+length) is required
bytes[0] = da;//Store [7:0] da in the dynamic array bytes[0]
bytes[1] = sa;
bytes[2] = length;
foreach(data[i])
bytes[3 + i] = data[i];
bytes[data.size() + 3] = cal_fcs;
byte_pack = bytes.size;
endfunction : byte_pack

//Note: ref indicates that the parameter is passed by reference;

13) Define unpacking method:

The unpack() method is the exact opposite of the pack method. Unpacking is usually used to convert data flows from DUT to advanced package objects.

virtual function void byte_unpack(const ref logic [7:0] bytes[]);
this.da = bytes[0];
this.sa = bytes[1];
this.length = bytes[2];
this.fcs = bytes[bytes.size - 1];
this.data = new[bytes.size - 4];
foreach(data[i])
data[i] = bytes[i + 3];
this.fcs = 0;
if(bytes[bytes.size - 1] != cal_fcs)
this.fcs = 1;
endfunction : byte_unpack

//Note: const ref indicates that parameters are passed by reference, but are not allowed to be modified.

14) Define the comparison method.

Compares the current value of an object instance with the current value of a specified object instance. If the values are different, FALSE is returned.

virtual function bit compare(packet pkt);
compare = 1;
if(pkt == null)
begin
$display(" ** ERROR ** : pkt : received a null object ");
compare = 0;
end
else
begin
if(pkt.da !== this.da)
begin
$display(" ** ERROR **: pkt : Da field did not match");
compare = 0;
end
if(pkt.sa !== this.sa)
begin
$display(" ** ERROR **: pkt : Sa field did not match");
compare = 0;
end

if(pkt.length !== this.length)
begin
$display(" ** ERROR **: pkt : Length field did not match");
compare = 0;
end
foreach(this.data[i])
if(pkt.data[i] !== this.data[i])
begin
$display(" ** ERROR **: pkt : Data[%0d] field did not match",i);
compare = 0;
end

if(pkt.fcs !== this.fcs)
begin
$display(" ** ERROR **: pkt : fcs field did not match %h %h",pkt.fcs ,this.fcs);
compare = 0;
end
end
endfunction : compare

Packet Class Source Code

`ifndef GUARD_PACKET
`define GUARD_PACKET

//Define the enumerated types for packet types
typedef enum { GOOD_FCS, BAD_FCS } fcs_kind_t;
typedef enum { GOOD_LENGTH, BAD_LENGTH } length_kind_t;

class packet;
rand fcs_kind_t fcs_kind;
rand length_kind_t length_kind;

rand bit [7:0] length;
rand bit [7:0] da;
rand bit [7:0] sa;
rand byte data[];//Real time size generation using dynamic array payloads
rand byte fcs;

constraint address_c { da inside {`P0,`P1,`P2,`P3} ; }

constraint payload_size_c { data.size inside { [1 : 255]};}

constraint length_kind_c {
(length_kind == GOOD_LENGTH) -> length == data.size;
(length_kind == BAD_LENGTH) -> length == data.size + 2 ; }

constraint solve_size_length { solve data.size before length; }

constraint fcs_kind_c {
(fcs_kind == GOOD_FCS) -> fcs == 8'b0;
(fcs_kind == BAD_FCS) -> fcs == 8'b1; }

/////Calculation method of fcs////
function byte cal_fcs;
integer i;
byte result ;
result = 0;
result = result ^ da;
result = result ^ sa;
result = result ^ length;
for (i = 0;i< data.size;i++)
result = result ^ data[i];
result = fcs ^ result;
return result;
endfunction : cal_fcs

/////How to print package fields////
virtual function void display();
$display("\n---------------------- PACKET KIND ------------------------- ");
$display(" fcs_kind : %s ",fcs_kind.name() );
$display(" length_kind : %s ",length_kind.name() );
$display("-------- PACKET ---------- ");
$display(" 0 : %h ",da);
$display(" 1 : %h ",sa);
$display(" 2 : %h ",length);
foreach(data[i])
$write("%3d : %0h ",i + 3,data[i]);
$display("\n %2d : %h ",data.size() + 3 , cal_fcs);
$display("----------------------------------------------------------- \n");
endfunction : display

/////A method of packing packets into bytes/////
virtual function int unsigned byte_pack(ref logic [7:0] bytes[]);
bytes = new[data.size + 4];
bytes[0] = da;
bytes[1] = sa;
bytes[2] = length;
foreach(data[i])
bytes[3 + i] = data[i];
bytes[data.size() + 3] = cal_fcs;
byte_pack = bytes.size;
endfunction : byte_pack

////Methods of unpacking bytes into packets/////
virtual function void byte_unpack(const ref logic [7:0] bytes[]);
this.da = bytes[0];
this.sa = bytes[1];
this.length = bytes[2];
this.fcs = bytes[bytes.size - 1];
this.data = new[bytes.size - 4];
foreach(data[i])
data[i] = bytes[i + 3];
this.fcs = 0;
if(bytes[bytes.size - 1] != cal_fcs)
this.fcs = 1;
endfunction : byte_unpack

////How to compare packets/////
virtual function bit compare(packet pkt);
compare = 1;
if(pkt == null)
begin
$display(" ** ERROR ** : pkt : received a null object ");
compare = 0;
end
else
begin
if(pkt.da !== this.da)
begin
$display(" ** ERROR **: pkt : Da field did not match");
compare = 0;
end
if(pkt.sa !== this.sa)
begin
$display(" ** ERROR **: pkt : Sa field did not match");
compare = 0;
end

if(pkt.length !== this.length)
begin
$display(" ** ERROR **: pkt : Length field did not match");
compare = 0;
end
foreach(this.data[i])
if(pkt.data[i] !== this.data[i])
begin
$display(" ** ERROR **: pkt : Data[%0d] field did not match",i);
compare = 0;
end

if(pkt.fcs !== this.fcs)
begin
$display(" ** ERROR **: pkt : fcs field did not match %h %h",pkt.fcs ,this.fcs);
compare = 0;
end
end
endfunction : compare

endclass

Now, we will write a small program to test our data package implantation. This block is not used to verify the DUT. Write a simple block and execute an instance of the package class. Randomly group, call display method to analyze and generate. The package is then packed into bytes, then the bytes are unpacked, and the compare method is called to check all the methods.

Block source code

program test;

packet pkt1 = new();//Create two objects for the packet, and operate the properties and methods of the class through the object handles pkt1 and pkt2
packet pkt2 = new();
logic [7:0] bytes[];
initial
repeat(10)
if(pkt1.randomize)//Randomize the variables of random in pkt1
begin
$display(" Randomization Successes full.");
pkt1.display();//Calling the task dispay in the class
void'(pkt1.byte_pack(bytes));//Package data
pkt2 = new();
pkt2.byte_unpack(bytes);
if(pkt2.compare(pkt1))
$display(" Packing,Unpacking and compare worked");
else
$display(" *** Something went wrong in Packing or Unpacking or compare ***");

end
else
$display(" *** Randomization Failed ***");

endprogram

Code path:

/home/zwj/wendang/systemverilog/SV_code/switch_4

Compile simulation:

vcs -sverilog -f filelist -R -ntb_opts dtm

Fifth stage driver

In phase 5, we will write a driver, instantiate the driver in the environment, and send the packets to the DUT. The driver class is defined in the Driver.sv file. Driver is a class that generates packets, drives them to the DUT input interface, and pushes them into the mailbox.

1) Declare a packet;

packet gpkt;

2) Declare the virtual input interface of the Switch. We will connect it to the physical interface of the top-level module, as we did in the environment class.

virtual input_interface.IP input_intf;

3) Define a mailbox "drvr2sb" to send packets to the scoreboard.

mailbox drvr2sb;

4) Defines a new constructor with parameters, a virtual input interface, and a mailbox for sending packets from the driver to the scoreboard.

function new(virtual input_interface.IP input_intf_new,mailbox drvr2sb);
this.input_intf = input_intf_new ;
if(drvr2sb == null)
begin
$display(" **ERROR**: drvr2sb is null");
$finish;
end
else
this.drvr2sb = drvr2sb;

5) Construct data package in driver;

gpkt = new();

6) Define the starting method;
In the startup method, perform the following operations, and repeat the following steps num of PKTS times.

repeat($root.num_of_pkts)

Randomize the packets, and then check that the randomization completed successfully.

if ( pkt.randomize())
begin
$display (" %0d : Driver : Randomization Successes full.",$time);
...........
...........
else
begin
$display (" %0d Driver : ** Randomization failed. **",$time);

Display package contents;

pkt.display();

Then the packet is packed into bytes;

length = pkt.byte_pack(bytes);

Then by asserting the data status of the input interface signal and driving the data bytes to the data in signal, the packet bytes are sent to the switch.

foreach(bytes[i])
begin
@(posedge input_intf.clock);
input_intf.cb.data_status <= 1;
input_intf.cb.data_in <= bytes[i];
end

After all data bytes are driven, set the data status signal of the input interface low

@(posedge input_intf.clock);
input_intf.cb.data_status <= 0;
input_intf.cb.data_in <= 0;

Send the packet to the scoreboard of the mailbox "drvr2sb".

drvr2sb.put(pkt);

If randomization fails, the error counter defined in the Globals.sv file is incremented

$root.error++;

Driver class source code

`ifndef GUARD_DRIVER
`define GUARD_DRIVER

class Driver;
virtual input_interface.IP input_intf;
mailbox drvr2sb;
packet gpkt;

//// constructor method ////
function new(virtual input_interface.IP input_intf_new,mailbox drvr2sb);
this.input_intf = input_intf_new ;
if(drvr2sb == null)
begin
$display(" **ERROR**: drvr2sb is null");
$finish;
end
else
this.drvr2sb = drvr2sb;
gpkt = new();
endfunction : new

/// method to send the packet to DUT ////////
task start();
packet pkt;
int length;
logic [7:0] bytes[];
repeat($root.num_of_pkts)
begin
repeat(3) @(posedge input_intf.clock);
pkt = new gpkt;
//// Randomize the packet /////
if ( pkt.randomize())
begin
$display (" %0d : Driver : Randomization Successes full. ",$time);
//// display the packet content ///////
pkt.display();

//// Pack the packet in tp stream of bytes //////
length = pkt.byte_pack(bytes);

///// assert the data_status signal and send the packed bytes //////
foreach(bytes[i])
begin
@(posedge input_intf.clock);
input_intf.cb.data_status <= 1;
input_intf.cb.data_in <= bytes[i];
end

//// deassert the data_status singal //////
@(posedge input_intf.clock);
input_intf.cb.data_status <= 0;
input_intf.cb.data_in <= 0;

//// Push the packet in to mailbox for scoreboard /////
drvr2sb.put(pkt);

$display(" %0d : Driver : Finished Driving the packet with length %0d",$time,length);
end
else
begin
$display (" %0d Driver : ** Randomization failed. **",$time);
////// Increment the error count in randomization fails ////////
$root.error++;
end
end
endtask : start

endclass

`endif

Now, let's take the driver instance in the environment class as an example.

1) Declare the mailbox "drvr2sb", which will be used to connect the scoreboard and driver;

mailbox drvr2sb;

2) Declare the driver object "drvr";

Driver drvr;

3) In the build method, the mailbox is constructed;

drvr2sb = new();

4) In the build method, build the driver object. Deliver the input_intf and the "drvr2sb" mailbox;

drvr= new(input_intf,drvr2sb);

5) to start sending packets to DUT, invoke the boot method of "drvr" in the boot method of the environment class.

drvr.start();

Environment Class Source Code:

`ifndef GUARD_ENV
`define GUARD_ENV

class Environment ;


virtual mem_interface.MEM mem_intf ;
virtual input_interface.IP input_intf ;
virtual output_interface.OP output_intf[4] ;

Driver drvr;
mailbox drvr2sb;

function new(virtual mem_interface.MEM mem_intf_new ,
virtual input_interface.IP input_intf_new ,
virtual output_interface.OP output_intf_new[4] );

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

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

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

drvr2sb = new();
drvr= new(input_intf,drvr2sb);

$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
mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 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);

mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
$display(" %0d : Environment : Port 0 Address %h ",$time,`P0);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
$display(" %0d : Environment : Port 1 Address %h ",$time,`P1);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
$display(" %0d : Environment : Port 2 Address %h ",$time,`P2);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
$display(" %0d : Environment : Port 3 Address %h ",$time,`P3);

@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;


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

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

drvr.start();

$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(10000) @(input_intf.clock);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end

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

task report();
endtask : report
endclass

`endif

Source path:

/home/zwj/wendang/systemverilog/SV_code/switch_5/

Simulation command:

vcs -sverilog -f filelist -R -ntb_opts dtm

Stage 6 RECEIVER

At this stage, we will write a receiver and use the receiver in the environment class to collect the data package of the output ﹣ interface of the switch.
The receiver collects data bytes from the interface signal. The bytes are then unpacked into the packet and pushed into the mailbox. The receiver class is written in the reveicer.sv file.

1) Declare a virtual output interface. We connect it to the physical interface of the top module, as we do in the environment class;

virtual output_interface.OP output_intf;

2) Declare a mailbox "rcvr2sb" to send packets to the scoreboard;

mailbox rcvr2sb;

3) The parameter virtual input interface and mailbox are used to define a new constructor, which is used to send packets from the receiver to the scoreboard;

function new(virtual output_interface.OP output_intf_new,mailbox rcvr2sb);
this.output_intf = output_intf_new ;
if(rcvr2sb == null)
begin
$display(" **ERROR**: rcvr2sb is null");
$finish;
end
else
this.rcvr2sb = rcvr2sb;
endfunction : new

4) Define the starting method;
In the start method, do the following and wait for the DUT to signal ready.

wait(output_intf.cb.ready)

If the ready signal is asserted, the request is sent by the DUT from the data output signal by asserting the read signal. When the DUT completes the data to be sent, it cancels the ready signal. Once the ready signal is de asserted, stop collecting data bytes and remove the read signal.

output_intf.cb.read <= 1;
repeat(2) @(posedge output_intf.clock);
while (output_intf.cb.ready)
begin
bytes = new[bytes.size + 1](bytes);
bytes[bytes.size - 1] = output_intf.cb.data_out;
@(posedge output_intf.clock);
end
output_intf.cb.read <= 0;
@(posedge output_intf.clock);
$display(" %0d : Receiver : Received a packet of length %0d",$time,bytes.size);

Create a new package object.

pkt = new();

Then the unpack method of the packet is invoked to unpack the byte, and then the contents of the packet are displayed.

pkt.byte_unpack(bytes);
pkt.display();

The packet is then sent to the scoreboard.

rcvr2sb.put(pkt);

Delete dynamic array bytes.

bytes.delete();

Receiver class source code:

`ifndef GUARD_RECEIVER
`define GUARD_RECEIVER

class Receiver;

virtual output_interface.OP output_intf;
mailbox rcvr2sb;

//// constructor method ////
function new(virtual output_interface.OP output_intf_new,mailbox rcvr2sb);
this.output_intf = output_intf_new ;
if(rcvr2sb == null)
begin
$display(" **ERROR**: rcvr2sb is null");
$finish;
end
else
this.rcvr2sb = rcvr2sb;
endfunction : new

task start();
logic [7:0] bytes[];
packet pkt;
forever
begin
repeat(2) @(posedge output_intf.clock);
wait(output_intf.cb.ready)
output_intf.cb.read <= 1;
repeat(2) @(posedge output_intf.clock);
while (output_intf.cb.ready)
begin
bytes = new[bytes.size + 1](bytes);
bytes[bytes.size - 1] = output_intf.cb.data_out;
@(posedge output_intf.clock);
end
output_intf.cb.read <= 0;
@(posedge output_intf.clock);
$display(" %0d : Receiver : Received a packet of length %0d",$time,bytes.size);
pkt = new();
pkt.byte_unpack(bytes);
pkt.display();
rcvr2sb.put(pkt);
bytes.delete();
end
endtask : start

endclass

`endif

Now, we will use an instance of the receiver in the environment class.

1) Declare mailbox "rcvr2sb" that will be used to connect scoreboard and receiver

mailbox rcvr2sb;

2) Declare 4 receiver objects "rcvr";

Receiver rcvr[4];

3) In the build method, the mailbox is constructed;

rcvr2sb = new();

4) In the build method, construct the receiver object. Deliver the output_intf and "rcvr2sb" mailboxes. There are four output interfaces and receiver objects. We will connect a receiver for an output interface.

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

5) to start collecting data packets from DUT, please invoke the "rcvr" startup method in the "startup" method of the environment class.

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

Environment class source code:

`ifndef GUARD_ENV
`define GUARD_ENV

class Environment ;


virtual mem_interface.MEM mem_intf ;
virtual input_interface.IP input_intf ;
virtual output_interface.OP output_intf[4] ;

Driver drvr;

Receiver rcvr[4];

mailbox drvr2sb;

mailbox rcvr2sb;

function new(virtual mem_interface.MEM mem_intf_new ,
virtual input_interface.IP input_intf_new ,
virtual output_interface.OP output_intf_new[4] );

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

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

function void build();
$display(" %0d : Environment : start of build() method",$time);
drvr2sb = new();

rcvr2sb = new();

drvr= new(input_intf,drvr2sb);

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

$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
mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 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);

mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
$display(" %0d : Environment : Port 0 Address %h ",$time,`P0);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
$display(" %0d : Environment : Port 1 Address %h ",$time,`P1);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
$display(" %0d : Environment : Port 2 Address %h ",$time,`P2);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
$display(" %0d : Environment : Port 3 Address %h ",$time,`P3);

@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;


$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();
rcvr[2].start();
rcvr[3].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(10000) @(input_intf.clock);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end

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

task report();
endtask: report
endclass

`endif

Source path:

/home/zwj/wendang/systemverilog/SV_code/switch_6/

Simulation command:

vcs -sverilog -f filelist -R -ntb_opts dtm

Stage 7 scoreboard

At this stage, we will see the implementation of the scoreboard. The scoreboard has two mailboxes. One is used to get packets from the driver, the other is used to get packets from the receiver. Then compare the packets and declare an error if they do not match. The scoreboard has been implemented in the file Scoreboard.sv.

1) Two mailboxes drvr2sb and rcvr2sb are declared;

mailbox drvr2sb;
mailbox rcvr2sb;

2) The constructor method is declared with the "drvr2sb" and "rcvr2sb" mailboxes as parameters.

function new(mailbox drvr2sb,mailbox rcvr2sb);

3) Connect the mailbox of the constructor to the mailbox of the scoreboard.

this.drvr2sb = drvr2sb;
this.rcvr2sb = rcvr2sb;

4) Define the startup method.
Always perform the following steps.
Wait until there is a packet in "rcvr2sb". Then pop up the packet from the mailbox.

rcvr2sb.get(pkt_rcv);
$display(" %0d : Scorebooard : Scoreboard received a packet from receiver ",$time);

Then the packet is extracted from drvr2sb.

drvr2sb.get(pkt_exp);

If two packets are not equal, compare them and add an error counter.

if(pkt_rcv.compare(pkt_exp))
$display(" %0d : Scoreboardd :Packet Matched ",$time);
else
$root.error++;

Scoreboard class source code:

`ifndef GUARD_SCOREBOARD
`define GUARD_SCOREBOARD

class Scoreboard;

mailbox drvr2sb;
mailbox rcvr2sb;

function new(mailbox drvr2sb,mailbox rcvr2sb);
this.drvr2sb = drvr2sb;
this.rcvr2sb = rcvr2sb;
endfunction:new


task start();
packet pkt_rcv,pkt_exp;
forever
begin
rcvr2sb.get(pkt_rcv);
$display(" %0d : Scorebooard : Scoreboard received a packet from receiver ",$time);
drvr2sb.get(pkt_exp);
if(pkt_rcv.compare(pkt_exp))
$display(" %0d : Scoreboardd :Packet Matched ",$time);
else
$root.error++;
end
endtask : start

endclass

`endif

Now we'll see how to connect the scoreboard in the environment class.

1) Declare a scoreboard.

Scoreboard sb;

2) Build the scoreboard in the build method. Pass the drvr2sb and rcvr2sb mailboxes to the scoreboard constructor.

sb = new(drvr2sb,rcvr2sb);

3) Start the scoreboard method in the start method.

sb.start();

4) The construction of verification environment is close to the end. In the report() method of the environment class, print the status of test passed or test failed according to the error count.

task report();
$display("\n\n*************************************************");
if( 0 == $root.error)
$display("******** TEST PASSED *********");
else
$display("******** TEST Failed with %0d errors *********",$root.error);

$display("*************************************************\n\n");
endtask : report

Source code of environment class:

`ifndef GUARD_ENV
`define GUARD_ENV

class Environment ;


virtual mem_interface.MEM mem_intf ;
virtual input_interface.IP input_intf ;
virtual output_interface.OP output_intf[4] ;

Driver drvr;
Receiver rcvr[4];

Scoreboard sb;

mailbox drvr2sb ;
mailbox rcvr2sb ;

function new(virtual mem_interface.MEM mem_intf_new ,
virtual input_interface.IP input_intf_new ,
virtual output_interface.OP output_intf_new[4] );

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

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

function void build();
$display(" %0d : Environment : start of build() method",$time);
drvr2sb = new();
rcvr2sb = new();

sb = new(drvr2sb,rcvr2sb);

drvr= new(input_intf,drvr2sb);
foreach(rcvr[i])
rcvr[i]= new(output_intf[i],rcvr2sb);
$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
mem_intf.cb.mem_data <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_en <= 0;
mem_intf.cb.mem_rd_wr <= 0;
input_intf.cb.data_in <= 0;
input_intf.cb.data_status <= 0;
output_intf[0].cb.read <= 0;
output_intf[1].cb.read <= 0;
output_intf[2].cb.read <= 0;
output_intf[3].cb.read <= 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);

mem_intf.cb.mem_en <= 1;
@(posedge mem_intf.clock);
mem_intf.cb.mem_rd_wr <= 1;

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h0;
mem_intf.cb.mem_data <= `P0;
$display(" %0d : Environment : Port 0 Address %h ",$time,`P0);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h1;
mem_intf.cb.mem_data <= `P1;
$display(" %0d : Environment : Port 1 Address %h ",$time,`P1);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h2;
mem_intf.cb.mem_data <= `P2;
$display(" %0d : Environment : Port 2 Address %h ",$time,`P2);

@(posedge mem_intf.clock);
mem_intf.cb.mem_add <= 8'h3;
mem_intf.cb.mem_data <= `P3;
$display(" %0d : Environment : Port 3 Address %h ",$time,`P3);

@(posedge mem_intf.clock);
mem_intf.cb.mem_en <=0;
mem_intf.cb.mem_rd_wr <= 0;
mem_intf.cb.mem_add <= 0;
mem_intf.cb.mem_data <= 0;


$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();
rcvr[2].start();
rcvr[3].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(10000) @(input_intf.clock);
$display(" %0d : Environment : end of wait_for_end() method",$time);
endtask : wait_for_end

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

task report();

$display("\n\n*************************************************");
if( 0 == $root.error)
$display("******** TEST PASSED *********");
else
$display("******** TEST Failed with %0d errors *********",$root.error);

$display("*************************************************\n\n");

endtask : report

endclass
`endif

Source path:

/home/zwj/wendang/systemverilog/SV_code/switch_7/

The eighth stage

In this stage, we will write the function introduction of switch protocol. The functional coverage is written in the Coverage.sv file. After running the simulation, you will analyze the coverage results to find out if some test scenarios have not been executed and write tests for testing.
What we need to cover is
1) Covering all port address configurations;
2) Covering all packet lengths;
3) Overwrite all correct and incorrect length fields;
4) Covering good and bad FCS;
5) All of the above combinations are covered;

1) Define an overlay group with the following overlay points.
a) All packet lengths:

length : coverpoint pkt.length;

b) All port addresses:

da : coverpoint pkt.da {
bins p0 = { `P0 };
bins p1 = { `P1 };
bins p2 = { `P2 };
bins p3 = { `P3 }; }

c) Correct and incorrect length field types:

length_kind : coverpoint pkt.length_kind;

d) The quality of FCS:

fcs_kind : coverpoint pkt.fcs_kind;

e) Cross product of all the above points:

all_cross: cross length,da,length_kind,fcs_kind;

2) In the constructor method, construct the override group

function new();
switch_coverage = new();
endfunction : new

3) Write a task that calls the sample method to cover the main points.

task sample(packet pkt);
this.pkt = pkt;
switch_coverage.sample();
endtask:sample

Source code for override category:

`ifndef GUARD_COVERAGE
`define GUARD_COVERAGE

class coverage;
packet pkt;

covergroup switch_coverage;

length : coverpoint pkt.length;
da : coverpoint pkt.da {
bins p0 = { `P0 };
bins p1 = { `P1 };
bins p2 = { `P2 };
bins p3 = { `P3 }; }
length_kind : coverpoint pkt.length_kind;
fcs_kind : coverpoint pkt.fcs_kind;

all_cross: cross length,da,length_kind,fcs_kind;
endgroup

function new();
switch_coverage = new();
endfunction : new

task sample(packet pkt);
this.pkt = pkt;
switch_coverage.sample();
endtask:sample

endclass

`endif

Now we will use this instance of the coverage class in the scoreboard.
1) Take the coverage class as an example, and construct it in the scoreboard class.

coverage cov = new();

2) Call the sample method and pass exp ﹣ pkt to the sample method

cov.sample(pkt_exp);

Source code of scoreboard class:

`ifndef GUARD_SCOREBOARD
`define GUARD_SCOREBOARD

class Scoreboard;

mailbox drvr2sb;
mailbox rcvr2sb;

coverage cov = new();

function new(mailbox drvr2sb,mailbox rcvr2sb);
this.drvr2sb = drvr2sb;
this.rcvr2sb = rcvr2sb;
endfunction:new


task start();
packet pkt_rcv,pkt_exp;
forever
begin
rcvr2sb.get(pkt_rcv);
$display(" %0d : Scorebooard : Scoreboard received a packet from receiver ",$time);
drvr2sb.get(pkt_exp);
if(pkt_rcv.compare(pkt_exp))
begin
$display(" %0d : Scoreboardd :Packet Matched ",$time);

cov.sample(pkt_exp);

end
else
$root.error++;
end
endtask : start

endclass

`endif

Stage 9

In this stage, we will write a constraint random test case. Let's verify the DUT by sending large packets longer than 200.
1) In the test case file, define a small packet class. This call is inherited from the package class, and the data.size() field is constrained to generate a package larger than 200.

class small_packet extends packet;

constraint small_c { data.size > 200 ; }

endclass

2) In the block, create an object of a small package class. Then we call the construction method of env.

small_packet spkt;

3) Pass the object of small packet to the packet handle in the driver.

env.drvr.gpkt = spkt;

Then call the reset(), cfg_dut(), start(), wait_for_end() and report() methods as if you were running the method.

env.reset();
env.cfg_dut();
env.start();
env.wait_for_end();
env.report();

Source code of constraint test case

`ifndef GUARD_TESTCASE
`define GUARD_TESTCASE

class small_packet extends packet;

constraint small_c { data.size > 200 ; }

endclass

program testcase(mem_interface.MEM mem_intf,input_interface.IP input_intf,output_interface.OP output_intf[4]);

Environment env;
small_packet spkt;

initial
begin
$display(" ******************* Start of testcase ****************");
spkt = new();
env = new(mem_intf,input_intf,output_intf);
env.build();
env.drvr.gpkt = spkt;
env.reset();
env.cfg_dut();
env.start();
env.wait_for_end();
env.report();
#1000;
end

final
$display(" ******************** End of testcase *****************");

endprogram
`endif

Published 80 original articles, won praise 9, visited 9584
Private letter follow

Tags: Verilog Attribute

Posted on Fri, 13 Mar 2020 04:12:20 -0700 by cfemocha