I work with microcontrollers where there are occasionally machine registers that have actions that occur when a register is read. Yes, that's not a typo, there's lots of registers that cause actions when they are written, but in a few cases if you read a register, something happens.
The most common instance of this is a UART receive register hooked up to one end of a FIFO; for example let's say there is RXDATA. When you read RXDATA it pulls one byte out of the FIFO and the next time you read RXDATA it will get the next byte.
Is there enough information in volatile to get the compiler to understand that there might be side effects from a read?
Example fragment in C:
#include <stdint.h>
#include <stdbool.h>
volatile uint8_t RXDATA;
// there is some mechanism for associating this with a known hardware address
// (either in linker information, or in some compiler-specific attribute not shown)
// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_1()
{
const uint8_t rxdata = RXDATA;
return (rxdata & 1) && ((rxdata & 0x80) == 0);
}
// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_2()
{
return (RXDATA & 1) && ((RXDATA & 0x80) == 0);
}
// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_3()
{
const bool bit_0_is_1 = RXDATA & 1;
const bool bit_7_is_0 = (RXDATA & 0x80) == 0;
return bit_0_is_1 && bit_7_is_0;
}
If I ignore the C standard and pretend that a compiler does exactly what I think I am asking it to do (DWIM), then my intuition is that these three functions have different behavior:
- In the first case, we read
RXDATAonce, so we pull out one byte of the FIFO and then do some math on it. - In the second case, we read
RXDATAeither once or twice (because&&has short-circuit behavior), doing math directly on the register value, so we might either pull out one or two bytes from the FIFO, and this has incorrect behavior. - In the third case, we read
RXDATAtwice, pulling two bytes from the FIFO, so this is incorrect behavior.
Whereas if RXDATA isn't volatile then presumably all three of the above implementations are equivalent.
Does the C standard require the compiler to interpret volatile in this case in the same way I am looking at it? If not, how can a hardware register be handled properly in C?