Strange observation: JMP and CALL are legal instructions on ATtinys

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

I am currently working on a debugWIRE debugger and along the way make a number of strange observations. One recent one is that contrary to what is claimed in the data sheets, ATtinys do support the 4-byte CALL and JMP instructions. I noted that when writing unit tests about executing 4-byte instructions. I guess it is a completely worthless observation, but one nevertheless wonders why this happened. If you want to try it for yourself,  use the following assembly program:

.org 0x0000

        rjmp MAIN     ; reset vector

SETOUT: ldi r16, 0xFF
        out 0x17, r16 ; DDRB  
        out 0x18, r16 ; PORTB 
STOP:   rjmp STOP
MAIN:   jmp  SETOUT
        nop
        nop
        break

If you upload it to an ATtiny85, then after RESET, the program jumps to MAIN, where it wants to execute the "illegal" JMP instruction. Being illegal, anything could happen, right? Well, most probably it will not jump around wildly, but either stop execution or just continue at the next address. In the latter case, it will hit the BREAK instruction and stop. What really happens is that DDRB and  PORTB are set to 0xFF and, if a LED is connected to any of the GPIOs of the ATtiny85, it will light up. So, it seems that the JMP instructions is actually executed. In fact, when one single steps through the program, that is what happens. Also CALL works perfectly, storing the return address to the stack and then jumping to the specified address. And this does also work for ATtiny13, ATtiny24, ATtiny84, ATtiny2313, Attiny861, and probably others.

 

If you want to actually generate code and upload it, you need to fool the assembler and linker and tell them that the code is to be generated for a MCU with the avr3 architecture. So a minimal Makefile could look like as follows:

all:
	avr-gcc jmp.S -mmcu=avr3 -c -o jmp.o
	avr-gcc -mmcu=avr3 jmp.o --output jmp.elf -Wl,-Map=call.map,--cref
	avr-objcopy -O ihex -R .eeprom jmp.elf jmp.hex

 

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

They don't need JMP/CALL because every location in the chip is reachable by RJMP/RCALL (combined with wraparound). 

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



ATtinys do support the 4-byte CALL and JMP instructions

It doesn't!

 

 

If you upload it to an ATtiny85,

If you can't assemble the code and you CAN'T then there is nothing to upload, of course the programmer will accept any rubbish if you tell it it's good code.

you need to fool the assembler and linker

WHY??????????? Using a screwdriver as a chisel?  DO THE CORRECT thing and you don't have to waste time with useless observations.

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

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

fogg wrote:

<snip>

So, it seems that the JMP instructions is actually executed.

Quite an interesting observation, thank you for sharing this.

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

fogg wrote:
In fact, when one single steps through the program, that is what happens.

Are you using Atmel/Microchip debugger to verify this or your own ?

 

You might have to correctly handle all the invalid instructions also. Just in case the CPU has run-away and is executing code that is actually text or a LUT.

 

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


js wrote:
It doesn't!
I tried the "other" assembler, "it doesn't" either!

 

 

BTW, those errors are "confused". Line 10 has the JMP and it has reported "ASR" ?? Then line 14 has the CALL and it has reported JMP ?

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

clawson wrote:
 those errors are "confused".

is that just the 'Error List' messing things up, or is the actual build output "confused" ?

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Raw errors just the same:

D:\junkavr\testGCC\testGCC\test.S(10,1): error: illegal opcode asr for mcu avr25
D:\junkavr\testGCC\testGCC\test.S(14,1): error: illegal opcode jmp for mcu avr25

 

All this leads me to wonder what assembler the OP is using that (when building for tiny85) allowed it to parse JMP and CALL without error?

Last Edited: Wed. Nov 10, 2021 - 11:23 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have not been following this thread.

 

If you want to see how a Tiny behaves to a JMP or CALL you would ask the Assembler to use the appropriate .WORD instructions.

Obviously any respectable Assembler will reject "illegal" opcodes.

 

Then you can trace the actual AVR behaviour on the real-life Tiny with debugWIRE,  UPDI or the AS7.0 Simulator.

 

Yes,  it would be interesting if the Simulator does not agree with the OCD hardware.

 

No one in their right mind would use JMP and CALL in a commercial product.    As illegal opcodes future Silicon revisions might change the behaviour.

There was a whole set of undocumented 6502 opcodes.   From memory they did not always behave the same on 6502 variants.

I suppose that you could insert illegal opcodes as .WORDs as a security measure of you are anally retentive.

 

David.

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

clawson wrote:

All this leads me to wonder what assembler the OP is using that (when building for tiny85) allowed it to parse JMP and CALL without error?

 

Just look into the posted Makefile: I used avr-gcc. And I did not specify ATtiny85 as the target, but I specified avr3 as the target architecture, i.e., a 16 KiB architecture which allows for JMP and CALL.

 

david.prentice wrote:

If you want to see how a Tiny behaves to a JMP or CALL you would ask the Assembler to use the appropriate .WORD instructions.

 

Yes, indeed. So, the following program should be acceptable to any assembler regardless of the architecture or concrete MCU (0x940C is the opcode for JMP - for the case that no more than 128 KiB needs to be addressed). avr-gcc, at least, does not give an error and generates the 'right' code.

.org 0x0000

        rjmp MAIN     ; reset vector

SETOUT: ldi r16, 0xFF
        out 0x17, r16 ; DDRB
        out 0x18, r16 ; PORTB
STOP:   rjmp STOP
MAIN:   .word 0x940C
        .word SETOUT
        nop
        nop
        break

 

As I mentioned, I am not suggesting to use this feature, and it is indeed VERY useless on small AVRs (particularly on an ATtiny13). I am just puzzled that Atmel tells in their data sheets that the instructions are missing while they are indeed implemented. So, why not leave it to the users/compilers not to use the instruction?

 

 

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


fogg wrote:
And I did not specify ATtiny85 as the target, but I specified avr3 as the target architecture, i.e., a 16 KiB architecture which allows for JMP and CALL.
But Tiny85 is AVR25 not AVR3 ?!?

 

BTW why on earth did you not specify the correct target (i.e. -mmcu=attiny85) if you don't then it will not know the right crtXXX.o to link to and hence the IVT (and stack initialisation) will be wrong. Also if you say just "avr3" you will get the wrong libc/libm so not only may they use CALL and RET but things like MUL. From the AVR-LibC manual:

 

avr25 [1] AVR_ARCH=25
AVR_HAVE_MOVW [1]
AVR_HAVE_LPMX [1]
AVR_2_BYTE_PC [2]

 

attiny85 is "avr25" architecture so it has the above facilities. Those for avr3 are:

avr3 AVR_ARCH=3
AVR_MEGA [5]
AVR_HAVE_JMP_CALL [4]
AVR_2_BYTE_PC [2]

 

That not only has JMP/CALL but is "MEGA" so the libraries will be built for MEGA architecture.

 

Looking at an installation:

 

 

So those are the 21 devices that are "avr25" architecture and:

 

 

there are only two devices that are avr3 architecture (and the tiny85 ain't one of them!)

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

clawson wrote:
BTW why on earth did you not specify the correct target

You seem to have missed, the OP is performing unit testing! so must have been thinking these instructions are "illegal" and the mpu would ignore or some how fault when it occurs...

fogg wrote:
I noted that when writing unit tests about executing 4-byte instructions.

So he needed to create the "illegal" instructions to see what happens....

what he found is the instructions work, even though they are not needed on small memory AVR's.....

 

fogg wrote:
So, why not leave it to the users/compilers not to use the instruction?

since gcc is an Optimizing compiler, the use of unnecessary 4 byte instructions would not benefit the algo to reduce flash space needed!  Simple enough to me....

 

Jim

 

 

FF = PI > S.E.T

 

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

ki0bk wrote:
You seem to have missed, the OP is performing unit testing!
I must be missing something - how does that justify building the code for the wrong micro architecture ?? In the limit you might as well build for an ARM and see how that code performs on an AVR!

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

clawson wrote:
I must be missing something

I'll say!

 

 

OP is writing a debugger and needs to know how to handle unsupported instructions,  so he writes a test but needs to "force" the unsupported instruction through the assembler.  I thought it was quite straightforward.

 

I think he should also handle illegal instructions but that's something for another day.

 

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

Since this is interesting (and not necessarily useful) I tested the call behaviour on an attiny45, using the code below.  Refer to post #1 for build instructions using gcc.

.org 0x0000
  rjmp MAIN  
PROC: 
  ret
MAIN:   
  call PROC
DONE:
  rjmp DONE

I used my own debugwire tool (basically a Pascal clone of https://github.com/dcwbrown/dwir...) for the debug session.  Start of session, to show ID of connected controller:

~/LazProjs/debugwire-gdb-bridge/dw_gdb -S /dev/ttyUSB0 -T 1234 -B 60000
21:24:03.092  Device ID: $9206 [ATtiny45]
21:24:03.157  PC: $0000
21:24:03.161  R28: $84, R29: $E6, R30: $40, R31: $00
Waiting for TCP connection on port 1234

Stepping through the code and inspecting the stack pointer and the memory at the top of the stack shows that the 4 byte call instruction does indeed work properly, as can be seen by the execution flow, and the return address on the stack just after the call. Below the gdb session for reference.

 

The same code was also tested on an attiny24, also with correct behaviour.

 

~/gdb/gdb-10.1/avr-build/gdb/gdb -ex "file testcall.elf" -ex "tar rem :1234" -ex load -ex "set disassemble-next-line 1"
GNU gdb (GDB) 10.1
<snip>
This GDB was configured as "--host=x86_64-pc-linux-gnu --target=avr".
<snip>
Reading symbols from testcall.elf...
(No debugging symbols found in testcall.elf)
Remote debugging using :1234
0x00000000 in __trampolines_start ()
Loading section .text, size 0xa lma 0x0
Start address 0x00000000, load size 10
Transfer rate: 384 bytes/sec, 10 bytes/write.
(gdb) si
0x00000004 in MAIN ()
=> 0x00000004 <MAIN+0>:	0e 94 01 00	call	0x2	;  0x2 <PROC>
(gdb) display $SP
1: $SP = (void *) 0x80015f
(gdb) si
0x00000002 in PROC ()
=> 0x00000002 <PROC+0>:	08 95	ret
1: $SP = (void *) 0x80015d
(gdb) x/c $SP+1
0x80015e:	0x00
(gdb) x/1xb $SP+2
0x80015f:	0x04
(gdb) si
0x00000008 in DONE ()
=> 0x00000008 <DONE+0>:	ff cf	rjmp	.-2      	;  0x8 <DONE>
1: $SP = (void *) 0x80015f
(gdb) si
0x00000008 in DONE ()
=> 0x00000008 <DONE+0>:	ff cf	rjmp	.-2      	;  0x8 <DONE>
1: $SP = (void *) 0x80015f

 

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

Wonder if this has any application to 'security' as in another thread.  If you can force in an illegal opcode, if someone steals your source code it won't work unless they can do the same...  S.

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

N.Winterbottom wrote:

I think he should also handle illegal instructions but that's something for another day.

 


 

Coming back to the issue of "illegal" instruction: I currently only consider the BREAK instruction (which can be inserted into a program using the asm statement) as 'illegal', meaning that I return "SIGILL" to gdb.  And, of course, this is only detected when one tries to single step such a BREAK or one wants to continue from a BREAK (inserted by the programmer). If the program hits such an instruction, it will simply stop execution as it will when reaching a regular breakpoint, with a slightly different message, though. Since there is no "illegal instruction exception" in the AVR MCUs, I won't be able to catch any "illegal" instruction while executing. Of course, when single-stepping or continuing, it would be possible to recognize 'illegal instructions'.  The big question then is: What is an illegal instruction? Should I consider JMP and CALL on Tinys as illegal instructions? What about ELPM and EIJMP? They probably do also something reasonable. I wonder about MUL ;-). And how much effort should one put in trying to recognize illegal instructions when one is already in a stopped state?

 

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

It appears that JMP and CALL opcodes are correctly executed by tiny24, tiny85, ...

 

But the official Assemblers do not recognise JMP, CALL

For consistency an official disassembler will not report the JMP, CALL opcodes for a Tiny.

 

This all seems fairly logical.   After all,   there is no need to ever use JMP, CALL when RJMP, RCALL will work.   (and use less WORDs)

A Compiler will never generate JMP, CALL.   If the official Assemblers do not recognise JMP, CALL they should (and do) report an error.

 

On a >8kB AVR it is up to the user to use RJMP when the destination is within range.

And some optimising Assemblers might even replace JMP with RJMP.    I have used M68000 Assemblers that generated the "shortest" addressing mode and written 8-bit Assemblers.

 

Oh,  if the OP want to write a Disassembler,  she should generate an absolute .WORD for the opcode and a relocatable .WORD for the address.   So that the output is acceptable to the official Assemblers.

 

David.