Main Screen Turn On

In my previous posts, I was able to make the Sega Genesis / Mega Drive compute the Fibonacci number sequence on it’s CPU and pass the TMSS check. In this post, I’m going to focus on making the Visual Display Processor draw images on the screen.

I will be ignoring steps performed by most other people writing on the subject: I’m not clearing the RAM, checking the Reset button, or initializing the Z-80 co-processor, controller ports, and the sound chips. I would rather add code for each subsystem as I need it. However, it’s possible that an actual Genesis will require all of these steps before it will work. I don’t know. I’m far from testing on real hardware, and I see no reason to complicate things early on. Code that works in my emulator is sufficient for learning.

The Visual Display Processor (VDP)

The VDP can display graphics on four planes: Scrolling Plane A, Scrolling Plane B, the Window Plane, and the Sprite Plane. The scrolling planes are typically used for background and level graphics. The Sprite plane is where moving objects like characters, enemies, bullets, items, etc reside. The Window plane is, I believe, used for pop-up windows like the dialogue box in a role playing game.

The VDP has 64kB of VRAM where most of the graphic data will be stored. Portions of VRAM will be designated as tables for the four planes. Other portions of VRAM can be used for storing patterns which are the graphic tiles that will populate the planes. There is also Color RAM (CRAM) where up to four 16-color palettes can be stored. Finally, there is Vertical Scroll RAM (VSRAM) which won’t be covered in this post.

The basic process of making graphics appear onscreen is like this:

  1. Initialize the VDP
  2. Load Palettes into CRAM
  3. Load Patterns into VRAM
  4. Copy Patterns into A Plane

The VDP occupies hardware addresses 0x00C00000 – 0x00C0001F. I will be focusing on the data port (0x00C00000) and the control port (0x00C00004). The Control Port is used to send commands to the VDP. The data port is used for copying data such as palettes and patterns into VRAM, CRAM, or VSRAM. Since we will be using the control and data ports a lot, it seems reasonable to store their locations in two of the CPU’s address registers:

; ******************************************************************************
; ************************* Initialize the Registers ***************************
; ******************************************************************************
	move.l #0x00C00000, a5		; Address of VDP Data Port
	move.l #0x00C00004, a6		; Address of VDP Control Port

Initialize the VDP

The VDP has 24 8-bit registers. I’ll initialize the first 19 registers and leave the last 5 (used for DMA) alone because real hardware will apparently crash otherwise. A VDP register is set by sending a 16-bit value to the VDP’s control port. The first byte specifies the register (beginning at 0x8000) and the second byte is the value to set. For example 0x80CC sets register 0 to CC and 0x8133 sets register 1 to 33. I created a “data” section near the end of my source file. In the data section, I put starting values for the 24 VDP registers:

; ******************************************************************************
; *********************************** Data *************************************
; ******************************************************************************
VDPRegisters:
	dc.b 0x04 ; 0: Horiz. interrupt on, plus bit 2 on (???)
	dc.b 0x74 ; 1: Vert. int on, display on, DMA on, V28 mode, bit 2 on (???)
	dc.b 0x30 ; 2: Pattern table for Scroll Plane A at 0xC000 (bits 3-5)
	dc.b 0x40 ; 3: Pattern table for Window Plane at 0x10000 (bits 1-5)
	dc.b 0x05 ; 4: Pattern table for Scroll Plane B at 0xA000 (bits 0-2)
	dc.b 0x70 ; 5: Sprite table at 0xE000 (bits 0-6)
	dc.b 0x00 ; 6: Unused
	dc.b 0x00 ; 7: Background colour - bits 0-3 = colour, bits 4-5 = palette
	dc.b 0x00 ; 8: Unused
	dc.b 0x00 ; 9: Unused
	dc.b 0x00 ; 10: Frequency of Horiz. interrupt in Rasters
	dc.b 0x08 ; 11: External interrupts on, V/H scrolling on
	dc.b 0x81 ; 12: Shadows and highlights off, interlace off, H40 mode
	dc.b 0x34 ; 13: Horiz. scroll table at 0xD000 (bits 0-5)
	dc.b 0x00 ; 14: Unused
	dc.b 0x00 ; 15: Autoincrement off
	dc.b 0x01 ; 16: Vert. scroll 32, Horiz. scroll 64
	dc.b 0x00 ; 17: Window Plane X pos 0 left (pos in bits 0-4, l/r in bit 7)
	dc.b 0x00 ; 18: Window Plane Y pos 0 up (pos in bits 0-4, u/d in bit 7)
	dc.b 0x00 ; 19: DMA length lo byte
	dc.b 0x00 ; 20: DMA length hi byte
	dc.b 0x00 ; 21: DMA source address lo byte
	dc.b 0x00 ; 22: DMA source address mid byte
	dc.b 0x00 ; 23: DMA source address hi byte, memory-to-VRAM mode (bits 6-7)

I must admit that I don’t know what all of this means, but I understand these to be reasonably safe starting values. I have no doubt that experienced programmers may have reasons to change these values, so I will link some documentation at the end of this article.

Here is a loop that copies the first 19 register values into the first 19 (0-18) VDP registers:

; ******************************************************************************
; ************** Initialize the Visual Display Processor (VDP) *****************
; ******************************************************************************
	move.l #VDPRegisters, a0	; Load address of register table into a0
	move.l #18, d0         		; Loop counter for 19 registers
	move.w #0x8000, d1   		; 0x80nn accesses VDP Register #0
VDPCopy:
	move.b (a0)+, d1		; Move value from table to low byte of d1
	move.w d1, (a6)			; Write register # and value to control port
	add.w #0x0100, d1		; Increment register #
	dbra d0, VDPCopy		; Decrement d0, loop until d0 = -1

Most of these instructions were explained in my previous article, so the comments should be sufficient for understanding. However, there are three new things. Two are in this line:

	move.b (a0)+, d1		; Move register value to lower byte of d1

At the beginning of this section, a0 points at the first entry in the VDP register data table. Putting parenthesis around a0 dereferences that address; meaning that the instruction is using the value in memory that a0 is addressing rather than the value stored in a0.

	; d1 contains 0x8000
	move.b (a0)+, d1	; moves 0x04 into the low byte of d1
	; d1 now contains 0x8004

The + following (a0) is a post-increment. It increases the value of a0 by the size of the operation. Since we were moving a byte (designated by .b), it increases a0 by 1 so it will point at the next byte in memory which is the second entry in the VDP register data table. This one line of code really does a lot!

Finally, I am using another way to loop:

	dbra d0, VDPCopy		; Decrement d0, loop until d0 = -1

dbra stands for Decrement and Branch. Used here, it decrements d0 by 1 and jumps up to VDPCopy: if d0 is not -1. d0 is set to 18 in the second line of the above code to loop through 19 registers. This type of loop will be used a lot.

Load a Palette into CRAM

Now the fun stuff, colors! People say the Sega Genesis can display 64 colors simultaneously. That’s a bit of a lie. The Genesis can have four 16-color palettes in CRAM. 4×16 = 64, right? However, the first color in each palette is transparent, so the four palettes only contain 60 colors.

Ready for another lie? The “16-bit graphics” only use 9-bit color. Each color has 3 bits for red, 3 bits for green, and 3 bits for blue which allows for 8 shades apiece ranging from black to full red (or green or blue). Mixing them all together, 8x8x8 = 512 which is the number of different colors the Genesis can produce. (Even that is a lie because the VDP can do highlighting and shading which actually increases the number of possible colors, but I will not be talking about that in this post.)

Each color in a palette is sent to the VDP’s data port as a 16-bit word. The bits in the color word are arranged as follows:

	0 0 0 0  B B B 0  G G G 0  R R R 0

The B, G, and R bits specify the intensity of Blue, Green, and Red from 0-7. It is convenient to write this word in hexadecimal using only even digits: 0, 2, 4, 6, 8, A, C, E. Some examples:

	0x0E00	; full blue
	0x0000	; black
	0x0EEE	; white
	0x00CC	; yellow
	0x0080	; medium green

In the Data section of the source file, I typed in a palette:

Palette0:
	dc.w 0x0000 ; Color 0 - Transparent
	dc.w 0x0E00 ; Color 1 - Blue
	dc.w 0x0C02 ; Color 2 - 
	dc.w 0x0A04 ; Color 3 - 
	dc.w 0x0806 ; Color 4 - 
	dc.w 0x0608 ; Color 5 - 
	dc.w 0x040A ; Color 6 - 
	dc.w 0x020C ; Color 7 - 
	dc.w 0x000E ; Color 8 - Red
	dc.w 0x0EEE ; Color 9 - White
	dc.w 0x0CCC ; Color A - 
	dc.w 0x0AAA ; Color B - 
	dc.w 0x0888 ; Color C - 
	dc.w 0x0666 ; Color D - 
	dc.w 0x0444 ; Color E - 
	dc.w 0x0222 ; Color F - Dark Gray

Remember that color 0 is always transparent, no matter what value is entered. Colors 1-8 are a gradient from blue to red. Colors 9-F are a gradient from White to Dark Gray.

Loading the palette data into CRAM involves three steps:

  1. Put the VDP into CRAM writing mode
  2. Tell the VDP which address in CRAM to start writing
  3. Copy the palette into CRAM using a loop similar to the one used for the VDP registers above.

Unfortunately, steps 1 and 2 are combined in a rather messy way. Here are the codes to set the VDP mode for Step 1:

	000000	VRAM read
	000001	VRAM write
	000011	CRAM write
	000100	VSRAM read
	000101	VSRAM write
	001000	CRAM read

As shown in the table, 000011 is the code for writing CRAM. Next is to determine which address in CRAM to start writing Palette 0 at. Palette 0 is at the beginning of CRAM, so the address will be zero. I did a little bit of reading, math, and testing to find the start addresses for the other three palettes as well:

	0000 0000 0000 0000	Palette 0 CRAM Address
	0000 0000 0010 0000	Palette 1 CRAM Address
	0000 0000 0100 0000	Palette 2 CRAM Address
	0000 0000 0110 0000	Palette 3 CRAM Address

The code and the address get combined in a rather messy way to create a long word that will be sent to the VDP’s control port. It is formatted like this:

c1 c0 aD aC  aB aA a9 a8  a7 a6 a5 a4  a3 a2 a1 a0
 0  0  0  0   0  0  0  0  c5 c4 c3 c2   0  0 aF aE

c0 – c5 are the 6 bits of the code. a0 – aF are the 16 bits of the address. The zeroes are always zero. Scribbling in a notebook and putting all of these bits together results in:

1100 0000 0000 0000 0000 0000 0000 0000 (in binary)
0xC0000000 (in hexadecimal)

Now that the VDP command has been created, another loop can load the palette into CRAM:

; ******************************************************************************
; *********************** Load a Palette into Color RAM ************************
; ******************************************************************************
	move.w #0x8f02, (a6)		; Set autoincrement to 2
	move.l #0xC0000000, (a6)	; Set VDP to write to Color RAM
	lea Palette0, a0		; Load address of palette into a0
	move.l #0x07, d0		; Loop counter to load 8 longwords
PaletteLoop:
	move.l (a0)+, (a5)		; Copy to data port, increment src addr
	dbra d0, PaletteLoop		; decrement d0, loop until d0 = -1

This loop is very similar to the loop above that was used to initialize the VDP registers, but there are a few new things here as well. First is the autoincrement:

	move.w #0x8f02, (a6)			; Set autoincrement to 2

This line of code changes the value in VDP Register 15. When the VDP was initialized, Auto-Increment was turned off. Normally, the VDP reads data 16 bits at a time. Turning autoincrement to 1 would move the address pointer 16 bits forward in CRAM after each color is added. Setting autoincrement to two allows data to be sent in 32-bit chunks instead, and the VDP processes it as two 16-bit writes and then bumps the address pointer 32 bits forward. It ultimately results in less looping and faster execution. (But loading VRAM really fast will have to wait until I cover Direct Memory Access (DMA) in a future article.)

There is also a new instruction in this section:

	lea Palette0, a0	; Load address of Palette0 table into a0

lea stands for Load Effective Address. It’s simply a more efficient way of doing this:

	move.l #Palette0, a0	; Load address of Palette0 table into a0

Now that a palette is loaded, it is wise to assemble the program and test the ROM in Regen. Going to Tools -> VDP Debugger will show that the palette has been loaded if everything is working correctly at this point. It is also possible to test that everything is working by changing the background color. This is done by changing the value in VDP register #7:

	move.w #0x870F, (a6)	; Set background to palette 0, color F

Setting the background color is as simple as setting VDP Register #7 equal to the palette number (0-3) followed by the color number (0-F) from that palette. With the palette I created, this changes the background from black to dark gray. This is the bare minimum of work needed to affect what appears on the screen.

Load Patterns into VRAM

Patterns are the shapes that will be colored by palettes and drawn onto the screen. Each pixel in a pattern is represented by 4-bits designating which color from a palette will be used to color it. This conveniently works out to one hexadecimal character per pixel. That means each pattern is simply an 8×8 grid of hexadecimal values, and each row in a pattern is a Long Word. Here are a few patterns I created in the data section of my .asm file:

CharSpace:
	dc.l 0x00000000
	dc.l 0x00000000
	dc.l 0x00000000
	dc.l 0x00000000
	dc.l 0x00000000
	dc.l 0x00000000
	dc.l 0x00000000
	dc.l 0x00000000
Char0:
	dc.l 0x00000000
	dc.l 0x00099000
	dc.l 0x00900900
	dc.l 0x00909900
	dc.l 0x00990900
	dc.l 0x00900900
	dc.l 0x00099000
	dc.l 0x00000000
Char1:
	dc.l 0x00000000
	dc.l 0x00090000
	dc.l 0x00990000
	dc.l 0x00090000
	dc.l 0x00090000
	dc.l 0x00090000
	dc.l 0x00999000
	dc.l 0x00000000
Char2:
	dc.l 0x00000000
	dc.l 0x00999900
	dc.l 0x09000090
	dc.l 0x00009900
	dc.l 0x00990000
	dc.l 0x09000000
	dc.l 0x09999990
	dc.l 0x00000000

CharA: ;0x41
	dc.l 0x00000000
	dc.l 0x00099000
	dc.l 0x00900900
	dc.l 0x00900900
	dc.l 0x00999900
	dc.l 0x09000090
	dc.l 0x09000090
	dc.l 0x00000000
CharB:
	dc.l 0x00000000
	dc.l 0x09999900
	dc.l 0x09000090
	dc.l 0x09999900
	dc.l 0x09000090
	dc.l 0x09000090
	dc.l 0x09999900
	dc.l 0x00000000

I am only using two colors here: 0 – transparent and 9 – white. Space is pretty simple, all zeroes. Here is a loop that will load them all into VRAM:

; ******************************************************************************
; ************************ Load Patterns into Video RAM ************************
; ******************************************************************************
	move.l #0x40000000, (a6)	; Set VDP to write to VRAM offset 0x0000
	lea CharSpace, a0             	; Load address of CharSpace into a0
	move.l #295, d0			; Loop Counter for 8x37 Longwords 
TileLoop:
	move.l (a0)+, (a5)		; Copy to data port, increment src addr
	dbra d0, TileLoop		; decrement d0, loop until d0 = -1

It’s basically the same as the loop for loading Palettes, but with a different VDP command and loop counter. The loop counter is set to 8×37-1 = 295 because I have 37 characters (space, 0-9, A-Z), and each character is made of 8 Long Words. Subtract 1 because the loop stops at -1.

Once the patterns are loaded, they can be viewed inside of Regen’s VDP debugger. I had to edit, reassmeble, and check these patterns several times before they all looked decent. Smarter people will just copy a font from somewhere.

vdp_debugger

Copy Patterns into a Plane

The pattern tables in the scrolling planes are made of 16-bit values. This is how they are composed:

pri  cp1  cp0  vf  hf  ptA  pt9  pt8  pt7  pt6  pt5  pt4  pt3  pt2  pt1  pt0

pri = priority - can make a tile appear above other graphics.
cp0 - cp1 = color palette - These two bits select a color palette.
vf = vertical flip - Setting this bit flips the pattern vertically.
hf = horizontal flip - Setting this bit flips the pattern horizontally.
pt0 - ptA = pattern number. These 10 bits select a pattern from 0-1023.

I copied some patterns into Scrolling Plane A like this:

; ******************************************************************************
; ******************** The Meat of the Program Begins Here *********************
; ******************************************************************************	
	move.l #0x40000003, (a6)	; Set VDP to write VRAM at 0xC000 (Plane A)
	move.w #0x0000, (a5)		; Low pri, palette 0, no flipping, pattern 0
	move.w #0x0001, (a5)		; ... pattern 1
	move.w #0x0002, (a5)		; ... pattern 2
...
	move.w #0x0024, (a5)		; ... pattern 36
	move.w #0x1000, (a5)		; low pri, palette 0, vert flip, pattern 0
	move.w #0x1001, (a5)		; ... pattern 1

No loops this time, just brute force. The first line is another VDP command. Looking way up at the initialization of VDP Register #2, 0xC000 was designated as the pattern table for Scrolling Plane A, so that is the address that gets mixed with the code to create the VDP command. The remaining lines are copying patterns into Scrolling Plane A using the 16 bits described above.

I manually wrote code to draw all 37 characters, and then drew all 37 characters again with the vertical-flip bit set. This was the result:

alphabet

As you can see, the screen is 40 tiles wide, but there is a gap in the characters before it wraps to the next line. Counting the missing characters, one can conclude that there are 64 tiles per line. Perhaps that is related to this VDP register setting:

	dc.b 0x01 ; 16: Vert. scroll 32, Horiz. scroll 64

Just a guess, don’t quote me on this! Regardless, that means I could move down 1 row of tiles by drawing 64 spaces. I made a loop to skip down four lines and then wrote a message:

	move.l #0xFE, d0		; Loop counter | 256 spaces = 4 lines
SpaceLoop:
	move.w #0x0000, (a5)		; Draw a space
	dbra d0, SpaceLoop		; Decrement d0, loop until d0 = -1
	
	move.w #0x0021, (a5)		; write a mysterious message
	move.w #0x000f, (a5)
	move.w #0x0000, (a5)
	move.w #0x0011, (a5)
	move.w #0x000f, (a5)
	move.w #0x001e, (a5)
	move.w #0x0000, (a5)
	move.w #0x001d, (a5)
	move.w #0x0013, (a5)
	move.w #0x0011, (a5)
	move.w #0x0018, (a5)
	move.w #0x000b, (a5)
	move.w #0x0016, (a5)

What’s Next?

It is horribly bothersome to write a line of code for every single character that appears on the screen, so adding the ability to display strings in arbitrary positions is my next priority. Maybe it doesn’t sound very exciting, but virtually every program will need to do this.

Links

Previous Article

Source Code (test3.asm)

Assembled ROM (test3.bin)

VDP Documentation

Leave a Reply

Your email address will not be published. Required fields are marked *