Ok, so Black Widow it would be. I downloaded the service manual, schematics, MAME sources, everything I could lay my hands on, and I found the hardware consisted of a few parts:
I would also like to integrate these things in the FPGA, but I could easily carry them over from my Asteroids prototype:
So, I set to work. Most of the things were fairly easy because I could copy them from my implementation of Asteroids, but the AVG, the part that draws the lines on the CRT independent of the main CPU, proved more difficult. The vector generators of both machines are both built on the same idea, but the Asteroids-machine didn't have an AVG, it generated its vectors digitally using something called a DVG. I decided I would be best off building the AVG from scratch, getting some VHDL experience doing it.
The digital part of the AVG basically is a processor-like state machine, which was invented to relieve the code in the 6502 from having to draw everything on the screen tens of times per second. It has a program counter which points to an address in vector ROM or RAM. It has a four-level stack so it is able to execute subroutines and return to the calling address. It doesn't have any conditional, ALU or memory instructions, though: it only has instructions to draw a line, change the color and intensity of a line and call and return from a subroutine. The idea is that the vector ROM contains subroutines to draw letters, numbers, the various enemies and other symbols. The main 6502 processor can then write to the AVG RAM instructions to move to a location, set a color, and jump to a subroutine to draw something. As soon as that's done, the AVG automatically will run that program over and over again, refreshing the screen without needing the 6502 CPU.
The analog part of the AVG is the bit of logic that draws the line. It consists of some logic, two DA-converters and some analog integrators. Basically, the AVG can set a rate of increase or decrease for both the X- and Y-location of the electron beam on the screen. When the integrators are then allowed to apply that rate to the location values for a certain time, a line will be drawn. The AVG also contains some logic to make sure all lines are drawn at about the same speed to make sure their brightness is the same.
While I could try to copy all the logic in the original schematics to my FPGA-implementation, I decided against it: the original design was full of gated clocks and asynchronous signals, and the analog part of the AVG was completely impossible to directly copy to VHDL. My plan was to make a workalike: a bit of logic that was built completely from scratch but would execute the instructions in vector memory to build the same image a real AVG would.
Building my workalike took some time, mostly because information about the particular AVG-quirks used in Black Widow were scattered all over the Internet. To make sure I wouldn't be thrown off by bugs in the rest of the code, I used a trick: I loaded up the Black Widow ROMs in MAME and let it run until it displayed something interesting. When that happened, I took a screenshot and opened the debugger. I then dumped the vector RAM to disk: combined with the vector ROMs, I now essentially had a 'screenshot' I could load in my own FPGA-code. This helped immensely in tracking down most bugs.
Recreating the digital part of the AVG wasn't too hard: it's basically a state machine and some logic to do vector scaling. Jed Margolin, one of the people designing vector games at Atari, wrote an excellent article about it, describing both the AVG as used in Black Widow as well as the DVG Asteroids uses.
Rebuilding the analog part was a bit trickier, but in the end still was doable. In the fully-digital vector generator in Asteroids, Atari used an up/down-counter which could be fed a clock signal with a frequency set by the state machine, but I don't really like that approach, and I wasn't limited by what logic chips I could get my hands on. My implementation is a fixed-point integrator. It is fed dx and dy, the rate of change for the x- and y-position of the electron beam. When it needs to integrate, it periodically adds these to the 22-bit x- and y-registers. Because the resulting X and Y are more than 10 bit, I then return only the most significant 10 bits as the X- and Y-coordinates of the electron beam. This implementation probably is way more precise than needed, but it doesn't cost too many logic elements in the FPGA anyway.