0

I am trying to write some conditional jumps in AVR assembly using AVR-gcc. According to AVR instruction set manual, the brxx instructions take in an operand k, and jumps to PC+k+1. Also, according to the tutorial PDF from http://www.avrbeginners.net/new/tutorials/jumps-calls-and-the-stack/, I should be able to use the PC operand to jump like this:

brne PC+2

However, when I write such test code:

#include <avr/io.h>
.section .text
.global main ; Note [5]
main:
    sbi _SFR_IO_ADDR(DDRA), PA0
    sbi _SFR_IO_ADDR(PORTA), PA0
    ldi 16, 0xFF
    cpi 16, 0xFF
    breq PC + 2
    cbi _SFR_IO_ADDR(PORTA), PA0
    rjmp end

end:
    rjmp end

I get this error:

avr-gcc -mmcu="atmega16" -DF_CPU="16000000UL" -O0 main.S -o main.o
/tmp/ccAa2ySf.o: In function `main':
(.text+0x8): undefined reference to `PC'
collect2: ld returned 1 exit status
make: *** [main.o] Error 1

Apparently PC is not defined in AVR-libc. Then how am I going to do such condition branch? Thanks!

Update 1

I found this question How can I jump relative to the PC using the gnu assembler for AVR? and found that the syntax for gnu as is breq .+2. However, I get the same error as that question have. When I disassemble using avr-objdump -d main.o, I do get

  74:   01 f0           breq    .+0             ;  0x76

Which is the same symptom as that question. I will try using linker script, but I have no experience in that.

Update 2

Actually I found that if I use even numbers in the breq instruction, like breq .+2 or breq .+4, the objdump shows correct result. However, if I use odd numbers, it will become breq .+0. Can someone explain why?

Community
  • 1
  • 1
Carl Dong
  • 1,299
  • 15
  • 26
  • Jump to labels. Why would you ever jump using a magic number? That defeats a big part of the reason for using an assembler in the first place. – TomServo Oct 31 '22 at 22:14

3 Answers3

0

OK, the answer is totally rewritten now. This is what I understand from the objdump of compiled C codes. Firstly, binutils uses byte addressing, not word addressing, for the program counter, and starts at the instruction right after the current one. This is explained in the following code:

#include <avr/io.h>
.section .text
.global main
main:
    sbi _SFR_IO_ADDR(DDRA), PA0
    sbi _SFR_IO_ADDR(PORTA), PA0
    ldi 16, 0xFF
    cpi 16, 0xFF
    breq .+4  ;; If we are executing here
    cbi _SFR_IO_ADDR(PORTA), PA0  ;; This is .+0, will be skipped
    cbi _SFR_IO_ADDR(PORTA), PA0  ;; This is .+2, will be skipped
    cbi _SFR_IO_ADDR(PORTA), PA0  ;; This is .+4, which will be executed
    rjmp end

end:
    rjmp end

Apparently, the PC width has nothing to do with relative address. It only affects the maximum PC value, either 0xFF or 0xFFF, so no matter what AVR platform I am compiling for, binutils uses two bytes for an instruction.

P.S. I think, if the only way I can know how a compiler works is to observe how it works, probably that means poor documentation? Or maybe I just don't know when to start. If someone see this, could you help pointing some useful books about 'this kind of things'? (I don't even know how to describe it) Thanks!

Carl Dong
  • 1,299
  • 15
  • 26
  • binutils uses 2 bytes because all instructions generate "16-bits opcodes". It is not binutils it is the AVR Instruction Set specification. – sessyargc.jp Sep 24 '14 at 00:17
0

An 8-bit MCU does not mean the assembly instructions are encoded as an 8-bit opcode. From the ATmega16 specification.

Most AVR instructions have a single 16-bit word format.

On the contratry even if the ATmega are 8-bits MCUs the instructions used are encoded as 16-bits opcodes. Look at the "AVR Instruction Set". This is the reason the program counter (PC) behaves as such (only assignments to 16-bit/2-byte aligned addresses). If it were able to be set to an 8-bit/1-byte aligned address it will try to execute an invalid opcode!

Here's a thing for you to do. Compile your example above to an object file. Then disassemble the file (use objdump -D) and look at the generated disassembly. The offsets of the instructions should be 16-bit aligned.

sessyargc.jp
  • 573
  • 5
  • 15
0

Then how am I going to do such condition branch?

Just define a label and branch to it. The assembler will calculate the offset for you!

    brne some_label2
    ; code1
some_label2:
    ; code2

In the case when the branch target is out of reach, do a jumpity-jump on the reversed condition:

    breq some_label1
    [r]jmp some_labe2
some_label1:
    ; code1
some_label2:
    ; code2

The GNU assembler also supports a special kind of labels, which is just some number, and you can use the same label more than once. The jump target is the first to be found in forward direction resp. backward direction:

1:
    ; code 1
    brne 1b ; jump to label 1 above (backwards)
    brcc 1f ; jump to label 1 below (forwards)
    ; code 2
1:

This might be useful when you are writing assembly macros that contain local labels.

Specifically to be used in assembly macros, there is also pseudo variable \@ which is increased with every macro use, and thus can also be used to declare labels without conflicts:

.macro loop reg
    .Lloop\@:
        dec \reg
        brne .Lloop\@
.endm

    loop r16
    loop r16

How to access the PC Pointer

If you really need the value of the program counter for some obscure reason, you can

    rcall .
#ifdef __AVR_3_BYTE_PC__
    pop  r18
#endif 
    pop  r17
    pop  r16

and you have the word-address of the code location right after the rcall. Symbol . is the assembler's "current location".

Depending on the situation, it might be easier to just define a label and take the address of it:

main:
    ldi r16,lo8(main)     ; Byte-address, low byte
    ldi r17,hi8(main)     ; Byte-address, high byte
    ldi r18,hh8(main)     ; Byte-address, highest byte

    ldi r19,pm_lo8(main)  ; Word-address, low byte
    ldi r20,pm_hi8(main)  ; Word-address, high byte
    ldi r21,pm_hh8(main)  ; Word-address, highest byte

    ldi r22,lo8(gs(main)) ; Word-address where the linker will 
    ldi r23,hi8(gs(main)) ; generate a stub as needed.
emacs drives me nuts
  • 2,785
  • 13
  • 23