nicocvn / cppreg Goto Github PK
View Code? Open in Web Editor NEWA C++11 header-only library for MMIO registers
Home Page: https://nicocvn.github.io/cppreg/
License: Other
A C++11 header-only library for MMIO registers
Home Page: https://nicocvn.github.io/cppreg/
License: Other
This is currently missing from the documentation and should include:
As of 5cf8a85 it seems we fail to compile on PowerPC GCC 4.8.5 according to GodBolt as shown here.
with the following compiler error:
<source>:138:40: error: redeclaration 'cppreg::Shadow<Register, true>::use_shadow' differs in 'constexpr'
const bool Shadow<Register, true>::use_shadow;
^
<source>:133:37: error: from previous declaration 'cppreg::Shadow<Register, true>::use_shadow'
constexpr static const bool use_shadow = true;
^
<source>:138:40: error: declaration of 'constexpr const bool cppreg::Shadow<Register, true>::use_shadow' outside of class is not definition [-fpermissive]
const bool Shadow<Register, true>::use_shadow;
^
Compiler returned: 1
When working on #1 using the following code, I spotted the following;
First up is the read modify write for the MODER
register which takes 2 more instructions in cppreg vs cmsis for cortex m0plus. This seems to be because for cppreg the address of the register is built via multiple immediate while in CMSIS it re-uses the MODER
address when accessing BSRR
.
Secondly, it seems cppreg for writing a value to the BSRR
does a simpler str r1, [r3]
in cppreg instead of the potentially more expensive str r2, [r3, #24]
instruction under CMSIS. The Cortex-M0+ Technical Reference Manual says there is no difference, but this may be different for M7 and more complicated architectures.
I feel the first and second difference are somewhat related because in CMSIS the compiler is informed that MODER
and BSRR
are related via offsets from a base pointer, while in CPPReg they look totally unrelated. This results in the compiler having to "rebuild" the address twice for cppreg, once from immediates for MODER
and another from a hardcoded address stored in the .text section. Furthermore, it seems the two instruction difference is also due to the masking and applying the value
I view this as being caused by the architecture limitations of Cortex M and the design of cppreg.
Regarding the architecture, if you compile this for X86-64 or non thumb ARM then you see the issue goes away (the assembly is identical). I think this is because immediates in those ISA's/ARCH's can be huge due to, well, instructions being allowed to be very large too. This results in all stores/loads being done via full immediates instead of an immediate and offset or shifting immediate to build the address.
Regarding cppreg, you cannot specify that multiple registers are just an offset from each other instead of totally unrelated areas (CMSIS does this by placing a huge struct on the address). I do not see an easy way to give the compiler that sort of information either.
To be frank, this difference in assembly from a performance standpoint is small, very small. Ideally the reason for the discrepancy can be verified/found with cppreg adopting the smaller of the two. But, writing to a register is very rarely a bottle neck unless you are bit banging, in which case you should probably be starting to seriously consider assembly instead.
(This issue is a continuation of the discussions and suggestions in #7)
Before merging the register pack implementation the following items need to be decided:
Access policies should be revised to leverage the fact that most data are actually known at compile time (and when using the template form of write
all data are actually known at compile time).
This issue was created following the discussion in #5.
I was working on an example with a UART, which involves writing to a (usually) 8 bit register that has one field that is also 8 bits large (fills the size of the register). This field is both read and write (same register to send data to the UART TX FIFO and to get data out of a UART RX FIFO). The code is on godbolt here
// CppReg example
struct UART {
static constexpr uintptr_t UART0_BASE = 0x48000000;
struct FIFO : cppreg::Register<UART0_BASE + 0, 8u> {
using Data = cppreg::Field<FIFO, 8u, 0u, cppreg::read_write>;
};
};
void UART_Test_CPPREG(void){
UART::FIFO::Data::write<0x21>();
UART::FIFO::Data::write<0x11>();
UART::FIFO::Data::write<0x90>();
UART::FIFO::Data::write<0xFF>();
UART::FIFO::Data::write<0x01>();
UART::FIFO::Data::write<0x02>();
UART::FIFO::Data::write<0x03>();
}
// CMSIS example
#define __IO volatile
typedef struct {
__IO uint8_t FIFO;
} UART_TypeDef;
#define UART ((UART_TypeDef *) 0x48000000)
void GPIO_Test_CMSIS(void){
UART->FIFO = 0x21;
UART->FIFO = 0x11;
UART->FIFO = 0x90;
UART->FIFO = 0xFF;
UART->FIFO = 0x01;
UART->FIFO = 0x02;
UART->FIFO = 0x03;
}
The CMSIS Example behaves as expected, we get the immediate into a register and write it to memory. Interestingly the immediates are gotten by adding or subtracting from the previous immediate instead of just a mov register, #number
instruction. I would have thought an ADD register, #number
would have longer, but for Cortex M0/M0+ (ARMv6m) they are the same (single cycle). Surprisingly enough, ARM don't say what the cycle count is for ARMv7m, so maybe it's different there, hence the discrepancy.
Anyways, I am expecting that cppreg will look the same or very similar, but instead I am seeing a load
being inserted between each store
. I consider this to be critical because for some peripherals, a read may cause the peripheral to change it's flow of execution and do something else, effectively putting the peripheral in an unknown state.
GPIO_Test_CMSIS(): UART_Test_CPPREG():
mov r3, #1207959552 mov r3, #1207959552
movs r2, #33 ldrb r2, [r3] @ zero_extendqisi2
strb r2, [r3] movs r2, #33
movs r2, #17 strb r2, [r3]
strb r2, [r3] ldrb r2, [r3] @ zero_extendqisi2
movs r2, #144 movs r2, #17
strb r2, [r3] strb r2, [r3]
movs r2, #255 ldrb r2, [r3] @ zero_extendqisi2
strb r2, [r3] movs r2, #144
movs r2, #1 strb r2, [r3]
strb r2, [r3] ldrb r2, [r3] @ zero_extendqisi2
movs r2, #2 movs r2, #255
strb r2, [r3] strb r2, [r3]
movs r2, #3 ldrb r2, [r3] @ zero_extendqisi2
strb r2, [r3] movs r2, #1
bx lr strb r2, [r3]
ldrb r2, [r3] @ zero_extendqisi2
movs r2, #2
strb r2, [r3]
ldrb r2, [r3] @ zero_extendqisi2
movs r2, #3
strb r2, [r3]
bx lr
This brings two potential ways to fix this. First I was thinking to just put in a check in the ::write()
that, if the width of the field is equal to the width of the register then perform the write like in the write_only
policy. This should be an easy way to fix the issue.
But then I noticed that there are some situations in which there are writes to a register field during which the state of the other fields do not need to be kept. This is probably going into fairly fancy enhancement territory, which wouldn't be trivial to implement, but possibly adding an optional argument to the creation of a field that lets you specify if you want the contents to be maintained in between writes to other fields. Just noticed this means you cannot change which field to ignore during writes. Instead, maybe for chained writes it would be possible to have a chained write accept either a integral or a null value. If the null value is present then it does not attempt retain that field contents during the write chain.
For example.
// CppReg example
struct UART {
static constexpr uintptr_t UART0_BASE = 0x48000000;
struct FIFO : cppreg::Register<UART0_BASE + 0, 8u> {
using Data = cppreg::Field<FIFO, 7u, 0u, cppreg::read_write>;
// Set to have UART start dumping FIFO contents. Remains set until done.
// If a write to this register occurs with this bit set while the bit is already set
// then undefined behavior can occur (stops current byte transmission and skips to next one).
using Start = cppreg::Field<FIFO, 1u, 7u, cppreg::read_write>;
};
};
UART::FIFO::Data::write<0x05>();
UART::FIFO::Data::write<0x15>();
UART::FIFO::merge_write<Start>(1)::with<Data>(std::ignore);
// or ideally
UART::FIFO::merge_write<Start>(1)::with<Data, std::ignore>();
As saw in #5 , there is a discrepancy between CMSIS and Cppreg which was found to be due to the CMSIS style allowing the ability to inform the compiler about groupings of registers. This allows the compiler to, when reading and writing from memory, use relative memory operations like this
C++(Cppreg) CMSIS vs CPPREG
int a[0] = SomeField0::read(); LD R0, R10 [#0] LD R0, R10
int a[1] = SomeField1::read(); LD R1, R10 [#4] LD R1, R11
int a[2] = SomeField2::read(); LD R2, R10 [#8] LD R2, R12
Since CppReg doesn't have that information explicitly stated, the compiler is stuck assuming that the registers are totally unrelated and is unable to get the address via offsets from a base address. I guess the optimization process is unable to do that extensive inspection for memory addresses (though it can do that for immediate as shown in the sidenote of #6 it seems).
Add a way to group registers together. I do not have a syntax example off the top of my head, but as @sendyne-nclauvelin mentioned in #5 this would require a not trivial amount of API rework, so before implementing, lets see what other potential API points of pain there are.
Almost all other projects include a link in their project description, I suggest we follow that route too since there is no other way to view the github page.
This issue is created following the suggestion in #14.
The current documentation does not mention the ro_mem_device()
and rw_mem_device()
functions which can be used to access the memory of a register type. These functions can be useful in some cases (for example, see #14 about write to toggle fields update).
Hello!
First of -- great lib! Pure enjoyment using this approach -- code become small and easy to read and juggle with.
Recently I encountered a very strange problem -- I couldn't work with single bit programming STM32. Details below.
I'm working with STM32F042F6. Problem arises when I tried to activate filter banks -- to do that I had to clear a single bit FINIT
. Here is his definition of this partical part:
#pragma once
#include <cppreg.h>
using namespace cppreg;
struct Peripheral {
struct bxCAN : RegisterPack<0x4000'6400, 1024> {
// Filter initialization mode
using FINIT =
Field<PackedRegister<bxCAN, RegBitSize::b32, 0x200>, 1, 0, read_write>;
};
};
Here is a screenshot of what does that register have:
Everywhere else lib works flawlessly! But only here I had to hand-write access to this particular register.
void can_t::init_filters_() {
// Strangest bug! Not working FINIT::set/clear();
// Well, let's get dirty!
uint32_t &finit = *((uint32_t *)(0x4000'6600));
finit |= 0b1;
// ... magic ...
// Turn all filters initialization -> active
finit &= ~0b1;
while (finit & 0b1) __nop();
}
I found at last while()
-cycle, where I check for successful activation of filters. If I use cppreg's
access to FINIT
-bitfield -- it does nothing, bit never changes. It always read bit as '1' (bit is always raised, cannot clear).
No one got hurt, except my pride. And a bit of my personal time.
Did I miss some caveat?
The main goal is to provide a document (e.g., Performance.md) where we show assembly outputs for various level of optimizations (and possibly compilers) to illustrate that cppreg
does not affect runtime performance or code size.
This requires:
cppreg
implementation and a traditional implementation (ร la CMSIS)We could put the various materials in a benchmark directory to not pollute the main one.
Channel0
in Field<Channel0, ...>
should be Channel0/1/2/3
https://github.com/sendyne/cppreg/blob/42bd134067b361f45b75ceb83e857c64923fd3fe/API.md?plain=1#L154-L165
I'm working on refactoring a small USB stack for STM32 to avoid its dependencies (CMSIS, HAL etc) and have my own svd->cppreg header generation flow which is working great.
However, the STM32 USB registers, specifically the endpoint registers, have a number of fields which are toggle-only (ie read is permitted, write 1 to flip) mixed in with normal read-write fields.
Would it be feasible to devise an access policy which changes the way cppreg attempts to maintain the value of a field?
Writing the existing value back, as I presume the existing implementation would do (to try to preserve the value of the toggle fields) when I'm trying to write to a normal field in the same register, will cause all the toggles to flip if they are currently set.
Happy to entertain other suggestions, too of course. CMSIS handles this just fine by forcing all toggle field values to 0 during read-modify-write, but to do the same I'd have to be able to read the entire packed register at once, then do a merge_write to all fields, and packed registers don't support read().
Looking over the documentation it seems that perhaps a modification of the shadow register functionality could achieve this, too?
Due to #9 documentation needs to be updated (in particular for merge write operations).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.