Am Sun, 01 Jun 2014 15:37:04 +0000
schrieb "Timo Sintonen" <t.sinto...@luukku.com>:

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

Thanks for joining this discussion, your input is very appreciated.

> 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.

I'll have to agree. Ironically the simple 'peek/poke' templates
discussed before would fail miserably - such short templates are almost
always inlined and then all guarantees gained by using normal functions
are lost... OTOH it must inline for performance reasons. So in the end
library templates can't work, we'd at least need compiler intrinsics.

> 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.

I guess for such complicated cases the solution presented by Mike are
nice:
https://github.com/JinShil/D_Runtime_ARM_Cortex-M_study/blob/master/1.4-memory_mapped_io/source/mmio.d

However, I'm not sure if it can work for simple cases where you need
zero memory/instruction overhead. (8/16bit controllers, GBA like
devices)

> 
> 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.

I guess we can't do much about this. Proper bitfields could help, but
try to sell these to Walter ;-) However, a struct + property functions
+ forceinline + volatile could work.

> 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.

Maybe I misunderstood, but you can always do this:
---------------------------------
struct Timer
{
    uint current;
    uint step;
    uint control;
    bool isActive() volatile
    {
        return control & 0b1 ? true : false;
    }
}

enum volatile(Timer)* TimerA = cast(volatile(Timer)*)0xABCD;

TimerA.isActive();
TimerA.isActive();
---------------------------------

Although we often say the 'this' pointer is qualified this is sloppy
speaking. The destination of the this pointer is qualified, but not the
pointer itself. So this is volatile(Timer)* in the above example, not
volatile(Timer*) and it's the same for shared, const, etc.

It is however true that you cannot mark the pointer immutable and keep
the data mutable, that's the usual head-const problem.


I think you should be mostly happy with the DIP ;-) Is there anything
you find lacking in C's volatile implementation? This DIP mostly follows
the C way, but adds transitivity and some D specific stuff (necessary
as our structs can have methods)

> 
> 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.

I see we agree on the importance of this ;-) 


Reply via email to