7

I'm looking for a way to pass embedded device registers to C++ templates, using gcc 4.8.4. In data sheets describing the embedded devices, the addresses of the registers are usually given as raw memory locations (0x40008000 for example).

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

So basically a wrapper around some device peripheral boils down to a class with it's register addresses given as 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 instantiate the template with a fixed, data sheet given 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 doesn't complain about this.

If I use the address given as integer as template parameter, I have problems to instantiate the template during my tests with the address of the mocked registers:

template < std::intptr_t Reg >
struct peripheral {};

std::uint32_t reg;
peripheral< reinterpret_cast< std::intptr_t >( &reg ) > mocked;

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

I can think of two solutions to this:

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

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

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

Torsten Robitzki
  • 3,041
  • 1
  • 21
  • 35
  • 1
    Keep in mind that the pointer type must always be volatile. This could be the cause of some of the problems. – Lundin Feb 19 '15 at 11:46
  • But that's not the problem that causes the compilation error. Every Pointer to T should be convertible to `volatile T`. – Torsten Robitzki Feb 19 '15 at 12:54
  • One rather simple solution is to use a pointer-to-function instead: `template struct peripheral {};` (since the function bound to that pointer is known at compile-time, it is easy to inline it). An alternative is to store the register as a type, e.g. `struct my_register { std::uint32_t volatile* operator()() const { return reinterpret_cast(0x40008000); } }; template struct peripheral {}; peripheral x;` – dyp Feb 24 '15 at 22:45
  • gcc also allows a hack via `__builtin_constant_p` for your second solution: [live demo](http://coliru.stacked-crooked.com/a/ee20da6a6087d374) It doesn't work for the first, though - which might have to do with the [restrictions on arguments for non-type template parameters of pointer type](http://stackoverflow.com/q/15885399). – dyp Feb 24 '15 at 22:48
  • Taking the address of a function seems to be counterintuitve to me. I think most compiler take that as hint to not inline the function or to at least keep a none inline version of the function. – Torsten Robitzki Feb 25 '15 at 22:50

2 Answers2

2

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

I would suggest the following (see also C++11 constexpr function's argument passed in 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;
}
Community
  • 1
  • 1
ipapadop
  • 1,432
  • 14
  • 19
  • One could even add some member function `std::uint32_t volatile operator()() const { return reinterpret_cast(Ptr); }` to `peripheral`, to provide a common interface (maybe plus some `static_assert`s on the `PtrT` type). – dyp Feb 24 '15 at 22:47
  • @ipadadop Thanks for the suggestion. How would I access the register (in production) and how would I access the mock register under test? TIA – Torsten Robitzki Feb 25 '15 at 22:47
  • 1
    @TorstenRobitzki I added a few helpers for that: `::value` will give you the pointer as a `void*` and the `::as()` static function allows you to have a handy cast through a function. I think that by also forcing whatever pointer to go through void* I should take care of any aliasing issues (if not please correct me). – ipapadop Feb 26 '15 at 20:23
0

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 Name should be any type that makes the instantiation different from other instantiations. When defining multiple types, one could use previous defined types as name:

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

For testing, the type keeps a 'semi' static variable to keep the registers value. For the arm architecture I have some cases, where it is helpful, 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 production part, the template is much easier:

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) a device abstraction takes a set of template parameters and use 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 register like syntax, could be added if assignment and implicit conversion to T would be implemented (but I'm not a big fan of that idea):

START start;
COUNT count;
start = 1;
std::uint32_t c = count;
Torsten Robitzki
  • 3,041
  • 1
  • 21
  • 35