Sunday, 18 January 2015

A New Game Console Project - Part 1

Wow. A lot of time has passed since the last post.  I have been busy though.  I have built the bones of an ARM-based game console.  I'll describe how I got to this point (a pointless spinning cube) over the last one and half years of sporadic effort.

A spinning cube...

STM32F4 Discovery Board

Firstly, though here are my influences.  You should certainly check out these amazing projects - they might inspire you to go down this crazy path.


A Quick History

Take 1: The AVR-based Approach

Originally I wanted to build a Z80 based computer with nice 8-bit AVR based GPU - a software GPU.  The idea for the GPU came after reading about the Lazarus64 project.  Brad (from LucidScience) managed to breadboard an ATMEGA324P, 2 SRAMs, switching logic and delay lines for NTSC colour generation.  It had the nice feature that it could switch which SRAM was connected to the CPU to achieve a hardware based double-buffered frame buffer.

I started thinking how it would be neat to use a VGA output since VGA is simple to generate. I wanted to have a widescreen 16:9 resolution so that my pixels would be square on any modern TV.  I settled on 480x270 which is nearly 16:9 and fits in a 128K SRAM.

I progressed to a reasonably advanced state with the circuit and PCB layout.  My design had two AVRs each with their own SRAM.  They would simply take turns rendering.  Another goal was to prototype at home and this meant I wanted a single-side board that I could make on my Zen Toolworks CNC router.  This constraint basically killed this design.  Well, in theory the design was sound but in practise it killed my patience due to the complexity involved in making changes.

Take 2: The AVR+FPGA-based Approach

I thought it might be easier to place a small FPGA as the centre of the design as hub for the SRAM, AVR and VGA port.  I bought this:

Altera Cyclone II

I still think this was a good move.  I could plug in pieces of my computer in chunks as I built them.  I set about making an SRAM adapter board on my CNC that would plug into the board above.  This contained the finest pitch routing I have ever attempted on my CNC and necessitated the use of a probe to correct for slight deviations in height in the PCB blank.  Here is the result:

For those interested, I used the following probing software with my Eagle, PCBGcode and LinuxCNC setup: AutoLeveller.  It is a terrific piece of software.

The board above took a 10ns ISSI 512KB SRAM in a TSOP-44 package.  I think the board turned out really well.  I soldered it up, plugged it in and toasted both my SRAM and a couple of pins on my FPGA. That is what happens when you have a solder bridge underneath the SRAM.  I should have checked it I know.  I didn't.  Anyway I ordered another FPGA board.

So, it was time time to write some VHDL code.  Since I had an FPGA, I thought I might as well move the VGA signal generation to it.  I had not written VHDL since university some 15 years ago.  Once I remembered that I was not supposed to be writing code but rather describing hardware, things went more smoothly.  Fortunately I found some VGA generation VHDL that I modified and plugged in appropriate 50MHz clock for a nice 800x600 mode:

 LIBRARY ieee;  
 USE ieee.std_logic_1164.all;  
 ENTITY vga_controller IS  
   h_pulse : INTEGER  := 120;  --horizontal sync pulse width in pixels  
   h_bp   : INTEGER  := 64;  --horizontal back porch width in pixels  
   h_pixels : INTEGER  := 800;  --horizontal display width in pixels  
   h_fp   : INTEGER  := 56;  --horizontal front porch width in pixels  
   h_pol  : STD_LOGIC := '1';  --horizontal sync pulse polarity (1 = positive, 0 = negative)  
   v_pulse : INTEGER  := 6;   --vertical sync pulse width in rows  
   v_bp   : INTEGER  := 23;  --vertical back porch width in rows  
   v_pixels : INTEGER  := 600;  --vertical display width in rows  
   v_fp   : INTEGER  := 37;  --vertical front porch width in rows  
   v_pol  : STD_LOGIC := '1'); --vertical sync pulse polarity (1 = positive, 0 = negative)  
   pixel_clk : IN  STD_LOGIC; --pixel clock at frequency of VGA mode being used  
   reset_n  : IN  STD_LOGIC; --active low asynchronous reset  
   h_sync  : OUT STD_LOGIC; --horizontal sync pulse  
   v_sync  : OUT STD_LOGIC; --vertical sync pulse  
   disp_ena : OUT STD_LOGIC; --display enable ('1' = display time, '0' = blanking time)  
   column  : OUT INTEGER;  --horizontal pixel coordinate  
   row    : OUT INTEGER;  --vertical pixel coordinate  
   n_blank  : OUT STD_LOGIC; --direct blacking output to DAC  
   n_sync  : OUT STD_LOGIC); --sync-on-green output to DAC  
 END vga_controller;  
 ARCHITECTURE behavior OF vga_controller IS  
  CONSTANT h_period : INTEGER := h_pulse + h_bp + h_pixels + h_fp; --total number of pixel clocks in a row  
  CONSTANT v_period : INTEGER := v_pulse + v_bp + v_pixels + v_fp; --total number of rows in column  
  n_blank <= '1'; --no direct blanking  
  n_sync <= '0';  --no sync on green  
  PROCESS(pixel_clk, reset_n)  
   VARIABLE h_count : INTEGER RANGE 0 TO h_period - 1 := 0; --horizontal counter (counts the columns)  
   VARIABLE v_count : INTEGER RANGE 0 TO v_period - 1 := 0; --vertical counter (counts the rows)  
   IF(reset_n = '0') THEN --reset asserted  
    h_count := 0;     --reset horizontal counter  
    v_count := 0;     --reset vertical counter  
    h_sync <= NOT h_pol; --deassert horizontal sync  
    v_sync <= NOT v_pol; --deassert vertical sync  
    disp_ena <= '0';   --disable display  
    column <= 0;     --reset column pixel coordinate  
    row <= 0;       --reset row pixel coordinate  
   ELSIF(pixel_clk'EVENT AND pixel_clk = '1') THEN  
    IF(h_count < h_period - 1) THEN  --horizontal counter (pixels)  
     h_count := h_count + 1;  
     h_count := 0;  
     IF(v_count < v_period - 1) THEN --veritcal counter (rows)  
      v_count := v_count + 1;  
      v_count := 0;  
     END IF;  
    END IF;  
    --horizontal sync signal  
    IF(h_count < h_pixels + h_fp OR h_count > h_pixels + h_fp + h_pulse) THEN  
     h_sync <= NOT h_pol;  --deassert horizontal sync pulse  
     h_sync <= h_pol;    --assert horizontal sync pulse  
    END IF;  
    --vertical sync signal  
    IF(v_count < v_pixels + v_fp OR v_count > v_pixels + v_fp + v_pulse) THEN  
     v_sync <= NOT v_pol;  --deassert vertical sync pulse  
     v_sync <= v_pol;    --assert vertical sync pulse  
    END IF;  
    --set pixel coordinates  
    IF(h_count < h_pixels) THEN --horizontal display time  
     column <= h_count;     --set horizontal pixel coordinate  
    END IF;  
    IF(v_count < v_pixels) THEN --vertical display time  
     row <= v_count;      --set vertical pixel coordinate  
    END IF;  
    --set display enable output  
    IF(h_count < h_pixels AND v_count < v_pixels) THEN --display time  
     disp_ena <= '1';                 --enable display  
    ELSE                        --blanking time  
     disp_ena <= '0';                 --disable display  
    END IF;  
   END IF;  
 END behavior;  

This produced a a stable 800x600 image which I took the worst photo in world of below:

Anyway, it was time for the wheels to fall off this idea too.  So, how did this one fail?  I changed the timings to generate my desired 480x270 mode and plugged the contraption into the VGA port on my TV.  The result: NOTHING.

So it turns out that TVs are far more pickier about modes that PC LCD monitors.  Both my Sony LCD and Pioneer Plasma will accept 640x480, 800x600, 1920x1080 and other popular modes.  480x270 didn't work on either.  I decided at this point to have bit of rest.

Take 3: The ATMega328 and SVideo/Composite-based Approach

About mid-2014 I started becoming interested in the Uzebox and started wondering if I could do something similar.  The Uzebox is game console.  It was around this time that I started becoming interested in retro gaming and the Uzebox is all about retro gaming.  It still amazes me that Minecraft (well Mojang) was sold for $2.5 billion to Microsoft .  Minecraft had pixelated graphics by design.  Pixel art can be very compelling clearly and I think that same spirit is found within the amazing Uzebox community.  Go and check out the Uzebox Forum - amazing stuff going on in there.

So my requirements were beginning to change at this point to something like the following:
  • Must be largely a single-chip design except for the TV encoder
  • Must support SNES game pads which are available cheaply on eBay
  • Must support 256 colours
  • Must support a 16:9 resolution
  • Must have sufficient resolution to be fun on a 42-inch screen but not too high that it isn't fun to make graphics for.
  • Must support audio output of some form.
  • Must support the ability to execute from RAM.
The last requirement gives me the ability for the console to still act as a general purpose computer.  I still haven't given up on that.

The Uzebox uses a Analog Device AD725 PAL/NTSC encoder chip to produce a 4:3 video signal from the AVR. I wanted to do a 16:9 widescreen mode but I remembered that the first DVD players (pre-HDMI) supported widescreen modes over composite video.  I dug into it the timings more and then realised that because PAL is analog, I can push out as many pixels as I like per line to achieve a widescreen mode.  Well, you are limited by the bandwidth of the AD724 and the TV's decoder though.  In practise I think this is around 4-5 MHz for PAL so my mode is achievable.  Essentially a 8-10MHz pixel clock is the upper limit.  In another post I might elaborate on the why the pixel clock can be double but take my word for now.

I started to look at other modes that might fit nicely in a PAL timing window and found that 416x234 required a nice 8MHz pixel clock that is exactly half of an Arduino clock frequency.  I had an Arduino sitting around so I though I might hook them up.  Unfortunately I couldn't work out how to achieve 8MHz with an external SRAM.  I felt the Arduino's 2K RAM wasn't enough to make the kinds of games I wanted to make.  Unfortunately there isn't enough time to address an SRAM and read data from it during scan out.

So then after reading about the PIC32-based Maximite and always having an interest in MIPS since learning about it at university I bought a PIC32 dev board called a UBW32.  I mad a small board for the AD724 (like the AD725) and ended up with this monstrosity:

Note that the AD724 is on the backside as it an SMD device

More to follow in another post...