Support for CDC from HW side in IDS-Verify

Introduction:

A typical SoC register block can be accessed from two sides. On one side, there is a register bus interface (i.e., the software side), while on the other side, there are signals driven by the user’s application logic (i.e., the hardware side). A register bus can be any industry standard parallel bus protocol like AMBA-AHB, AMBA-APB, AXI, Tilelink, or serial buses like I2C or SPI. The register bus forms the SW interface, while the signals driven from the user’s application logic form the HW interface.

In a multi-clock design, some or all three of these – SW interface, HW interface, and register block – are on different asynchronous clocks. Thus, the design can have the following three possible clock domains:

  • Register bus clock domain
  • Register block clock domain 
  • Application logic clock domain

 

As both data and control signals are transferred across these clock domains, proper synchronization techniques must be used to avoid metastability.

Currently, only CDC from the HW side is supported in IDS-Verify.

HW Interface Side CDC:

The register block HW interface consists of multiple data and control signals, which are used to perform read and write operations on any volatile register field in the block. IDesignSpecTM supports both simple mux synchronization and sophisticated handshake synchronization techniques to deal with metastability on the HW interface signals crossing the clock domain boundary.

Mux Synchronization:

In Mux synchronizer instead of the multibit data an enable signal is synchronized into the register block clock domain using a 2FF synchronizer. This synchronized signal act as the load pulse for the data.

The read data (<register_name>Field_r) is synchronized between the register block clock domain to the HW clock domain using a 2 Flip-Flop synchronizer.

Below, a sample SystemRDL specification shows how the mux synchronization technique can be configured on the HW interface of a register field in IDesignSpecTM:

property cdc_clk {type = string; component = reg;};                                                             
addrmap Block1{                                           
  reg Reg1{ 
    cdc_clk = "hw_clk";
    field {}fld [0:0];
  };
 Reg1 Reg1;
};

 

Handshake Synchronization: 

The mux synchronization approach works in design scenarios, where it is not required to sample each data value from the sending clock domain into the receiving clock domain. 

In designs where the sending clock frequency is greater than the receiving clock frequency, the sending clock domain data can change before it is synchronized into the receiving domain. Using a mux synchronizer in these designs can lead to data loss. A better approach is to use a handshake synchronizer, in which the feedback acknowledge path is used to hold the field write busy (<>fld_busy_out) signal and the write data value should only be changed if the write busy signal is low.

During the write operation, as soon as the write enable signal is asserted, a write busy (<>Field_busy_out) flag is set. Then, this write enable control signal (<>Field_in_enb) is synchronized into the register block domain through a two-flip-flop synchronizer as a write request. In the register block, this synchronized write request is used to load the HW write data (<>Field_in) into the required flip-flop and an acknowledgement is sent back to the HW domain. 

An FSM in the HW domain controls the write busy flag, which is deasserted only when acknowledgement comes back from the register block. This enables the write data value to change only when the write busy signal is low and thus ensures that every data value is sampled in the register block.

Write Operation

Similarly, during the read cycle, an FSM in the register block domain sets the read_busy flag on the read request. The toggle read request is then synchronized into the HW domain using a two-flip-flop synchronizer. The synchronized toggle is then converted into a load pulse on which the registered field data in the HW domain is fed into the HW read data bus (<>Field_r) and an acknowledgement is sent back, which deasserts the read_busy flag. As the field value can only be updated when the read_busy flag is low, no data loss occurs.

Read Operation

A sample SystemRDL specification below shows how handshake synchronization techniques can be configured on the HW interface of a register field in IDesignSpecTM:

Use Cases

IDS-NG:

SystemRDL

property cdc_clock {type =string; component = reg ; };
addrmap cdc1 {
 name  = "cdc1 Address Map";
 reg reg1 { 
    regwidth = 32;
     cdc_clock = "hw_clk:handshake" ;  
   field {
     hw = rw;
     sw = rw;
     onread=r;
         onwrite=w;
   } f1[31:0] = 32'h0;
 };
 reg reg2 { 
    regwidth = 32;
   field {
     hw = rw;
     sw = rw;
     onread=r;
         onwrite=w;
   } f2[31:0] = 32'h0;
 };
 reg1 reg1 @0x0;
 reg2 reg2 @0x4;
};

 

Generated Top file output:




cdc1_ids #(.addr_width(addr_width),.bus_width(bus_width)) cdc1(  .reg1_enb(cdc1_hw.reg1_enb),
.hw_clk(cdc1_hw.hw_clk ),
.reg1_f1_in_enb(cdc1_hw.reg1_f1_in_enb),
.reg1_f1_in(cdc1_hw.reg1_f1_in),
.reg1_f1_r(cdc1_hw.reg1_f1_r),
.reg2_enb(cdc1_hw.reg2_enb),
.reg2_f2_in_enb(cdc1_hw.reg2_f2_in_enb),
.reg2_f2_in(cdc1_hw.reg2_f2_in),
.reg2_f2_r(cdc1_hw.reg2_f2_r),



 bit clk;
  bit hw_clk;
 assign cdc1_ambaapb.pclk = clk;
 assign cdc1_hw.clk=clk;
  assign cdc1_hw.hw_clk=hw_clk;
 always
   #5 clk = ~clk;
  always 
    begin 
        #5 hw_clk = ~hw_clk;
    end 
 initial
   begin
     clk = 1'b0;
      hw_clk = 1'b0;


 

Conclusion:

In a multi-clock-domain design, metastability is inevitable. However, by following critical design guidelines and using correct and well-established synchronization techniques, these expensive CDC failures can be avoided. IDesignSpecTM largely contributes to plugging these failures by automatically generating code that follows CDC design guidelines and allows users to configure different synchronization techniques according to their design requirements.

Steps to Start Using:

  1. If you don’t have already, get the license for IDS-Verify
  2. Use the property cdc_clk=”clock_name”
  3. Generate not only RTL and UVM RAL model using IDS, but also automatic tests using IDS-Verify

Accumulator Register

Introduction

Accumulator register plays a pivotal role in modern digital designs, enabling the accumulation and summation of data over time. They offer a powerful mechanism to capture and store accumulated values, making them essential components in various applications such as signal processing, data analytics, and statistical analysis.

In Verilog, accumulator registers are implemented using reg data types and can be utilized to accumulate values within a procedural block or across multiple clock cycles. By leveraging the capabilities of Verilog, designers can create efficient and flexible designs that can process and accumulate data in a systematic manner.

On the verification side, the Universal Verification Methodology (UVM) provides a standardized and robust framework for register-based verification. The Register Abstraction Layer (RAL) within UVM offers a convenient way to model and verify accumulator registers. By employing the UVM RAL, verification engineers can easily define register models, configure registers with desired properties, and perform verification operations such as read, write, and accumulation checks.

To effectively work with accumulator registers in Verilog and UVM RAL, it is essential to understand their features, limitations, and best practices. Moreover, familiarizing oneself with the various verification techniques, such as randomization, functional coverage, and assertions, can greatly enhance the verification process and ensure comprehensive validation of the register behavior.

Example:

IDS-NG:

SystemRDL:

property accumulate{type = string; component = reg|field; }; 

addrmap block_n1{
 reg reg_n1 { 
accumulate = true;
     field {
     hw = r;
     sw = rw;
     } F1[31:0] = 32'h0;
 };
 reg_n1 reg_n1 @0x0;
};

 

Generated Verilog

.
.
.
always @(posedge clk)  begin
   if (!reset_l)
       begin
           reg_n1_F1_q <= 32'd0;
       end
   else
       begin
       if (reg_n1_wr_valid) //F1 : SW Write
           begin
              {reg_n1_overflow,reg_n1_F1_q} <= reg_n1_F1_q + (wr_data [31 : 0]  & reg_enb  [31 : 0] ) | (reg_n1_F1_q & (~reg_enb  [31 : 0] ));
           end
       end
  end //end always
   assign reg_n1_F1_r = reg_n1_F1_q; // Field : F1
    assign reg_n1_rd_data  = reg_n1_rd_valid ? {reg_n1_F1_q} : 32'd0;

    assign reg_n1_accum_err =reg_n1_overflow? 1'b1 :1'b0;
    assign acum_err = reg_n1_accum_err;   
   assign rd_data_vld = rd_stb;
   assign rd_data = reg_n1_rd_data ;
   assign request = 1'b1;
   assign rd_wait = 1'b1;
    assign error = 1’b0;   

 

 Generated accumulator UVM callback in the package file 

. . .
. . .
reg_n1.add_hdl_path_slice("reg_n1_F1_q", 0, 32);
     begin
          acum_cb acum_block_n1_reg_n1_F1;
          acum_block_n1_reg_n1_F1 = new("acum_block_n1_reg_n1_F1",
reg_n1.F1);
          uvm_reg_field_cb::add(reg_n1.F1,acum_block_n1_reg_n1_F1);
     end
. . .
. . .
//*_pkg.regmem.sv file
class acum_cb extends uvm_reg_cbs;
       uvm_reg_field m_estatus;

       function new( string name,uvm_reg_field estatus);
           super.new(name);
            m_estatus = estatus;
       endfunction
virtual task pre_write(uvm_reg_item rw);
    if(rw.status == UVM_IS_OK) begin         void'(m_estatus.predict(m_estatus.get_mirrored_value() + m_estatus.get()));
       end
       endtask  
   endclass
. . .
. . .

 

Conclusion 

The accumulator is used for many computing tasks and plays a crucial role in computing systems. Their ability to store and accumulate intermediate results makes them invaluable in performing arithmetic operations and carrying out iterative tasks with limited resources.

While a device is working on multi-step operations, intermediate values are sent to the accumulator and then overwritten as needed.

Steps to Start Using

In order to start using the accumulator register, simply start using the property “accumulator=true” in your IDS specification.

Support for Python multi-out output

Introduction  

A new feature has been  introduced in IDS to support multi-out in Python output, which enables users to generate multiple Python output files based on hierarchical structures. This feature enables users to obtain specific Python output files for individual chips within the hierarchy, providing great flexibility and ease of use.

How it Works

To generate Python multi out files, users need to utilise the “-if” switch during the output generation process. This switch instructs the system to generate separate Python output files for each chip in the hierarchy.

Example
Let’s take a look at an example to understand how Python multi out works. Consider the following example:

SystemRDL

property chip {type=boolean; component=addrmap;};
reg my_reg{
 field{}F[31:0]=0;
};
addrmap my_block{
 my_reg my_reg;
};
addrmap chip_5{
 chip=true;
 my_block my_block;
};
addrmap chip_4{
 chip=true;
 chip chip_5;
};
addrmap chip_3{
 chip=true;
 chip_4 chip_4;
};
addrmap chip_2{
 chip=true;
 chip_3 chip_3;
};
addrmap chip_1{
 chip=true;
 chip_2 chip_2;
};
addrmap chip_top{
 chip=true;
 chip_1 chip_1;
};

 

Output for the Top Chip (chip_top)

When generating the output for the top chip, the user will get one Python output file and one directory containing Python output files for the lower hierarchy chips. Here’s an example of the generated code:

sys.path.append(".")
sys.path.append("./chip_top/chip_1")
sys.path.append("./chip_top/chip_1/chip_2")
sys.path.append("./chip_top/chip_1/chip_2/chip_3")
sys.path.append("./chip_top/chip_1/chip_2/chip_3/chip_4")
sys.path.append("./chip_top/chip_1/chip_2/chip_3/chip_4/chip_5")
count = 0
#for i in range(len(___)):
# count +=1
# sys.path.append("./chip_top/chip_{}
## importing user defined script
from chip_1 import *
chip_top = {

        'chip_top' :

—-----------------------------------
'chip_1' : chip_1,
};

 

Output for the Lower Hierarchy Chip

import os
import sys
sys.path.append(".")
sys.path.append("./chip_2")
sys.path.append("./chip_2/chip_3")
sys.path.append("./chip_2/chip_3/chip_4")
sys.path.append("./chip_2/chip_3/chip_4/chip_5")
## importing user defined script
from chip_2 import *
chip_1 = {

       'chip_1' :
       {
       'type' : 'chip',

—-------------------------------------------------------------------
   'chip_2' : chip_2,
};

import os
import sys
sys.path.append(".")
sys.path.append("./chip_3")
sys.path.append("./chip_3/chip_4")
sys.path.append("./chip_3/chip_4/chip_5")
## importing user defined script
from chip_3 import *
chip_2 = {


       'chip_2' :
       {
        'type' : 'chip',

—---------------------------------------------
   'chip_3' : chip_3,
};

import os
import sys
sys.path.append(".")
sys.path.append("./chip_4")
sys.path.append("./chip_4/chip_5")
## importing user defined script
from chip_4 import *
chip_3 = {

       'chip_3' :
       {
       'type' : 'chip',
   'chip_4' : chip_4,
};


import os
import sys
sys.path.append(".")
sys.path.append("./chip_5")
## importing user defined script
from chip_5 import *
chip_4 = {

       'chip_4' :
       {
       'type' : 'chip',
—-----------------------------------------------------------
   'chip_5' : chip_5,
};
import os
import sys
sys.path.append(".")
## importing user defined script
from my_block import *
chip_5 = {

       'chip_5' :
       {
       'type' : 'chip',
—----------------------------------------------------
   'my_block' : my_block,
};

Output for the Lower Hierarchy Block

my_block = {
       'my_block' :
       {
       'type' : 'block',
—----------------------------------------------------------
       {
       'type' : 'field',
           'name' : "F" ,
           'offset' : "31:0" ,
               'sw' : "rw",
                'hw' : "rw",
              'default' : "0x0"
       }
       },
       },
}; 

 

Conclusion

The Python multi-out support provides users with the ability to generate multiple Python output files for hierarchical chip structures. By using the “-if” switch during output generation, users can obtain specific Python output files for individual chips within the hierarchy. This feature enhances flexibility and efficiency in managing Python outputs, catering to diverse user needs.

Steps to Start Using

  1. Ensure that Python output generation capability is enabled in your IDS installation

  2. Use generating outputs, use these switches :  -out “python” -if 

PSS Support in IDS-Validate

Introduction

The Portable test and Stimulus Standard defines a specification for creating a single representation of stimulus and test scenarios, usable by a variety of users across different levels of integration. With this standard, users can specify a set of behaviours, from which multiple implementations may be derived.

  • PSS has constructs for
    • Modelling Data flow (Buffers, Streams, States)
    • Modeling Behavior (Actions, Activities, Components, Resource, Pooling)
    • Constraints, Randomization, Coverage
  • PSS is useful for SoC high-level test scenario creation

A concept of defining Registers and Sequences has been introduced in PSS2.0. Currently, three accesses are supported i.e., Read-Only, Read-Write, Write-Only.

IDS-Validate helps in  generating the PSS register model through various inputs supported by IDS such as SystemRDL, IP-XACT, IDS-NG, Word, Custom CSV etc.

Steps for Generating PSS Register model in the IDS-Validate:

Use Case

IDS-Validate is a tool that helps verify the functionality of complex systems by creating and managing models of the hardware registers. These models regulate how the system interacts with the programmable registers in the hardware.

When testing a system, it’s important to ensure that the system reads from and writes to the hardware registers correctly. IDS-Validate uses the supported input from the system to generate a model called the PSS Register model. This model defines the rules for accessing the registers.

The PSS 2.0 LRM core library provides a set of tools and functions (APIs) to control how the system accesses the registers and allows for modelling of the registers. These tools help in creating and maintaining the PSS Register model.

One of the features of IDS-Validate is the generation of C code equivalents of PSS sequences. These C-based testbench environments can be used for various purposes like testing CPUs or checking embedded systems. This allows for more flexibility in testing and verification.

IDS-Validate also focuses on reusability and scalability. It enables efficient verification of complex systems by providing a model-based approach. The PSS Register model and the automated test generation make it easier to verify and test large systems. This scalability allows the tool to handle sophisticated verification tasks effectively.

Example

Input SystemRDL Register Specification:

addrmap block1{
 reg reg1 {
     field {
       sw=rw;
       hw=rw;
         }f1=0;
   }; 
  reg reg2 {
     field {
       sw=rw;
       hw=rw;
         }f1=0;
     field {
       sw=rw;
       hw=rw;
         }f2[31:15]=0;
   }; 
  reg reg3 {
      field {
       sw=rw;
       hw=rw;

         }f1[31:0]=32'h10;
   }; 
   reg1 reg1;
   reg2 reg2;
   reg3 reg3;
};

 

PSS Sequence specification for the Regsiter model defined above in SystemRDL format: 

/* Package containing block1 HW registers as PSS register components*/
package  block1_regs_pkg {
   import addr_reg_pkg::*;
   struct reg1_reg_s  : packed_s<> {
       bit[1] f1;
       bit[31] rsvd_0;
   };
   struct reg2_reg_s  : packed_s<> {
       bit[1] f1;
       bit[14] rsvd_0;
       bit[17] f2;
   };
   struct reg3_reg_s  : packed_s<> {
       bit[32] f1;
   };

   component block1_regs_c: reg_group_c {

   reg_c<reg1_reg_s,READWRITE,32> reg1
       exec init {
           reg1.offset = 0x0;
           reg1.reset_val = 0x00000000;
           reg1.reset_mask = 0x1;
       };
   reg_c<reg2_reg_s,READWRITE,32> reg2
       exec init {
           reg2.offset = 0x4;
           reg2.reset_val = 0x00000000;
           reg2.reset_mask = ;
       };
   reg_c<reg3_reg_s,READWRITE,32> reg3
       exec init {
           reg3.offset = 0x8;
           reg3.reset_val = 0x00000010;
           reg3.reset_mask = 0xFFFFFFFF;
       };
   }
};

 

Conclusion

IDS-Validate is a powerful tool that simplifies the verification of complex systems by creating and managing models of hardware registers. By generating the PSS Register model based on the system’s input, it ensures correct interaction with the programmable registers. With the support of the PSS 2.0 LRM core library, the tool provides tools and functions for modelling and controlling register access. By automating test generation and facilitating reuse, IDS-Validate saves time and effort in system verification. The ability to generate C code equivalents of PSS sequences adds flexibility to the testing process. Overall, IDS-Validate enhances reusability and scalability, making it an effective solution for the verification of sophisticated system-level designs.

Steps to Start Using

If you already have IDS, get licence for IDS-Validate. The command to use is Agnisys PSS Compiler or apc.sh and you can generate several outputs from it.

% apc.sh in_file.pss 

Scroll to Top