top of page

Spicing up your UVM driver: noise injection made easy with callback iterators


I generated two files with noise data using my spice simulator. One file contains thermal noise data, while the other contains flicker noise data. I obtained this information through an analog circuit simulator. My current objective is to incorporate this noise data into an ADC sequence item.


I'd like the ability to configure the specific type of noise to apply to the item, and establish a flexible infrastructure for adding new noise types seamlessly in the future. Additionally, I aim to effortlessly customize the noise characteristics, such as shaping it with digital filters or adjusting noise scaling, for instance.


To fulfill this requirement, I opted for the uvm callback mechanism because it feels like a natural and straightforward approach for me. Alternatively, I could achieve this by extending the sequence item and integrating this functionality into the extended class, but personally I think it is less preferred solution.


 

Solution inheritance tree



 

The spice file structure consists of two columns, with the required data located in the second column. Here's an snippet from the 'thermal.dat' file as an example:


0 6.055662804937967e-05

1e-06 0.0002011872344549379

2e-06 0.0002213623516087995

3e-06 -5.907203846894023e-05

4e-06 -2.059237828303266e-06

5e-06 1.612311888096226e-05


The following code resides within the user-defined class 'inject_adc_noise.' This code extracts noise data from the second column and pushes it into a noise queue. Later, we will retrieve noise items from this queue and incorporate them into the sequence item data.


real noise[$];


virtual function void read_noise_file (string file_name); fd = $fopen(file_name, "r"); while ($fscanf(fd,"%s %0f",str,noise_value)==2) begin noise.push_back(noise_value); end $fclose(fd);

endfunction


Additionally, this class includes a method for converting the real noise data into an integer format. This conversion is necessary to include this value in a 12-bit ADC value.


virtual function int convert_noise_to_bits (real noise_val); return $rtoi(noise_val * (2 ** (`NBITS - 1) - 1)); endfunction


The primary method in this class is 'inject_noise.' It takes a pointer to the sequence item we intend to modify. Initially, we remove a noise value from the back of the noise queue. Next, we perform an integer conversion on this value and ultimately append the result to the item's data field.


virtual function void inject_noise (adc_item item); int noise_dval; real nval; nval = noise.pop_back(); noise_dval = convert_noise_to_bits(nval); item.data = item.data + adc_resolution'(noise_dval); endfunction




The extended classes, such as 'inject_thermal_noise,' are responsible for reading noise data and shape its characteristics. Each extended class has the flexibility to implement distinct noise shaping techniques or incorporate other methods relevant to the specific noise type.


class inject_thermal_noise extends inject_adc_noise; `uvm_object_utils(inject_thermal_noise) function new (string name=""); super.new(name); read_noise_file("../sv/thermal.dat"); shape_noise(); endfunction function void inject_noise (adc_item item); super.inject_noise(item); endfunction function void shape_noise(); foreach (noise[idx]) noise[idx] = noise[idx] * 60.0; endfunction endclass



 

The following step involves registering the callback class within the driver and activating the iterator to incorporate noise from all registered callback classes.


class adc_drv extends uvm_driver #(adc_item); ... `uvm_register_cb(adc_drv,inject_adc_noise) ... task run_phase(uvm_phase phase); ... forever begin seq_item_port.get_next_item(req); begin uvm_callback_iter #(adc_drv, inject_adc_noise) iter = new(this); for (inject_adc_noise cb = iter.first(); cb !=null ; cb = iter.next()) begin cb.inject_noise(req) ; end end ... end endtask ... endclass


You can get the same functionally using the macro `uvm_do_callbacks. Personally I prefer the above code because it allows me to add code in the for loop if needed.



 

The last stage entails creating the callback classes within the test class and inserting them into the callbacks queue. To specify which noise type to include in the test, I utilized the command line input $test$plusargs.


source run -gui +THERMAL +FLICKER


class cb_iter_test extends uvm_test;

...

function void end_of_elaboration_phase (uvm_phase phase); m_inject_thermal_noise=

inject_thermal_noise::type_id::create("m_inject_thermal_noise",this); m_inject_flicker_noise=

inject_flicker_noise::type_id::create("m_inject_flicker_noise",this);

if ($test$plusargs("THERMAL")) begin `uvm_info(get_name(),"Add thermal noise to adc output signal",UVM_MEDIUM) uvm_callbacks #(adc_drv, inject_adc_noise)::

add(m_adc_agnt.m_adc_drv, m_inject_thermal_noise); end

if ($test$plusargs("FLICKER")) begin `uvm_info(get_name(),"Add flicker noise to adc output signal",UVM_MEDIUM) uvm_callbacks #(adc_drv, inject_adc_noise)::

add(m_adc_agnt.m_adc_drv, m_inject_flicker_noise); end

endfunction

...

endclass



 

The following plot shows a 2MHz sine wave with 2 noise types added to the signal






 

In conclusion, when it comes to tasks such as incorporating noise data from files into sequence item data, utilizing callback classes and a callback iterator proves to be an effective approach. It grants the flexibility to introduce various noise types and implement specific behaviors within extended classes tailored to each noise type. The use of $test$plusargs to activate specific callbacks enables thorough testing of how the design responds to different noise types.


The code can be found here


Comments


bottom of page