Some time ago we developed a lightweight register modeling framework, amiq_rm, similar with the uvm_reg and vr_ad libraries. We implemented amiq_rm in C++ such that we could seamlessly integrate it with both SystemC and C++-based projects.
Here are the main features of amiq_rm:
- Simple and intuitive API (HTML documentation included)
- You can group registers into register blocks
- You can map registers to one or more address maps and create complex map hierarchies
- You can define and instantiate fields with configurable name, reset value, size, attributes (e.g. R1C, RW, RO, etc.)
- You can add custom field attributes
- You can perform field operations: adding fields, getting and setting the value of a field
- You can register callbacks that take place after/before the actual READ/WRITE operation (pre_access, post_access)
- You can perform hierarchical searching of registers by name or by offset
- You can easily integrate the register models with LT/AT SystemC protocol implementations
- The library was tested using both unit tests and production use cases.
- It is released under Apache License 2.0
This article is a step-by-step tutorial for modeling a set of registers using the amiq_rm library.
The Example
The example illustrates how to model several registers which are accessible by two different interfaces (See Fig. 1).
Step 1. Download amiq_rm library
The example is part of amiq_rm library, which you can download from GitHub.
Step 2. Define the registers
Every register class should inherit amiq_rm_reg. You should add the register fields in its constructor.
//create a register class that models register local_id
class local_id_reg: public amiq_rm_reg {
public:
local_id_reg(string my_name) : amiq_rm_reg(my_name) {
//add a field for "local_id" register called "id"
add_field(new amiq_rm_field("id", 0x00, 8, "RW"));
add_field(new amiq_rm_field("reserved", 0x0, 4, "RO"));
}
};
Step 3. Define a register block
A register block inherits amiq_rm_reg_block. It contains the entire set of registers and all the address maps required. You should add the registers to the corresponding address map in its constructor.
class amiq_rtr_reg_block: public amiq_rm_reg_block {
public:
//handle for the local ID register
local_id_reg local_id_h;
// handle for the physical address map corresponding to UART interface
amiq_rm_physical_address_map uart_map;
// handle for the physical address map corresponding to RTR interface
amiq_rm_physical_address_map rtr_map;
amiq_rtr_reg_block() : amiq_rm_reg_block("my_reg_block"),
local_id_h("local_id"), uart_map("uart_map"), rtr_map("rtr_map") {
//add register local_id to the UART address map, at offset 0x00
uart_map.add_reg(local_id_h, 0x00);
uart_map.build();
}
};
Step 4. Access the registers
The registers can be accessed using the “write()” and the “read()” methods from the address map.
int main(int args, char * argv[]) {
//do a write to local_id (register address 0x00) with data 0xFF
reg_block.uart_map.write(0x00, 0xFF);
//do a read from local_id (register address 0x00)
pair data_with_status = reg_block.uart_map.read(0x00);
return 0;
};
At this point all you need to do is to compile and run. The example package contains a Makefile that you can use as follows:
$> cd build
$> make -f makefile all
$> ./amiq_rm
Roadmap
We plan to extend the model’s functionality with the following:
- add logic to make the register accesses thread safe
- provide examples for integration with TLM-1.0/2.0 sockets (LT, AT models)
- implement checks for overlapping addresses
- implement a get_absolute_addresses() method for address map and registers – it requires to return a list of addresses and the paths to the register