Software stack

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

Hi Guys

Is the compiler in AS6.1 utilize a software stack? Or does it only use the hardware one? Thanks.

Zhuhua Wu - Electronic Engineering Student

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

Unlike most other C compilers for AVR the avr-gcc in AS6.1 uses only a single (hardware stack) and it mixes both stack frame data and call/ret/saved registers all on the single stack. Other compilers use the hardware for call/ret and a soft stack for the automatic data. GCC uses the Y register pair as a stack frame pointer to be able to access the automatics amongst the other data on the stack.

If I write this:

void foo(void) {
	volatile int n;
	char buff[10];
	n = PINA * 23;
	sprintf(buff, "val is %d", n);
	uart_putstring(buff);
}

it generates:

.global	foo
	.type	foo, @function
foo:
//==> void foo(void) {
	push r16
	push r17
	push r28
	push r29
	in r28,__SP_L__
	in r29,__SP_H__
	sbiw r28,12
	in __tmp_reg__,__SREG__
	cli
	out __SP_H__,r29
	out __SREG__,__tmp_reg__
	out __SP_L__,r28
/* prologue: function */
/* frame size = 12 */
/* stack size = 16 */
.L__stack_usage = 16
//==> 	n = PINA * 23;
	in r24,0
	ldi r18,lo8(23)
	mul r24,r18
	movw r24,r0
	clr __zero_reg__
	std Y+12,r25
	std Y+11,r24
//==> 	sprintf(buff, "val is %d", n);
	ldd r24,Y+11
	ldd r25,Y+12
	push r25
	push r24
	ldi r24,lo8(.LC0)
	ldi r25,hi8(.LC0)
	push r25
	push r24
	movw r16,r28
	subi r16,-1
	sbci r17,-1
	push r17
	push r16
	call sprintf
//==> 	uart_putstring(buff);
	movw r24,r16
	call uart_putstring
	pop __tmp_reg__
	pop __tmp_reg__
	pop __tmp_reg__
	pop __tmp_reg__
	pop __tmp_reg__
	pop __tmp_reg__
/* epilogue start */
//==> }
	adiw r28,12
	in __tmp_reg__,__SREG__
	cli
	out __SP_H__,r29
	out __SREG__,__tmp_reg__
	out __SP_L__,r28
	pop r29
	pop r28
	pop r17
	pop r16
	ret

You can see it starts by preserving r16/17/28/29. It then reads the stack pointer and subtracts 12 from it (2 bytes for the int and 10 for the char buffer). Disabling I it then writes the new value back to SP and because it used R28/29 in the calculation it has left Y pointing to the 12 bytes. It then enters the function code itself and reads PINA and does the multiplication storing the new value of 'n' at Y+11/Y+12 (Y+1 to Y+10 is "buffer"). When it calls sprintf() is does a movw r16,r28 to copy Y into 16/17. It subtracts 1 (because the Y indexing is 1 based not 0 based) and pushes this address as an input parameter to sprintf(). After the call to sprintf() it clears all the stacked parameters with all this "pop __tmp_reg__"s and then in the epilogue of the function it adjusts SP back up by 12 bytes with that "adiw r28,12".

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

Hi clawson

Thanks for your details explanation, and sorry I don't know much about assembly.

So does the compiler assign a fix size for the stack, or does it just start the stack from the highest address, and hope the data and in ram and stack doesn't crash?

Or are different chips have different fix stack size in the physical chip?

Hope I am making sense and not asking silly questions.

Thanks

Zhuhua Wu - Electronic Engineering Student

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

It just starts the stack at the highest address and goes.

All of the modern 8/16 bit micros I know (with the exception of several VERY small chips) mix the stack and the data ram. Some of the older chips had a call/return stack that was only a few slots deep, as I recall. These were NOT designed for C with parameter passing. Those stacks were intended only for storing interrupt return addresses.

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

Thanks Jim

If I understand it correctly, say a chip has 1K RAM, the stack size could in theory range from 0 to 1K?

Zhuhua Wu - Electronic Engineering Student

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

Yes.

Regards,
Steve A.

The Board helps those that help themselves.

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

Thanks guys

Zhuhua Wu - Electronic Engineering Student

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

One needs to be a bit careful because 16 bit return addresses in an 8 bit stack (like most AVRs) can only handle a theoretical maximum of 512 return addresses in a 1K device.

Jim

Jim Wagner Oregon Research Electronics, Consulting Div. Tangent, OR, USA http://www.orelectronics.net

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

This page in the user manual:

http://www.nongnu.org/avr-libc/u...

and this diagram in particular:

are very useful. Although the diagram covers the case of something like a mega128 that can have external RAM ignore the grey bit to the right and it reflects pretty much all other AVRs.

Yes the CRT that runs before entry to main() sets SP to RAMEND which is why you see that "brown" area with the arrow pointing left representing the stack area where call/ret/register/automatics will be stored. It works its way down towards lower memory addresses (arrow left because this diagram is on the side).

Meanwhile any global variables you create that are assigned an initial value such as:

int n = 12345;
char text[] = "hello world";

will be placed in .data which is the cyan area at the left and is at the lowest memory addresses. Any global variables you create but that are not assigned initial values:

long l;
short nums[20];

will next be placed in a section immediately after .data called .bss (block started by symbol) and the CRT code wipes all this to 0 before entry to main() which is why you can rely on non-init globals holding 0.

If you do use malloc()/free() then the "heap" that allocated blocks of memory from grows from just beyond .bss upwards. However most embedded programs on 1..4K RAM micros don't use the heap, so usually you just have .data, .bss and the stack in memory. As .data and .bss are a fixed size (set at compile time which is why avr-size can tell you how big they are when you build the code) the only concern would be if your use of the stack and automatic (local) variables in particular was so large that it caused the lower limit of the stack to come down so far that the brown area began to clash with the end of .bss (the green area). As long as you avoid doing things like:

void a_function(void) {
  char hugebuffer[2048];
...
}

You should be OK though.

Finally I just realised that I forgot to move this very GCC specific topic to that forum the other day so I'll do that now.

PS

Quote:
sorry I don't know much about assembly.

Time to learn then - it will make you a far better C/AVR programmer.

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

ka7ehk wrote:
One needs to be a bit careful because 16 bit return addresses in an 8 bit stack (like most AVRs) can only handle a theoretical maximum of 512 return addresses in a 1K device.

Jim

Thanks Jim, this is something likely overlook by me if you don't kindly point it out.

Zhuhua Wu - Electronic Engineering Student

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

clawson wrote:
This page in the user manual:

http://www.nongnu.org/avr-libc/u...

and this diagram in particular:

are very useful. Although the diagram covers the case of something like a mega128 that can have external RAM ignore the grey bit to the right and it reflects pretty much all other AVRs....

Thanks again clawson, that's very helpful info, the picture + your explanation helps a lot.

Quote:

Quote:
sorry I don't know much about assembly.

Time to learn then - it will make you a far better C/AVR programmer.

To be precise, I do know assembly of other MCU, but I think you are right, I should start learning AVR assembly too. :D

Zhuhua Wu - Electronic Engineering Student

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

Quote:

I should start learning AVR assembly too

On the whole you only need to be able to read it not write it (which is more complex as you kind of have to remember every opcode and know when you might use it). Just get in the habit of looking through the .lss when you build and also consider using a mixed C+Asm view in the debugger/simulator. When you are looking at disassembly the [Step] works for one opcode at a time rather than one whole C statement at a time. It's also useful to keep a "Processor" window open and see what's happening in the registers (each new update is colored red).

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

clawson wrote:
Quote:

I should start learning AVR assembly too

On the whole you only need to be able to read it not write it (which is more complex as you kind of have to remember every opcode and know when you might use it). Just get in the habit of looking through the .lss when you build and also consider using a mixed C+Asm view in the debugger/simulator. When you are looking at disassembly the [Step] works for one opcode at a time rather than one whole C statement at a time. It's also useful to keep a "Processor" window open and see what's happening in the registers (each new update is colored red).

Note taken! :D Thanks again Clawson

Zhuhua Wu - Electronic Engineering Student