[Bug 126] Add support for attribute to mark data as volatile.

Timo Sintonen via D.gnu d.gnu at puremagic.com
Sun Jun 1 08:37:04 PDT 2014


I did not yet read the dip but here are some of my thoughts:

At the old days peripherals were simple. An uart might have a 
control register, a status register and a data register, 8 bit 
each. It just did not matter how they were accessed. Now a 
peripheral like usb or ethernet may have tens of 32 bit registers.

The Basic language did not have pointers or any way to address a 
certain location of memory. Several extensions were made to get 
access to system locations. One common extension was poke and 
peek functions. They had 16 bit address and 8 bit data. Basic did 
not have any data types or stuctures.
D has pointers that can be used to access memory. It also has 
several data types. A library function does not know if it should 
do 8/16/32/64 bit access without templates. That would be too 
complicated for such a low level operation like register access.

The registers of a peripheral may be defined as a struct and a 
pointer of this struct type is used to access the registers. 
There are individual registers but there may also be some sub 
register sets inside the register set. A peripheral may have 
common registers and then per channel registers. The register 
struct may then have substructs or an array of register sets that 
may be accessed as structs or arrays.

Yes, there are different kind of registers.
- Normal registers can be read and written. These are used as 
normal control and status registers. Some bits may be changed by 
hardware at any time. This may be a problem because it is 
impossible to have a fully atomic access. The time between read 
and write should be as short as possible.
- Read only registers may be used to represent the capabilities 
of the peripheral or calibration values. They always return the 
same data. Status registers represent the current state of the 
hardware and may change any time when the conditions change. 
Write to these registers has no effect.
- Write only registers are used to send data. The data packet is 
written byte by byte to this same address. These type of 
registers are also used to clear status. Reading the register may 
return the last data or zero or anything else and the value 
should be ignored.
- Bidirectional registers are used as data registers. A read will 
return the last received byte and a write will transmit the byte 
written.

Usually it does not matter if these registers are accessed wrong 
way (write a read only or read a write only) so there is no need 
to mark them different. They can all be volatile.


It is also common that one register has mixed read/write, read 
only and write only bits. Many registers have also 
undefined/reserved bits, which sometimes should be written with 
zeros and sometimes left as they are.

One of the most common operations is to wait some status:
while ((regs.status&0x80)==0)  { /* check timeout here */ }
The way to clear the status may be one of:
- write directly to the status bit
   regs.status &= 0xffffff7f;
- write a 1 to the bit
   regs.status |= 0x80;
- sometimes writing 0 to other bits has no effect and there is no 
need to read-modify-write
   regs.status = 0x80;
- sometimes status is cleared by writing to another bit
   regs.status |= 0x200;
- sometimes there is a separate clear register
   regs.statusclear = 0x80;
- sometimes accessing the data register clears status 
automatically
- sometimes reading the status register clears the status. In 
this case all status bits have to be checked at once.

Many of these have the result that reading the register does not 
give back the data that was written.

And no, I did not read this on Wikipedia. All these forms of 
access exist in the processor I use (STM32F407) It seems that 
several teams have made the peripherals on the chip and every 
peripheral has its own way to access it.


Another thing is: do I need to mark every member in a struct 
volatile or is it enough to mark the struct definition or the 
struct pointer. Will it go transitively to every member of an 
array of substructs or do I need mark the substructs volatile?

One thing is the struct pointer. The peripherals have a fixed 
address. If there is only one peripheral, the address can be a 
compile time constant. If there are several similar peripherals, 
the address may be known at compile time or it could be immutable 
that is initialized at start.
Now I have to make the pointer shared to have the struct members 
shared. This means the pointer is also mutable and volatile. The 
pointer can not be cached and has to be fetched again every time 
the struct variables are accessed. This decreases performance.


D has been marketed as a system language. Accessing registers is 
an essential part of system programming. Whatever the method is, 
it has to be in the language, not an external library function.



More information about the D.gnu mailing list