summaryrefslogtreecommitdiff
path: root/1_hello_world/main.c
blob: ee4ee640be752bd22c463b89057a984263145234 (plain)
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
// Rewritten from https://github.com/BigEvilCorporation/megadrive_samples/blob/master/1_hello_world/hello.asm

#include <stdint.h>
#include <stddef.h>

#define VDP_DATA                ((volatile uint16_t*)0x00C00000L)
#define VDP_CONTROL             ((volatile uint16_t*)0x00C00004L)
#define VDP_CMD_VRAM_WRITE      ((uint32_t)0x40000000L)
#define VDP_CMD_CRAM_WRITE      ((uint32_t)0xC0000000L)
#define VRAM_ADDR_TILES         (0x0000)
#define VRAM_ADDR_PLANE_A       (0xC000)
#define VRAM_ADDR_PLANE_B       (0xE000)
#define VDP_SCREEN_WIDTH        (0x0140)
#define VDP_SCREEN_HEIGHT       (0x00F0)
#define VDP_PLANE_WIDTH         (0x40)
#define VDP_PLANE_HEIGHT        (0x20)
#define HARDWARE_VER_ADDRESS    ((uint8_t*)0x00A10001L)
#define TMSS_ADDRESS            ((uint32_t*)0x00A14000L)
#define TMSS_SIGNATURE          (('S' << 24) | ('E' << 16) | ('G' << 8) | 'A')
#define SIZE_WORD               (2)
#define SIZE_LONG               (4)
#define SIZE_PALETTE_B          (0x20)
#define SIZE_PALETTE_W          (SIZE_PALETTE_B/SIZE_WORD)
#define SIZE_PALETTE_L          (SIZE_PALETTE_B/SIZE_LONG)
#define SIZE_TILE_B             (0x20)
#define SIZE_TILE_W             (SIZE_TILE_B/SIZE_WORD)
#define SIZE_TILE_L             (SIZE_TILE_B/SIZE_LONG)
#define TEXT_POS_X              (0x08)
#define TEXT_POS_Y              (0x04)

// ==============================================================
//  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.
// ==============================================================
#define TILE_ID_SPACE (0x0)
#define TILE_ID_H     (0x1)
#define TILE_ID_E     (0x2)
#define TILE_ID_L     (0x3)
#define TILE_ID_O     (0x4)
#define TILE_ID_W     (0x5)
#define TILE_ID_R     (0x6)
#define TILE_ID_D     (0x7)
#define TILE_COUNT    (0x8) // Last entry is just the count

static uint8_t vdp_registers[] = {
    0x14, // 0x00:  H interrupt on, palettes on
    0x74, // 0x01:  V interrupt on, display on, DMA on, Genesis mode on
    0x30, // 0x02:  Pattern table for Scroll Plane A at VRAM 0xC000 (bits 3-5 = bits 13-15)
    0x00, // 0x03:  Pattern table for Window Plane at VRAM 0x0000 (disabled) (bits 1-5 = bits 11-15)
    0x07, // 0x04:  Pattern table for Scroll Plane B at VRAM 0xE000 (bits 0-2 = bits 11-15)
    0x78, // 0x05:  Sprite table at VRAM 0xF000 (bits 0-6 = bits 9-15)
    0x00, // 0x06:  Unused
    0x00, // 0x07:  Background colour: bits 0-3 = colour, bits 4-5 = palette
    0x00, // 0x08:  Unused
    0x00, // 0x09:  Unused
    0x08, // 0x0A: Frequency of Horiz. interrupt in Rasters (number of lines travelled by the beam)
    0x00, // 0x0B: External interrupts off, V scroll fullscreen, H scroll fullscreen
    0x81, // 0x0C: Shadows and highlights off, interlace off, H40 mode (320 x 224 screen res)
    0x3F, // 0x0D: Horiz. scroll table at VRAM 0xFC00 (bits 0-5)
    0x00, // 0x0E: Unused
    0x02, // 0x0F: Autoincrement 2 bytes
    0x01, // 0x10: Scroll plane size: 64x32 tiles
    0x00, // 0x11: Window Plane X pos 0 left (pos in bits 0-4, left/right in bit 7)
    0x00, // 0x12: Window Plane Y pos 0 up (pos in bits 0-4, up/down in bit 7)
    0xFF, // 0x13: DMA length lo byte
    0xFF, // 0x14: DMA length hi byte
    0x00, // 0x15: DMA source address lo byte
    0x00, // 0x16: DMA source address mid byte
    0x80, // 0x17: DMA source address hi byte, memory-to-VRAM mode (bits 6-7)
};

static uint16_t palette[SIZE_PALETTE_W] = {
    0x0000, // Colour 0 = Transparent
    0x0000, // Colour 1 = Black
    0x0EEE, // Colour 2 = White
    0x000E, // Colour 3 = Red
    0x00E0, // Colour 4 = Blue
    0x0E00, // Colour 5 = Green
    0x0E0E, // Colour 6 = Pink
    0x0000, // Leave the rest black...
    0x0000,
    0x0000,
    0x0000,
    0x0000,
    0x0000,
    0x0000,
    0x0000,
    0x0000,
};

static uint32_t characters[TILE_COUNT * SIZE_TILE_L] = {
    // Space
    0x00000000L,
    0x00000000L,
    0x00000000L,
    0x00000000L,
    0x00000000L,
    0x00000000L,
    0x00000000L,
    0x00000000L,
    // H
    0x22000220L,
    0x22000220L,
    0x22000220L,
    0x22222220L,
    0x22000220L,
    0x22000220L,
    0x22000220L,
    0x00000000L,
    // E
    0x22222220L,
    0x22000000L,
    0x22000000L,
    0x22222220L,
    0x22000000L,
    0x22000000L,
    0x22222220L,
    0x00000000L,
    // L
    0x22000000L,
    0x22000000L,
    0x22000000L,
    0x22000000L,
    0x22000000L,
    0x22000000L,
    0x22222220L,
    0x00000000L,
    // O
    0x22222220L,
    0x22000220L,
    0x22000220L,
    0x22000220L,
    0x22000220L,
    0x22000220L,
    0x22222220L,
    0x00000000L,
    // W
    0x22000220L,
    0x22000220L,
    0x22000220L,
    0x22020220L,
    0x22020220L,
    0x22020220L,
    0x22222220L,
    0x00000000L,
    // R
    0x22222200L,
    0x22000220L,
    0x22000220L,
    0x22222200L,
    0x22022000L,
    0x22002200L,
    0x22000220L,
    0x00000000L,
    // D
    0x22222200L,
    0x22002220L,
    0x22000220L,
    0x22000220L,
    0x22000220L,
    0x22002220L,
    0x22222200L,
    0x00000000L,
};

// Set the VRAM (video RAM) address to write to next
static inline void SetVRAMWrite(uint16_t addr)
{
    *(volatile uint32_t*)VDP_CONTROL = VDP_CMD_VRAM_WRITE | ((addr & 0x3FFF) << 16) | (addr >> 14);
}

// Set the CRAM (colour RAM) address to write to next
static inline void SetCRAMWrite(uint16_t addr)
{
    *(volatile uint32_t*)VDP_CONTROL = VDP_CMD_CRAM_WRITE | ((addr & 0x3FFF) << 16) | (addr >> 14);
}

static void VDP_WriteTMSS(void)
{
    // 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.
    const uint8_t ver = (*HARDWARE_VER_ADDRESS) & 0x0f;
    if (ver != 0) {
        *TMSS_ADDRESS = TMSS_SIGNATURE;
    }

    // Check VDP
    // Read VDP status register (hangs if no access)
    *VDP_CONTROL;
}

static void VDP_LoadRegisters(void)
{
    // 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
    for (size_t i = 0; i < sizeof(vdp_registers) / sizeof(*vdp_registers); i++) {
        const uint16_t cmd = 0x8000; // 'Set register 0' command
        const uint16_t reg_num = i << 8;
        *VDP_CONTROL = cmd | reg_num | vdp_registers[i];
    }
}

int main(void)
{
    // ==============================================================
    //  Initialise the Mega Drive
    // ==============================================================

    // Write the TMSS signature (if a model 1+ Mega Drive)
    VDP_WriteTMSS();

    // Load the initial VDP registers
    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
    const size_t count = (0x00010000 / SIZE_WORD); // Loop counter = 64kb, in words
    for (size_t i = 0; i < count; i++) {
        *VDP_DATA = 0x0; // Write a 0x0000 (word size) to VRAM
    }

    //==============================================================
    // Initialise status register and set interrupt level.
    // This begins firing vertical and horizontal interrupts.
    //==============================================================
    asm inline volatile ("  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
    for (size_t i = 0; i < SIZE_PALETTE_W; i++) {
        *VDP_DATA = palette[i]; // Write palette entry
    }

    //==============================================================
    // 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
    for (size_t i = 0; i < TILE_COUNT * SIZE_TILE_L; i++) {
        *(volatile uint32_t*)VDP_DATA = characters[i]; // Write palette entry
    }

    //==============================================================
    // 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
    *VDP_DATA = TILE_ID_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.
    *VDP_DATA = TILE_ID_E;
    *VDP_DATA = TILE_ID_L;
    *VDP_DATA = TILE_ID_L;
    *VDP_DATA = TILE_ID_O;
    *VDP_DATA = TILE_ID_SPACE;
    *VDP_DATA = TILE_ID_W;
    *VDP_DATA = TILE_ID_O;
    *VDP_DATA = TILE_ID_R;
    *VDP_DATA = TILE_ID_L;
    *VDP_DATA = TILE_ID_D;

    // 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.
    while (1);
}