Sonic and Sega Retro Message Board: How do I turn the Genesis VDP screen green? - Sonic and Sega Retro Message Board

Jump to content

Hey there, Guest!  (Log In · Register) Help
Page 1 of 1
    Locked
    Locked Forum

How do I turn the Genesis VDP screen green? And how to find and fix my dumb newbie mistake?

#1 User is offline PinoBatch 

Posted 05 February 2017 - 02:46 PM

  • Posts: 4
  • Joined: 21-January 17
I have developed several games for Nintendo Entertainment System in 6502 assembly language, and now I'm thinking of getting into the Genesis. But now I'm stumped. I'm trying to write a program that turns the backdrop green to show that at least the palette has loaded. But all I get in DGen or Gens is a black screen. What do I need to change to turn the screen green?

My PC runs Xubuntu 16.04 with Wine installed. This means I can build Linux software from source and run many Windows binaries. I successfully built cross-Binutils based on these instructions:

#!/bin/bash
# based on
# https://daveho.github.io/2012/10/26/m68k-elf-cross-compiler.html
set -e
export BASEDIR=$HOME/develop/crossgcc
export M68KPREFIX=$HOME/.local
export PATH=$M68KPREFIX/bin:$PATH
mkdir -p "$BASEDIR/build/binutils" "$BASEDIR/build/gcc"
echo $M68KPREFIX

cd "$BASEDIR"
wget https://ftp.gnu.org/gnu/binutils/binutils-2.27.tar.gz
gunzip -c binutils-2.27.tar.gz | tar xf -
cd "$BASEDIR/build/binutils"
../../binutils-2.27/configure --target=m68k-unknown-elf --prefix=$M68KPREFIX
make -j2
make install



With that built, here's the shell script I'm using to assemble and link my program. I plan to switch to a makefile once I have the toolchain fully working, but until such time, I continue to keep things simple.

#!/bin/bash
set -e
m68k-unknown-elf-as -m68000 -g init.s -o init.o
m68k-unknown-elf-ld -T md.ld -o hello.elf init.o
m68k-unknown-elf-objcopy -O binary hello.elf hello.gen



The file "md.ld" is the Genny linker file. The program I am trying to assemble is as follows, based on init code from Marc's Realm:

| begin init.s

| This is what I came up with, based on init code from Marc's Realm
| http://darkdust.net/writings/megadrive/initializing

.bss
hsyncs: .skip 4
vsyncs: .skip 4

.text

initial_sp = 0xFFE000

| 68K vectors

  .long initial_sp
  .long reset
  .long crash_handler   | Bus error
  .long crash_handler   | Address error
  .long crash_handler   | Illegal instruction
  .long crash_handler   | Division by zero
  .long crash_handler   | CHK exception
  .long crash_handler   | TRAPV exception
  .long crash_handler   | Privilege violation
  .long crash_handler   | TRACE exception
  .long crash_handler   | Line-A emulator
  .long crash_handler   | Line-F emulator
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Spurious exception
  .long crash_handler   | IRQ level 1
  .long crash_handler   | IRQ level 2
  .long crash_handler   | IRQ level 3
  .long HBlankInterrupt | IRQ level 4 (horizontal retrace interrupt)
  .long crash_handler   | IRQ level 5
  .long VBlankInterrupt | IRQ level 6 (vertical retrace interrupt)
  .long crash_handler   | IRQ level 7
  .long crash_handler   | TRAP #00 exception
  .long crash_handler   | TRAP #01 exception
  .long crash_handler   | TRAP #02 exception
  .long crash_handler   | TRAP #03 exception
  .long crash_handler   | TRAP #04 exception
  .long crash_handler   | TRAP #05 exception
  .long crash_handler   | TRAP #06 exception
  .long crash_handler   | TRAP #07 exception
  .long crash_handler   | TRAP #08 exception
  .long crash_handler   | TRAP #09 exception
  .long crash_handler   | TRAP #10 exception
  .long crash_handler   | TRAP #11 exception
  .long crash_handler   | TRAP #12 exception
  .long crash_handler   | TRAP #13 exception
  .long crash_handler   | TRAP #14 exception
  .long crash_handler   | TRAP #15 exception
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)
  .long crash_handler   | Unused (reserved)

| Internal header

  .ascii "SEGA MEGA DRIVE (C)DY   2017.FEB"
  .ascii "MARCS TEST CODE                                 "
  .ascii "MARCS TEST CODE                                 "
  .ascii "GM 12345678-01"
  .word 0x0000               | checksum
  .ascii "JD              "  | controllers
  .long 0                    | ROM start address
  .long 0x00020000           | ROM end address goes here
  .byte 0x00,0xFF,0x00,0x00  | unknown meaning
  .byte 0x00,0xFF,0xFF,0xFF  | unknown meaning
  .ascii "               "   | modem configuration
  .ascii "                        "    | unused
  .ascii "                         "   | unused
  .ascii "JUE             "  | region

reset:
  | Hard reset detection?
  tst.l 0xa10008
  bne SkipJoyDetect                               
    tst.w 0xa1000c
  SkipJoyDetect:
  bne SkipSetup

  move #0x2700, %sr  | disable interrupts

  | If bits 3-0 of 0xA10001 aren't all zero, write the TMSS init value
  move.b 0xA10001, %d0
  andi.b #0x0F, %d0
  beq skip_tmss
    move.l  #0x53454741, 0xA14000
  skip_tmss:

  | Load common values used to initialize registers
  lea Table, %a5
  movem.w (%a5)+, %d5-%d7  | Load initial values for VDP and RAM setup
  movem.l (%a5)+, %a0-%a4  | Load addresses for Z80 and VDP setup

  | Clear all RAM ($FFFF0000-$FFFFFFFF) by pushing 16384 zeroes
  | Table code set d6 to $3FFF
  clr.l %d0
  movea.l %d0, %a6      | Set A6 to top of RAM
  move %a6, %sp         | Set stack to top of RAM
  clrramloop:
    move.l %d0, -(%a6)    | do { *--x = d0;
    dbra %d6, clrramloop  | } while (d6-- != 0);
  | At this point, D0 = D6 = 0 and A6 = 0xFFFF0000

  | Set up the Z80
  move.w  %d7, (%a1)  | Stop the Z80: 0xA11100 = 0x0100
  move.w  %d7, (%a2)  | Reset the Z80: 0xA11200 = 0x0100
  waitz80avail:
    btst %d0, (%a1)
    bne waitz80avail
  moveq #0x25, %d2    | Program length in bytes - 1
  z80loadloop:
    move.b (%a5)+, (%a0)+
    dbra %d2, z80loadloop
  move.w  %d0, (%a2)  | Reset the Z80: 0xA11200 = 0x0000
  move.w  %d0, (%a1)  | Release stop: 0xA11100 = 0x0000
  move.w  %d7, (%a2)  | Reset the Z80: 0xA11200 = 0x0100

  | Silence the PSG
  moveq #0x03, %d1      | Number of bytes to write to PSG minus 1
  psgclearloop:
    move.b (%a5)+, 0x0011(%a3)  | Pull a byte from table to 0xC00011
    dbra %d1, psgclearloop
  move.w %d0, (%a2)     | Pull the Z80 out of reset

  | Set up the VDP
  moveq #18, %d0        | Number of bytes to write to VDP minus 1
  vdpclearloop:
    move.b (%a5)+, %d5  | Replace bottom half of D5 with byte from table
    move.w %d5, (%a4)   | Write word to VDP
    add.w   %d7, %d5    | Next register
    dbra %d0, vdpclearloop

  | Set up the controllers
  move.b  #0x40, %d0
  move.b  %d0, 0xA10009
  move.b  %d0, 0xA1000B
  move.b  %d0, 0xA1000D

  movem.l (%a6), %d0-%d7/%a0-%a6  | zero all registers other than SP
SkipSetup:
  jmp main

Table:
  .word 0x8000  | D5: VDP value #1
  .word 0x3FFF  | D6: size of RAM to clear in words minus 1
  .word 0x0100  | D7: Z80/VDP value #2
  .long 0x00A00000  | A0: version port?
  .long 0x00A11100  | A1: Z80 BUSREQ
  .long 0x00A11200  | A2: Z80 RESET
  .long 0x00C00000  | A3: VDP data
  .long 0x00C00004  | A4: VDP control

  .word   0xaf01, 0xd91f  | dummy Z80 program
  .word   0x1127, 0x0021
  .word   0x2600, 0xf977
  .word   0xedb0, 0xdde1
  .word   0xfde1, 0xed47
  .word   0xed4f, 0xd1e1
  .word   0xf108, 0xd9c1
  .word   0xd1e1, 0xf1f9
  .word   0xf3ed, 0x5636
  .word   0xe9e9, 0x8104
  .word   0x8f01

  .word   0x9fbf, 0xdfff  | PSG: Silence all four channels
  
  .byte   0x04    | Reg.  0: Enable Hint, HV counter stop
  .byte   0x74    | Reg.  1: Enable display, enable Vint, enable DMA, V28 mode (PAL & NTSC)
  .byte   0x30    | Reg.  2: Plane A is at 0xC000
  .byte   0x40    | Reg.  3: Window is at 0x10000 (disable)
  .byte   0x07    | Reg.  4: Plane B is at 0xE000
  .byte   0x6A    | Reg.  5: Sprite attribute table is at 0xD400
  .byte   0x00    | Reg.  6: always zero
  .byte   0x00    | Reg.  7: Background color: palette 0, color 0
  .byte   0x00    | Reg.  8: always zero
  .byte   0x00    | Reg.  9: always zero
  .byte   0x00    | Reg. 10: Hint timing
  .byte   0x08    | Reg. 11: Enable Eint, full scroll
  .byte   0x81    | Reg. 12: Disable Shadow/Highlight, no interlace, 40 cell mode
  .byte   0x34    | Reg. 13: Hscroll is at 0xD000
  .byte   0x00    | Reg. 14: always zero
  .byte   0x00    | Reg. 15: no autoincrement
  .byte   0x01    | Reg. 16: Scroll 32V and 64H
  .byte   0x00    | Reg. 17: Set window X position/size to 0
  .byte   0x00    | Reg. 18: Set window Y position/size to 0
  .byte   0x00    | Padding

| IRQs just add 1 to a counter

HBlankInterrupt:
  add.l #1, (hsyncs)
  rte

VBlankInterrupt:
  add.l #1, (vsyncs)
  rte

| TODO: Make an actual crash handler once I learn how to display
| something, *anything*
crash_handler:
  rte

/* End of init code ************************************************/

main:
  move.l  #0x00C00004, %a5    | A5 = VDP command
  move.l  #0x00C00000, %a4    | A4 = VDP data
  move.l  #0xC0000000, (%a5)  | Seek to CRAM
  move.w  #0x8F02, (%a5)      | VRAM autoincrement = 2
  moveq   #15, %d0            | number of colors - 1
  lea     initial_palette, %a0
  fillpaletteloop:
    move.w  (%a0)+, (%a4)
    dbra    %d0, fillpaletteloop 

forever:
  jmp forever

| The first is supposed to be a green backdrop, but somehow
| it isn't making it into CRAM.
initial_palette:
  .word 0x00E0, 0x06E6, 0x0AEA, 0x0EEE
  .word 0x000E, 0x000E, 0x000E, 0x000E
  .word 0x000E, 0x000E, 0x000E, 0x000E
  .word 0x000E, 0x000E, 0x000E, 0x000E

| end init.s



I expect the screen to be green, but it's black. I probably made some sort of dumb newbie mistake and missed a step, but being an absolute newbie to all three of 68000, the Genesis VDP, and use of Genesis emulators, I can't even figure out how to single-step through the program. The version of DGen SDL in Debian's and Ubuntu's repository is ancient, appearing to be from before the debugger was added. As for the copy of Gens KMod on the tools page, its CPU "debugger" appears to be just a disassembler, with no visible button that I can click to single-step. On the guess that it's keyboard-driven, I tried opening its .chm file in Wine HTML Help to see which keys I ought to be pressing, but Wine HTML Help shows me only the .chm's About page, with no links to any other page.

What do I need to change in my program to turn the screen from solid black to solid green? Is there another complete yet simple assembly project in a folder that I can assemble to ensure that my Binutils and emulator are working? Or should I first become experienced developing for the Master System and its Z80 and VDP before I even attempt the Genesis?

#2 User is offline MarkeyJester 

Posted 05 February 2017 - 04:06 PM

  • Full of surprises, pull the ribbons for details~
  • Posts: 1761
  • Joined: 22-July 08
  • Gender:Male
  • Location:Japan
  • Wiki edits:16
Write the VDP register first, then write the CRAM write mode:

move.w #0x8F02, (%a5) | VRAM autoincrement = 2
move.l #0xC0000000, (%a5) | Seek to CRAM

#3 User is offline GerbilSoft 

Posted 05 February 2017 - 04:07 PM

  • RickRotate'd.
  • Posts: 2656
  • Joined: 11-January 03
  • Gender:Male
  • Location:USA
  • Project:Gens/GS
  • Wiki edits:5,000 + one spin
Potential issue:
  move.l  #0xC0000000, (%a5)  | Seek to CRAM
  move.w  #0x8F02, (%a5)      | VRAM autoincrement = 2


Doing a register write resets the write destination. (I forget exactly where it goes, but it won't be CRAM write.)

Try moving the VRAM autoincrement command to before the CRAM write command.

EDIT: 2slow. :specialed:
This post has been edited by GerbilSoft: 05 February 2017 - 04:07 PM
Reason for edit: 2slow

#4 User is offline PinoBatch 

Posted 05 February 2017 - 04:28 PM

  • Posts: 4
  • Joined: 21-January 17
The change turned Gens green. Thank you. So here's the comment I left:

  | On the NES, writes to the VRAM/CRAM address clobber the scroll position.
  | Likewise on the Genesis, writes to VDP registers clobber the VRAM/CRAM
  | write address.



Now onto exploring the rest of the VDP...

Page 1 of 1
    Locked
    Locked Forum

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users