; What: Example on how to display a message using EasyRequestArgs in Intuiiton. ; ; Author: Tomas Jacobsen - bedroomcoders.com ; Date: January 2026 ; Version: 1.0 opt d+ ; Include debug information in file output RAM:DisplayMessage ; Save program to RAM: incdir "include:" include "exec/exec_lib.i" include "intuition/intuition_lib.i" include "intuition/intuition.i" section my_code,code ;Initialization ;------------------------ _Init movea.l 4.w,a6 ; ExecBase in a6 moveq #0,d0 ; 0 = any version of intuition, 39 = at least OS 3.0 lea _IntuitionName,a1 jsr _LVOOpenLibrary(a6) move.l d0,_IntuitionBase beq.s .libraryError movea.l _IntuitionBase,a6 suba.l a0,a0 ; Set a0=0 - Display on default screen lea _EasyStruct,a1 ; Pointer to structure in a1 move.l #es_SIZEOF,es_StructSize(a1) ; Fill in size in structure clr.l es_Flags(a1) ; Set flags in structure to 0 move.l #_Title,es_Title(a1) ; Write a pointer to title into strucure move.l #_Message,es_TextFormat(a1) ; Write a pointer to message into strucure move.l #_OKMsg,es_GadgetFormat(a1) sub.l a2,a2 sub.l a3,a3 jsr _LVOEasyRequestArgs(a6) movea.l 4.w,a6 movea.l _IntuitionBase,a1 jsr _LVOCloseLibrary(a6) .libraryError moveq #0,d0 ; Return zero (no error) to the system rts section my_data,data _IntuitionName dc.b "intuition.library",0 even _Title dc.b "My message",0 even _Message dc.b "This is a message from my assembler code.",0 even _OKMsg dc.b "OK",0 even _EasyStruct ds.b es_SIZEOF ; EasyStruct for Requesters even _IntuitionBase dc.l 0
Category: Beginner
-
Example: Display message requester.
-
Coding for AmigaOS.

AmigaOS is your bridge to the hardware. An operating system serves as a bridge between the user and the computer’s hardware, while also providing routines and services that help applications run smoothly on the platform and take advantage of all the ready-made functionality the OS offers.
AmigaOS is no different, providing a rich set of routines you can tap into for your code. This made it far ahead of its time, and it’s still seen as both efficient and welcoming to coders and users alike.
Most routines are organized into libraries. For example, the intuition.library contains routines for managing windows and screens. If you need to open a file or read a directory in your code, the dos.library is the place to look. The graphics.library can help you draw a circle on the screen or animate an image by moving it around.
For a full guide to the built-in libraries, take a look at the official autodocs here:
https://developer.amigaos3.net/autodocs
Let’s dive in and explore how to work with all this functionality using assembler. To use a library’s contents, you first need to open it, and when you’re finished, close it properly. The one exception is the exec.library, which is always open and ready to use. This is the library that lets us open any other library.
movea.l 4.w,a6 ; At memory address 4 you will always find a pointer to ExecBase/SysBase
With ExecBase loaded in a6, we can call any of Exec’s functions. A library base is essentially a table of available functions, with the base pointing to the last one. To call a function, we use a negative offset from the base. Confusing, I know. An example is in order.
; Find ExecBase and use Exec's OpenLibrary funtion to open Intuition. ; Store IntuitionBase in a variable for later using it's functions. _LVOOpenLibrary = -552 _LVOCloseLibrary = -414 Start: movea.l 4.w,a6 ; ExecBase in a6 lea IntuitionName,a1 ; Name of Intuition Library moveq #0,d0 ; Any version of the library is OK jsr _LVOOpenLibrary(a6) ; Jump to Execbase - 552. move.l d0,IntuitionBase ; Store IntuitionBase in memory ; ... Lots of useful code ... movea.l 4.w,a6 movea.l IntuitionBase,a1 ; Library to close jsr _LVOCloseLibrary(a6) ; Always clean up and close after us. rts IntuitionBase: dc.l 0 IntuitionName: dc.b "intuition.library",0 even
jsr _LVOOpenLibrary(a6) translates to jsr -552(a6), which means the CPU jumps to the address located 552 bytes before the address that a6 points to.
In the developer tools, you’ll come across include files—definitions you can add to your code that let you use function names instead of numbers. We’ll explore this in more detail in a later article. In the upcoming articles, you’ll also see more examples of how to use OS functions. It might feel a bit confusing at first, but it will start to click as you get more examples of how they’re used.
-
The 12 most commonly used CPU instructions.
The Motorola 68000 CPU has about 50–60 basic instructions, each with variants that bring the total to around 150. If you’re coding for the 68080 CPU, you can multiply those numbers significantly. Luckily, you can get pretty far with just a few of the basic ones, and an understanding of variants. In this article, I’ll share just 12 commands that are all you need to build complete projects.

You only need 12 commands to code in assembler. Even though the 68000 is a 16-bit CPU, moving at most 16 bits of memory in one go, it has a 32-bit internal architecture and appears as a 32-bit processor from a programmer’s perspective. From the 68020 onward, the CPUs in this series are true 32-bit. Instructions that read or write data have variants indicating the size of the data being processed. This is done by adding a suffix to the command.
instruction.b Byte – 8 bits instruction.w Word – 16 bits instruction.l Long – 32 bits Be careful to use the correct size when writing your code. Getting this wrong is one of the most common mistakes and can lead to tricky bugs. If you write a byte-sized value into a register, you have no idea what’s in the upper 24 bits. Reading that register as a long afterward might just give you a random number.
Here’s the list of the 12 most common instructions along with a couple of usage examples for each.
move lea add sub cmp tst btst beq (and other branches) dbf jsr bsr rts
MOVE – The move command is used to move data to and from memory and registers.
Instruction Example Description move.b move.b #255,d0 Write the number 255 into the lower 8 bits of d0 move.w move.w #$4ff,d0 Write the hexadecimal number 4ff into the lower 16-bits of d0 move.l move.l a0,d0 move the address stored in a0 into d0 move.l (a0),d0 Move the content that a0 points to into d0. move.l (a0)+,d0 Same as above, but modify a0 to point to the next address. move.l (a0),(a1) move the data that a0 point to, into the address that a1 points to. moveq moveq #200,d0 Move the number 200 into d0 with a quick command, but ensure the number is 8-bit. The operation still performs a 32-bit write to the register. movea.l movea.l d0,a0 move the content of d0 into address register a0 movea.l a0,a1 move the content of a0 into a1 movem movem.l d0-d2/a6,-(sp) Move multiple. This example stores d0,d1,d2, and a6 on the stack. The key takeaways are:
- # is used to indicate a hardcoded number to be written; otherwise, it represents an address.
- The $ symbol is used to indicate that the number is in hexadecimal format.
- An address register in parentheses refers to the data stored at the address contained in the register.
- MOVEA is used when the destination is an address register, but most assemblers will automatically convert MOVE to MOVEA during assembly.
LEA – Load Effective Address
The LEA command creates a pointer to an address and stores it into an address register.
Example
lea mydata,a0 mydata: dc.l 1000,2000,3000,0
Here we have a data section with some numbers. The LEA command will load the a0 register with the address where these numbers are stored. A movea.l mydata,a0, on the other hand, would place the actual number 1000 into a0. This is an important distinction.
Add – Mathematical addition.
Example Description add.l #50,d0 Add the content of d0 with 50 add.w #50,d0 Same as above, but only writes the lower 16-bits. addq.l #5,d0 Add the content of d0 with 5. Quick command that only works with numbers between 1 and 8. add.l d0,d1 Add the content of d0 to d1. result is stored in d1 adda.l #4,a0 Adding 4 to the address stored in a0. a0’s pointer have now been increased with 4 bytes. Sub – Mathematical substraction.
Example Description sub.l #50,d0 Substracts the content of d0 with 50 sub.w #50,d0 Same as above, but only writes the lower 16-bits. subq.l #5,d0 Substracts the content of d0 with 5. Quick command that only works with numbers between 1 and 8. sub.l d0,d1 Substracts the content of d0 to d1. result is stored in d1 suba.l #4,a0 Substracts 4 to the address stored in a0. a0’s pointer have now been decreased with 4 bytes. Tip: If you want to clear an address register, you can indeed use:
movea.l #0,a0
However, the same result can be achieved using sub, which would be quicker for the CPU to execute.
suba.l a0,a0
CMP – Compare two values and sets status flag
Example Description cmp.b #100,d0 Check if the lower 8 bits of d0 contain the value 100. cmp.l #100,d0 Check if d0 (all 32 bits) contain the value 100. cmp.l d0,d1 Check if the content of d0 is the same as in d1. cmpa.l a0,a1 Check if content of a0 is the same as in a1 cmpm.l (a0),(a1) Check if the data at the location a0 points to is the same as the data at the location a1 points to. cmpi.l #100,(a0) Check if the data at the location a0 points to is 100 CMP instructions are paired with branch commands like beq, bne, and others to handle conditions correctly.
Start: cmp.b #'A',d0 ; Does d0 contain the letter A? beq .Afound ; Branch if equal to .Afound cmp.b #'B',d0 ; Does d0 contain the letter B? beq .BFound ; Branch if equal to .BFound .... .... .done rts ; Return from subroutine .Afound moveq #0,d0 bra .done ;Branch always .Bfound moveq #1,d0 bra .done ; Branch always
TST – Test for 0 and set status flag
Example Description tst.l d0 Does d0 contain 0? tst.b (a0) Does the byte a0 points to contain 0? TST is faster than using CMP to test for zero. Note that TST can not test an address register.
BTST – Bit Test – Test single bit for value 0
Example Description btst #7,d0 Does bit number 7 in d0 contain 0? btst #20,d0 Does bit number 20 in d0 contain 0? btst #6,$bfe001 Does bit number 6 at address $bfe001 contain 0 ? Important: When testing a bit in a data register, you can use a number between 0 and 31. When testing directly in memory, it’s an 8-bit operation, so you’re checking the byte at the given address. In that case, you should use a number between 0 and 7.
Branches – A branch jumps to a specific location when a condition is met.
Example Description beq Branch if equal to bne Branch if not equal to bgt Branch if greater than bge Branch if greater or equal to blt Branch if less than ble Branch if less or equal to bra Branch always cmp.l d0,d1 ; Compare d0 to d1 beq .isEqual ; Jump if d0 and d1 have the same values bgt .isGreater ; Jump if d0 have a greater value than d1 does blt .isLess ; Jump if d0 have a lower value than d1 does bne .isNotEqual ; Jump if d0 and d1 are not equal.
DBF – Decrement and Branch if False.
This is your go-to command for creating loops that run a specific number of times. DBF decreases the data register by one and branches to the label if the register is not -1.
moveq #9,d0 ; Loop 10 times (0-9) .loop: ; ... your code ... dbf d0,.loop ; d0 = d0 -1, branch if d0 >= 0
JSR – Jump to Subroutine
This command lets you jump to a subroutine and return to the same point once it’s finished. In AmigaOS, it’s widely used for calling OS functions.
BSR – Branch to Subroutine
BSR works just like JSR, but it’s a more efficient command that can only jump up to 32KB from the current location. It’s commonly used to jump to your own subroutines within your code.
RTS – Return from Subroutine
RTS returns from a subroutine called by either JSR or BSR.
Start: moveq #100,d0 move.l #5000,d1 bsr AddNumbers .... ; d0 now contains the value 5100 rts ; Exit this program ; AddNumbers ; INPUT: d0,d1 = numbers to add ; OUTPUT: d0 = result AddNumbers: add.l d0,d1 ; Adds d0 to d1 move.l d1,d0 ; Move result to d0 rts ; Return to line after the calling BSR
-
What is assembler, and why should i care?

She is not happy with you at all. You’ve probably heard that computers are made up of bits and bytes—ones and zeros—that somehow transform into your favorite game or that lecturing email from your mother-in-law. And you’ve heard right. The basic building block of a computer’s memory is the bit, a tiny switch that’s either on or off, one or zero. Bits can be grouped together to form larger numbers. For example, combining 8 bits can represent values ranging from 0 to 255.
These numbers can represent data, like creating a picture of your dog or capturing waveform patterns in a pumping techno beat. They can also serve as instructions for the CPU to execute. The CPU reads these numbers and performs operations based on their meaning. This is machine code.
With machine code, a programmer can fully control the computer, writing programs that run at peak performance while keeping the code compact. But if a programmer only wrote numbers to make the computer do what he wanted, it would quickly become too difficult, and he would lose track of his program.
This is where assembler comes to the rescue. It provides a human-friendly layer on top of machine code. Each instruction still operates at the basic machine code level, but numbers are replaced with words that carry the exact same meaning.
0680 0000 0001These numbers are the machine code that tells the CPU to add the number 1 to a register called d0.
add.l #1,d0This is the same instruction written in assembly language, which the assembler will convert into the number shown above.

A real-time look at an assembler generating machine code. As you can see, remembering these words is much easier than recalling the raw numbers. These translated cues are called mnemonics, a term derived from the Greek word “mnemonikos,” meaning “related to memory.”
Why learn assembler and low-level coding when you have options like C, Basic, or even Pascal? It’s a fair question. If you want to get your code up and running quickly, those other languages might be the better choice. But when it comes to optimizing that tight loop slowing down your application, assembler is the way to go. And maybe, like me, you’ll discover that assembler becomes your preferred programming language for almost everything.
Let’s look at some pros and cons to break it down.
Pro Con Fastest code. Steep learning curve Compact code. Takes longer to finish projects. Full control of all details. More likely to create bugs in you code. Fun and challenging. It’s not as difficult once you’ve got some experience. Makes you one of the cool guys. -
How the heck does a CPU work?

68K is the SUV of processors. The Motorola 68K line of CPUs is probably among the most coder-friendly on the market, with its rich feature set and sensible syntax. This makes it an ideal choice for learning assembler coding, but be warned: once you understand the inner workings of the 68K, you might find yourself disliking the design of other CPUs. Switching from 68K to x86 coding is like trading your SUV for a kit car.

Are CPU’s magic? A CPU isn’t magical, though it might seem like you need to master some arcane art to program one. It’s simply a unit that can move data around, store it in internal registers, and perform calculations. Once you understand the basics, it’s actually pretty straightforward.
The 68000 CPU features eight data registers, eight address registers, a Program Counter, and a Status Register. The 68080 CPU, found in Apollo computers, adds 40 extra registers for calculations and data storage. In the remaining articles of this series, the focus will be on the classic 68K, used in early Amiga, Atari, and Apple computers, the Sega Megadrive, and various other devices like laser printers, synthesizers, and medical equipment.
d0-d7 : 32 bit data registers a0-a7 : 32 bit Address registers pc : 24 bit Program Counter sr : 16 bit Status Register Lower 8 bits are the Condition Control Register Higher 8 bits are system control bitsNext up are the CPU’s instructions. These are stored in memory and executed by the CPU one by one sequentially. The PC register always holds the address of the next instruction to run. Changing the value of the PC register will make the CPU jump to that memory location to execute code.
Address Instruction $f8000 moveq #10,d0 $f8002 moveq #5,d1 <- pc register = $f8004 $f8004 add.l d0,d1 These instructions happen to be two bytes each, but a 68K instruction can range from two to ten bytes in length.In addition to showing how the CPU knows which instruction to execute next, the example also introduces two common commands: Move, for transferring data, and Add, for performing a mathematical addition. There are several variants of both these instructions, but explaining these details is beyond the scope of this article.

This is all well and good, but how does it turn into a real program a computer user can run? That’s where integration with the rest of the hardware comes in. The CPU, or Central Processing Unit, sits at the core of the hardware design, executing commands. By sending data to the screen buffer, it displays images on the screen, and by fetching data from I/O (input/output), it processes the user’s keyboard input.
For more information and a straightforward example, take a look at this article next.
-
Your first ever Assembler program for the Amiga
If you’re looking to learn Motorola 680×0 Assembler for the Amiga computer and compatible systems like Apollo’s Vampire or Unicorn, you’ve come to the right place. This site offers a variety of resources for anyone interested in low-level coding.
To follow along, make sure your developer tools are set up and installed on your computer. For more details on how to do that, please see:
LINK
LINK
Now that you’re ready, let’s jump right in. I can guess what you’re thinking—another “Hello World” code example to kick off learning a new programming language.

In the Amiga world, we do things a bit differently. Here, we start by learning how to read input from the mouse’s left button, of course.
waitLoop: btst #6,$bfe001 bne.s waitLoop rtsNow compi….. assemble the code, and run it. It will loop continuously until you click the left mouse button.
Let’s break it down and go over what each part means.
waitLoop:This is a label, essentially a bookmark in the code pointing to this location. It’s not an instruction for the CPU, but a marker for the assembler to reference when generating actual machine code.
Labels must either end with a colon or be placed at the start of a line with no space before them to be recognized by the assembler, and they are case-sensitive.
btst #6,$bfe001The “btst” instruction, short for “Bit Test,” is a real CPU command. Here, it’s checking bit number 6 at the memory address $bfe001. The outcome of this test is saved in the CPU’s Condition Code Register (CCR). Essentially, it’s used to determine if that specific bit is set to zero.
On the Amiga hardware platform, the system sets this particular bit to zero when the LMB is pressed; otherwise, it’s set to one.
bne.s waitLoopBNE stands for “branch if not equal,” meaning that if the previous test doesn’t result in zero, the program jumps to the label waitLoop. You might have noticed the “.s” after BNE—this is an optimization, where “.s” means short. It’s used to create smaller code when the branch or jump is to a nearby address.
rtsRTS stands for “Return to Subroutine.” When our program is called by the operating system, we need the CPU to resume executing the OS’s own instructions; otherwise, the system will crash. In later articles, we’ll use the RTS command extensively when creating our own functions and subroutines.

There you have it—your first Amiga program, written in the programming language for hardcore bearded or bald guys. And if you happen to be a gal, you’re, of course, welcome to be a little less hairy.
