Using an FPGA to replicate no longer available components on the Sound
and CPU Boards in a WPC-DCS Pinball Machine.
These include the two ASICs and PAL.
Introduction
With the introduction of Indiana
Jones in 1993, Williams Pinball
started to base their audio system on the Digital Compression
System. The main CPU for this system is an ADSP-2105 DSP
chip. There were two main generations of these boards, the first
(used in the IJ) used a PAL to contain the decoding logic. This
is known as the WPC-DCS audio
board. In the second generation, Williams combined the dot matrix
controller onto the same board, and moved all the logic into an
ASIC. This latter generation is known as WPC-95.
Due to the unavailability of the ASIC, the WPC-95 audio boards have
soared in price (RGP
discussion).
They are commonly several hundred dollars each. In the summer of
2007, I was looking for a pinball/FPGA related project, and this seemed
like a good one to dive into. After some discussion with Martin
Reynolds, I decided to give it a try.
The first step was to reproduce the WPC-DCS circuit, since it was
simpler (only uses a PAL). My hope was that once it was running
using IJ ROMs, I could make some small changes, load in MM ROMs, and
show
that WPC-95 was simply a repackaging of WPC-DCS. To get started,
I needed to know the equations for U16, the PAL on the WPC-DCS
board. Help came from Brent Butler, who was the one who reverse
engineered the WPC Security PIC. He read the contents of his PAL,
and sent me the equations. This, along with the WPC-DCS
schematics, and the part datasheets were all the ingredients I needed
to start coding.
The
prototype/breadboard
The platform I chose to use was the Spartan3A
development board. It includes
a Spartan3A FPGA, and lots of peripherals such as 4 MBytes of Flash,
buttons, LEDs, a power supply, a USB
programmer, and an expansion connector. This latter connector
allowed the connection to wire
wrap boards, where the DSP and D/A chips would reside. The
self-contained Flash memory was nice because it would allow me to
eliminate the large PROM chips, and put their contents in a compact
modern part. IJ needs 3.5 MBytes, so it should fit quite nicely.
The first step was to load the ROMs for the IJ into the Flash of the
Spartan3A board. The image of the seven sound ROMs were merged
into one large binary file, and I then used Ken Chapman's excellent
Flash
loader (see link below) to transfer this into the Spartan 3A.
Once that was complete, I could use his bootloader program to read back
sections, and I could see that the 7 chip
image was correctly loaded into the Flash. The next step was to
develop the VHDL code. A simulation of the result is shown below.
Simulation of the VHDL code shows no bus contention and the proper data
being fetched for the DSP. In general, the signal names are the
same as
on the WPC-DCS schematic.
In the above waveform diagram, the signals from 'cmd_read' to
'ram_read' indicate what the decoding logic thinks the DSP is
doing. We can see only one of these become asserted at any one
time. Also, an inspection of the databus shows the proper data
being fetched for the DSP. This shows that the PAL equations and
my reading of the schematic appear to be correct.
Once the code was complete, it came time to assemble the
prototype. As mentioned before, the DSP and D/A were to be
installed onto the breadboard that plugs into the Spartan3A. I
decided to mount them pin side up to allow access to all the
connections. A photograph of the completed prototype is shown
below.
The WPC-DCS prototype board. The board on the right holds the DSP
and the D/A.
All other components are on the other board.
Close-up of the DSP and D/A area. The two components are on the
reverse side.
Testing
I did some tests to characterize what the DSP is supposed to do on
startup. When an original WPC-DCS board is properly running, the
clock (DacClk) and strobe (DacStb) lines from the DSP to the D/A are
expected to be clocking, but the data (DacDat) line will be
static. The data line will have transitions only when a sound is
being played. Also, the power-up BONG is expected to be played by
the DSP a few seconds after bootup. In contrast, a WPC-DCS board
with no ROMs installed, will have these lines as hi-Z, which is
presumably the power-on state for this port. Clearly, there will
be no sound issued by the DSP if it does not boot. This information
will allow me to gauge if the DSP on the prototype is running or
not.
Up to this point, a lot of effort had been invested, and if the DSP did
not boot, I would have very few options to incrementally debug the
problem. A great deal was at stake here, and it would all hinge
on the initial power-up. I flipped the power switch, and to my
relief, the DSP appeared to be working. I could see the D/A lines
clocking, and a single burst would appear on the data line a few
seconds after power-up. Also, I programmed the switches on the
Spartan 3A for command input (to play sounds), and when a command was
sent to the DSP, I could see it fetch the command, and clear the
interrupt flag. This was very promising.
I then plugged in the D/A chip next. In addition to the usual 5V,
this chip requires -5V as well. Presumably for the internal audio
op amp. This latter supply is provided by a battery pack. I
connected the audio output of this chip to a signal tracer (basically a
small audio amplifier with speaker). On the subsequent power up,
I get a cool single "BONG" from the prototype. This indicates the
DSP boots and that the checksum of all the Flash was good.
Success!
The DCS audio board is controlled by a bus consisting of 8 data bits, 5
address bits,
and 2 control lines. I needed to know what
commands the DSP code would recognize so
that I can stimulate it on my test bench. Help came once
again from
Martin, who knows how to run PinMAMED.
Using this emulator/debugger, we were able to figure out that command
packets are two bytes long. For example "0001" causes the Indy
march to play on PinMAMED. A check with an oscilloscope on my IJ
shows that the two
bytes are normally separated by about 1 millisecond. Based on my
initial testing with sending single bytes, the DSP times out after a
certain period, and the two bytes in the packet need to follow each
other by 1
millisecond or less. I then changed my user interface to send two
bytes in rapid succession, with the bottom nibble controlled by the
four slide switches on the Spartan3A board. This proved
successful, and I was able to get my prototype to play all the sounds
identified by 0001 through 000F on PinMAMED.
The DSP is mounted into a socket, and
then plugged into some wire wrap pins. This is the square grid of
pins in the above image. To the right of that is the 16-pin D/A
chip, also mounted pin side up. The D/A needs a -5V supply, and
this is supplied by a AA battery pack. As you can see in the
image above, the DSP connects to the Spartan via series
resistors. These are included to protect the Spartan 3 device
because its inputs are not 5V tolerant.
WPC-95
The next step was to emulate/replicate the WPC-95 A/V board. When
this project was started, I had hoped to be able to simply port the
WPC-95 ROMs into the testbed above. This would eliminate my
having to reverse engineer the ASIC. However, examination of the
schematic shows why this would not work.
WPC-95 ROMs are larger than WPC-DCS. Per the schematic, the
address bus for the WPC-DCS ROMs is 23 bits wide (8 Mbytes address
space). In contrast, the address bus on WPC-95 is 24 bits (16
Mbytes address space).
Of this latter 24 bits, the 10 LSB bits come directly from the DSP address bus, while the remaining 14 come from a paging register inside the ASIC. This is much different than WPC-DCS, where the bottom 12 bits are from the DSP, and the top 11 from a paging register.
Only 8 bits of the DSP's data bus is connected to the ASIC, thus the 14 bits of the paging register must be stored as two separate writes. This makes the two platforms incompatible. Loading the paging register is completely different in one configuration compared to the other.
There are two RAM enable signals on WPC-95 (both from ASIC) vs. one on the WPC-DCS.
Cracking
the code
An important tool to understand what the ASIC and DSP are doing is a
logic analyzer. I connected three pods of my unit to the various
points on the WPC-95 board that was loaned from Jim Knight. This
unit had Scared Stiff ROMs on it. I was able to slowly gain an
understanding of what the DSP does using this setup. It felt like
the process of cracking a cipher code or opening a combination lock.
The logic analyzer connected to a WPC-95 board.
The order of operations of the DSP is as follows:
When the POR timer expires, the DSP reads the inital boot code from ROM using the BMS signal. This takes about 610 useconds.
The internal boot functions take about 300 useconds, and the first of the RAM checks then start.
A pattern is written to the entire range of RAM using the DMS line.
The pattern is read back using the DMS line.
Another pattern is written to RAM with the PMS line.
The pattern is read back using the PMS line.
If any of the previous four steps fail, the DSP issues ten
bongs. This checkout takes about 200 microseconds.
RAM is then erased with the PMS line.
Another erasure of RAM with the DMS line.
The DSP then proceeds to the checksum calculation of the ROMs. This takes about 2 seconds on Scared Stiff ROMs.
If all is well, at t=2.5 seconds, the BONG starts. A single sound indicates all is well with the ROMs. This takes about 0.6 seconds.
At t=3.4 seconds, the DSP reads from ROMs again with the BMS line. At this point, the DSP starts to operate.
While this is going on, every second or so, the DSP writes to a
register in the ASIC to flash the red LED on the board on and off.
Another important tool is Xilinx's Chipscope.
This allows one to see live data from an FPGA using only its JTAG
port. Although I extensively tested my code with the simulator, I
do not have a simulation of the DSP, and this tool allows me to see the
interaction of the DSP on the prototype board, and my FPGA code.
View of Xilinx's Chipscope Pro tool. It allows you to display
live data from the
FPGA design using only the JTAG interface. For example, at the
blue "X"
cursor, the DSP is writing "F20B00" to the
address "2FFF". This maps to RAM.
After implementing the logic
relationships that were read with the logic analyzer, my prototype
initially bonged ten times. This indicated a communication
problem with the embedded FPGA RAM. After debugging with
Xilinx's Chipscope Pro
(see above), I found the problem, and the prototype
bonged twice on startup, indicating a remaining problem with the way
the paging register is being handled. After about one month of
work,
I finally fixed all the bugs, and the prototype bonged once on startup,
and flashed one of the LEDs on the Spartan board, matching the
operation of the loaned WPC-95 board. This represented a huge
milestone in the project, and I was very pleased to get to this point.
It works!
To understand how the CPU commands the WPC-95 A/V board, I
inserted the loaned board into a WPC-95 machine, to see how it
interacts
with the CPU. The kids were tickled when they heard our TOTAN machine
producing Scared Stiff sounds. By activating the sound test menu,
I could see how the ASIC interrupts the DSP, and how the command is
delivered to the DSP.
The loaner board with all the probes hooked up installed
into my Tales of the Arabian Nights machine.
With the information gathered, I
created a command interface with a pushbutton and the four slide
switches on the Spartan board similar to the interface to the WPC-DCS
board above. On the first try, I was able to get the prototype to
play Scared Stiff sounds! I had successfully reproduced the audio
portion of the ASIC.
Youtube video of the WPC-95 prototype
in action.
Note the flashing LED on the prototype, matching the original board.
The DMD
interface
The second half of the A/V board is the DMD interface. After the
above results, I took a few months off from this project, and returned
to it during the 2007 Christmas season. After a week or two of
work, I was able to display a test pattern on my IJ's DMD on the first
test.
The test image shown on the DMD is produced by the Spartan development
board.
The image is sharp with no ghosting showing that the timing is
correct.
In the above image, the DMD data cable is plugged into a solderless
breadboard that is in turn plugged into the Spartan board. I
initially started to implement the circuit by simply converting the
schematic from the WPC-89 schematics into VHDL. I soon realized
that the discrete design was not very suitable for implementation in an
FPGA. This is because good FPGA coding practices recommend a
fully synchronous design. The original WPC-89 implementation is
far from it. I rewrote the VHDL code as a fully synchronous
design, and using only one clock (the CPU E-clock) rather than two as
the original DMD interface board. By using a single clock, it
makes it easier to one day integrate everything into one FPGA.
The DMD display shown working on a WPC-89 machine.
I develop the code on my laptop and load it into the FPGA via JTAG.
After another week's work, I found all the bugs and was able to drive
the DMD by plugging into the CPUs IO expansion ribbon cables! It
took more effort than I thought it would, and was mainly stymied by the
design of the original controller. The way it switches the
address bus into the RAM violated the FPGA RAM's hold time, and it took
me a while to figure it out.
The photo above shows my development system. The solderless
breadboard has the three connectors to the IJ (two to the CPU board,
and one for the DMD). The Spartan board interfaces to these
connectors, and is controlled by the CPU board. The laptop is
simply set on the playfield glass, and connected via JTAG to the
Spartan. I can then develop the code, and run it with Chipscope
(on the laptop). This allows me access to the signals without a
logic analyzer connected.
A day after I was able to successfully use the programmed Spartan on my
Indiana Jones (WPC-89), I was able to use it on my Medieval Madness
(WPC-95). This shows that the DMD interface on both generations
are the same.
The CPU
Board
In September 2007, after the successful completion of
the WPC-95 A/V VHDL code, I started working on an FPGA version of
the CPU board. Since so much of this board is digital in nature,
it would lend itself well to recasting into an FPGA. After some
searching, I found a 6809
core. If I could also combine the RAM and the ASIC's
functionality, we would have the essential parts of the CPU board on a
single FPGA. I also previously had the good fortune of purchasing
a CPU board (with the
valuable ASIC) on Ebay for $65. The board did not initially boot,
but after I replaced the RAM, it booted fine. I was thus set to
start the replication process.
I first loaded the game ROM code into the Flash memory that is on the
Spartan board, and started by tackling the ROM paging that is done
inside the ASIC. Information on how this is done is very sketchy,
and even looking at the logic analyzer plots showed very little
information. I then found this
memory map by Matthias Bucher, and the
PinMAME source code. Among the files in this latter resource, the file
WPC.H contains the memory map locations of the ASIC registers, and
a description of the "shifted bit hardware". I implemented my
best guess of the paging
algorithm, and it appeared to successfully step through the ROM check
process (by using Chipscope).
With the logic analyzer hooked up to read the CPU's activity, I could
roughly see what the WPC CPU does during the boot process:
After the reset is deasserted, the reset vector is fetched at FFFE and FFFF. These are the last two bytes of the program ROM.
The ROM is checked first starting with the highest 32kbytes. This is the non-paged section. Then, the lower ones are checked in turn by using the paging register at 3FFC.
If that passes, the CPU moves onto the RAM check. It does this by writing and then reading the data byte 0x55 to the RAM, and then repeating the test with 0xAA as data.
If that is ok, it continues with inits to the I/O such as the flippers, sound board, DMD, etc.
Then comes a lot of initialization which purpose is not clear to me.
After about one second of initialization, the main code enters an infinite loop and waits for an interrupt.
It took me one month to get my VHDL code to this point. However,
I did not at the time suspect the role of the interrupt.
Therefore, I could not figure out why the code was getting stuck in the
infinite loop. This prevented the LED on the Spartan board from
flashing, which is how the game code indicates that all is well with
the tests. It was very puzzling and somewhat frustrating. I
then decided to set aside this project for a few months and take a
fresh look later.
In January 2008, I completed the DMD interface
above, and decided to tackle this again. One tool that I had set
up was a separate Spartan board consisting of a simple 24 bit counter
that would basically be my time keeper for the WPC CPU board.
With this addition, I could tell how many clock cycles had elapsed
since the reset was released. This data was available on the
logic analyzer, and I could trigger on events (such as writing to a
register or memory location), and see the accompanying count of the
elapsed clock cycles. The use of this latter counter allows me to
know where in the boot process the CPU board was. I had always
noticed that the first flash of the LED always occurs with some spread
in the values for the elapsed time count, and I could never figure out
why. Then it hit me that the LED process may be controlled by
another separate asynchronous process...... such as an interrupt!
A quick check of the IRQ line on the WPC CPU showed that it pulsed at
1kHz. Thus the WPC ASIC interrupts the processor at that rate,
and this is probably why the main code on my Spartan board was stuck in
an infinite loop!
I added the interrupt feature in my VHDL ASIC in the Spartan, and
bingo! the LED on the Spartan board flashes rapidly and continuously
after a second from power
up, just like the WPC CPU board. Then, intentionally corrupting
the ROM paging results in one flash of the LED, and corrupting the RAM
mapping results in two flashes. These results agree with the
diagnostic information from my IJ manual. All this means I am
able to
successfully get the game code to boot up. The RAM, CPU, ASIC and
other minor glue logic from the CPU board were all inside a single FPGA.
Combining
the CPU board and the DMD interface
To truly verify that the game code is running properly on the Spartan
board, I decided to integrate the previously developed DMD interface in
the same FPGA. This would allow the board to drive a DMD using
the game code that is loaded into the Flash memory. It should
have not taken me much time, but a simple mistake cost me a few days to
track down. Once that was corrected, success! As can be
seen in the animated GIF below, the game code boots up and drives the
DMD display cleanly. If I hit the reset button after initial
bootup, the display shows the familiar "Testing..." message, and then a
"Check Fuses...." message. This is because the switch matrix has
not been hooked up, and the game code thinks a fuse is blown.
An animated GIF showing the Spartan board running WPC code. In
this case
"Slug Fest". The only connections to the IJ machine is power for
the DMD.
The DMD data is driven by
the Spartan board.
A VGA Interface
Up to this point, if I wanted to test
the DMD interface, I had to walk down two floors from my office to my
basement where my machines are located. This started to get old,
and I wanted to find another way to display the DMD information.
I hit upon the idea of using a VGA interface so that the TV/monitor in
my office could be used. This idea has several advantages:
As mentioned, I would be able to test the WPC prototype anywhere with a VGA monitor.
This hardware could become part of a WPC CPU tester, similar to my Williams System 3 - 11 tester.
This is consistent with the underlying theme of this project, which is prevention of hardware obsolesence. Although unlikely, monitors could be substituted if DMD displays become unavailable one day.
Since only one third of the display is used, the rest could be
used for display of animation. For example, with a machine like
IJ, the video scenes from where the quotes are taken could be displayed
along with the sound. The index of which sound is being played
can be obtained by intercepting the command to the DCS sound board.
The Spartan board has a VGA interface connector, and the associated
hardware is very simple. It consists only of a dozen resistors
and the connector. I found a document that describes the VGA
interface timing, and started work on the interface. The WPC
DMD is refreshed at about 122 Hz. The normal VGA frequency is
60Hz. I knew from my previous work that the WPC CPU switches
rapidly between two screens to form pixels of half intensity. I
wanted to make sure that this feature was preserved with the VGA
display. However, since it only displays about one every
other DMD screen, some special accomodations needed to be made.
Another aspect is that the VGA display is 640 pixels wide, which is
five times more than a DMD display. It means that each DMD pixel
can be mapped to one five by five grid of VGA pixels.
The VGA interface hardware on the Spartan board is very simple, and
consists of
just some resistors. It is possible to get by with just six
resistors for
this application
(fewer bits and no blue resistors).
The VGA interface on the WPC
prototype works by writing the DMD data into a separate buffer, and
'smooths' the value across two screens. This analog value is then
sent to the monitor for display. This simulates a persistence that
lasts across multiple VGA screenfuls. I coded this idea up, and
found it to work quite well. After initial testing, I refined the
code to provide a black border around each pixel, and rounded the
pixels' corners. This makes it much more like a real DMD.
Referring to the image below, one can see half-intensity round pixels
in the simulated DMD.
The VGA interface at work. I can now debug the WPC Spartan
prototype from
anywhere such as my home office. Note the monitor cable plugged
into
the top connector of the Spartan board. The
code being run here is "Slug Fest",
a baseball themed game. Note that I no longer get the "Check
Fuses" message
because I faked out
the switch matrix and simulated some closed switches.
Close-up of the VGA screen shows that the displayed dots are round with
gaps in between. This is meant to match the appearance of a real
DMD. This hardware could in principle be added to any
machine
that uses the 128x32 dot DMD display.
Go to
Page 2 of the WPC FPGA Project
Project Log
6/20/07 - Start of Project.
6/21/07 - Equations for U16 PAL from Brent Butler
(pinball.at.ptpbroadband.com).
7/7/07 - Simulation of VHDL code runs properly.
7/23/07 - Assembly of prototype starts.
7/24/07 - BONG! Powered up the complete prototype, and it gives one sound indicating DSP booted properly. A huge milestone.
7/26/07 - Fixed method of feeding the prototype commands, and it
now plays any of the IJ sounds.
Discussion here.
8/5/07 - Some thoughts on WPC-95 interface.
8/13/07 - WPC-95 prototype bongs ten times on initialization. This indicates that the critical boot ROM access is good, but the DSP RAM interface is faulty.
8/16/07 - Fixed DSP RAM problem, and WPC-95 prototype bongs twice on startup. Now I need to fix the page register.
9/1/07 - A/V board prototype finally issues a single BONG on startup! One of the LEDs on the Spartan board serves as the red LED on the WPC-95 board, and flashes just like the original board. Another huge milestone passed.
9/1/07 - After figuring out how commands are passed, the prototype plays Scared Stiff sounds. Discussion here.
9/27/07 - Initial development of the CPU board code
completed. At this point, this project was shelved for a few
months.
1/7/08 - Verified the refresh portion of the DMD interface is correct. Discussion here.
1/12/08 - Completed the DMD interface. It works completely and passes all tests.
1/22/08 - CPU board on an FPGA boots! I get a fast flashing from the LED that is mapped to the equivalent LED on the WPC CPU board.
2/3/08 - WPC CPU to VGA interface completed.
See Page 2 for the rest of the
Project Log and "Links".
(c) 2012 Edward Cheung, all rights reserved.