Skip to content

Commit 88ef964

Browse files
thomasahleclaude
andcommitted
[uvm] Add Register Abstraction Layer lesson
Hand-rolled uvm_object starter (compiles in CIRCT) with TODOs guiding conversion to standard uvm_reg/uvm_reg_block/uvm_reg_field. Description includes abstract skeleton template, bit-layout SVG, UVM hierarchy SVG, configure() parameter table, and hand-rolled vs UVM API mapping table. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b0c53d5 commit 88ef964

7 files changed

Lines changed: 206 additions & 1 deletion

File tree

src/lessons/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export const parts = [
109109
title: 'Functional Coverage',
110110
lessons: [L('uvm/covergroup'), L('uvm/cross-coverage'), L('uvm/coverage-driven')],
111111
},
112-
{ title: 'Advanced UVM', lessons: [L('uvm/factory-override')] },
112+
{ title: 'Advanced UVM', lessons: [L('uvm/factory-override'), L('uvm/ral')] },
113113
],
114114
},
115115
{

src/lessons/meta.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export default {
6565
'uvm/cross-coverage': { title: 'Cross Coverage', focus: '/src/mem_coverage.sv' },
6666
'uvm/coverage-driven': { title: 'Coverage-Driven Verification', focus: '/src/mem_test.sv' },
6767
'uvm/factory-override': { title: 'Factory Overrides', focus: '/src/mem_test_corner.sv' },
68+
'uvm/ral': { title: 'Register Abstraction Layer', focus: '/src/sram_reg_block.sv' },
6869

6970
// ── MLIR & CIRCT ──────────────────────────────────────────────────────────
7071
'mlir/intro': { title: 'What is MLIR?', focus: '/src/adder.mlir' },
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<p>Writing verification code that pokes raw hex addresses is fragile — change the register map and everything breaks. The <dfn data-card="The Register Abstraction Layer (RAL) is a UVM framework that models a DUT's register map as a set of SystemVerilog objects. Instead of writing raw addresses, you access named fields on typed register objects. UVM tracks the mirror value (what software wrote) and the desired value (what you want), and provides built-in read/write/update/mirror tasks that drive the physical bus through an adapter.">Register Abstraction Layer (RAL)</dfn> replaces raw addresses with named, typed register objects:</p>
2+
<pre>// Before: error-prone
3+
axi.write(32'h4000_0010, 8'h05); // Which register? Which field?
4+
5+
// After: self-documenting
6+
ral.ctrl.enable.set(1);
7+
ral.ctrl.mode.set(2'b10);</pre>
8+
9+
<h2>The UVM RAL skeleton</h2>
10+
<p>Every register file follows the same two-class pattern:</p>
11+
<pre>class &lt;RegName&gt; extends uvm_reg;
12+
uvm_reg_field &lt;field&gt;;
13+
function new(string name="&lt;RegName&gt;"); super.new(name, &lt;width&gt;, UVM_NO_COVERAGE); endfunction
14+
virtual function void build();
15+
&lt;field&gt; = uvm_reg_field::type_id::create("&lt;field&gt;");
16+
&lt;field&gt;.configure(this, &lt;size&gt;, &lt;lsb_pos&gt;, "RW", 0, 0, 1, 0, 1);
17+
endfunction
18+
`uvm_object_utils(&lt;RegName&gt;)
19+
endclass
20+
21+
class &lt;BlockName&gt; extends uvm_reg_block;
22+
&lt;RegName&gt; &lt;reg&gt;;
23+
function new(string name="&lt;BlockName&gt;"); super.new(name, UVM_NO_COVERAGE); endfunction
24+
virtual function void build();
25+
&lt;reg&gt; = &lt;RegName&gt;::type_id::create("&lt;reg&gt;");
26+
&lt;reg&gt;.build();
27+
&lt;reg&gt;.configure(this);
28+
default_map = create_map("map", 'h0, 1, UVM_LITTLE_ENDIAN);
29+
default_map.add_reg(&lt;reg&gt;, '&lt;offset&gt;, "RW");
30+
lock_model();
31+
endfunction
32+
`uvm_object_utils(&lt;BlockName&gt;)
33+
endclass</pre>
34+
<p>The three map calls at the end of <code>build()</code>:</p>
35+
<ul>
36+
<li><code>create_map()</code> — creates a named address map; the 3rd argument is bus width in bytes, the 4th is endianness</li>
37+
<li><code>add_reg()</code> — places a register at a byte offset within the map</li>
38+
<li><code>lock_model()</code> — freezes the model structure; must be called before any register access</li>
39+
</ul>
40+
41+
<h2>UVM RAL hierarchy</h2>
42+
<svg viewBox="0 0 340 110" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:340px;display:block;margin:12px 0">
43+
<rect x="100" y="4" width="140" height="28" rx="4" fill="#1e3a2e" stroke="#4ade80" stroke-width="1.5"/>
44+
<text x="170" y="23" text-anchor="middle" font-size="12" fill="#4ade80">uvm_reg_block</text>
45+
<line x1="170" y1="32" x2="170" y2="48" stroke="#6b7280" stroke-width="1.5"/>
46+
<rect x="100" y="48" width="140" height="28" rx="4" fill="#1e3a2e" stroke="#4ade80" stroke-width="1.5"/>
47+
<text x="170" y="67" text-anchor="middle" font-size="12" fill="#4ade80">uvm_reg</text>
48+
<line x1="170" y1="76" x2="170" y2="82" stroke="#6b7280" stroke-width="1.5"/>
49+
<rect x="100" y="82" width="140" height="28" rx="4" fill="#1e3a2e" stroke="#4ade80" stroke-width="1.5"/>
50+
<text x="170" y="101" text-anchor="middle" font-size="12" fill="#4ade80">uvm_reg_field</text>
51+
</svg>
52+
53+
<h2>The starter model</h2>
54+
<p>This lesson ships a hand-rolled register model that already compiles and simulates in CIRCT. Study how it maps to the skeleton above before converting it:</p>
55+
<ul>
56+
<li><strong>Fields as class members</strong><code>logic enable</code> and <code>logic [1:0] mode</code> are plain variables; <code>uvm_reg_field</code> replaces them</li>
57+
<li><strong><code>to_data()</code></strong> — packs fields by hand: <code>{5'b0, mode, enable}</code>; <code>uvm_reg::get()</code> does this automatically using the <code>lsb_pos</code> from <code>configure()</code></li>
58+
<li><strong><code>ADDR</code> localparam</strong> — the address is baked in; <code>uvm_reg_block</code> owns the map instead</li>
59+
</ul>
60+
61+
<h2>Bit layout of the ctrl register</h2>
62+
<svg viewBox="0 0 400 60" xmlns="http://www.w3.org/2000/svg" style="width:100%;max-width:400px;display:block;margin:12px 0">
63+
<rect x="1" y="10" width="48" height="30" fill="none" stroke="#4ade80" stroke-width="1.5"/>
64+
<text x="25" y="30" text-anchor="middle" font-size="11" fill="#6b7280">enable</text>
65+
<text x="25" y="48" text-anchor="middle" font-size="9" fill="#9ca3af">[0]</text>
66+
<rect x="49" y="10" width="97" height="30" fill="none" stroke="#4ade80" stroke-width="1.5"/>
67+
<text x="97" y="30" text-anchor="middle" font-size="11" fill="#6b7280">mode</text>
68+
<text x="97" y="48" text-anchor="middle" font-size="9" fill="#9ca3af">[2:1]</text>
69+
<rect x="146" y="10" width="253" height="30" fill="none" stroke="#374151" stroke-width="1.5"/>
70+
<text x="272" y="30" text-anchor="middle" font-size="11" fill="#374151">reserved</text>
71+
<text x="272" y="48" text-anchor="middle" font-size="9" fill="#6b7280">[7:3]</text>
72+
</svg>
73+
74+
<h2>The <code>configure()</code> call explained</h2>
75+
<p><code>uvm_reg_field::configure()</code> takes 9 parameters. The most important:</p>
76+
<table>
77+
<thead><tr><th>Parameter</th><th>Value in this lesson</th><th>Meaning</th></tr></thead>
78+
<tbody>
79+
<tr><td><code>parent</code></td><td><code>this</code></td><td>The owning <code>uvm_reg</code></td></tr>
80+
<tr><td><code>size</code></td><td><code>1</code> / <code>2</code></td><td>Width in bits</td></tr>
81+
<tr><td><code>lsb_pos</code></td><td><code>0</code> / <code>1</code></td><td>Position of the LSB in the register — replaces the bit concatenation in <code>to_data()</code></td></tr>
82+
<tr><td><code>access</code></td><td><code>"RW"</code></td><td>Access policy (RW, RO, WO, …)</td></tr>
83+
<tr><td><code>reset</code></td><td><code>0</code></td><td>Reset value</td></tr>
84+
</tbody>
85+
</table>
86+
87+
<h2>Hand-rolled vs UVM RAL</h2>
88+
<table>
89+
<thead><tr><th>Hand-rolled</th><th>UVM RAL</th></tr></thead>
90+
<tbody>
91+
<tr><td><code>class ctrl_reg extends uvm_object</code></td><td><code>class ctrl_reg extends uvm_reg</code></td></tr>
92+
<tr><td><code>logic enable</code></td><td><code>uvm_reg_field enable</code></td></tr>
93+
<tr><td><code>ctrl.enable = 1</code></td><td><code>ctrl.enable.set(1)</code></td></tr>
94+
<tr><td><code>ctrl.to_data()</code></td><td><code>ctrl.get()</code></td></tr>
95+
<tr><td><code>class sram_reg_block extends uvm_object</code></td><td><code>class sram_reg_block extends uvm_reg_block</code></td></tr>
96+
</tbody>
97+
</table>
98+
99+
<h2>Exercise</h2>
100+
<p>Convert both files to standard UVM RAL classes using the skeleton above as a guide:</p>
101+
<ol>
102+
<li>Open <code>sram_reg_block.sv</code> — follow the TODO comments: change the base classes, replace the <code>logic</code> fields with <code>uvm_reg_field</code>, add <code>build()</code> to <code>ctrl_reg</code> with two <code>configure()</code> calls, and expand <code>sram_reg_block::build()</code> with the three map calls.</li>
103+
<li>Open <code>mem_test_ral.sv</code> — follow the TODO comments: replace direct field assignment with <code>.set()</code>, and replace <code>to_data()</code>/<code>convert2string()</code> with <code>.get()</code>.</li>
104+
</ol>
105+
<blockquote><p>The <code>lsb_pos</code> argument to <code>configure()</code> replaces what <code>to_data()</code> was doing manually. <code>enable</code> sits at bit 0 (<code>lsb_pos=0</code>, size=1); <code>mode</code> sits at bits [2:1] (<code>lsb_pos=1</code>, size=2). UVM uses this to pack and unpack the register value automatically.</p></blockquote>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class mem_test_ral extends uvm_test;
2+
`uvm_component_utils(mem_test_ral)
3+
function new(string name, uvm_component parent); super.new(name, parent); endfunction
4+
5+
task run_phase(uvm_phase phase);
6+
sram_reg_block ral;
7+
phase.raise_objection(this);
8+
ral = sram_reg_block::type_id::create("ral", this);
9+
ral.build();
10+
11+
// UVM RAL API: call .set() on uvm_reg_field
12+
ral.ctrl.enable.set(1);
13+
ral.ctrl.mode.set(2'b10);
14+
15+
if (ral.ctrl.get() == 8'h05)
16+
`uvm_info("RAL_TEST", $sformatf("PASS: ctrl=0x%02h", ral.ctrl.get()), UVM_NONE)
17+
else
18+
`uvm_error("RAL_TEST", $sformatf("FAIL: expected 0x05, got 0x%02h", ral.ctrl.get()))
19+
20+
phase.drop_objection(this);
21+
endtask
22+
endclass
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class mem_test_ral extends uvm_test;
2+
`uvm_component_utils(mem_test_ral)
3+
function new(string name, uvm_component parent); super.new(name, parent); endfunction
4+
5+
task run_phase(uvm_phase phase);
6+
sram_reg_block ral;
7+
phase.raise_objection(this);
8+
ral = sram_reg_block::type_id::create("ral", this);
9+
ral.build();
10+
11+
// Hand-rolled API: direct field assignment
12+
ral.ctrl.enable = 1; // TODO: replace with .set(1) on the uvm_reg_field
13+
ral.ctrl.mode = 2'b10; // TODO: replace with .set(2'b10) on the uvm_reg_field
14+
15+
if (ral.ctrl.to_data() == 8'h05) // TODO: replace to_data() with .get(); update the strings below too
16+
`uvm_info("RAL_TEST", $sformatf("PASS: %s", ral.ctrl.convert2string()), UVM_NONE)
17+
else
18+
`uvm_error("RAL_TEST", $sformatf("FAIL: expected 0x05, got 0x%02h", ral.ctrl.to_data()))
19+
20+
phase.drop_objection(this);
21+
endtask
22+
endclass
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class ctrl_reg extends uvm_reg;
2+
uvm_reg_field enable;
3+
uvm_reg_field mode;
4+
function new(string name = "ctrl_reg"); super.new(name, 8, UVM_NO_COVERAGE); endfunction
5+
virtual function void build();
6+
enable = uvm_reg_field::type_id::create("enable");
7+
enable.configure(this, 1, 0, "RW", 0, 0, 1, 0, 1);
8+
mode = uvm_reg_field::type_id::create("mode");
9+
mode.configure(this, 2, 1, "RW", 0, 0, 1, 0, 1);
10+
endfunction
11+
`uvm_object_utils(ctrl_reg)
12+
endclass
13+
14+
class sram_reg_block extends uvm_reg_block;
15+
ctrl_reg ctrl;
16+
function new(string name = "sram_reg_block"); super.new(name, UVM_NO_COVERAGE); endfunction
17+
virtual function void build();
18+
ctrl = ctrl_reg::type_id::create("ctrl");
19+
ctrl.build();
20+
ctrl.configure(this);
21+
default_map = create_map("map", 'h0, 1, UVM_LITTLE_ENDIAN);
22+
default_map.add_reg(ctrl, 'h0, "RW");
23+
lock_model();
24+
endfunction
25+
`uvm_object_utils(sram_reg_block)
26+
endclass
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// TODO: change to extend uvm_reg instead; update super.new() to pass the register
2+
// width and coverage mode: super.new(name, 8, UVM_NO_COVERAGE)
3+
class ctrl_reg extends uvm_object;
4+
`uvm_object_utils(ctrl_reg)
5+
// TODO: replace the two logic fields below with uvm_reg_field objects
6+
logic enable = 0; // bit 0
7+
logic [1:0] mode = 0; // bits [2:1]
8+
localparam logic [3:0] ADDR = 4'h0;
9+
function new(string name = "ctrl_reg"); super.new(name); endfunction
10+
// TODO: add a virtual function void build(); create each field with
11+
// uvm_reg_field::type_id::create(), then call .configure() to set its size and position
12+
// (the hand-rolled helpers below are replaced by uvm_reg's built-in .get() and .convert2string())
13+
function logic [7:0] to_data(); return {5'b0, mode, enable}; endfunction
14+
function string convert2string();
15+
return $sformatf("ctrl @ 0x%0h: enable=%0b mode=%0b (raw=0x%02h)", ADDR, enable, mode, to_data());
16+
endfunction
17+
endclass
18+
19+
// TODO: change to extend uvm_reg_block instead; update super.new() to pass UVM_NO_COVERAGE
20+
class sram_reg_block extends uvm_object;
21+
`uvm_object_utils(sram_reg_block)
22+
ctrl_reg ctrl;
23+
function new(string name = "sram_reg_block"); super.new(name); endfunction
24+
function void build();
25+
ctrl = ctrl_reg::type_id::create("ctrl");
26+
// TODO: call ctrl.build() then ctrl.configure(this) to register ctrl with this block
27+
// TODO: create the address map, add ctrl to it at offset 0, then call lock_model()
28+
endfunction
29+
endclass

0 commit comments

Comments
 (0)