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(JIR) & Jitter Impulse Response(JIR)

image-20220313231013645

image-20220313231027512

image-20220313231038542

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

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

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: Regular Papers, vol. 56, no. 1, pp. 17-39, Jan. 2009, doi: 10.1109/TCSI.2008.931647.

Phase-Locked Frequency Generation and Clocking : Architectures and Circuits for Modern Wireless and Wireline Systems by Woogeun Rhee (2020, Hardcover)

Mathuranathan 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"

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;

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

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"

How does EDI System identify spare cells in a post-mask ECO flow?

Spare cells should have a unique string in their instance name to identify them. Then the command specifySpareGate or ecoDesign -useSpareCells patternName is run to identify the spare instances. For example, if all spare cells have _spare_ in their name then they are identified using:

1
specifySpareGate -inst *_spare_*

OR

1
ecoDesign -spareCells *_spare_* ...

Note: if you are making manual ECO changes to a netlist and converting a spare cell to a logical instance, it's important to change the instance name. Otherwise, the instance may be identified as a spare cell if a future ECO is performed because it still has the spare cell instance name.

Example

The cell to be swapped is unplaced

image-20220307002842020

pre_buf: unplaced

spare_buf: placed

innovus 49> dbGet top.insts.name

spare_buf pre_buf UDriver USink

innovus 50> dbGet top.insts.

0x7f7b03ef60e0 0x7f7b03ef6150 0x7f7b03ef6000 0x7f7b03ef6070

1
2
specifySpareGate -inst spare_*
ecoSwapSpareCell pre_buf spare_buf

image-20220307003059068

innovus 55> dbGet top.insts.name

pre_buf UDriver USink

innovus 56> dbGet top.insts.

0x7f7b03ef6150 0x7f7b03ef6000 0x7f7b03ef6070

innovus 57> dbGet top.insts.Pstatus

placed fixed fixed

Note: sparecell's pointer and name is swapped with the unplaced cell.

The cell to be swapped is placed

image-20220307004654614

innovus 62> dbGet top.insts.name

spare_buf pre_buf UDriver USink

innovus 63> dbGet top.insts.

0x7f7b03ef60e0 0x7f7b03ef6150 0x7f7b03ef6000 0x7f7b03ef6070

innovus 64> dbGet top.insts.pStatus

placed placed fixed fixed

1
2
3
4
innovus 66> specifySpareGate -inst spare_*
Specifying instance [spare_buf] as spare gate.
Specified 1 instances as spare gate.
innovus 67> ecoSwapSpareCell pre_buf spare_buf

image-20220307005254488

innovus 68> dbGet top.insts.name

spare_buf pre_buf UDriver USink

innovus 69> dbGet top.insts.

0x7f7b03ef60e0 0x7f7b03ef6150 0x7f7b03ef6000 0x7f7b03ef6070

innovus 70> dbGet top.insts.pStatus

placed placed fixed fixed

Note: sparecell's pointer and name is swapped with the placed cell.

Error in "Innovus Text Command Reference 21.12"

ecoSwapSpareCell

If the cell to be swapped is unplaced, it is mapped to the spare cell. *instName* is deleted, and its connection is transferred to the spare cell. If the cell to be swapped is placed, it is swapped with the spare cell and is renamed to *instNameSuffix* if the -suffix option is used. If a suffix is not specified, the *instName* cell is renamed to *spareCellInstName*. The *instName* cell's connections are transferred to *spareCellInstName*. The input of *instName* is tielo, based on the global connection definition.

reference:

Answers to Top 10 Questions on Performing ECOs in EDI System

EE 582: Physical Design Automation of VLSI Circuits and Systems

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

Before inserting sign-off metal fill, stream out a GDSII stream file of the current database. Specify the mapping file and units that match with the rule deck you specify while inserting metal fill. If necessary, include the detailed-cell (-merge option) Graphic Database System (GDS).

PVS:

1
2
streamOut -merge $GDSFile -mode ALL -units $GDSUNITS -mapFile $GDSMAP -outputMacros pvs.fill.gds
run_pvs_metal_fill -ruleFile $DUMMYRULE -defMapFile $DEFMAP -gdsFile pvs.fill.gds -cell [dbgDesignName]

Pegasus:

1
2
streamOut -merge $GDSFile -mode ALL -units $GDSUNITS -mapFile $GDSMAP -outputMacros pegasus.fill.gds
run_pegasus_metal_fill -ruleFile $DUMMYRULE -defMapFile $DEFMAP -gdsFile pegasus.fill.gds -cell [dbgDesignName]

Just replace run_pvs_metal_fill with run_pegasus_metal_fill

Note: Innovus metal fill (e.g. addMetalFill, addViaFill, etc.) does not support 20nm and below node design rules. We strongly recommend the Pegasus/PVS metal fill solution for 20nm and below. If you have sign-off metal fill rule deck for 28nm and above available, we recommend you to move to Pegasus/PVS solution too.

  1. trimMetalFillNearNet does not check DRC rules. It only removes the metal fill with specified spacing

  2. Do not perform ECO operations after dump in sign-off metal fill (by run_pvs_metal_fill or run_pegasus_metal_fill), especially, at 20nm and below nodes.

  3. If you perform an ECO action, the tool cannot get DRC clean because trimMetalFill does not support 20nm and below node design rules.

  4. The sign-off metal fill typically does not cause DRC issues with regular wires.

The run_pvs_metal_fill command does the following:

  • Runs PVS with the fill rules to create a GDSII output file.
  • Converts the GDSII to a DEF format file based on the GDSII to DEF layermap provided.
  • Loads the resulting DEF file into Innovus.

Pegasus is similar to PVS, shown as below,

The run_pegasus_metal_fill command does the following:

  • Runs Pegasus with the fill rules to create a GDSII output file.
  • Converts the GDSII to a DEF format file based on the GDSII to DEF layermap provided.
  • Loads the resulting DEF file into Innovus.

Reference:

Innovus User Guide, Product Version 21.12, Last Updated in November 2021

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

0%