Register as a template parameter

I am looking for a way to pass built-in device registers into C ++ templates using gcc 4.8.4. In the data sheets describing the embedded devices, register addresses are usually indicated as raw memory cells (for example, 0x40008000).

When I test the software, I want to use static integers as registers to see if the register values ​​are set correctly.

Thus, basically the shell around some peripheral device is reduced to a class with its registers specified as a template parameter:

template < volatile std::uint32_t* Reg > struct peripheral {}; 

Testing works fine:

 std::uint32_t reg; peripheral< &reg > mocked; 

But when I want to create an instance of a template with a fixed, specified data address:

 peripheral< reinterpret_cast< std::uint32_t* >( 0x40008000 ) > mocked; 

gcc complains: could not convert template argument '1073774592u' to 'volatile uint32_t* {aka volatile long unsigned int*} . clang is not complaining about this.

If I use the address specified as an integer as a template parameter, I am having problems creating an instance of the template during my tests with the address of the mocking registers:

 template < std::intptr_t Reg > struct peripheral {}; std::uint32_t reg; peripheral< reinterpret_cast< std::intptr_t >( &reg ) > mocked; 

The result is error: conversion from pointer type 'uint32_t* {aka long unsigned int*}' to arithmetic type 'intptr_t {aka int}' in a constant-expression .

I can imagine two solutions:

1) Use pointers as template parameters, use global variables as registers and correct the address of the registers with some linker script.

2) Use special types of registers that have a common interface for the peripheral template, but two very different implementations for testing and for the real application.

But I'm looking for an easier way to achieve this. Any ideas, pointers or comments?

+6
source share
2 answers

My solution looks like this:

 template < class Name = int, typename T = std::uint32_t, T* Value = nullptr > class mocked_register { public: static void set( T v ) { *address() = v; } static T get() { return *address(); } static volatile T* address() { static T internal_; return Value ? Value : &internal_; } }; 

Where the name should be any type that makes the instance different from other instances. When defining multiple types, you can use the previous defined types as a name:

 typedef mocked_register<> START; typedef mocked_register< START > STOP; typedef mocked_register< STOP > COUNT; 

For testing, the type saves the old 'semi' variable to preserve the value of the registers. For arm architecture, I have some cases where it is useful to use an array of registers. In this case, the Value parameter can be used to provide an external array:

 std::uint32_t capture_regs[ 4 ]; typedef mocked_register< SHUTDOWN, std::uint32_t, capture_regs > CAPTURE; 

For the working part, the template is much simpler:

 template < std::uint32_t Register > struct addressed_register { static void set( std::uint32_t value ) { *reinterpret_cast< volatile std::uint32_t* >( Register ) = value; } static std::uint32_t get() { return *reinterpret_cast< volatile std::uint32_t* >( Register ); } }; 

In both cases (testing and production), the device abstraction takes a set of template parameters and uses them as registers:

 template < class OUTSET, class OUTCLEAR, class DIRSET, class DIRCLR, class PIN_CNF, std::uint8_t Nr > struct pin { static void init_as_input() { DIRCLR::set( 1 << Nr ); } }; 

More registers, such as syntax, could be added if assignment and implicit conversion to T were implemented (but I'm not a big fan of this idea):

 START start; COUNT count; start = 1; std::uint32_t c = count; 
0
source

Doing reinterpret_cast<> not allowed in a constant expression (this is what the compiler also tells you); see also Constant expressions .

I would suggest the following (see also the C ++ 11 argument of the constexpr function passed in the template argument ):

 #include <cstdint> #include <cassert> template<typename PtrT, PtrT Ptr> struct peripheral { static void* const value; template<typename T> static T* as() noexcept { return static_cast<T*>(value); } }; #define PERIPHERAL(addr) peripheral<decltype((addr)), (addr)> std::uint32_t reg = 0; int main() { peripheral<std::uintptr_t, 0x42> mocked1; peripheral<volatile std::uint32_t*, &reg> mocked2; PERIPHERAL(0x42) mocked3; PERIPHERAL(&reg) mocked4; assert((mocked3.value == PERIPHERAL(0x42)::as<void*>())); return 0; } 
+2
source

Source: https://habr.com/ru/post/982730/


All Articles