Author Topic: Why does Cortex-M0(+) implement instructions like ISB if it has no cache?  (Read 17881 times)

0 Members and 1 Guest are viewing this topic.

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17824
  • Country: fr
In particular, when setting the clock(s) for the peripheral bus(es), you have to understand the implications in terms of synchronization delays with the system clock. Low frequency clocks for APBs yield more sync delays.
 

Offline Sauli

  • Contributor
  • Posts: 48
  • Country: fi
Quote from ARM application note 321:
Quote
Hardware latency
Software errors or race conditions can also be caused by latency in hardware designs. Examples
of hardware latency are the delay:
• between triggering interrupt generation at a peripheral by a memory access, to the time
the processor actually received the interrupt
• between enabling the clock to a peripheral, to the time the peripheral can be safely
accessed
• after switching memory map by writing to a system memory control register, to the time
that the new memory map arrangement becomes valid.
Note
These cannot be resolved using memory barrier instructions and should be addressed during
device specification.

I think the first example also holds true for clearing an interrupt request from a peripheral to the side effect of that reaching the NVIC. So the request can be active at NVIC even after the write has completed. How would the barrier instructions even be expected to handle this? How could they know that the active request at NVIC is just not yet cleared by the peripheral or if it actually is valid because the peripheral has another interrupt to be serviced? 
 
The following users thanked this post: Siwastaja, incf

Offline incfTopic starter

  • Frequent Contributor
  • **
  • Posts: 482
  • Country: us
  • ASCII > UTF8
The peripheral writes all generate wait states back to the CPU.

In practice, peripheral writes don't generate wait states. Peripheral reads do generate wait states.

In most CPUs peripheral writes are buffered by something like the AHB-APB bridge (eg. STM32s). And it takes setting bits in the MPU to disable write buffering on peripherals. (Most small ARMs don't have a MPU and therefore cannot disable any buffering)
Test
 

Online peter-h

  • Super Contributor
  • ***
  • Posts: 6095
  • Country: gb
  • Doing electronics since the 1960s...
Yes I meant reads cause wait states. Writes must be buffered otherwise every time a 500MHz CPU wrote to some UART, it would lose a load of time.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline gpr

  • Contributor
  • Posts: 43
  • Country: gb
What is also interesting, is how flag clear write behaves. For example, code like this:
Code: [Select]
dma_interrupt_handler()
{
  if (DMA1->ISR & DMA_ISR_TCIF4) {
    DMA1->IFCR = DMA_IFCR_CTCIF4;
    do_something();
}

Should I wait for ISR flag to clear before returning from interrupt handler and write something like this?
Code: [Select]
dma_interrupt_handler()
{
  if (DMA1->ISR & DMA_ISR_TCIF4) {
    // wait for flag to become zero in ISR
    while (DMA1->ISR & DMA_ISR_TCIF4) {
      DMA1->IFCR = DMA_IFCR_CTCIF4;
    }
    do_something();
}
 

Offline incfTopic starter

  • Frequent Contributor
  • **
  • Posts: 482
  • Country: us
  • ASCII > UTF8
Technically, yes, but it is overkill for most peripherals.
Test
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 4610
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
 

Online peter-h

  • Super Contributor
  • ***
  • Posts: 6095
  • Country: gb
  • Doing electronics since the 1960s...
AN321 is interesting but you need to spend hours reading it.

It describes where DSB etc are needed, but also says that mostly none of these are needed in reality since any delays sort themselves out in 1 or 2 instructions. If you are coding in C, you are gonna get more than 1 or 2 instructions in most cases ;)

I can also see another reason why this does not bite lots of people in the bum: in most embedded systems, one is not doing weird stuff like disabling a peripheral interrupt and especially then assuming it will be immediate. Let's say you have a UART receiving data. If you no longer want that data, you chuck it away in the ISR (by not emptying the RX circular buffer).
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline incfTopic starter

  • Frequent Contributor
  • **
  • Posts: 482
  • Country: us
  • ASCII > UTF8
I think we need an architect to answer this question.

I even question this solution now:
https://www.eevblog.com/forum/microcontrollers/why-does-cortex-m0()-implement-instruction-like-isb-if-it-has-no-cache/msg5837941/#msg5837941
Per ARM AN321, section "4.6 Disabling Interrupts using NVIC"

Code: [Select]
NVIC_DisableIRQ(device_IRQn); // Disable interrupt (C macro that translates into one or two instructions to calculate and store the new register value)
__DSB(); //DSB is optional on Cortex-M, but is good practice
__ISB(); //ISB is absolutely required for all Cortex-M devices

Is ARM's recommended way to do it.*

NVIC and all System Control Block peripheral registers are special in that they respect memory barrier instructions.

The only thing that saves most folks is short pipeline length on most cores, and rareness of that 1-2 clock cycle window actually mattering

External peripherals (eg. RTC, UART) are a totally different matter since (1) they don't respect memory barrier instructions (2) writes to the peripheral busses are usually buffered (3) IRQ pending lines for external peripherals often have clock synchronizers on them that delay the effect of clearing an ISR pending/enabled bit in the peripheral.

*See "3.5 Cortex-M processor implementation specific requirements" for cortex M3/M4 vs M0 vs M0+

This single screenshot/except from AN321 (plus the one or two other excepts that I posted earlier) more or less explain my original question, and all of the follow up questions about memory barriers (and things like ISRs) on Cortex-M0+

« Last Edit: March 13, 2025, 12:33:17 pm by incf »
Test
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 11327
  • Country: fi
In other words, you are saying that DSB is only effective to limited set of ARM-supplied registers, in NVIC and SCB block, and never any peripheral register as implemented by, for example, ST; not even AHB/APB buffers/arbiters/clock domain synchronizers.

I had always assumed, based on absence of documentation and rarity of problems, that special synchronization sequences of reading back peripheral registers are exceptions and documented on case-by-case basis, basically limited to when manufacturer implements a peripheral so that it uses separate clock domains for AHB/APB interface and actual storage of the registers internally, for whatever reason. So that DSB would be effective up to the point where the peripheral connects to the bus. But it seems I have been completely wrong.

This is good to keep in mind for those rare corner cases where naturally occurring delays from code are not sufficient. A communication peripheral (UART, SPI, I2C...) with "clear-interrupt-flag-on-data-register-access" pattern, when used at very slow peripheral clock, while CPU runs order of magnitude faster, would be such a case.
« Last Edit: March 13, 2025, 12:48:26 pm by Siwastaja »
 

Online langwadt

  • Super Contributor
  • ***
  • Posts: 5826
  • Country: dk
I can also see another reason why this does not bite lots of people in the bum: in most embedded systems, one is not doing weird stuff like disabling a peripheral interrupt and especially then assuming it will be immediate. Let's say you have a UART receiving data. If you no longer want that data, you chuck it away in the ISR (by not emptying the RX circular buffer).

for a UART transmitting data it is not uncommon to enable TX interrupt after you put something in the buffer, and in the TX interrupt disable the TX interrupt when you send the last data in the buffer 
 

Online peter-h

  • Super Contributor
  • ***
  • Posts: 6095
  • Country: gb
  • Doing electronics since the 1960s...
That works as expected because when there is no more to send out, the UART will not interrupt. So your TX interrupt disable will work fine. You control the cause of the TX interrupt.

On received data, you might get more RX interrupt(s) after you disabled them.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 11327
  • Country: fi
That works as expected because when there is no more to send out, the UART will not interrupt. So your TX interrupt disable will work fine. You control the cause of the TX interrupt.

On received data, you might get more RX interrupt(s) after you disabled them.

Surely langwadt would mean something like "TX buffer empty" interrupt or similar. That would continue generating interrupts, so disabling it and having the flag propagate all the way into NVIC before exiting the ISR would be necessary, no?
 

Online langwadt

  • Super Contributor
  • ***
  • Posts: 5826
  • Country: dk
That works as expected because when there is no more to send out, the UART will not interrupt. So your TX interrupt disable will work fine. You control the cause of the TX interrupt.

On received data, you might get more RX interrupt(s) after you disabled them.

Surely langwadt would mean something like "TX buffer empty" interrupt or similar. That would continue generating interrupts, so disabling it and having the flag propagate all the way into NVIC before exiting the ISR would be necessary, no?

yep
 

Online peter-h

  • Super Contributor
  • ***
  • Posts: 6095
  • Country: gb
  • Doing electronics since the 1960s...
It would be in the "TX buffer empty" ISR where one would disable further interrupts if there is no more to transmit.

I suppose one could do it in the "TX buffer has room" interrupt... But in that case the next interrupt won't happen for a whole character period, no?
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 5410
  • Country: si
The TX buffer on the UART wouldn't pose problems because a CPU write triggers all the actions that happen with it.

Where the problem pops up with peripherals that work autonomously to generate an interrupt. Like a UART RX buffer interrupt, Timer interrupt, GPIO edge interrupt etc.. As that has a random chance of triggering within the next few cycles of an CPU instruction that disabled that interrupt. However it is very rare that the softwares particular use case actually breaks if this happens, it will typically look as if that event occurred a few fractions of a microsecond earlier.

But yes theoretically there is a way to detect this if your code is written in an certain way. You would need to have a write to an interrupt disabling register quickly followed by an instruction that reads memory from a location that gets written inside the interrupt handler. If you wrap this write in a function call you likely already solved the paradox as the time it takes the CPU to return from that function would already be long enough for the IRQ to make it trough before any extra code gets executed (branches are annoying for CPUs because they have to flush the pipeline and/or push/pop state so it can take a significant number of CPU cycles)
« Last Edit: March 14, 2025, 11:38:04 am by Berni »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

- - -
-->