The uvm_object class is the base class for all UVM classes. From it, all the rest of classes are extended. It provides basic functionalities such as print, compare, copy and similar methods.

This class can be used when defining reusable parts of a sequence items. For example, in a packet like uvm_sequence_item, we could define a uvm_object extended object for defining the header. This would be:

class packet_header extends uvm_object;

   rand bit [2:0] len;
   rand bit [2:0] addr;

   `uvm_object_utils_begin(packet_header)
      `uvm_field_int(len, UVM_DEFAULT)
      `uvm_field_int(addr, UVM_DEFAULT)
   `uvm_object_utils_end

   function new (string name="packet_header");
      super.new(name);
   endfunction : new

endclass : packet_header

This packet_header could be included in a packet class for conforming the uvm_sequence_item (the transaction) which will compose the sequences:

class simple_packet extends uvm_sequence_item;

   rand packet_header header;
   rand bit [4:0] payload;

   `uvm_object_utils_begin(simple_packet)
      `uvm_field_object(header, UVM_DEFAULT)
      `uvm_field_int(payload, UVM_DEFAULT)
   `uvm_object_utils_end

   function new (string name = "simple_packet");
      super.new(name);
      header = packet_header::type_id::create("header");
   endfunction : new

endclass : packet

 

\[ s_k = (k\cdot A) \bmod B\]

\(s_k\) is the pseudo-random number and \(A\) and \(B\) are prime numbers. \(k\) is in the range \([0,B-1]\). If \(k\) is greater than \(B-1\), the results will be repeat as \(B\) is the period of the sequence.
For example, \(A = 7\) and \(B = 17\). This sequence written in MATLAB could be:

A = 7;
B = 17;
n = [];

t = 0:B;
for i=0:B
    n = [n mod(i*A,B)];
end

stem(t,n);

 

Pseudo-random values
Periodicity of the sequence when k > B

Let’s z be a 2D point in the space as \(z = x + jy\), if we want to rotate this point a given angle \(\theta\), we get the following expressions:
\[e^{j\theta} \cdot z = \left(\cos{\theta} + j \sin{\theta}\right)\left(x+jy\right) \\ = x\cos{\theta}-y\sin{\theta} + j \left(y \cos{\theta} + x \sin{\theta} \right) \\ = x’ + j y’ \]

Then, for a generic point, the rotation can be expressed as an equation system, where \(x’\) and \(y’\) are the new coordinates, \(\theta\) is the rotation angle and \(x\) and \(y\) are the original coordinates:
\[\begin{bmatrix}
x’\\
y’
\end{bmatrix}=
\begin{bmatrix}
\cos{\theta} & -\sin{\theta}\\
\sin{\theta} & \cos{\theta}
\end{bmatrix}\begin{bmatrix}
x\\
y
\end{bmatrix} \]

This rotation can be coded in MATLAB as:


%% Function to rotate vector
function v = rotate(P, theta)
    rot = [cos(theta) -sin(theta);
           sin(theta) cos(theta)];
    v = rot*P;
end

A possible implementation of the cordic algorithm could be:


%% Clear all previous values
clear all;

%% Define vectors
A = [2;-3];
O = [0;0];

%% Define accuracy
error_limit = 0.01;

%% Initialize variables
start_angle = pi/2; % 90º
current_angle = start_angle;
acc_angle = 0;

A_rotated = A;
steps = 0;

% Second quadrant (90º - 180º)
if(A_rotated(1) < 0 && A_rotated(2) > 0)
    A_rotated = rotate(A_rotated, -pi/2);
    acc_angle = pi/2;
% Third quadrant (180º - 270º)
elseif(A_rotated(1) < 0 && A_rotated(2) < 0)
    A_rotated = rotate(A_rotated, -pi);
    acc_angle = pi;
% Forth quadrant (270º - 360º)
elseif(A_rotated(1) > 0 && A_rotated(2) < 0)
    A_rotated = rotate(A_rotated, -3*pi/2);
    acc_angle = 3*pi/2;    
end


%% Compute angle
% Keep rotating while error is too high
while(abs(A_rotated(2)) > error_limit)
    % Represent current vector
    quiver(0, 0,A_rotated(1), A_rotated(2));
    % Keep previous vectors
    hold on;
    % Decrease angle rotation
    current_angle = current_angle/2;
    % (For debugging purposes)
    current_angle_deg = current_angle*180/pi;
    % Save current error
    error = A_rotated(2);
    % If y coordinate is still positive
    if(error > 0)
        % Rotate again conterclockwise
        A_rotated = rotate(A_rotated, -1*current_angle);
        % Accumulate rotated angle
        acc_angle = acc_angle + current_angle;
    % If y coordinate is negative
    else
        % Rotate vector clockwise
        A_rotated = rotate(A_rotated, current_angle);
        % Substract current angle to the accumulator because we have
        % overcome the actual angle value
        acc_angle = acc_angle - current_angle;
    end
    
    % (For debugging purposes)
    acc_angle_deg = acc_angle*180/pi;
    % Increase step counter
    steps = steps + 1;
end

% Print angle, hypotenuse length and number of steps needed to compute
fprintf('Angle = %f\nHypotenuse = %f\nNumber of steps: %d\n', acc_angle*180/pi, A_rotated(1), steps);


%% Function to rotate vector
function v = rotate(P, theta)
    rot = [cos(theta) -sin(theta);
           sin(theta) cos(theta)];
    v = rot*P;
end

 

I have coded an interactive applet to illustrate the algorithm. It has been done using the p5.js library. The error limit has been set to \(0.5\).

    1. Open http://dl.altera.com/?edition=lite
    2. Login and click on desired Quartus version download
    3. Click in the individual file links to start download (Akamai DLM3 Download Manager might not work).
    4. Extract Quartus installer.
    5. Run setup.sh: ./setup.sh
    6. Select desired devices.
    7. For launching modelsim, install libxft2 32 bit version library: sudo apt install libxft2:i386. Then execute ./vsim in the path intelFPGA_lite/17.1/modelsim_ase/linuxaloem/
#define round(x) x >= 0.0 ? (int)(x + 0.5) : ((x - (double)(int)x) >= -0.5 ? (int)x : (int)(x - 0.5))

Example:

#include <stdio.h>

#define round(x) x >= 0.0 ? (int)(x + 0.5) : ((x - (double)(int)x) >= -0.5 ? (int)x : (int)(x - 0.5))

void main(void){
   float f1 = 3.14, f2 = 6.5, f3 = 7.99;
   int r1, r2, r3;
   r1 = round(f1);
   r2 = round(f2);
   r3 = round(f3);


   printf("r1 = %d\nr2 = %d\nr3 = %d\n", r1, r2, r3);
}

The console output is:

r1 = 3
r2 = 7
r3 = 8

UVM introduces the concept of phases to ensure that all objects are properly configured and connected before starting the runtime simulation. Phases contribute to a better synchronised simulation and enable to the verification engineer to get better modularity of the testbench.

UVM phases consists of:

  1. build
  2. connect
  3. end_of_elaboration
  4. start_of_simulation
  5. run
    1. reset
    2. configure
    3. main
    4. shutdown
  6. extract
  7. check
  8. report
  9. final

The run phase has been simplified to get a better picture of how phases worked. Nevertheless, all subphases in the run phase have a pre_ and post_ phase to add flexibility. Therefore, the run phase is actually composed by the following phases:

  1. run
    1. pre_reset
    2. reset
    3. post_reset
    4. pre_configure
    5. configure
    6. post_configure
    7. pre_main
    8. main
    9. post_main
    10. pre_shutdown
    11. shutdown
    12. post_shutdown

Although all phases play an important role, the most relevant phases are:

  • build_phase: objects are created
  • connect_phase: interconnection between objects are hooked
  • run_phase: the test starts. The run_phase is the only phase which is a task instead of a function, and therefore is the only one that can consume time in the simulation.

UVM phases are executed from a hierarchical point of view from top to down fashion. This means that the first object that executes a phase is the top object, usually

testbench  test  environment agent {monitor, driver, sequencer, etc}

Nevertheless, in the connect phase, this happens the other way round in a down to top fashion.

{monitor, driver, sequencer} agent environment test testbench

To use UVM in your Verilog test bench, you need to compile the UVM package top. To do so, you need to include it on your file by using:

`include "uvm_macros.svh"
import uvm_pkg::*;

The uvm_pkg is contained in the uvm_pkg.sv that must be passed to the compiler. Therefore, it is necessary to indicate the UVM path to the compiler. In Cadence Incisive Enterprise Simulator (IES) is as easy as to specify -uvm switch.

In Modelsim, from Modelsim console, run:

vsim -work work +incdir+/path/to/uvm-1.1d/src +define+UVM_CMDLINE_NO_DPI +define+UVM_REGEX_NO_DPI +define+UVM_NO_DPI

After compilation, click on Simulate > Start simulation and select the tb in the work library. Then, run the simulation for the desired time.

When an operation such as an addtion or a substraction is done using different size operands than final variable, it is necessary to extend sign to ensure the operation is done properly.

Example:

logic signed [21:0] acc;
logic signed [5:0] data_in;
logic [3:0] offset;

Wrong:

acc = data_in + offset;

Sign on data_in will not be respected. data_in will be filled with 0 before doing the operation and won’t be taken as negative (if applies).

Correct:

acc = {{16{data_in[5]}},data_in} + offset;

Extend sign to match number of acc_add bits before doing operation

El uso de un amplificador operacional con realimentación positiva es útil para obtener una respuesta histerética. Para entender cómo funciona, primero necesitamos explicar cómo funciona un amplificador operacional sin realimentación.

Un operacional sin realimentación actúa como un comparador. Definimos \(V_d = V_p – V_n\). Si esta tensión es positiva, la salida \(V_o\) será \(+V_{cc}\). Si la tensión es negativa, la salida \(V_o\) será \(-V_{cc}\).

\[ V_o = \left\{\begin{matrix}
+V_{sat} & ; V_d > 0 \\
-V_{sat} & ; V_d < 0
\end{matrix}\right. \]

El funcionamiento de un amplificador operacional con histéresis sigue el mismo razonamiento. Como hemos visto, el funcionamiento de este topología es no lineal, y por tanto los valores de salida se limitan a \(+V_{sat}\) y \(-V_{sat}\). En un comparador como el anterior, el cambio entre la tensión de saturación positiva y negativa se da cuando la tensión diferencia cambia de signo. Si fijamos una de las tensiones, podemos entender mejor qué ocurre. Por ejemplo, si hacemos \(V_n = 0\):

El cambio se de tensió de salida se da en 0V, ya que es cuando la tensión diferencia de entrada (\(V_d = V_p – V_n\)).

Si se aumenta la tensión \(V_p\) a 2 V, la tensión de cambio aumenta a 2V

Si se utiliza realimentación positiva, es posible hacer que la tensión de cambio sea diferente si la tensión de salida es la de saturación positiva o negativa en función de la salida actual. Con una histéresis no-inversora, si la tensión de salida es positiva, la salida cambiará cuando \(V_{in}\) es \(V_{TL}\) (threshold low). Si la tensión de salida es negativa, la salida cambiará cuando \(V_{in}\) es \(V_{TH}\) (threshold high).

Existen dos versiones básicas de op amp con histéresis: la inversora y la no inversora.

Op amp con histéresis no inversora

\[ V_{TH} = \frac{}{}V_{sat}^+\]