Example recommends high effort code

In this article (Registers/FunctionalCoverage | Verification Academy) a method to ensure all registers have been read and write, outside the RAL model has been shown with the following code at the heart of the coverage model:

// Checks that the SPI master registers have
// all been accessed for both reads and writes
covergroup reg_rw_cov;
  option.per_instance = 1;
  ADDR: coverpoint address {
    bins DATA0 = {0};
    bins DATA1 = {4};
    bins DATA2 = {8};
    bins DATA3 = {5'hC};
    bins CTRL  = {5'h10};
    bins DIVIDER = {5'h14};
    bins SS = {5'h18};
  }
  CMD: coverpoint wnr {
    bins RD = {0};
    bins WR = {1};
  }
  RW_CROSS: cross CMD, ADDR;
endgroup: reg_rw_cov

What is glossed over in this example, is that the ADDR coverage group manually lists the names and addresses of each register. This is fine for the 7 register example but not so much for any modern actual SOC with thousands of registers and hundreds of changes during development. The coverage model could be generated to fill in the ADD coverpoint but there is a better solution.

This would best be implemented in a generic per-instance coverage group in a uvm register callback. This per-instance coverage group is covered when the uvm_reg_cbs::post_read/write() function executes. It can be uvm_callback:add()'d to every register by looping through the register list provided by uvm_ral_block::get_registers(). Once this is implemented it requires no further changes if the register specification changes.

So, rather than recommending a high initial and maintenance effort structure you should be taking advantage of the inherent features in a RAL model; callbacks and arrays of generic registers.