-
Notifications
You must be signed in to change notification settings - Fork 1
/
hello.asm
585 lines (500 loc) · 18.5 KB
/
hello.asm
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
;==============================================================
; INITIAL VDP REGISTER VALUES
;==============================================================
; 24 register values to be copied to the VDP during initialisation.
; These specify things like initial width/height of the planes,
; addresses within VRAM to find scroll/sprite data, the
; background palette/colour index, whether or not the display
; is on, and clears initial values for things like DMA.
;==============================================================
VDPRegisters:
dc.b 0x14 ; 0x00: H interrupt on, palettes on
dc.b 0x74 ; 0x01: V interrupt on, display on, DMA on, Genesis mode on
dc.b 0x30 ; 0x02: Pattern table for Scroll Plane A at VRAM 0xC000 (bits 3-5 = bits 13-15)
dc.b 0x00 ; 0x03: Pattern table for Window Plane at VRAM 0x0000 (disabled) (bits 1-5 = bits 11-15)
dc.b 0x07 ; 0x04: Pattern table for Scroll Plane B at VRAM 0xE000 (bits 0-2 = bits 11-15)
dc.b 0x78 ; 0x05: Sprite table at VRAM 0xF000 (bits 0-6 = bits 9-15)
dc.b 0x00 ; 0x06: Unused
dc.b 0x00 ; 0x07: Background colour: bits 0-3 = colour, bits 4-5 = palette
dc.b 0x00 ; 0x08: Unused
dc.b 0x00 ; 0x09: Unused
dc.b 0x08 ; 0x0A: Frequency of Horiz. interrupt in Rasters (number of lines travelled by the beam)
dc.b 0x00 ; 0x0B: External interrupts off, V scroll fullscreen, H scroll fullscreen
dc.b 0x81 ; 0x0C: Shadows and highlights off, interlace off, H40 mode (320 x 224 screen res)
dc.b 0x3F ; 0x0D: Horiz. scroll table at VRAM 0xFC00 (bits 0-5)
dc.b 0x00 ; 0x0E: Unused
dc.b 0x02 ; 0x0F: Autoincrement 2 bytes
dc.b 0x01 ; 0x10: Scroll plane size: 64x32 tiles
dc.b 0x00 ; 0x11: Window Plane X pos 0 left (pos in bits 0-4, left/right in bit 7)
dc.b 0x00 ; 0x12: Window Plane Y pos 0 up (pos in bits 0-4, up/down in bit 7)
dc.b 0xFF ; 0x13: DMA length lo byte
dc.b 0xFF ; 0x14: DMA length hi byte
dc.b 0x00 ; 0x15: DMA source address lo byte
dc.b 0x00 ; 0x16: DMA source address mid byte
dc.b 0x80 ; 0x17: DMA source address hi byte, memory-to-VRAM mode (bits 6-7)
even
;==============================================================
; CONSTANTS
;==============================================================
; Defines names for commonly used values and addresses to make
; the code more readable.
;==============================================================
SNK_VInt equ 0xFFFFFFF0
SNK_Hint equ 0xFFFFFFF6
status_reg_trace equ (1<<15)
status_reg_unused1 equ (1<<14)
status_reg_supervisor equ (1<<13)
status_reg_unused2 equ (1<<12)
status_reg_unused3 equ (1<<11)
status_reg_int2 equ (1<<10)
status_reg_int1 equ (1<<9)
status_reg_int0 equ (1<<8)
status_reg_unused4 equ (1<<7)
status_reg_unused5 equ (1<<6)
status_reg_unused6 equ (1<<5)
status_reg_ccr_extend equ (1<<4)
status_reg_ccr_negative equ (1<<3)
status_reg_ccr_zero equ (1<<2)
status_reg_ccr_overflow equ (1<<1)
status_reg_ccr_carry equ (1<<0)
status_reg_int_disable equ (status_reg_int0|status_reg_int1|status_reg_int2)
; RAM
ram_start equ 0x00FF0000
; VDP port addresses
vdp_control equ 0x00C00004
vdp_data equ 0x00C00000
; VDP commands
vdp_cmd_vram_write equ 0x40000000
vdp_cmd_cram_write equ 0xC0000000
; VDP memory addresses
; according to VDP registers 0x2 and 0x4 (see table above)
vram_addr_tiles equ 0x0000
vram_addr_plane_a equ 0xC000
vram_addr_plane_b equ 0xE000
; Screen width and height (in pixels)
vdp_screen_width equ 0x0140
vdp_screen_height equ 0x00F0
; The plane width and height (in tiles)
; according to VDP register 0x10 (see table above)
vdp_plane_width equ 0x40
vdp_plane_height equ 0x20
; Hardware version address
hardware_ver_address equ 0x00A10001
; TMSS
tmss_address equ 0x00A14000
tmss_signature equ 'SEGA'
; The size of a word and longword
size_word equ 2
size_long equ 4
; The size of one palette (in bytes, words, and longwords)
size_palette_b equ 0x20
size_palette_w equ size_palette_b/size_word
size_palette_l equ size_palette_b/size_long
; The size of one graphics tile (in bytes, words, and longwords)
size_tile_b equ 0x20
size_tile_w equ size_tile_b/size_word
size_tile_l equ size_tile_b/size_long
; Hello World draw position (in tiles)
text_pos_x equ 0x08
text_pos_y equ 0x04
;==============================================================
; VRAM WRITE MACROS
;==============================================================
; Some utility macros to help generate addresses and commands for
; writing data to video memory, since they're tricky (and
; error prone) to calculate manually.
; The resulting command and address is written to the VDP's
; control port, ready to accept data in the data port.
;==============================================================
; Set the VRAM (video RAM) address to write to next
SetVRAMWrite: macro addr
move.l #(vdp_cmd_vram_write)|((\addr)&$3FFF)<<16|(\addr)>>14, vdp_control
endm
; Set the CRAM (colour RAM) address to write to next
SetCRAMWrite: macro addr
move.l #(vdp_cmd_cram_write)|((\addr)&$3FFF)<<16|(\addr)>>14, vdp_control
endm
;==============================================================
; PALETTE
;==============================================================
; A single colour palette (16 colours) we'll be using to draw text.
; Colour #0 is always transparent, no matter what colour value
; you specify.
; We only use white (colour 2) and transparent (colour 0) in this
; demo, the rest are just examples.
;==============================================================
; Each colour is in binary format 0000 BBB0 GGG0 RRR0,
; so 0x0000 is black, 0x0EEE is white (NOT 0x0FFF, since the
; bottom bit is discarded), 0x000E is red, 0x00E0 is green, and
; 0x0E00 is blue.
;==============================================================
Palette:
dc.w 0x0000 ; Colour 0 = Transparent
dc.w 0x0000 ; Colour 1 = Black
dc.w 0x0EEE ; Colour 2 = White
dc.w 0x000E ; Colour 3 = Red
dc.w 0x00E0 ; Colour 4 = Blue
dc.w 0x0E00 ; Colour 5 = Green
dc.w 0x0E0E ; Colour 6 = Pink
dc.w 0x0000 ; Leave the rest black...
dc.w 0x0000
dc.w 0x0000
dc.w 0x0000
dc.w 0x0000
dc.w 0x0000
dc.w 0x0000
dc.w 0x0000
dc.w 0x0000
;==============================================================
; GRAPHICS TILES
;==============================================================
; The 8x8 pixel graphics tiles that describe the font.
; We only need to specify glyphs for "HELO WRD" since they're reusable.
; 'SPACE' is first, which is unneccessary but it's a good teaching tool for
; why we leave the first tile in memory blank (try changing it
; and see what happens!).
;==============================================================
; 0 = transparent pixel
; 2 = colour 'white' in our palette (see palette above)
;==============================================================
; Change all of the 2's to 3, 4 or 5 to draw the text in red, blue
; or green (see the palette above).
;==============================================================
CharacterSpace:
dc.l 0x00000000
dc.l 0x00000000
dc.l 0x00000000
dc.l 0x00000000
dc.l 0x00000000
dc.l 0x00000000
dc.l 0x00000000
dc.l 0x00000000
CharacterH:
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22222220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x00000000
CharacterE:
dc.l 0x22222220
dc.l 0x22000000
dc.l 0x22000000
dc.l 0x22222220
dc.l 0x22000000
dc.l 0x22000000
dc.l 0x22222220
dc.l 0x00000000
CharacterL:
dc.l 0x22000000
dc.l 0x22000000
dc.l 0x22000000
dc.l 0x22000000
dc.l 0x22000000
dc.l 0x22000000
dc.l 0x22222220
dc.l 0x00000000
CharacterO:
dc.l 0x22222220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22222220
dc.l 0x00000000
CharacterW:
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22020220
dc.l 0x22020220
dc.l 0x22020220
dc.l 0x22222220
dc.l 0x00000000
CharacterR:
dc.l 0x22222200
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22222200
dc.l 0x22022000
dc.l 0x22002200
dc.l 0x22000220
dc.l 0x00000000
CharacterD:
dc.l 0x22222200
dc.l 0x22002220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22002220
dc.l 0x22222200
dc.l 0x00000000
CharacterK:
dc.l 0x22000220
dc.l 0x22002200
dc.l 0x22022000
dc.l 0x22200000
dc.l 0x22220000
dc.l 0x22022000
dc.l 0x22002200
dc.l 0x00000000
CharacterN:
dc.l 0x22000220
dc.l 0x22200220
dc.l 0x22220220
dc.l 0x22022220
dc.l 0x22002220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x00000000
CharacterU:
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22000220
dc.l 0x22222220
dc.l 0x22222220
dc.l 0x00000000
CharacterC:
dc.l 0x00002220
dc.l 0x00222000
dc.l 0x22220000
dc.l 0x22200000
dc.l 0x22200000
dc.l 0x00222000
dc.l 0x00002220
dc.l 0x00000000
CharacterS:
dc.l 0x00222220
dc.l 0x02000000
dc.l 0x20000000
dc.l 0x02222200
dc.l 0x00000020
dc.l 0x00000200
dc.l 0x02222000
dc.l 0x00000000
;==============================================================
; TILE IDs
;==============================================================
; The indices of each tile above. Once the tiles have been
; written to VRAM, the VDP refers to each tile by its index.
;==============================================================
tile_id_space equ 0x0
tile_id_h equ 0x1
tile_id_e equ 0x2
tile_id_l equ 0x3
tile_id_o equ 0x4
tile_id_w equ 0x5
tile_id_r equ 0x6
tile_id_d equ 0x7
tile_id_k equ 0x8
tile_id_n equ 0x9
tile_id_u equ 0xA
tile_id_c equ 0xB
tile_id_s equ 0xC
tile_count equ 0xD ; Last entry is just the count
;==============================================================
; CODE ENTRY POINT
;==============================================================
; The "main()" function. Your code starts here. Once the CPU
; has finished initialising, it will jump to this entry point
; (specified in the vector table at the top of the file).
;==============================================================
CPU_EntryPoint_SNK:
; Disable interrupts (they're pointing to RAM, but we overwrote it when blowing the stack)
ori.w #status_reg_int_disable, sr
; Reset the stack from S&K
lea 0x00FFE000, sp
; Write RTEs to interrupt handlers
move.w #0x4E73, SNK_VInt
move.w #0x4E73, SNK_HInt
; Booted from S&K
move.l #'SNK_', ram_start
CPU_EntryPoint:
;==============================================================
; Initialise the Mega Drive
;==============================================================
; If we didn't boot from SNK
cmp.l #'SNK_', ram_start
beq @NoTMSS
; Write the TMSS signature (if a model 1+ Mega Drive)
jsr VDP_WriteTMSS
@NoTMSS:
; Load the initial VDP registers
jsr VDP_LoadRegisters
;==============================================================
; Clear VRAM (video memory)
;==============================================================
; Setup the VDP to write to VRAM address 0x0000 (start of VRAM)
SetVRAMWrite 0x0000
; Write 0's across all of VRAM
move.w #(0x00010000/size_word)-1, d0 ; Loop counter = 64kb, in words (-1 for DBRA loop)
@ClrVramLp: ; Start of loop
move.w #0x0, vdp_data ; Write a 0x0000 (word size) to VRAM
dbra d0, @ClrVramLp ; Decrement d0 and loop until finished (when d0 reaches -1)
;==============================================================
; Initialise status register and set interrupt level.
; This begins firing vertical and horizontal interrupts.
;==============================================================
move.w #0x2300, sr
;==============================================================
; Write the palette to CRAM (colour memory)
;==============================================================
; Setup the VDP to write to CRAM address 0x0000 (first palette)
SetCRAMWrite 0x0000
; Write the palette to CRAM
lea Palette, a0 ; Move palette address to a0
move.w #size_palette_w-1, d0 ; Loop counter = 8 words in palette (-1 for DBRA loop)
@PalLp: ; Start of loop
move.w (a0)+, vdp_data ; Write palette entry, post-increment address
dbra d0, @PalLp ; Decrement d0 and loop until finished (when d0 reaches -1)
;==============================================================
; Write the font tiles to VRAM
;==============================================================
; Setup the VDP to write to VRAM address 0x0000 (the address of the first graphics tile, index 0)
SetVRAMWrite vram_addr_tiles
; Write the font glyph tiles to VRAM
lea CharacterSpace, a0 ; Move the address of the first graphics tile into a0
move.w #(tile_count*size_tile_l)-1, d0 ; Loop counter = 8 longwords per tile * num tiles (-1 for DBRA loop)
@CharLp: ; Start of loop
move.l (a0)+, vdp_data ; Write tile line (4 bytes per line), and post-increment address
dbra d0, @CharLp ; Decrement d0 and loop until finished (when d0 reaches -1)
;==============================================================
; Write the tile IDs of "HELLO WORLD" to Plane A's cell grid
;==============================================================
; Each scroll plane is made up of a 64x32 tile grid (this size is specified in VDP register 0x10),
; with each cell specifying the index of each tile to draw, the palette to draw it with, and
; some flags (for priority and flipping).
;
; Each plane cell is 1 word in size (2 bytes), in the binary format
; ABBC DEEE EEEE EEEE, where:
;
; A = Draw priority (1 bit)
; B = Palette index (2 bits, specifies palette 0, 1, 2, or 3)
; C = Flip tile horizontally (1 bit)
; D = Flip tile vertically (1 bit)
; E = Tile index to draw (11 bits, specifies tile index from 0 to 2047)
;
; Since we're using priority 0, palette 0, and no flipping, we
; only need to write the tile ID and leave everything else zero.
; Setup the VDP to write the tile ID at text_pos_x,text_pos_y in plane A's cell grid.
; Plane A's cell grid starts at address 0xC000, which is specified in VDP register 0x2.
;
; Since each cell is 1 word in size, to compute a cell address within plane A:
; ((y_pos * plane_width) + x_pos) * size_word
SetVRAMWrite vram_addr_plane_a+(((text_pos_y*vdp_plane_width)+text_pos_x)*size_word)
; then move the tile ID for "H" to VRAM
move.w #tile_id_h, vdp_data ; H
; Repeat for the remaining characters in the string.
; We don't need to adjust the VRAM address each time, since the auto-increment
; register (VDP register 0xF) is set to 2, so the destination address
; will automatically increment by one word (conveniently the size of a cell)
; after each write.
move.w #tile_id_e, vdp_data ; E
move.w #tile_id_l, vdp_data ; L
move.w #tile_id_l, vdp_data ; L
move.w #tile_id_o, vdp_data ; 0
move.w #tile_id_space, vdp_data ; SPACE
cmp.l #'SNK_', ram_start
beq @Knuckles
move.w #tile_id_w, vdp_data ; W
move.w #tile_id_o, vdp_data ; O
move.w #tile_id_r, vdp_data ; R
move.w #tile_id_l, vdp_data ; L
move.w #tile_id_d, vdp_data ; D
bra @NoKnuckles
@Knuckles:
move.w #tile_id_k, vdp_data ; K
move.w #tile_id_n, vdp_data ; N
move.w #tile_id_u, vdp_data ; U
move.w #tile_id_c, vdp_data ; C
move.w #tile_id_k, vdp_data ; K
move.w #tile_id_l, vdp_data ; L
move.w #tile_id_e, vdp_data ; E
move.w #tile_id_s, vdp_data ; S
@NoKnuckles:
; Finished!
;==============================================================
; Loop forever
;==============================================================
; This loops forever, effectively ending our code. The VDP will
; still continue to run (and fire vertical/horizontal interrupts)
; of its own accord, so it will continue to render our Hello World
; even though the CPU is stuck in this loop.
@InfiniteLp:
bra @InfiniteLp
;==============================================================
; INTERRUPT ROUTINES
;==============================================================
; The interrupt routines, as specified in the vector table at
; the top of the file.
; Note that we use RTE to return from an interrupt, not
; RTS like a subroutine.
;==============================================================
; Vertical interrupt - run once per frame
INT_VInterrupt:
; Doesn't do anything in this demo
rte
; Horizontal interrupt - run once per N scanlines (N = specified in VDP register 0xA)
INT_HInterrupt:
; Doesn't do anything in this demo
rte
; NULL interrupt - for interrupts we don't care about
INT_Null:
rte
; Exception interrupt - called if an error has occured
CPU_Exception:
; Just halt the CPU if an error occurred. Later on, you may want to write
; an exception handler to draw the current state of the machine to screen
; (registers, stack, error type, etc) to help debug the problem.
stop #0x2700
rte
;==============================================================
; UTILITY FUNCTIONS
;==============================================================
; Subroutines to initialise the TMSS, and load all VDP registers
;==============================================================
VDP_WriteTMSS:
; The TMSS (Trademark Security System) locks up the VDP if we don't
; write the string 'SEGA' to a special address. This was to discourage
; unlicensed developers, since doing this displays the "LICENSED BY SEGA
; ENTERPRISES LTD" message to screen (on Mega Drive models 1 and higher).
;
; First, we need to check if we're running on a model 1+, then write
; 'SEGA' to hardware address 0xA14000.
move.b hardware_ver_address, d0 ; Move Megadrive hardware version to d0
andi.b #0x0F, d0 ; The version is stored in last four bits, so mask it with 0F
beq @SkipTMSS ; If version is equal to 0, skip TMSS signature
move.l #tmss_signature, tmss_address ; Move the string "SEGA" to 0xA14000
@SkipTMSS:
; Check VDP
move.w vdp_control, d0 ; Read VDP status register (hangs if no access)
rts
VDP_LoadRegisters:
; To initialise the VDP, we write all of its initial register values from
; the table at the top of the file, using a loop.
;
; To write a register, we write a word to the control port.
; The top bit must be set to 1 (so 0x8000), bits 8-12 specify the register
; number to write to, and the bottom byte is the value to set.
;
; In binary:
; 100X XXXX YYYY YYYY
; X = register number
; Y = value to write
; Set VDP registers
lea VDPRegisters, a0 ; Load address of register table into a0
move.w #0x18-1, d0 ; 24 registers to write (-1 for loop counter)
move.w #0x8000, d1 ; 'Set register 0' command to d1
@CopyRegLp:
move.b (a0)+, d1 ; Move register value from table to lower byte of d1 (and post-increment the table address for next time)
move.w d1, vdp_control ; Write command and value to VDP control port
addi.w #0x0100, d1 ; Increment register #
dbra d0, @CopyRegLp ; Decrement d0, and jump back to top of loop if d0 is still >= 0
rts
; A label defining the end of ROM so we can compute the total size.
ROM_End: