Sunday, April 14, 2024

Magnavox Odyssey 2

 

Image is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license.



The Odyssey 2, known as the Philips Videopac G7000 in Europe, was a cartridge based videogame console developed by Magnavox and released in 1978. My first exposure to this machine was back in the late 70's when one of my friends had one. In 1996 I wrote the first emulator of the system, O2EM. At the time there was no publicly available information on the system so I ended up having to reverse engineer it. Eventually the project was taken over by Andre de la Rocha who continued to update it. 

Processor: Intel 8048

Speed: .36 MHz clock speed

RAM: 128 Bytes + 64 Bytes in processor

ROM: BIOS: 1K (Internal to processor)
            Cartridges: 2K address space, 2 bank select inputs for up to 8K bank switched carts

Sound: Intel 8244 custom Audio/Video IC
             24-bit shift register, clockable at 2 frequencies; noise generator

Graphics: Intel 8244 custom Audio/Video IC

Graphics RAM: 256 bytes of video control registers
Colors: 16
Sprites: 4, 8x8 pixel, 1 color
Background Graphics: 9x8 grid of lines or blocks. Each segment individually controllable
          28 Character objects from predefined internal character set

I/O: 2, 8 direction digital joysticks with one fire button

        48 key QWERT membrane keyboard with reset button        

Ports: Cartridge ports doubles as a system expansion port

           Early versions of the system had joystick ports, but later systems hardwired the joysticks

The system was a big leap forward compared to the dedicated "pong" console Magnavox had been producing up to that point, but was not as flexible as the Atari 2600. The graphics system is quite limited with the four 8x8 sprites being the only objects where the programmer had complete control of the shape which made if difficult to port existing games to the system. The 8048, which was designed to be an embedded controller, is also a little tricky to use for game programming. It has a very lean instruction set, for example it has no subtraction instructions, and no comparison instruction. 

I have recently setup a development system that will allow me to run software directly on the hardware so I can study the operation of the system in more detail. This will allow me to update the technical documentation I wrote back in the 90s and possibly make some improvements to O2EM.

If you are interested in more information about the system I recommend this Facebook group which has a good collection of available O2 documentation and example code.

Odyssey 2 and Videopac Homebrew Games





Thursday, February 22, 2024

Centurion FCC Shift and Carry

This is one of a series of articles about the Centurion FFC board's custom CPU. In this post I will look at the shift and carry logic. I will start with the shift logic. 

In the 2901 chip slice there are two places where data can be shifted, as it enters the RAM and as it enters the Q registers. On either end of the shift registers is a pin that can be used to shift a bit into the register or view the bit being shift out. Since the register can be shifted in either direction, these pins can behave as either inputs our outputs depending on the direction of the shift. 


These shift pins are cascaded between the two 2901s to create 8-bit shift registers with either ended labeled ALU.Q0/ALU.Q7 and ALU.RAM0/ALU.RAM1.

Looking at the FCC logic the 74LS241 is used to handle these bits depending on the direction of the shift. The enables on the chip are connected to ALU control bit 7 which will be low for down shifts and high for up shifts. Depending on the direction of the shift ALU.RAM0 or ALU.RAM7 is connected to Shift.RAMout and the other to Shift.RAMin. Q is connected the same way to Shift.QOut and Shift.Qin. 



Now lets look at where these signals are used. We will start with the ShiftIns. There are two 74LS151 multiplexers that handle the ShiftIns. The one for Q is controlled by microcode bits H10-H12 and the one for RAM by microcode bits H14-H16. 


The input options are the same for both registers:

0 = Always 0
1 = Always 1
2 = The shift out from the registers, thus forming a rotate operation
3 = The shift out of the other shift registers, this allowing Q to be shifted into RAM and RAM shifted in to Q.
4,5= The inverted and non-inverted Flag Bit. See my previous post for more details on these. 
6 = The Carry flag from the ALU
7 = The Sign flag from the ALU

The shift outs go a couple different places, most of which we have covered already. First, they go to Shift In Mux which I covered above, and they go to the Flag Mux which I showed in a previous post. The final place they go is to the Carry Mux which selects in the input to the ALU Carry In signal. The mux is controlled by the MP.H84-MP.H86 microcode bits. 



The input options for the Carry Mux are:

0 = Always 0
1 = Always 1
2 = RAM Shift Out from the ALU
3 = Q Shift Out from the ALU
6 = ALU Carry Out
7 = ALU Sign



Sunday, November 5, 2023

Centurion CPU Branch/Skip Logic

 This is one of a series of articles about the Centurion FFC board's custom CPU.  In my post on the microcode sequencer I explained that branches in the code can be controlled by the Test input to the sequencer. In this post I will show where that test signal comes from. 

We will start with the Branch/Skip selector. The select inputs come from three bits (M.H35-MP.H37) of the microcode so eight different conditions can be selected. The condition then passes through an XOR gate where it is combined with  microcode bit MP.H34 which can invert any of the the conditions. Finally this passes to the Test input of the microcode sequencer. 


Now let's look at the conditions. Input zero is tied directly to +5 so allows for a branch always condition. I will cover the FlagReg input later in this post. The next four inputs are condition bits from the ALU, Zero, Carry, Overflow and Sign. The final two inputs come directly from the drive control hardware which makes it easy for the CPU to react to specific conditions in the driver controller. 

The Branch/Skip selector selects a condition that is happening on the current microcode instruction. The Mux input on the other hand allows for the checking of a condition that happened during the previous instruction. 


The Flag Mux works similar to the Branch/Skip Selector. The flag is selected by microcode bits MP.H31 - MP.H33 and then goes through an XOR gate where microcode bit MP.H3g controls whether it is inverted or not. From here is passes for 74LS74 flip flop which latches the flag value using the system clock. The outputs of the flip flop go to the Branch/Skip Selector but they also go to the Shift and Carry muxes where they can become inputs to the ALU. I will cover this in a future post. 

Now lets look at the conditions. Condition zero, just like the branch select, is tied high so represents a branch always condition. Condition one is interesting, it is just the output of the Flag Mux, so this allows the condition to be carried forward one more cycle. Conditions two and three come from the output of the ALU shift registers. Finally four through sever are condition flags from the ALU. 

Saturday, October 7, 2023

Centurion FFC RAM

This is one of a series of articles about the Centurion FFC board's custom CPU.  In this post we will look at the board's RAM. The board has 4K of RAM but it is accessed in a way that is a little different then a traditional CPU. Instead of the CPU directly addressing the RAM it uses a 12 bit index register to address the RAM before it can be read or written to. For example to write to the RAM the CPU would first write the address to the index register, it can then write data to that location in memory. There is also an option to have the index register automatically increment the address so it doesn't need to be constantly updated when access a range of addresses.

We will start by looking at the circuit for the index registers. These are composed of three 74LS161 synchronous counters. They are split into two separate registers, address bits 0-7 in the low register and 8-11 in the high register. These are loaded by setting the LoadIndexL or LoadIndexH signals, see my last post for how this works. The data being written into these registers come from the Data.Fx bus which in this case is normally from the output of the 2901 chip slice

The CEP signal to the counter comes from the MP.H63 microcode bit and will increment the counter when the master clock goes high. This bit can be set in any microcode instruction so it doesn't have to be associated with any RAM activity. 


These two chips allow the CPU to read back the current contents of the index registers. ReadIndexL reads back bits 0-7 and ReadIndexH reads back bits 8-11. The read back allows these registers to behave like an index register on a traditional processor, so when looping through a part of memory the current index doesn't need to be stored anywhere else.




The RAM is composed of two 2K RAM chips which are selected using the A11 line. During a read the ~readRAM signal enables the 74LS240 buffer at the top right to pass the data to the Data bus. During a write the data is latched into the 74LS374 at the bottom and sent to the RAM chips.


The chips in the lower right control the write timing. The 74L121 is a monostable multivibrator. When the ~writeRAM signal goes high it sets the ~Q output low which starts the write cycle. The output will stay low for 100ns which is set by the resistor and capacitor connected to the 74L121. This makes sense since the RAM chips used requires data to be active for 100ns during a write. The next part is a little confusing. The output of the 74LS121 goes to two places. First it goes to the enable input on the 74LS374 which will allow the latched data to pass through to the RAM chips. Second it goes through 4 inverters which will simply add a delay to the signal, and then to the Write Enable signal on the RAM chip. This seems backwards because when the write cycle ends you would want the to RAM go out of write mode before the latch is turned off, but its wired the other way around. It's probably not surprising since these old system tended to have some very tricky timing. 

Here is a look at some micro-code instructions that work with the RAM.


All three instructions work the same way. They enable the constant ROM to get the values that will be written, remember there is one constant byte for each micro-code instruction. The constant passed over the DataIn bus to the 2901 chip slice where the ALU applies the function D + 0 which leaves the value unchanged. The ALU of the 2901 is not impacted by the clock so the immediate value passed right out onto the Data.F bus which goes to the index registers and RAM. The Write value then controls which device the data gets written to. 


Saturday, September 16, 2023

Centurion FFC I/O Control

 


The next part of the FFC CPU we will look at is the I/O control. As shown in my last post there are two sets of 4 bits that are used to select input and output devices. The selectors can be seen in the schematic below. The one of the left control reads and the on on the right controls writes. 


On the read side both enable inputs are tied to ground so the outputs are always enabled, meaning that one of the read outputs is always enabled, but S13 and S14 are not connected to anything so these can be used if no read operation is needed in a microcode instruction. The signals connected to the read outputs are as follows:

These are used to select various registers in the drive control hardware. These are used to read status and data from the drive.

0 = ReadFS1
1 = ReadFS3
2 = ReadFS2
3 = ReadFSDR1
4 = ReadFSDR2
6 = ReadFSDR3

This input reads the DMA status

11 = ReadStatus

These are used to access the internal RAM on the board. To read the RAM the address you want to reads needs to first be put in the memory index register then the ReadRAM input can be used to read the data. ReadIndexL and ReadIndexH can be used to read back the contents of the index register. 

8 = ReadIndexL
12 = ReadIndexH
9 = ReadRAM

This inputs reads the System Register which is used by the FFC to communicate with the rest of the system.

10 = ReadSysReg

Finally this input reads from the Constant ROM. Unlike a normal CPU, the FFC's CPU cannot included data as part of an opcode so this is used to provide constant data to the CPU.

15 = ReadConst


On the write selector the enables are connected to MCClock and MasterReset. MCClock is the master clock so it probably needed for the proper timing of the writes. MasterReset is probably used to prevent spurious writes when the board is starting up. The signals connected to the write outputs are as follows:

This set of signals control writes to drive control registers. 

0 = LoadDCR1
1 = LoadDCR2
2 = LoadSSBCL
3 = LoadFSDR1
4 = LoadFSDR23
5 = WriteFloppySync
6 = LoadSSBCH
7 = LoadTSM

This one loads the DMA control register

12 = LoadCtrlReg

These signals control access to the internal RAM. Just like for read, the address to write two must first be written to the Index registers and then the RAM can be written to using WriteRAM.

8 = LoadIndexL
11 = LoadIndexH
9 = WriteRAM

I believe this output is used to trigger interrupts to the main processor in the system.

13 = LoadIPL

This output writes to the System Register which is used by the FFC to communicate with the rest of the system.

10=WriteSys

These two outputs are unconnected so can be used for instructions where not output is needed.

14=No connection
15=No connection












Sunday, April 30, 2023

Centurion FFC Microcode

The is one of a series of articles about the Centurion FFC board's custom CPU. In previous posts I explained how the program is stored as microcode. In this post we will take a look at what each bit in he microcode instruction does. In later posts I will dig deeper into some of these functions.

The first set of signals control the 2901 Chip Slice which I discussed in an earlier post


These signals select which 2901 register goes on the A and B inputs to the ALU.

MP.H50 - ALU A Select 0
MP.H51 - ALU A Select 1
MP.H52 - ALU A Select 2
MP.H53 - ALU A Select 3
MP.H54 - ALU B Select 0
MP.H55 - ALU B Select 1
MP.H56 - ALU B Select 2
MP.H57 - ALU B Select 3


These signals select the ALU source, function and destination.

MP.H60 - ALU Source Select 0
MP.H61 - ALU Source Select 1
MP.H62 - ALU Source Select 2
MP.H64 - ALU Function Select 0
MP.H65 - ALU Function Select 1
MP.H66 - ALU Function Select 2
MP.H70 - ALU Destination Select 0
MP.H71 - ALU Destination Select 1
MP.H72 - ALU Destination Select 2


The next set of signals selects the source for the bit that gets shifted into the 2901 Q register and RAM register during a shift operation. This bit could get shifted into either the high or low bit depending on the direction of the shift.

MP.H10 - Shift Qin Select 0
MP.H11 - Shift Qin Select 1
MP.H12 - Shift Qin Select 2

MP.H14 - Shift RAM In Select 0
MP.H15 - Shift RAM In Select 1
MP.H16 - Shift RAM In Select 2


These three lines select what will go into the carry input of the 2901's ALU which is used during addition and subtraction operations.

MP.H84 - ALU.Carry_IN Select 0
MP.H85 - ALU.Carry_IN Select 1
MP.H86 - ALU.Carry_IN Select 2


These signals are used to select the bit, for example Carry Out, that will be tested by the Microcode Sequencer to determine if a branch should be taken or not. Normally the branch selection is determined by the result of the current microcode instruction, but there is also a latch that records the state of a flag bit at the end of the previous instruction. The Flag Mux signals select which bit will be latched which can then be selected using Branch Select.

MP.H34 - Branch Select Invert
MP.H35 - Branch Select 0
MP.H36 - Branch Select 1
MP.H37 - Branch Select 2

MP.H30 - Flag Mux Invert
MP.H31 - Flag Mux 0
MP.H32 - Flag Mux 1
MP.H33 - Flag Mux 2


These signals control which microcode instruction gets executed next. I discussed this in detail in a previous post.

MP.H80 - Control Sequence Next Select  0
MP.H81 - Control Sequence Next Select  1
MP.H82 - Control Sequence Next Select  2
MP.H83 - Control Sequencer B8
MP.H87 - Control Sequencer B9


This set of signals controls reading and writing from various sources on the board. This includes RAM, the system register and various control and status signals. One of the outputs from these control muxes is always enabled, but some are unused so these can be specified in the microcode when no I/O operation is needed.

MP.H20 - Read Control 0
MP.H21 - Read Control 1
MP.H22 - Read Control 2
MP.H23 - Read Control 3
MP.H24 - Write Control 0
MP.H25 - Write Control 1
MP.H26 - Write Control 2
MP.H27 - Write Control 3


This signal disconnects the output of the ALU from the Data.Fx bus and connects that bus to the Data.INx bus. This allows data to be passed between parts of the CPU without the ALU being involved. For example, this allows a branch destination to be passed directly from the Constant ROM to the Control Sequencer. 

MP.H77 - DataRtoW - R/W Data Bridge


I will cover this signal in a later pot on how the RAM works.

MP.H63 - RAM Address Counter enable


This is an interesting signal. It simply goes to a test point on the board and doesn't actually control anything. I assume this was used for debugging during development. The released microcode ROMs never set this bit.

MP.H73 - TP4


The remaining signals directly control sections of the drive control electronics. 

MP.H17 - CRCEnable
MP.H13 - RDSSEL Sync Select
MP.H74 - CRCout
MP.H75 - CRCReset
MP.H76 - TSM_CSEL


 




Saturday, April 22, 2023

Centurion FFC Overview

In my last two posts I took a look at the two key components that form the heart of the Centurion FFC board's custom CPU. Now let's take a step back and take a look at an overview of the board. The FFC is actually composed of two boards, one is primarily for the CPU and the other for the drive control, but there is some overlap. 

This is the board with the drive control electronics.

This is the board that contains the drive control CPU which is the one I am going to focus on in these posts. 



Here is a block diagram I created of the CPU section. Each thick colored line is a different bus in the circuit. This may look complicated by we can look at it piece by piece to more easily understand it. 



As discussed in my last post, the 8x02 Control Sequencer provides the program counter for the microcode program and also controls the jumps and branches within the code. It outputs the MP.Ax bus which is used to address the Microcode ROM which controls the functions of the CPU, the Constant ROM which provides a single data byte for each microcode instruction and the Sequence Control ROM which controls how the sequencer determines what the next instruction should be. 

Coming out of the Microcode ROMs is the MP.Hxx bus which has the control signals that control the rest of the CPU. We will go into more details on these in later posts. 

There are two data buses in the processor, one that goes into the 2901 ALU and another that comes out of it. The one going into the ALU is shown in red and is called Data.INx. The data on this bus can come from RAM, the Constant ROM, the System Data Register, or input devices. The one going out of the ALU is in orange and called Data.Fx. The data on this bus can go to the RAM, to output devices, System Data Register, or to the Control Sequencer to allow for jumps or branches to a specific address in the microcode. 

The two buses can also be linked together through the Data Bridge. When the bridge is enabled, the output of the 2901 is disabled. This allows, for example, the constant ROM to be fed directly to the Sequence Controller to provide branch addresses. 
 
In my next post we will look at the what each bit of the microcode does.