Saturday, 29 April 2006

We have a pulse

Continuing my adventures with the XGS I decided to make use of the larger breadboard bought the other week from maplins (along with a nice pre cut set of jumper leads).

My long term plan is to build a simple games console capable of loading games from an external flash rom (possibly cartridge based on simply onboard via switching to access a handful of pre-loaded games). This is a lot more work than it sounds since the SX range of chips can only execute instructions from their internal rom, 2K on the SX28 and 4K on the SX52. There are ways around this which I'll cover at a later date.

There are a lot of hurdles between now and the simple console I'm picturing, so the first versions of the console will be limited to 2K of ROM and 144 bytes of RAM, basically the onboard rom/ram for the SX28. The SX28 (parallax micro-processor) is going to be the heart of my games console. The reason, because thats what the XGS used which means I have a boat load of reference docs, manuals and papers about it plus a local supplier and I'm already slightly familiar with SX assembly. Also, the SX28 is available in a DIL package which makes prototyping much simpler than the surface mount versions of the SX48 and SX52 (although I've a few of those ready for a future project :)

So the plan currently is to start very simple, and progressivly add more and more "features" to the console such as graphics/sounds/external flash rom & sram/joystick port etc If you've read the XGS ebook then you'll probably notice many similarities to the PICO and XGS which are been used as a base reference, however I plan to expand the feature set of the PICO much further. For example with the addition of graphics and sound hardware. How far this can be taken with just the SX28 and the limited output pins, I'm not sure. At the expensive of speed I can probably serialise a number of the systems although worst case scenario will be stepping the design up to use the SX48. Thats the rough plan anyway, I'm sure it will change as I learn more.

Step one was getting a power supply up and running, actually very easy to do, a 7805 regulator did all the hard work plus theres quite a good section on power supply regulation in the XGS ebook :) I'm still not 100% certain on how the capacitors reduce the ripple in the DC supply, well I understand the concept but don't quite understand the calculations enough to work out which frequencys are getting through and which are filtered. Something to work on in the future.

Anyhow, enough reading, heres a picture of a very basic SX28 project. In the top right of the picture can be seen the 7805 voltage regulator that takes a 9V DC supply and produces a 5V supply, along with the smoothing and noise filtering capacitors to clean up the 5V output.



Located at the top/middle of the image is the SX28, the heart of this project. Pin one is the top right pin with pin 28 been the bottom right pin. The connection is pretty simple, pins 2 and 4 provide +5V and GND along with decoupling capacitors. On the other side of the chip pins 26 and 27 carry the oscillator 1 and 2 lines which are connected to the pin header in the top left of the photo where the SX Key Programmer connects. Pin 28 is the /mclr line used to reset the processor by grounding (via the connected reset switch). Finally pin 18 (output port RC.0) is connected to an LED to be used in testing whether the circuit actually works.

Missing from the circuit is an external osciallator to provide an accurate clock to the SX28, I plan on ordering a selection of oscillators next week. In the mean time the SX28 has an internal oscillator that can provide frequencies in the range 31.25kHz to 4MHz, then there is also the SX Key which can generate a clock between 400kHz and 100Mhz more than enough for my purposes (although it can be less accurate than an external oscillator it will do for now).

Programming the SX28 with this setup is simple, connect the SX Key to the pin header, program the chip then remove the SX Key and we're done. That is assuming we're running the chip via its internal osciallator, otherwise the SX Key remains connected to provide a clock source. Heres the program I used to test the hardware:

; //////////////////////////////////////////////////////////////////////
    ; SX28 - V0.1 LED Blink
    ; (c) Gary Preston 29th April 2006
    ; gary@figmentgames.com
    ; //////////////////////////////////////////////////////////////////////
    include "Setup28.inc"
     
    ; ----------------------------------------------------------------------
    ; Global Data $08-$0F in all 8 banks on SX28
    org $08
    counter1  ds  1
    counter2  ds  2
    counter3  ds  3
    ; ----------------------------------------------------------------------
    ; Banked Data
     
    ; ----------------------------------------------------------------------
    ; Main entry
    ; ----------------------------------------------------------------------
    Main
      ; clear up mem avoiding 00-07 in each bank
      clr fsr
    :cleardata
      sb  fsr.4                 ; skip if accessing $10 - $1F, $30 - 3F...
      setb  fsr.3               ; offset by 8 bits to avoid first 8 mem locations
      clr ind
      ijnz fsr, :cleardata      ; keep looping until wrap around to $00
     
      mov W, #%11111110         ; RC.0 set to output
      mov !RC, W               
     
      setb  RC.0                ; default to on
     
    :blink
      mov W, /RC
      mov RC, W
      ; Running @ 1MHz one clock cycle = 1.0uS
      ; 1023 cycles per loop = 525833 cycles = ~0.53 seconds
     
      mov counter3, #2
    :loop
      decsz counter1            ; 1/3
        jmp :loop               ; 3
      decsz counter2            ; 1/3
        jmp :loop               ; 3
      decsz counter3            ; 1/3
        jmp :loop
      jmp :blink   

The layout of the board leaves a bit to be desired, I'll probably shuffle things around a little for the next itteration. A few months ago I wouldn't have had a clue what any of the above did, but today it actually looks quite simple (aside from circuit analysis of the capacitor filter frequency, but I'm working on that :P)

The next task is to get graphics generation working and add an RCA output connected to the TV. How successful this will be using the SX Key as a clock source rather than a good osciallator remains to be seen.

Monday, 10 April 2006

GID j - Mission Accomplished

Having only had a few hours Sunday night to work on the last part of the code I wasn't expecting to actually reach my goal of a pixel on the screen. In the end though, I managed to get a square block drawn in the center of the screen synced well enough to show flicker free on both my TV and Digital TV card in the PC.

The actual TV picture is a nice vibrant yellow, the screen capture doesn't do it justice looking a little washed out. Oh, that reminds me, the colour burst wasn't that hard afterall, just a case of making sure the colour is set in the upper 4 bits and the luma in the lower 4 :)


Adding in the VSync and overscan code wasn't that difficult, the hard part was counting all the clock cycles to make sure they met the timing requirements. In fact the most trouble was with the comparison loop where I check whether the scanline is within the block and needs to be drawn in colour. This results in 3 possible code paths all of which had to have exactly the same number of cycles before reaching the final DEALY(174-3) instruction. A little fudging goes a long way though :)

The theme for this GID was "Extreme Forces" well the forces exerted on my TV by all the badly formed signals I sent throughout Saturday were pretty extreme, does that count?

For those that care the source code is zipped up and available for download. Although it works fine on my TV that doesn't mean the code is correct so I'd love to hear back from any of the more experienced XGS users if you can see any obvious flaws. The final code weighed in at roughly 374 bytes (don't quote me on that ;) ) After looking at a few other peoples delay macros I can see a saving of several bytes there alone, so I'm sure this code can be optimised significantly, maybe another day :)

Code Dump: Be aware the formatting is a little messed up

; //////////////////////////////////////////////////////////////////////
    ; GID j - Pixel in a day entry
    ; Gary Preston 8th April 2006
    ; //////////////////////////////////////////////////////////////////////
    include "Setup52.inc"
    include "macros.inc"
     
    ; //////////////////////////////////////////////////////////////////////
    ; Consts
    ; //////////////////////////////////////////////////////////////////////
    ;
    BLACK_LEVEL EQU 6
    WHITE_LEVEL EQU 15
     
     
    ; Colors
    VID_SYNC      EQU %11110000             ; No color + 0v
    VID_BLACK   EQU (%11110000 + BLACK_LEVEL)     ; 0.25-0.3V
    VID_WHITE   EQU (%11110000 + WHITE_LEVEL)      ; 1.0V
    TEMP_COLOUR EQU 011111                                       ; yellow
     
    ; Color Burst signal
    VID_COLORBURST_OFF EQU (%11110000 + BLACK_LEVEL)
    VID_COLORBURST_ON EQU  (000000 + BLACK_LEVEL)
     
    ; //////////////////////////////////////////////////////////////////////
    ; Variables
    ; //////////////////////////////////////////////////////////////////////
     
    ;----------------------------------------------------------------------
    ; $00 - 09 - registers (05-09 = ports RA-RE)
    ; 0A - 0F - globals
    ; Banks (00-0F, 10-1F, 20-2F ... F0-FF) bank 0 via semi-direct only
     
    ;----------------------------------------------------------------------
    ; GLOBALS (Not available using semi-direct!)
    ORG $0A        ; skip past global registers and banks 1-F
    counter DS   1  ; counter required by delay macro
     
    ;----------------------------------------------------------------------
    ; defines for TV Signal generation using Bank 1
    ORG $10
    BANK_VIDEO EQU $
     
    pixels    DS 1
    scanline       DS 1
     
    ; //////////////////////////////////////////////////////////////////////
    ; Program start
    ; //////////////////////////////////////////////////////////////////////
    main
        ; Initialise ports for video (RE port tied to video hardware)
        mov RE, #000000      ; clear port
        mov !RE, #000000    ; set all pins to output
     
        ; --------------------------------------------------------------
        ; Video Kernel (Colour upper nibble, Luma lower nibble)
        ; VID_CSEL3 (b7) - VID_CSEL0 (b4)
        ; VID_ISEL3 (b3) - VID_ISEL0 (b0)
        ; Tied to port RE0-7
        ; PAL 5Mhz
        ;   312 scan lines
        ;   pixel every 181.81ns approx 292 pixels per line (53.1us / 181.81ns)
     
        ; set bank
        _bank   BANK_VIDEO
     
    vidloop  
     
        ; we have 312 lines potentially with pal but will use only 192 active
        mov scanline, #206          ; 2
    :scanline_loop
        ;--------------------------------------------------------------
        ; Horizontal signal component 274 active lines
        ; blanking voltage 0.3V for 1.5us (front porch)
        mov RE, #VID_BLACK                        ; 2
        DELAY( 120-2 )
     
        ; horizontal sync 0V delay 4.7us
        mov RE, #VID_SYNC                           ; 2
        DELAY( 376-2 )
     
        ; pre burst breezeway 0.6us voltage 0.3V
        mov RE, #VID_BLACK                  ; 2
        DELAY( 48-2 )
     
        ; color burst 9-10cycles 0.3V for 2.5us (skipping data just delay for now)
        mov RE, #VID_COLORBURST_ON            ; 2
        DELAY( 200-2 )                              ; 200-2
       
        ; post burst blanking 0.3V 1.6us
        mov RE, #VID_BLACK                ; 2
        mov pixels, #255                              ; 2   
        DELAY( 128-4 )
     
        ; All the 3 branches following MUST have the same execution time, thus
      ; the extra jumps that don't go anywhere
        ; black for 141 pixels (2037 cycles) and color for 12 (174 cycles)
        mov RE, #VID_BLACK                ; 2
     
        ; color for 12 pixels (174 cycles)   
        cjb scanline, #97, :skipcolor1        ; 4/6
        cja scanline, #109, :skipcolor2   ; 4/6
        DELAY(2037-10)
        mov RE, #TEMP_COLOUR                            ; 2 setup pixel colour 
        jmp :color                        ; 3
     
    :skipcolor2
        DELAY(2037-12)
        nop 
        nop
        jmp :color                        ; 3
    :skipcolor1
        DELAY(2037-6)
        nop 
        nop
        jmp :color                        ; 3
    :color
        DELAY( 174-5 )
     
        ; black for 141 pixels (2037 cycles)                           
        mov RE, #VID_BLACK                              ; 2 setup pixel colour 
        DELAY(2037-6)            ; inc following jump
     
        djnz scanline, :scanline_loop   ; 2/4 (extra 2 from 4 is unaccounted for!)
       
      ; END of Horizontal Component (total scanline = 64us)
      ; we're 2 clock cycles over!
     
    ; ---------------------------------------------------------------------------------------------
    ; OVERSCAN BOTTOM
    ; --------------------------------------------------------------------------------------------- 
        mov scanline, #50   ; 2
    :o verscan_bottom
        ;--------------------------------------------------------------
        ; Horizontal signal component 274 active lines
        ; blanking voltage 0.3V for 1.5us (front porch)
        mov RE, #VID_BLACK                        ; 2
        DELAY( 120-2 )
     
        ; horizontal sync 0V delay 4.7us
        mov RE, #VID_SYNC                           ; 2
        DELAY( 376-2 )
     
        ; pre burst breezeway 0.6us voltage 0.3V
        mov RE, #VID_BLACK                  ; 2
        DELAY( 48-2 )
     
        ; color burst 9-10cycles 0.3V for 2.5us (skipping data just delay for now)
        mov RE, #VID_COLORBURST_ON            ; 2
        DELAY( 200-2 )                              ; 200-2
       
        ; post burst blanking 0.3V 1.6us
        mov RE, #VID_BLACK                ; 2
        mov pixels, #255                              ; 2   
        DELAY( 128-4 )
     
        ; draw full scanline (53.1us = 4248 cycles)
        mov RE, #VID_BLACK    ; 2
        DELAY( 2124-2 )  ; 4248 is one full scanline, delay function would overflow with that amount
        DELAY( 2124-4 )  ; Really need to build a better delay function that supports longer delays
        djnz scanline, :o verscan_bottom ; 2/4
        ; full scanlein = 64us
     
    ; ---------------------------------------------------------------------------------------------
    ; VSYNC LINES
    ; ---------------------------------------------------------------------------------------------
        mov scanline, #5        ; 2
    :vsync
        ; 4 sync lines
        mov RE, #VID_SYNC      ; 2
        DELAY( 2560-2 )  ; 4248 is one full scanline, delay function would overflow with that amount
        DELAY( 2560-4 )  ; Really need to build a better delay function that supports longer delays
        djnz scanline, :vsync   ; 2/4
     
    ; ---------------------------------------------------------------------------------------------
    ; OVERSCAN TOP
    ; --------------------------------------------------------------------------------------------- 
        mov scanline, #50   ; 2
    :o verscan_top
        ;--------------------------------------------------------------
        ; Horizontal signal component 274 active lines
        ; blanking voltage 0.3V for 1.5us (front porch)
        mov RE, #VID_BLACK                        ; 2
        DELAY( 120-2 )
     
        ; horizontal sync 0V delay 4.7us
        mov RE, #VID_SYNC                           ; 2
        DELAY( 376-2 )
     
        ; pre burst breezeway 0.6us voltage 0.3V
        mov RE, #VID_BLACK                  ; 2
        DELAY( 48-2 )
     
        ; color burst 9-10cycles 0.3V for 2.5us (skipping data just delay for now)
        mov RE, #VID_COLORBURST_ON            ; 2
        DELAY( 200-2 )                              ; 200-2
       
        ; post burst blanking 0.3V 1.6us
        mov RE, #VID_BLACK                ; 2
        mov pixels, #255                              ; 2   
        DELAY( 128-4 )
     
        ; draw full scanline (53.1us = 4248 cycles)
        mov RE, #VID_BLACK    ; 2
        DELAY( 2124-2 )  ; 4248 is one full scanline, delay function would overflow with that amount
        DELAY( 2124-4 )  ; Really need to build a better delay function that supports longer delays
        djnz scanline, :o verscan_top    ; 2/4
     
        jmp     vidloop     ; 3

Sunday, 9 April 2006

GID j - signal generation

Having nailed the delay code I've spent most of the evening reading up on the SX52 memory architecture and the PAL timing signal. To start with I've created the signal needed for each scanline (see previous blog with timing diagram)

The first attempt didn't go too well



It was only a few minutes though before I realise my timing was out by a few clock cycles. The reason? Well in order to generate say the SYNC pulse 0.0V on RE port for 4.7µs we need to delay for 4.7µs*1000/12.5ns = 376 clock cycles, so I did the following

mov RE, VID_SYNC
DELAY(376)

The problem though is that really we've taken up 378 clock cycles and thus the code is executing for 25ns longer than we need it to. Couple this with all the other mov/delay statements for the other parts of the signal and we end up many cycles out of sync. The solution is simple, a mov takes 1 cycle or in the case of a compound mov (which this is) 2 cycles, so we just reduce the delay by 2 cycles to give

mov RE, VID_SYNC       ; 2
DELAY(376-2)              ; 376-2

The result is a lot more promising, using VID_WHITE (1.0v) for the output port RE gives us a nice white screen, however we're not finished yet, since there is no handling of the vertical sync yet.


So thats as far as I managed to get by the end of Saturday. I expected to have a good few hours sunday to work on getting the vsync sorted and plotting a specific pixel, but plans change. If I'm lucky I'll squeeze in a few more hours tonight and hopefully finish things off.

Saturday, 8 April 2006

GID j - Delay Debugging

As mentioned in the last blog entry, timing is cricical down to the cycle. In order to make sure all branches of code execute in the exact same amount of time I've created a delay macro. This isn't an optimal method, I'm sure there are ways to reduce the number of bytes this uses however it does the job.. at least it does after a little debugging.

After coding up the above I programmed it into the SX52 and put the SXkey into debug mode which allows you to single step the processor one instruction at a time and view the programs memory/registers and flags. Using various delays of 1, 7, 10, 14 etc I noticed the NOP repeat was only generating a single nop rather than the expected number, this turned out to be because missed the ending ENDR directive

;
    ; DELAY for a specified number of clock cycles
    ; EXPECTS:
    ;    "counter" 1 byte variable defined
    DELAY MACRO clocks
      ; eat up clock cycles 10 at a time
      IF ( (clocks / 10)> 0)
        mov counter, #(clocks / 10)         ; 2
    :loop
        jmp $ + 1                   ; 3 jmp to next instruction
        jmp $ + 1                   ; 3 jmp to next instruction
        djnz counter, :loop                ; 2/4  (4 if <> zero)
                                    ; 10 cycles per loop, 8 for last loop
      ENDIF
     
      ; handle remaining delay 1-9 using nops
      IF ( (clocks // 10)> 0)
        REPT (clocks // 10)
          NOP                   ; 1
        ENDR
      ENDIF
    ENDM

The following screenshot (click for a larger view) shows the debugger running the DELAY(14) code and circled in red the lone NOP instruction. A simple mistake putting an extra slash but one which would if not caught render the video output useless. Glad I caught it early, otherwise I might have wasted hours debugging my video code.



With the delay loop out of the way, its time to final make a start on generating accurate h/v signals. Hopefully by the end of tonight :)

GID j - Attempt 1 snow

Satuday 2pm and I'm a little closer to understanding how to plot a pixel, although only a little. The first attempt to generate a video signal resulted in, well see for yourself, detune a tv channel and look at the white snow, thats pretty close to what I'm looking at right now :)

The reason been, I've not setup any kind of timing for generating the video signal yet but I know that at least I'm outputting to the correct port to generate "a" signal. After a little digging through the XGS docs I've got the information on the PAL signal specs, the main bits of interest are:
  • Front Porch 1.5µs
  • Active Video 53.1µs
  • Sync 0V
  • Black 0.3V
  • White 1.0V
Based on this we can calculate the number of pixels a PAL tv can cope with (@5.5Mhz), we can change the signal at a rate of 1/5.5Mhz = 182ns which gives us 53.1µs / 182ns = 292 pixels per scanline (best case; although I think my tv will do 720 due to extra bandwidth, but we'll go for a conservative value)

So in order to get a pixel (assuming no colour) on the screen I need to generate a video signal to display 312 scanlines each comprised of at most 292 pixels. Heres the full scanline signal needed for a PAL system. (I'm ignoring colour burst for now; image obtained from Haagans website)



Once the horizontal signal is up and running the vertical signal should be a snap, its nothing more than another timing signal, so we'll be sending a H Sync signal for each line followed by 4-10 lines of vsync. I'm keeping this simple and going for 274 lines at 50Hz (50 frames per second) with 16 blank lines top/bottom to account for tv variations and the 6 lines of vsync giving a total of 312 lines.

The key to this is the timing, with the SX52 processor that the XGS uses running at 80Mhz, 1 clock cycle = 12.5ns we need to ensure that the number of cycles used to generate the signal matches the above timing diagram.

There is a whole lot more to the signal generation than the overview above, if you're really interested in the details either get the XGS and read the book (which in addition covers designing and building the actual video hardware) or google it.

So the plan for the larger part of today is to get a suitable delay macro up and running and outputting the appropriate voltages to generate a stable H/V signal. Initially using a fixed value for each pixel (we'll get to plotting a single specific pixel later.. for now its a screen fill) Fingers crossed that is.

Friday, 7 April 2006

GID j - Black Pixels

Its that time of the month again, we're now on GID j, for a change I thought I'd code something for the XGS. I sense this GID is going to be rather painful just setting things up ready for tomorrows start brought problems.

Lesson1: When routing the video/audio output through your digital tv card and you only get black and white check the Crystal. I'd forgot that the last time I used the XGS I'd hooked it up to my TV which supports PAL or NTSC and had been playing around with some of the NTSC only demos. I'd left the 3.57Mhz crystal in the XGS rather than the 4.43Mhz crystal needed for PAL output.

Lesson2: Prepare for things to only get worse from here on in. This little voice keeps telling me this is a bad idea. Trying to learn the details of programming a chip such as the SX52 and assosiated hardware is bad enough, but considering how rusty my asm skills are we're in for a bumpy ride.

The chances of a successful GID appear to be slim to say the least, so I'm lowering my expectation (and I mean reeeealy lowering) and instead doing a PID; pixel in a day (if you hadn't already guessed ). In other words, if I manage to get a pixel up on the screen within the time constraints of this GID I'll be happy, anything beyond that is a bonus. On a good note I've already written code to handle interfacing with the joystick port (see previous blog) so if we can get a pixel up, maybe we can get it moving, perhaps even a several pixels ;)

Hey with 4k of direct memory who knows we may even get as far as displaying a sprite, maybe two but lets not get too carried away.

Anyhow the XGS is rigged up with video/audio routed through my pc to make for easier screen grabs (erm that is assuming we get as far as displaying anything, otherwise you're in for some code dumps instead ;) ) so, for what remains of Friday night and a few hours of Saturday morning I'm going to hit the SX Programming manuals and read over the XGS hardware specs.

I think this first calls for a fresh cuppa.