Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

read-only, write-only and read-write memory mapped I/O

66 views
Skip to first unread message

Richard

unread,
Jul 27, 2017, 10:20:32 PM7/27/17
to
[Please do not mail me a copy of your followup]

I've been tinkering around with how C++ can improve embedded
development. My reference platform is the Nintendo Game Boy Advance.
It has a bunch of coprocessing circuitry that is controlled through
memory-mapped registers. I came up with this little header for
modeling read-only, write-only and read-write registers located at a
specific address. I'm interested to hear if anyone else has done
something similar and how you approached the problem. This is my
lowest level abstraction around a memory-mapped register. Higher
level abstractions would be built on top of this.

I'm also interested to hear how you might have unit tested any embedded
code. I have my own ideas, but I don't want to lead the question with
my thoughts before I hear what oethers have done.

namespace RegisterModel
{

template <typename T, volatile T const *const address>
struct ReadOnlyRegister
{
operator T() const { return *address; }
T operator=(T) = delete;
};

template <typename T, volatile T *const address>
struct WriteOnlyRegister
{
operator T() const = delete;
void operator=(T value) { *address = value; }
};

template <typename T, volatile T *const address>
struct ReadWriteRegister
{
operator T() const { return *address; }
void operator=(T value) { *address = value; }
};

}
--
"The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline>
The Terminals Wiki <http://terminals-wiki.org>
The Computer Graphics Museum <http://computergraphicsmuseum.org>
Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com>

Jens Thoms Toerring

unread,
Jul 30, 2017, 6:57:51 AM7/30/17
to
Richard <legaliz...@mail.xmission.com> wrote:
> I've been tinkering around with how C++ can improve embedded
> development. My reference platform is the Nintendo Game Boy Advance.
> It has a bunch of coprocessing circuitry that is controlled through
> memory-mapped registers. I came up with this little header for
> modeling read-only, write-only and read-write registers located at a
> specific address.

> namespace RegisterModel
> {

> template <typename T, volatile T const *const address>
> struct ReadOnlyRegister
> {
> operator T() const { return *address; }
> T operator=(T) = delete;
> };

> template <typename T, volatile T *const address>
> struct WriteOnlyRegister
> {
> operator T() const = delete;
> void operator=(T value) { *address = value; }
> };

> template <typename T, volatile T *const address>
> struct ReadWriteRegister
> {
> operator T() const { return *address; }
> void operator=(T value) { *address = value; }
> };
> }

I've played around with this bit but get a problem when I
try to do e.g,

ReadWriteRegister<uint16_t, 0x1000> rwr;

The compiler complains that it can't convert the integer constant
to 'volatile T *const'. One way around may be to use 'intptr_t' in
the second template parameter and then do the appropriate casts on
it where needed.

Another thing is that the address may not always a compile
time constant, it might be e.g. obtained via a function call
and thus unsuitable as a template parameter.

Here's my take on this, trying to support both cases:

template<typename T>
class RegisterBase
{
protected:
RegisterBase(T * address) : m_address(address) {}
T read() const { return *m_address; }
T write(T value) { return *m_address = value; }

private:
volatile T * m_address;
};

template<typename T>
struct ReadOnlyRegister : public RegisterBase<T>
{
ReadOnlyRegister(intptr_t address)
: RegisterBase<T>(reinterpret_cast<T *>(address))
{}

ReadOnlyRegister(void const * address)
: RegisterBase<T>(const_cast<T *>(reinterpret_cast<T const *>(address)))
{}

operator T () const { return this->read(); }
T operator = (T) = delete;
};

template<typename T>
struct WriteOnlyRegister : public RegisterBase<T>
{
WriteOnlyRegister(intptr_t address)
: RegisterBase<T>(reinterpret_cast<T *>(address))
{}

WriteOnlyRegister(void * address)
: RegisterBase<T>(reinterpret_cast<T *>(address))
{}

operator T () const = delete;
T operator = (T value) { return this->write(value); }
};

template<typename T>
struct ReadWriteRegister : RegisterBase<T>
{
ReadWriteRegister(intptr_t address)
: RegisterBase<T>(reinterpret_cast<T *>(address))
{}

ReadWriteRegister(void * address)
: RegisterBase<T>(reinterpret_cast<T *>(address))
{}

operator T () const { return this->read(); }
T operator = (T value) { return this->write(value); }
};

I.e. the address is not a template parameter but is passed to the
constructor. You thus should be able to use it like this

#defined STAT_FLAGS_ADDR 0x10ea
ReadOnlyRegister<uint16_t> state_flags(STATE_FLAGS_ADDR)

as well as

extern unsigned char * reg_base(void);
#define STATE_FLAGS_OFFSET 0xea

ReadOnlyRegister<uint16_t> state_flags(reg_base() + STATE_FLAGS_OFFSET);

Another possibly interesting addition might be to be able to
set other than the default methods for reading and writing.
On some systems I've worked with certain registers where
"protected" in that you couldn't write directly to them but
this required that you first wrote certain values to some
other register with a certain timing, before a write to the
"real" register had an effect (to protect some important set-
tings fron getting overwritten inadvertently).

> I'm interested to hear if anyone else has done something similar
> and how you approached the problem.

Sorry, got no chance to use C++ in embedded programming yet, it
always had to be C...
Best regards, Jens
--
\ Jens Thoms Toerring ___ j...@toerring.de
\__________________________ http://toerring.de

Richard

unread,
Aug 1, 2017, 10:38:55 PM8/1/17
to
[Please do not mail me a copy of your followup]

j...@toerring.de (Jens Thoms Toerring) spake the secret code
<eu5sco...@mid.uni-berlin.de> thusly:

>Richard <legaliz...@mail.xmission.com> wrote:
>> I've been tinkering around with how C++ can improve embedded
>> development. My reference platform is the Nintendo Game Boy Advance.
>> It has a bunch of coprocessing circuitry that is controlled through
>> memory-mapped registers. I came up with this little header for
>> modeling read-only, write-only and read-write registers located at a
>> specific address.
>
>> namespace RegisterModel
>> {
>
>> template <typename T, volatile T const *const address>
>> struct ReadOnlyRegister
>> {
>> operator T() const { return *address; }
>> T operator=(T) = delete;
>> };
>
>> template <typename T, volatile T *const address>
>> struct WriteOnlyRegister
>> {
>> operator T() const = delete;
>> void operator=(T value) { *address = value; }
>> };
>
>> template <typename T, volatile T *const address>
>> struct ReadWriteRegister
>> {
>> operator T() const { return *address; }
>> void operator=(T value) { *address = value; }
>> };
>> }
>
>I've played around with this bit but get a problem when I
>try to do e.g,
>
>ReadWriteRegister<uint16_t, 0x1000> rwr;

Yeah, I had similar problems and my test code made it compile, but as
I look on it now, it wasn't representative of the use case.

I can certainly do it with the address held at runtime, but in most
embedded environments the memory map doesn't change because you're
using physical addresses, not addresses obtained at runtime. My goal
is to find something that is as idiomatic as the following C code
(Game Boy Advance is my reference platform):

#include "toolbox.h"

int main()
{
DISPCNT = DCNT_MODE3 | DCNT_BG2;

m3_plot( 120, 80, RGB15(31, 0, 0) ); // or CLR_RED
m3_plot( 136, 80, RGB15( 0,31, 0) ); // or CLR_LIME
m3_plot( 120, 96, RGB15( 0, 0,31) ); // or CLR_BLUE

while (1);

return 0;
}

Refer to the memory-mapped IO registers summarized here:
<http://problemkaputt.de/gbatek.htm#gbaiomap>

There are read/write, read-only and write-only registers on the GBA.
The C libraries used by most coders don't prevent you from mistakenly
reading a write-only register, etc.

Refer to "tonclib" tutorial for snippet above:
<http://www.coranac.com/tonc/text/first.htm#sec-second>

What I like about this snippet:
- any GBA programmer is going to understand what is happening to the
DISPCNT register here.
- The "m3_plot" routine is easily inferred as "(display) mode 3 pixel
plot" based on the arguments and the face that DISPCNT is put into mode 3.
Again, this is going to be understandable if you understand GBA display
hardware.
- Computing the value assigned to DISPCNT results in very efficient
code (simply store fixed constant value at a fixed constand address)
- RGB15 macro results in efficient constants passed into m3_plot.

What I dislike about this snippet:
- It relies on macros with all their pitfalls
- No guard against mistakes. What if I use the wrong bitfield macros
(e.g. macros intended for the values of a different register) for
DISPCNT register? DISPCNT is read/write, but other registers are
read-only or write-only. What if I use them incorrectly?
- RGB15 macro can't alert me when I use arguments that are out of
range.

I think this tonclib library is a good starting point, but I want to
explore ways in which the zero overhead abstraction of C++ can allow
us to do better at finding coding mistakes at compile-time, assist us
in unit testing our code, all without giving up the efficiency of the
resulting code that we can already get from tonclib.

Gcc is kept current for the GBA, so we aren't held back by older
compilers. <https://sourceforge.net/projects/devkitadv/files/>

Richard

unread,
Aug 2, 2017, 4:55:12 PM8/2/17
to
[Please do not mail me a copy of your followup]

This seems to do the trick better. I'm not a fan of having to
sprinkle the reinterpret_cast's in there, but at least they are
encapsulated inside the implementation.

#include <cstdint>

namespace RegisterModel
{

typedef uint64_t addr_t;

template <typename T, addr_t const address>
struct ReadOnlyRegister
{
operator T() const {
return *reinterpret_cast<volatile T const *>(address);
}
T operator=(T) = delete;
};

template <typename T, addr_t const address>
struct WriteOnlyRegister
{
operator T() const = delete;
void operator=(T value) {
*reinterpret_cast<volatile T *>(address) = value;
}
};

template <typename T, addr_t const address>
struct ReadWriteRegister
{
operator T() const {
return *reinterpret_cast<volatile T const *>(address);
}
void operator=(T value) {
*reinterpret_cast<volatile T *>(address) = value;
}
};

}

namespace
{

using namespace std;
using namespace RegisterModel;

ReadWriteRegister<uint32_t, 0x04000000U> DISPCNT;

}

It's also a little ugly that I now need to declare
RegisterModel::addr_t as the type of an integer big enough to hold an
address in the target platform. In the GBA, this is a std::uint32_t
but if I'm compiling unit test code on Windows this might be
std::uint64_t. Still, it solves the compiler complaining about the
constraints on the pointer argument to the template while giving me
an address known at compile time.

Going back to the tonclib example I posted earlier, we could get more
safety for the DISPCNT register in one of a couple ways. We could
view

DISPCNT = DCNT_MODE3 | DCNT_BG2;

as constructing a constexpr expression on the right that produces some
explicit type representing valid values of the DISPCNT register, say
DisplayControl, and write an assignment operator for values of these
types:

struct DisplayControl
{
// standard enum bit flags style class implementation
};

struct DisplayControlRegister :
public ReadWriteRegister<uint32_t, 0x04000000U>
{
DisplayControl operator=(DisplayControl value);
};

DisplayControlRegister DISPCNT;

Now I'm protected against assigning invalid values into the display
control register. Another approach is to model the individual fields
in the display control register as getter/setter functions on
DisplayControlRegister. However, that would likely involve
read/modify/write cycles for the individual chunks and wouldn't be as
efficient as the C style code above. The nature of this register is
that it is generally configured once wholesale at game startup.

For other registers, getter/setter methods might be more reasonable if
you are changing the individual fields at different times. Knowing
which is best involves more understanding of idiomatic GBA
programming.

red floyd

unread,
Aug 2, 2017, 7:14:11 PM8/2/17
to
On 8/2/2017 1:54 PM, Richard wrote:

> It's also a little ugly that I now need to declare
> RegisterModel::addr_t as the type of an integer big enough to hold an
> address in the target platform. In the GBA, this is a std::uint32_t
> but if I'm compiling unit test code on Windows this might be
> std::uint64_t. Still, it solves the compiler complaining about the
> constraints on the pointer argument to the template while giving me
> an address known at compile time.
>

What about uaddr_t? I know it's probably not ISO standard, but I
*THINK* it's POSIX....

David Brown

unread,
Aug 3, 2017, 3:16:20 AM8/3/17
to
Or better still, uintptr_t, which is part of <stdint.h> in C and
therefore part of C++.

Chris Vine

unread,
Aug 3, 2017, 7:55:15 AM8/3/17
to
That would be better, but templating a struct on an integer type to
represent a hardware address is subject to the difficulty that the
compiler knows that the underlying template type is an integer and, on
the face of it, is entitled to generate whatever rubbish it wants to
when that integer is cast to a pointer type and that pointer type is (as
here) dereferenced, because the dereferencing would break the strict
aliasing rule.

I say "on the face of it" because §3.10/10 of the standard refers to
"the _dynamic_ type of the object" and here the typing is static, so
until the reinterpret_cast is made there is no dynamic type. Maybe
therefore the compiler is obliged to assume that the dynamic type is
the type cast to: dunno.

In practice I suspect gcc and clang will accept this construct even
without the no-strict-aliasing flag.

Chris

David Brown

unread,
Aug 3, 2017, 8:43:03 AM8/3/17
to
On 03/08/17 13:54, Chris Vine wrote:
> On Thu, 03 Aug 2017 09:15:57 +0200
> David Brown <david...@hesbynett.no> wrote:
>> On 03/08/17 01:13, red floyd wrote:
>>> On 8/2/2017 1:54 PM, Richard wrote:
>>>
>>>> It's also a little ugly that I now need to declare
>>>> RegisterModel::addr_t as the type of an integer big enough to hold
>>>> an address in the target platform. In the GBA, this is a
>>>> std::uint32_t but if I'm compiling unit test code on Windows this
>>>> might be std::uint64_t. Still, it solves the compiler complaining
>>>> about the constraints on the pointer argument to the template
>>>> while giving me an address known at compile time.
>>>>
>>>
>>> What about uaddr_t? I know it's probably not ISO standard, but I
>>> *THINK* it's POSIX....
>>>
>>
>> Or better still, uintptr_t, which is part of <stdint.h> in C and
>> therefore part of C++.
>
> That would be better, but templating a struct on an integer type to
> represent a hardware address is subject to the difficulty that the
> compiler knows that the underlying template type is an integer and, on
> the face of it, is entitled to generate whatever rubbish it wants to
> when that integer is cast to a pointer type and that pointer type is (as
> here) dereferenced, because the dereferencing would break the strict
> aliasing rule.

That is, at best, a hypothetical interpretation. The standards say that
converting an integer type to a pointer or vice-versa is implementation
defined - with a footnote saying it is "intended to be consistent with
the addressing structure of the execution environment". The type
"uintptr_t" is defined as being able to hold any pointer-to-void, such
that when it is converted back to a pointer-to-void it will compare
equal to the original pointer. The code here uses "reinterpret_cast" -
the allows conversion back and forth between an integer representation
and a safely-derived pointer. Access via "volatile" is implementation
dependent, but guarantees certain things such as ordering and evaluation.

These combine to mean that a compiler would have to go to significant
lengths to prove that access via these RegisterModel types is not
technically defined behaviour, and then generate rubbish. And any
compiler that did not do the obvious thing when casting an integer to a
volatile pointer and then dereferencing it would by classified as
useless by embedded developers.

>
> I say "on the face of it" because §3.10/10 of the standard refers to
> "the _dynamic_ type of the object" and here the typing is static, so
> until the reinterpret_cast is made there is no dynamic type. Maybe
> therefore the compiler is obliged to assume that the dynamic type is
> the type cast to: dunno.
>
> In practice I suspect gcc and clang will accept this construct even
> without the no-strict-aliasing flag.
>

Well, indeed - any C++ compiler suitable for embedded development (and
where else do you need RegisterModel?) will work fine with this
construct, with any choice of flags or optimisation.


Chris Vine

unread,
Aug 3, 2017, 1:05:50 PM8/3/17
to
On Thu, 03 Aug 2017 14:42:40 +0200
What the standard says is that (i) converting a pointer value to integer
and then back to pointer value is required to work if the integer type
is large enough, (ii) uintptr_t (if available) is large enough, and
(iii) other mappings between pointers and integers are implementation
defined (and the text of the footnote in my version of the standard says
that the mapping "is intended to be unsurprising to those who know the
addressing structure of the underlying machine"). Volatile is not
relevant to this, but I agree with what you say about its meaning.

None of that is relevant to aliasing on dereferencing, which is not
hypothetical, whether "at best" or otherwise. As I said, what I find
unclear is how that operates in relation to a conversion from what
originates as a statically typed integer value rather than a pointer
value.

> These combine to mean that a compiler would have to go to significant
> lengths to prove that access via these RegisterModel types is not
> technically defined behaviour, and then generate rubbish. And any
> compiler that did not do the obvious thing when casting an integer to
> a volatile pointer and then dereferencing it would by classified as
> useless by embedded developers.

Possibly, but the view could be taken that you should templatize on the
pointer type and not the integer type, as originally proposed.

I said that on the face of it a compiler appears "entitled" to generate
rubbish but in practice it is probably fine; and if not there are flags
to deal with it. You seem to be agreeing with me vigorously.

Chris

David Brown

unread,
Aug 3, 2017, 2:58:35 PM8/3/17
to
On 03/08/17 19:05, Chris Vine wrote:

> I said that on the face of it a compiler appears "entitled" to generate
> rubbish but in practice it is probably fine; and if not there are flags
> to deal with it. You seem to be agreeing with me vigorously.
>

I would put it a bit more solidly than that. A compiler /may/ appear
"entitled" to generate rubbish, depending on what it documents for its
implementation-defined behaviour, but in practice it /will/ be fine -
regardless of the flags.

("volatile" makes any aliasing issues irrelevant, if you are consistent
about only accessing the relevant data via volatile accesses, because
the compiler is required to make each read and write as directed in the
source code and can't "optimise away" accesses even if the aliasing
looks suspicious.)

Apart from that balance, I am agreeing with you.

red floyd

unread,
Aug 3, 2017, 4:17:59 PM8/3/17
to
Damn, *THAT'S* the one I was thinking of.

David Brown

unread,
Aug 4, 2017, 2:48:16 AM8/4/17
to
That's what Usenet is for :-)

0 new messages