Tag: 68K Assembler

  • MUI coding in assembler

    MUI, or Magic User Interface, is a popular UI system on the Amiga that offers users an attractive and consistent interface to work with. For developers, it delivers an object-oriented way to code UIs, with perks like automatic layout management and simple Arexx integration.

    MUI

    MUI coding is an advanced topic, but one that’s worth learning properly. There’s no doubt MUI was designed with C programmers in mind, and little has been done to accommodate those of us who prefer assembler syntax. The MUI development package hides much of its functionality behind C macros and libraries, making it tricky to find documentation on how it really works. That’s the kind of insight assembler coders need.

    I highly recommend checking out the developer guide and autodocs included with the MUI developer package. They offer a solid understanding of how the system works. Here, I’ll highlight the parts specific to the assembler and share examples you can use as a starting point for your own applications.

    To get started, we need to open “muimaster.library”. Although MUI objects are technically Intuition BOOPSI classes, we should use MUI functions to create new objects and set their attributes. Another important thing to note is that the UI is built from the bottom up. That’s why, in the later example, we start by creating the buttons and text, then the groups they belong to. Only after that do we create the window, and finally, the application to host everything. The reason for this is that each object we create needs to have a pointer to its children. As a result, when we create our window, for example, the application isn’t ready yet. This means we can’t actually open the window at that moment. We need to build the entire chain of objects first, then open the window using _LVOMUISetAttrsA.

    Creating new objects is done using the _LVOMUINewObjectA function. Like other functions ending with a capital “A,” its attributes are provided using a taglist. In the example, I use my own macros to build a taglist on the stack. They’re the same ones I’d use with, for instance, _LVOOpenScreenTaglist, which is actually an alias for _LVOOpenScreenA, by the way.

    Every new object we create needs to be of a MUIC_<class>. In the C documentation for MUI, you’ll see references to MUIC_Window, MUIC_Text, and so on, but these don’t appear in the assembler includes. The fix is to add the necessary classes ourselves in the assembler source. These aren’t constants representing numbers like other labels, but pointers to strings containing the class names. Here is how it’s done in assembler:

    MUIC_Application	dc.b	"Application.mui",0
    MUIC_Window		dc.b	"Window.mui",0
    MUIC_Group		dc.b	"Group.mui",0
    MUIC_Text		dc.b	"Text.mui",0
    			even

    One big advantage of having the entire application and its UI as a single chain of objects is that when it’s time to exit and clean up, we just make one call to _LVOMUI_DisposeObject with our application as the parameter. MUI will then automatically close all its child objects and free up all used resources in one go.

    With our application and its UI elements now created and visible on screen, it’s time to add functionality to bring everything to life. The ideal MUI application has a minimalistic event handler that only checks whether the program should quit or continue running. All other functionality should be added as hooks to the UI elements, with MUI handling the calls to these routines. This is exactly the approach we took in the example.

    Important: When MUI calls your hook, don’t assume any data will be in the CPU’s registers. Retrieve all necessary parameters from variables or memory locations, and be sure to save and restore any registers you use on the stack.

    In object-oriented programming, the functions an object can perform are called “methods”. In MUI, these methods are invoked using the DoMethod() command. However, DoMethod() isn’t actually an AmigaOS function; it resides in a linker library used by the C compiler, making it not particularly useful for assembler coders. To rub salt in the assembler coders’ wounds, the documentation on how to manually invoke these methods is flat-out wrong.

    Here is what needs to be done:

    • Make a list of methods and their parameters stored in memory or on the stack.
    • Point a1 to this list
    • Point a2 to the relevant MUI object
    • Point a0 to the objects hook
    • execute h_entry from the hook

    	movea.l	mt_MUI_Application(a5),a2
    	movea.l	-4(a2),a0					; Offset to Hook Struct (Is this undocumented?)
    	movea.l	h_Entry(a0),a6				; Find entry to execute method
    	lea	mt_Method_Input,a1
    	jsr	(a6)							; DoMethod();

    All of this could be made easier for the coder by creating macros to generate specific types of objects and invoke methods, but for clarity and understanding, I’ve chosen not to include them in this source.

    To sum it up, here’s a list of what needs to be done to build a MUI application:

    • Open “muimaster.library”
    • Build your application starting from the bottom objects and up while linking any child objects to it
    • Set attributes that only become valid after creating the application, such as opening a window.
    • Use DoMethod() to set notify and hooks to your objects
    • Run a minimalistic eventhandler loop that fetch Application_ReturnID_Quit by calling DoMethod() and waiting for signal
    • Clean up by a single call to MUI_DisposeObject()
    • Close “muimaster.library”

    Finally, here’s a complete example of source code showing how to accomplish all of the above.

    Basic_MUISetup.s

  • Example: Display message requester.

    		; 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
    

  • 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.bByte – 8 bits
    instruction.wWord – 16 bits
    instruction.lLong – 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.

    InstructionExampleDescription
    move.bmove.b #255,d0Write the number 255 into the lower 8 bits of d0
    move.wmove.w #$4ff,d0Write the hexadecimal number 4ff into the lower 16-bits of d0
    move.lmove.l a0,d0move the address stored in a0 into d0
    move.l (a0),d0Move the content that a0 points to into d0.
    move.l (a0)+,d0Same 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.
    moveqmoveq #200,d0Move 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.lmovea.l d0,a0move the content of d0 into address register a0
    movea.la0,a1move the content of a0 into a1
    movemmovem.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.

    ExampleDescription
    add.l #50,d0Add the content of d0 with 50
    add.w #50,d0Same as above, but only writes the lower 16-bits.
    addq.l #5,d0Add the content of d0 with 5. Quick command that only works with numbers between 1 and 8.
    add.l d0,d1Add the content of d0 to d1. result is stored in d1
    adda.l #4,a0Adding 4 to the address stored in a0. a0’s pointer have now been increased with 4 bytes.

    Sub – Mathematical substraction.

    ExampleDescription
    sub.l #50,d0Substracts the content of d0 with 50
    sub.w #50,d0Same as above, but only writes the lower 16-bits.
    subq.l #5,d0Substracts the content of d0 with 5. Quick command that only works with numbers between 1 and 8.
    sub.l d0,d1Substracts the content of d0 to d1. result is stored in d1
    suba.l #4,a0Substracts 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

    ExampleDescription
    cmp.b #100,d0Check if the lower 8 bits of d0 contain the value 100.
    cmp.l #100,d0Check if d0 (all 32 bits) contain the value 100.
    cmp.l d0,d1Check if the content of d0 is the same as in d1.
    cmpa.l a0,a1Check 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

    ExampleDescription
    tst.l d0Does 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

    ExampleDescription
    btst #7,d0Does bit number 7 in d0 contain 0?
    btst #20,d0Does bit number 20 in d0 contain 0?
    btst #6,$bfe001Does 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.

    ExampleDescription
    beqBranch if equal to
    bneBranch if not equal to
    bgtBranch if greater than
    bgeBranch if greater or equal to
    bltBranch if less than
    bleBranch if less or equal to
    braBranch 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 0001

    These numbers are the machine code that tells the CPU to add the number 1 to a register called d0.

    add.l #1,d0

    This 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.

    ProCon
    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 bits

    Next 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

  • 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
              rts

    Now 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,$bfe001

    The “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 waitLoop

    BNE 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.

    rts

    RTS 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.