During the summer of 2019 (Year 1 bc, (before covid)), one of our beloved customers sent us a pinball that was in a very poor condition. They wanted us to refurbish the electronic section. It was a very rare pinball of an Italian brand, called "Bell Games".
This pinball is probably named "Movie" and is actually a Gottlieb's Hulk mod. You can read the story about this Here Besides the original backglass had likely been broken long time ago, and replaced by some ugly "Roger" one.
Pinrepair's Clay Harrell's Bible Here
At first, the repair operation were going beating drums. One just had to read the nice Clay's manual step by step, and change what it needed to be changed. You might have difficulties to find 7448 7-digit chips, but we happen to have a bunch of them here in Vittel. The only problem was to remember where they were hidden in the lab. But it was not time to tidy up. So many things to do and so little time... (This hard to find 7448s gave us the idea of developping a clone version of 10788PAs that integrates all the logic glue including 7448s. This is an ongoigng project at the moment)
So far, so good. But suddenly, trying to fix some targets that were not reacting, we were stuck. That was not looking good at all when we fell here in the manual: "The story ends here"...
The manual was perfectly right at this time. It was very hard, if not impossible, to find a working A17xx. It is even worse if you need a specific version such as an A1752CF. The only way to get one actually is to unsolder one of these jewels by scavenging an original CPU board. You will need to check that it is still good first. Such an in situ checking is not an easy job, unless the entire board is OK. Why would you unsolder such a device if the entire board is working correctly? This is a vicious circle. Hence, if you eventually find a working PPS4 device on the market, there is a high probability that you soon discover that it was a dodgy one.
We refused to give up. Why would not we recreate a device like this? Is not it a very old architecture that would be very easy to copy on these days? Actually there are tons of reasons. The most important reason for going to a solution of emulation instead is that the technical documentation about Rockwell PPS4 components is rather terse. The second other reason why it is madness just imagining to make PPS4 clones, is the power supply voltage. PPS4 are powered through VSS=+5V, VDD=-12V. That is big. This is also a reason why these chips fail very often. The least short circuit on the wrong pins and this is the catastrophe. Moreover, 17V is often to much for the delicate circuits of our 21st century, where voltages are decreasing years after years. 1,2V is a common value today. Then, finding MOSFETs that holds such voltage is not easy, nor cheap.
By these days, there are numerous turnkey solutions of emulation for Gottlieb® system1 CPU boards.
For example:
Janin's board
Nevertheless, we decided to give it a try. We started by studying the original A17xx component The compilation of our findings is available here
At first glance, one might think that emulating a 50-year old PPS4 is not going to be complicated. The cycle for such a device is 200KHz. One might think that using a standard Microchip PIC will make the deal and that's it! Well... Looking at the clock system will rapidly prove the contrary. You will need to detect phases whose duration is about 279ns tick more or less. Using the interruption lines of an MPU would be an utopia in these conditions, even with a powerful one. Hence there is no other choice than to turn oneself towards a charming FPGA.
FPGAs are virtually as fast as you want. Its only Achilles heel resides in its initialization time. Can be hard to manage for hard real-time applications. Fortunately this is not a problem in our case, because in the old times the reset signal could reach 100ms easy! This is enough time for our FPGA to read the tiny bitfield we have. Next question... Which FPGA are we going to choose? Of which brand? We had some good experience with Altera when we were working for RenaultF1 team in 2001-2006. But there was some doubt concerning the dev IDE. Is it still free? Is it still a nightmare with licencing, even though it was free? Because, when developing with FPGAs, time flies by... And then, in the glimpse of an eye, your licence file has expired, and you need to start over, and you don't remember from which site you got it last time, and you don't have time to waste with these things, and bloody hell, let us select something robust. Because yes, these pieces of software are often full of flaws, we don't know why, but this is it.
There were too many questions with Altera, and moreover business evolves rapidly in this field of electronics... Altera is no more Altera exactly, but something related (or drown in) to Intel. Lattice is interesting, but once again you need paid licences to fully get the benefits of Diamond, their Application for programing FPGAs. What do you have eventually? Xilinx? Yes we chose Xilinx, which became AMD in the intervall, which is not necessarily good news. OK, the IDE, whose name is ISE, is not maintained since 2013... It does not work with Windows 11, there are a lot of bugs, it is far from perfect, and should we have to choose again it is not sure if we would go again for Xilinx. But well, this is the cheaper solution when you look at farnell and order the answer by increasing cost. So we went for Xilinx Spartan3 XC3S50 in a QFP100 package (same pinout as XC3S200, if you need more gates) Easy to solder, when you are used to it. Easy to program. Friendly FPGA.
In terms of clock frequency, the minimum requirement is twice the one of the crystal at 3,57Mhz. In prevision of future potential enhancement we went for a quartz at 50MHz. This value is large enough for our needs, and slow enough to be able to manage the PCB layout.
Cloning an A17 device is the most complicated task compared to any other PPS-4 components. Why? Just because A17s contain 3 subsystems and each will use the data bus differently. Which means that all 8 lines of data are bidrirectional. This is the reason why 3 boards are required to achieve the cloning (plus the socket). All other devices of the series may be cloned with only 2 boards.
Please, remember that original spider chips are essentially based on high side P channel MOS transistors. Moreover, they are powered in the range [0V, -17V]. This antique combination of data makes it impossible to find equivalent components. SN7407 for example would not be suitable because they are low side, or any other modern buffer, etc..
This is why the A17 clones are a little bit more expensive, and more complicate.
In a PPS4 architecture, every component of the system is synchronized through 2 clock signals named A and /B. This architecture is described here
In VHDL, a specific type is defined to determine in which phase we are for a given slice of time :
type pps4_ph_type is (idlexx, idle00, idle01, idle10, idle11, phi1A, phi1, phi2, phi3A, phi3, phi4);
This defines a state machine. idlexx, idle00, idle01, idle10, idle11 serve for indeterminate phases, especially during reset. Once locked on a specific phase, the state machine should go in its predeterminate chaining ...phi1A, phi1, phi2, phi3A, phi3, phi4, phi1A, phi1, phi2, phi3A, phi3, phi4...
clkgen is defined as a process returning 2 values: pps4_phi is the current clock phase, and diagclk for diagnosing clock signals integrity. clkgen takes 2 signals on input: clk A en clk /B
--hiclk is the 50MHz system clock
--lclk(1) holds the current A PPS4 clock signal
--lclk(0) holds the current /B PPS4 clock signal
--lclk_prev holds A&/B from the previous hiclk rising edge
--there is no need to filter over more periods, at 50MHz
--because the edges on A and /B are fast enough and
--rising times are always faster than 20ns
--
--
process(hiclk)
begin
if rising_edge(hiclk) then
lclk_prev <= lclk;
lclk(1)<=c_a;
lclk(0)<=nc_b;
if nrst = '0' then
pps4_t0 <= idlexx;
else
case lclk is
when "00" =>
if lclk_prev = "10" then
pps4_t0 <= phi1A;
elsif lclk_prev = "01" then
pps4_t0 <= phi2;
elsif lclk_prev = "00" then
null;
else
pps4_t0 <= idle00;
end if;
when "10" =>
if lclk_prev = "00" then
pps4_t0 <= phi3A;
elsif lclk_prev = "11" then
pps4_t0 <= phi4;
elsif lclk_prev = "10" then
null;
else
pps4_t0 <= idle10;
end if;
when "11" =>
if lclk_prev = "10" then
pps4_t0 <= phi3;
elsif lclk_prev = "11" then
null;
else
pps4_t0 <= idle11;
end if;
when "01" =>
if lclk_prev = "00" then
pps4_t0 <= phi1;
elsif lclk_prev = "01" then
null;
else
pps4_t0 <= idle01;
end if;
when others =>
null;
end case;
end if; --end if nrst
end if;
end process;
In the main process, one calls the clock monitoring as follows:
PHIGN : clkgen port map (hiclk=>SYSCLK, c_a=> not CKA, nc_b=> not nCKB,
nrst=>'1', pps4_ph=>pps4_phi, diagclk=>diagAnB);
The diagnostic is performed as follows: For every timeslice of 100ms, the correctness of phase chaining is verified for the last 10 cycles. The precision of timings is not checked. This means that any value for a tick will be accepted, provided that there will be at least 10 cycles running in less than 100ms. Then, a cycle as slow as 10ms will be accepted. Such slow values might be generated for debugging purposes. (The standard cycle period is 5us) The only thing of importance for the diagnostic is actually the sync and phasing of A and /B. The timeslice of 100ms is defined in order to allow for a reset of the diag every 100ms. This will manage for phase locking during reset.
In the current design of our clones, this diag parameter is directly connected to a red flash LED of the Core fpga board. Flashing at 0,5Hz means that clock signals A and /B are present and correctly sync'ed. A faster flash of this LED means that there is a problem on A, /B, or both. No light at all probably means that there is a problem of power supply on the clone.
process(hiclk)
variable v_diagcycle : natural range 0 to 5000000 := 0;
variable v_count : natural range 0 to 63 := 0;
begin
if rising_edge(hiclk) then
last_pps4 <= pps4_t0;
v_diagcycle := v_diagcycle + 1;
if v_diagcycle = 5000000 then
if v_count < 50 then
diagclk_t0 <= '0';
else
diagclk_t0 <= '1';
end if;
v_diagcycle := 0;
v_count := 0;
end if;
if last_pps4 /= pps4_t0 then
case pps4_t0 is
when phi1A =>
if last_pps4 = phi4 then
v_count := v_count + 1;
else
v_count := 0;
end if;
when phi1 =>
if last_pps4 = phi1A then
v_count := v_count + 1;
else
v_count := 0;
end if;
when phi2 =>
if last_pps4 = phi1 then
v_count := v_count + 1;
else
v_count := 0;
end if;
when phi3A =>
if last_pps4 = phi2 then
v_count := v_count + 1;
else
v_count := 0;
end if;
when phi3 =>
if last_pps4 = phi3A then
v_count := v_count + 1;
else
v_count := 0;
end if;
when phi4 =>
if last_pps4 = phi3 then
v_count := v_count + 1;
else
v_count := 0;
end if;
when others =>
v_count := 0;
end case;
if v_count > 50 then
v_count := 50;
end if;
end if;
end if;
end process;
In a PPS4 architecture, there are 2 clock signals A and /B, plus SPO which is the reset signal, and 21 bus signals: A/B1..12, W/IO, I/D1..8. They are described here
In total, there are 24 input signals to be acquired. Particular attention must be paid to clock signals, whose frequency is the highest in the system (400KHz). A voltage level translation must be applied on each of them, since PPS4 negative logic is [+5V, -12V], whether standard voltage on the fpga is [OV, +3V3]. The standard schematic is displayed on the figure below. It is duplicated 24 times. The gate resistor of 27 Ohms has a 2 reasons: a) protection of the MOSFET and b) limit the current flowing when switching the MOSFET and limit the parasitic oscillations. Can be increased to 100 Ohms Too high values will limit the MOSFET response time. So it is for the drain to ground resistor. The higher the slower, but the lower, the more power consumption it has (current 3.3/3300=>1mA, multiplied by 24 inputs, potentially)
At the beginning of our experimentation on PPS4s, we were not fully confident about our understanding of the data bus. In addition to the voltage translation problematic, there was the question of how to handfle potential unexpected bus contention and hi-Z state. Which bit is dominant? How much current must flow to impose a given state? Conversely, what is the maximum admissible current flowing to or from the bus? etc... We could not take any risk with the unique working CPU board that we had at the time.
Given all these indetermates and the fact that a Spartan3 XC3S50A in VQFP100 has got a lot of I/O pins available, we decided to go for 2 separate 8-bit channels respectively for inputs and outputs. We also thought, at this time, that +5V and HI-Z were all the same, so we neglected to design a gate MOSFET for setting the data bus of the device in HI-Z mode. This will be done in the next PCB design, since we exactly know how it works today.
Each *_33 labellized signal is connected to an I/O of the FPGA, through a 100 ohms resistor for protecting the FPGA. In early designs these resistors are not present for PCB surface sparing and fabrication simplicity. Then users must be very careful not to apply an inadvertant overvoltage on these I/Os, because in such an case the FPGA would be destroyed.
The ROM emulation is the easiest job to be achieved. Its size is rather modest : 2K words of 8 bits. We decided to use a simple IP block supplied by Xilinx. It is called IPCore and easy to configure. Please, see the corresponding documentation to get detailled information. The important setup is the .coe file where resides the 2KB ROM binary content in the requested format (see below). Once this IP instance is created and configured, there is only 1 line of VHDL to be inserted:
CFROM : A17INTERNROM PORT MAP (SYSCLK, ROM_Addr_Latch, ROM_DOUT);
Then the timing on the bus must be implemented in VHDL and transformed in bitfield for the FPGA. The things to be done:
process (SYSCLK)
...
begin
if (rising_edge(SYSCLK)) then
...
last_pps4 <= pps4_phi;
if last_pps4 /= pps4_phi then
case pps4_phi is
when phi1A =>
--nothing to do with ROM in phi1A except for diag
...
is_ROM_Device_On <= '0'; --next clock this signal will be unconditionnaly reset
if (is_ROM_Device_On = '1') then
--diag ROM: check that input=output when we have the control of bus
--and that no other source is trying to get the control
--also, in mute mode, one can have the clone in parallel
--with an original and active device (the one to be cloned)
--Then, one can verify that the clone would deliver the same value
--as the original
if nDO_int /= ID then
diagbadrom <= '1';
end if;
end if;
--
...
when phi1 =>
--from beginning of phi1 through end of phi3 device data bus must
--be hiz
nDO_int <= (others => '0'); --bus data is hiz during phi1 (and also phi3)
when phi2 =>
--nothing new here, nDO_int still hiz
when phi3A =>
--A17 samples addr for next rom delivering if we are concerned
ROM_Addr_Latch <= AB;
--check config bit. Is it us who are selected?
if RRSEL = cROMSEL then
is_ROM_Device_On <= '1';
ROM_used_at_least_once <= '1'; --for diag
else
is_ROM_Device_On <= '0';
end if;
...
when phi3 =>
--we just have to keep DO in hiz
nDO_int <= (others => '0');
...
when phi4 =>
--A17 push rom data on the bus from begin of phi4 thru end of phi1A
if (is_ROM_Device_On = '1') then
nDO_int <= ROM_DOUT; -- expose rom values as input of iobufs
--this value set continues in phi1A. bus will be deactivated in phi1
end if;
...
when others =>
null;
end case;
end if;
end if;
end if;
end process;
CFROM : A17INTERNROM PORT MAP (SYSCLK, ROM_Addr_Latch, ROM_DOUT);
You will find below, the beginning of a .COE file used for the configuration of a XILINX IP ROM block. This will close our discussion about ROM emulation. Thank you for reading up to here.
;******************************************************************
;******** u5_cf ROM Block Memory .COE file *********
;******************************************************************
memory_initialization_radix=16;
memory_initialization_vector=
81,
77,
1c,
20,
77,
1c,
40,
00,
f2,
77,
1c,
41,
17,
77,
1c,
...
We proceeded in the same way for RAM emulation as we did for ROM, even though it is a bit more complicated for RAM because a RAM access can be a read access, but also a read & simultaneous write command (these 2 operations being performed each on a different nibble). Besides, one does not access the 8 bits of data in the same way as for a ROM access. Finally, there are 2 config bits for the RAM subsystem chip selection, instead of 1 for ROM.
So we started by creating an IP RAM instance from the Xilinx IPCore library. Its size is even smaller than for the ROM section: 128 words of 4 bits.
CFRAM : A17INTERNRAM PORT MAP (SYSCLK, RAM_nRW, RAM_Addr_Latch, RAM_DIN, RAM_DOUT);
By these troubled times of supply shortage (as for 2022), it surely would have been wiser to design these ROM and RAM sub-blocks directly in pure VHDL code. That is quite simple, and would have make the entire project independent from the FPGA vendor. It is often said that proprietary IP blocks are tailored to the device for which they are destinated, optimized, more compact, etc... It is probably true, even though we have no problem of real-time nor space, and likely won't have any in our further development. But it is always worth it trying dedicated tools that are offered to you, for free.
Then the timing on the bus must be implemented in VHDL and transformed in bitfield for the FPGA. The things to be done:
last_pps4 <= pps4_phi;
if last_pps4 /= pps4_phi then
case pps4_phi is
when phi1A =>
...
--read RAM addr
RAM_Addr_Latch <= AB(7 downto 1);
if W_IO = '0' then
...
if (RRSEL = cRAMSEL) and (AB(8) = cRAMAB8) then
--yes, this is our RAM device
is_RAM_Device_On <= '1';
else
is_RAM_Device_On <= '0';
end if;
else
is_RAM_Device_On <= '0';
...
end if;
when phi1 =>
nDO_int <= (others => '0'); --bus data is hiz during phi1 (and also phi3)
when phi2 =>
--from beginning of phi2 till end of phi3A we have to drive
--RAM D1..D4 or I/O D1..D4 or nothing
if is_RAM_Device_On = '1' then --RAM selected (during phi1A)
nDO_int (4 downto 1) <= RAM_DOUT; --RAM_DOUT is read from CFRAM(RAM_ADDR_Latch)
--to be continued in phi3A part dedicated to ram,which means that the only
--thing to do is to rest T lines to 1 at the
--beginning of phi3. That's very simple in fact.
...
end if;
when phi3A =>
--A17 samples addr for next rom delivering
...
--RAM device selected? (tested during previous phi1A)
if (is_RAM_Device_On = '1') then --RAM_nRW must be 1 if w_io is also 1
--do we have to write to A17's ram?
if W_IO = '1' then --it is a write
RAM_DIN <= ID(8 downto 5);
RAM_nRW <= "1"; --we will have to reset this signal to 0 the sooner the better
--need to be set for at least 1 or 2 sysclk, (2 to be sure)
--but 1 should be enough
--actually we will reset it at the next phi, that will be fine
end if;
--diag RAM (optional))
if nDO_int(4 downto 1) /= ID(4 downto 1) then
diagbadram <= '1';
end if;
...
end if; --endif of if is_RAM_Device_On else is_IODevice_On
is_RAM_Device_On <= '0';
...
when phi3 =>
--we just have to put DO in input mode
nDO_int <= (others => '0');
RAM_nRW <= "0";
when phi4 =>
...
RAM_nRW <= "0"; --just in case, but it is not necessary
when others =>
null;
end case;
end if;
...
end if;
end process;
CFRAM : A17INTERNRAM PORT MAP (SYSCLK, RAM_nRW, RAM_Addr_Latch, RAM_DIN, RAM_DOUT);
There are 16 pseudo-independent I/Os. We use the term "pseudo" because they are not fully independent. Typically, they are all powered or not at the same time. There is actually a unique command MOSFET which powered all the 16 IOs (5V) when they are configured as output (SOS). Apart from this they are genuinely independent.
Nevertheless, it would not have been sensible to use 16 true port from the FPGA. We decided to make a bus multiplexing of 2x8 bidirectional bits. For the FPGA, we make use of the IOBUF building block (x8: IOx_i_33). Then this bus is connected to 2x74244 for reading inputs (behind 16 input MOSFET), and to 2x74573 for output (before 16 output MOSFET
Does it look complicated? Even too complicated? Direct connection to the FPGA is out of the question, for it would require 16 input port pins plus 16 output port pins. Moreover, there is the problem of voltage translation and disabling outputs by program. Then, there is the question of mux or not mux... mux of 2x8bits looks optimum. Finally, the circuit counts 33 MOSFETs, 8 FPGA pins, 2 TTL latches and 2 TTL line buffers.
if last_pps4 /= pps4_phi then
case pps4_phi is
when phi1A =>
...
RAM_Addr_Latch <= AB(7 downto 1);
if W_IO = '0' then
--all IO devices to be staying off
else
--an IO device is called. We will know if it's us
--by reading rom data value
if (ID(8 downto 5) = cDEVNUM) then
--yes this is our io device id...
--get the command from ID1: 0:SES or 1:SOS
--command which will be executed at next phi2
IOCmd <= ID(1);
IONum(3 downto 0) <= AB(4 downto 1);
IOBMl <= AB(6 downto 5);
is_IODevice_On <= '1';
end if;
end if;
--
when phi1 =>
when phi2 =>
if is_RAM_Device_On = '1' then
...
elsif is_IODevice_On = '1' then
--we are to load accumulator to value of IO(IONum)
if IOBMl = "00" then
--let's activate the reading of IOx(x=IONum)
--then, we will assign nDO_int (4) to not this value
callloadio <= '1';
end if;
end if;
when phi3A =>
...
--RAM device selected? (tested during previous phi1A)
if (is_RAM_Device_On = '1') then --RAM_nRW must be 1 if w_io is also 1
...
elsif is_IODevice_On = '1' then
--lets read the accumulator from DO8
--to get the param
--IOCmd contains ses or sos
if IOCmd = cSES then -- cste cSES set at 0 which is
--D04=1 : enable all outputs
--DO4=0 : disable all outputs ==> all Ts configured as input (T=1)
--(T=0: outputs, T=1: inputs)
--CtrlIODIR=1: IOs as input, CtrlIODIR=0: IOs as output
if ID(8) = '0' then --set IOs as outputs
--That means : set IOs as output if out from fpga=1
--When fpga out is 0, out mosfet is off then we shall be in input mode
--in order to catch the state of the pin coming from the outside
--Thus, we have to set T(x)<=0 if OIO_VEC=1 and T(x)<=1 otherwise
SELIODir <= '0'; --allio are input at reset. 0 is output, 1 is input
else
--disable all outputs
SELIODir <= '1'; --allio are input at reset. 0 is output, 1 is input
end if;
else
--cSOS
if IOBMl = "00" then
-- IOx_out <= not ID(8); -- obsolete old hw
iomem := unsigned(IIO_MEM);
iomem(to_integer(unsigned(IONum))) := not ID(8);
IIO_MEM <= std_logic_vector(iomem);
--we need to initiate the latching (2 cycles at 50MHz should be enough)
--signal will be reset after counting 2 clock cycles
IOLatchSig(1) <= not IONum(3);
IOLatchSig(0) <= IONum(3);
TIO_ENABLE_SIG <= '0'; --set IOs internally as outputs
if IONum(3) = '1' then
IIO_VEC <= std_logic_vector(iomem(15 downto 8));
else
IIO_VEC <= std_logic_vector(iomem(7 downto 0));
end if;
end if;
end if; --endif of if cSES
end if; --endif of if is_RAM_Device_On else is_IODevice_On
when phi3 =>
...
when others =>
null;
--state machine
--
--
if (rising_edge(SYSCLK)) then
--start of latch management of IO on write
--IOLatch is maintained to 1 for 2 clock cycles
if IOLatchSig /= "11" then
IOLatchTime := IOLatchTime +1;
if IOLatchTime = 4 then
IOLatchTime := 0;
IOLatchSig <= "11";
TIO_ENABLE_SIG <= '1'; --set internal IOs as inputs (IOx bus)
end if;
end if;
--end of latch management of IO on write
--start of IO reading
if callloadio = '1' then
callloadio <= '0'; --this is a state machine
callreadio <= '1';
--disable latches
IOLatchSig <= "11";
--set T sig of fpga in read mode
TIO_ENABLE_SIG <= '1'; --set IOs internally as input
if IONum(3) = '1' then
IOSelSig <= "01"; --activate IO8..15 bank
else
IOSelSig <= "10"; --activate IO0..7 bank
end if;
end if;
if callreadio = '1' then
callclrdio <= '1';
callreadio <= '0'; --this is a state machine. reset
--final step. We assign the accumulator with the right value
nDO_int (4) <= not OIO_VEC(to_integer(unsigned(IONum(2 downto 0))));
end if;
if callclrdio = '1' then
IOrdTime := IOrdTime +1;
if IOrdTime = 4 then
IOrdTime := 0;
callclrdio <= '0';
--final step. We close enble sig
IOSelSig <= "11"; --deactivate IO0..7 bank
end if;
end if;
--end of IO reading
Pinout A17xx
An additional feature in the PPS-4/2 CPU is an expanded discrete output capability. This capability is achieved by using the same instruction (DOA) as in the PPS-4 CPU but providing more I/O control. In the PPS-4 the DOA instruction causes the contents of the accumulator (A) to be transferred to an output buffer and then retained until another DOA instruction is executed. The contents of this buffer controls the output drivers. In the PPS-4/2 CPU this same function is performed but, additionally, the contents of the X register are transferred to a similar second set of buffers and drivers (DIO) so that the DOA instruction causes 8 bits to be available to external devices.
On constate sur ce tableau que le pinout des principaux signaux ne suit pas de logique particulière.