CH32V003 GPIO registers - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: February 15, 2023, 06:22:28 pm
content: I'm looking at the GPIO section of the WCH CH32V003 Reference Manual, and I'm perplexed by the bit-setting registers GPIOx_BSHR and GPIOx_BCR.So, we have the GPIOx_BSHR register, which has the first 8 bits of the lower 16 as 'set' function (by writing 1), and the first 8 bits of the upper 16 as 'reset' (i.e. clear) function (also by writing 1).But, we then also have the GPIOx_BCR register, which seems to duplicate the 'reset' functionality of the other register.Why the duplication of functionality? Some kind of optimisation to fit better with the EC instruction set, to make GPIO bit clear operations more efficient?What does the 'H' in 'BSHR' stand for?There appears to be no functionality fortogglingGPIO bits. Surely it would make more sense if instead the GPIOx_BCR register was for toggling (where the 'C' stands for 'complement')? Perhaps the manual is incorrect (copy/paste error)?Anybody got any thoughts?

author: hans
author_rank: Super Contributor
timestamp: February 15, 2023, 07:15:01 pm
content: I suspect it has to do with that the reset bits in BSHR are in the upper 16-bits of the 32-bit word, while in BCR they are in the lower 16-bits. If the code is setting and then resetting some pins via a constant value, then it can reuse the same (lower 16-bit) literal while using a different write address (=relative offset).https://github.com/openwch/ch32v003/blob/main/C%2B%2B/Use%20MRS%20Create%20C%2B%2B%20project-example/CH32V003F4P6/Peripheral/src/ch32v00x_gpio.c#L257I'm not sure about the comments relating to accessing in 16-bit form. This code seems to access them as 32-bit registers.Now granted, if the CPU has to load constants into a register for 1 access, then it needs to fit in the instruction format:https://riscv.org/wp-content/uploads/2017/05/riscv-spec-v2.2.pdfpage 11 (PDF: 23)The I-type instruction is used for immediate ALU instructions like ADDI, and in combination with the zero register, can be used to load a constant. It's 12-bit wide, so the top most 4 pins can only be accessed with an additional shift.However the LUI instruction is formatted in the U-type and can load the upper 20 bits.Alternatively, one could load these constants from FLASH.. which is probably what a compiler like GCC would do.These are all trade-offs and probably far beyond what one should worry about in 99% of the cases. But for niche cases, it's nice these alternatives are given..

author: GromBeestje
author_rank: Frequent Contributor
timestamp: February 15, 2023, 08:30:30 pm
content: This behaviour is the same as the GPIO registers on the STM32F103. (RM0008 Rev 20 page 173)The CH32V103 is basically an CH32F103 with the cortex m3 core replaced with a rv32imac core.So, the peripherals are basically compatible with STM32F103. (with one exception: USB)And for the CH32V003, I didn't have a close look yet, but on first glance, they are the same.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: February 15, 2023, 08:53:02 pm
content: Quote from: hans on February 15, 2023, 07:15:01 pmThat's part of the thought I had too. In case that is the reason, I took a look at the assembly generated for a simple operation of setting a bit of a GPIO register (in this case, port C):Code:/* C code */GPIOC->BSHR |= GPIO_BSHR_BS0;/* Assembly */392:400117b7luia5,0x40011396:4b98lwa4,16(a5)398:400117b7luia5,0x4001139c:00176713oria4,a4,13a0:cb98swa4,16(a5)My interpretation of that is the following:1. Load the peripheral (GPIOC) base address into register a5. The LUI instruction is only capable of holding a 20-bit immediate address.2. Load the BSHR register value into register a4. It does this by loading from the address in a5 offset by 16 (BSHR register offset is 0x10).3. Same as step 1. (Needed because the previous instruction added the offset to a5?)4. ORs the previously-loaded register value with an immediate constant, a '1' in BS0 (bit 0) to set the GPIO pin high.5. Store the new value back into the register (again with address in a5 offset by 16).I think step 4, the ORI instruction is key here. The ISA manual describes it as"ANDI, ORI, XORI are logical operations that perform bitwise AND, OR, and XOR on register rs1 andthe sign-extended 12-bit immediateand place the result in rd". So, I guess this means there cannot be an efficient single-instruction method of OR/AND-ing the register's 'reset' bits in the upper 16, and so that is why they provide a secondary register dedicated to clearing/resetting bits.Makes me curious what code it does generate when you do want to use the BR bits in the upper 16.

author: mblinov
author_rank: Contributor
timestamp: February 15, 2023, 10:35:00 pm
content: Quote from: HwAoRrDk on February 15, 2023, 08:53:02 pmI got curious about this, and I'm fairly confident its the compiler being stupid.Test setup:https://godbolt.org/z/h1EMKMf5YDisassembly:QuoteBoth GCC 12.2 and 8.2 give the same assembly (I'm assuming this is with GCC.) Specifying the field as 'volatile' (as it often is for GPIO stuff) doesn't make a difference.Not sure what's going on there - would be interesting to know what version of GCC the WCH IDE is using.

author: mblinov
author_rank: Contributor
timestamp: February 15, 2023, 10:37:42 pm
content: Oops, I neglected to model the fact that the field is at an offset of 16 bytes - regardless, it doesn't make a difference:New test setup:https://godbolt.org/z/MaYn5ofnqResult:Quote

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 15, 2023, 10:41:52 pm
content: Quote from: HwAoRrDk on February 15, 2023, 08:53:02 pmNote: This is a write-only register, with zero bits causing no operation. So, better useGPIOC->BSHR = GPIO_BSHR_BS0;or for exampleGPIOC->BSHR = GPIO_BSHR_BS0 | GPIO_BSHR_BC1;for a real-world use case (assuming I guessed the constant right), the latter setting GPIO C0 but clearing C1.

author: brucehoult
author_rank: Super Contributor
timestamp: February 15, 2023, 11:42:19 pm
content: Quote from: mblinov on February 15, 2023, 10:35:00 pmYou can force it to be stupid and do the LUI twice by specifying -O0. Which you should never, ever, do unless you actually like stupid slow code. Always use at least -O / -O1 (same thing).

author: HwAoRrDk
author_rank: Super Contributor
timestamp: February 16, 2023, 02:25:45 pm
content: Quote from: mblinov on February 15, 2023, 10:35:00 pmIn retrospect, probably because I was compiling with -g (no -Ox) - I forgot that was the case. Re-compiling with -Os, it doesn't do that.Quote from: mblinov on February 15, 2023, 10:35:00 pmI think I remember seeing mentioned elsewhere that WCH use customised GCC 8.x in their MounRiver IDE, but I'm not sure. I am using GCC 12.2.Quote from: Nominal Animal on February 15, 2023, 10:41:52 pmAh, good point. Yes, no need for OR-ing.

author: hans
author_rank: Super Contributor
timestamp: February 16, 2023, 05:37:44 pm
content: I do wonder why they bothered though. If DMA has the granularity of writing 16-bit data with 16-bit alignments (e.g. address 0x12), e.g. upper part of BSHR, it's completely redundant to BCR.Only in specific cases the CPU can save 1 instruction:https://godbolt.org/z/s43bc6T3PBut even that is subject to how the code is written.In fact, if 16-bit access is allowed, one could split up BSHR using an union and have the compiler address each 16-bit half independently.I was a bit surprised between the small difference between the code " 1<<pin<<16 " and " 1<<(16+pin) ".The former will load mask 2^16 and shift that by value 'pin', while the latter adds 16 to 'pin' and then performs the shift. I'm not sure why, because I think for unbounded values of pin (e.g. pin=240), the value is not going to experience an overflow (back to 0) since it gets cast to an 'int' which is 32-bit wide. And for sure it doesn't have to take overflow of 'int' into account, as it's UB.Therefore the compiler should be able to rearrange " C << (x+16) " to " C << 16 << x " if I'm not mistaken. But perhaps I am, the only thing I remember is that UB w.r.t. signed/unsigned overflows are always cause for troubleAnd honestly, if these details are of concern in one's firmware to set/clear pins in adequate time, then I have more questions to ask.