Erratum "Delay after an RCC peripheral clock enabling" - suggested workaround fails to work
[This is a reconstruction of a thread I've started on Apr 7, 2017, but got lost in the two forum software transitions since then.]
In STM32F40x and STM32F41x Errata sheet DocID022183 Rev 8, 2.1.13 Delay after an RCC peripheral clock enabling, there are three workarounds suggested.
The third one is:
3. Or simply insert a dummy read operation from the corresponding register just after enabling the peripheral clock.
It is not clear, what is "corresponding register" - is it the register of RCC, or the register of peripheral which clock is being enabled?
However, regardless of this, it does not appear to be working in either case. In the following code (with all clocks settings at their reset defaults):
8000214: 2201 movs r2, #1
8000216: 6b18 ldr r0, [r3, #48] ; 0x30
8000218: f440 5080 orr.w r0, r0, #4096 ; 0x1000
800021c: 6318 str r0, [r3, #48] ; 0x30
800021e: 6b18 ldr r0, [r3, #48] ; 0x30 <------------------ readback
8000220: 600a str r2, [r1, #0]
8000222: 2000 movs r0, #0
8000224: bf00 nop
8000226: bf00 nop
where r3 is preloaded with RCC address and r1 with CRC address, the readback is performed just after the write to RCC, and subsequently the now-presumably-enabled CRC data register is written, which should result in CRC for one word containing 0x00000001 being calculated, so CRC->DR should read as 0xC3C5C0CC. However, placing a breakpoint at the last nop and reading out CRC->DR shows that it is at 0xFFFFFFFF, so the previous write to CRC->DR has been ignored.
I tried also
800021e: 6808 ldr r0, [r1, #0] <------------------ readback
with the same result.
The two nops or one dsb at that place (i.e. the two other suggested workarounds) work as expected, CRC->DR contains 0xC3C5C0CC at the breakpoint.
ST, please comment.
JW
PS. I experimented with this as it appears to have a similar timing relationship as the CRC-reset-to-data issue I reported here {link leads to my own thread titled "CRC data ignored after CRC Reset" which is another lost thread, to which a later thread links, too}{edit - original thread reconstructed}.
---
{Clive Two.Zero} {Apr 7, 2017 5:15 PM}
Perhaps it is not exactly the same errata, but one specific to the logic of the CRC peripheral. The original errata is one where a pipelined back-to-back write via the write buffer (enabling clock, writing peripheral) provided no margin.
Adding the read of the RCC register after the write gave the peripheral additional clocks to recognize it had just come out of a reset state with clocks for the first time.
The CRC peripheral clearly seems to need more cycles of its state machine to get its act together. It speaks to a deficiency in the CRC logic, because it should be possible to do single cycle computation (in HW its way faster than a 32-bit full-adder)
---
{waclawek.jan} @ {Clive Two.Zero} on {Apr 8, 2017 12:17 PM}
Perhaps it is not exactly the same errata, but one specific to the logic of the CRC peripheral.
Agree. I will retry on Monday with GPIO.
JW
---
{Clive Two.Zero} @ {waclawek.jan} on {Apr 8, 2017 6:15 PM}
I generally try to push the clock stuff up earlier in the initialization. Have you profiled the power consumption of the CRC peripheral? ie Clock disabled, vs clock enabled but peripheral unused.
The APB/AHB clock hazard is more systemic, a bit like the Pipeline/Write-Buffer issue getting IRQ clearing quickly enough to propagate to the Tail-Chaining logic.
ARM has generally eschewed putting logic in to hide all hazards. Intel erects a lot of cliff top fencing to save every lemming.
---
{waclawek.jan} @ {Clive Two.Zero} on {Apr 8, 2017 8:53 PM}
> I generally try to push the clock stuff up earlier in the initialization.
Me too.
> Have you profiled the power consumption of the CRC peripheral?
No. As I've said above, I was trying to find a simple, fast but safe solution to the {CRC problem} I discovered earlier (and it was lots of fun finding, as it was the compiler which kept putting the CRC-reset and CRC-data-write instructions a random number of other instructions apart upon attempts to add debugging code around). Yes a number of nops appear to solve the problem but then I'd have to resort to inline assembler, which is not my forte - gcc is kind enough to reorder NOPs inserted as standalone/macros, too.
I looked at the errata as a source of inspiration.
> ARM has generally eschewed putting logic in to hide all hazards. Intel erects a lot of cliff top fencing to save every lemming.
I'd say in this case it's ST's guilt. As in the case of consecutive writes to CRC->DR, the peripherals (including RCC) which require guard times may insert a sufficient number of waitstates, or provide a readback indicator. At the end of the day, I am absolutely content with things being dangerous, as long as they are clearly and concisely documented, possibly with an accompanying simple and clean program example - which unfortunately is far from being the case.
Jan
---
{waclawek.jan} @ {waclawek.jan} on {Apr 10, 2017 10:02 AM}
Confirming the same problem still pertains when writing to GPIO (an APB-related readback might make a difference in case of enabling APB-based peripherals but I am not interested in experimenting further with what is ST's task to ensure. [EDIT] OK so I tried with APB1/TIM2, and the problem did not show up at all, i.e. write to TIM2_CR1 after immediately after write to RCC_APB1ENR was successful, no matter what the AHB/APB divider was.)
Interestingly, when write buffer on processor-to-bus interface is disabled, one nop is sufficient, but ldr still not (IMO this is related to processor giving up/reacquiring the bus in case of nops, while still keeping the bus in case of successive st/ld - but again this is ST's task to explain).
As I've said, my inline asm skills are not the top, so please don't laugh too loudly. {indentations, tabulators lost, sorry}
// SCnSCB->ACTLR = SCnSCB_ACTLR_DISDEFWBUF_Msk; // reduces needed nr of nops
{
register uint32_t tmp1, tmp2;
__asm volatile(
"movs %[t1], #1 " "\n\t"
"ldr %[t2], [%[p2], #48] " "\n\t"
"orr %[t2], #4 " "\n\t"
"str %[t2], [%[p2], #48] " "\n\t"
#if(0)
"nop" "\n\t"
"nop" "\n\t"
#elif(0)
"dsb" "\n\t"
#elif(0)
"ldr %[t2], [%[p2], #48] " "\n\t"
#elif(1)
"ldr %[t2], [%[p], #0x14] " "\n\t"
#else
// nothing
#endif
"str %[t1], [%[p], #0x14]" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
:[t1] "=&r" (tmp1)
,[t2] "=&r" (tmp2)
:[p] "r" (GPIOC)
,[p2] "r" (RCC) );
}
JW
