Robotronic adventures

38 posts in this topic

A while back Vlait made a mention of a Robotron game in this post so I decided to grab the source code mentioned and try and bring it up on my Pipistrello board because of the memory requirements of the game currently 49 RAMB.
I encourage you to read the wiki of the original author, Jared Boone, but as a quick recap, his implementation uses a real 6809 CPU connected to the FPGA board. Because the real Robotron uses in fact two 6809 CPUs, one for the sound and one for the game, and Jared could only connect just one physical CPU to his FPGA board, he has a split project where he can test either the game or the sound but can't have a complete game with sound running all at the same time.
As Vlait said, there is now an Opencores implementation of a 6809 so it should be possible to do away with the physical 6809 CPU and implement the entire game on one FPGA, complete with sound. I grabbed the Opencores 6809 and and quickly made a top level module connecting all the components together, should be a piece of cake bringing this to life, right? Well... no, actually.
Running the game ROMs in MAME, I can see that the expected behaviour at power on for the game is to fill the screen with what I call rainbow noise, like this post-29560-0-83818100-1380114519.png
This is a side effect of the memory test filling the entire RAM including the video buffer with pseudo-random data, but my board produced... a black screen. On to the simulator we go, fire up Isim and start a simulation, while in parallel run MAME in debug mode to bring up the game disassembly and single step and compare the two. Immediately apparent was that the soft CPU failed to fetch the reset vector and was executing garbage. This turned out to be due to slight timing differences between the real CPU and the soft CPU so I had to delay the latching of the CPU signals by a few ns. As soon as I did that I got the rainbow noise just like the real game which was promising but immediately after that, the screen would turn black and remain so indefinitely, whereas the real game would post a message "Initial tests indicates: operational" and proceed to the attract screen. What could be wrong, I wondered?
Back to Isim. It turns out that the memory test takes a significant amount of time, about 5 seconds in real time, and since I know that works fine given the screen output and that the problem occurs after that, there is no point trying to simulate 5 seconds of real time which would take a very long time to simulate, and probably fail because Isim can only store so much signal data in its database before it throws up its hands and stops due to it having reached its limits.
The easy way around this is to make the game skip the memory test and just go straight after it. This can be accomplished by patching the game ROMs in the right place. A (not so) quick session inside the MAME debugger reveals some interesting locations:
F472 JMP $FD65   (7E FD 65) Jump to mem test area, fall through to next instruction to skipF479 JMP $FF3F   (7E FF 3F) Jump to checksum area, fall through to next instruction to skipF47C start of game

So the easiest thing to do turned out to be to patch memory locations F46B through F47B inclusive, with no-op type instructions such as LDX #$0000 (three byte) or LDY #$0000 (four byte). After this, the game happily skipped through the time consuming parts to quickly move to the problem area at about 70ms after reset. The simulator shows that the CPU address freezes and this is of course due to the halt signal being asserted.




Zooming in shows that the last instruction fetched before the freeze are at address 6007. What we really have there is:
ROM:6004                 sta     word_CA00  ; start blitterROM:6007                 puls    y,cc

So the last instruction actually executed was that at 6004, storing the A register at address CA00, which according to MAME source code from williams.c is the "start blitter" address. So basically, as soon as the blitter is accessed, it halts the CPU to gain the bus, but for some reason the CPU remains halted indefinitely. Why would that be I wonder?


Well it turns out that the real CPU is meant to acknowledge certain events such as IRQ, fast IRQ, Sync, Reset and Halt as well, by posting certain levels on output pins BA ("bus available") and BS (bullsh... err, I mean "bus status"). But the Opencores 6809 does not implement those signals at all, so there's the problem.


Taking a glance through the source it seems the best place to add the missing signals is to hook them to the state machine like so:

  change_state : process(clk)  begin    if rising_edge(clk) then      if rst = '1' then        state <= reset_state;      else        if hold = '0' then          state <= next_state;          case next_state is            -- halt ack            when halt_state =>              ba <= '1'; bs <= '1';            -- sync ack            when sync_state =>              ba <= '1'; bs <= '0';            -- irq/rst ack            when reset_state | int_mask_state | vect_hi_state =>              ba <= '0'; bs <= '1';            -- normal            when others =>              ba <= '0'; bs <= '0';          end case;        end if;      end if;    end if;  end process;

The part between case ... end case statement was what was added to the existing code. Pretty easy fix really. The proof was of course when synthesizing this and running it on the FPGA I now get past the memory test rainbow and into the game attract screen. At a glance it mostly looks correct though I can make out some small issues, such as some graphics are a little corrupt and some sprites are missing. Here's a screen shot of the actual FPGA running the game showing one of the faulty graphics (the tall column of lines bottom right)




Enough debugging for today though, it's 1:30am, yawn... next I'll need to add the sound CPU to the project, not sure how it connects to the rest, will have to consult the schematics.

Share this post

Link to post
Share on other sites

Great write-up - I see I will need to buy a second arcade joystick from Sparkfun!

Share this post

Link to post
Share on other sites

So it turns out I was wrong when I said above that the Robotron uses two 6809 CPUs and that this is the reason why Jared split the project into two separate projects. This was an assumption on my part. On closer inspection, the game uses a 6809 CPU for which Jared chose to use a real CPU and the audio board uses a 6808 for which he used a soft core. I guess he's still working on this project and split it in two so he can throubleshoot each section individually.

Share this post

Link to post
Share on other sites

Would completing this also unlock the other Williams games of the era, which look to be on more or less the same hardware, control schemes aside?  Defender, Joust, Sinistar all start up with that rainbow visual noise memory test and pinball-esque sound effects suggesting they're reusing routines from each other, so I'd be surprised if the hardware platforms are much different.

Share this post

Link to post
Share on other sites

You rock Alex!!

The platform is *very* picky about timing because the cpu essentially controls everything and

it's no small feat getting something even closely resembling the original picture on the screen.

RorscachUK: yes, almost all of them should - Joust, Robotron and Bubbles are more or less the same hardware.

Splat is a bit of a mystery to me, never seen a board or looked at mame source but it should be *almost* the same as the above.

Stargate and Defender are older hardware revisions, both should actually be easier to get in working state since the blitting

is a little less complicated.

Sinistar board is a bit more advanced than the previous ones + it has the speech hardware which requires lots more ram for samples unless the chip is modeled (no idea if that has been done as an open source project)

Share this post

Link to post
Share on other sites

Haha you're very kind, but my contribution is small, I'm just porting Jared's work to a new platform as well as using a ready made core to replace the CPU so it all fits in the FPGA. Hopefully if I have enough time I'll be able to integrate the sound in and get a few other games running on the Williams hardware. The real fun for me is not having everything work first time. Debugging and troubleshooting the digital logic, writing test benches and running simulations is quite cool IMO.

Share this post

Link to post
Share on other sites

Well two weeks have passed, so what's been happening. Mostly I struggle to find enough free time to play with this :)


One thing I did was rig up the sound section as a standalone project to a top level module that allowed me to use a PS2 keyboard to send custom values to the input of the sound module. Every time a coin is inserted into the Robotron game, it makes a different sound out of several choices, seemingly picked at random. This is great because it would let me test different sounds.


Using MAME in debug mode I set a breakpoint in the sound CPU's IRQ handler so that every time the game made a sound, I could read the value that triggered it. This is done in MAME by starting the game in debug mode then hit the "run to next CPU" key F6. At this point a little disassembly and tracing though the sound CPU reveals it mostly sits idle in an endless loop. When a value is written to its PIA chip an interrupt is generated causing the CPU to spring to life and make that sound. The IRQ vector for the 6800 family is at address $FFF8 and in this case it points to address $FB11 so this is where we set our breakpoint. This is what the debug session looks like:




The line in red is our breakpoint and it just initializes the stack, then the next line reads memory address $402, which happens to be the PIA port that connects to the main game CPU board. In this particular case the value it read was $F3 as can be seen in the register section. By obtaining this and other values in this way and sending them to the FPGA through the keyboard I was able to compare that the sound generated by MAME matched in fact the sound I was getting from the FPGA. This was very encouraging as this was not just a simulation, but actually running on the FPGA making real sounds.


I thought this would now be a breeze to get going, so I hooked up the sound section to the rest of the game. As per the schematic, the game's ROM PIA port B low 6 bits plus the PA7 line go to the sound section's PIA port B. There is a little trickiness there because the PIA port pins have external pullups, so when the port pins are set to be inputs, the resistors pull the lines high. This has to be accounted for by also considering the PIA's data direction register. Anyway, long story short, hooked everything up and of course it didn't work, no sounds came out at all.


At this point I got majorly sidetracked as I realized I never really attempted to start and play a single game. I didn't expect much since as I said in my original post, I could see some of the graphics looked a little corrupt but I thought I'd just see what happens. So I hit the coin insert button and nothing happened :) this is getting to be a theme here!


Looking through the code and hooking the game button inputs to some debug LEDs showed some of the buttons were being doubly inverted ending up with the wrong polarity. After fixing these and firing up the game on the FPGA again, this time the screen was not progressing to the game attract (demo) screen but would get stuck at an ominous looking message.




Pressing the advance button would then go past this to the familiar attract screen, however pushing the coin buttons still would not increment the credit so I could not start a game. It turns out that when the buttons were doubly inverted this had the effect of the advance button being constantly pressed causing the game to quickly skip past this CMOS error into the attract screen so I never got to see it, but now that the buttons were fixed, I could clearly see there was a problem.


Entering the game self test mode via the advance button would generate a CMOS test failure, but the section that tested the screen RAM, color palette RAM would pass, the buttons all passed and showed to have the correct mapping, then at the end of the tests, on the last screen titled "Game Adjustment" that should normally show this:




It instead showed all the values as zero. This immediately made sense to me. Since the price selection coin slot units were zero (because of the CMOS failure), every time I pressed the coin insert button it would add zero credits and without credit, I couldn't start a game.


I looked into why the CMOS was failing and I quickly realized that it wasn't actually implemented in the game as a separate RAM. The CMOS is supposedly a 4 bit wide RAM placed at address $CC00-$CFFF but Jared chose to implement this as a full 2KB RAM from $C000-$D000. When I added the game RAMS I looked at the memory map and assumed that the CMOS region was outside the scope of the main RAM so I didn't bother adding another FPGA BRAM unit there. After adding the extra RAM and running the game diagnostics again the CMOS test now passed the test however on every power up it would pop up this message and wait for the advance button to be pressed




That seemed like it would quickly become annoying, so I fixed it by adding initialization values to that section of the BRAM, similar to how we initialize the game ROMs, with values I obtained from dumping that section of memory from MAME with the command "save CMOS.BIN,CC00,400"


The game now boots to the attract screen on power on without complains. Finally, inserting a coin increments the credit so I go ahead and start a game... aaand crash! The game screen would start to be built in the typical Robotron fashion where graphics fly as line segments across the screen and assemble into the game objects, but in the middle of doing that, the game would completely freeze requiring a reset only to do the same thing every time I attempted to start a new game. This was peculiar because the game would sit happily at the attract screen going thorough all the demo screens just fine for as long as I cared to leave it running, I even had it run for over an hour while debugging other code, and it would run happily. It only froze when a new game was started. This is what the freeze looks like:




At this point it's back to the simulator to track down the issue. My gut feel tells me it's a blitter issue, or a subtle timing issue or both. Remember, Jared used a real 6809 CPU whereas this game now runs a softcore 6809.


Share this post

Link to post
Share on other sites

Great progress!  I'd wondered if the first power-on CMOS settings would be a problem, in MAME the first time you start a Williams game it makes you go through all that factory settings press advance nonsense, but saves it into some sort of non-volatile CMOS memory and boots properly afterwards.  Grabbing a snapshot of already-setup values sounds easier than reimplementing saving to CMOS with FLASH or EEPROM or something.  Oh and just in case any observers are thinking that bottom screenshot shows horrible corrupted graphics, that's just what Robotron does normally when a level starts! 

Share this post

Link to post
Share on other sites
In order to troubleshoot the game freezing issue I need to simulate the problem so again the challenge here is to quickly make the game skip to the problem area so I don't have to spend endless time simulating things that work just fine. In my first post I showed one way to patch the game to skip the lengthy initialization, but a little more time with the debugger reveals a slightly better patch. The game initialization section seems to be fairly separate from the rest of the game and when it finishes it jumps to the game start section at address $D106. All I had to do is change the reset vector to just point to $D106 and the game happily just starts into the attract screen skipping over several seconds of RAM and other hardware tests.
But this is still not far enough into the game, for example, the picture below shows a side by side comparison of a good video frame (left) from MAME and a corrupt screen from the FPGA (right).
However this only occurs about half a minute after power on. I need this screen to come up in under two seconds or even sooner. The game always cycles through the same demo screens in the same order, so tracing through the program reveals that the way that works is by chaining several discrete demos. Once the first one finishes the address of the next demo is loaded and so on. The relevant disassembly is here:
D178: 97 51            STA   $51D17A: BD D0 54         JSR   $D054D17D: 77 A0D17F: 86 2C            LDA   #$2CD181: B7 C8 0E         STA   $C80ED184: 03 59            COM   $59D186: 1C 00            ANDCC #$00D188: 20 0C            BRA   $D196

The game reaches $D17A and "jumps to a subroutine". Anyone who knows even a little assembly language expects that the code will return from the subroutine and continue execution at $D17D however you'd be wrong. The Robotron code is full of these little nasty surprises. The subroutine jumped to goes and does some magic like pulling the return address ($D17D) from the stack, retrieving the data there ($77A0) into register X then adjusting the return address on the stack to point to $D17F before returning.

It turns out that $77A0 is used as a jump address to the first demo screen. By continuing to run the game code with suitable breakpoints I finally find out that the demo screen I want has an address of $77EF so by patching location $D17D to contain $77EF and in conjunction with the other reset vector patch to $D106 the game now powers on and almost immediately goes to the problem area. The game is now suitably patched for simulation.
When doing simulation troubleshooting, the waveform view in the simulator is great for zooming into and focusing on a spot once I know where the problem is, but when I zoom out to get a global view, everything just looks the same, so I need to use other tricks here. One such trick is to output data from simulation into one or more files.
Since I spend a lot of time bringing old arcade games to life, one of the more useful modules I built a long time ago takes a video signal input (RGB values and H/V sync) and writes that out into a PBM (portable bitmap) file. As the simulation runs, a new sequentially numbered file is created for every complete frame of video allowing me to easily view the files and with a flick of the scroll wheel, quickly scroll through them if I need to see an impromptu animation of the video frames.
Additional to that, I log other interesting data to another log file. Such data can be register contents, signal values, timestamps, or anything of interest. The simulation can then run while I do something else and I end up with a nice log of all the data I need, allowing me to quickly find points of interest in the simulation that I can zoom into.
Before I focus specifically on why the game just freezes at the start of a level, I wanted to find out why the blitter produces corrupt graphics. I have a feeling these are related issues and even if they aren't related, the blitter issue still has to be resolved anyway.
The thing that is puzzling me is that the blitter is pretty much used to build everything on the screen, from drawing text, to animating those round multicolored Williams logos (the solid circles with W inside them) that rotate around the outside border of the screen, to also building the large Robotron logo in the center of the screen. So why would the blitter work for some things and not other?
As a quick intro to the blitter, it's a special chip with 8 registers mapped to addresses $CA00-$CA07, that is programmed by the CPU to do certain things, mainly move data around the memory like a sort of DMA. The registers are typically programmed with a source and destination address, the length of data as a width and height, the type of operation to perform and some attributes. When the CPU writes to the address $CA00 the blitter is activated and asserts HALT. This causes the CPU to freeze so the blitter can take over the data/address bus to perform its operations, then when it is finished, it releases the CPU from HALT and the blitter goes back to idle while the CPU takes charge of the buses again.
So clearly I need to know what the blitter is doing at all times, so this is one thing I need to log to a file as well as the video frames I mentioned before. I decided to log the data as a blitter register dump preceded by the CPU program counter so I know what address the code was executing at when the blitter was activated. The trigger condition is when the CPU acknowledges the HALT, as at that point the blitter registers have been fully programmed and it's about to start doing its thing. When running the simulation, the blitter log fills up with this sort of stuff:
8DFE [92, EE, 05, FF, 7D, 3D, 05, 00]8DFE [92, EE, 05, FF, 7D, 4F, 05, 00]... hundreds more lines ...F233 [12, 00, 00, 48, 18, 48, 0F, 05]F163 [0E, 00, 9B, 54, 18, 48, 0F, 05]F16E [0E, 00, 9B, 5F, 18, 57, 0F, 05]F179 [0E, 00, 9B, 6A, 18, 66, 0F, 05]... hundreds more lines ...

Once enough data is gathered from the simulation, I can compare it with data from the game running in MAME, by settign suitable breakpoints. For example

Start a fresh game and type "g 5e2a" to run the game until that address. Once that address is reached place a watch point "wp ca00,1,w" and hit F5 to run the game. Note in the window above the debugger I have a small memory window pointing to the blitter state RAM, which shows me the blitter registers. It is a direct comparison with the blitter registers in my simulation log file above and the address where the watchpoint was hit corresponds to the program counter as logged in the simulation log too. By using a slightly different go address in my MAME debugger I was able to compare the earlier blitter accesses from addresses $8DFE and they were all a match so they are not interesting here, but as soon as code starts executing at addresses $F1xx through $F2XX I can see that the second blitter register is always zero whereas in MAME it is set to $0E
I though I had this problem nailed, so I quickly investigated why this is happening. The disassembly below shows the cause:
F15B: A7 A4            STA   ,YF15D: BF CA 02         STX   $CA02F160: FF CA 00         STU   $CA00F163: 3A               ABX   

Unlike other places in the earlier code where the blitter is loaded one register at a time, the code here writes the 16 bit U register to address $CA00. This is an 8 bit CPU so each write is broken up into two writes, first the top half of U is written to address $CA00 then the bottom half of U is written to address $CA01

The way the blitter was implemented in VHDL, it only accepts register writes when it is in the idle state, so as soon as a write to address $CA00 is detected it advances the state from idle and onwards towards halting the CPU. The next write at address $CA01 is no longer accepted by the blitter because it is not in the idle state and is ignored, so that register is never written to whenever that style 6800 assembly coding is used. After going and changing the VHDL code of the blitter to separate the state machine from the register access, so the blitter register can always be written to no matter what state it is in, the simulation log now shows:
8DFE [92, EE, 05, FF, 7D, 3D, 05, 00]8DFE [92, EE, 05, FF, 7D, 4F, 05, 00]... hundreds more lines ...F233 [12, 00, 00, 48, 18, 48, 0F, 05]F163 [0E, 0E, 9B, 54, 18, 48, 0F, 05]F16E [0E, 0E, 9B, 5F, 18, 57, 0F, 05]F179 [0E, 0E, 9B, 6A, 18, 66, 0F, 05]... hundreds more lines ...

These register values now agree 100% with what I observed in MAME debugger. Feeling confident, I run the new code on the FPGA and... you guessed it, it made absolutely no difference. If you didn't guess it, you must not have read my previous posts, failure it seems is the usual outcome :) The problem demo screen still has the problem, so I spend a fair amount of time troubleshooting and finding an issue that turned out to not have a bearing on the actual observed problem!

By continuing to compare the blitter register from simulation with those from MAME only about 20 lines later, I come across another difference, this time it seems to be in the blitter register 5 which is part of the destination address. Bingo! This looks important.
The highlighted line in the text editor behind the debugger doesn't match with the register contents as displayed in the MAME memory window at the top. After zooming into the relevant point in the simulation waveform, it turns out that this specific issue happens when the CPU is halted in the middle of an ADDA (add a value to Accumulator) instruction. The CPU enters the HALT state with the register A set to a specific value, then when it exits the HALT state, the A register changes value just before the ADDA instruction is executed. This causes A to have an incorrect value the next time it is used to write to the blitter register, hence the incorrect blitter behavior.
This is clearly a problem with the softcore 6800 so I went back to basics and created a testbench with just the 6800 core and a tiny memory space of 64 bytes. This way I could test what is going on isolating the 6800 and having complete control over it. This is the memory I used in the test bench mapped to the top of the memory space, it sets up the required vectors so the CPU comes out of reset and jumps to the code that initializes the CPU registers with some known values.
type memory_t is array(0 to 63) of std_logic_vector(7 downto 0);signal ram : memory_t :=(-- FFC0: load regs with some known valuesx"10", x"CE", x"BF", x"6A", -- LDS #$BF6Ax"10", x"8E", x"98", x"18", -- LDY #$9918x"8E", x"CA", x"00",        -- LDX #$CA00x"CC", x"48", x"12",        -- LDD #$4812x"CE", x"CA", x"05",        -- LDU #$CA05x"1C", x"00",               -- ANDCC #$00x"1A", x"B0",               -- ORCC  #$B0-- FFD5: test sectionx"A7", x"C4",               -- STA   ,Ux"E7", x"84",               -- STB   ,Xx"8B", x"15",               -- ADDA  #$15-- x"CB", x"13",               -- ADDB  #$13-- x"C3", x"12", x"34"         -- ADDD  #$1234x"4C",                      -- INCA-- FFDC: fillerx"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",x"4C",-- FFEE: loop foreverx"20", x"FE",               -- BRA   .-- FFF0: vectorsx"FF", x"EE",               -- RSVD vectorx"FF", x"EE",               -- SWI3 vectorx"FF", x"EE",               -- SWI2 vectorx"FF", x"EE",               -- FIRQ vectorx"FF", x"EE",               -- IRQ  vectorx"FF", x"EE",               -- SWI  vectorx"FF", x"EE",               -- NMI  vectorx"FF", x"C0"                -- RST  vector);
This shows a simulation of the above code without halting the CPU.
Below is the exact same simulation but HALT is asserted for several clock cycles at a certain point and the problem can be clearly seen. Whereas in the first simulation, the A register (highlighted) contains $48 and then has $15 added to end up with the value $5D, in the bottom simulation we see that while A starts with the same initial value, at the end of the HALT cycle it is loaded (overwritten) with $CC then the value $15 is added to end up with incorrect result of $E1

The attached pictures are only a small view into the CPU internal workings and the cause of this error is not visible in these pictures, but further investigation shows that the A register is overwritten because acca_ctrl, the state that controls operation on A, transitions into the wrong state at the end of a HALT cycle. Instead of remaining in latch_acca which maintains the A register contents it transitions into load_acca causing the A register to load the value output by the ALU, $CC in this case.


So what controls what state acca_ctrl is in? Well the current op_code determines that state and as you can see in the top simulation, the op_code at the start of address $FFD9 (where the ADDA instruction is stored) is $E7 but in the bottom simulation, the op_code at the same address (after halt) is now $8B


So the fix turned out to be fairly simple, the CPU must not be allowed to fetch op codes while either in HALT or when about to enter HALT. This is accomplished by wrapping "op_code <= data_in;" in an if statement as per below:

  op_reg : process(clk)  begin    if rising_edge(clk) then      if hold = '0' then        case op_ctrl is          when reset_op =>            op_code <= "00010010";          when fetch_op =>            -- fetching opcodes while entering halt state causes register corruption            if (state /= halt_state) and (next_state /= halt_state) then              op_code <= data_in;            end if;          when others =>   -- when latch_op =>            null;        end case;      end if;    end if;  end process;

Simulation now shows the problem is fixed so the next step is to test this on actual hardware and for a change, the outcome this time is no longer failure but sweet, sweet success.


The Robotron logo now builds perfectly and more importantly, the freeze at the start of a game level is now completely gone. The game seems to me to work perfectly but with no sound (still working on that).


Speaking of sound, I also loaded the Joust ROMs and miraculously the sound works fine in Joust, even though it doesn't work at all in Robotron and the only change is the ROMs. Here is a very quick test of a few game ROMs

Game status:Bubbles  : init screen fine, but corrupt gfx, hangs after (check ROMs?)Defender : worksJoust    : works during game + sound, hangs at a certain point in attract screenRobotron : works, no soundSinistar : does not go past initial mem test screenStargate : works


Share this post

Link to post
Share on other sites tedious as it sounds - you rock Alex :)

not going to repeat the previous rant but by now even you would have to agree getting this to run

on a non-commercial softcore is *a lot* of work...

-Defender should not work since it the ROM is paged different from the others (crashes with the latest published source as expected)

-Joust crashes ingame too (had time to play until the end of 3rd level, looked like a paging problem), also is a lot happier with a clean sram image.

-robotron does have the occasional graphic glitch - could not tell if because of blitter or cpu, would blame cpu for the moment.

wish i could actually contribute something useful...



Share this post

Link to post
Share on other sites

Thanks, as I said I do enjoy the debugging process. it's like solving a puzzle. I'm still working on the sound which is such a pain because it looks like it should be so easy to get going but it just refuses to work.

In the meantime here are some interesting and relevant links that make a nice read on a rainy afternoon:

Share this post

Link to post
Share on other sites

Continuing the debugging into why the sound doesn't work, I took a look at the 6821 PIA since I feel that it is the key to this issue. The main board PIA interfaces to the sound board PIA to activate each sound. One thing I noticed a while ago is that in this VHDL implementation of Robotron, the sound section uses pia6821.vhd which is written by John Kent but the main board ROM and Widget PIAs use mc6821.vhd which according to the credits is written by Jared.

I could not explain why this project uses two different implementations of the PIA and without asking Jared, I'd only be guessing. I took a look at the implementation of both and it seemed to me that Jared's implementation was less complete that John Kent's. This is based on a number of things such as TODO comments, some left over code that has no functional effect (first process titled "Effects of register reads") and a bug that I came across while analyzing the code in mc6821.vhd.
This bug is related to the clearing of the interrupt lines. According to the 6821 datasheet, there are two interrupt pins on the chip, IRQA and IRQB. The chip can be configured to enable interrupts based on certain inputs and triggers on its other pins. Once an interrupt pin is activated, the CPU must clear it by reading the relevant port's output register. So IRQA pin is cleared by reading output register A at address "00" and IRQB pin is cleared by reading output register B at address "01". These are binary addresses as the chip has only two address bits, the rest of the CPU address is decoded by external circuitry and converted to chip enable signals. This allows this PIA to be mapped to any address in the CPU address space. In this case the ROM PIA is at address $C80C through $C80F
When analyzing the VHDL code, the four processes under section "Interrupt edge detection" I noticed that IRQA was indeed cleared when reading from address rs="00" but IRQB was also cleared from rs="00" when in fact it should be rs="10". After correcting this bug I run it in the FPGA and I noticed that something was off. While everything seemed mostly OK, the distinctive Robotron color palette animations were missing. Instead of the flashy changing colors on most of the demo screens, the colors were static. What the...? This sent me into another troubleshooting session to find the root cause of this peculiar effect.
First thing I looked at was the game disassembly for the ISR (interrupt service routine). This is really easy to find, from the 6809 manual we know that the IRQ vector is at address $FFF8 and the address stored there is $DC56. So disassembling the ISR code there I can see: 
ROM:DC56 irq_handler:                     ROM:DC56                 lda     word_C80E       ; read ROM PIA data B to clear IRQROM:DC59                 lda     #1ROM:DC5B                 oraa    byte_45ROM:DC5D                 sta     word_C900       ; overlay ROM onROM:DC60                 lda     word_CB00       ; get video counterROM:DC63                 cmpa    #$80ROM:DC65                 bcs     loc_DC99... more code ...ROM:DCE1                 lda     byte_45ROM:DCE3                 sta     word_C900       ; overlay ROM offROM:DCE6                 rti

It's obvious from the code that the first thing the ISR does is clear the interrupt by reading the ROM PIA output register B at $C80E (last two address bits are "10"). The effect of this should be that the IRQB line from the ROM PIA should immediately go low (deasserted). Bear in mind that the way the 6809 CPU works is by having a level sensitive IRQ pin, this means the IRQ is not triggered by a transition on the IRQ pin, but simply by the pin being asserted. This means that as long as the IRQ pin to the CPU remains high, the ISR handler will be repeatedly triggered. The CPU has an internal flag so that while it is executing an ISR, interrupts are disabled, this prevents an ISR being interrupted from finishing by another interrupt (at the same priority level) causing it to get stuck. As soon as the ISR routine ends and the RTI instruction is executed, the interrupt disable flag in the CPU is automatically cleared allowing the CPU to recognize interrupts again. If the IRQ pin is still asserted at that point in time, the CPU interrupts again and executes the ISR. This is exactly what is happening here!!!

From the simulation screen above we see the behavior of the original code before fixing the bug. The ROM PIA is fed a irq_4ms signal which is a square wave with a 4ms period and on every high to low transition of this signal an interrupt is generated on the IRQB line which ends up interrupting the CPU. Notice the IRQ line to the CPU remains asserted for a very long time and the CPU ends up executing numerous interrupts repeatedly. This is obvious by looking at the bs line, every time it goes high (notice how many times it does that), the CPU acknowledges the interrupt and if we were to zoom in all the way we could see the CPU's program counter and see it executing instructions inside the ISR. The CPU ISR clears the interrupt on the ROM PIA with no effect, then finishes the ISR and exits only to be triggered again because the interrupt line is still asserted. If the ISR had a purely static behavior, the CPU would be stuck in this interrupt hell forever, however the ISR is influenced by external events. One of these is the video counter at address $CB00, see the disassembly above. When the counter has certain values the ISR execution is affected causing it to execute different code paths. One of these eventually performs a read from address $C80C clearing IRQA (which isn't triggered) but also due to the bug, clearing IRQB releasing the CPU from the endless interrupt it was stuck in.
So now you may be wondering, just like I did, why would this buggy behavior actually seemingly produce a working Robotron game, while fixing the bug actually stops the game from running as intended (color cycling is gone)?
The simulation above shows the buggy PIA replaced with John Kent's PIA implementation, showing correct interrupt response and clearing. The interrupt pulses are short and the IRQ line is cleared (deasserted) as soon as the ISR is executed. In case you're wondering, the large portion of time the IRQ line is high as the start of the trace above, is the power on initialization of the CPU and there are no interrupts being handled as can be seen by the bs line remaining low. So what is the root of the problem?
The irq_4ms high to low transition is meant to correspond with the video counter having certain values. The ISR takes certain actions when the video counter is $00, $40, $80 and $C0 (low 6 bits all zero) and the irq_4ms falling edge hits all these values except the $00 one, in that case the edge is early and hits value $FC as seen in the picture above, before the counter has had time to roll over to $00


The video address value is obtained from a video counter. Currently irq_4ms is simply tied to the counter like this: "irq_4ms <= video_count(11)" and looking at the schematic this appears on the surface to be correct, but in fact is not. The irq_4ms is meant to be tied to video address line 11 (in schematic labeled as VA11 ) and if we in fact look at that signal in the simulation we can see it perfectly lines up with the counter value we want as well as all the others so the intended VHDL was in fact "irq_4ms <= video_address(11)", see picture above, where the falling edge of irq_4ms now lines up with the correct counter values as well as generating interrupts at the correct time.


This is another view after zooming in further and can clearly now see that the IRQ line is asserted while the video counter value just changes to $00. Be aware that on interrupt the CPU has to push its registers on the stack and it is about 34 clock cycles later that it actually reads the video counter, so the fact that the IRQ line is asserted just shortly after the video counter has changed is not causing any timing issues.
The proof is always in the pudding as they say, so running this new updated and fixed code on the FPGA shows correct color cycling operation. So why did the buggy one work too? Well it's because the interrupts were asserted for such a long time and executed continuously for long periods of time that sooner or later, the ISR was executed at the right time by pure accident. This is like carpet bombing an entire city just so you can hit one house... It's also possible that because the CPU spent such a long time repeatedly running the ISR it left less time for running game code possibly slowing it up or altering game play, though a cursory look over the game execution did not show that to be an obvious problem.
So after all this, do I have sound working? No, dammit! Except now at startup or after a reset the characteristic Robotron sound plays, but after that nothing. The adventure continues...

Share this post

Link to post
Share on other sites

Very, very nice work Alex (referring to the latest commit you haven't yet advertised :)

Share this post

Link to post
Share on other sites
Yeah, this forum's account was temporarily suspended so I couldn't post, but yeah, finally sound is now working!


Once I sorted out all the issues I discussed previously, I concentrated specifically to find out what the issue was with the sound. From simulation I tracked it down to two separate issues, one was the clock to the PIAs was 12MHz causing them to react too quickly to data from the bus and latch in incorrect data to the output ports. Once I connected the PIAs to run off the 1MHz CPU clock, this went away but I still wasn't getting sound, so further simulation showed that I had an incorrectly wired signal. On the schematic there are 6 lines connecting ROM PIA to the sound PIA and one additional line line labeled HAND that connects to ROM PIA PA7 (port A bit 7). It was this line I had connected to PB7 instead of PA7. Once these were fixed I could hear glorious Robotronic sounds abusing my ears :)


It's worth noting here that I expected as soon as I reset the game to hear a continuous sound like I hear when I start the MAME game. This sound persists the entire few seconds while the RAM is being tested and the screen displays the rainbow noise, but on the FPGA I wasn't hearing that. I immediately assumed there was a fault so I started a new simulation with the relevant signals (game CPU, ROM PIA, sound CPU, sound PIA). While the simulation was running (takes a good 20 min) I decided to browse through youtube videos of Robotrons. Mostly they are all videos of MAME gameplay and very few are of the actual hardware running the game. Even when finding real hardware, the video is mostly of gameplay but not the actual power on sequence which is what I was interested in. Finally after much hunting around I found this video  showing what I was after, at time marker 4:30 and while the environment is a bit noisy as the guy is shooting it in a video arcade parlour, it is clear that the behaviour of the real game is just as I observed on the FPGA, at power on or after reset, the game is silent (unlike MAME!) and only after finishing the memory test and displaying the message "Initial tests indicate: operational" it emits a very brief sound before moving to the attract screen.


Share this post

Link to post
Share on other sites

Sorry about the suspended account this weekend. I was on vacation with the family and didn't notice until Sunday that the account was suspended. I don't know why there is not an option to automatically charge me for the forum. Seems like I've missed the manual payment a couple times but usually catch it pretty quickly. This time I was out on vacation so it was a much bigger deal than it normally is. On my task list, a call to the forum host to see why I can't do an automatic bill to avoid this type of thing.


Update: They pointed me to an obscure area where I could enter a recurring billing account! So we should not see any more suspensions. :) Sorry to interrupt the thread.



Share this post

Link to post
Share on other sites

No problem Jack. So in case you all thought that now that the sound is working this was all over, think again. I'm here to torture you some more. Incidentally, is anyone reading these posts (other than RorschachUK and Vlait) and should I continue posting or should I just STFU and GTFO :) ?


Since Robotron is now playable, though I haven't spent much time actually playing it, I thought it made sense so get more of the Williams games running on this platform but my last quick tests indicated that the other games have issues. The next in line to focus my attention on is Joust. This is because Joust is actually playable, I can insert coin and play through the first couple of levels, the problem is that when it goes to the attract screen, the fist text only screen displays fine but as soon as it goes to display this logo screen:




The game just hard hangs and I get a black picture and I have to reset it to get it back to life. Last time I investigated similar behaviour with Robotron it turned out to be a CPU bug in the handling of a HALT event, but now that this is fixed, what could be the issue? Is it still related to the blitter? Is it another CPU bug? Time to investigate.


The first step is to disassemble the Joust game and figure out how it works and how I can patch it to speed through the parts of the game that work and just quickly get to the problem area. Joust behaves sort of similar to Robotron in that it fills the memory with random data (hence the rainbow noise on the screen at power on), initializes the hardware, does some other things I didn't bother to investigate in detail then clears the RAM by filling it with zeroes and jumps to the beginning of the game. I won't spend a lot of time explaining the debugging and patching of the game as that is a whole 'nother skill set not technically related to FPGAs. Suffice to say that I determined that the actual start of the game is at address $E000


As before I patch address $E000 at the reset vector and the game just skips the lengthy hardware tests and initialization and jumps to the attract screen which is text only and displays the high scores, but we need it to just go the next attract screen with the logo.

ROM:E0D3 loc_E0D3:ROM:E0D3                 stb     byte_F2ROM:E0D5                 ldu     #$A00AROM:E0D8                 ldx     #$5ED0          ; address to jump to for attract screenROM:E0DB                 clraROM:E0DC                 clrbROM:E0DD                 jsr     loc_E284ROM:E0E0                 andcc   #$EF            ; enable interrupts

The disassembly at this location shortly after the game start address at $E000 shows the relevant part, X register is loaded with value $5ED0 which is the address of the code that displays the high scores. After some tracing and stepping though the code I determined that the code that displays the Joust logo just happens to live at address $D000 so I patched the location at $E0D8 to load the X register with $D000. Now immediately after power on the game jumps directly to the problem screen.


By setting watchpoints at the blitter address $CA00 and running the patched code in MAME and observing the output to the screen I discovered that the blitter is used to draw the all text in the Joust logo screen, but then at a certain point no more blitter watchpoints were triggered while it drew the Joust logo on the top half of the screen. This was curious but it also explained why that logo is drawn so slowly, you can literally see it being painted on the screen. If I had to guess beforehand I would have thought the logo is drawn with the blitter and the text is drawn by the CPU. In any case, the fact that when the game hangs I see nothing but a blank screen, it means the game hangs at the blitter since no text is drawn on the screen. Running a simulation shows this:




A rough explanation is that after about 300ms the CPU has finished the game initialization and enables the interrupts, which can be seen by the IRQ line starting to pulse (it means it has initialized the PIAs, which generate the interrupts). Another 100ms later the HALT line starts to become asserted meaning it has begun to use the blitter. Immediately apparent is that after some initial brief pulses on the HALT line, it then remains halted for extremely long times (for a CPU) almost 1/4 second at a time. This strikes me as abnormal behaviour that needs investigation, but since I spent most of the evening in the debugger finding patch locations, this will have to be continued... maybe.

Share this post

Link to post
Share on other sites

at first glance the sc chips are missing 2 functions, shift and sync to E clock for RAM-RAM copying - is the latter handled by the cpu handler ?

If not that might well be causing problems.


Sean Riddle's blitter tests might actually be easier to debug (assuming they crash too)

.. i'll try them out if/when i get off from work today before i have to leave to work again :/



Perhaps also jrok's benchmarking test

(i really had fun generating the font code for that to compile... turns out bash and awk can do almost anything together :)




Share this post

Link to post
Share on other sites


I think the very detailed account for how you go about to debug this code is extremely interesting to read, especially for more experienced users. 

There are very few articles on how to debug VHDL code out there, most tutorials are step-by-step instructions of how to make something but not what to do if your creation doesn't work as you expect.

Share this post

Link to post
Share on other sites

Thanks Magnus.


Also thanks Vlait, I had visited Sean Riddle's site and Jrok too just this weekend and came across those detailed tests, made for very interesting reading. If you could help out with these that would be awesome. I was a little sad (to say the least) that Jrok seems to have gotten all the Williams games to run on his custom FPGA board ( a Spartan 2 no less) but he has no downloadable code, grrrr. Jrok also uses an external 6809 just like Jared from Sharebrained so I wonder if he's just using a modified version of Jared's code or a completely independent implementation. I haven't seen any videos on Jrok's platform so not sure how playable all the games are or if they have any issues.


I think you may be right that the SC VHDL implementation is incomplete, I think I'll have to check it against Sean Riddle's modified MAME C code on his site and update the VHDL.


EDIT: OK sorted it, after observing that the SC registers on the FPGA were loaded with different values than I was seeing in MAME, I tracked down where those values were coming from and I arrived at the CMOS, then it struck me, I was initializing the CMOS with default values for Robotron! After I got a dump from MAME and initialized the CMOS with default values for Joust, it is now very happy all the way through. I decided that it'll be too hard to have different CMOS initialization values for each game so I'll just have a blank CMOS and let each game reset its settings on power on. This means on first boot the game will stop with a message that the CMOS was reset to factory defaults and the user has to press the "advance" button. At the moment the games that seem to play fine are Joust, Robotron and Stargate, the others are very sad.

Share this post

Link to post
Share on other sites

Alex, when it comes to Bombjack and others on an LX45 platform, would the be there enough left over block RAM to buffer the display, so it can be displayed at the correct orientation and at a 'more standard' pixel clock? (in much the same ways as the scanline doubler currently works, but for the whole frame)


Of course Joust (and Robotron) are already use the monitor in 'landscape' mode, but might benefit from using a standard pixel clock.

Share this post

Link to post
Share on other sites

The blitter is definitely the cause for crashing (or rather trashing)


Width and height of size 0 are incorrectly tested allowing the counter to roll over (why a 9 bit counter?)


Changing line 196 to "if x_count_next >= width then"

and line 200 to "if y_count_next >= height then"

would fix this but there's still something wrong with the src/destination stride calculation...

Share this post

Link to post
Share on other sites

Mike, if I'm understanding what you're asking correctly, most old school games would be at less than QVGA resolution so less than 320x240 or 75KB RAM assuming you can fit the color into a byte perhaps as a 3-3-2 RGB tuple. This translates to 38 RAMB rounded, pretty hefty really for an FPGA


The LX45 has a total of 116 RAM blocks so you may be able to get away using 1/3 of them as frame buffer though if you're talking about the Pipistrello specifically, you should consider using the external DRAM for this particular purpose since the LX45 has an MCB and more importantly the frame buffer is accessed in a predictable sequential order which is ideal for getting high speed out of the DRAM. Extending this logic, you could use the DRAM controller you made recently on a LX9 even, though Bombjack specifically won't run an a LX9 due to memory requirements (not just size but speed).


So if I understand correctly you want to do something like:

[game video]------->[ in     frame     out ]--->[VGA monitor]                    [ port   buffer   port ]                       ^                ^                       |                |          [game video pixel clock]   [VGA pixel clock]

The other thing to consider is that if you change the pixel clock on the output relative to the pixel clock on the input the in/out video frames become unsynchronized so you may end up with unwanted video artifacts.

Share this post

Link to post
Share on other sites

Bubbles seems to be happy as long as height/width = 0 do not cause the blitter to run off (my earlier post).

There are some artefacts which might be caused by the missing functionality in the blitter

Splat also is running ok at least in the attract mode when the blitter is changed to SC2 (ie remove xor 0x04 from width/height registers)


You can also advance from the "Factory Settings Restored" by pressing reset btw.

Share this post

Link to post
Share on other sites

Nice one Vlait, I hadn't thought of adding Splat ROMs. What changes did you to the the blitter? Do you just not run when h=0 and w=0 ?


Good point about reset, once the CMOS is initialized, a reset won't wipe it :)


Also can you point me to a 6809 assembler? I tried a bunch of them and none are happy compiling jrok's test code.

Share this post

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now