➟ Développer un Hello World bootable pour Amiga 500 sous Linux

Hello, World! sur Amiga 500 compilé sous Linux

Si vous avez grandi dans les années 80-90

Vous vous souvenez probablement de l’Amiga 500, cette machine révolutionnaire qui nous a tous fait rêver avec ses capacités graphiques et sonores exceptionnelles. Le Motorola 68000, son processeur mythique, nous permettait de créer des démos incroyables, des jeux spectaculaires et d’explorer les profondeurs du “bare metal programming”.

Aujourd’hui, nous allons replonger dans cette époque dorée, mais avec un twist moderne : nous allons développer un programme bootable “Hello, World!” pour Amiga 500, entièrement cross-compilé sous Linux, sans avoir besoin d’un Amiga pour développer. Seulement pour tester sur le vrai hardware !

Notre objectif est de créer un programme qui :

Boot directement depuis une disquette - Pas besoin d’AmigaOS, le programme se lance au démarrage de l’Amiga
Affiche “Hello, World!” à l’écran - En mode graphique, texte blanc sur fond noir, centré
Contrôle directement le hardware - Programmation “bare metal” des custom chips (Copper, Bitplanes, etc.)
Est compilé sous Linux (Archlinux ou Ubuntu) - Workflow moderne avec des outils open source
Fonctionne sur un vrai Amiga 500 - Pas seulement dans un émulateur

Qu’allons-nous créer ?

Un fichier ADF (Amiga Disk File) bootable de 880 Ko contenant :

  • Un bootblock de 1024 octets qui se charge automatiquement au démarrage
  • Le programme principal (~12 Ko) qui gère l’affichage graphique
  • Une police bitmap 8×8 intégrée pour le rendu du texte

Prérequis techniques

  • Connaissances de base en assembleur (idéalement 68000)
  • Une distribution Linux (Arch ou Ubuntu)
  • Un émulateur Amiga (fs-uae) avec une ROM Kickstart 1.3
  • Optionnel : Un vrai Amiga 500 pour le test final !

Installation des dépendances

Sous Arch Linux

Arch Linux facilite grandement l’installation grâce à l’AUR et à pipx.

1. Installer l’assembleur

1
2
# Installer vasm (assembleur 68000, syntaxe Motorola)
yay -S vasm

Vérification :

1
2
3
4
5
vasmm68k_mot -v
# Devrait afficher : vasm 2.0d (c) in 2002-2025 Volker Barthelmann

python3 --version
# Devrait afficher : Python 3.x

2. Installer l’émulateur Amiga

1
sudo pacman -S fs-uae fs-uae-launcher

Sous Ubuntu

Ubuntu nécessite quelques étapes supplémentaires car vasm n’est pas dans les dépôts officiels.

1. Installer vasm (depuis les sources)

1
2
3
4
5
6
7
8
9
10
11
12
# Dépendances de compilation
sudo apt update
sudo apt install build-essential wget

# Télécharger et compiler vasm
cd /tmp
wget http://sun.hasenbraten.de/vasm/release/vasm.tar.gz
tar xzf vasm.tar.gz
cd vasm
make CPU=m68k SYNTAX=mot
sudo cp vasmm68k_mot /usr/local/bin/
sudo chmod +x /usr/local/bin/vasmm68k_mot

2. Installer fs-uae

1
sudo apt install fs-uae

Vérification finale de l’installation

Tous les outils devraient maintenant être disponibles :

1
2
3
4
5
6
7
8
9
10
$ vasmm68k_mot -v
vasm 2.0d (c) in 2002-2025 Volker Barthelmann
vasm M68k/CPU32/ColdFire cpu backend 2.8 (c) 2002-2025 Frank Wille
vasm motorola syntax module 3.19d (c) 2002-2025 Frank Wille

$ python3 --version
Python 3.x

$ fs-uae --version
FS-UAE ...

✓ La chaîne de compilation est prête !

Structure du projet

Créez un répertoire pour votre projet :

1
2
mkdir helloworld-amiga
cd helloworld-amiga

Nous allons créer 4 fichiers :

  • bootblock.s - Le secteur de boot
  • hello.s - Le programme principal
  • Makefile - L’automatisation de la compilation
  • fix_checksum.py - Le calcul du checksum du bootblock

Là je crois que bébé attend sa disquette…

Réalisation du bootblock

Le fichier bootblock.s

Le bootblock est le composant critique qui permet à votre programme de démarrer automatiquement. Le Kickstart de l’Amiga lit automatiquement les 1024 premiers octets de la disquette au boot et les exécute s’ils sont valides.

Créez le fichier bootblock.s :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
; =============================================================================
; Bootblock Amiga - Structure correcte pour Kickstart 1.3
; Ce code est chargé automatiquement par le Kickstart au boot
; =============================================================================

SECTION bootblock,CODE

ORG 0 ; Le bootblock commence à l'offset 0

; -----------------------------------------------------------------------------
; En-tête du bootblock (12 octets obligatoires)
; -----------------------------------------------------------------------------
dc.b 'D','O','S',0 ; +0: Signature "DOS" + type 0 (OFS)
dc.l 0 ; +4: Checksum (sera calculé par fix_checksum.py)
dc.l 880 ; +8: Pointeur vers le rootblock

; -----------------------------------------------------------------------------
; Code exécutable (commence à l'offset 12)
; Le Kickstart saute ici avec:
; A1 = IORequest pour trackdisk.device
; A6 = ExecBase
; -----------------------------------------------------------------------------

; Offsets exec.library
_LVODoIO EQU -456

; Constantes trackdisk
TD_READ EQU 2
TD_MOTOR EQU 9
IO_COMMAND EQU 28
IO_LENGTH EQU 36
IO_DATA EQU 40
IO_OFFSET EQU 44
IO_ERROR EQU 31

; Adresse de chargement du programme (en Chip RAM)
LOAD_ADDR EQU $20000

; Taille du programme à charger
LOAD_SIZE EQU 512*24 ; 12Ko

; Offset sur la disquette (après le bootblock = 1024 octets)
LOAD_OFFSET EQU 1024

Boot:
; Sauvegarder les registres
movem.l d0-d7/a0-a6,-(sp)

; A1 = IORequest, A6 = ExecBase (passés par le Kickstart)
move.l a1,a5 ; Sauvegarder IORequest

; Lire le programme depuis la disquette
move.w #TD_READ,IO_COMMAND(a5)
move.l #LOAD_SIZE,IO_LENGTH(a5)
move.l #LOAD_ADDR,IO_DATA(a5)
move.l #LOAD_OFFSET,IO_OFFSET(a5)
move.l a5,a1
jsr _LVODoIO(a6)

; Vérifier les erreurs
tst.b IO_ERROR(a5)
bne.s .error

; Éteindre le moteur du lecteur
move.w #TD_MOTOR,IO_COMMAND(a5)
clr.l IO_LENGTH(a5) ; 0 = éteindre le moteur
move.l a5,a1
jsr _LVODoIO(a6)

; Restaurer les registres
movem.l (sp)+,d0-d7/a0-a6

; Sauter au programme chargé
lea LOAD_ADDR,a0
jmp (a0)

.error:
; En cas d'erreur, restaurer et retourner au Kickstart
movem.l (sp)+,d0-d7/a0-a6
moveq #-1,d0 ; Code d'erreur
rts

; -----------------------------------------------------------------------------
; Padding pour remplir exactement 1024 octets
; -----------------------------------------------------------------------------
CNOP 0,2
PaddingStart:
dcb.b 1024-PaddingStart,0

END

Explications du bootblock

Structure obligatoire (12 premiers octets)

Le Kickstart attend une structure très précise :

  1. Signature “DOS\0” (4 octets) : Identifie le bootblock comme valide. Le \0 indique le type de filesystem (OFS = Old File System)
  2. Checksum (4 octets) : Somme de contrôle qui valide l’intégrité du bootblock. Elle sera calculée par notre script Python
  3. Rootblock pointer (4 octets) : Pointe vers le block 880 (milieu de la disquette), même si on ne l’utilise pas

Fonctionnement du code

Lorsque le Kickstart lance le bootblock, il passe deux pointeurs essentiels :

  • A6 : Pointeur vers ExecBase, la structure système principale
  • A1 : Pointeur vers une IORequest pour le trackdisk.device

Notre bootblock utilise ces pointeurs pour :

  1. Sauvegarder tous les registres (movem.l) - Bonne pratique pour ne pas corrompre l’état système
  2. Configurer une lecture disque :
    • Commande : TD_READ (lire des données)
    • Taille : 12 Ko (24 secteurs de 512 octets)
    • Destination : $20000 (adresse en Chip RAM)
    • Offset : 1024 octets (juste après le bootblock)
  3. Exécuter la commande avec DoIO() - Fonction d’exec.library
  4. Vérifier les erreurs - Le champ IO_ERROR indique si la lecture a réussi
  5. Éteindre le moteur - Économie d’énergie et réduction du bruit
  6. Sauter au programme chargé - jmp (a0) avec A0 = $20000

Gestion du padding

Le bootblock doit faire exactement 1024 octets. La directive dcb.b remplit automatiquement avec des zéros jusqu’à atteindre cette taille.

Réalisation du programme principal

Le fichier hello.s

C’est le cœur de notre programme. Il configure directement les custom chips de l’Amiga pour afficher notre message.

Créez le fichier hello.s (je vais montrer les parties principales, le fichier complet est long) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
; =============================================================================
; Hello, World! graphique pour Amiga 500
; Boot direct depuis disquette - Pas besoin d'AmigaOS
; Affichage blanc sur fond noir, centré à l'écran
; =============================================================================

; -----------------------------------------------------------------------------
; Registres hardware custom chips
; -----------------------------------------------------------------------------
CUSTOM EQU $DFF000

; Registres d'affichage
DIWSTRT EQU $08E ; Display window start
DIWSTOP EQU $090 ; Display window stop
DDFSTRT EQU $092 ; Data fetch start
DDFSTOP EQU $094 ; Data fetch stop
BPLCON0 EQU $100 ; Bitplane control 0
BPLCON1 EQU $102 ; Bitplane control 1
BPLCON2 EQU $104 ; Bitplane control 2
BPL1MOD EQU $108 ; Bitplane modulo odd
BPL2MOD EQU $10A ; Bitplane modulo even
BPL1PTH EQU $0E0 ; Bitplane 1 pointer high
BPL1PTL EQU $0E2 ; Bitplane 1 pointer low

; Registres Copper
COP1LCH EQU $080 ; Copper list 1 pointer high
COP1LCL EQU $082 ; Copper list 1 pointer low
COPJMP1 EQU $088 ; Copper jump strobe 1

; Registres couleurs
COLOR00 EQU $180 ; Couleur 0 (fond)
COLOR01 EQU $182 ; Couleur 1 (texte)

; Registres DMA
DMACON EQU $096 ; DMA control write
DMACONR EQU $002 ; DMA control read

; Registres interruptions
INTENA EQU $09A ; Interrupt enable
INTENAR EQU $01C ; Interrupt enable read
INTREQ EQU $09C ; Interrupt request
INTREQR EQU $01E ; Interrupt request read

; Bits DMACON
DMAF_SETCLR EQU $8000
DMAF_COPPER EQU $0080
DMAF_RASTER EQU $0100
DMAF_MASTER EQU $0200

; Bits pour VBlank
INTF_VERTB EQU $0020 ; Vertical blank interrupt

; -----------------------------------------------------------------------------
; Constantes écran
; -----------------------------------------------------------------------------
SCREEN_WIDTH EQU 320
SCREEN_HEIGHT EQU 256
BYTES_PER_LINE EQU SCREEN_WIDTH/8 ; 40 octets par ligne
SCREEN_SIZE EQU BYTES_PER_LINE*SCREEN_HEIGHT

; Position du texte (centré)
; "Hello, World!" = 13 caractères x 8 pixels = 104 pixels de large
; Centre horizontal: (320 - 104) / 2 = 108 pixels = 13 octets + 4 bits
TEXT_X EQU 14 ; Octet de départ (colonne)
TEXT_Y EQU 124 ; Ligne de départ (centre vertical: (256-8)/2)

; -----------------------------------------------------------------------------
; Point d'entrée (appelé par le bootblock)
; -----------------------------------------------------------------------------
SECTION code,CODE

Start:
lea CUSTOM,a5 ; Base des registres custom

; Désactiver les interruptions et DMA
move.w #$7FFF,INTENA(a5) ; Désactiver toutes les interruptions
move.w #$7FFF,DMACON(a5) ; Désactiver tout le DMA

; Attendre la fin du frame courant (VBlank)
bsr WaitVBlank

; Calculer l'adresse absolue de l'écran
lea Screen(pc),a0
move.l a0,d0

; Patcher la Copper list avec l'adresse de l'écran
lea CopperList(pc),a1
move.w d0,6(a1) ; Partie basse (BPL1PTL)
swap d0
move.w d0,2(a1) ; Partie haute (BPL1PTH)

; Effacer l'écran (remplir de 1 pour fond noir)
move.l #SCREEN_SIZE/4-1,d0
.clear:
move.l #$FFFFFFFF,(a0)+ ; Remplir de noir (bits à 1)
dbf d0,.clear

; Dessiner le texte "Hello, World!"
bsr DrawText

; Configurer l'affichage
; BPLCON0: 1 bitplane, couleur activée
move.w #$1200,BPLCON0(a5) ; 1 bitplane + COLOR
move.w #$0000,BPLCON1(a5) ; Pas de scroll
move.w #$0000,BPLCON2(a5) ; Priorité sprites

; Configuration de la fenêtre d'affichage (PAL)
move.w #$2C81,DIWSTRT(a5) ; Display start: ligne $2C, colonne $81
move.w #$2CC1,DIWSTOP(a5) ; Display stop: ligne $2C+256, colonne $C1

; Configuration du data fetch
move.w #$0038,DDFSTRT(a5) ; Data fetch start
move.w #$00D0,DDFSTOP(a5) ; Data fetch stop

; Modulo (0 pour écran standard)
move.w #0,BPL1MOD(a5)
move.w #0,BPL2MOD(a5)

; Installer la Copper list
lea CopperList(pc),a0
move.l a0,d0
move.w d0,COP1LCL(a5)
swap d0
move.w d0,COP1LCH(a5)

; Forcer le redémarrage du Copper
move.w d0,COPJMP1(a5)

; Activer le DMA nécessaire
move.w #DMAF_SETCLR|DMAF_MASTER|DMAF_RASTER|DMAF_COPPER,DMACON(a5)

; Boucle infinie
.loop:
bsr WaitVBlank
bra.s .loop

; -----------------------------------------------------------------------------
; Attendre le VBlank (synchronisation verticale)
; -----------------------------------------------------------------------------
WaitVBlank:
; Attendre la fin du VBlank actuel
.wait1:
move.w INTREQR(a5),d0
btst #5,d0 ; INTF_VERTB
beq.s .wait1

; Acquitter l'interruption VBlank
move.w #INTF_VERTB,INTREQ(a5)

rts

; -----------------------------------------------------------------------------
; Dessiner le texte "Hello, World!" sur l'écran
; -----------------------------------------------------------------------------
DrawText:
lea Screen(pc),a0
lea Message(pc),a1
lea Font8x8(pc),a2

; Calculer l'adresse de départ: Screen + (TEXT_Y * 40) + TEXT_X
move.l a0,a3
add.w #TEXT_Y*BYTES_PER_LINE+TEXT_X,a3

.nextchar:
moveq #0,d0
move.b (a1)+,d0 ; Lire le caractère
beq.s .done ; Fin de chaîne

; Calculer l'offset dans la police (caractère - 32) * 8
sub.w #32,d0
lsl.w #3,d0 ; x8 (8 octets par caractère)

; Copier les 8 lignes du caractère
lea (a2,d0.w),a4 ; Pointeur vers le glyphe
move.l a3,a0 ; Position courante à l'écran

; Inverser les bits car fond noir (1) et texte blanc (0)
moveq #7,d1
.copyline:
move.b (a4)+,d2
not.b d2 ; Inverser: 1->0 (blanc), 0->1 (noir)
and.b d2,(a0) ; Effacer les pixels du texte (mettre à 0)
add.w #BYTES_PER_LINE,a0 ; Ligne suivante
dbf d1,.copyline

; Caractère suivant (1 octet à droite)
addq.w #1,a3
bra.s .nextchar

.done:
rts

; -----------------------------------------------------------------------------
; Copper list - doit définir le pointeur du bitplane à chaque frame
; -----------------------------------------------------------------------------
CNOP 0,4 ; Alignement sur 4 octets
CopperList:
; Définir le pointeur du bitplane (sera patché au démarrage)
dc.w BPL1PTH,$0000 ; +0: Partie haute (patchée)
dc.w BPL1PTL,$0000 ; +4: Partie basse (patchée)

; Couleurs
dc.w COLOR00,$0FFF ; Couleur 0 = blanc (pour bits à 0)
dc.w COLOR01,$0000 ; Couleur 1 = noir (pour bits à 1)

; Fin de la Copper list (attendre ligne impossible)
dc.w $FFFF,$FFFE

; -----------------------------------------------------------------------------
; Message à afficher
; -----------------------------------------------------------------------------
CNOP 0,2 ; Alignement sur mot
Message:
dc.b "Hello, World!",0
CNOP 0,2

; -----------------------------------------------------------------------------
; Police bitmap 8x8 (caractères ASCII 32-127)
; Chaque caractère fait 8 octets (8 lignes de 8 pixels)
; -----------------------------------------------------------------------------
CNOP 0,2 ; Alignement sur mot
Font8x8:
; Espace (32)
dc.b $00,$00,$00,$00,$00,$00,$00,$00
; ! (33)
dc.b $18,$18,$18,$18,$18,$00,$18,$00
; (... police complète, voir fichier hello.s ...)

; -----------------------------------------------------------------------------
; Écran (bitplane) - doit être aligné sur 8 octets pour le DMA
; -----------------------------------------------------------------------------
CNOP 0,8 ; Alignement sur 8 octets
Screen:
dcb.b SCREEN_SIZE,0

END

Explications du programme principal

Architecture de l’affichage Amiga

L’Amiga utilise un système de bitplanes pour l’affichage :

  • Chaque bitplane est un buffer de 40×256 octets (320×256 pixels)
  • Le nombre de bitplanes détermine le nombre de couleurs : 1 bitplane = 2 couleurs, 2 bitplanes = 4 couleurs, etc.
  • Nous utilisons 1 seul bitplane : bit à 0 = COLOR00 (blanc), bit à 1 = COLOR01 (noir)

Les custom chips

L’Amiga 500 possède trois coprocesseurs hardware :

  1. Copper : Processeur de listes qui modifie les registres de façon synchronisée avec le balayage vidéo
  2. Blitter : Processeur de manipulation de bitmaps ultra-rapide
  3. DMA : Accès direct à la mémoire pour l’affichage, le son, etc.

Notre programme utilise principalement le Copper et le DMA.

Séquence d’initialisation

  1. Désactivation complète ($7FFF) :

    • Interruptions (INTENA)
    • DMA (DMACON)
    • Cela nous donne un contrôle total sur le hardware
  2. Configuration de l’écran :

    • BPLCON0 = $1200 : Active 1 bitplane et la couleur
    • DIWSTRT/DIWSTOP : Définit la fenêtre d’affichage PAL (320×256)
    • DDFSTRT/DDFSTOP : Contrôle quand le hardware récupère les données
  3. Patching de la Copper list :

    • La Copper list contient les instructions pour le coprocesseur Copper
    • On doit lui indiquer où se trouve notre bitplane en mémoire
    • L’adresse 32 bits est séparée en partie haute (BPL1PTH) et basse (BPL1PTL)
  4. Activation du DMA :

    • DMAF_MASTER : Active le DMA global
    • DMAF_RASTER : Active le DMA pour l’affichage des bitplanes
    • DMAF_COPPER : Active le coprocesseur Copper

Routine de dessin de texte

La fonction DrawText est un mini moteur de rendu :

  1. Parcourt chaque caractère du message “Hello, World!”
  2. Calcule l’offset dans la police : (caractère - 32) × 8
    • ASCII 32 = espace, premier caractère de notre police
    • Chaque glyphe fait 8 octets (8 lignes de 8 pixels)
  3. Copie ligne par ligne les 8 octets du glyphe vers l’écran
  4. Inverse les bits avec not.b car notre fond est noir (bits à 1) et le texte blanc (bits à 0)
  5. Utilise and.b pour “creuser” les pixels blancs (bits à 0) dans le fond noir

Le Copper

Le Copper est un processeur de listes extraordinairement puissant qui a fait la réputation de l’Amiga. Notre Copper list minimale :

1
2
3
4
5
6
CopperList:
dc.w BPL1PTH,$0000 ; Pointeur bitplane (partie haute)
dc.w BPL1PTL,$0000 ; Pointeur bitplane (partie basse)
dc.w COLOR00,$0FFF ; Couleur 0 = blanc (bits à 0 = texte)
dc.w COLOR01,$0000 ; Couleur 1 = noir (bits à 1 = fond)
dc.w $FFFF,$FFFE ; Fin de liste

Chaque instruction est un mot de 16 bits (registre) suivi d’un mot de 16 bits (valeur).

Synchronisation VBlank

La fonction WaitVBlank est cruciale pour éviter le “tearing” (déchirement de l’image). Elle :

  1. Lit le registre INTREQR (Interrupt Request Read)
  2. Teste le bit 5 (INTF_VERTB = Vertical Blank)
  3. Boucle tant que le VBlank n’est pas actif
  4. Acquitte l’interruption avec INTREQ

Police bitmap

Notre police 8×8 contient 96 caractères (ASCII 32 à 127). Chaque caractère est défini par 8 octets :

1
2
3
4
5
6
7
8
9
10
; Lettre 'H' (ASCII 72)
dc.b $C6,$C6,$C6,$FE,$C6,$C6,$C6,$00
; Binaire : 11000110, 11000110, 11000110, 11111110...
; Visuel : ## ##
; ## ##
; ## ##
; #######
; ## ##
; ## ##
; ## ##

Réalisation du Makefile

Le Makefile automatise toute la chaîne de compilation. Créez le fichier Makefile :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# =============================================================================
# Makefile pour cross-compilation Amiga 500 - Version bootable
# =============================================================================

# Outils
ASM = vasmm68k_mot

# Fichiers
SOURCE = hello.s
BOOTBLOCK_SRC = bootblock.s
BOOTBLOCK_BIN = bootblock.bin
PROGRAM_BIN = hello.bin
ADF = hello.adf

# =============================================================================
# Cibles
# =============================================================================

.PHONY: all clean run

# Cible par défaut : créer l'ADF bootable
all: $(ADF)

# Assembler le programme principal en binaire brut
$(PROGRAM_BIN): $(SOURCE)
$(ASM) -Fbin -m68000 -nosym -quiet -o $@ $<

# Assembler le bootblock
$(BOOTBLOCK_BIN): $(BOOTBLOCK_SRC)
$(ASM) -Fbin -m68000 -nosym -quiet -o $@ $<

# Créer le fichier ADF bootable
$(ADF): $(BOOTBLOCK_BIN) $(PROGRAM_BIN)
@echo "Création de l'ADF bootable..."
# Créer un fichier ADF vide (880 Ko)
dd if=/dev/zero of=$(ADF) bs=512 count=1760 2>/dev/null
# Écrire le bootblock (premiers 1024 octets)
dd if=$(BOOTBLOCK_BIN) of=$(ADF) bs=512 count=2 conv=notrunc 2>/dev/null
# Écrire le programme après le bootblock (à partir de l'offset 1024)
dd if=$(PROGRAM_BIN) of=$(ADF) bs=512 seek=2 conv=notrunc 2>/dev/null
# Calculer et écrire le checksum du bootblock
python3 fix_checksum.py $(ADF)
@echo "ADF bootable créé: $(ADF)"

# Lancer dans l'émulateur fs-uae
run: $(ADF)
fs-uae \
--amiga_model=A500 \
--floppy_drive_0=$(ADF) \
--window_width=1170 \
--window_height=900 \
--window_resizable=1 \
--keep_aspect=1

# Nettoyer les fichiers générés
clean:
rm -f $(ADF) $(BOOTBLOCK_BIN) $(PROGRAM_BIN)

Explications du Makefile

Variables d’outils

1
ASM = vasmm68k_mot      # Assembleur 68000, syntaxe Motorola

Options de compilation

Les options passées à vasmm68k_mot sont :

  • -Fbin : Génère un binaire brut (pas de header Amiga)
  • -m68000 : Cible le processeur 68000 (pas les extensions 68020+)
  • -nosym : Pas de symboles de debug
  • -quiet : Mode silencieux

Création de l’ADF

La cible principale $(ADF) effectue une séquence complexe :

  1. Créer un fichier vide de 880 Ko (1760 secteurs × 512 octets) :

    1
    dd if=/dev/zero of=hello.adf bs=512 count=1760
  2. Écrire le bootblock aux 1024 premiers octets :

    1
    dd if=bootblock.bin of=hello.adf bs=512 count=2 conv=notrunc
    • count=2 : 2 secteurs de 512 octets = 1024 octets
    • conv=notrunc : Ne pas tronquer le fichier de destination
  3. Écrire le programme à l’offset 1024 :

    1
    dd if=hello.bin of=hello.adf bs=512 seek=2 conv=notrunc
    • seek=2 : Sauter les 2 premiers secteurs (le bootblock)
  4. Calculer le checksum :

    1
    python3 fix_checksum.py hello.adf

Cible run

Lance l’émulateur fs-uae avec des paramètres optimisés :

  • --amiga_model=A500 : Émule un Amiga 500
  • --floppy_drive_0=... : Insère l’ADF dans le lecteur DF0:
  • --window_width/height : Taille de fenêtre confortable
  • --keep_aspect=1 : Garde le ratio 4:3 de l’Amiga

Cible clean

Supprime tous les fichiers générés pour repartir de zéro.

Réalisation du script de checksum

Le checksum est crucial : sans lui, le Kickstart refuse de booter la disquette !

Créez le fichier fix_checksum.py :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env python3
"""
Calcule et corrige le checksum du bootblock Amiga.
Le checksum est à l'offset 4 et doit être tel que la somme
de tous les mots longs (32 bits) du bootblock soit égale à 0.
"""

import sys

def calculate_checksum(data):
"""Calcule le checksum du bootblock Amiga."""
checksum = 0
# Parcourir le bootblock par mots longs (4 octets)
for i in range(0, 1024, 4):
if i == 4:
# Ignorer l'emplacement du checksum lui-même
continue
word = int.from_bytes(data[i:i+4], byteorder='big')
checksum += word
# Gestion du carry (addition modulo 2^32 avec carry)
if checksum > 0xFFFFFFFF:
checksum = (checksum & 0xFFFFFFFF) + 1

# Le checksum final est le complément
checksum = (~checksum) & 0xFFFFFFFF
return checksum

def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <fichier.adf>")
sys.exit(1)

adf_file = sys.argv[1]

# Lire le fichier ADF
with open(adf_file, 'rb') as f:
data = bytearray(f.read())

# Calculer le checksum
checksum = calculate_checksum(data[:1024])

# Écrire le checksum à l'offset 4
data[4:8] = checksum.to_bytes(4, byteorder='big')

# Réécrire le fichier
with open(adf_file, 'wb') as f:
f.write(data)

print(f"Checksum calculé et écrit: 0x{checksum:08X}")

if __name__ == "__main__":
main()

Explications du script Python

Algorithme du checksum Amiga

Le checksum du bootblock est une somme de contrôle spéciale qui garantit l’intégrité du code. L’algorithme :

  1. Additionner tous les mots longs (32 bits) du bootblock (256 mots longs)
  2. Ignorer le mot long à l’offset 4 (c’est là qu’on écrira le checksum)
  3. Gérer le carry : Si la somme dépasse 32 bits, ajouter le bit de carry à la somme
  4. Calculer le complément : checksum = ~somme

Le résultat : la somme de tous les mots longs du bootblock (y compris le checksum) = 0x00000000.

Gestion du carry

1
2
if checksum > 0xFFFFFFFF:
checksum = (checksum & 0xFFFFFFFF) + 1

Cette ligne implémente l’addition avec carry circulaire :

  • Si la somme dépasse 32 bits, on garde les 32 bits bas et on ajoute 1
  • C’est équivalent à l’instruction 68000 addx (add extended)

Complément à 2

1
checksum = (~checksum) & 0xFFFFFFFF

Le ~ inverse tous les bits (complément à 1), et le masque & 0xFFFFFFFF assure qu’on reste sur 32 bits.

Écriture Big Endian

1
data[4:8] = checksum.to_bytes(4, byteorder='big')

Le Motorola 68000 est big endian : l’octet le plus significatif est stocké en premier. Python gère cela automatiquement avec byteorder='big'.

Compilation et test

Compilation

Maintenant que tous les fichiers sont créés, compilez le projet :

1
make

Vous devriez voir :

1
2
3
Création de l'ADF bootable...
Checksum calculé et écrit: 0x12345678
ADF bootable créé: hello.adf

✓ Le fichier hello.adf de 880 Ko est prêt !

Test dans l’émulateur

Lancez le programme dans fs-uae :

1
make run

L’émulateur démarre, et après quelques secondes (temps de boot du Kickstart), vous devriez voir :

Hello, World!

Résolution de problèmes courants

L’écran reste noir

Problème : Le bootblock n’a pas le bon checksum
Solution : Vérifiez que fix_checksum.py s’est bien exécuté

L’émulateur ne démarre pas

Problème : ROM Kickstart manquante
Solution : fs-uae télécharge automatiquement les ROMs libres, ou utilisez fs-uae-launcher pour configurer une ROM Kickstart 1.3

Le texte n’apparaît pas

Problème : Adresse du bitplane incorrecte dans la Copper list
Solution : Vérifiez le patching de la Copper list dans hello.s

Test sur un vrai Amiga 500

Transfert de l’ADF

Pour tester sur un vrai Amiga, plusieurs méthodes :

  1. Disquette physique :
    • Utilisez un Greaseweazle pour écrire directement sur disquette

Greaseweazle V4.1

  1. Gotek avec FlashFloppy :
    • Remplacez le lecteur interne par un Gotek (émulateur de lecteur USB)
    • Copiez hello.adf sur une clé USB
    • Sélectionnez le fichier depuis le Gotek

Internal USB Floppy Emulator Gotek for Amiga 500

  1. WHDLoad / HDD :

    • Si vous avez un disque dur ou un IDE68K, copiez l’ADF et montez-le

    IDE68K + GottaGo FastRAM 8MB

Ce que vous devriez voir

Sur un vrai Amiga 500 :

  1. Insérez la disquette / sélectionnez l’ADF
  2. Allumez l’Amiga (ou Ctrl+Amiga+Amiga pour reset)
  3. Le lecteur tourne pendant ~1-2 secondes
  4. L’écran noir apparaît avec “Hello, World!” en blanc
  5. Le lecteur s’arrête (moteur éteint)

C’est un moment magique ! Voir votre code tourner sur le vrai hardware après des décennies…

J'attends de codé ça depuis 1988 😅

Pour aller plus loin

Maintenant que vous maîtrisez les bases, quelques idées d’amélioration :

Ajout d’animations

  • Scrolling horizontal : Modifiez BPLCON1 pour faire défiler le texte
  • Effet rainbow : Utilisez le Copper pour changer les couleurs ligne par ligne
  • Sprite matériel : Ajoutez un curseur ou une petite icône animée

Utilisation du Blitter

Le Blitter est le secret des performances graphiques de l’Amiga :

  • Copie ultra-rapide de bitmaps
  • Opérations logiques (AND, OR, XOR)
  • Remplissage de zones

Son et musique

Le chip Paula de l’Amiga permet 4 canaux audio 8 bits :

  • Ajoutez un petit jingle au démarrage
  • Intégrez un module ProTracker

Compression

Pour des programmes plus gros, utilisez :

  • Doynax LZ : Compresseur optimisé pour 68000
  • Shrinkler : Excellent ratio de compression

Ressources pour continuer

Mes livres sur l'Amiga 500

Documentation officielle

  • Amiga Hardware Reference Manual : La bible du développement Amiga
  • Motorola M68000 Programmer’s Reference Manual : Documentation complète du 68000

Sites web

Outils modernes

  • Amiga Assembly (extension VS Code) : Coloration syntaxique et auto-complétion
  • vscode-amiga-debug : Débogueur intégré pour fs-uae
  • Photon’s m68k vscode extension : Snippets et aide

Conclusion

Mon bébé 🖤

Nous avons parcouru un long chemin : de l’installation des outils à un programme bootable complet qui contrôle directement le hardware de l’Amiga 500. Vous disposez maintenant d’une base solide pour explorer plus en profondeur la programmation de cette machine légendaire.

Ce qui rend l’Amiga si spécial, c’est cette sensation de contrôle total sur la machine. Pas d’API abstraite, pas de couche système : juste vous et le métal. C’était l’esprit des années 80-90, et il est toujours vivant aujourd’hui grâce à la communauté rétro-computing.

Alors, qu’allez-vous créer ? Une démo ? Un petit jeu ? Une application utilitaire ? Les possibilités sont infinies, et le Motorola 68000 attend vos instructions.

💾 Le code source complet du projet sur Github

Bon codage, et vive l’Amiga !


Article rédigé avec nostalgie et passion pour le retro-computing.

Jérémy @ Code Alchimie