Devil Zone

HudsonArcade

Well-known member
Joined
Aug 12, 2009
Messages
14,729
Reaction score
3,632
Location
01749, Massachusetts
At first glance it looks like Devil Zone (Universal Board #8022) is *almost* identical to No Man's Land (#8003) and Magical Spot (#8013).
It's got the same hideous #8013-V1 hack daughterboard from Magical Spot, and its own sound and aux gfx boads.

The one change I do see is the priority in the final gfx mux. The mux inputs are the same on both, but the mux select decodes are different.

Code:
BA Input
00 Black
01 Background 
10 Sprites (CRL)
11 Aux Gfx (CRT)

On NML, the aux board bit comes into D6, and forces the mux selects both high to give the bits from the aux highest priority.
Priority doesn't really matter here since the code doesn't let you drive/shoot through the trees,

1700600234596.png

On Devil zone, the mux selects don't care about the aux board data.
The logic is a bit goofy since they were reworking existing hardware, but it works out as
MuxB (pin 3) is now ~Video1 || ~CSEL2
MuxA (pin 8) is now CSEL2

When CSEL2 is low (either sprite bit plane set), we get 01, selecting the CRL sprite colors.
When CSEL2 is high and VIDEO1 is high (no sprite, BG on), we get 10, selecting the BG color bits from the D2 PROM.
When CSEL2 is high and VIDEO1 is low (no sprite or BG data), set get 11, selecting the aux board colors, giving them the lowest priority.
There's no 00 decode for black here, but when everything is off we still get black.

1700600276927.png

Since the priority matters on Devil Zone (blue lines over other graphics would look dumb), but not on No Man's Land (sprites are never drawn over the water/trees) or Magical Spot (no aux gfx board), it makes sense to mod the design for this priority scheme instead (and to redo the 4-NAND mess above to free up some gates.
 
After a beer, a brisket quesadilla, and a little more thought, a better solution came to mind.

I already added a pullup on ~NOCRT on the board so if the signal is undriven (ie. on Magical Spot with no aux board) the aux graphics are disabled.

By running the CRT signals to both the 00 and 11 inputs, they can be both highest priority (by pulling down ~NOCRT) and lowest priority (by leaving ~NOCRT high). This requires putting pulldowns on the CRT<3:0> lines for when there's no aux board to ensure no sprite & no bg gives black, but it gives more flexibility for different effects if you want to do something else with the hardware.

1700610276859.png
 
Apparently Universal put an impressive amount of engineering into doing the background grid effect on Devil Zone, and it's actually pretty cool.

First the boring part:
1700666433269.png

This guy got a 32x8 PROM and some shifters. 32x8 = 256, matching the number of horizontal pixels on the screen, and it's only addressed by horontal counts, so the 'image' is the same on every row, so clearly this has to be the straight lines in the blue background.
aHD' - aHA' are already pre-XOR'ed with INV0 on the CPU board, so the XOR here just to flip HD to match. In normal mode this just counts 0-31, but when cocktail flipped it'll count 31-0.
To get the cocktail flip to work right, they need a second '166 with the bits reversed, and a later mux selects the reverse ordered bits.

Obviously, we're going to want to get rid of that PROM, and a GAL's probably big enough to get rid of the XOR and the 2nd shifter too, but first let's crunch on the GAL:

Code:
$ cat ic1.sub | bindmpesp 5 8 | espresso | esp2vlog -gal - > /dev/null
Using address width = 5, data width = 8, shift = 0
# d7 : 7 terms
# d6 : 3 terms
# d5 : 5 terms
# d4 : 5 terms
# d3 : 5 terms
# d2 : 4 terms
# d1 : 3 terms
# d0 : 5 terms

Well, that doesn't look too promising.. lots of minterms, and we're planning on adding INV0 as a 6th input and adding the "flips" could double the minterms per bit, and the 16v8 is limited to 8.
Overall, that seems like a lot of data for a pretty sparse set of lines, but that's really because the optimization was a little dumb.

1700667056676.png

The final color generation NANDs the straight lines with the perspective lines from the interesting part of the circuit, so what we really care about is the 0s, not the 1s. The GALs have polarity control with XORs on the output anyway, so it could be neater to do the logic for the 0s then flip:

Code:
$ cat ic1.sub | inv | bindmpesp 5 8 | espresso | esp2vlog -gal - > /dev/null
Using address width = 5, data width = 8, shift = 0
# d7 : 4 terms
# d6 :  terms
# d5 : 1 terms
# d4 : 1 terms
# d3 : 1 terms
# d2 : 1 terms
# d1 :  terms
# d0 : 1 terms
That makes way more sense. -- 9 minterms low out of 256 for the 10 lines on the screen (2 minterms were combined into 1)

Some quick C code to generate a 64x8 image with inv0 as the high bit, inverting HD when set and reversing the order of the data should give 5 minterms for d7 & d0 and 2 for d5-d2.

Crunching through it (and putting out7 into the equations as an enable rather than relying on the MUX) gives:
Code:
GAL16V8
ic1a

NC  en  inv ahh ahg ahf ahe hd  NC  GND
NC  d0  d1  d2  d3  d4  d5  d6  d7  VCC

; Logic
/d7 =  inv &  ahh & /ahg & /ahf &  ahe & /hd & en
    + /inv &  ahh &  ahg & /ahf &  ahe & /hd & en
    + /inv &  ahh & /ahg &  ahf &  ahe & /hd & en
    + /inv & /ahh &  ahg &  ahf & /ahe &  hd & en
    + /inv & /ahh & /ahg & /ahf &  ahe &  hd & en

/d6 = GND

/d5 =  inv &  ahh &        /ahf & /ahe &  hd & en
    + /inv & /ahh &  ahg & /ahf & /ahe & /hd & en

/d4 =  inv &  ahh & /ahg &  ahf &  ahe & /hd & en
    + /inv &  ahh &  ahg & /ahf & /ahe &  hd & en

/d3 =  inv &  ahh &  ahg & /ahf & /ahe & /hd & en
    + /inv &  ahh & /ahg &  ahf &  ahe &  hd & en

/d2 =  inv & /ahh &  ahg & /ahf & /ahe &  hd & en
    + /inv &  ahh &        /ahf & /ahe & /hd & en

/d1 = GND

/d0 =  inv &  ahh &  ahg & /ahf &  ahe &  hd & en
    +  inv &  ahh & /ahg &  ahf &  ahe &  hd & en
    +  inv & /ahh &  ahg &  ahf & /ahe & /hd & en
    +  inv & /ahh & /ahg & /ahf &  ahe & /hd & en
    + /inv &  ahh & /ahg & /ahf &  ahe &  hd & en

DESCRIPTION
Inputs:  en inv ahh ahg ahf ahe hd
Outputs: d7 d6 d5 d4 d3 d2 d1 d0
en == out7

...with the expected # of minterms per bit, eliminating some logic from the design.

(equation terms lined manually for clarity)

Note that ahg is missing from terms in d2/d5, hence 9 minterms not 10.
 
Last edited:
Now for the interesting part:

The low horizontal count bits aren't present on the 40-pin so some of them are generated here for internal timing:

1700671295149.png

Some clocks to start with:
IC6:4 = HB
IC6:5 = HC
HLoad = ~(Hx & HC & HB & HA) from the main board

1700671421097.png
HC clocks this counter.
When the count reaches 0xFF, CO goes high and on the next rising edge of HC it loads a new initial count from the ROM -- interesting.

1700671523913.png
/VBL' resets this counter to 0, then HB (falling edge) clocks this counter only when the carry out from the other counter is high, so the first counter is controlling how we step through the ROM.

1700671714293.png

The output of the ROMs also gets clocked into some shift registers, but HLOAD is once again gated by the carry out on the counter.
Otherwise the shift registers just keep shifting out the 1's that are shifted in when there's no load signal.
(Once again there's 2 shift registers to handle cocktail flip).

Once we throw all this crap into a timing diagram, the operation (on carry) is a little more clear:
1700677644755.png

When the IC11/14 counter counts up to 0xFF, CO goes high, and on the next rising HB edge the address increments.
The high CO allows the /HLOAD to pass through the NAND, allowing the shift registers to load the data from the odd address.
The following rising HB edge increments the address again, and on the following HC rising edge the IC11/14 counters load data from the even address.

So what's this all mean?
Odd addresses hold the (sparse) data to load some zeros into the '166. Even addresses hold an (inverse) count how how many all 0xff bytes get shifted out before the next byte with a zero.

...which is an primitive type of run-length encoding (RLE).

Back when this was made bigger ROMs cost significantly more. Without encoding, they'd need a 2764 ROM and a 13-bit counter to address it. With the encoding, the data fits into a 2716 with an 11-bit address counter.

What's even cooler is they padded the encoding a bit so it fits *exactly* into the 2716. For cocktail mode, the address counter just counts backwards (and the shift registers shift backwards) since the distance between 'on' bits in the grid is the same either forwards or backwards.

Now ROMs all pretty much cost the same, and we'd want to eliminate the 2nd shift register and mux to make the board smaller, but this was a nice feat of engineering for the time.
 
To make sure the encoding is properly understood (and to verify the redesign), I threw some simple C together to parse out the data and dump it as text:

1700678484567.png

Someone probably spent a LOT of time to get that right, for a relatively stupid effect.

Unfortunately, there's a stray bad bit, but I'll clean that up later.
1700678619212.png
 
So if the encoding works well, why not put the other lines into the image?
Well we'll need our own encoder to play with that.
We know the image was padded so it'd work either forwards or backwards, but how big is the unpadded image?

Code:
$ ./hackic12b > starsx
Length: 06da

That's significantly smaller than the 0x800 length, so we could change some bytes in the image (each change adds 2-4 to the length)

What happens if we add the other lines back in:

Code:
$ ./hackic12b > starsx
Length: 11e4

Ouch... 0x1000 we could live with but the extra 0x1e4 bytes requires a larger ROM and the 13th address bit requires another counter.

So what can we do do pare that back a bit?

1700837929564.png

For starters, the top horizontal line starts a new byte, so if we shift that line line down, we should save (at least) 2x192 bytes.
This equates to munging the ic1 data a little since just moving the tip line would compress things a bit, we'll move the top 4 lines each down one:

Code:
$ ./hackic12b > starsx
Length: 1064

That's a little better but still doesn't quite do the trick.
Perspective could make the back lines darker, so what if we only do every other pixel on the rearmost horizontal lines:

Code:
$ ./hackic12b > starsx
Length: 0fdc

That'll fit, so let's go with it:
(Only doing the top 2 lines wasn't enough)

1700838251620.png
 
Anyways... long story short, since I'm tired of writing:

By re-encoding the BG to include the lines, we get rid of the PROM and the IC4 and IC7 shifters. As stated before, this requires a bigger ROM -- a 2764 for both normal and flipped.

With the encoded data now including the lines from the prom, there are no runs of blank bytes longer than 16, so the high nybble of every count is always 0xf, so the IC11 counter can be dropped from the design.

By putting both forward and backward encodings in the ROM, IC13 is eliminated, and the only purpose left for IC8 is blanking. If we invert the data, OUT7 can be used to clear the shifter to blank it, so IC8 can go.

Since we now only need to count in 1 direction, the 191s can be replaced with 161s, which has a positive count enable, so the 7404 gate to invert ~VBL for enable the 191s is unneeded

Since there's only 1 source of bits, the output NAND is unnecessary (and we don't want the inversion anymore anyway).

The design still needs 2 NANDs and 3 inverters, which can't be crunched into 1 chip.

So let's use a NAND as an inverter and put a 7486 instead of the 7404, leaving us 2 extra XORs.

With the 2 XORs, we can throw in a couple 164s to make a PRBS15 generator, and then AND bits from the LFSR with the shifter data to make the background lines 'sparkle' in different colors instead of being just blue:

That leaves a nice little ~3x4" PCB that's way cooler that the original ! (Or just leave off the 164s and 08, and add a single jumper to make it look original).

1700960150318.png
 
Just some final bookkeeping:
The encoder needed to be rewritten to put out non-inverted data, and to output the output the cocktail-flipped data.
The decoder needed to be tweaked to draw based on non-inverted data.

(Extra crud on the bottom is the unused 10 0xff bytes at the end of each file -- decoded as 5x [0xff = 0 blank byes + 0xff byte].

Final results:
Code:
$ cat newic12f | ./hackic12x  > starsfwd
x = 5, y = 192, add = 1000
1701096509906.png
Code:
$ cat newic12b | ./hackic12x  > starsbak
x = 5, y = 192, add = 1000
1701096471107.png
 
...thread on this design continues with Space Panic:
 
Back
Top Bottom