LVS check

LVS issue for circuits with customized devices

  • auCdl: Analog and Microwave CDL, is a netlister used for creating CDL netlist for analog circuits

  • auLVS: Analog and Microwave LVS, is used for analog circuit LVS

EMX ports

plain labels

  • pin layer
  • uncheck Cadence pins in Advanced options

rectangle pins

  • drawing layer rectangle pin and specify Access Direction as intended
  • check Cadence pins in Advanced options

The rectangle pins are always selected as driven port while there are only rectangle pin whether Cadence pins checked or not.

check ports used for simulation

use GDS view - EMX

EMX Synthesis Kits

Synthesis is a capability of the EMX Pcell library and uses scalable model data pre-generated by Continuum for a specific process and metal scheme combination.

Synthesis is supported by the Pcells that are suffixed _scalable, and these Pcells have the additional fields and buttons needed for synthesis.

port order (signals)

emxform.ils

type Port order
inductor P1 P2
shield inductor P1 P2 SHIELD
tapped inductor P1 P2 CT
tapped shield inductor P1 P2 CT SHIELD
mom/mim capacitor P1 P2
tcoil P1 P2 TAP
shield tcoil P1 P2 TAP SHIELD
tline P1 P2
differential tline P1 P2 P3 P4

EMX device info

name menu_selection (split with _ ) num_ports modelgen_type generic_model_type plot_fn
Single-ended inductor inductor_no tap_no shield_single-ended 2 inductor inductor EMX_plot_se_ind
Differential inductor inductor_no tap_no shield_differential 2 inductor inductor EMX_plot_diff_ind
Single-ended shield inductor inductor_no tap_with shield_single-ended 3 shield_inductor shield_inductor EMX_plot_se_ind
Differential shield inductor inductor_no tap_with shield_differential 3 shield_inductor shield_inductor EMX_plot_diff_ind
Tapped inductor (diff mode only) inductor_with tap_no shield_differential mode only 3 center_tapped_inductor tapped_inductor EMX_plot_ct_ind
Tapped inductor (common mode too) inductor_with tap_no shield_also fit common mode 3 center_tapped_inductor_common_mode tapped_inductor EMX_plot_ct_ind
Tapped shield inductor (diff only) inductor_with tap_with shield_differential mode only 4 center_tapped_well_inductor_common_mode tapped_shield_inductor EMX_plot_ct_ind
Single-ended cap (symm) capacitor_symmetric single-ended 2 complex_mom_capacitor mom_capacitor EMX_plot_se_cap
Differential cap (symm) capacitor_symmetric differential 2 complex_mom_capacitor mom_capacitor EMX_plot_diff_cap
Single-ended cap (asymm) capacitor_asymmetric single-ended 2 complex_asymmetric_mom_capacitor mom_capacitor EMX_plot_se_cap
Differential cap (asymm) capacitor_asymmetric differential 2 complex_asymmetric_mom_capacitor mom_capacitor EMX_plot_diff_cap
MiM capacitor capacitor_MiM 2 mim_capacitor mim_capacitor EMX_plot_se_cap
Tcoil (simple model) tcoil_simple model 3 tcoil tcoil EMX_plot_tcoil
Tcoil (complex model) tcoil_complex model 3 complex_tcoil complex_tcoil EMX_plot_tcoil
Shield tcoil tcoil_with shield 4 shield_complex_tcoil shield_tcoil EMX_plot_shield_tcoil
Transmission line transmission line_single 2 xline xline EMX_plot_xline
Diff transmission line transmission line_coupled (differential) 4 coupled_xline diff_xline EMX_plot_diff_xline

EMX plot function

EMX's formulation is defined in

1
/path/to/EMX/share/emx/virtuoso_ui/emxinterface/emxskill/emxform.ils

EMX import this file at Virtuoso startup, you have to relaunch Virtuoso if you change this file

Single-ended inductor

Both with and without shield apply

  • port-1 impedance when port-2 short

\[ Z_1 = \frac{1}{Y_{11}} \]

  • port-2 impedance when port-1 short

\[ Z_2 = \frac{1}{Y_{22}} \] Then \[\begin{align} L1 &= \frac{Im(Z_1)}{2\pi f} \\ Q1 &= \frac{Im(Z_1)}{Re(Z_1)} \\ L2 &= \frac{Im(Z_2)}{2\pi f} \\ Q2 &= \frac{Im(Z_2)}{Re(Z_2)} \end{align}\]

EMX only plot L1 and Q1

differential impedance

Y parameters to Z parameters

\[\begin{align} |Y| &= Y_{11}*Y_{22} - Y_{12}*Y_{22} \\ \begin{bmatrix} Z_{11} & Z_{12}\\ Z_{21} & Z_{22} \end{bmatrix} &= \begin{bmatrix} \frac{Y_{22}}{|Y|} & \frac{-Y_{12}}{|Y|}\\ \frac{-Y_{21}}{|Y|} & \frac{Y_{11}}{|Y|} \end{bmatrix} \end{align}\]

Then differential impedance is \[ Z_{diff} = Z_{11} - Z_{12} - Z_{21} + Z_{22} \]

image-20220330234833756

similarly, Z parameters to Y parameters \[ \begin{bmatrix} Y_{11} & Y_{12}\\ Y_{21} & Y_{22} \end{bmatrix} = \begin{bmatrix} \frac{Z_{22}}{|Z|} & \frac{-Z_{12}}{|Z|}\\ \frac{-Z_{21}}{|Z|} & \frac{Z_{11}}{|Z|} \end{bmatrix} \] where \[ |Z| = Z_{11}Z_{22} - Z_{12}Z_{21} \]

Differential inductor

Both with and without shield apply

\[\begin{align} L_{diff} &= \frac{Im(Z_{diff})}{2\pi f} \\ Q_{diff} &= \frac{Im(Z_{diff})}{Re(Z_{diff})} \end{align}\]

Center-tapped inductor

\[ Y = \begin{bmatrix} Y_{11} & Y_{12} & Y_{13}\\ Y_{21} & Y_{22} & Y_{23}\\ Y_{31} & Y_{32} & Y_{33} \end{bmatrix} \]

where port order is P1 P2 CT.

1
2
3
4
5
6
7
8
9
10
11
(define (EMX_plot_ct_ind bgui wid what)
(EMX_plot_aux bgui wid what 3
'("Differential inductance" "Differential Q")
'("Henry" "")
(lambda (ys)
(letseq ((z (EMX_differential (nth 0 ys) (nth 1 ys) (nth 3 ys) (nth 4 ys)))
(f (xval z))
(L (imag z)/(2*3.14159265358979*f))
(Q (imag z)/(real z)))
`((,L) (,Q))))
'(("L") ("Q"))))

Assume CT i.e. port 3 in S-parameter is grounded, (z (EMX_differential (nth 0 ys) (nth 1 ys) (nth 3 ys) (nth 4 ys))) obtain differential impedance with \(Y_{11}\), \(Y_{12}\), \(Y_{21}\) and \(Y_{22}\). \[ Y = \begin{bmatrix} Y_{11} & Y_{12}\\ Y_{21} & Y_{22} \end{bmatrix} \] Finally, differential inductance and Q are obtained, shown as below

\[\begin{align} L_{diff} &= \frac{Im(Z_{diff})}{2\pi f} \\ Q_{diff} &= \frac{Im(Z_{diff})}{Re(Z_{diff})} \end{align}\]

image-20220331013735370

Single-ended cap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(define (EMX_plot_se_cap bgui wid what)
(EMX_plot_aux bgui wid what 2
'("Capacitance" "Q" "Capacitance" "Q")
'("Farad" "" "Farad" "")
(lambda (ys)
(letseq ((z1 1.0/(nth 0 ys))
(y12 (nth 1 ys))
(z2 1.0/(nth 3 ys))
(f (xval z1))
(C1 (-1.0/(imag z1))/(2*3.14159265358979*f))
(C12 -(imag y12)/(2*3.14159265358979*f))
(C2 (-1.0/(imag z2))/(2*3.14159265358979*f))
(Q1 -(imag z1)/(real z1))
(Q12 (imag y12)/(real y12))
(Q2 -(imag z2)/(real z2)))
`((,C1) (,Q1) (,C12))))
'(("Cse") ("Qse") ("C12"))))

We define Port-1 impedance \(Z_1\), Port-2 impedance \(Z_2\)

\[\begin{align} Z_1 &= \frac {1}{Y_{11}}\\ Z_2 &= \frac {1}{Y_{22}} \end{align}\]

Then single-ended cap and Q \[\begin{align} C_1 &= -\frac{1/Im(Z_1)}{2\pi f} \\ Q_1 &= -\frac{Im(Z_1)}{Re(Z_1)} \\ C_2 &= -\frac{1/Im(Z_2)}{2\pi f} \\ Q_2 &= -\frac{Im(Z_2)}{Re(Z_2)} \\ C_{12} &= -\frac{Im(Y_{12})}{2\pi f}\\ Q_{12} &= \frac{Im(Y_{12})}{Re(Y_{12})} \end{align}\]

  • Series equivalent model is used in \(C_1\), \(Q_1\), \(C_2\) and \(Q_2\)
    • \(Z_1 = R + \frac{1}{sC_1}\) and \(Z_2 = R + \frac{1}{sC_2}\)
  • Parallel model is used in \(C_{12}\) and \(Q_{12}\)
    • \(Y_{12} = \frac{1}{R} + sC_{12}\)

EMX plot \(C_{se}\), \(Q_{se}\) and \(C_{12}\), i.e. \(C_1\), \(Q_1\) and \(C_{12}\)

image-20220331020334023

Differential cap

1
2
3
4
5
6
7
8
9
10
11
(define (EMX_plot_diff_cap bgui wid what)
(EMX_plot_aux bgui wid what 2
'("Differential capacitance" "Differential Q")
'("Farad" "")
(lambda (ys)
(letseq ((z (apply EMX_differential ys))
(f (xval z))
(C (-1.0/(imag z))/(2*3.14159265358979*f))
(Q -(imag z)/(real z)))
`((,C) (,Q))))
'(("C") ("Q"))))

First obtain differential impedance, \(Z_{diff}\) then apply series equivalent model \[\begin{align} C_{diff} &= -\frac{1/Im(Z_{diff})}{2\pi f} \\ Q_{diff} &= -\frac{Im(Z_{diff})}{Re(Z_{diff})} \end{align}\]

image-20220331022224865

Tline

Open circuit impedance \(Z_o\), short circuit impedance \(Z_s\) and characteristic impedance \(Z_0\)

\[\begin{align} Z_o &= Z_{11}\\ Z_s &= \frac{1}{Y_{11}}\\ Z_0 &= \sqrt{Z_o*Z_s} \end{align}\]

propagation constant is given as \[\begin{align} \gamma &= \frac{1}{2}\log\left( \frac{Z_0+Z_s}{Z_0-Z_s} \right) \\ &= \alpha + j\beta \end{align}\] where \(\alpha\) is attenuation constant and \(\beta\) is phase constant

The relationship between these parameter and geometry of the transmission line \[\begin{align} Z_0 &= \sqrt{\frac{R+j\omega L}{G+j\omega C}} \\ \gamma &= \sqrt{(G+j\omega C)(R+j\omega L)} \end{align}\] EMX plot the real and imaginary part of \(Z_0\), \(\alpha\) and \(\beta\) of \(\gamma\)

Note EMX plot the absolute value of \(\alpha\) and \(\beta\)

image-20220630215343377image-20220630215418372

image-20220630215630849

Transformer

image-20231015002344332

image-20231015002403566

[IC Prophet GDSII 文件使用和仿真测试说明]

EMX autoplot

using AC simulation, and inductor's parallel model or series model

That is to say: both sp (network parameter) and ac (impedance) can be used to plot inductance, Q value.

usually EMX choose ac method

image-20220501173856442

image-20220501173930035

left 2 figures are used for AC simulation, \(Y_{nn}\) can be obtained conveniently

Foundary model

image-20221217141519947

for single-end capicator \[\begin{align} Q_1 &= -\frac{Im(Z_1)}{Re(Z_1)} \\ &= -\frac{Im(1/Y_{11})}{Re(1/Y_{11})} \\ &= -\frac{Im(Y_{11}^*)/|Y_{11}|^2}{Re(Y_{11}^*)/|Y_{11}|^2} \\ &= \frac{Im(Y_{11})}{Re(Y_{11})} \end{align}\]

So, the EMX model and foundary model is consistent.

Tips

Process file encryption mostly for advanced nodes, like TSMC 16nm Finfet, whose process file is encrypted.

  • Use --key=EMXkey in the EMX Advanced options

GDSviewer has two options

  • EMX: shows the final gds sent to EMX for simulation after it has been processed by EMX
  • Raw: shows the raw gds

If there are port name with the # sign, it means EMX sees a port but it is not in the signal list.

EMX Accuracy

  • Edge mesh: controls layout discretization in the X-Y plane

    • For MoM capacitors, use the edge mesh to be the same as the width of the finger (for example, 0.1um).
  • Thickness: controls layout discretization in the Z dimension

  • 3D metals: skips all 2D assumptions about conductors and their currents and charges

    • If you set 3D metals to * then all metals are treated as 3D
      • For Inductor type structures, only thick metal needs 3D.
      • For MoM, all layers are needed.

Ports entered in Grounds will cause these nets to be grounded; these ports will not show up in the S-parameter result.

Setup Temperature

  • EMX: --temperature=100

ParaView

  • If check ParaView related options when ParaView is not setup properly, EMX simulation stop at Creating mesh... without waring or errors (version 6.2).

pad & bump

EMX process file contain M0 up to RDL-AP

PM, CB2_FC, UBM is in the chip package

image-20250613235639258

PEX extract up to RDL-AP as expected

DC model

quick find routing resistance

image-20250705231456546

image-20250705231907284

GPDK045 metal resistor model is not consistent with its process file on sheet resistance

image-20250705233530802

reference

Tips on Specifying Ports in EMX [link]

Using 'Cadence pins' as ports with access direction in EMX simulations [[link](Article (20496398) Title: Using 'Cadence pins' as ports with access direction in EMX simulations URL: https://support.cadence.com/apex/ArticleAttachmentPortal?id=a1O3w00000AH2OfEAL)]

EMX miscellaneous features [https://picture.iczhiku.com/resource/eetop/WyIFKleSLTRIuvCb.pdf]

image-20250817101208717


image-20250606203026237

discrete time jitter impulse response (normalized to the input jitter stimulus similar to the procedure used to represent a conventional system impulse response)

image-20250606203531304

When impulsive jitter is injected into clock distribution circuits (i.e., a small incremental time delay or advance applied to an individual clock edge), it results in jitter in multiple subsequent edges in the output clock

image-20250606215100752

image-20250606215204625

transient noise and rms_jitter function

image-20220608233610066

image-20220313230930333

RJ(rms): single Edge or Both Edge?

RJ(seed): what is it?

phase noise method

Directly compare the input phase noise and output phase noise, the input waveform maybe is the PLL output or other clock distribution end point

Jitter Impulse Response & Jitter Transfer Function

assuming linear, time-invariant phase response

image-20250522223530464

n =5 buffers, fclk = 10GHz

[Alphawave’s CTO, Tony Chan Carusone, High Speed Communications Part 8 – On Die CMOS Clock Distribution]

image-20250818230528344

Theoretically, the DC gain of JTF of low pass filter shall be 1. Unfortunately, the gain less than 1 due to numerical error or nonlinearity

image-20220313231027512

Example

Low Pass Filter

image-20220322124344158

1
2
3
4
5
6
7
8
9
10
11
N = 32;
x = zeros(N,1);
x(1) = 6;
x(2) = -2;
x(3) = 0.5;
x = x/5;
figure(1)
stem(x)
Y = fft(x, N);
figure(2)
plot(abs(Y(1:N/2)));

image-20220322124902394

image-20220322124932584

discrete time jitter impulse response

both input and output are discrete time signal, i.e. no sampling in the input, that's why ratio \(1/T_s\) is not in the jtf

High Pass Filter

image-20220327010223664

1
2
3
4
5
6
7
8
9
10
11
12
N = 128;
j_hp = zeros(N, 1);
j_hp(1)= 1;
j_hp(2) = 0.5;
j_hp(3) = -0.3;
j_hp(4) = 0.3;
j_hp(5) = -0.1;
jtf_hp = abs(fft(j_hp));
semilogx(jtf_hp(1:N/2+1));
xlabel('Freq');
ylabel('Jitter Amplification Factor');
grid on;

image-20220327010421475

inverter chain

image-20220608232251056

image-20220608232658054

image-20220608232438188

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ji = 1e-12; % 1ps
data = importdata('/path/to/jir.csv');
jo = data.data(:, 2);
Ts = 31.25e-12;
Fs = 1/Ts;
jir = jo/ji;
N = 2^(nextpow2(length(jir)-1));
Y = fft(jir, N);
jtf = abs(Y(1:N/2+1));
freqs= Fs/N*(0:N/2);
plot(feqs/1e9, jtf, 'linewidth', 2);
grid on;
xlabel('Freq (GHz)');
ylabel('JTF');
title('JTF of inverter chain');

image-20220608233303576

Colored Jitter Amplification

image-20250522225506217

[Alphawave’s CTO, Tony Chan Carusone, High Speed Communications Part 8 – On Die CMOS Clock Distribution]

image-20250522234522208

Four major noise sources are included in the modeling: Input noise, DAC quantization noise (DAC QN), DCO random noise (DCO RN), and delay line random noise (DL RN).

H. Kang et al., "A 42.7Gb/s Optical Receiver with Digital CDR in 28nm CMOS," 2023 IEEE Radio Frequency Integrated Circuits Symposium (RFIC), San Diego, CA, USA, 2023 [https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=10630516]

Dirac impulse at edge position

image-20250606205753885

Full-rate JTF

Singe edge is using

Half-rate

image-20250606220410050

image-20250607113251021

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ji = 10;
jir = readmatrix("~/cargo/jir.csv");
jir = jir / ji;
jir_2e = jir(:, 2); % both edge
jir_1e = jir(1:2:end, 2); % single edge

[mag, w] = freqz(jir_2e, 1, [], 1);
plot(w, abs(mag), LineWidth=2);
hold on
[mag, w] = freqz(jir_1e, 1, [], 0.5);
plot(w, abs(mag), LineWidth=2);
grid on;
legend("2Edge", "1Edge")
title("JTF")

image-20250607113553089

image-20250607114048892

fck = 1GHz

Note: Phase Noise dBc is SSB, that's why we add 10log10(2)

image-20250607114018289

The calculated JTF from JIR is too small compared with Pnoise simualtion

245.1/272.4 = 89.98%


image-20250607123008345

image-20250607123126301

245.1/272.4 = 83.74%

image-20250607123235447

Reference

Sam Palermo, ECEN 720, Lecture 13 - Forwarded Clock Deskew Circuits

B. Casper and F. O'Mahony, "Clocking Analysis, Implementation and Measurement Techniques for High-Speed Data Links-A Tutorial," in IEEE Transactions on Circuits and Systems I. [https://people.engr.tamu.edu/spalermo/ecen689/clocking_analysis_hs_links_casper_tcas1_2009.pdf]

Rhee, W. (2020). Phase-locked frequency generation and clocking : architectures and circuits for modern wireless and wireline systems. The Institution of Engineering and TechnologyMathuranathan Viswanathan, Digital Modulations using Matlab : Build Simulation Models from Scratch

Tony Chan Carusone, University of Toronto, Canada, 2022 CICC Educational Sessions "Architectural Considerations in 100+ Gbps Wireline Transceivers"

X. Mo, J. Wu, N. Wary and T. Chan Carusone, "Design Methodologies for Low-Jitter CMOS Clock Distribution," in IEEE Open Journal of the Solid-State Circuits Society, 2021 [https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9559395]

Modeling Oscillators with Arbitrary Phase Noise Profiles [https://community.cadence.com/cadence_blogs_8/b/rf/posts/modeling-oscillators-with-arbitrary-phase-noise-profiles]

Y. Zhao and B. Razavi, "Phase Noise Integration Limits for Jitter Calculation," 2022 IEEE International Symposium on Circuits and Systems (ISCAS), Austin, TX, USA, 2022 [https://www.seas.ucla.edu/brweb/papers/Conferences/YZ_ISCAS_22.pdf]

Thomas Toifl. TWEPP 2012. Low-power High-Speed CMOS I/Os: Design Challenges and Solutions [https://indico.cern.ch/event/170595/contributions/266344/attachments/212179/297391/twepp_sept2012_final_v2.pdf]

Ganesh Balamurugan and Naresh Shanbhag, "Modeling and mitigation of jitter in multiGbps source-synchronous I/O links," Proceedings 21st International Conference on Computer Design, San Jose, CA, USA, 2003, pp. 254-260, doi: 10.1109/ICCD.2003 [https://shanbhag.ece.illinois.edu/publications/ganesh-ICCD2203.pdf]

Balamurugan, G. & Casper, Bryan & Jaussi, James & Mansuri, Mozhgan & O'Mahony, Frank & Kennedy, Joseph. (2009). Modeling and Analysis of High-Speed I/O Links. Advanced Packaging, IEEE Transactions on. [https://sci-hub.se/10.1109/TADVP.2008.2011366]

Jihwan Kim, ISSCC2019 F5: Design Techniques for a 112Gbs PAM-4 Transmitter

Parallel Compensation is also known as Lead Compensation, Pole-Zero Compensation

image-20220307234938855

Note: The dominant pole is at output of the first stage, i.e. \(\frac{1}{R_{EQ}C_{EQ}}\).

image-20221006001114562

Pole and Zero in transfer function

Design with operational amplifiers and analog integrated circuits / Sergio Franco, San Francisco State University. – Fourth edition

image-20221005220854411

\[ Y = \frac{1}{R_1} + sC_1+\frac{1}{R_c+1/SC_c} \]

\[\begin{align} Z &= \frac{1}{\frac{1}{R_1} + sC_1+\frac{1}{R_c+1/SC_c}} \\ &= \frac{R_1(1+sR_cC_c)}{s^2R_1C_1R_cC_c+s(R_1C_c+R_1C_1+R_cC_c)+1} \end{align}\] If \(p_{1c} \ll p_{3c}\), two real roots can be found \[\begin{align} p_{1c} &= \frac{1}{R_1C_c+R_1C_1+R_cC_c} \\ p_{3c} &= \frac{R_1C_c+R_1C_1+R_cC_c}{R_1C_1R_cC_c} \end{align}\]

The additional zero is \[ z_c = \frac{1}{R_cC_c} \] Given \(R_c \ll R\) and \(C_c \gg C\) \[\begin{align} p_{1c} &\simeq \frac{1}{R_1(C_c+C_1)} \simeq \frac{1}{R_1C_c}\\ p_{3c} &= \frac{1}{R_cC_1}+\frac{1}{R_cC_c}+\frac{1}{R_1C_1} \simeq \frac{1}{R_cC_1} \end{align}\]

The output pole is unchanged, which is \[ p_2 = \frac{1}{R_LC_L} \] We usually cancel \(p_2\) with \(z_c\), i.e. \[ R_cC_c=R_LC_L \]

Phase margin

unity-gain frequency \(\omega_t\) \[ \omega_t = A_\text{DC}\cdot P_{1c} =\frac{g_{m1}g_{m2}R_L}{C_c} \]

  1. PM=45\(^o\) \[ p_{3c} = \omega_t \] Then, \(C_c\) and \(R_c\) can be obtained

    \[\begin{align} R_c &= \sqrt{\frac{R_1}{C_1\cdot A_{DC}\cdot p_2}}=\sqrt{\frac{R_1\cdot R_LC_L}{C_1\cdot A_{DC}}} \\ C_c &= \sqrt{\frac{A_{DC}\cdot C_1}{R_1\cdot p_2}}=\sqrt{\frac{A_{DC}\cdot C_1 \cdot R_LC_L}{R_1}} \end{align}\]

  2. PM=60\(^o\) \[ p_{3c} = 2\cdot\omega_t \] Then, \(C_c\) and \(R_c\) can be obtained \[\begin{align} R_c &= \sqrt{\frac{R_1}{C_1\cdot 2A_{DC}\cdot p_2}} = \sqrt{\frac{R_1\cdot R_LC_L}{C_1\cdot 2A_{DC}}} \\ &= \sqrt{\frac{C_L}{2g_{m1}g_{m2}C_1}}\\ C_c &= \sqrt{\frac{2A_{DC}\cdot C_1}{R_1\cdot p_2}} = \sqrt{\frac{2A_{DC}\cdot C_1 \cdot R_LC_L}{R_1}} \\ &= R_L\sqrt{2g_{m1}g_{m2}C_1C_L} \end{align}\]

    for the unity-gain frequency \(\omega_t\) we find \[ \omega_t = \sqrt{\frac{1}{2}\cdot \frac{g_{m1}g_{m2}}{C_1C_L}} \] The parallel compensation shows a remarkably good result. The new 0 dB frequency lies only a factor \(\sqrt{2}\) lower than the theoretical maximum

To increase \(\phi_m\), we need to raise \(C_c\) a bit while lowering \(R_c\) in proportion in order to maintain pole-zero cancellation. This causes \(p_{1c}\) and \(p_{3c}\) to split a bit further apart.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
clc;
clear;

fd = 84*1e3; % dominant freq, unit: Hz
fnd = 3.25*1e6; % unit: Hz
C = 478*1e-15;
R = 1/fd/(2*pi)/C;
Adc = 10^(80/20);

ri = 2; % PM=45: 1; PM=60: 2
Rc = (R/C/fnd/2/pi/ri/Adc)^0.5; % compensation resistor
Cc = (ri*Adc*C/fnd/2/pi/R)^0.5; % compensation capacitor

wzc = 1/2/pi/Rc/Cc; % zero frequency

reference

Viola Schäffer, Designing Amplifiers for Stability, ISSCC 2021 Tutorials

R.Eschauzier "Wide Bandwidth Low Power Operational Amplifiers", Delft University Press, 1994.

Gene F. Franklin, J. David Powell, and Abbas Emami-Naeini. 2018. Feedback Control of Dynamic Systems (8th Edition) (8th. ed.). Pearson. 6.7 Compensation

Application Note AN-1286 Compensation for the LM3478 Boost Controller

ECEN 607 Advanced Analog Circuit Design Techniques Spring 2017 URL: Lect 1D Op-Amps Stability and Frequency Compensation Techniques

Sergio Franco, San Francisco State University, Design with Operational Amplifiers and Analog Integrated Circuits, 4/e

J. H. Huijsing, 6.2.2.1 Two-GA-stage Parallel Compensation (PC), "Operational Amplifiers, Theory and Design, 3rd ed. New York: Springer, 2017"

maximum frequency

A conventional inverter-based ring oscillator consists of a single loop of an odd number of inverters. While compact, easy to design and tunable over a wide frequency range, this oscillator suffers from several limitations.

  • it is not possible to increase the number of phases while maintaining the same oscillation frequency since the frequency is inversely proportional to the number of inverters in the loop. In other words, the time resolution of the oscillator is limited to one inverter delay and cannot be improved below this limit.
  • the number of phases that can be obtained from this oscillator is limited to odd values. Otherwise, if an even number of inverters is used, the circuit remains in a latched state and does not oscillate.

To overcome the limitations of conventional ring oscillators, multi-paths ring oscillator (MPRO) is proposed. Each phase can be driven by two or more inverters, or multi-paths instead of having each phase in oscillator driven by a single inverter, or single path.

One thing that makes the MPRO design problem even more complicated is its property of having multiple possible oscillation modes. Without a clear understanding of what makes one of these modes dominant, it is very likely that a designer might end-up having an oscillator that can start-up each time in a different oscillation mode depending on the initial state of the oscillator.

image-20220320155440541

In practive, the oscillator starts first from a linear mode of operation where all the buffers are indeed acting as linear transconductors. All oscillation modes that have mode gains, \(a_n\), lower than the actual dc gain of the inverter, \(a_0\), start to grow. As the oscillation amplitude grows, the effective gain of the inverter drops due to nonlinearity. Consequently, modes with higher mode gain die out and only the mode that requires the minimum gain continues to oscillate and hence is the dominant mode.

The dominant mode is dependent only on the relative sizing vector

maximum oscillation frequency

The oscillation frequency of the dominant mode of any MPRO having any arbitrary coupling structure and number of phases is \[ f_{n^*} = \frac {1}{2\pi}\frac {(a_0-1) \cdot \sum_{i=1}^{N}x_isin\left ( \frac {2\pi n^*(i-1)}{N} \right)}{(a_0\tau _p - \tau _o)\cdot \sum_{i=1}^{N}-x_icos\left( \frac{2\pi n^*(i-1)}{N}+(\tau _o - \tau _p) \right)} \] image-20220320172952925

A linear increase in the maximum possible normalized oscillation frequency as the number of stages increases provided that the dc gain of the buffer sufficient to provide the required amplification

image-20220320170324494

image-20220320170523390

assuming unlimited dc gain and zero mode gain margins

mode stability

A common problem in MPRO design is the stability of the dominant oscillation mode. Mode stability refers to whether the MPRO always oscillates at the same mode regardless of the initial conditions of the oscillator. This problem is especially pronounced for MPROs with a large number of phases. This is due to the existence of many modes and the very small differences in the value of the mode gain of adjacent modes if the MPRO is not well designed.

In general, when the mode gain difference between two modes is small, the oscillator can operate in either one depending on initial conditions.

coupling configurations and simulation results

image-20220320165217231

Abou-El-Sonoun, A. A. (2012). High Frequency Multiphase Clock Generation Using Multipath Oscillators and Applications. UCLA. ProQuest ID: AbouElSonoun_ucla_0031D_10684. Merritt ID: ark:/13030/m57p9288. Retrieved from https://escholarship.org/uc/item/75g8j8jt

frequency stability

A problem associated with the design of MPROs is the existence of different possible modes of oscillation. Each of these modes is characterized by a different frequency, phase shift and phase noise.

Linear delay-stage model (mode gain)

mode gain is based on the linear model, independent of process but depends on coupling structure (coupling configuration, size ratio).

The inverting buffer modeled as a linear transconductor. The input-output relationship of a single buffer scaled by \(h_i\) and driving the input capacitance of a similar buffer can be expressed as \[\begin{align} h_ig_mv_{in}(t) + h_ig_oV_{out}(t)+h_iC_g\frac {dV_{out}(t)}{dt} &= 0 \\ a_nV_{in}(t)+V_{out}(t)+\tau \frac {V_{out}(t)}{dt} &= 0 \end{align}\] where \(g_m\) is the transconductance, \(g_o\) is the output conductance, \(C_g\) is the buffer input capacitance which also acts as the load capacitance for the driving buffer, and \(a_n = \frac {g_m}{g_o}\) is the linear dc gain of the buffer, and \(\tau=\frac {C_g}{g_o}\) is a time constant.

Similarly, \(V_1\), the output of the first stage in MPRO can be expressed \[ \sum_{i=1}^{N}h_ig_mV_i(t)+\sum_{i=1}^{N}h_ig_oV_i(t)+\sum_{i=1}^{N}h_iC_g\frac {dV_it(t)}{dt} = 0 \] Defining the fractional sizing factors and the total sizing factor as \(x_i=\frac{h_i}{H}\) and \(H=\sum_{i=1}^{N}h_i\) \[ a_n\sum_{i=1}^{N}x_iV_i(t) + V_1(t)+\tau\frac {dV_i(t)}{dt} = 0 \] where \(a_n = \frac {g_m}{g_o}\) and \(\tau=\frac {C_g}{g_o}\) are same dc gain and time constant defined previously

Since the total phase shift around the loop should be multiples of \(2\pi\), the oscillation waveform at the ith node can be expressed as \[ V_i(t) = V_o \cos(\omega_nt-\Delta \varphi \cdot i) \] where \(\omega_n\) is the oscillation frequency and \(\Delta \varphi = \frac {2\pi n}{N}\), \(N\) is the number of stages in the oscillator and \(n\) can take values between \(0\) and \(N-1\)

Plug \(V_i(t)\) into differential equation, we get \[ a_n\sum_{i=1}^{N}x_i\cos(\omega_n t-\frac{2\pi n}{N}i)+\cos(\omega_n t-\frac{2\pi n}{N}) - \omega_n \tau \sin(\omega_n t-\frac{2\pi n}{N}) = 0 \] By equating the \(cos(\omega_n t)\) and \(sin(\omega_n t)\) terms of the above equation, we get expressions for the oscillation frequency of the nth mode and the minimum dc gain required for this mode to exist. we refer to this gain as the mode gain \[\begin{align} \omega_n\tau &= \frac {\sum_{i=1}^{N}x_i \cdot \sin(\frac{2\pi n}{N}(i-1))}{-\sum_{i=1}^{N}x_i \cdot \cos(\frac{2\pi n}{N}(i-1))} \\ a_n &= \frac {1}{-\sum_{i=1}^{N}x_i \cdot \cos(\frac{2\pi n}{N}(i-1))} \end{align}\] where \(a_n\) should be greater than \(0\) for a existent mode

In practice, the oscillator starts first from a linear mode of operation where all the buffers are indeed acting as linear transconductors. All oscillation modes that have mode gains \(a_n\) lower than the actual dc gain of the inverter \(a_o\) start to grow. As the oscillation amplitude grows, the effective gain of the inverter drops due to nonlinearity. Consequently, modes with higher mode gain die out and only the mode that requires the minimum gain continues to oscillate and hence is the dominant mode

A. A. Hafez and C. K. Yang, "Design and Optimization of Multipath Ring Oscillators," in IEEE Transactions on Circuits and Systems I: Regular Papers, vol. 58, no. 10, pp. 2332-2345, Oct. 2011, doi: 10.1109/TCSI.2011.2142810.

Simulation-based approach

GCHECK is an automated verification tool that validate whether a ring oscillator always converges to the desired mode of operation regardless of the initial conditions and variability conditions. This is the first tool ever reported to address the global convergence failures in presence of variability. It has been shown that the tool can successfully validate a number of coupled ring oscillator circuits with various global convergence failure modes (e.g. no oscillation, false oscillation, and even chaotic oscillation) with reasonable computational costs such as running 1000-point Monte-Carlo simulations for 7~60 initial conditions (maximum 4 hours).

  • The verification is performed using a predictive global optimization algorithm that looks for a problematic initial state from a discretized state space
  • despite the finite number of initial state candidates considered and finite number of Monte-Carlo samples to model variability, the proposed algorithm can verify the oscillator to a prescribed confidence level

image-20220320183344877

  • The observation that the responses of a circuit with nearby initial conditions are strongly correlated with respect to common variability conditions enables us to explore a discretized version of the initial condition space instead of the continuous one.
  • the settling time increases as the initial state gets farther away from the equilibrium state allowed us to use the settling time as a guidance metric to find a problematic initial condition.

Selecting the Next Initial Condition Candidate to Evaluate

To determine whether the algorithm should continue or terminate the search for a new maximum, the algorithm estimates the probability of finding a new initial condition with the longer settling time, based on the information obtained with the previously-evaluated initial conditions.

GCHECK EXAMPLE

1
python gcheck_osc.py input.scs

output log:

1
2
3
4
5
6
7
8
Step 1/4: Simulating the setting-time distribution with the reference initial condition
...
Step 2/4: Simulating the setting-time distribution for randomly-selected initial probes
...
Step 3/4: Searching for Problematic Initial Conditions
...
Step 4/4: Reporting Verification Results and Statistics
...

image-20220320201718018

T. Kim, D. -G. Song, S. Youn, J. Park, H. Park and J. Kim, "Verifying start-up failures in coupled ring oscillators in presence of variability using predictive global optimization," 2013 IEEE/ACM International Conference on Computer-Aided Design (ICCAD), 2013, pp. 486-493, doi: 10.1109/ICCAD.2013.6691161.GCHECK: Global Convergence Checker for Oscillators

Process Corners

image-20220302000743092

Global/Local Variation

image-20220302000808679

Timing and RC Modeling with Process Corners

image-20220302000916728

Global and Local variation by Gaussian

image-20230111003336823

Local Monte-Carlo (SSG, FFG with Local Gaussian) as Signoff golden

image-20231215234014594

Process Corner Model Limitations

image-20220323232119661

image-20230111003705417

Variation section

  • Total corner (TT/SS/FF/SF/FS)
    • E.g. TTMacro_MOS_MOS_MOSCAP
  • Global Corner (TTG/SSG/FFG/SFG/FSG) + Local MC
    • E.g. TTGlobalCorner_LocalMC_MOS_MOSCAP
  • Local MC
    • E.g. LocalMCOnly_MOS_MOSCAP
  • Global MC + Local MC (Total MC)
    • GlobalMC_LocalMC_MOS_MOSCAP

image-20230308003005235

image-20230111010426775

img

image-20230111011757049

SSGNP, FFGNP:

When N/P global correlation is weak (R^2=0.15), the corner of N/PMOS balance circuit (e.g. inverter) can be tightened (3sigma -> 2.5sgma) due to the cancellation between NMOS and PMOS

SSGNP, FFGNP usually used in Digital STA

  • Global variation validation with global corner
    • 3-sigma of global MC simulation is aligned with global corner
  • Total variation validation with total corner
    • 3-sigma of global MC + local MC (total) simulation is aligned with total corner

Global Corner

https://community.cadence.com/cadence_technology_forums/f/custom-ic-design/20466/monte-carlo-simulation-global-local-vs-local-and-process-vs-mismatch/1365101#1365101

The "total corner" is representative of the maximum device parameter variation including local device variation effects. However, it is not a statistical corner.

The "global" corner is defined as the "total" corner minus the impact of "local variation"

Hence, if you were to examine simulation results for a parameter using a "total" and "global" corner, you would find the range of variation will be less with the "global" corner than with the "total" corner.

The "global" corner is provided for use in statistical simulations. Hence, when performing a Monte-Carlo simulation, the "global" corner is selected - NOT the "total" corner.

image-20230511234313012

\[ \Delta V_{T,\sigma_{total}} = \sqrt{\Delta^2 _{T, \sigma_{global}}+\Delta^2 _{T, \sigma_{local}}} \]

reference

Eric J.-W. Fang, T5: Fundamentals of Process Monitors for Signoff-Oriented Circuit Design, 2022 IEEE International Solid-State Circuits Conference

Alvin Loke, Device and Physical Design Considerations for Circuits in FinFET Technology, ISSCC 2020 Short Course

簡報 Cln16ffcll Sr V1d0 2p1 Usage Guide URL: https://usermanual.wiki/Document/cln16ffcllsrv1d02p1usageguide.1649731847/view

Radojcic, Riko, Dan Perry and Mark Nakamoto. “Design for manufacturability for fabless manufactuers.” IEEE Solid-State Circuits Magazine 1 (2009): n. pag.

How To Reduce Implementation Headaches In FinFET Processes URL: https://semiengineering.com/how-to-reduce-implementation-headaches-in-finfet-processes/

陌上风骑驴看IC, STA | SSGNP, FFGNP. https://mp.weixin.qq.com/s/eJ8fYRJBR1E9XbfH95OUOg

陌上风骑驴看IC, STA | ssg 跟ss corner 的区别——谬误更正版 https://mp.weixin.qq.com/s?__biz=MzUzODczODg2NQ==&mid=2247486225&idx=1&sn=e9c68f6108ae6c9958d47ca0b29373ca&chksm=fad262cfcda5ebd949cc91353c7cbfaf4ba61179306f7d8e98461a4f4ca9d8a9baef5e9f2cc1&scene=178&cur_album_id=1326356275000705025#rd

The Evolution, Pitfalls, and Cargo Cult Engineering of Advanced Digital Timing Sign-off https://www.tauworkshop.com/2021/speaker_slides/christian_l.pdf

Don O'Riordan Cadence Design Systems. Recommended Spectre Monte Carlo modeling methodology [https://designers-guide.org/modeling/montecarlo.pdf]

PrimeTime does not automatically identifies multicycle paths.

specifying multicycle path for setup

1
2
3
4
5
set_multicycle_path 6 -from reg[26]/CP -to reg/D
# or
set_multicycle_path -setup 6 -from reg[26]/CP -to reg/D
# check the exception
report_exception

specifying multicycle path for hold (new data every 6 cycles)

1
2
set_multicycle_path -setup 6 -to [get_pins "*reg[*]/D"]
set_multicycle_path -hold [expr 6-1] -to [get_pins "*reg[*]/D"]

image-20220301215938296

MH stands for Hold Multiplier, MS for Setup Multiplier. The Setup multiplier counts up with increasing clock cycles, the Hold Multiplier counts up with decreasing cycles. The origin (0) for the Hold Multiplier is always at the Setup Multiplier - 1 position.

Reporting a multicycle path with report_timing

1
report_timing -exceptions all -from *reg[26]/CP -to *reg/D

Timing Exceptions

If certain paths are not intended to operate according to the default setup and hold behavior assumed by the PrimeTime tool, you need to specify those paths as timing exceptions. Otherwise, the tool might incorrectly report those paths as having timing violations.

The PrimeTime tool lets you specify the following types of exceptions:

  • False path – A path that is never sensitized due to the logic configuration, expected data sequence, or operating mode.
  • Multicycle path – A path designed to take more than one clock cycle from launch to capture.
  • Minimum or maximum delay path – A path that must meet a delay constraint that you explicitly specify as a time value.

REF

PrimeTime® User Guide Version O-2018.06-SP4 Chapter 1: Introduction to PrimeTime Overview of Static Timing Analysis - Timing Exceptions

PT picks the most restrictive pair of edges for setup and for hold. It determines which edges to be used as follows:

  1. Evaluate waveforms over the smallest common base period

  2. For each capture edge, find the closest setup launch edge. Call these the primary pairs

  3. Out of the primary pairs, pick the most restrictive setup launch and capture edges.

  4. For each primary pair, draw two hold relationships:

    • Launch to (capture - 1)

    • (Launch + 1) to Capture

      From all of these hold relationships, pick the most restrictive.

PrimeTime uses the ideal clock waveform (as reported in report_clock) to determine the appropriate clock edges for inter-clock analysis.

image-20220301203135490

The most restrictive setup pair is from Clk1 8ns to Clk2 9ns

The most restrictive hold pair is from Clk1 0ns to Clk2 0ns

primary clocks

  • Primary clocks should be created at input ports and output pins of black boxes.
  • Never create clocks on hierarchy pins. Creating clocks on hierarchy will cause problems when reading SDF. The net timing arc becomes segmented at the hierarchy and PrimeTime will be unable to annotate the net successfully.

generated clocks

  • Generated clocks are generally created for waveform modifications of a primary clock (not including simple inversions). PrimeTime does not simulate a design and thus will not derive internally generated clocks automatically - these clocks must be created by the user and applied as a constraint.
  • PrimeTime caculate source latency for generated clocks if primary clock is propagated, otherwise its source latency is zero.
1
2
3
4
5
# primary clock
create_clock -period 4 [get_ports Clk]
set_clock_latency -source 2 [get_clocks Clk]
set_propagated_clock [get_clocks Clk]
create_generated_clock -divide_by 2 -name div_clk -source [get_ports Clk] FF3/Q

virtual clocks

  • Are clock objects without a source
  • Do not clock sequential devices within the current_design
  • Serve as references of input or output delays
1
2
3
4
# create a virtual clock, vclk, for input and output delay constraints
create_clock -period 5 -name vclk
set_input_delay -max 2 -clock vclk [get_ports in1]
set_output_delay -max 1 -clock vclk [get_ports out2]

There is no network latency to calculate, even if a virtual clock is propagated.

package import in SystemVerilog compilation

class forward declaration [https://verificationacademy.com/forums/systemverilog/class-forward-declaration#answer-41383]

import and compile order

project

yapp_pkg.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package yapp_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"

typedef uvm_config_db#(virtual yapp_if) yapp_vif_config;

`include "yapp_packet.sv"
`include "yapp_tx_monitor.sv"
`include "yapp_tx_sequencer.sv"
`include "yapp_tx_seqs.sv"
`include "yapp_tx_driver.sv"
`include "yapp_tx_agent.sv"
`include "yapp_env.sv"

endpackage

yapp_tx_monitor.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class yapp_tx_monitor extends uvm_monitor;

// Collected Data handle
yapp_packet pkt;

// Count packets collected
int num_pkt_col;

// component macro
`uvm_component_utils_begin(yapp_tx_monitor)
`uvm_field_int(num_pkt_col, UVM_ALL_ON + UVM_NOCOMPARE)
`uvm_component_utils_end
/////////////////// virtual interface ////////////////////
virtual interface yapp_if vif;
/////////////////////////////////////////////////////////

function new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new
...

endclass : yapp_tx_monitor

yapp_tx_driver.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class yapp_tx_driver extends uvm_driver #(yapp_packet);

////////////////// virtual interface ////////////////////
virtual interface yapp_if vif;
/////////////////////////////////////////////////////

int num_sent;

// component macro
`uvm_component_utils_begin(yapp_tx_driver)
`uvm_field_int(num_sent, UVM_ALL_ON + UVM_NOCOMPARE)
`uvm_component_utils_end

// Constructor - required syntax for UVM automation and utilities
function new (string name, uvm_component parent);
super.new(name, parent);
endfunction : new

function void start_of_simulation_phase(uvm_phase phase);
`uvm_info(get_type_name(), {"start of simulation for ", get_full_name()}, UVM_HIGH)
endfunction : start_of_simulation_phase
...

endclass : yapp_tx_driver

yapp_if.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface yapp_if (input clock, input reset );
timeunit 1ns;
timeprecision 100ps;

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

////////////////// import package //////////////////////
import yapp_pkg::*;
//////////////////////////////////////////////////////

// Actual Signals
logic in_data_vld;
logic in_suspend;
logic [7:0] in_data;

// signal for transaction recording
bit monstart, drvstart;

// local storage for payload
logic [7:0] payload_mem [0:63];
...

endinterface : yapp_if

virtual interface is in package;

yapp_pkg is imported into interface

!!! typedef uvm_config_db#(virtual yapp_if) yapp_vif_config; is forward declaration

A forward typedef declares an identifier as a type in advance of the full definition of that type

VCS compile

1
vcs -full64 -R -sverilog  -ntb_opts uvm-1.2 +UVM_TESTNAME=short_yapp_012_test  -f vcs.f  

vcs.f

1
2
3
4
5
6
7
8
9
-timescale=1ns/1ns

+incdir+../sv

../sv/yapp_if.sv
../sv/yapp_pkg.sv
./clkgen.sv
./hw_top.sv
./tb_top.sv

output ERROR

1
2
3
4
5
6
Error-[SV-LCM-PND] Package not defined
../sv/yapp_if.sv, 18
yapp_if, "yapp_pkg::"
Package scope resolution failed. Token 'yapp_pkg' is not a package.
Originating module 'yapp_if'.
Move package definition before the use of the package.

xrun compile

1
xrun -f xrun.f

xrun.f

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-64

-uvmhome CDNS-1.2

// incdir for include files
-incdir ../sv

+UVM_TESTNAME=short_yapp_012_test

-timescale 1ns/1ns

// compile files
../sv/yapp_if.sv
../sv/yapp_pkg.sv
./clkgen.sv
./hw_top.sv
./tb_top.sv

output Error

1
2
3
4
5
6
file: ../sv/yapp_if.sv
import yapp_pkg::*;
|
xmvlog: *E,NOPBIND (../sv/yapp_if.sv,18|14): Package yapp_pkg could not be bound.
interface worklib.yapp_if:sv
errors: 1, warnings: 0

solution

place ../sv/yapp_pkg.sv before ../sv/yapp_if.sv.

In this particular example, yapp_pkg is NOT needed in interface. just delete import yapp_pkg::* is enough in ../sv/yapp_if.sv

plain example

The order of compilation unit DON'T matter

project

hw_top.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
module hw_top;

// Clock and reset signals
logic [31:0] clock_period;
logic run_clock;
logic clock;
logic reset;

// YAPP Interface to the DUT
yapp_if in0(clock, reset);

// CLKGEN module generates clock
clkgen clkgen (
.clock(clock),
.run_clock(1'b1),
.clock_period(32'd10)
);

yapp_router dut(
.reset(reset),
.clock(clock),
.error(),
// YAPP interface signals connection
.in_data(in0.in_data),
.in_data_vld(in0.in_data_vld),
.in_suspend(in0.in_suspend),
// Output Channels
//Channel 0
.data_0(),
.data_vld_0(),
.suspend_0(1'b0),
//Channel 1
.data_1(),
.data_vld_1(),
.suspend_1(1'b0),
//Channel 2
.data_2(),
.data_vld_2(),
.suspend_2(1'b0),
// Host Interface Signals
.haddr(),
.hdata(),
.hen(),
.hwr_rd());

...

endmodule

yapp_router.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module yapp_router (input clock,                              
input reset,
output error,

// Input channel
input [7:0] in_data,
input in_data_vld,
output in_suspend,
// Output Channels
output [7:0] data_0, //Channel 0
output reg data_vld_0,
input suspend_0,
output [7:0] data_1, //Channel 1
output reg data_vld_1,
input suspend_1,
output [7:0] data_2, //Channel 2
output reg data_vld_2,
input suspend_2,

// Host Interface Signals
input [15:0] haddr,
inout [7:0] hdata,
input hen,
input hwr_rd);
...
endmodule

compile

place hw_top.sv before or after yapp_router.sv doesn't matter, the compiler (xrun, vcs) can compile them successfully.

Conclusion

Always place package before DUT is preferred choice during compiling.

Transaction Level Modeling (TLM)

image-20220403094350570

image-20220403094404891

  • Blocking methods are defined with get() or put() tasks to allow them to consume time
  • Non-blocking methods are defined with try_get() or try_put() functions as they execute in zero time

Uni-Directional TLM Methods Reference

Method Description Syntax
put() Blocking put virtual task put(iput TR t);
try_put() Nonblocking put
-return 1 if successful
-return 0 if not
virtual function bit try_put(input TR t);
can_put() Nonblocking test put
-return 1 if put would be successful
-return 0 if not
virtual function bit can_put();
get() Blocking get virtual task get(output TR t);
try_get() Nonblocking get
-return 1 if successful
-return 0 if not
virtual function bit try_get(output TR t);
can_get() Nonblocking test get
-return 1 if get would be successful
-return 0 if not
virtual function bit can_get();
peek() Blocking peek virtual task peek(output TR t);
try_peek() Nonblocking peek
-return 1 if successful
-return 0 if not
virtual function bit try_peek(output TR t);
can_peek Nonblocking test peek
-return 1 if peek would be successful
-return 0 if not
virtual function bit can_peek();

The peek() methods are similarly to the get() methods, but copy the transaction instead of removing it. The transaction is not consumed, and a subsequent get or peek operation will return the same transaction

Selected Connector and Method Options

put try_put can_put get try_get can_get peek try_peed can_peek
uvm_put_*
uvm_blocking_put_*
uvm_nonblocking_put_*
uvm_get_*
uvm_blocking_get_*
uvm_nonblocking_get_*
uvm_get_peek_*
uvm_blocking_get_peek_*
uvm_nonblocking_get_peek_*

in the connectors above, * can be replaced by port, imp, or export

All the methods for a specific connector type MUST be implemented. If you define an uvm_put connection between two compoents, then the component with the uvm_put_imp object must provide implementations of ALL three put methods, put, try_put and can_put, even if these methods are not explicitly called

TLM FIFO

The TLM FIFO is a FIFO component wrapped in get and put imp connectors. This has the benefit of data storage as well as providing implementations of the communication methods. Components connected to the TLM FIFO are in control of data transfer and can simply defined port connectors to initiate read and write operations on the FIFO

uvm_tlm_fifo

image-20220403141222126

The TLM FIFO object is effectively a FIFO component instantiated between and connected to two components. The FIFO contains imp connectors for the standard TLM put and get/peek interfaces, therefore the user does not have to defineimp ports or communication methods and the FIRO takes care of data storage

The advantages are:

  • The user does not need to define communication methods or imp connectors
  • The FIFO provides data storage between the write (put) and read (get/peek) components
  • There are a number of built-in methods for checking FIFO status

The disadvantages are:

  • The user must now initiate both sides of the transfer (both get/peek and put) to complete the transaction
  • Two connections must be made (both sides of the FIFO) rather than one

The put_export and get_peek_export connection objects have alternatives which provide subsets of the full connector. For example, blocking_put_export and nonblocking_put_export can replace put_export. blocking_get_export, nonblocking_get_export and get_export (as well as others) can replace get_peek_export.

built-in methods
Method Description Syntax
new Standard component constructor with an additional third argument, size, which sets the maximum FIFO size. Default size is 1. A size of 0 is an unbounded FIFO function new(string name, uvm_component parent=null, int size=1);
size Return size of FIFO. 0 indicates unbounded FIFO virtual function int size()
used Return number of entries written to the FIFO virtual function int used();
is_empty Return 1 if used() is 0, otherwise 0 virtual function bit is empty();
is_full Return 1 if used() is equal to size, otherwise 0 virtual function bit is_full()
flush Delete all entries from the FIFO, upon which used() is 0 and is_empty() is 1 virtual funciton void flush();
1
2
3
class uvm_tlm_fifo #(type T=int) extends uvm_tlm_fifo_base #(T);
...
endclass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
virtual class uvm_tlm_fifo_base #(type T=int) extends uvm_component;
uvm_put_imp #(T, this_type) put_export;

uvm_get_peek_imp #(T, this_type) get_peek_export;

uvm_analysis_port #(T) put_ap;

uvm_analysis_port #(T) get_ap;

// The following are aliases to the above put_export
uvm_put_imp #(T, this_type) blocking_put_export;
uvm_put_imp #(T, this_type) nonblocking_put_export;

// The following are all aliased to the above get_peek_export, which provides
// the superset of these interfaces.
uvm_get_peek_imp #(T, this_type) blocking_get_export;
uvm_get_peek_imp #(T, this_type) nonblocking_get_export;
uvm_get_peek_imp #(T, this_type) get_export;

uvm_get_peek_imp #(T, this_type) blocking_peek_export;
uvm_get_peek_imp #(T, this_type) nonblocking_peek_export;
uvm_get_peek_imp #(T, this_type) peek_export;

uvm_get_peek_imp #(T, this_type) blocking_get_peek_export;
uvm_get_peek_imp #(T, this_type) nonblocking_get_peek_export;

function new(string name, uvm_component parent = null);
super.new(name, parent);

put_export = new("put_export", this);
blocking_put_export = put_export;
nonblocking_put_export = put_export;

get_peek_export = new("get_peek_export", this);
blocking_get_peek_export = get_peek_export;
nonblocking_get_peek_export = get_peek_export;
blocking_get_export = get_peek_export;
nonblocking_get_export = get_peek_export;
get_export = get_peek_export;
blocking_peek_export = get_peek_export;
nonblocking_peek_export = get_peek_export;
peek_export = get_peek_export;

put_ap = new("put_ap", this);
get_ap = new("get_ap", this);

endfunction

Analysis FIFO

uvm_tlm_analysis_fifo

image-20220403144951278

uvm_tlm_analysis_fifo is a specialization of uvm_tlm_fifo

  • Intended to buffer write transactions between the UVC monitor analysis port and scoreboard

It has the following characteristics:

  • Unbounded (size=0)
  • analysis_export connector replaces put_export
    • Support the analysis write method

image-20220403150326750

By declaring the FIFO in the scoreboard, we can get directly from the FIFO output.

However the write side connection of the FIFO to the interface UVC monitor analysis port must be made (usually) in the testbench which has visibility of both UVC and scoreboard components. The connection is made using a connect method call inside the connect phase method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class uvm_tlm_analysis_fifo #(type T = int) extends uvm_tlm_fifo #(T);

// Port: analysis_export #(T)
//
// The analysis_export provides the write method to all connected analysis
// ports and parent exports:
//
//| function void write (T t)
//
// Access via ports bound to this export is the normal mechanism for writing
// to an analysis FIFO.
// See write method of <uvm_tlm_if_base #(T1,T2)> for more information.

uvm_analysis_imp #(T, uvm_tlm_analysis_fifo #(T)) analysis_export;


// Function: new
//
// This is the standard uvm_component constructor. ~name~ is the local name
// of this component. The ~parent~ should be left unspecified when this
// component is instantiated in statically elaborated constructs and must be
// specified when this component is a child of another UVM component.

function new(string name , uvm_component parent = null);
super.new(name, parent, 0); // analysis fifo must be unbounded
analysis_export = new("analysis_export", this);
endfunction

const static string type_name = "uvm_tlm_analysis_fifo #(T)";

virtual function string get_type_name();
return type_name;
endfunction

function void write(input T t);
void'(this.try_put(t)); // unbounded => must succeed
endfunction

endclass

Analysis Port Broadcast

image-20220403152922101

Analysis ports can (uniquely) be connected to any number of imp connectors, including zero

image-20220403153148158

The analysis port in component yapp_monitor will be connected to both monitor_one and monitor_two components. Each of the receiving components has an analysis imp object and a write communication method declared. The write method must have the same signature - i.e., they must be void functions called write with a single input argument of type yapp_packet, but their implementations can be completely different.

When the send_yapp port is connected to both mone_in and mtwo_in imps, then a single write call from yapp_monitor executes both write implementations in monitor_one and monitor_two

image-20220403153742706

For non-analysis connections

  • the TLM object must be connected to a single destination only, i.e., each non-analysis object has exactly one connect call. An object may be used multiple times as the argument to a connect, but exactly once as the caller. This is called many-to-one connection. For example, many ports can be connected to a one imp connector

For analysis connections

  • a TLM object can be connected to any number of destinations i.e., one analysis object can call connect many times. This is called one-to-many connection. For example, one port can be connected to many imp connectors. one-to-many is only allowed for analysis connections. An analysis connection can also NOT call connect. An unconnected TLM object is also only allowed for analysis connections

Bi-Directional TLM Transport Connection

image-20220403160248099

Connector syntax

1
2
3
uvm_blocking_transport_XXX #(type REA, type RSP)
uvm_nonblocking_transport_XXX #(type REQ, type RSP)
uvm_transport_XXX #(type REQ, type RSP)

Communication methods

1
2
task transport(REQ request, output RSP response)
function bit nb_transport(REQ request, output RSP response)

Gotchas

FIFO/analysis FIFOs do not perform any cloning on input transactions. Therefore, you will need to check that the UVC monitors collect every transaction into a different instance to avoid overwriting data in the FIFOs

reg_verifier of Cadence

Generation

image-20220404125201390

1
reg_verifier -domain uvmreg -top yapp_router_regs.xml -dut_name yapp_router_regs -out_file yapp_router_regs -cov -quicktest -pkg yapp_router_reg_pkg

image-20220404125541442

image-20220404125421201

generated file description
yapp_router_regs_config.dat Configuration information
yapp_router_regs_hdlpaths.dat Path information for backddoor access
yapp_router_regs_rdb.sv Register Model
cdns_uvmreg_utils_pkg.sv Cadence utility package
quicktest.sv UVM test to verify model

Sanity Check

1
make run_test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class yapp_router_regs_t extends cdns_uvm_reg_block;
uvm_reg_map default_map;
uvm_reg_map router;
rand yapp_regs_c router_yapp_regs;
rand yapp_mem_c router_yapp_mem;
rand yapp_pkt_mem_c router_yapp_pkt_mem;
...
virtual function void build();
router = create_map("router", `UVM_REG_ADDR_WIDTH'h0, 1, UVM_LITTLE_ENDIAN, 1);
default_map = router;
...
//Mapping router map
router.add_submap(router_yapp_regs.default_map, `UVM_REG_ADDR_WIDTH'h1000)
router.add_mem(router_yapp_mem, `UVM_REG_ADDR_WIDTH'h1100);
router.add_mem(router_yapp_pkt_mem, `UVM_REG_ADDR_WIDTH'h1010);
...
endfunction

endclass


class qt_test extends uvm_test;
yapp_router_regs_t model;
...
task run_phase(uvm_phase phase);
...
model.print();
model.default_map.print();
model.router_yapp_regs.default_map.print();
endtask
...
endclass

yapp_mem_c

1
2
3
4
5
6
7
class yapp_mem_c extends uvm_mem;                                                                                                                                                                                                                                
`uvm_object_utils(yapp_mem_c)

function new(input string name="router_yapp_mem");
super.new(name, 'h100, 8, "RW", UVM_NO_COVERAGE);
endfunction
endclass

yapp_pkt_mem_c

1
2
3
4
5
6
7
class yapp_pkt_mem_c extends uvm_mem;                                                                                                                                                                                                                                      
`uvm_object_utils(yapp_pkt_mem_c)
function new(input string name="router_yapp_pkt_mem");
super.new(name, 'h40, 8, "RO", UVM_NO_COVERAGE);
endfunction

endclass

yapp_regs_c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class yapp_regs_c extends cdns_uvm_reg_block;
`uvm_object_utils(yapp_regs_c)
rand addr0_cnt_reg_c addr0_cnt_reg;
rand addr1_cnt_reg_c addr1_cnt_reg;
rand addr2_cnt_reg_c addr2_cnt_reg;
rand illegal_addr_cnt_reg_c addr3_cnt_reg;
rand ctrl_reg_c ctrl_reg;
rand en_reg_c en_reg;
rand mem_size_reg_c mem_size_reg;
rand oversized_pkt_cnt_reg_c oversized_pkt_cnt_reg;
rand parity_err_cnt_reg_c parity_err_cnt_reg;
virtual function void build();
uvm_reg reg_set[$];
default_map = create_map(get_name(), `UVM_REG_ADDR_WIDTH'h1000, 1, UVM_LITTLE_ENDIAN, 1);
begin
uvm_reg_config_ta ta = get_reg_config("yapp_router_regs.router_yapp_regs");
build_uvm_regs(default_map, this, null, ta, reg_se);
end
if(! $cast(addr0_cnt_reg, reg_set[0]))
`uvm_error("UVM_REG", "addr0_cnt_reg register casting error")
if(! $cast(addr1_cnt_reg, reg_set[1]))
`uvm_error("UVM_REG", "addr1_cnt_reg register casting error")
if(! $cast(addr2_cnt_reg, reg_set[2]))
`uvm_error("UVM_REG", "addr2_cnt_reg register casting error")
if(! $cast(addr3_cnt_reg, reg_set[3]))
`uvm_error("UVM_REG", "addr3_cnt_reg register casting error")
if(! $cast(ctrl_reg, reg_set[4]))
`uvm_error("UVM_REG", "ctrl_reg register casting error")
if(! $cast(en_reg, reg_set[5]))
`uvm_error("UVM_REG", "en_reg register casting error")
if(! $cast(mem_size_reg, reg_set[6]))
`uvm_error("UVM_REG", "mem_size_reg register casting error")
if(! $cast(oversized_pkt_cnt_reg, reg_set[7]))
`uvm_error("UVM_REG", "oversized_pkt_cnt_reg register casting error")
if(! $cast(parity_err_cnt_reg, reg_set[8]))
`uvm_error("UVM_REG", "parity_err_cnt_reg register casting error")
endfunction

function new(input string name="router_yapp_regs");
super.new(name, UVM_NO_COVERAGE);
endfunction
endclass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class uvm_reg_map extends uvm_object;
// Function: add_submap
//
// Add an address map
//
// Add the specified address map instance to this address map.
// The address map is located at the specified base address.
// The number of consecutive physical addresses occupied by the submap
// depends on the number of bytes in the physical interface
// that corresponds to the submap,
// the number of addresses used in the submap and
// the number of bytes in the
// physical interface corresponding to this address map.
//
// An address map may be added to multiple address maps
// if it is accessible from multiple physical interfaces.
// An address map may only be added to an address map
// in the grand-parent block of the address submap.
//
extern virtual function void add_submap (uvm_reg_map child_map,
uvm_reg_addr_t offset);


// Function: add_mem
//
// Add a memory
//
// Add the specified memory instance to this address map.
// The memory is located at the specified base address and has the
// specified access rights ("RW", "RO" or "WO").
// The number of consecutive physical addresses occupied by the memory
// depends on the width and size of the memory and the number of bytes in the
// physical interface corresponding to this address map.
//
// If ~unmapped~ is TRUE, the memory does not occupy any
// physical addresses and the base address is ignored.
// Unmapped memories require a user-defined ~frontdoor~ to be specified.
//
// A memory may be added to multiple address maps
// if it is accessible from multiple physical interfaces.
// A memory may only be added to an address map whose parent block
// is the same as the memory's parent block.
//
extern virtual function void add_mem (uvm_mem mem,
uvm_reg_addr_t offset,
string rights = "RW",
bit unmapped=0,
uvm_reg_frontdoor frontdoor=null);


endclass: uvm_reg_map

register model print

// test run_phase

model.default_map.print();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
----------------------------------------------------------------------------------------------------------------------------            
Name Type Size Value
----------------------------------------------------------------------------------------------------------------------------
model yapp_router_regs_t - @3724
router_yapp_regs yapp_regs_c - @3792
addr0_cnt_reg addr0_cnt_reg_c - @3827
addr0_cnt_reg_fld uvm_reg_field ... RO addr0_cnt_reg[7:0]=8'h00
addr1_cnt_reg addr1_cnt_reg_c - @3839
addr1_cnt_reg_fld uvm_reg_field ... RO addr1_cnt_reg[7:0]=8'h00
addr2_cnt_reg addr2_cnt_reg_c - @3857
addr2_cnt_reg_fld uvm_reg_field ... RO addr2_cnt_reg[7:0]=8'h00
addr3_cnt_reg illegal_addr_cnt_reg_c - @3874
addr3_cnt_reg_fld uvm_reg_field ... RO addr3_cnt_reg[7:0]=8'h00
ctrl_reg ctrl_reg_c - @3891
plen uvm_reg_field ... RW ctrl_reg[5:0]=6'h3f
rsvd_0 uvm_reg_field ... RW ctrl_reg[7:6]=2'h0
en_reg en_reg_c - @3908
router_en uvm_reg_field ... RW en_reg[0:0]=1'h1
parity_err_cnt_en uvm_reg_field ... RW en_reg[1:1]=1'h0
oversized_pkt_cnt_en uvm_reg_field ... RW en_reg[2:2]=1'h0
rsvd_0 uvm_reg_field ... RW en_reg[3:3]=1'h0
addr0_cnt_en uvm_reg_field ... RW en_reg[4:4]=1'h0
addr1_cnt_en uvm_reg_field ... RW en_reg[5:5]=1'h0
addr2_cnt_en uvm_reg_field ... RW en_reg[6:6]=1'h0
addr3_cnt_en uvm_reg_field ... RW en_reg[7:7]=1'h0
mem_size_reg mem_size_reg_c - @3928
mem_size_reg_fld uvm_reg_field ... RO mem_size_reg[7:0]=8'h00
oversized_pkt_cnt_reg oversized_pkt_cnt_reg_c - @3966
oversized_pkt_cnt_reg_fld uvm_reg_field ... RO oversized_pkt_cnt_reg[7:0]=8'h00
parity_err_cnt_reg parity_err_cnt_reg_c - @3983
parity_err_cnt_reg_fld uvm_reg_field ... RO parity_err_cnt_reg[7:0]=8'h00
router_yapp_mem yapp_mem_c - @3757
n_bits integral 32 'd8
size integral 32 'd256
router_yapp_pkt_mem yapp_pkt_mem_c - @3779
n_bits integral 32 'd8
size integral 32 'd64
router uvm_reg_map 0 id=@3737 seqr=HI0.sqr offset=0x0 size=0x1200 baseaddr=0x0
router_yapp_regs uvm_reg_map 0 id=@3816 seqr=HI0.sqr offset=0x1000 size=0x5 baseaddr=0x1000
----------------------------------------------------------------------------------------------------------------------------

address map print

// test run_phase

model.router_yapp_regs.default_map.print();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---------------------------------------------------------------------------
Name Type Size Value
---------------------------------------------------------------------------
router uvm_reg_map - @3737
endian ... UVM_LITTLE_ENDIAN
effective sequencer uvm_sequencer ... HI0.sqr
router_yapp_mem yapp_mem_c ... @3757 +'h1100
router_yapp_pkt_mem yapp_pkt_mem_c ... @3779 +'h1010
router_yapp_regs uvm_reg_map - @3816
endian ... UVM_LITTLE_ENDIAN
effective sequencer uvm_sequencer ... HI0.sqr
addr0_cnt_reg addr0_cnt_reg_c ... @3827 +'h1009
addr1_cnt_reg addr1_cnt_reg_c ... @3839 +'h100a
addr2_cnt_reg addr2_cnt_reg_c ... @3857 +'h100b
addr3_cnt_reg illegal_addr_cnt_reg_c ... @3874 +'h1006
ctrl_reg ctrl_reg_c ... @3891 +'h1000
en_reg en_reg_c ... @3908 +'h1001
mem_size_reg mem_size_reg_c ... @3928 +'h100d
oversized_pkt_cnt_reg oversized_pkt_cnt_reg_c ... @3966 +'h1005
parity_err_cnt_reg parity_err_cnt_reg_c ... @3983 +'h1004
---------------------------------------------------------------------------

submap print

// test run_phase

model.router_yapp_regs.default_map.print();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-------------------------------------------------------------------------
Name Type Size Value
-------------------------------------------------------------------------
router_yapp_regs uvm_reg_map - @3814
endian ... UVM_LITTLE_ENDIAN
effective sequencer uvm_sequencer ... HI0.sqr
addr0_cnt_reg addr0_cnt_reg_c ... @3825 +'h1009
addr1_cnt_reg addr1_cnt_reg_c ... @3837 +'h100a
addr2_cnt_reg addr2_cnt_reg_c ... @3855 +'h100b
addr3_cnt_reg illegal_addr_cnt_reg_c ... @3872 +'h1006
ctrl_reg ctrl_reg_c ... @3889 +'h1000
en_reg en_reg_c ... @3906 +'h1001
mem_size_reg mem_size_reg_c ... @3926 +'h100d
oversized_pkt_cnt_reg oversized_pkt_cnt_reg_c ... @3964 +'h1005
parity_err_cnt_reg parity_err_cnt_reg_c ... @3981 +'h1004
-------------------------------------------------------------------------

cdns_uvmreg_utils_pkg

uvm_reg_field_config_t

1
2
3
4
5
6
7
8
9
10
11
typedef struct  {                                                                         
string name;
int unsigned size;
int unsigned lsb_pos;
string access;
bit volatile;
uvm_reg_data_t reset;
bit has_reset;
bit is_rand;
bit individually_accessible;
} uvm_reg_field_config_t;

uvm_reg_config_t

1
2
3
4
5
6
7
8
9
10
11
typedef struct  {
string reg_type_name;
string configUID;
string name;
uvm_reg_addr_t offset;
int unsigned size_bytes;
int unsigned addr_bits;
string access;
int is_array;
string range;
} uvm_reg_config_t;

uvm_blk_config_t

1
2
3
4
5
6
typedef struct {                             
uvm_reg_addr_t base_addr;
int n_bytes;
uvm_endianness_e endianess;
bit byte_addr_t;
} uvm_blk_config_t;

uvm_reg_field_config_ta

1
typedef uvm_reg_field_config_t uvm_reg_field_config_ta[];

uvm_reg_field_config_tq

1
typedef uvm_reg_field_config_ta uvm_reg_field_config_tq[string];

build_uvm_regs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function automatic void build_uvm_regs(uvm_reg_map map, uvm_reg_block pblock, uvm_reg_file pfile, ref uvm_reg_config_t f_props[], output uvm_reg out[$]);
foreach(f_props[idx]) begin
uvm_reg_config_t f= f_props[idx];
if(f.is_array == 0) begin
cdns_uvm_reg reg_t;
uvm_object obj = factory.create(f.reg_type_name, pfile!=null ? pfile.get_full_name(): "",f.name);
assert($cast(reg_t,obj));
reg_t.configure(pblock, pfile, "");
reg_t.setconfigUID(f.configUID);
reg_t.build();
// !!! f.offset !!!
map.add_reg(reg_t, f.offset, f.access);
out.push_back(reg_t);
// !!! print the offset
`uvm_info("[build_uvm_regs]", $sformatf("### %s offset: %0x", f.reg_type_name, f.offset), UVM_LOW)
end

else if (f.is_array == 1) begin
int unsigned array_offset;
string range_str = f.range;
int indx, arr_idx = 0;
int range_arr [];
int arr_size = 1;
  • dynamic array
    • need new[size]
  • queue
    • DONT need new
1
2
3
4
5
6
7
8
9
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### addr0_cnt_reg_c offset: 9                                 
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### addr1_cnt_reg_c offset: a
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### addr2_cnt_reg_c offset: b
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### illegal_addr_cnt_reg_c offset: 6
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### ctrl_reg_c offset: 0
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### en_reg_c offset: 1
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### mem_size_reg_c offset: d
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### oversized_pkt_cnt_reg_c offset: 5
UVM_INFO ./cdns_uvmreg_utils_pkg.sv(322) @ 0: reporter [[build_uvm_regs]] ### parity_err_cnt_reg_c offset: 4

In submap, offset is 0-based, router.add_submap(router_yapp_regs.default_map, ``UVM_REG_ADDR_WIDTH'h1000) configure base addr for submap

IP-XACT XML : yapp_router_regs.xml

image-20220404194550165

memory

yapp_pkt_mem

image-20220404194628354

yapp_mem

image-20220404194721416

register

image-20220404194941613

yapp_regs

ctrl_reg

image-20220404195052310

en_reg

image-20220404195156776

UVM Register Abstraction Layer (RAL) - overview

Within an UVM testbench a register model is used

  • either as a means of looking up a mirror of the current DUT hardware state

  • or as means of accessing the hardware via the front or back door

    and updating the register model database.

image-20220312125516500

Register Frontdoor Write

1
model.r0.write(status, value, [UVM_FRONTDOOR], .parent(this));
  • Sequence sets uvm_reg with value
  • uvm_reg content is translated into bus transaction
  • Driver gets bus transaction and writes DUT register
  • Mirror can be updated either implicitly or explicitly (predictor)

Register Backdoor Write

1
model.r0.write(status, value, UVM_BACKDOOR, .parent(this));

uvm_reg uses DPI/XMR to set DUT register with value

  • Physical interface is bypassed
  • Register behavior is mimicked

Register Backdoor Poke

1
model.r0.poke(status, value, .parent(this));

uvm_reg used DPI/XMR to set DUT register with value as is

  • Physical interface is bypassed
  • Register behavior is NOT mimicked

Register Frontdoor Read

1
model.r0.read(status, value, [UVM_FRONTDOOR], .parent(this));
  • Sequence executes uvm_reg READ
  • uvm_reg is translated into bus transaction
  • Driver gets bus transaction and read DUT register
  • Read value is translated into uvm_reg data and returned to sequence
  • Mirror updates

Register Backdoor Read

1
model.r0.read(status, value, UVM_BACKDOOR, .parent(this));

uvm_reg used DPI/XMR to get DUT register value

  • Physical interface is bypassed
  • Register behavior is mimicked (acc)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// EXECUTE READ...
case (rw.path)

// ...VIA USER BACKDOOR
UVM_BACKDOOR: begin
uvm_reg_backdoor bkdr = get_backdoor();

uvm_reg_map map = uvm_reg_map::backdoor();
if (map.get_check_on_read()) exp = get();

if (bkdr != null)
bkdr.read(rw);
else
backdoor_read(rw);

value = rw.value[0];

// Need to clear RC fields, set RS fields and mask WO fields
if (rw.status != UVM_NOT_OK) begin

uvm_reg_data_t wo_mask;

foreach (m_fields[i]) begin
string acc = m_fields[i].get_access(uvm_reg_map::backdoor());
if (acc == "RC" ||
acc == "WRC" ||
acc == "WSRC" ||
acc == "W1SRC" ||
acc == "W0SRC") begin
value &= ~(((1<<m_fields[i].get_n_bits())-1)
<< m_fields[i].get_lsb_pos());
end
else if (acc == "RS" ||
acc == "WRS" ||
acc == "WCRS" ||
acc == "W1CRS" ||
acc == "W0CRS") begin
value |= (((1<<m_fields[i].get_n_bits())-1)
<< m_fields[i].get_lsb_pos());
end
else if (acc == "WO" ||
acc == "WOC" ||
acc == "WOS" ||
acc == "WO1") begin
wo_mask |= ((1<<m_fields[i].get_n_bits())-1)
<< m_fields[i].get_lsb_pos();
end
end

if (value != rw.value[0]) begin
uvm_reg_data_t saved;
saved = rw.value[0];
rw.value[0] = value;
if (bkdr != null)
bkdr.write(rw);
else
backdoor_write(rw);
rw.value[0] = saved;
end

rw.value[0] &= ~wo_mask;

if (map.get_check_on_read() &&
rw.status != UVM_NOT_OK) begin
void'(do_check(exp, rw.value[0], map));
end

do_predict(rw, UVM_PREDICT_READ);
end
end

Register Backdoor Peek

1
model.r0.peek(status, value, .parent(this));

uvm_reg uses DPI/XMR to get DUT register value as is

  • Physical interface is bypassed
  • Register behavior is NOT mimicked
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
task uvm_reg::peek(output uvm_status_e      status,
output uvm_reg_data_t value,
input string kind = "",
input uvm_sequence_base parent = null,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);

uvm_reg_backdoor bkdr = get_backdoor();
uvm_reg_item rw;

m_fname = fname;
m_lineno = lineno;

// create an abstract transaction for this operation
rw = uvm_reg_item::type_id::create("mem_peek_item",,get_full_name());
rw.element = this;
rw.path = UVM_BACKDOOR;
rw.element_kind = UVM_REG;
rw.kind = UVM_READ;
rw.bd_kind = kind;
rw.parent = parent;
rw.extension = extension;
rw.fname = fname;
rw.lineno = lineno;

do_predict(rw, UVM_PREDICT_READ);

endtask: peek

Methods to handle property of uvm_reg or uvm_reg_block

mirror

1
2
model.mirror(status, [check], [path], .parent(this));
model.r0.mirro(status, [check], [path], .parent(this));

Update mirrored and desired properties with DUT content

set

1
model.r0.set(value);

Set value in desired properties

randomize

1
2
model.randomize();
model.r0.randomize();

Populate desired property with random value

get

1
value = model.r0.get();

Get value from desired property

update

1
2
model.update(status, [path], .parent(this));
model.r0.update(status, [path], .parent(this));

Update DUT and mirrored property with desired property if mirrored property is different from desired

predict

1
model.r0.predict(value);

Set the value of mirrored property

get_mirrored_value

1
value = model.r0.get_mirrored_value();

Get value from mirrored property

Backdoor Access

  • Two ways to generate the backdoor access:
    • Via SystemVerilog Cross Module Reference (XMR)
    • Via SystemVerilog DPI call
  • Both allow register model to be part of SystemVerilog package
  • XMR implementation is faster
    • Requires user to compile one additional file and at compile-time provide top level path to DUT
    • VCS only
  • DPI implementation is slower
    • No additional file is needed and top level path can be provided at run-time
    • Portable to other simulators

UVM Register Classes: uvm_reg_bus_op & uvm_reg_item

The generic register item is implemented as a struct in order to minimise the amount of memory resource it uses. The struct is defined as type uvm_reg_bus_op and this contains 6 fields:

Property Type Comment/Description
addr uvm_reg_addr_t Address field, defaults to 64 bits
data uvm_reg_data_t Read or write data, defaults to 64 bits
kind uvm_access_e UVM_READ or UVM_WRITE
n_bits unsigned int Number of bits being transferred
byte_en uvm_reg_byte_en_t Byte enable
status uvm_status_e UVM_IS_OK, UVM_IS_X, UVM_NOT_OK
1
2
3
4
5
6
7
8
9
typedef struct {
uvm_access_e kind; // Kind of access: READ or WRITE.
uvm_reg_addr_t addr; // The bus address.
uvm_reg_data_t data; // The data to write.
// The number of bits of <uvm_reg_item::value> being transferred by this transaction.
int n_bits;
uvm_reg_byte_en_t byte_en; // Enables for the byte lanes on the bus.
uvm_status_e status; // The result of the transaction: UVM_IS_OK, UVM_HAS_X, UVM_NOT_OK.
} uvm_reg_bus_op;
1
2
3
4
5
6
7
class uvm_reg_item extends uvm_sequence_item;
rand uvm_access_e kind;
rand uvm_reg_data_t value[];
rand uvm_reg_addr_t offset;
uvm_status_e status;
uvm_reg_map map;
endclass

Registers

The register class contains a build method which is used to create and configure the fields.

this build method is not called by the UVM build_phase, since the register is an uvm_object rather than an uvm_component

1
2
3
4
5
6
//
// uvm_reg constructor prototype:
//
function new (string name="", // Register name
int unsigned n_bits, // Register width in bits
int has_coverage); // Coverage model supported by the register
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Function: new
//
function new(string name = "ctrl_reg");
super.new(name, 32, build_coverage(UVM_CVR_FIELD_VALS));
add_coverage(build_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS))
cg_vals = new();
endfunction


// Function: sample_values
//
virtual function void sample_values();
super.sample_values();
if (get_coverage(UVM_CVR_FIELD_VALS))
cg_vals.sample();
endfunction


// Function: build
//
virtual function void build();
ass = uvm_reg_field::type_id::create("ass");
ie = uvm_reg_field::type_id::create("ie");
lsb = uvm_reg_field::type_id::create("lsb");
tx_neg = uvm_reg_field::type_id::create("tx_neg");
rx_neg = uvm_reg_field::type_id::create("rx_neg");
go_bsy = uvm_reg_field::type_id::create("go_bsy");
reserved2 = uvm_reg_field::type_id::create("reserved2");
char_len = uvm_reg_field::type_id::create("char_len");

ass.configure(this, 1, 13, "RW", 0, 1'b0, 1, 1, 0);
ie.configure(this, 1, 12, "RW", 0, 1'b0, 1, 1, 0);
lsb.configure(this, 1, 11, "RW", 0, 1'b0, 1, 1, 0);
tx_neg.configure(this, 1, 10, "RW", 0, 1'b0, 1, 1, 0);
rx_neg.configure(this, 1, 9, "RW", 0, 1'b0, 1, 1, 0);
go_bsy.configure(this, 1, 8, "RW", 0, 1'b0, 1, 1, 0);
reserved2.configure(this, 1, 7, "RW", 0, 1'b0, 1, 1, 0);
char_len.configure(this, 7, 0, "RW", 0, 7'b0000000, 1, 1, 0);
endfunction

As shown above, Register width is 32 same with the bus width, lower 14 bit is configured.

RTL

1
2
`define SPI_CTRL_BIT_NB         14
reg [`SPI_CTRL_BIT_NB-1:0] ctrl; // Control and status register

Register Maps

Two purpose of the register map

  • provide information on the offset of the registers, memories and/or register blocks
  • identify bus agent based sequences to be executed ???

There can be several register maps within a block, each one can specify a different address map and a different target bus agent

register map has to be created which the register block using the create_map method

1
2
3
4
5
6
7
8
9
10
11
12
13
//
// Prototype for the create_map method
//
function uvm_reg_map create_map(string name, // Name of the map handle
uvm_reg_addr_t base_addr, // The maps base address
int unsigned n_bytes, // Map access width in bytes
uvm_endianness_e endian, // The endianess of the map
bit byte_addressing=1); // Whether byte_addressing is supported

//
// Example:
//
AHB_map = create_map("AHB_map", 'h0, 4, UVM_LITTLE_ENDIAN);
  • The n_bytes parameter is the word size (bus width) of the bus to which the map is associated. If a register's width exceeds the bus width, more than one bus access is needed to read and write that register over that bus.

  • he byte_addressing argument affects how the address is incremented in these consecutive accesses. For example, if n_bytes=4 and byte_addressing=0, then an access to a register that is 64-bits wide and at offset 0 will result in two bus accesses at addresses 0 and 1. With byte_addressing=1, that same access will result in two bus accesses at addresses 0 and 4.

    The default for byte_addressing is 1

  • The first map to be created within a register block is assigned to the default_map member of the register block

byte_addressing.drawio

Register Adapter

uvm_reg_adapter
Methods Description
reg2bus Overload to convert generic register access items to target bus agent sequence items
bus2reg Overload to convert target bus sequence items to register model items
Properties (Of type bit) Description
supports_byte_enable Set to 1 if the target bus and the target bus agent supports byte enables, else set to 0
provides_responses Set to 1 if the target agent driver sends separate response sequence_items that require response handling

The provides_responses bit should be set if the agent driver returns a separate response item (i.e. put(response), or item_done(response)) from its request item

Prediction

the update, or prediction, of the register model content can occur using one of three models

Auto Prediction

This mode of operation is the simplest to implement, but suffers from the drawback that it can only keep the register model up to date with the transfers that it initiates. If any other sequences directly access the target sequencer to update register content, or if there are register accesses from other DUT interfaces, then the register model will not be updated.

1
2
3
4
function void uvm_reg_map::set_auto_predict(bit on=1); m_auto_predict = on; endfunction

// Gets the auto-predict mode setting for this map.
function bit uvm_reg_map::get_auto_predict(); return m_auto_predict; endfunction

// Function: set_auto_predict

//

// Sets the auto-predict mode for his map.

//

// When on is TRUE,

// the register model will automatically update its mirror (what it thinks should be in the DUT)

immediately after any bus read or write operation via this map. Before a uvm_reg::write

// or uvm_reg::read operation returns, the register's uvm_reg::predict method is called to update

the mirrored value in the register.

//

// When on is FALSE, bus reads and writes via this map do not

// automatically update the mirror. For real-time updates to the mirror

// in this mode, you connect a uvm_reg_predictor instance to the bus

// monitor. The predictor takes observed bus transactions from the

// bus monitor, looks up the associated uvm_reg register given

// the address, then calls that register's uvm_reg::predict method.

// While more complex, this mode will capture all register read/write

// activity, including that not directly descendant from calls to

// uvm_reg::write and uvm_reg::read.

//

// By default, auto-prediction is turned off.

//

Reg auto predict.gif

The register model content is updated based on the register accesses it initiates

Explicit prediction is the default mode of prediction

Reg explicit prediction.gif

The register model content is updated via the predictor component based on all observed bus transactions, ensuring that register accesses made without the register model are mirrored correctly. The predictor looks up the accessed register by address then calls its predict() method

uvm_reg::predict & uvm_reg::do_predict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// predict
function bit uvm_reg::predict (uvm_reg_data_t value,
uvm_reg_byte_en_t be = -1,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_path_e path = UVM_FRONTDOOR,
uvm_reg_map map = null,
string fname = "",
int lineno = 0);
uvm_reg_item rw = new;
rw.value[0] = value;
rw.path = path;
rw.map = map;
rw.fname = fname;
rw.lineno = lineno;
do_predict(rw, kind, be);
predict = (rw.status == UVM_NOT_OK) ? 0 : 1;
endfunction: predict


// do_predict
function void uvm_reg::do_predict(uvm_reg_item rw,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_reg_byte_en_t be = -1);

uvm_reg_data_t reg_value = rw.value[0];
m_fname = rw.fname;
m_lineno = rw.lineno;

if (rw.status ==UVM_IS_OK )
rw.status = UVM_IS_OK;

if (m_is_busy && kind == UVM_PREDICT_DIRECT) begin
`uvm_warning("RegModel", {"Trying to predict value of register '",
get_full_name(),"' while it is being accessed"})
rw.status = UVM_NOT_OK;
return;
end

foreach (m_fields[i]) begin
rw.value[0] = (reg_value >> m_fields[i].get_lsb_pos()) &
((1 << m_fields[i].get_n_bits())-1);
m_fields[i].do_predict(rw, kind, be>>(m_fields[i].get_lsb_pos()/8));
end

rw.value[0] = reg_value;

endfunction: do_predict
uvm_reg_field::predict & uvm_reg_field::do_predict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// predict

function bit uvm_reg_field::predict (uvm_reg_data_t value,
uvm_reg_byte_en_t be = -1,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_path_e path = UVM_FRONTDOOR,
uvm_reg_map map = null,
string fname = "",
int lineno = 0);
uvm_reg_item rw = new;
rw.value[0] = value;
rw.path = path;
rw.map = map;
rw.fname = fname;
rw.lineno = lineno;
do_predict(rw, kind, be);
predict = (rw.status == UVM_NOT_OK) ? 0 : 1;
endfunction: predict


// do_predict

function void uvm_reg_field::do_predict(uvm_reg_item rw,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_reg_byte_en_t be = -1);

uvm_reg_data_t field_val = rw.value[0] & ((1 << m_size)-1);

if (rw.status != UVM_NOT_OK)
rw.status = UVM_IS_OK;

// Assume that the entire field is enabled
if (!be[0])
return;

m_fname = rw.fname;
m_lineno = rw.lineno;

case (kind)

UVM_PREDICT_WRITE:
begin
uvm_reg_field_cb_iter cbs = new(this);

if (rw.path == UVM_FRONTDOOR || rw.path == UVM_PREDICT)
field_val = XpredictX(m_mirrored, field_val, rw.map);

m_written = 1;

for (uvm_reg_cbs cb = cbs.first(); cb != null; cb = cbs.next())
cb.post_predict(this, m_mirrored, field_val,
UVM_PREDICT_WRITE, rw.path, rw.map);

field_val &= ('b1 << m_size)-1;

end

UVM_PREDICT_READ:
begin
uvm_reg_field_cb_iter cbs = new(this);

if (rw.path == UVM_FRONTDOOR || rw.path == UVM_PREDICT) begin

string acc = get_access(rw.map);

if (acc == "RC" ||
acc == "WRC" ||
acc == "WSRC" ||
acc == "W1SRC" ||
acc == "W0SRC")
field_val = 0; // (clear)

else if (acc == "RS" ||
acc == "WRS" ||
acc == "WCRS" ||
acc == "W1CRS" ||
acc == "W0CRS")
field_val = ('b1 << m_size)-1; // all 1's (set)

else if (acc == "WO" ||
acc == "WOC" ||
acc == "WOS" ||
acc == "WO1" ||
acc == "NOACCESS")
return;
end

for (uvm_reg_cbs cb = cbs.first(); cb != null; cb = cbs.next())
cb.post_predict(this, m_mirrored, field_val,
UVM_PREDICT_READ, rw.path, rw.map);

field_val &= ('b1 << m_size)-1;

end

UVM_PREDICT_DIRECT:
begin
if (m_parent.is_busy()) begin
`uvm_warning("RegModel", {"Trying to predict value of field '",
get_name(),"' while register '",m_parent.get_full_name(),
"' is being accessed"})
rw.status = UVM_NOT_OK;
end
end
endcase

// update the mirror with predicted value
m_mirrored = field_val;
m_desired = field_val;
this.value = field_val;

endfunction: do_predict
uvm_access_e
1
2
3
4
5
6
7
8
9
10
11
12
13
// Enum: uvm_access_e
//
// Type of operation begin performed
//
// UVM_READ - Read operation
// UVM_WRITE - Write operation
//
typedef enum {
UVM_READ,
UVM_WRITE,
UVM_BURST_READ,
UVM_BURST_WRITE
} uvm_access_e;
uvm_predict_e
1
2
3
4
5
6
7
8
9
10
11
12
13
// Enum: uvm_predict_e
//
// How the mirror is to be updated
//
// UVM_PREDICT_DIRECT - Predicted value is as-is
// UVM_PREDICT_READ - Predict based on the specified value having been read
// UVM_PREDICT_WRITE - Predict based on the specified value having been written
//
typedef enum {
UVM_PREDICT_DIRECT,
UVM_PREDICT_READ,
UVM_PREDICT_WRITE
} uvm_predict_e;
uvm_path_e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Enum: uvm_path_e
//
// Path used for register operation
//
// UVM_FRONTDOOR - Use the front door
// UVM_BACKDOOR - Use the back door
// UVM_PREDICT - Operation derived from observations by a bus monitor via
// the <uvm_reg_predictor> class.
// UVM_DEFAULT_PATH - Operation specified by the context
//
typedef enum {
UVM_FRONTDOOR,
UVM_BACKDOOR,
UVM_PREDICT,
UVM_DEFAULT_PATH
} uvm_path_e;

Passive Prediction

UVM Register Abstraction Layer (RAL) - source code reading

Field Access Policies

image-20220526092125583

whether a register field can be read or written depends on both the field's configured access policy and the register's rights in the map being used to access the field

http://www.verilab.com/files/litterick_register_final.pdf

https://www.verilab.com/files/litterick_register_slides_sm.pdf

uvm_reg::write

  • If a back-door access path is used, the effect of writing the register through a physical access is mimicked. For example, read-only bits in the registers will not be written.

  • The mirrored value will be updated using the uvm_reg::predict() method.

1
2
3
4
5
6
7
8
9
10
11
extern virtual task write(output uvm_status_e      status,
input uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);

extern virtual task do_write(uvm_reg_item rw);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
task uvm_reg::write(output uvm_status_e      status,
input uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);

// create an abstract transaction for this operation
uvm_reg_item rw;

XatomicX(1);

set(value);

rw = uvm_reg_item::type_id::create("write_item",,get_full_name());
rw.element = this;
rw.element_kind = UVM_REG;
rw.kind = UVM_WRITE;
rw.value[0] = value;
rw.path = path;
rw.map = map;
rw.parent = parent;
rw.prior = prior;
rw.extension = extension;
rw.fname = fname;
rw.lineno = lineno;

do_write(rw);

status = rw.status;

XatomicX(0);

endtask

uvm_reg::read

  • If a back-door access path is used, the effect of reading the register through a physical access is mimicked. For example, clear-on-read bits in the registers will be set to zero.

  • The mirrored value will be updated using the uvm_reg::predict() method.

1
2
3
4
5
6
7
8
9
10
11
extern virtual task read(output uvm_status_e      status,
output uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);

extern virtual task do_read(uvm_reg_item rw);

uvmreg_read.drawio

readback value can be different from m_mirrored , m_desired or value

uvm_reg::poke

  • Deposit the value in the DUT register corresponding to this abstraction class instance, as-is, using a back-door access.

  • Uses the HDL path for the design abstraction specified by kind.

  • The mirrored value will be updated using the uvm_reg::predict() method.

1
2
3
4
5
6
7
extern virtual task poke(output uvm_status_e      status,
input uvm_reg_data_t value,
input string kind = "",
input uvm_sequence_base parent = null,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);

uvm_reg::peek

  • Sample the value in the DUT register corresponding to this abstraction class instance using a back-door access. The register value is sampled, not modified.
  • Uses the HDL path for the design abstraction specified by kind.
  • The mirrored value will be updated using the uvm_reg::predict() method.
1
2
3
4
5
6
7
extern virtual task peek(output uvm_status_e      status,
output uvm_reg_data_t value,
input string kind = "",
input uvm_sequence_base parent = null,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);

uvm_reg::mirror

  • Read the register and optionally compared the readback value with the current mirrored value if check is UVM_CHECK.

  • The mirrored value will be updated using the uvm_reg::predict() method based on the readback value.

  • The mirroring can be performed using the physical interfaces (frontdoor) or uvm_reg::peek() (backdoor).

  • If the register contains write-only fields, their content is mirrored and optionally checked only if a UVM_BACKDOOR access path is used to read the register.

1
2
3
4
5
6
7
8
9
extern virtual task mirror(output uvm_status_e      status,
input uvm_check_e check = UVM_NO_CHECK,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);

uvm_reg::update

  • Write this register if the DUT register is out-of-date with the desired/mirrored value in the abstraction class, as determined by the uvm_reg::needs_update() method.
  • The update can be performed using the using the physical interfaces (frontdoor) or uvm_reg::poke() (backdoor) access.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extern virtual task update(output uvm_status_e      status,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);


function bit uvm_reg::needs_update();
needs_update = 0;
foreach (m_fields[i]) begin
if (m_fields[i].needs_update()) begin
return 1;
end
end
endfunction: needs_update
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
task uvm_reg::update(output uvm_status_e      status,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);
uvm_reg_data_t upd;

status = UVM_IS_OK;

if (!needs_update()) return;

// Concatenate the write-to-update values from each field
// Fields are stored in LSB or MSB order
upd = 0;
foreach (m_fields[i])
upd |= m_fields[i].XupdateX() << m_fields[i].get_lsb_pos();

write(status, upd, path, map, parent, prior, extension, fname, lineno);
endtask: update

uvm_reg::predict

  • Update the mirrored and desired value for this register.
  • Predict the mirror (and desired) value of the fields in the register based on the specified observed value on a specified address map, or based on a calculated value.
  • See uvm_reg_field::predict() for more details.
1
2
3
4
5
6
7
8
9
10
11
12
13
extern virtual function bit predict (uvm_reg_data_t    value,
uvm_reg_byte_en_t be = -1,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_path_e path = UVM_FRONTDOOR,
uvm_reg_map map = null,
string fname = "",
int lineno = 0);

extern virtual function void do_predict
(uvm_reg_item rw,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_reg_byte_en_t be = -1);

uvm_reg::do_predict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function void uvm_reg::do_predict(uvm_reg_item      rw,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_reg_byte_en_t be = -1);

uvm_reg_data_t reg_value = rw.value[0];
m_fname = rw.fname;
m_lineno = rw.lineno;

if (rw.status ==UVM_IS_OK )
rw.status = UVM_IS_OK;

if (m_is_busy && kind == UVM_PREDICT_DIRECT) begin
`uvm_warning("RegModel", {"Trying to predict value of register '",
get_full_name(),"' while it is being accessed"})
rw.status = UVM_NOT_OK;
return;
end

foreach (m_fields[i]) begin
rw.value[0] = (reg_value >> m_fields[i].get_lsb_pos()) &
((1 << m_fields[i].get_n_bits())-1);
m_fields[i].do_predict(rw, kind, be>>(m_fields[i].get_lsb_pos()/8));
end

rw.value[0] = reg_value;

endfunction: do_predict

uvm_reg_field::do_predict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
function void uvm_reg_field::do_predict(uvm_reg_item      rw,
uvm_predict_e kind = UVM_PREDICT_DIRECT,
uvm_reg_byte_en_t be = -1);

uvm_reg_data_t field_val = rw.value[0] & ((1 << m_size)-1);

if (rw.status != UVM_NOT_OK)
rw.status = UVM_IS_OK;

// Assume that the entire field is enabled
if (!be[0])
return;

m_fname = rw.fname;
m_lineno = rw.lineno;

case (kind)

UVM_PREDICT_WRITE:
begin
uvm_reg_field_cb_iter cbs = new(this);

if (rw.path == UVM_FRONTDOOR || rw.path == UVM_PREDICT)
field_val = XpredictX(m_mirrored, field_val, rw.map);

m_written = 1;

for (uvm_reg_cbs cb = cbs.first(); cb != null; cb = cbs.next())
cb.post_predict(this, m_mirrored, field_val,
UVM_PREDICT_WRITE, rw.path, rw.map);

field_val &= ('b1 << m_size)-1;

end

UVM_PREDICT_READ:
begin
uvm_reg_field_cb_iter cbs = new(this);

if (rw.path == UVM_FRONTDOOR || rw.path == UVM_PREDICT) begin

string acc = get_access(rw.map);

if (acc == "RC" ||
acc == "WRC" ||
acc == "WSRC" ||
acc == "W1SRC" ||
acc == "W0SRC")
field_val = 0; // (clear)

else if (acc == "RS" ||
acc == "WRS" ||
acc == "WCRS" ||
acc == "W1CRS" ||
acc == "W0CRS")
field_val = ('b1 << m_size)-1; // all 1's (set)

else if (acc == "WO" ||
acc == "WOC" ||
acc == "WOS" ||
acc == "WO1" ||
acc == "NOACCESS")
return;
end

for (uvm_reg_cbs cb = cbs.first(); cb != null; cb = cbs.next())
cb.post_predict(this, m_mirrored, field_val,
UVM_PREDICT_READ, rw.path, rw.map);

field_val &= ('b1 << m_size)-1;

end

UVM_PREDICT_DIRECT:
begin
if (m_parent.is_busy()) begin
`uvm_warning("RegModel", {"Trying to predict value of field '",
get_name(),"' while register '",m_parent.get_full_name(),
"' is being accessed"})
rw.status = UVM_NOT_OK;
end
end
endcase

// update the mirror with predicted value
m_mirrored = field_val;
m_desired = field_val;
this.value = field_val;

endfunction: do_predict

uvm_reg_field::XpredictX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function uvm_reg_data_t uvm_reg_field::XpredictX (uvm_reg_data_t cur_val,
uvm_reg_data_t wr_val,
uvm_reg_map map);
uvm_reg_data_t mask = ('b1 << m_size)-1;

case (get_access(map))
"RO": return cur_val;
"RW": return wr_val;
"RC": return cur_val;
"RS": return cur_val;
"WC": return '0;
"WS": return mask;
"WRC": return wr_val;
"WRS": return wr_val;
"WSRC": return mask;
"WCRS": return '0;
"W1C": return cur_val & (~wr_val);
"W1S": return cur_val | wr_val;
"W1T": return cur_val ^ wr_val;
"W0C": return cur_val & wr_val;
"W0S": return cur_val | (~wr_val & mask);
"W0T": return cur_val ^ (~wr_val & mask);
"W1SRC": return cur_val | wr_val;
"W1CRS": return cur_val & (~wr_val);
"W0SRC": return cur_val | (~wr_val & mask);
"W0CRS": return cur_val & wr_val;
"WO": return wr_val;
"WOC": return '0;
"WOS": return mask;
"W1": return (m_written) ? cur_val : wr_val;
"WO1": return (m_written) ? cur_val : wr_val;
"NOACCESS": return cur_val;
default: return wr_val;
endcase

`uvm_fatal("RegModel", "uvm_reg_field::XpredictX(): Internal error");
return 0;
endfunction: XpredictX

uvm_reg::reset , uvm_reg_field::reset

Resetting a register model sets the mirror to the reset value specified in the model

uvm_reg::reset

1
2
3
4
5
6
7
8
9
10
function void uvm_reg::reset(string kind = "HARD");
foreach (m_fields[i])
m_fields[i].reset(kind);
// Put back a key in the semaphore if it is checked out
// in case a thread was killed during an operation
void'(m_atomic.try_get(1));
m_atomic.put(1);
m_process = null;
Xset_busyX(0);
endfunction: reset

uvm_reg_field::reset

1
2
3
4
5
6
7
8
9
10
11
12
13
function void uvm_reg_field::reset(string kind = "HARD");

if (!m_reset.exists(kind))
return;

m_mirrored = m_reset[kind];
m_desired = m_mirrored;
value = m_mirrored;

if (kind == "HARD")
m_written = 0;

endfunction: reset

uvm_reg_field::randomize

uvm_reg_field::pre_randomize()

Update the only publicly known property value with the current desired value so it can be used as a state variable should the rand_mode of the field be turned off.

value is m_desired if rand_mode is off.

1
2
3
function void uvm_reg_field::pre_randomize();
value = m_desired;
endfunction: pre_randomize

uvm_reg_field::post_randomize

1
2
3
function void uvm_reg_field::post_randomize();
m_desired = value;
endfunction: post_randomize

misc

1
typedef  bit unsigned [`UVM_REG_DATA_WIDTH-1:0]  uvm_reg_data_t ;
1
2
3
4
5
6
7
8
9
10
11
12
13
// Enum: uvm_predict_e
//
// How the mirror is to be updated
//
// UVM_PREDICT_DIRECT - Predicted value is as-is
// UVM_PREDICT_READ - Predict based on the specified value having been read
// UVM_PREDICT_WRITE - Predict based on the specified value having been written
//
typedef enum {
UVM_PREDICT_DIRECT,
UVM_PREDICT_READ,
UVM_PREDICT_WRITE
} uvm_predict_e;

UVM REG RALF & IP-XACT

UVM Register Abstraction Layer Generator User Guide, S-2021.09-SP1, December 2021

User guide for the IEEE 1685 Standard for IP-XACT

uvm_reg_field

There are no properties for unused or reserved fields, and unlike register arrays

ralf

  • bytes: the register size, default is N*8 > all field
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
register CTRL {
left_to_right;
field unused {bits 6;}
field HC1R {
bits 1; access rw ;
coverpoint { bins x = {0,1}}
}
field HC1E {bits 1 ; access rw }
field unused {bits 2; }
field HC0R {bits 1; access rw }
field HCOE {bits 1 ; access rw }
field unused {bits 2; }
field BL {bits 1; access rw }
field CSL {bits 1 ; access rw}
field VSL {bits 1 ; access rw}
field HSL {bits 1 ; access rw}
field PC {bits 1 ; access rw}
field CD {bits 2 ; access rw}
field VBL {bits 2 ; access rw}
field CBSWE {bits 1 ; access rw}
field VBSWE {bits 1 ; access rw}
field CBSIE {bits 1 ; access rw}
field VBSIE {bits 1 ; access rw}
field HIE {bits 1 ; access rw}
field VIE {bits 1 ; access rw}
field VEN {bits 1 ; access rw}
}

generated sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function void build();
this.HC1R = uvm_reg_field::type_id::create("HC1R",,get_full_name());
this.HC1R.configure(this, 1, 23, "RW", 0, 1'h0, 0, 0, 0);
this.HC1E = uvm_reg_field::type_id::create("HC1E",,get_full_name());
this.HC1E.configure(this, 1, 22, "RW", 0, 1'h0, 0, 0, 0);
this.HC0R = uvm_reg_field::type_id::create("HC0R",,get_full_name());
this.HC0R.configure(this, 1, 19, "RW", 0, 1'h0, 0, 0, 0);
this.HCOE = uvm_reg_field::type_id::create("HCOE",,get_full_name());
this.HCOE.configure(this, 1, 18, "RW", 0, 1'h0, 0, 0, 0);
this.BL = uvm_reg_field::type_id::create("BL",,get_full_name());
this.BL.configure(this, 1, 15, "RW", 0, 1'h0, 0, 0, 0);
this.CSL = uvm_reg_field::type_id::create("CSL",,get_full_name());
this.CSL.configure(this, 1, 14, "RW", 0, 1'h0, 0, 0, 0);
this.VSL = uvm_reg_field::type_id::create("VSL",,get_full_name());
this.VSL.configure(this, 1, 13, "RW", 0, 1'h0, 0, 0, 0);
this.HSL = uvm_reg_field::type_id::create("HSL",,get_full_name());
this.HSL.configure(this, 1, 12, "RW", 0, 1'h0, 0, 0, 0);
this.PC = uvm_reg_field::type_id::create("PC",,get_full_name());
this.PC.configure(this, 1, 11, "RW", 0, 1'h0, 0, 0, 0);
this.CD = uvm_reg_field::type_id::create("CD",,get_full_name());
this.CD.configure(this, 2, 9, "RW", 0, 2'h0, 0, 0, 0);
this.VBL = uvm_reg_field::type_id::create("VBL",,get_full_name());
this.VBL.configure(this, 2, 7, "RW", 0, 2'h0, 0, 0, 0);
this.CBSWE = uvm_reg_field::type_id::create("CBSWE",,get_full_name());
this.CBSWE.configure(this, 1, 6, "RW", 0, 1'h0, 0, 0, 0);
this.VBSWE = uvm_reg_field::type_id::create("VBSWE",,get_full_name());
this.VBSWE.configure(this, 1, 5, "RW", 0, 1'h0, 0, 0, 0);
this.CBSIE = uvm_reg_field::type_id::create("CBSIE",,get_full_name());
this.CBSIE.configure(this, 1, 4, "RW", 0, 1'h0, 0, 0, 0);
this.VBSIE = uvm_reg_field::type_id::create("VBSIE",,get_full_name());
this.VBSIE.configure(this, 1, 3, "RW", 0, 1'h0, 0, 0, 0);
this.HIE = uvm_reg_field::type_id::create("HIE",,get_full_name());
this.HIE.configure(this, 1, 2, "RW", 0, 1'h0, 0, 0, 0);
this.VIE = uvm_reg_field::type_id::create("VIE",,get_full_name());
this.VIE.configure(this, 1, 1, "RW", 0, 1'h0, 0, 0, 0);
this.VEN = uvm_reg_field::type_id::create("VEN",,get_full_name());
this.VEN.configure(this, 1, 0, "RW", 0, 1'h0, 0, 0, 0);
endfunction: build

uvm_reg_block

ralf

  • bytes : bus width
1
2
3
4
5
6
7
8
9
10
11
block vga_lcd  {
bytes 4;
endian little;

register STAT (`RAL_HDL_PATH.wbs.stat[31:0]) @0004;
register HTIM (`RAL_HDL_PATH.htim[31:0]) @0008;
register CTRL (`RAL_HDL_PATH.wbs.ctrl[31:0]) @00010;
register VTIM (`RAL_HDL_PATH.vtim[31:0]) @0012;
register C1CR (`RAL_HDL_PATH.c1cr[31:0]) @0016;
memory CLUT1 @'h0C00;
}

generated sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);

this.STAT = ral_reg_STAT::type_id::create("STAT",,get_full_name());
this.STAT.configure(this, null, "");
this.STAT.build();
this.STAT.add_hdl_path('{ '{"wbs.stat[31:0]", -1, -1}});
this.default_map.add_reg(this.STAT, `UVM_REG_ADDR_WIDTH'h4, "RW", 0);

this.HTIM = ral_reg_HTIM::type_id::create("HTIM",,get_full_name());
this.HTIM.configure(this, null, "");
this.HTIM.build();
this.HTIM.add_hdl_path('{'{"htim[31:0]", -1, -1} });
this.default_map.add_reg(this.HTIM, `UVM_REG_ADDR_WIDTH'h8, "RW", 0);

this.CTRL = ral_reg_CTRL::type_id::create("CTRL",,get_full_name());
this.CTRL.configure(this, null, "");
this.CTRL.build();
this.CTRL.add_hdl_path('{'{"wbs.ctrl[31:0]", -1, -1} });
this.default_map.add_reg(this.CTRL, `UVM_REG_ADDR_WIDTH'hA, "RW", 0);

this.VTIM = ral_reg_VTIM::type_id::create("VTIM",,get_full_name());
this.VTIM.configure(this, null, "");
this.VTIM.build();
this.VTIM.add_hdl_path('{ '{"vtim[31:0]", -1, -1} });
this.default_map.add_reg(this.VTIM, `UVM_REG_ADDR_WIDTH'hC, "RW", 0);

this.C1CR = ral_reg_C1CR::type_id::create("C1CR",,get_full_name());
this.C1CR.configure(this, null, "");
this.C1CR.build();
this.C1CR.add_hdl_path('{ '{"c1cr[31:0]", -1, -1} });
this.default_map.add_reg(this.C1CR, `UVM_REG_ADDR_WIDTH'h10, "RW", 0);

this.CLUT1 = ral_mem_CLUT1::type_id::create("CLUT1",,get_full_name());
this.CLUT1.configure(this, "");
this.CLUT1.build();
this.default_map.add_mem(this.CLUT1, `UVM_REG_ADDR_WIDTH'hC00, "RW", 0);

1
2
3
4
5
6
7
class ral_reg_CTRL extends uvm_reg;
class ral_reg_STAT extends uvm_reg;
class ral_reg_HTIM extends uvm_reg;
class ral_reg_VTIM extends uvm_reg;
class ral_reg_C1CR extends uvm_reg;
class ral_mem_CLUT1 extends uvm_mem;
class ral_block_vga_lcd extends uvm_reg_block;

ralgen command

1
ralgen -uvm -t dut_regmodel0 vga_lcd_env.ralf

BYTE or HALFWORD access

User is verifying 32 bit registers and the design also allows the BYTE (8 bits) and HALFWORD (16 bits) accesses.

this is achieved by setting the bit_addressing=0 field in the uvm_reg_block::create_map function.

Using create_map in uvm_reg_block, you can change the type of addressing scheme you want to use; namely BYTE or HALFWORD.

create_map

1
2
3
4
5
virtual function uvm_reg_map create_map( string name,
uvm_reg_addr_t base_addr,
int unsigned n_bytes,
uvm_endianness_e endian,
bit byte_addressing)

Creates an address map with the specified name, then configures it with the following properties:

Parameter Description
base_addr It is the base address for the map. All registers, memories, and sub-blocks within the map will be at offsets to this address.
n_bytes It is the byte-width of the bus on which this map is used
endian It is the endian format. See uvm_endianness_e for possible values.
byte_addressing It specifies whether consecutive addresses referred are 1 byte apart (TRUE) or n_bytes apart (FALSE). Default is TRUE.
  • For HALFWORD addressing, you should call create_map the following way:
1
default_map = create_map(get_name(), 0, 2, UVM_LITTLE_ENDIAN, 0); // 32 bit registers offset are 0x00, 0x02, 0x04
  • For WORD addressing
1
default_map = create_map(get_name(), 0, 4, UVM_LITTLE_ENDIAN, 0); // 32 bit registers offset are 0x00, 0x01, 0x02
  • For BYTE addressing (default) :
1
default_map = create_map(get_name(), 0, 4, UVM_LITTLE_ENDIAN, 1); // 32 bit registers offset are 0x00, 0x04, 0x08
  • BYTE width and byte addressing
1
default_map = create_map(get_name(), 0, 1, UVM_LITTLE_ENDIAN, 1); // 32 bit registers offset are 0x00, 0x04, 0x08 

uvm_reg_block::create_map

Create an address map in this block

n_bytes - the byte-width of the bus on which this map is used

byte_addressing - specifies whether consecutive addresses refer are 1 byte apart (TRUE) or n_bytes apart (FALSE). Default is TRUE.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function uvm_reg_map uvm_reg_block::create_map(string name,
uvm_reg_addr_t base_addr,
int unsigned n_bytes,
uvm_endianness_e endian,
bit byte_addressing=1);

uvm_reg_map map;

if (this.locked) begin
`uvm_error("RegModel", "Cannot add map to locked model");
return null;
end

map = uvm_reg_map::type_id::create(name,,this.get_full_name());
map.configure(this,base_addr,n_bytes,endian,byte_addressing);

this.maps[map] = 1;
if (maps.num() == 1)
default_map = map;

return map;
endfunction

uvm_reg_map::add_reg

The register is located at the specified address offset from this maps configured base address.

The number of consecutive physical addresses occupied by the register depends on the width of the register and the number of bytes in the physical interface corresponding to this address map.

If unmapped is TRUE, the register does not occupy any physical addresses and the base address is ignored. Unmapped registers require a user-defined frontdoor to be specified.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function void uvm_reg_map::add_reg(uvm_reg rg,  
uvm_reg_addr_t offset,
string rights = "RW",
bit unmapped=0,
uvm_reg_frontdoor frontdoor=null);

if (m_regs_info.exists(rg)) begin
`uvm_error("RegModel", {"Register '",rg.get_name(),
"' has already been added to map '",get_name(),"'"})
return;
end

if (rg.get_parent() != get_parent()) begin
`uvm_error("RegModel",
{"Register '",rg.get_full_name(),"' may not be added to address map '",
get_full_name(),"' : they are not in the same block"})
return;
end

rg.add_map(this);

begin
uvm_reg_map_info info = new;
info.offset = offset;
info.rights = rights;
info.unmapped = unmapped;
info.frontdoor = frontdoor;
m_regs_info[rg] = info;
end
endfunction

Register Defines

`UVM_REG_ADDR_WIDTH

Maximum address width in bits

Default value is 64. Used to define the type.

1
2
3
`ifndef UVM_REG_ADDR_WIDTH
`define UVM_REG_ADDR_WIDTH 64
`endif

`UVM_REG_DATA_WIDTH

Maximum data width in bits

Default value is 64. Used to define the type.

1
2
3
`ifndef UVM_REG_DATA_WIDTH
`define UVM_REG_DATA_WIDTH 64
`endif

Generic RALF Features and IP-XACT Mapping

field

image-20220314212141988

register

addressOffset

a register has an addressOffset that describes the location of the register expressed in addressUnitBits as offset to the starting address of the containing addressBlock or the containing registerFile

addressUnitBits

The addressUnitBits element describes the number of bits of an address increment between two consecutive addressable units in the addressSpace. If addressUnitBits is not described, then its value defaults to 8, indicating a byte-addressable addressSpace

image-20220314212217722

block

image-20220314212251012

memory

image-20220314212427347

Global Handles in UVM

Mechanism in UVM-1.1

1
2
3
4
5
function void test_base::start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
uvm_top.print_topology(); // Will not compile in UVM-1.2
factory.print(); // Will not compile in UVM-1.2
endfunction

Global handles uvm_top and factory in uvm_pkg have been removed in UVM-1.2 and later

Mechanism in UVM-1.1 and UVM-1.2

Call the get() method of the class to retrieve the singleton handle.

1
2
3
4
5
function void test_base::start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
uvm_root::get().print_topology(); // Works in UVM-1.1 & UVM-1.2
uvm_factory::get().print(); // Works in UVM-1.1 & UVM-1.2
endfunction

Mechanism Only in UVM-1.2

1
2
3
4
5
function void test_base::start_of_simulation_phase(uvm_phase phase);
uvm_coreservice_t cs = uvm_coreservice_t::get();
cs.get_root().print_topology();
cs.get_factory().print();
endfunction

uvm_coreservice_t is the uvm-1.2 mechanism for accessing all the central UVM services such as uvm_root,uvm_factory, uvm_report_server, etc.

1
2
3
4
5
6
7
// Using the uvm_coreservice_t:
uvm_coreservice_t cs;
uvm_factory f;
uvm_root top;
cs = uvm_coreservice_t::get();
f = cs.get_factory();
top = cs.get_root();

uvm_config_db::set & precedence rule

https://vlsiverify.com/uvm/uvm_config_db-in-uvm#Precedence_Rule

Two Precedence rules

There are two precedence rules applicable to uvm_config_db. In the build_phase,

  1. A set() call in a context higher up the component hierarchy takes precedence over a set() call that occurs lower in the hierarchical path.
  2. On having same context field, the last set() call takes precedence over the earlier set() call.

Example for rule - 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
`include "uvm_macros.svh"
import uvm_pkg::*;

class component_A extends uvm_component;
int id;
`uvm_component_utils(component_A)

function new(string name = "component_A", uvm_component parent = null);
super.new(name, parent);
id = 1;
endfunction

function display();
`uvm_info(get_type_name(), $sformatf("inside component_A: id = %0d", id), UVM_LOW);
endfunction
endclass

class component_B extends component_A;
int receive_value;
int id;

`uvm_component_utils(component_B)

function new(string name = "component_B", uvm_component parent = null);
super.new(name, parent);
id = 2;
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db #(int)::get(this, "*", "value", receive_value))
`uvm_fatal(get_type_name(), "get failed for resource in this scope");
endfunction

function display();
`uvm_info(get_type_name(), $sformatf("inside component_B: id = %0d, receive_value = %0d", id, receive_value), UVM_LOW);
endfunction
endclass

class env extends uvm_env;
`uvm_component_utils(env)
component_A comp_A;
component_B comp_B;

function new(string name = "env", uvm_component parent = null);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_A = component_A ::type_id::create("comp_A", this);
comp_B = component_B ::type_id::create("comp_B", this);

uvm_config_db #(int)::set(this, "*", "value", 200);
endfunction

task run_phase(uvm_phase phase);
super.run_phase(phase);
void'(comp_A.display());
void'(comp_B.display());
endtask
endclass

class my_test extends uvm_test;
bit control;
`uvm_component_utils(my_test)
env env_o;

function new(string name = "my_test", uvm_component parent = null);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
////////////////////////////////////////////////////
env_o = env::type_id::create("env_o", this); // line 99

uvm_config_db #(int)::set(null, "*", "value", 100); // line 101
////////////////////////////////////////////////////
endfunction

function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_top.print_topology();
endfunction
endclass

module tb_top;
initial begin
run_test("my_test");
end
endmodule
1
2
3
4
5
6
7
8
9
10
11
12
UVM_INFO /xcelium20.09/tools//methodology/UVM/CDNS-1.2/sv/src/base/uvm_root.svh(605) @ 0: reporter [UVMTOP] UVM testbench topology:
--------------------------------------
Name Type Size Value
--------------------------------------
uvm_test_top my_test - @1810
env_o env - @1877
comp_A component_A - @1922
comp_B component_B - @1953
--------------------------------------

UVM_INFO testbench.sv(14) @ 0: uvm_test_top.env_o.comp_A [component_A] inside component_A: id = 1
UVM_INFO testbench.sv(36) @ 0: uvm_test_top.env_o.comp_B [component_B] inside component_B: id = 2, receive_value = 100

As you can see , the uvm_config_db::set is after the factory type_id::create, but receive_value is still 100, I believe that build_phase of upper hierarchy finish first before create lower components.

But the recommendation in literature and many web is place uvm_config_db::set before create

Simple Example for rule - 2

1
2
uvm_config_db #(int)::set(null, "*", "value", 100);
uvm_config_db #(int)::set(null, "*", "value", 200);

[complete code]

1
2
3
4
5
6
7
8
9
10
11
12
UVM_INFO /xcelium20.09/tools//methodology/UVM/CDNS-1.2/sv/src/base/uvm_root.svh(605) @ 0: reporter [UVMTOP] UVM testbench topology:
--------------------------------------
Name Type Size Value
--------------------------------------
uvm_test_top my_test - @1810
env_o env - @1877
comp_A component_A - @1924
comp_B component_B - @1955
--------------------------------------

UVM_INFO testbench.sv(14) @ 0: uvm_test_top.env_o.comp_A [component_A] inside component_A: id = 1
UVM_INFO testbench.sv(36) @ 0: uvm_test_top.env_o.comp_B [component_B] inside component_B: id = 2, receive_value = 200

Another Example for rule - 2

The set in super.build_phase and set in current build_phase are at the same hierarchy, so rule-2 apply

base_test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class base_test extends uvm_test;

// component macro
`uvm_component_utils(base_test)

router_tb tb;

// component constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new

// UVM build_phase()
function void build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_int::set(this, "*", "recording_detail", 1);
uvm_config_wrapper::set(this, "tb.yapp.tx_agent.sequencer.run_phase",
"default_sequence",
yapp_5_packets::get_type());
tb = router_tb::type_id::create("tb", this);
endfunction : build_phase

endclass : base_test

test2 - One

yapp_incr_payload_seq override the yapp_5_packets in super.build_phase(phase), yapp_incr_payload_seq is used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class test2 extends base_test;

// component macro
`uvm_component_utils(test2)

// component constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new

function void build_phase(uvm_phase phase);
yapp_packet::type_id::set_type_override(short_yapp_packet::get_type());
super.build_phase(phase);
uvm_config_wrapper::set(this, "tb.yapp.tx_agent.sequencer.run_phase",
"default_sequence",
yapp_incr_payload_seq::get_type());
endfunction : build_phase

endclass : test2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
---------------------------------------------------------------------------------------------------------------------                   
Name Type Size Value
---------------------------------------------------------------------------------------------------------------------
req short_yapp_packet - @3045
length integral 6 'h5
addr integral 2 'h1
payload da(integral) 5 -
[0] integral 8 'h0
[1] integral 8 'h1
[2] integral 8 'h2
[3] integral 8 'h3
[4] integral 8 'h4
parity integral 8 'h11
parity_type parity_t 1 GOOD_PARITY
packet_delay integral 32 'd19
begin_time time 64 0
depth int 32 'd2
parent sequence (name) string 21 yapp_incr_payload_seq
parent sequence (full name) string 61 uvm_test_top.tb.yapp.tx_agent.sequencer.yapp_incr_payload_seq
sequencer string 39 uvm_test_top.tb.yapp.tx_agent.sequencer
---------------------------------------------------------------------------------------------------------------------

test2 - Two

the yapp_5_packets in super.build_phase(phase) overide yapp_incr_payload_seq in test2's build_phase, yapp_5_packets is used

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class test2 extends base_test;

// component macro
`uvm_component_utils(test2)

// component constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new

function void build_phase(uvm_phase phase);
yapp_packet::type_id::set_type_override(short_yapp_packet::get_type());
uvm_config_wrapper::set(this, "tb.yapp.tx_agent.sequencer.run_phase",
"default_sequence",
yapp_incr_payload_seq::get_type());
super.build_phase(phase);
endfunction : build_phase

endclass : test2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
--------------------------------------------------------------------------------------------------------------                          
Name Type Size Value
--------------------------------------------------------------------------------------------------------------
req short_yapp_packet - @3176
length integral 6 'h6
addr integral 2 'h1
payload da(integral) 6 -
[0] integral 8 'h4f
[1] integral 8 'hbe
[2] integral 8 'hb
[3] integral 8 'h1d
[4] integral 8 'h72
[5] integral 8 'hc5
parity integral 8 'h49
parity_type parity_t 1 GOOD_PARITY
packet_delay integral 32 'd0
begin_time time 64 40
depth int 32 'd2
parent sequence (name) string 14 yapp_5_packets
parent sequence (full name) string 54 uvm_test_top.tb.yapp.tx_agent.sequencer.yapp_5_packets
sequencer string 39 uvm_test_top.tb.yapp.tx_agent.sequencer
--------------------------------------------------------------------------------------------------------------

complex project topology in UVM

topo.drawio

compile

  • -incdir for all UVC's `include
  • all pkg.sv
  • all if.sv
  • all RTL DUT
  • uvm top module

uvm top.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module tb_top;

// import the UVM library
import uvm_pkg::*;

// include the UVM macros
`include "uvm_macros.svh"

// import the yapp UVC
import yapp_pkg::*;
import hbus_pkg::*;
import channel_pkg::*;
import clock_and_reset_pkg::*;

// include the test library file
`include "router_tb.sv"
`include "router_test_lib.sv"

initial begin
yapp_vif_config::set(null, "uvm_test_top.tb.yapp.tx_agent.*", "vif", hw_top.in0);
hbus_vif_config::set(null, "uvm_test_top.tb.hbus.*", "vif", hw_top.hif );
channel_vif_config::set(null, "uvm_test_top.tb.chan0.*", "vif", hw_top.ch0);
channel_vif_config::set(null, "uvm_test_top.tb.chan1.*", "vif", hw_top.ch1);
channel_vif_config::set(null, "uvm_test_top.tb.chan2.*", "vif", hw_top.ch2);
clock_and_reset_vif_config::set(null, "uvm_test_top.tb.clock_and_reset.*", "vif", hw_top.clk_rst_if);
run_test();
end


endmodule : tb_top

RTL top

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
module hw_top;

// Clock and reset signals
logic [31:0] clock_period;
logic run_clock;
logic clock;
logic reset;

// YAPP Interface to the DUT
yapp_if in0(clock, reset);
// reset will now be generated by the Clock and Reset UVC
// input: clock; output: reset, run_clock, clock_period
clock_and_reset_if clk_rst_if(.clock(clock), .reset(reset), .run_clock(run_clock), .clock_period(clock_period));
hbus_if hif(.clock(clock), .reset(reset));

channel_if ch0(.clock(clock), .reset(reset));
channel_if ch1(.clock(clock), .reset(reset));
channel_if ch2(.clock(clock), .reset(reset));

// CLKGEN module generates clock
clkgen clkgen (
.clock(clock),
.run_clock(run_clock),
.clock_period(clock_period)
);

yapp_router dut(
...


endmodule

pkg.sv

`include all related uvm class in pgk.sv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package yapp_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"

typedef uvm_config_db#(virtual yapp_if) yapp_vif_config;

`include "yapp_packet.sv"
`include "yapp_tx_monitor.sv"
`include "yapp_tx_sequencer.sv"
`include "yapp_tx_seqs.sv"
`include "yapp_tx_driver.sv"
`include "yapp_tx_agent.sv"
`include "yapp_env.sv"

endpackage

if.sv

1
2
3
4
5
6
7
8
9
10
11
interface yapp_if (input clock, input reset );
timeunit 1ns;
timeprecision 100ps;

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

import yapp_pkg::*;
...

endinterface : yapp_if

Hierarchical Connections and Exports in UVM

imp and port connectors are sufficient for modeling most connections, but there is third connector, export, which is used exclusively in hierarchical connections.

Normally hierarchical routing is not required. A port on an UVC monitor can be connected directly to an imp in a scoreboard by specifying full hierarchical pathname (e.g., env.agent.monitor.analysis_port). The structure of an UVC is fixed and so the user knows to look in the monitor component for the analysis ports.

However the internal hierarchy of a module UVC is more arbitrary, and it may be convenient to route all the module UVC connectors to the top level to allow use without knowledge of the internal structure.

On the initiator side

  • ports are routed up the hierarchy via other port instances

On the target side

  • only the component which defines the communication method is allowed to have an imp instance. So we need a third object to route connections up the target side hierarchy - export

The hierarchical route must be connected at each level in the direction of control flow:

1
initiator.connect(target)

Connection rules are as follows:

  • port initiators can be connected to port, export or imp targets
  • export initiators can be connected to export or imp targets
  • imp cannot be connection initiator. imp is target only and is always the last connection object on a route

uvm_analysis_port

1
2
3
class uvm_analysis_port # (
type T = int
) extends uvm_port_base # (uvm_tlm_if_base #(T,T))

uvm_analysis_port

1
2
3
class uvm_analysis_port # (
type T = int
) extends uvm_port_base # (uvm_tlm_if_base #(T,T))

uvm_analysis_imp

1
2
3
4
class uvm_analysis_imp #(
type T = int,
type IMP = int
) extends uvm_port_base #(uvm_tlm_if_base #(T,T))

QA

  1. What are the three distinct functions of a scoreboard?

    Reference model, expected data storage and comparison

  2. To how many consumer components can an analysis port be connected?

    Any number, including zero

  3. What are the names of the two declarations made available by the following macro:

    1
    `uvm_analysis_imp_decl(_possible)

    Subclass uvm_analysis_imp_possible

    Method write_possible

  4. Why should a scoreboard clone the data received by a write method?

    The write method only sends a reference, therefore if the sending component uses the same reference for every data item, overwriting of data in the storage function of the scoreboard is possible without cloning.

Debug topology and Check config usage in UVM

Topology of the test

1
2
3
function void test_base::start_of_simulation_phase(uvm_phase phase);
uvm_root::get().print_topology(); // defaults to table printer
endfunction

The default printer policy is uvm_default_table_printer

There are three default printer policies that the uvm_pkg provides:

uvm_default_table_printer uvm_default_tree_printer uvm_default_line_printer

Check all configuration settings

Check all configuration settings in a components configuration table to determine if the setting has been used, overridden or not used. When recurse is 1 (default), configuration for this and all child components are recursively checked. This function is automatically called in the check phase, but can be manually called at any time.

To get all configuration information prior to the run phase, do something like this in your top object:

1
2
3
function void start_of_simulation_phase(uvm_phase phase);
check_config_usage();
endfunction

UVM phase

image-20220221230440017

  • Functions are executed bottom-up (Except for build and final phases , which are executed top-down)
  • Tasks are forked into concurrent executing threads

project topology in UVM

Most user-defined UVM classes are placed in separate files

Classes can only be compiled from a module or package scope

Recommendation:

  • Include related class files into a package
  • Import the package into modules where required

The UVM library is supplied in the package uvm_pkg

  • Import package to access library
  • The macro file uvm_macros.svh must be included separately

yapp_packet.sv

1
2
3
class yapp_packet extends uvm_sequence_item;
...
endclass : yapp_packet

yapp_pkg.sv

1
2
3
4
5
6
package  yapp_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"

`include "yapp_packet.sv"
endpackage : yapp_pkg

run.f for xrun

1
2
3
4
5
6
7
8
9
10
11
// 64 bit option for AWS labs
-64

-uvmhome CDNS-1.2

// include directories
-incdir ../sv

// compile files
../sv/yapp_pkg.sv
./top.sv

Functional Coverage Modeling

Coverage Options

  • Explicit (user defined)
    • Fully and clearly expressed within sources
      • Sequence and temporal coverage using assertions
      • Data-oriented coverage using covergroup
    • Planned and defined by the verification team
  • Implicit
    • Derived or computed from source
      • Code coverage measured by the simulator
    • May be defined outside the verification team
    • May be implied by the verification interface
      • e.g., an industry standard protocol

Explicit Coverage in Systemverilog

Assertions for control-oriented coverage

  • Defined as procedural statements
  • CANNOT be defined in a class
1
2
3
4
5
6
7
8
property req_gnt (cyc);
@(posedge clk)
$rose(req) ##0 (req && !gnt)[cyc] ##1 gnt;
endproperty

cover property (req_gnt(3));
cover property (req_gnt(4));
cover property (req_gnt(5));

Control-oriented coverage uses SystemVerilog Assertion (SVA) syntax and the cover directive. It is used to cover sequences of signal values over time. Assertions cannot be declared or "covered" in a class declaration, so their use in an UVM verification environment is restricted to interface only

Covergroup for data-oriented coverage

  • CAN be declared as a class member and created in class constructor
  • Used in interface and module UVCs
1
2
3
4
5
6
7
8
9
10
covergroup cg @(posedge clk);
len: coverpoint pkt.lenght {
illegal_bins zero = {0};
bins sml = {[1:10]}
...
}
addrxlen : cross pkt.addr, len;
endgroup

cg cg1 = new();

Data-oriented coverage uses the covergroup construct. Covergroups can be declared and created in classes, therefore they are essential for explicit coverage in a UVM verification environment.

Interface Monitor Coverage

covergroup new constructor is called directly off the declaration of the covergroup, it does not have a separate instance name

1
collected_pkts_cq = new();

The covergroup instance must be created in the class constructor, not a UVM build_phase or other phase method

image-20220403210225837

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class yapp_tx_monitor extends uvm_monitor;
// constructor

covergroup collected_pkts_cg;
...
endgroup

yapp_packet packet_collected;

task run_phase(uvm_phase phase);
collect_packet();
...
endtask

task collect_packet();
// read RTL signals via interface
// reconstruct packet
collected_pkts_cg.sample();
...
endtask

...
endclass
Module UVC Coverage

Typical module UVC coverage:

  • Routing - packets flow from input ports to all legal outputs
  • latency - all packets received within speicied delay

The module UVC monitor receives data from the interface monitors via analysis ports, so the implementation of the analysis write function is a convenient place to put coverage code

image-20220403212529981

run_test search testcase in UVM

It depends on the simulator:

  • QuestaSim, Xcelium: You have to import pkg or `include file in top testbench
  • VCS: VCS automatically search testcase in other Compilation Units
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// test_pkg.sv
package sim_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"

class my_test extends uvm_test;
`uvm_component_utils(my_test)

function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new

task run_phase(uvm_phase phase);
`uvm_info(get_type_name(), "this is run_phase of my_test", UVM_LOW)
endtask : run_phase
endclass : my_test

class its_test extends uvm_test;
`uvm_component_utils(its_test)

function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new

task run_phase(uvm_phase phase);
`uvm_info(get_type_name(), "this is run_phase of its_test", UVM_LOW)
endtask : run_phase
endclass : its_test

endpackage : sim_pkg
1
2
3
4
5
6
7
8
9
// tb_top.sv
module tb_top;
import uvm_pkg::*;
//import sim_pkg::*;

initial begin
run_test();
end
endmodule
simulator cmd result
VCS vcs -sverilog -ntb_opts uvm-1.2 test_pkg.sv tb_top.sv
./simv +UVM_TESTNAME=my_test
Xcelium xrun -64 -uvmhome CDNS-1.2 test_pkg.sv tb_top.sv +UVM_TESTNAME=my_test
QuestaSim vlog test_pkg.sv tb_top.sv -L $QUESTA_HOME/uvm-1.2
vsim -c -do "run -all;exit" +UVM_TESTNAME=my_test work.tb_top -L $QUESTA_HOME/uvm-1.2

Xcelium log:

UVM_WARNING @ 0: reporter [BDTYP] Cannot create a component of type 'my_test' because it is not registered with the factory. UVM_FATAL @ 0: reporter [INVTST] Requested test from command line +UVM_TESTNAME=my_test not found. UVM_INFO /home/EDA/Cadence/XCELIUM2109/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_catcher.svh(705) @ 0: reporter [UVM/REPORT/CATCHER]

QuestaSim log:

# UVM_INFO verilog_src/questa_uvm_pkg-1.2/src/questa_uvm_pkg.sv(277) @ 0: reporter [Questa UVM] QUESTA_UVM-1.2.3 # UVM_INFO verilog_src/questa_uvm_pkg-1.2/src/questa_uvm_pkg.sv(278) @ 0: reporter [Questa UVM] questa_uvm::init(+struct) # UVM_WARNING @ 0: reporter [BDTYP] Cannot create a component of type 'my_test' because it is not registered with the factory. # UVM_FATAL @ 0: reporter [INVTST] Requested test from command line +UVM_TESTNAME=my_test not found. # UVM_INFO verilog_src/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]

solution:

uncomment //import sim_pkg::*; in tb_top.sv

Efficient Sequence Objection in UVM

Objections are handled in pre/post body decalared in a base sequence class

This is efficient for all sequence execution options:

  • Default sequences use body objections

  • Test sequences use test objections

  • Subsequences use objections of the root sequence which calls them

1
2
3
4
5
6
7
8
9
10
11
12
13
class yapp_base_seq extends uvm_sequence#(yapp_packet);

task pre_body();
if(starting_phase != null);
starting_phase.raise_objection(this, get_type_name());
endtask
task post_body();
if(starting_phase != null);
starting_phase.drop_objection(this, get_type_name());
endtask

class yapp012 extends yapp_base_seq;

Set as default sequence of sequencer:

A default sequence is a root sequence, so the pre/post body methods are executed and objection are raised/dropped there

Test sequence:

A test sequence is a root sequence, so the pre/post body methods are executed. However, for a test sequence, starting_phase is null and so the objection is not handled in the sequence. The test must raise and drop objections

Subsequence:

Not a root sequence, so pre/post body methods are not executed. The root sequence which ultimately called the subsequence handles the objections, using one of the two options above.

If a sequence is call via a `uvm_do variant, the it is defined as a subsequence and its pre/post_body() methods are not executed.

objection change in UVM1.2

Raising or dropping objections directly from starting_phase is deprecated

  • must use get_starting_phase() method

  • prevents modification of phase during sequence

1
2
3
4
5
task pre_body();
uvm_phase sp = get_starting_phase();
if (sp != null)
sp.raise_objection(this, get_type_name());
endtask
0%