FPGA Creativision / Tech Docs

Talk about programming CreatiVision (except games programming). Projects of homebrew hardware are also welcome.
User avatar
@username@
Posts: 320
Joined: Tue Oct 22, 2013 6:59 pm
Location: Scotland

Re: FPGA Creativision / Tech Docs

Post by @username@ » Wed Dec 04, 2013 1:55 am

kevtris wrote:Aah, so they did use NMI. I just use reset though because NMI and reset both point to the same vector, so there's not much difference.
Well, depends on your point of view. To the 6502 the two are fundamentally different.
From the MOS manual
On a RESET, the CPU loads the vector from $FFFC/$FFFD into the program counter and continues fetching instructions from there.
On an NMI, the CPU pushes the low byte and the high byte of the program counter as well as the processor status onto the stack, disables interrupts and loads the vector from $FFFA/$FFFB into the program counter and continues fetching instructions from there.

However, by using RESET and NMI as the same, you do indeed add a random factor :)

I confess, I just *bodged* the keypad / sound / cassette stuff by looking at the DDR bit settings, and going from there :)
User avatar
kevtris
Posts: 10
Joined: Wed Nov 27, 2013 7:44 pm
Location: USA
Contact:

Re: FPGA Creativision / Tech Docs

Post by kevtris » Wed Dec 04, 2013 2:21 am

@username@ wrote:
kevtris wrote:Aah, so they did use NMI. I just use reset though because NMI and reset both point to the same vector, so there's not much difference.
Well, depends on your point of view. To the 6502 the two are fundamentally different.
From the MOS manual
On a RESET, the CPU loads the vector from $FFFC/$FFFD into the program counter and continues fetching instructions from there.
On an NMI, the CPU pushes the low byte and the high byte of the program counter as well as the processor status onto the stack, disables interrupts and loads the vector from $FFFA/$FFFB into the program counter and continues fetching instructions from there.

However, by using RESET and NMI as the same, you do indeed add a random factor :)
Yes, but both reset and NMI vectors point to the same place:

Code: Select all

FFFA : 66 FF    ROR $FF
FFFC : 66 FF    ROR $FF
So both should theoretically do the same thing on the Creativision. (Obviously this is not the case on most other 6502 powered systems). The only minor difference is NMI pushes the return address and flags on the stack while reset does not (it forces R/W high). This doesn't matter on the Creativision, however.
I confess, I just *bodged* the keypad / sound / cassette stuff by looking at the DDR bit settings, and going from there :)
Yeah I was kinda doing a hack to get it going but I got bit by a bug. Now I use the DDR regs and it works fine. I do not support the cassette yet but will probably end up adding it later on.

Btw I looked thru your disassembly a bit, and I can name one more subroutine. FF58h is an LFSR (linear feedback shift register) that is used to generate "random" numbers.

Another minor update: I was wrong and Planet Defender / Deep Sea Adventure both work fine in NTSC as well as PAL. All games seem to function OK in both modes actually. I think it was that minor bug in the 6821 that affected those two games instead of TV standard. The only oddity is those two games, the "terrain" on the hills disappears after awhile.. seemingly on a timer almost.
User avatar
@username@
Posts: 320
Joined: Tue Oct 22, 2013 6:59 pm
Location: Scotland

Re: FPGA Creativision / Tech Docs

Post by @username@ » Wed Dec 04, 2013 2:40 am

The graphics disappearing is normal too! Quality control obviously slept through those ;)

Also you'll find the sound stops in Crazy Chicken, and that's normal too. Fortunately, mobsie posted some video of those running on a real CV, so you're fine.

Maybe it's different in FPGA, but using RESET in an emulator leaves the stack screwed, and things then go anywhere, as RESET sticks stack back to top of zeropage.

This, I believe, is where all the magic mirroring came from.
User avatar
kevtris
Posts: 10
Joined: Wed Nov 27, 2013 7:44 pm
Location: USA
Contact:

Re: FPGA Creativision / Tech Docs

Post by kevtris » Wed Dec 04, 2013 6:21 am

@username@ wrote:The graphics disappearing is normal too! Quality control obviously slept through those ;)

Also you'll find the sound stops in Crazy Chicken, and that's normal too. Fortunately, mobsie posted some video of those running on a real CV, so you're fine.

Maybe it's different in FPGA, but using RESET in an emulator leaves the stack screwed, and things then go anywhere, as RESET sticks stack back to top of zeropage.

This, I believe, is where all the magic mirroring came from.
Haha, almost makes me want to disassemble it and figure out why those graphics disappear. Where are those videos posted? I saw a few videos on youtube but they appear to all be from emulators.

As for resets, on an actual 6502, reset will attempt to write to the stack, but R/W is held high so the chip will never actually write to the stack, though it will "go through the motions". Interestingly, the SP can be pointing ANYWHERE on reset; I checked the BIOS and the SP is NEVER SET! There is not a single TXS instruction in the entire BIOS as far as I can tell. tut tut, Vtech. I could admit that NMI would indeed be different from reset in one case- SP will be decremented only 3 times instead of continuously as in the case of reset.

It appears that the games I have all seem to function properly though using reset. I think I will add a "soft reset" button for fun anyways to be 100% correct, just in case.

BTW, reset should never place the stack at the top of zeropage. The stack can never leave page 1 (0100-01ffh). If you had variables sitting in this area and then reset, they could be overwritten by the stack on the first JSR in the BIOS (and other stack things) however. Using NMI would indeed fix this problem- but your game has to reset SP to a known place on reset/NMI instead of leaving it hanging wherever it feels like residing, using up an entire 1/4th of your available RAM.
User avatar
Mobsie
Posts: 708
Joined: Fri Jun 13, 2008 10:38 am
Location: Weinheim, Germany

Re: FPGA Creativision / Tech Docs

Post by Mobsie » Wed Dec 04, 2013 7:29 am

Hi, here are the links to my little videos on youtube. They are from REAL CV but not very good quality because i take them quick with my iPhone to help @username@ with his emu.

https://www.youtube.com/watch?v=smUxS-M70j8

https://www.youtube.com/watch?v=WGAGuXk31nA

I can record more if you need.

Br,
Mike
User avatar
MADrigal
Site Admin
Posts: 1189
Joined: Sun Sep 15, 2013 1:00 pm
Contact:

Re: FPGA Creativision / Tech Docs

Post by MADrigal » Wed Dec 04, 2013 7:32 am

kevtris wrote:Another oddity I noticed. I believe the BIOS I have is hacked, because it does not show the logo
You're probably using the Funvision / Hanimex Rameses version of the BIOS, in which the logo was deleted.

Take the CreatiVision BIOS from the "downloads" area on the CreatiVEmu website: that has the logo.
User avatar
kevtris
Posts: 10
Joined: Wed Nov 27, 2013 7:44 pm
Location: USA
Contact:

Re: FPGA Creativision / Tech Docs

Post by kevtris » Thu Dec 05, 2013 7:20 am

Thanks for the video links and the logo BIOS! Looks like the sound and video on my doodad is matching your videos, so that's good.

I did some testing and all the games do seem to run fine in PAL or NTSC mode, without too much in the way of speed variations. To finish things up, I finally implemented a "virtual" tape player!

This "tape player" is simply another 6502 CPU running at 2MHz. I disassembled the BASIC ROM and used bits of their code from CSAVE in an attempt to get BASIC program loading working. I will post full source for this if anyone else wishes to use it on an emulator or what have you.

I ended up being partially successful- the BASIC program loads, but it's corrupt. I was looking at the .bas files posted to this site, and they appear to have CRLF's in them for each line. Is there some kind of special processing you have to do before uploading the BASIC files? Like put each line of the program in its own 64 byte packet? The first line of the program loads fine but the rest are a jumble of data, with an occasional "good" line of code thrown in there too.

I was just attempting to blast the entire .bas file to the Creativision, 64 bytes at a time. I will give the "single line at a time" thing a go before I am officially stumped.

After I get that working, I will attempt to add CSAVE support too, to allow full saving/loading.
User avatar
MADrigal
Site Admin
Posts: 1189
Joined: Sun Sep 15, 2013 1:00 pm
Contact:

Re: FPGA Creativision / Tech Docs

Post by MADrigal » Thu Dec 05, 2013 7:56 am

@ kevtris: download the source code of my "FunnyMu Unofficial" from the website.

There's an useful "instant load/save" routine, written in C, which you can use to understand the specifics about loading/saving from/to cassette tape. ;)
User avatar
kevtris
Posts: 10
Joined: Wed Nov 27, 2013 7:44 pm
Location: USA
Contact:

Re: FPGA Creativision / Tech Docs

Post by kevtris » Fri Dec 06, 2013 5:08 am

MADrigal wrote:@ kevtris: download the source code of my "FunnyMu Unofficial" from the website.

There's an useful "instant load/save" routine, written in C, which you can use to understand the specifics about loading/saving from/to cassette tape. ;)
Actually I looked through the source a bit earlier and didn't find one.

I did manage to finish loading/saving in any event. Last night I tried the "send one line at a time" trick and it works good. Upon inspection of the code in the BASIC ROM I see this is what it is expecting to see.

A bit more poking around and I got CSAVE working as well as CLOAD so now I think everything's working. Save/load are mostly "automatic" now; it watches bits 6 and 7 of the port A DDR to figure out when it's loading or saving from the tape to trigger a load/save routine. I have attached the source code in case anyone else wants to use it for whatever purpose. Theoretically this code could be used on a real 6502 with a few minor changes to load in a game from an EPROM or two.

This code works with the .bas files as found on this site, and saving them back from BASIC results in bit-identical .bas files. Enjoy!

Code: Select all

;Creativision tape drive emulator written in 6502 asm.
;
;
;12052013
;K.Horton
;
;Free to use for any purpose.
;
;Based on the code in the Creativision BASIC cartridge
;6502 should run at 2MHz just like the Creativision's CPU.
;
;This code will save and load programs somewhat "automatically".
;
;Interface is minimal and this makes it easy to implement.
;
;
;
;Memory Map:
;
You do not have the required permissions to view the files attached to this post.
User avatar
MADrigal
Site Admin
Posts: 1189
Joined: Sun Sep 15, 2013 1:00 pm
Contact:

Re: FPGA Creativision / Tech Docs

Post by MADrigal » Fri Dec 06, 2013 7:54 am

Source code is:
- http://www.madrigaldesign.it/creativemu ... al_src.zip

File is:
- funnysdl.c

Functions are:
- SaveListing()
- LoadListing()

Quote:

Code: Select all

/** SaveListing() ********************************************/
/** Saves current VRAM data from $1C00 to $3FFF to plain    **/
/** text file (used to save BASIC listings)                 **/
/**                                                         **/
/** Adapted from Giovanni Ortu's CvEmu2 source code         **/
/*************************************************************/
void SaveListing(void) {
  #define START_CHECK 0x1800
  #define START_VID 0x1c00
  #define END_VID 0x4000
  #define MEM_SIZE (END_VID - START_VID)
  #define USE_ORIGINAL_LINE 0				// 0 to remove any zero's before line number	(e.g. 005 REM -> 5    REM)	
							// 1 to keep zero's 				(e.g. 005 REM -> 005  REM)
  int exit=0;
  int allowed_count=0;
  int exit_line=0;
  int found=0;
  int counter=0;

  char buff[10000][64];
  memset(buff, 0x00, 10000*64);

  char allowed[10000];
  memset(allowed, 0x00, 10000);

  while (!exit) {
    int memline = VRAM[START_CHECK + allowed_count * 2] * 256 + VRAM[START_CHECK + allowed_count * 2 + 1];

    if ((memline > 0xff00) || (START_CHECK + allowed_count * 2 > 0x1bff))
      exit = 1;
    else {
      allowed[memline] = 1;
      allowed_count++;
    }
  }

  if (allowed_count != 0) {
    exit = 0;

    while (!exit) {
      if (counter>MEM_SIZE
          || (VRAM[START_VID + counter+0] == 0
          && VRAM[START_VID + counter+1] == 0
          && VRAM[START_VID + counter+2] == 0
          && VRAM[START_VID + counter+3] == 0)
        )
        exit = 1;
      else
      {
        exit_line = 0;
        char temp[64], linest[7], line[64];
        memset(temp, 0x00, 64);

        sprintf(linest, "%c%c%c%c\0",
          VRAM[START_VID + counter + 0],
          VRAM[START_VID + counter + 1],
          VRAM[START_VID + counter + 2],
          VRAM[START_VID + counter + 3]
        );

        int linenum = atoi(linest);
        counter += 5;
        int index = 0;

        while (counter < MEM_SIZE && !exit_line) {
          temp[index] = VRAM[START_VID + counter];
          if (VRAM[START_VID + counter] == 0x0d) {
            temp[index + 1] = 0x0a;
            temp[index + 2] = 0x00;
            index++;
            exit_line = 1;
            found = 1;
          }
          counter++;
          index++;
        }

        if (USE_ORIGINAL_LINE)
          sprintf(line, "%s %s", linest, temp);
        else
          sprintf(line, "%-4d %s", linenum, temp);
        strcpy(buff[linenum], line);
      }
    }
  }

  char fn[12];
  FILE *fl, *fs;

  if (found) {						// if any valid listing is found in VRAM
    int i = 1;							// finds unexisting savexxx.txt file to save listing
    while (i <= 999) {
      sprintf(fn, "save%03d.txt", i);
      if (fl = fopen(fn, "r")) {
        i++;
        fclose(fl);
      }
      else { 							// new filename generated, proceed to saving listing
        fs = fopen(fn, "wb");
        int j = 0;
        for (j = 0; j < 9999; j++)
          if (buff[j][0] != 0x00 && allowed[j] != 0)		// save existing data if present in line counter buffer
            fprintf(fs, "%s", buff[j]);
        fclose(fs);
        i = 1000;
      }
    } 
  }
}





/** LoadListing() ********************************************/
/** Loads "load.txt" data into VRAM from $1C00 to $3FFF     **/
/** (used to load BASIC listings)                           **/
/*************************************************************/
void LoadListing(void) {
  #define START_CHECK 0x1800
  #define START_VID 0x1c00
  #define END_VID   0x4000
  #define SECOND_CHECK 0x1400
  #define MEM_SIZE (END_VID - START_VID)

  char line[64];
  char temp[64], temp2[64];
  int count=0;

  FILE *fp;
  fp=fopen("load.txt", "r");
  if (fp) {
    char buff[10000][64];
    memset(buff, 0x00, 10000 * 64);

    while (!feof(fp) && fgets(line, 64, fp)) {
      int start = 0;
      memset(temp, ' ', 64);
      int len = strlen(line);
      int num_line = -1;
      
      while (start < len && line[start] != ' ')		// parsing string to find line number
        temp[start] = line[start++];

      if (start > 0) {					// ok, line number found
        num_line = atoi(temp);					// this is the line number
        while (start < len && line[start] == ' ')
         start++;						// skip white spaces after line number

        if (num_line > 0 && num_line < 10000)			// start -> index of the first 'non space' char	
          memcpy(buff[num_line], &line[start], len - start + 1);	// inserting only allowed line numbers

        count++;					// line counter;
      }
    }

    if (count != 0) {					// at least one line found
      int i = 0;
      int offset_mem = 0;
      int offset_check = 0;
      memset(&VRAM[START_CHECK], 0xff, START_VID-START_CHECK);
      memset(&VRAM[START_VID], 0x00, END_VID-START_VID);

      int prevlen = 0xdc00;
      for (i = 0; i < 10000; i++) {
        if (buff[i][0] != '\0')	{			// the current line (i) exists
          sprintf(line, "%-4u %s", i, buff[i] );		// formatting line
          int cc = 0;
          int len2 = strlen(line);
            
          while (cc < len2 && line[cc] != 0x0d && line[cc] != 0x0a)	// copy formatted line into VDP memory
            VRAM[START_VID + offset_mem++] = line[cc++];

          VRAM[START_VID + offset_mem++] = 0x0d;		// adding end of line

          unsigned char high = prevlen / 256;
          unsigned char low = (unsigned char)(((float)prevlen / 256 - high) * 256);

          VRAM[SECOND_CHECK + offset_check * 2] = high;
          VRAM[SECOND_CHECK + offset_check * 2+1] = low;

          prevlen += cc + 1;

          high = i / 256;

          low=(unsigned char)(((float)i / 256 - high) * 256);

          VRAM[START_CHECK + offset_check * 2] = high;
          VRAM[START_CHECK + offset_check * 2 + 1] = low;

          offset_check++;

          printf("%s",line);
        }
      }
    }

    fclose(fp);
  }
}
Post Reply