This section will introduce the first part of lab5, which mainly summarizes the ways of learning and thinking about general design and the points that need to be emphasized. At the same time, taking lab5 as a reference, it will analyze the flow direction of data flow, verify the communication and abstraction of components, and realize the overall idea.
Lab5 braod spectrum verification (Part I)
Experimental objectives
First, clarify the experimental objectives. According to the experimental guidance in lab4, we have realized the data transmission and detection of the specified port. In this section of the lab, we will realize 16 channels to send data at the same time and monitor 16 groups of output chanel at the same time. The validation component is further encapsulated in the form of class. Further improve our verification structure.
Train of thought combing
We have defined the design goal, and we can obviously feel that lab5 is more abstract than the previous one. According to the most basic verification component framework, combined with rest SV code to sort out the data flow and the approximate sequence of program operation. The more detailed part will be explained and combed in the module analysis of the design.
You need to understand two basic concepts, mailbox and semaphore. Simply put, mailbox is used for data interaction between threads. Semaphore implements access control of the same resource. Refer to the author's Communication between System Verilog threads
The first is the generation of data, which is generated by the Generator in the form of Package and put into out_box is sent to the Driver. The Driver will send data from the corresponding drvr according to sa, and Package the data to the Driver of scoreboard_ Mbox, compare the data by the scoreboard, and then rcvr receive it to the receiver of the scoreboard_ Mbox, compare the data. The specific comparison method will be described in subsequent codes.
There are some points that need to be considered here
- gen data generation form and data format.
- How to solve the problem of sending multiple sd's to one da
- How does the Driver use the data generated by gen to complete data transmission
- How does scoreboard compare data
- How does the test environment stop the simulation
This blog post will analyze the generation, transmission and overall verification structure of data. The next chapter analyzes data monitoring and data comparison.
sim.do file
set rtl ../../rtl/router.v set svtb {./router_test_top.sv ./router_io.sv ./test.sv} vlib work vmap work work vlog $rtl vlog -sv ./router_test_top.sv ./router_io.sv ./test.sv vsim -t ns -novopt +notimingchecks -l router_test_top.log work.router_test_top run -all
code analysis
First, look at the top layer of the test. The top layer instantiates the interface and test program. These are the same as before and will not be explained here. It is important to analyze the test program. In order to ensure the integrity of the code, the analysis will show all the code.
router_test_top.sv
`timescale 1ns/100ps module router_test_top; parameter simulation_cycle = 100; bit SystemClock;//bit is binary logic. There are only 0 and 1. The initial value is 0 by default router_io top_io(SystemClock); test t(top_io); router dut( .reset_n (top_io.reset_n), .clock (top_io.clock), .din (top_io.din), .frame_n (top_io.frame_n), .valid_n (top_io.valid_n), .dout (top_io.dout), .valido_n (top_io.valido_n), .busy_n (top_io.busy_n), .frameo_n (top_io.frameo_n) ); initial begin $timeformat(-9, 1, "ns", 10); SystemClock = 0; forever begin #(simulation_cycle/2) SystemClock = ~SystemClock; end end endmodule
router_test.h
Mailbox is a communication mechanism, which enables data to be transmitted and communicated between processes. Data is sent by one process to another mailbox, and another process can obtain it.
mailbox #(type = dynamic_type)
Where dynamic_type represents a special type that can perform type checking at run time (by default). Refer to the author's Communication between System Verilog threads
typedef class Packet; typedef mailbox #(Packet) pkt_mbox;
router_io.sv
`timescale 1ns/100ps interface router_io(input bit clock); logic reset_n; logic [15:0] din; logic [15:0] frame_n; logic [15:0] valid_n; logic [15:0] dout; logic [15:0] valido_n; logic [15:0] busy_n; logic [15:0] frameo_n; clocking cb @(posedge clock); //default input #1 output #1; output reset_n; output din; output frame_n; output valid_n; input dout; input valido_n; input busy_n; input frameo_n; endclocking modport TB(clocking cb, output reset_n); endinterface
test.sv
16 test programs are instantiated, including semaphore, Driver and Receiver, and one Generator and Scoreboard
. Here, all the corresponding data (out_boxes) generated by gen are sent to the mailbox of each instantiated drvr [] and each drvr [] is copied and put into the mailbox of the drvr itself. drvr completes the transmission and puts the transmitted data into the driver_mbox, the sending is completed
`timescale 1ns/100ps program automatic test(router_io.TB rtr_io); `include "Packet.sv" int run_for_n_packets; // number of packets to test int TRACE_ON = 1; `include "router_test.h" `include "Driver.sv" `include "Receiver.sv" `include "Generator.sv" `include "Scoreboard.sv" semaphore sem[]; //Declare dynamic array Driver drvr[]; // driver Receiver rcvr[]; // receiver Generator gen; // generator Scoreboard sb; // scoreboard initial begin run_for_n_packets = 200; sem = new[16]; //Allocate 16 elements (16 handles) drvr = new[16]; rcvr = new[16]; gen = new("gen"); sb = new("sb"); foreach (sem[i]) sem[i] = new(1);//Assign a key to each semaphore foreach (drvr[i]) drvr[i] = new($sformatf("drvr[%0d]",i), i, sem, gen.out_box[i], sb.driver_mbox, rtr_io); foreach (rcvr[i]) rcvr[i] = new($sformatf("rcvr[%0d]",i), i, sb.receiver_mbox, rtr_io); //The above processes do not consume time, so they have been completed at simulation time 0. Complete communication and between components reset(); gen.start(); sb.start(); foreach (drvr[i]) drvr[i].start(); foreach (rcvr[i]) rcvr[i].start();//The start function takes no time, so it is also executed at the same time after the reset. You can view the print information yourself wait(sb.DONE.triggered);//Wait for scoreboard end event to be triggered end task reset(); if (TRACE_ON) $display("[TRACE]%t :%m", $realtime); rtr_io.reset_n = 1'b0; rtr_io.cb.frame_n <= '1; rtr_io.cb.valid_n <= '1; #2 rtr_io.cb.reset_n <= 1'b1; repeat(15) @(rtr_io.cb); endtask: reset endprogram: test
Generator.sv
%m will print the directory structure
out_box [] will pass in the corresponding drvr and send it, which is also the logic of data generation. There are 16 groups_ box. Each group is sent to the corresponding drvr. Each group of SAS is put into the sa me out_box.
Packet pkt = new this.pkt2send;// Copy a copy of pkt2send to pkt. This sentence involves shallow copy through new. See Chapter 5.15 of the green paper
Copying an object using the new operator is simple and reliable. It creates a new object and copies all the variables of the existing object. This is a simple copy, which is similar to a photocopy of the original object. The value of the original object is blindly copied to the destination object. If a class contains a handle to another class, only the objects at the highest level will be copied by the new operator, and the objects at the lower level will not be copied.
`ifndef INC_GENERATOR_SV `define INC_GENERATOR_SV class Generator; string name; // unique identifier Packet pkt2send; // stimulus Packet object pkt_mbox out_box[]; // mailbox to Drivers extern function new(string name = "Generator"); extern virtual task gen(); extern virtual task start(); endclass: Generator function Generator::new(string name); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, name); this.name = name; this.pkt2send = new(); this.out_box = new[16];//Allocate 16 elements (16 handles) foreach(this.out_box[i]) this.out_box[i] = new();//Add each mailbox new endfunction: new task Generator::gen(); static int pkts_generated = 0; if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name); this.pkt2send.name = $psprintf("Packet[%0d]", pkts_generated++); if (!this.pkt2send.randomize()) begin $display("\n%m\n[ERROR]%t Randomization Failed!\n", $realtime); $finish; end endtask: gen task Generator::start(); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name); fork for (int i=0; i<run_for_n_packets || run_for_n_packets <= 0; i++) begin this.gen(); begin Packet pkt = new this.pkt2send;//Copy a copy of pkt2send to pkt //this.out_box[pkt.sa].put(this.pkt2send); this.out_box[pkt.sa].put(pkt);//Send to the corresponding out according to the sa of the data itself_ In box end end join_none//In order not to be in rest Block the following processes in SV and use join_none. However, it is also possible to change it to join here, because there is no delay statement here endtask: start `endif
DriverBase.sv
The function of DriverBase is to send the data in pkt2send to the corresponding da port, which is the same as the previous implementation. Send data to the interface.
`ifndef INC_DRIVERBASE_SV `define INC_DRIVERBASE_SV class DriverBase; virtual router_io.TB rtr_io; // interface signal string name; // unique identifier bit[3:0] sa, da; // source and destination addresses logic[7:0] payload[$]; // Packet payload Packet pkt2send; // stimulus Packet object extern function new(string name = "DriverBase", virtual router_io.TB rtr_io); extern virtual task send(); extern virtual task send_addrs(); extern virtual task send_pad(); extern virtual task send_payload(); endclass function DriverBase::new(string name, virtual router_io.TB rtr_io); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, name); this.name = name; this.rtr_io = rtr_io; endfunction task DriverBase::send(); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name); this.send_addrs(); this.send_pad(); this.send_payload(); endtask task DriverBase::send_addrs(); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name); this.rtr_io.cb.frame_n[this.sa] <= 1'b0; for(int i=0; i<4; i++) begin this.rtr_io.cb.din[this.sa] <= this.da[i]; @(this.rtr_io.cb); end endtask task DriverBase::send_pad(); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name); this.rtr_io.cb.din[this.sa] <= 1'b1; this.rtr_io.cb.valid_n[this.sa] <= 1'b1; repeat(5) @(this.rtr_io.cb); endtask task DriverBase::send_payload(); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name); foreach(this.payload[index]) begin for(int i=0; i<8; i++) begin this.rtr_io.cb.din[this.sa] <= this.payload[index][i]; this.rtr_io.cb.valid_n[this.sa] <= 1'b0; this.rtr_io.cb.frame_n[this.sa] <= ((index == (this.payload.size() - 1)) && (i == 7)); @(this.rtr_io.cb); end end this.rtr_io.cb.valid_n[this.sa] <= 1'b1; endtask `endif
Driver .sv
Copy the data generated at the top level to each drvr instantiated. Then sem came in. Each instantiated Driver can send data and has its own ID. Assuming the ID is 3, only data with sa=3 can be sent.
this.in_box.get(this.pkt2send); Here, take out a copy of the incoming verse and put it in pkt2send. Next, determine whether the sa sending port is the current port.
if (this.pkt2send.sa != this.sa) continue;
Note the continue here. When the sa of the comparison data is inconsistent with the current port, continue is to exit the current cycle and start the next cycle. Equivalent to this data
`ifndef INC_DRIVER_DV `define INC_DRIVER_DV `include "DriverBase.sv" class Driver extends DriverBase; pkt_mbox in_box; // Generator mailbox pkt_mbox out_box; // Scoreboard mailbox semaphore sem[]; // output port arbitration extern function new(string name = "Driver", int port_id, semaphore sem[], pkt_mbox in_box, out_box, virtual router_io.TB rtr_io); extern virtual task start(); endclass function Driver::new(string name, int port_id, semaphore sem[], pkt_mbox in_box, out_box, virtual router_io.TB rtr_io); super.new(name, rtr_io); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name); this.sa = port_id;//There are 16 IDS in total. These IDS correspond to 16 SAS, that is, one SA has one sending incentive. The DRIVER label corresponding to SA is the same. If SA is 1, the corresponding instantiated transmission data is used this.sem = sem;//Pass the sem of the top layer of the test into the internal this.in_box = in_box;//Copy the data generated at the top level to each drvr instantiated this.out_box = out_box; endfunction: new task Driver::start(); if (TRACE_ON) $display("[TRACE]%t %s:%m", $realtime, this.name); fork forever begin this.in_box.get(this.pkt2send);//Get the packet content from the copied data if (this.pkt2send.sa != this.sa) continue;//From in_ Get relevant sending information in box. Including where it comes from, where it goes, and how many related information it contains. If the Driver ID is different from it, it will be put to the next judgment. Otherwise, it will exit to receive the next message. //In fact, there is no need to judge here, because the data sa in the incoming mailbox corresponds to the ID currently sent. See Generator this.da = this.pkt2send.da; this.payload = this.pkt2send.payload; this.sem[this.da].get(1);//Here, multiple SAS are prevented from sending the same da. Protect. When multiple SAS send data to a DA, they will block, forming a protection. this.send(); this.out_box.put(this.pkt2send);//Put the sent data into out_ In box this.sem[this.da].put(1); end join_none endtask: start `endif