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): #!/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. Code (Text): #!/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: Code (Text): | 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?
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
Potential issue: Code (Text): 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:
The change turned Gens green. Thank you. So here's the comment I left: Code (Text): | 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...