Thread overview
volatile struct definition for peripheral control registers
December 02

I'm working on an embedded project writing some device drivers to access peripheral control registers as MMIO addresses. Due to D's lack of a volatile keyword, I'm trying to come up with a way to define these peripheral control register structures in a way that doesn't require the user of the structure to have to remember to call volatileStore() or volatileLoad() for each register access separately and I want the interface to be as clean as possible.

In C I would have just declared the entire structure to be volatile since the entire thing lives in MMIO address range, and I suppose I'm trying to achieve something close to that...

Attempt #1

Declare a Volatile(T) struct template:

module myproj.volatile;

import core.volatile;

struct Volatile(T)
{
    T v;

    T get()
    {
        return volatileLoad(&v);
    }

    void opAssign(T v)
    {
        volatileStore(&this.v, v);
    }
}

Use as:

import myproj.volatile;

struct S
{
    Volatile!uint reg;
    Volatile!uint reg2;
    struct
    {
        Volatile!uint subreg;
    };
}

void vtest()
{
    S * s = cast(S *)0xDEADBEEF;
    while (s.reg.get == 0)
    {
        s.subreg = 1;
    }
}

This works but requires that ugly .get call rather than just accessing it directly.

Attempt #2

module myproj.volatile;

public import core.volatile;

template volatile_field(string type, string name)
{
    mixin("private "~type~" v_"~name~";");
    mixin("public @property "~type~" "~name~"() { return volatileLoad(&v_"~name~"); }");
    mixin("public @property void "~name~"("~type~" v) { volatileStore(&v_"~name~", v); }");
}

template volatile_ubyte(string name)
{
    mixin volatile_field!("ubyte", name);
}

template volatile_ushort(string name)
{
    mixin volatile_field!("ushort", name);
}

template volatile_uint(string name)
{
    mixin volatile_field!("uint", name);
}

template volatile_ulong(string name)
{
    mixin volatile_field!("ulong", name);
}
import myproj.volatile;

struct S
{
    mixin volatile_uint!"reg";
    mixin volatile_uint!"reg2";
    struct
    {
        mixin volatile_uint!"subreg";
    };
}

void vtest()
{
    S * s = cast(S *)0xDEADBEEF;
    while (s.reg == 0)
    {
        s.subreg = 1;
    }
}

This works and I like from the user side being able to access each field as if it was just the normal field. However, declaring the fields is a bit uglier syntax.

Questions

Between the two of these attempts I'm leaning toward #2 because of the easier user syntax to read/write the fields.

I am wondering:

  1. Is there any way to improve attempt #1 to not require the .get() call? I tried with opCast() but that requires the user to do an explicit cast which I felt was even worse than the .get
  2. Is there any way to improve attempt #2 to make field declaration cleaner than mixin volatile_uint!"reg"?
  3. Is there any other way of achieving what I'm trying to achieve different from these two methods?
December 02
On 12/2/24 11:10 AM, Josh Holtrop wrote:

> This works but requires that ugly .get call rather than just accessing
> it directly.

'alias this' may help:

  https://dlang.org/spec/struct.html#alias-this

All you need to do is to add the following line to the Volatile template:

  alias get this;

Ali

December 02

On Monday, 2 December 2024 at 19:30:06 UTC, Ali Çehreli wrote:

>

On 12/2/24 11:10 AM, Josh Holtrop wrote:

>

This works but requires that ugly .get call rather than just
accessing
it directly.

'alias this' may help:

https://dlang.org/spec/struct.html#alias-this

All you need to do is to add the following line to the Volatile template:

alias get this;

Ali

If I use alias this I don't know how I can get volatileLoad() to be called.

December 02

On Monday, 2 December 2024 at 20:27:23 UTC, Josh Holtrop wrote:

>

If I use alias this I don't know how I can get volatileLoad() to be called.

Please allow me to illustrate the point with a simple example so that you can visualize it:

struct FixedStr(size_t capacity)
{
  char[capacity] data = ' ';
  char[] buff;
  size_t titleLength;

  this(string _data)
  {
    buff = data[];
    this ~= _data;
    titleLength = _data.length;
  }
  alias opReturn this;

  @property auto opReturn() inout
    => data[titleLength..$]; // cuts off the title

  auto ref opOpAssign(string op)(string arr) if(op == "~")
    => buff.put(arr); // add in the data
}

import std.range  : chunks, put;
import std.string : format;
import std.stdio  : writeln;

void main()
{
  enum text = "sixtwoone";

  auto test = FixedStr!22("Nums : ");
  assert(is(typeof(test) == struct));
  // not a string ------------^

  foreach(num; text.chunks(3))
  {
    test ~= num.format!"%s, ";
  }
  test.writeln; // six, two, one,
  test[5..8].writeln; // two
}

Let's have a 2-function struct as above. In fact, the functions do not have a feature that is open to direct use. There's not even an overload like opReturn(), it's my made :)

To rotate a fixed string (char[]) by cropping it from the beginning (open it to the outside world) and to add data sequentially (via std.range.put) to its limit. But we also want to use it like the familiar string (e.g. line 2 output: "two"). And that's what we do easily with alias ... this. However, in this example, it is necessary to use @property and inout; keep in mind...

It's also a good idea to keep this in mind: https://dlang.org/changelog/2.100.0.html#alias_this_assignment

SDB@79

December 02
On 12/2/24 12:27 PM, Josh Holtrop wrote:

>>   alias get this;
>
> If I use `alias this` I don't know how I can get `volatileLoad()` to be
> called.

Note that alias this is used with your 'get' function, which still calls volatileLoad().

Ali

December 03

On Monday, 2 December 2024 at 22:44:24 UTC, Ali Çehreli wrote:

>

On 12/2/24 12:27 PM, Josh Holtrop wrote:

> >

alias get this;

If I use alias this I don't know how I can get
volatileLoad() to be
called.

Note that alias this is used with your 'get' function, which still calls volatileLoad().

Ali

Ah... very nice. I completely missed that "alias this" could be used with a function.

This works great and lets me use a nicer syntax to declare the fields.

Thank you both. :)

December 03

Just to follow up, I introduced the Volatile struct to my project and had to add a couple extra functions to make everything happy:

module myproj.volatile;

import core.volatile;

struct Volatile(T)
{
    private T value;

    public @property T read()
    {
        return volatileLoad(&value);
    }
    alias read this;

    public void opAssign(T value)
    {
        volatileStore(&this.value, value);
    }

    public T opUnary(string s)()
    {
        T v = read();
        mixin(s ~ "v;");
        volatileStore(&this.value, v);
        return v;
    }

    public T opOpAssign(string s)(T rhs)
    {
        T v = read();
        mixin("v " ~ s ~ "= rhs;");
        volatileStore(&this.value, v);
        return v;
    }
}

So far it is working great!