Theoretical problem with accessing to 16-bit register

Go To Last Post
18 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi, my problem is rather theoretical, but I think it is interesting to others. Let's assume I want to access 16-bit register, for example ADC. I can use int a=ADC, and it works perfectly. I can use int a=ADCL | (ADCH<<8); and it should work ok if the compiler will not reorder the sequence of ADC readings. So my question is "“ according to C standard is it possible that optimizer will reorder the ADC read sequence, so ADCH will be read first and ruin ADC result?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When you tell the compiler witch chip it's for, it will (should) know that kind of traps and make the correct code.

Edit I stay corrected sorry.

Last Edited: Wed. Sep 8, 2010 - 11:43 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When

Quote:
you tell the compiler witch chip it's for, it will (should) know that kind of traps and make the correct code.
This is untrue in the second situation. The compiler has absolutely no knowledge that ADCL should be read first. In the line:

int a = ADCL + (ADCH << 8);

the compiler is free to access the two registers in whatever order it wants. The line:

int a = ADC;

it is guaranteed that the access is in the correct order,

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If "ADC" did not exist then I'll bet the compiler does not generate any more code for:

int a = ADCL;
a |= (ADCH << 8);

than it does for

int a = ADCL | (ADCH << 8);

Carriage returns cost nothing when writing clear C code. Programmers are far too often tempted to make complex compound statements because they don't trust the optimiser.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
If "ADC" did not exist then I'll bet the compiler does not generate any more code for:

int a = ADCL;
a |= (ADCH << 8);

than it does for

int a = ADCL | (ADCH << 8);

Carriage returns cost nothing when writing clear C code. Programmers are far too often tempted to make complex compound statements because they don't trust the optimiser.

It is NOT the carriage return (which, after preprocessing and lexical analysis, is ignored by the compiler as any whitespace), it is so-to-say the semicolon which makes difference. That states a sequence point (6.8#4; Annex C lists all sequence points), at which point all side effects (volatile reads and writes) must be finished (5.1.2.3#2).

Otherwise, if you have a compound expression, "the order of evaluation
of subexpressions and the order in which side effects takeplace are both unspecified." (6.5.#4). This may have surprising consequences for the unaware not only with volatile (peripheral) accesses, see C-faq's http://c-faq.com/expr/precvsooe.... or better the whole chapter http://c-faq.com/expr/index.html .

JW

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Jan,

Yes I know that - but what I meant is that it doesn't hurt to break down a compound statement into separate parts on new lines (by implication that means ending each with ; ). People should get into the habit of looking at the generated Asm. In this case:

#include 

void use_var(uint16_t);

int main(void) {
	uint16_t a;
	
	a = ADCL; 
	a |= (ADCH << 8); 
	use_var(a);

	a = ADCL | (ADCH << 8);
	use_var(a);

	a = ADC;
	use_var(a);
}

generates:

	a = ADCL; 
  6c:	24 b1       	in	r18, 0x04	; 4
  6e:	30 e0       	ldi	r19, 0x00	; 0
	a |= (ADCH << 8); 
  70:	45 b1       	in	r20, 0x05	; 5
	use_var(a);
  72:	94 2f       	mov	r25, r20
  74:	80 e0       	ldi	r24, 0x00	; 0
  76:	82 2b       	or	r24, r18
  78:	93 2b       	or	r25, r19
  7a:	0e 94 4b 00 	call	0x96	; 0x96 

and

	a = ADCL | (ADCH << 8);
  7e:	24 b1       	in	r18, 0x04	; 4
  80:	45 b1       	in	r20, 0x05	; 5
	use_var(a);
  82:	94 2f       	mov	r25, r20
  84:	80 e0       	ldi	r24, 0x00	; 0
  86:	30 e0       	ldi	r19, 0x00	; 0
  88:	82 2b       	or	r24, r18
  8a:	93 2b       	or	r25, r19
  8c:	0e 94 4b 00 	call	0x96	; 0x96 

In this case clarity and guaranteed order of access "costs" just an "ldi r19,0"

The use of GCC's "ADC" is the most efficient though:

	a = ADC;
  90:	84 b1       	in	r24, 0x04	; 4
  92:	95 b1       	in	r25, 0x05	; 5
	use_var(a);
  94:	0e 94 4f 00 	call	0x9e	; 0x9e 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But the problem is that you are still not garanteed that the code will stay in order! (Remember the CLI SEI problem) .

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

But the problem is that you are still not garanteed that the code will stay in order!

But that's not true of C when the source/destination is volatile - the problem is just asm("") inserted in amongst the C where "volatile" doesn't mean what we think at all.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry I could be wrong, but my impression was just that volatile means that it has to read/store the correct place, but not necessarily following the order!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
[...]what I meant is that it doesn't hurt to break down a compound statement into separate parts on new lines (by implication that means ending each with ; ).

What I meant is that it is *necessary* to do so (the CR stuff was just some extra hair splitting :-) )

Jan

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:
Sorry I could be wrong, but my impression was just that volatile means that it has to read/store the correct place, but not necessarily following the order!
True.
I, quoting C99, above wrote:
"the order of evaluation
of subexpressions and the order in which side effects takeplace are both unspecified." (6.5.#4)

---
That avr-gcc handles "correctly" the 16-bit SFRs is an extension; in the light of C99 it IMHO falls into the "what is volatile access is implementation defined".

However it should be said explicitly and loudly in the documentation (if there would be any... :-( ), to avoid possible confusion when somebody ports/moves to a different AVR C compiler.

JW

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, so as I understood the conclusion is that int a=ADC; is always safe and is a recommended way of reading a 16-bit register. It is implemented as compiler extension. Reading separately ADCL and ADCH should work ok, as ADCL and ADCH are volatile, and compiler cannot reorder the sequence of access to ADC. But if I put everything in one line like int a=ADCL | (ADCH<<8)); it can work, but it is not guaranteed, and it should be avoided? Correct? I have some dubts, because if volatile is a barrer and compiler cannot reorder access to volatile register, it should worke exactly in the same way in case like int a=ADCL | (ADCH<<8));. Or compiler can reorder volatile subexpressions in one expression?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

TFrancuz wrote:
I have some dubts, because if volatile is a barrer

"barrier" is only a word. There is no mention of this word in the standard.
TFrancuz wrote:
and compiler cannot reorder access to volatile register,

Who said that?

TFrancuz wrote:
Or compiler can reorder volatile subexpressions in one expression?
C99 (quoted already 3rd time in this thread) wrote:
the order of evaluation of subexpressions and the order in which side effects take place are both unspecified. (6.5.#4)

If you read up in the standard what "side effects" mean (volatile access is the prime example of "side effects"), I don't think this can be any clearer.

JW

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, so the only 100% legal way of accessing ADC is int a=ADC; everything else could work, but it is not guaranteed? Correct?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Correct?

Incorrect. C statments with volatile source/destination will be executed in the order given. The "split" solution will always work. But why bother when "ADC" exists and is proven to be the most efficient solution?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
The "split" solution will always work
Who gave you that warranty?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

TFrancuz wrote:
everything else could work, but it is not guaranteed? Correct?

No. Volatile accesses are guaranteed to be completed at the access points - I wrote about this above in this thread too. Please re-read my posts and the relevant chapters in the standard before you post further questions to this topic.

Cliff wrote:
But why bother when "ADC" exists and is proven to be the most efficient solution?
Because you might want to write more portable programs; because you might want to understand how things work behind the scene; because you might want to have a solution for cases when the compiler does not provide a pre-chewed solution.

Are 3 minority reasons enough?

JW

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:
Quote:
The "split" solution will always work
Who gave you that warranty?

5.1.2.3#2