InLine Assembler Pointer register Z required error

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

I have been porting some inline assembler code that uses lpm to access the nvm, from AS 6.2 to 7.0.

 

uint8_t nvm_read_product_sig_row_a(uint16_t offset)

{

uint8_t w_value_lo;

(void)offset; // Suppress unused parameter warning, doesn't do anything (thanks, stack overflow)

NVM_CMD = NVM_CMD_READ_CALIB_ROW_gc;

asm volatile ( "lpm %0, %a1":"=r"(w_value_lo):"e"(offset) ); // r18 is w_value_lo

// NVM_CMD = NVM_CMD_NO_OPERATION_gc;

return w_value_lo;

}

 

Compiler is well behaved and produces the code below which is as good as it gets.

00001ae2 <nvm_read_product_sig_row_a>:

    1ae2: 22 e0        ldi r18, 0x02 ; 2

    1ae4: 20 93 ca 01 sts 0x01CA, r18 ; 0x8001ca <__TEXT_REGION_LENGTH__+0x7001ca>

    1ae8: fc 01        movw r30, r24

    1aea: 84 91        lpm r24, Z

    1aec: 08 95        ret

 

I uncomment the line in bold above and I get the error "pointer register Z required' from the assembler.

 

This happens in Atmel studio 7.  6.2 produces the the assembler correctly..

00001af2 <nvm_read_product_sig_row_a>:

    1af2: 22 e0        ldi r18, 0x02 ; 2

    1af4: 20 93 ca 01 sts 0x01CA, r18

    1af8: fc 01        movw r30, r24

    1afa: 84 91        lpm r24, Z

    1afc: 10 92 ca 01 sts 0x01CA, r1

    1b00: 08 95        ret

 

I have tried replacing the commented line with the assembler that is produced by Studio 6.2 into the 7.0 build

asm volatile ( "sts %0, r1":"=m"(NVM_CMD));

..and still get the error. It seems associated with the lpm statement.

 

Cannot make sense of this. Is there anyone with some experience of inline assembler that might have any idea?

 

Thanks for the help.

 

This topic has a solution.
Last Edited: Wed. Oct 4, 2017 - 03:08 AM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

uint8_t nvm_read_product_sig_row_a(uint16_t offset) {

  uint8_t w_value_lo;

  (void)offset; // Suppress unused parameter warning, doesn't do anything (thanks, stack overflow)

  NVM_CMD = NVM_CMD_READ_CALIB_ROW_gc;

  __asm__ __volatile__ (
                        "lpm %0, %a1"
                      :
                        "=r" (w_value_lo)
                      :
                         "e" (offset)
                       ); // r18 is w_value_lo

  // NVM_CMD = NVM_CMD_NO_OPERATION_gc;

  return w_value_lo;

}

Isn't that easier to read?

 

Now, the "e" constraint tells the compiler to place the operand in a pointer register.  This could be any of the three pointer registers X, Y, or Z.  But lpm requires Z.  X and Y won't do.

 

I'd guess that by uncommenting the NVM_CMD... line, the compiler is allocating different registers for you than when it was with the line was commented.

 

You can use the "z" constraint instead.  This guarantees that the compiler will place offset in r31:30 i.e. Z.

 

If you use -save-temps when compiling, a .s file with the compiler's assembler output will be generated.  An examination of that file would have revealed the issue.

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

<never mind>

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

Last Edited: Sun. Oct 1, 2017 - 09:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I may be missing something here but if you want an LPM in GCC code I can think of two existing mechanisms:

 

1)

#include <avr/pgmspace.h>

...
    uint8_t w_value_lo;
    // NVM setup 
    w_value_low = pgm_read_byte(offset); // LPM a byte from *offset

2)

uint8_t nvm_read_product_sig_row_a(const __flash uint16_t * offset) {
    // NVM setup
    return *offset & 0xFF;
}

Why is neither of these suitable? Why does it need you to write inline asm?

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

Thanks for the code format instructions, missed that. Will certainly use in the future.

Away at present but the -save-temps is a great suggestion. Didn't realize I could access the generated source file. Will try that out when I get back to the task.

Much appreciate the response.

 

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

Wow, thanks for those suggestions. I use the pgm_read for strings but hadn't made the leap to the other nvm reads. Bits of inline are simple and have been working for me for a long time, until Atmel Studio 7. Will try these and see if I can make it work in the space restraints. The result would be much better portability. Much appreciate the response and help.

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

__flash is the modern alternative to pgm_read*() so for "new" code I would switch to using __flash instead.

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

Thank you @joeymorin for the suggestions. I built with -save-temps and the 's' file showed the problem was exactly as you said. Changed 'e' to the 'z' and the code was corrected. The compiler is very effective. I will reproduce the code (with the formatting:)) here so other passers-by can see how simple it turns out.

uint8_t nvm_read_product_sig_row_a(uint16_t offset)
{
	uint8_t w_value_lo;
	NVM_CMD = NVM_CMD_READ_CALIB_ROW_gc;
	asm volatile (	"lpm %0, %a1":"=r"(w_value_lo):"z"(offset) );
	NVM_CMD = NVM_CMD_NO_OPERATION_gc;
	return w_value_lo;
}

Produces the assembler code

00001ae2 <nvm_read_product_sig_row_a>:
    1ae2:	aa ec       	ldi	r26, 0xCA	; 202
    1ae4:	b1 e0       	ldi	r27, 0x01	; 1
    1ae6:	22 e0       	ldi	r18, 0x02	; 2
    1ae8:	2c 93       	st	X, r18
    1aea:	fc 01       	movw	r30, r24
    1aec:	84 91       	lpm	r24, Z
    1aee:	1c 92       	st	X, r1
    1af0:	08 95       	ret

Thank you also @clawson. I used your suggested 'pgm_read_byte' and that produced exactly the same code so is a much better choice than the in-line.  '__flash' isn't recognised but I suspect I simply need to read about it before using it.

 

I truly appreciate your comprehensive responses. You have saved me a lot of time and frustration and presented me with a simple direction.

Thank you both.

 

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

jrgrobinson wrote:
'__flash' isn't recognised but I suspect I simply need to read about it before using it.
Either your compiler is out of date or you are using C++

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

Using 'C'.  GCC with recent Atmel 7 install is 5.4.0.   Found my problem with __flash keyword, silly, too hurried. '__flash' gives the same code as the 'pgm_read_byte' macro but I do like '__flash' as it is a more obvious type definition.  Thanks for pointing it out.

 

 

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

It's not so much the difference between:

const int PROGMEM foo;

and

const __flash int bar;

but how they are used. You can either do:

ram_copy = pgm_read_word(&foo);

or with __flash:

ram_copy = bar;

THAT is when __flash comes into its own. No more pgm_read_*() needed.

 

There are also extensions to this such as __memx which allows for something that can be located in either __flash or RAM.

Last Edited: Wed. Oct 4, 2017 - 11:15 AM