How do I turn the Genesis VDP screen green?

Discussion in 'Technical Discussion' started by PinoBatch, Feb 5, 2017.

  1. PinoBatch

    PinoBatch

    Member
    4
    0
    0
    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:

    Code (Text):
    1.  
    2. #!/bin/bash
    3. # based on
    4. # https://daveho.github.io/2012/10/26/m68k-elf-cross-compiler.html
    5. set -e
    6. export BASEDIR=$HOME/develop/crossgcc
    7. export M68KPREFIX=$HOME/.local
    8. export PATH=$M68KPREFIX/bin:$PATH
    9. mkdir -p "$BASEDIR/build/binutils" "$BASEDIR/build/gcc"
    10. echo $M68KPREFIX
    11.  
    12. cd "$BASEDIR"
    13. wget https://ftp.gnu.org/gnu/binutils/binutils-2.27.tar.gz
    14. gunzip -c binutils-2.27.tar.gz | tar xf -
    15. cd "$BASEDIR/build/binutils"
    16. ../../binutils-2.27/configure --target=m68k-unknown-elf --prefix=$M68KPREFIX
    17. make -j2
    18. make install
    19.  
    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.

    Code (Text):
    1.  
    2. #!/bin/bash
    3. set -e
    4. m68k-unknown-elf-as -m68000 -g init.s -o init.o
    5. m68k-unknown-elf-ld -T md.ld -o hello.elf init.o
    6. m68k-unknown-elf-objcopy -O binary hello.elf hello.gen
    7.  
    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:

    Code (Text):
    1.  
    2. | begin init.s
    3.  
    4. | This is what I came up with, based on init code from Marc's Realm
    5. | http://darkdust.net/writings/megadrive/initializing
    6.  
    7. .bss
    8. hsyncs: .skip 4
    9. vsyncs: .skip 4
    10.  
    11. .text
    12.  
    13. initial_sp = 0xFFE000
    14.  
    15. | 68K vectors
    16.  
    17.   .long initial_sp
    18.   .long reset
    19.   .long crash_handler   | Bus error
    20.   .long crash_handler   | Address error
    21.   .long crash_handler   | Illegal instruction
    22.   .long crash_handler   | Division by zero
    23.   .long crash_handler   | CHK exception
    24.   .long crash_handler   | TRAPV exception
    25.   .long crash_handler   | Privilege violation
    26.   .long crash_handler   | TRACE exception
    27.   .long crash_handler   | Line-A emulator
    28.   .long crash_handler   | Line-F emulator
    29.   .long crash_handler   | Unused (reserved)
    30.   .long crash_handler   | Unused (reserved)
    31.   .long crash_handler   | Unused (reserved)
    32.   .long crash_handler   | Unused (reserved)
    33.   .long crash_handler   | Unused (reserved)
    34.   .long crash_handler   | Unused (reserved)
    35.   .long crash_handler   | Unused (reserved)
    36.   .long crash_handler   | Unused (reserved)
    37.   .long crash_handler   | Unused (reserved)
    38.   .long crash_handler   | Unused (reserved)
    39.   .long crash_handler   | Unused (reserved)
    40.   .long crash_handler   | Unused (reserved)
    41.   .long crash_handler   | Spurious exception
    42.   .long crash_handler   | IRQ level 1
    43.   .long crash_handler   | IRQ level 2
    44.   .long crash_handler   | IRQ level 3
    45.   .long HBlankInterrupt | IRQ level 4 (horizontal retrace interrupt)
    46.   .long crash_handler   | IRQ level 5
    47.   .long VBlankInterrupt | IRQ level 6 (vertical retrace interrupt)
    48.   .long crash_handler   | IRQ level 7
    49.   .long crash_handler   | TRAP #00 exception
    50.   .long crash_handler   | TRAP #01 exception
    51.   .long crash_handler   | TRAP #02 exception
    52.   .long crash_handler   | TRAP #03 exception
    53.   .long crash_handler   | TRAP #04 exception
    54.   .long crash_handler   | TRAP #05 exception
    55.   .long crash_handler   | TRAP #06 exception
    56.   .long crash_handler   | TRAP #07 exception
    57.   .long crash_handler   | TRAP #08 exception
    58.   .long crash_handler   | TRAP #09 exception
    59.   .long crash_handler   | TRAP #10 exception
    60.   .long crash_handler   | TRAP #11 exception
    61.   .long crash_handler   | TRAP #12 exception
    62.   .long crash_handler   | TRAP #13 exception
    63.   .long crash_handler   | TRAP #14 exception
    64.   .long crash_handler   | TRAP #15 exception
    65.   .long crash_handler   | Unused (reserved)
    66.   .long crash_handler   | Unused (reserved)
    67.   .long crash_handler   | Unused (reserved)
    68.   .long crash_handler   | Unused (reserved)
    69.   .long crash_handler   | Unused (reserved)
    70.   .long crash_handler   | Unused (reserved)
    71.   .long crash_handler   | Unused (reserved)
    72.   .long crash_handler   | Unused (reserved)
    73.   .long crash_handler   | Unused (reserved)
    74.   .long crash_handler   | Unused (reserved)
    75.   .long crash_handler   | Unused (reserved)
    76.   .long crash_handler   | Unused (reserved)
    77.   .long crash_handler   | Unused (reserved)
    78.   .long crash_handler   | Unused (reserved)
    79.   .long crash_handler   | Unused (reserved)
    80.   .long crash_handler   | Unused (reserved)
    81.  
    82. | Internal header
    83.  
    84.   .ascii "SEGA MEGA DRIVE (C)DY   2017.FEB"
    85.   .ascii "MARCS TEST CODE                                 "
    86.   .ascii "MARCS TEST CODE                                 "
    87.   .ascii "GM 12345678-01"
    88.   .word 0x0000               | checksum
    89.   .ascii "JD              "  | controllers
    90.   .long 0                    | ROM start address
    91.   .long 0x00020000           | ROM end address goes here
    92.   .byte 0x00,0xFF,0x00,0x00  | unknown meaning
    93.   .byte 0x00,0xFF,0xFF,0xFF  | unknown meaning
    94.   .ascii "               "   | modem configuration
    95.   .ascii "                        "    | unused
    96.   .ascii "                         "   | unused
    97.   .ascii "JUE             "  | region
    98.  
    99. reset:
    100.   | Hard reset detection?
    101.   tst.l 0xa10008
    102.   bne SkipJoyDetect                              
    103.     tst.w 0xa1000c
    104.   SkipJoyDetect:
    105.   bne SkipSetup
    106.  
    107.   move #0x2700, %sr  | disable interrupts
    108.  
    109.   | If bits 3-0 of 0xA10001 aren't all zero, write the TMSS init value
    110.   move.b 0xA10001, %d0
    111.   andi.b #0x0F, %d0
    112.   beq skip_tmss
    113.     move.l  #0x53454741, 0xA14000
    114.   skip_tmss:
    115.  
    116.   | Load common values used to initialize registers
    117.   lea Table, %a5
    118.   movem.w (%a5)+, %d5-%d7  | Load initial values for VDP and RAM setup
    119.   movem.l (%a5)+, %a0-%a4  | Load addresses for Z80 and VDP setup
    120.  
    121.   | Clear all RAM ($FFFF0000-$FFFFFFFF) by pushing 16384 zeroes
    122.   | Table code set d6 to $3FFF
    123.   clr.l %d0
    124.   movea.l %d0, %a6      | Set A6 to top of RAM
    125.   move %a6, %sp         | Set stack to top of RAM
    126.   clrramloop:
    127.     move.l %d0, -(%a6)    | do { *--x = d0;
    128.     dbra %d6, clrramloop  | } while (d6-- != 0);
    129.   | At this point, D0 = D6 = 0 and A6 = 0xFFFF0000
    130.  
    131.   | Set up the Z80
    132.   move.w  %d7, (%a1)  | Stop the Z80: 0xA11100 = 0x0100
    133.   move.w  %d7, (%a2)  | Reset the Z80: 0xA11200 = 0x0100
    134.   waitz80avail:
    135.     btst %d0, (%a1)
    136.     bne waitz80avail
    137.   moveq #0x25, %d2    | Program length in bytes - 1
    138.   z80loadloop:
    139.     move.b (%a5)+, (%a0)+
    140.     dbra %d2, z80loadloop
    141.   move.w  %d0, (%a2)  | Reset the Z80: 0xA11200 = 0x0000
    142.   move.w  %d0, (%a1)  | Release stop: 0xA11100 = 0x0000
    143.   move.w  %d7, (%a2)  | Reset the Z80: 0xA11200 = 0x0100
    144.  
    145.   | Silence the PSG
    146.   moveq #0x03, %d1      | Number of bytes to write to PSG minus 1
    147.   psgclearloop:
    148.     move.b (%a5)+, 0x0011(%a3)  | Pull a byte from table to 0xC00011
    149.     dbra %d1, psgclearloop
    150.   move.w %d0, (%a2)     | Pull the Z80 out of reset
    151.  
    152.   | Set up the VDP
    153.   moveq #18, %d0        | Number of bytes to write to VDP minus 1
    154.   vdpclearloop:
    155.     move.b (%a5)+, %d5  | Replace bottom half of D5 with byte from table
    156.     move.w %d5, (%a4)   | Write word to VDP
    157.     add.w   %d7, %d5    | Next register
    158.     dbra %d0, vdpclearloop
    159.  
    160.   | Set up the controllers
    161.   move.b  #0x40, %d0
    162.   move.b  %d0, 0xA10009
    163.   move.b  %d0, 0xA1000B
    164.   move.b  %d0, 0xA1000D
    165.  
    166.   movem.l (%a6), %d0-%d7/%a0-%a6  | zero all registers other than SP
    167. SkipSetup:
    168.   jmp main
    169.  
    170. Table:
    171.   .word 0x8000  | D5: VDP value #1
    172.   .word 0x3FFF  | D6: size of RAM to clear in words minus 1
    173.   .word 0x0100  | D7: Z80/VDP value #2
    174.   .long 0x00A00000  | A0: version port?
    175.   .long 0x00A11100  | A1: Z80 BUSREQ
    176.   .long 0x00A11200  | A2: Z80 RESET
    177.   .long 0x00C00000  | A3: VDP data
    178.   .long 0x00C00004  | A4: VDP control
    179.  
    180.   .word   0xaf01, 0xd91f  | dummy Z80 program
    181.   .word   0x1127, 0x0021
    182.   .word   0x2600, 0xf977
    183.   .word   0xedb0, 0xdde1
    184.   .word   0xfde1, 0xed47
    185.   .word   0xed4f, 0xd1e1
    186.   .word   0xf108, 0xd9c1
    187.   .word   0xd1e1, 0xf1f9
    188.   .word   0xf3ed, 0x5636
    189.   .word   0xe9e9, 0x8104
    190.   .word   0x8f01
    191.  
    192.   .word   0x9fbf, 0xdfff  | PSG: Silence all four channels
    193.  
    194.   .byte   0x04    | Reg.  0: Enable Hint, HV counter stop
    195.   .byte   0x74    | Reg.  1: Enable display, enable Vint, enable DMA, V28 mode (PAL & NTSC)
    196.   .byte   0x30    | Reg.  2: Plane A is at 0xC000
    197.   .byte   0x40    | Reg.  3: Window is at 0x10000 (disable)
    198.   .byte   0x07    | Reg.  4: Plane B is at 0xE000
    199.   .byte   0x6A    | Reg.  5: Sprite attribute table is at 0xD400
    200.   .byte   0x00    | Reg.  6: always zero
    201.   .byte   0x00    | Reg.  7: Background color: palette 0, color 0
    202.   .byte   0x00    | Reg.  8: always zero
    203.   .byte   0x00    | Reg.  9: always zero
    204.   .byte   0x00    | Reg. 10: Hint timing
    205.   .byte   0x08    | Reg. 11: Enable Eint, full scroll
    206.   .byte   0x81    | Reg. 12: Disable Shadow/Highlight, no interlace, 40 cell mode
    207.   .byte   0x34    | Reg. 13: Hscroll is at 0xD000
    208.   .byte   0x00    | Reg. 14: always zero
    209.   .byte   0x00    | Reg. 15: no autoincrement
    210.   .byte   0x01    | Reg. 16: Scroll 32V and 64H
    211.   .byte   0x00    | Reg. 17: Set window X position/size to 0
    212.   .byte   0x00    | Reg. 18: Set window Y position/size to 0
    213.   .byte   0x00    | Padding
    214.  
    215. | IRQs just add 1 to a counter
    216.  
    217. HBlankInterrupt:
    218.   add.l #1, (hsyncs)
    219.   rte
    220.  
    221. VBlankInterrupt:
    222.   add.l #1, (vsyncs)
    223.   rte
    224.  
    225. | TODO: Make an actual crash handler once I learn how to display
    226. | something, *anything*
    227. crash_handler:
    228.   rte
    229.  
    230. /* End of init code ************************************************/
    231.  
    232. main:
    233.   move.l  #0x00C00004, %a5    | A5 = VDP command
    234.   move.l  #0x00C00000, %a4    | A4 = VDP data
    235.   move.l  #0xC0000000, (%a5)  | Seek to CRAM
    236.   move.w  #0x8F02, (%a5)      | VRAM autoincrement = 2
    237.   moveq   #15, %d0            | number of colors - 1
    238.   lea     initial_palette, %a0
    239.   fillpaletteloop:
    240.     move.w  (%a0)+, (%a4)
    241.     dbra    %d0, fillpaletteloop
    242.  
    243. forever:
    244.   jmp forever
    245.  
    246. | The first is supposed to be a green backdrop, but somehow
    247. | it isn't making it into CRAM.
    248. initial_palette:
    249.   .word 0x00E0, 0x06E6, 0x0AEA, 0x0EEE
    250.   .word 0x000E, 0x000E, 0x000E, 0x000E
    251.   .word 0x000E, 0x000E, 0x000E, 0x000E
    252.   .word 0x000E, 0x000E, 0x000E, 0x000E
    253.  
    254. | end init.s
    255.  
    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. MarkeyJester

    MarkeyJester

    ♡ ! Resident Jester
    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. GerbilSoft

    GerbilSoft

    RickRotate'd. Administrator
    2,890
    23
    18
    USA
    rom-properties
    Potential issue:
    Code (Text):
    1.  
    2.   move.l  #0xC0000000, (%a5)  | Seek to CRAM
    3.   move.w  #0x8F02, (%a5)      | VRAM autoincrement = 2
    4.  
    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:
     
  4. PinoBatch

    PinoBatch

    Member
    4
    0
    0
    The change turned Gens green. Thank you. So here's the comment I left:

    Code (Text):
    1.  
    2.   | On the NES, writes to the VRAM/CRAM address clobber the scroll position.
    3.   | Likewise on the Genesis, writes to VDP registers clobber the VRAM/CRAM
    4.   | write address.
    5.  
    Now onto exploring the rest of the VDP...