Jump to content
IGNORED

Getting Started in Lynx Programming


disjaukifa

Recommended Posts

Would it be a one time program cart or could you re-program it multiple times?

It is a flash cart that can be reprogrammed over and over again. I also need to design a flasher for the cart. I've been looking at using one of the Atmel ATMega chips with built-in USB so that the flasher is a USB device.

 

USB would be so nice, also if you could make it so that it's Windows/Mac/Linux I'm sure many would appreciate it as well ;)

 

Also as for starting a page on your site, I will once you get that Lynx Dev-kit up, I defiantly want to get Atomic Meltdown going for the Lynx.

Deal. I'll write them.

 

Perfect, once you get them done, I start on Atomic Meltdown!

 

For the tutorials, could you do something like the tutorials for Batari Basic? Here is the link:

Code Snippets Samples for BB Beginners

 

Honestly that was the biggest help, it covered the bare essentials to get going with Batari Basic. I can help with documentation and what not, I'm just one of those people that needs a simple beginning and then building from it. If you could make the could I could make a page or blog page on your site that explains what each line does and why its need, which I think would be beneficially to many people.

I'll take a look and try to make the tutorials as easy to follow and helpful as I can.

 

--Wookie

 

All you need to do is write the different demos, I can document them for you if that would help ;)

 

-Disjaukifa

Link to comment
Share on other sites

Wookie has an interesting concept for extended addressing. My suggestion is to take that route and create cheap blank carts. Preferably similar designs to Zaku. But with 8MB of extended addressing and a large EEPROM for saves.

 

The idea of these large carts are great, but the Lynx really suffers by not having real random (banked) access to the cartridge. :(

Link to comment
Share on other sites

Not really. You just have to think in terms of streaming in data as you need it.

 

In many ways, the lynx is very similar to modern, DVD based consoles in that way. The streaming loader I have described in other posts is directly inspired by a loader used in a game engine I used to work with when I was a professional video game programmer.

 

--Wookie

Link to comment
Share on other sites

One good alternative is to use Karri's U3 tools and MegaPak cartridge template to write games in C.

 

Here's the AtariAge thread:

MegaPak template available

 

I happened to update my Lynx website today, and added instructions for a "Hello World" with the u3 tools and MegaPak template.

My Lynx programming page

 

I don't know if it works on a mac with win emulator though.

 

Unable to download right now... it gave me the 'unavailable error'

Link to comment
Share on other sites

lots of great info here. (I think that is why the downloads are not working... as they are being updated or something).

I'll keep an eye on this.

 

Yeah, seems like the old version isn't available anymore. Maybe a new release of the tools is closing in :)

 

EDIT: It was only a server crash. The Files are still available.

Edited by Turbo Laser Lynx
Link to comment
Share on other sites

lots of great info here. (I think that is why the downloads are not working... as they are being updated or something).

I'll keep an eye on this.

 

Yeah, seems like the old version isn't available anymore. Maybe a new release of the tools is closing in :)

 

The web server had crashed for some reason. It is just an old PC sitting next to my front door. No 24/7 availability I'm afraid.

Now it should work again.

 

--

Karri

Link to comment
Share on other sites

Wookie has an interesting concept for extended addressing. My suggestion is to take that route and create cheap blank carts. Preferably similar designs to Zaku. But with 8MB of extended addressing and a large EEPROM for saves.

 

The idea of these large carts are great, but the Lynx really suffers by not having real random (banked) access to the cartridge. :(

 

The lynx has 256 direct access blocks of 2048 bytes. With an 8-bit latch you can extend this to 65536 direct accessible chunks. You can assign a 16-bit chunk address to your graphics/sound segment (0000-FFFF). The only thing you need to do then is to put the high byte to the latch, the low byte to the block select lines and read in data from the cart. The cc65 linker knows where you need to read it and how long the chunk is.

 

There should be no need for a directory at all.

 

lda #<$1234
sta blockSelect
ldx #>$1234
sta latch
lda #<__EPISODE1234_CODE_SIZE__+__EPISODE1234_DATA_SIZE__+__EPISODE1234_RODATA_SIZE__
ldx #>__EPISODE1234_CODE_SIZE__+__EPISODE1234_DATA_SIZE__+__EPISODE1234_RODATA_SIZE__
jsr loadChunk
jsr episode1234

 

You can tag all your code and data into overloaded named segments at compile time. The linker will produce the actual size-data when the cart is built.

--

Karri

Edited by karri
Link to comment
Share on other sites

Karri, I've been trying to wrap my head around your cart demo so that I could make a new skeleton using my micro loader.

 

I'm currently stuck on writing the secondary loader because I can't figure out what to load where and what to jump to after loading it.

 

I wish there was a _start symbol in there that the secondary loader would jump to. I also wish there was a clear cut way to load in the resident code. I was reading your cooments in the code and it confusing.

 

Does ld65 generate code to load segments into RAM for us?

 

--Wookie

Link to comment
Share on other sites

Karri, I've been trying to wrap my head around your cart demo so that I could make a new skeleton using my micro loader.

 

I'm currently stuck on writing the secondary loader because I can't figure out what to load where and what to jump to after loading it.

 

I wish there was a _start symbol in there that the secondary loader would jump to. I also wish there was a clear cut way to load in the resident code. I was reading your cooments in the code and it confusing.

 

Does ld65 generate code to load segments into RAM for us?

 

--Wookie

 

The point is that you need to name the first C-file starting with the letter A. Something like aaresident.c

 

This will create the assembly listing so that the first start address is also the start address of the first loadable segment.

 

In the cc65/libsrc/lynx there is a file called crt0.s. It will be first thing to run in the STARTUP segment.

 

Now we get to a bit complicated thing in my template...

 

I want to start by loading the RAM segment that contains the C-library and the main program into high memory. After this the main program will load in the INTRO segment (that also contains the STARTUP segment) to a memory location that can be discarded later.

 

aaresident.c:

int main(void)
{
   asm("lda _first_time");
   asm("cmp #0");
   asm("beq not_first_time");
   asm("lda #<_INTRO_FILENR");
   asm("ldx #0");
   asm("stz _first_time");
   asm("jmp _FileExecFile");   // Start at beginning of intro segment. There is STARTUP_CODE.
   asm("not_first_time:");
   start_irqs();

 

intro.c:

// Remember, it has invisible crt0.s startup code at the beginning of this INTRO segment (STARTUP)

void intro()
{
}

void start_irqs(void)
{
   running_in_Handy = *((char *)0xfd97) == 0x42;
   lynx_change_framerate(75); // Maximum speed please...
   tgi_install(&lynxtgi); // This will activate the Lynx screen 
   joy_install(&lynxjoy); // This will activate the Lynx joypad
   tgi_init();
   tgi_setpalette(black_pal);
   tgi_setdrawpage(drawpage); // Set page for drawing stuff on. Can be 0 or 1.
   init_irq();
   install_irq(2, (int)&vbl);
   enable_irq(2);
   CLI();

 

After the intro has run I can re-use all the RAM that was used for initializing the Lynx.

 

I might take the train to Tampere tomorrow. There I could write a new loader.

 

The address where I want to load the RAM-segment is defined in the config file as a symbol __CODE_LOAD__

 

And the number of bytes you need to load is __CODE_SIZE__+__DATA_SIZE__+__RODATA_SIZE__

       .import __CODE_LOAD__
       .import __CODE_SIZE__
       .import __RODATA_SIZE__
       .import __DATA_SIZE__

 

   # The RAM segment is resident and contains the C-stack an heap
   # the size is code size + __STACKSIZE__ and it should be
   # just below the SCREEN area
   RAM: start = $9d38, size = $2108, define = yes, file = %O;

 

--

Karri

Link to comment
Share on other sites

Can I suggest we simplify things a bit? I have worked with a few different embedded OS's such as uC-OSII and Contiki and they are vastly simpler than your cart demo and lynx lib setup. I humbly propose we change things up a bit so that it is organized like so:

 

micro_loader.s -- This is my encrypted micro loader that gets decrypted and loaded to 0x0200. It is exactly 52 bytes long when encrypted and immediately follows the Handy Lynx header in the ROM file, but will be at location 0x0000 in cart ROM memory. Right now it loads the next 256 bytes from the ROM (file/cart) into 0x0300 and runs it. The 256 byte number is purely arbitrary as is the location it loads it to. The size could be increased to any multiple of 256, say 512 or 1024 bytes and the destination could be anything. I'm thinking that the micro_loader.s should load the data into a higher RAM address so that the library and game executable can be loaded down low in RAM, say 0x0100.

 

crt0.s -- This is what the micro loader will load into memory and run. As I suggested above, it should probably be loaded high up in RAM so that it can be overwritten after the library and main executable are loaded and running. Currently this code sets up the C runtime (initializes the stack) and then it initializes the lynx hardware and library and calls the main function of the game. I think we should change it so that it only loads the lynx library and main executable low in RAM, then sets up the C stack, and calls the main function in the main executable. I think we should leave the library initialization to the main executable so that games have maximum flexibility in configuring the library. If we have a standard linker symbols for where to load the library and main executable and how big they are, say __RAM_LIB_START__, __RAM_EXE_START__, __LIB_SIZE__ and __MAIN_EXE_SIZE__ respectively, then the crt0.s code will just have to load __LIB_SIZE__ number of bytes to __RAM_LIB_START__ and then load __MAIN_EXE_SIZE number of bytes to __RAM_EXE_START__ and then setup the C stack and call the main function. If the library and main exe immediately follows the crt0.s code on the cart ROM, then it won't have to initialize the address register/counter.

 

whatever.c/whatever.s -- This file can be called anything. It just has to define a symbol called "main" that the crt0.s code can call. This is your game's main code and it's job is to initialize the lynx library code, then load in your game data, initialize that and run it. I would think that the first thing I would do is load in the bare minimum needed to display an animated intro, initialize it, and play the intro why the rest of the game code/data gets loaded and initialized.

 

I'm thinking of something like this:

 


#include "lynx.h"

#define ever ;;

void main(void)
{
   /* all interrupts are turned off by crt0.s */

   /* initialize the parts of the lynx library */
   lynx_init_cart(I2C_EEPROM | I2C_EXTENDED_ADDRESSES);   /* configures the type of ROM cart */
   lynx_init_mikey();
   lynx_init_suzy(/* pointer to suzy done callback */);
   lynx_init_input(/* pointer to input buffer, input callback */);
   lynx_init_sound(/* sample rate, sound buffers, callbacks */);
   lynx_init_com(/* pointers to in/out buffers, baud rate, etc */);
   lynx_init_interrupts();  /* this hooks up the IRQ timer and hooks up the library tick IRQ */

   /* start the interrupts, this gets the whole thing going */
   lynx_interrupts_on();

   /* now we can do whatever we want :-)  I would kick off my
    * intro animation and sound while loading in the menu/game code and
    * data. */

   for(ever) {
       /* do something interesting */
   }
}

 

So, by putting all of the library initialization into the main function, it will greatly simplify the linker script and get into the programmer's main function as soon as possible and give them maximum flexibility. The runtime library is just that, a library of functions that you call to do various things. It make the programming paradigm for the Lynx much more familiar to C programmers coming to the platform from PC's. They won't have to worry about any other details other than setting the segment name for their main executable to be MAIN_EXE. The library makefiles will set the library segment to LIB and the linker script will be about as simple as it can get.

 

Anyway, this design I'm proposing makes writing a Lynx game and working with the Lynx library much more like "traditional" embedded system coding. This is exactly like how uC-OSII works. When building and linking your game, you would build your game code and then link it against crt0.o and lynx.o. The crt0.o code would be located at whatever the fixed address the micro loader loads it too and it's reference to main would be resolved by the linker.

 

Karri, I guess I'm mostly talking to you since you wrote most, if not all, of the Lynx runtime library. I'm more than happy to help you work on it. What do you think of this design?

 

--Wookie

 

P.S.

Because we are soon going to have a whole ecosystem of new cart designs, I'm suggesting we have a lynx_cart_init() function so that you programmer's can describe the geometry of the cart they are using. Then I would like the lynx library to have abstracted read/write functions that do the right thing based on the cart geometry. For instance, in the above example, I initialized the cart library telling it that my cart has an i2c EEPROM and an i2c extended addressing scheme. When reading from the cart the library will know to set the upper address bits on the i2c I/O extender first then set the segment address before reading. When writing, it will know to use the i2c EEPROM write routine instead of the BLL EEPROM write routine. If all read/writes go through the library read and write functions, then the library can keep track of the current address and it will know if it needs to set the upper address bits before reading.

Edited by Wookie
Link to comment
Share on other sites

Sounds good to me.

 

Yesterday I sat in the train and fiddled around with addresses. If the crt0.s is included with the main code then it could all be loaded as one big blob in high memory.

 

I am open to any solution that gives me no permanent garbage in memory at runtime.

 

I would also like to have a single header and a single config file that would be good for all. There is already a default config-file that will create a BLL-uploadable application. Perhaps this could be changed to a default cart target?

 

This means that whatever we create would be the standard out-of-the-box cart design for C-programmers.

 

If you include posix directory calls it should also put in a directory. If no directory calls are used then you could use direct block access routines instead.

 

As the hardware is not standardized for the Lynx in any way I believe that we need a few implementations that can be linked in at compile time. I don't like the idea to keep support for all cart styles linked in always. It is much better to have a separate library for EEPROM support. The EEPROM is only 128 bytes. Every byte counts and will be accessed directly by the code so we do not need standard POSIX file read/write for this.

- StrataFlash (devflash)

- 512 bytes/block

- 1024 bytes/block

- 2048 bytes/block

- EEPROM 1024 bits

- SRAM 16k (EOTB)

- 2 x 256k (Alpine Games, Drive school)

- Our new 8M design

 

This is just to be able to produce carts for existing cart PCB's.

 

I wonder if these asm-files could be written in a way that they could be re-used by people using Assember for programming? My focus is pretty much C as it is such a fast programming language. Fast to write programs - not to execute them ;)

--

Karri

Edited by karri
Link to comment
Share on other sites

   /* all interrupts are turned off by crt0.s */

   /* initialize the parts of the lynx library */
   lynx_init_cart(I2C_EEPROM | I2C_EXTENDED_ADDRESSES);   /* configures the type of ROM cart */
   lynx_init_mikey();
   lynx_init_suzy(/* pointer to suzy done callback */);
   lynx_init_input(/* pointer to input buffer, input callback */);
   lynx_init_sound(/* sample rate, sound buffers, callbacks */);
   lynx_init_com(/* pointers to in/out buffers, baud rate, etc */);
   lynx_init_interrupts();  /* this hooks up the IRQ timer and hooks up the library tick IRQ */

   /* start the interrupts, this gets the whole thing going */
   lynx_interrupts_on();

 

For you and me this is a nice idea to be able to set up every bit in the user program. But I seriously question whether the average programmer wants this extra complexity.

 

My suggestion is to put this stuff into a STARTUP-segment. By default it would be invisibly linked into start of the program. But if you want to override this with your own implementation then you create your own cfg file and map segment STARTUP into NULL. Instead you create a new segment MYSTARTUP that is then mapped into whereever STARTUP was mapped to.

 

Another thing is the interruptor usage. The idea behind the interruptor is that you can declare your code to be an interrupt handler at compile time. This means that the compiler will already do the work to set up the vectors. In a Lynx game you do not need to change these things at runtime. All routine entry points marked with .interruptor will be included in a table created by the linker.

 

Setting up the timers should be done later when you start up your graphics library or real-time library or whatever. The linker will automatically include the interruptor entry points of all these libraries. There is no need to execute code at run-time for this. The only run-time code needed is to program the timers and of course handle interrupts.

 

Oh, and there are no interrupt-levels. Every interrupt handler must examine if his timer caused the interrupt and return carry clear if the interrupt was not handled. This also means that all interruptors will be called at every interrupt unless someone sets the carry bit. This is of course only for cc65 programs.

 

--

Karri

Edited by karri
Link to comment
Share on other sites

Ok, I like where you are going with this. I really like the idea of making the library code "a la carte" where the programmer decides what code is included and what is not. The uC-OSII and Contiki embedded OS's uses #define's in a config.h file that specifies which code gets included. Something like the following:

 

game_config.h:

#ifndef _GAME_CONFIG_H_
#define _GAME_CONFIG_H_

/******************** PUT YOUR CUSTOM CONFIG HERE ************************/

/****** MY CUSTOM GAME CONFIG ******/
#define CGD_DEV_CART         /* using a CGD dev cart */
#define CART_BLOCK_IO        /* don't include the file I/O code */
#define CUSTOM_MUSIC         /* don't include the ABC music system */
#define CUSTOM_STARTUP_CODE  /* don't include the default STARTUP code */

#endif

 

 

config.h:

#ifndef _CONFIG_H_
#define _CONFIG_H_

/************ DEFAULT CONFIG IF YOU DON'T CUSTOMIZE ANYTHING *************/

/****** CART GEOMETRY DEFINITIONS ****** /

#if defined(CGD_DEV_CART)

 /* CGD is 16MB with i2c I/O expander and 128KB, i2c serial EEPROM */
 #define I2C_EXTENDED_ADDRESSES
 #define I2C_EEPROM
 #define CART_NUM_PAGES (8192)
 #define CART_PAGE_SIZE (2048)

#elif defined(BLL_DEV_CART)

 /* BLL is 512KB with a 1024 byte, non-i2c serial EEPROM */
 #define BLL_EEPROM
 #define CART_NUM_PAGES (256)
 #define CART_PAGE_SIZE (2048)

#else

 /* default cart is 512K with no EEPROM */
 #define NO_EEPROM
 #define CART_NUM_PAGES (256)
 #define CART_PAGE_SIZE (2048)

#endif


/****** DEFAULT LIBRARY CONFIG ******/

/* default cart I/O is to use C file I/O */
#if !defined(CART_BLOCK_IO) && !defined(CART_FILE_IO)
#define CART_FILE_IO
#endif

/* default is to include the ABC music system in the library */
#if !defined(CUSTOM_MUSIC)
#define ABC_MUSIC
#endif

/* default it to include library startup code in crt0.s 
* if you want to make your own calls to the library init functions to
* customize callback function pointers and other settings, then you 
* must define CUSTOM_STARTUP_CODE and make the calls in your main function. */
#if !defined(CUSTOM_STARTUP_CODE)
#define DEFAULT_STARTUP_CODE
#endif

#endif /* _CONFIG_H_ */

 

Now, when we build the library, we only compile a single C file. Inside the C there are preprocessor directives to include code based on the configuration headers:

 

lynx.c:


#include "game_config.h"
#include "config.h"

/* include the EEPROM code if needed */
#if !defined(NO_EEPROM)
 #if defined(I2C_EEPROM)
   #include "i2c_eeprom.c"
 #elif defined(BLL_EEPROM)
   #include "bll_eeprom.c"
 #endif
#endif

/* block I/O is always included */
#include "blockio.c"

/* include the file I/O library if requested.  it sits between the programmer and
* the block I/O layer to provide file I/O functions: fopen, fread, fwrite, fclose */
#if defined(CART_FILE_IO)
 #include "fileio.c"

 /* NOTE: with file I/O you must also include a file directory that gets stored in
  * the DIRECTORY segment.  The file I/O code imports and uses linker symbols for
  * the DIRECTORY segment. */
#endif

/* do the same with all of the subsystems...you get the idea */

 

Now for the startup code, I like your idea of having a STARTUP segment. I think the crt0.s should just turn off interrupts and initialize the C stack, then call a _startup() function that we will define in startup.c. The crt0.s and startup.c will get linked together and located in the STARTUP segment which will be high up in RAM somewhere so that they can be overwritten after they are done. They will be immediately after the encrypted micro loader in the cart ROM and will be what the micro loader loads up and runs. Here's how it should look:

 

startup.c:


/* put this code in the STARTUP SEGMENT
#segment STARTUP
#import _LIB_SIZE_
#import _LIB_START_
#import _MAIN_EXE_SIZE_

#include "game_config.h"
#include "config.h"

/* this function loads the lynx library and the main executable from the cart
* to the Lynx RAM */
void load_main()
{
   unsigned char * cart0 = 0xFCB2;       /* cart I/O register */
   unsigned char * ram = _LIB_START_;    /* address to load the lib and exe */

   for(i = 0; i < (_LIB_SIZE_ + _MAIN_EXE_SIZE_); i++)
   {
       /* copy a byte from the cart to RAM */
       *ram = *cart0;

       /* move the RAM pointer */
       ram++;

       /* NOTE: this will have to be a little more sophisticated to know how 
        * to set the page address register when crossing page boundaries */
   }
}

void _startup()
{
   /* first thing is to load the library and main code low in RAM */
   load_main();

#if defined (DEFAULT_STARTUP_CODE)
   /* initialize the library subsystems */
   init_mikey();
   init_suzy();

#if defined(CART_FILE_IO)
   /* this will load in the DIRECTORY segment and initialize the file library */
   init_filesystem();
#endif

#if defined(ABC_MUSIC)
   /* initialize the ABC music system */
   init_abc_music();
#endif

   /* etc... */
#endif

   /* now call main */
   main();
}

 

So this startup code just loads in the Lynx library and main executable low in ram, then it initializes the Lynx library subsystems if the programmer chooses to use the default initialization and lastly it calls the game's main function. If the programmer is using file I/O then will also have to create a DIRECTORY segment that contains a file directory:

 

directory.c:


#segment DIRECTORY

/* bring in the file directory entry struct definition */
#include "lynx_dir_ent.h"

/* directory of files sorted by file ID, the file ID is implied by
* the file's index in this array. */
dirent_t files[] = {

   /* cart address, file size */
   {      0x345000,    0x0234 },
   {      0x345234,    0x0100 }

};

 

I imagine that this directory.c file would be generated from a file packer utility that we would have to write. You could give it a bunch of files that it would pack into an assembly file with .byte blocks in it that will be located in your DATA segment. The directory.c file would be generated automatically so that it can be included in your game's build.

 

So the last piece to of a game skeleton would be the game itself. A minimal game would be just an empty main function like so:

 

main.c:


#include "game_config.h"
#include "config.h"
#include "lynx.h"

void main()
{
   /* this is where your game code starts */
}

 

I think this is the way we should go. I think this incorporates your ideas:

 

1. hide the library init details when the programmer doesn't want to worry about them.

2. put the startup code high in RAM so that it can be overwritten after it runs.

3. make the library code configurable so that it only compiles in the requested/needed code.

4. allow the programmer to choose if they want file I/O semantics and to include a file directory if they do.

 

This also satisfies my requirements:

 

1. allow the programmer to "unhide" the details so that they can customize the library initialization with custom callbacks and custom timer setups.

2. simplify the link step as much as possible. programmers shouldn't have to worry about the linker script.

3. simplify the loading process so that we can use the micro loader plus simple startup code that gets execution to the main() function as soon as possible.

 

Karri, what do you think?

 

--Wookie

Edited by Wookie
Link to comment
Share on other sites

Oh, yeah, and I don't think this library organization prevents assembly programmers from writing their games in assembly. It would still work even if a programmer wants to write a game in assembly but they also want the default initialization to happen. In their assembly code, they would just have to define a "main" entry point.

 

If they want to call library functions written in C, they would just have to make sure they are following the C calling convention used by the cc65 compiler. We could even write simple assembly macros for each of the library functions that would handle the calling convention stuff. So for instance, if they wanted to call init_mikey(), the would just JMP to "call_init_mikey" which would handle pushing stuff on the stack as needed and then JMP'ing to the actual C function.

 

--Wookie

Link to comment
Share on other sites

Wookie has an interesting concept for extended addressing. My suggestion is to take that route and create cheap blank carts. Preferably similar designs to Zaku. But with 8MB of extended addressing and a large EEPROM for saves.

 

The idea of these large carts are great, but the Lynx really suffers by not having real random (banked) access to the cartridge. :(

 

The lynx has 256 direct access blocks of 2048 bytes. With an 8-bit latch you can extend this to 65536 direct accessible chunks. You can assign a 16-bit chunk address to your graphics/sound segment (0000-FFFF). The only thing you need to do then is to put the high byte to the latch, the low byte to the block select lines and read in data from the cart. The cc65 linker knows where you need to read it and how long the chunk is.

 

What I meant is that the cartridge isn't mapped into the 6502 memory space like most other machines (unless I don't recall correctly), you load data from it one byte at a time into RAM (?).

Link to comment
Share on other sites

What I meant is that the cartridge isn't mapped into the 6502 memory space like most other machines (unless I don't recall correctly), you load data from it one byte at a time into RAM (?).

 

Oh, yes that is of course true. It memory maps only one byte into Lynx space at a time.

 

This is probably the slowest possible hardware for cart access you could think of.

This is also the reason why I would like to put every piece of information on the cart only once. And be able to load the stuff into RAM when it is absolutely required.

 

My solitaire game works that way :)

--

Karri

Link to comment
Share on other sites

If they want to call library functions written in C, they would just have to make sure they are following the C calling convention used by the cc65 compiler. We could even write simple assembly macros for each of the library functions that would handle the calling convention stuff. So for instance, if they wanted to call init_mikey(), the would just JMP to "call_init_mikey" which would handle pushing stuff on the stack as needed and then JMP'ing to the actual C function.

 

I hope we would write all the low level functions in pure assembly so that you do not need to initialize the C-stack. This includes all inits like init_mikey etc.

 

I also have a dream to have a structured asm-toolkit available. Perhaps your NESHLA would be good for that?

 

I can easily include separate applications in the final cart as pre-compiled blobs. In MegaPak I had Sketch, Yahtzee and some third program make by the BLL asm kit. I just linked it in as a binary blob and let it take over the whole Lynx. After the game finished it jumped to $0200 where I had a miniloader to load in the cc65 environment again.

 

I would not mix in asm into a C-program. It is better to have two completely separate environments.

--

Karri

Link to comment
Share on other sites

I hope we would write all the low level functions in pure assembly so that you do not need to initialize the C-stack. This includes all inits like init_mikey etc.

 

I totally agree. I was going the C route because it looked like that was what most of the existing code was written in.

 

I also have a dream to have a structured asm-toolkit available. Perhaps your NESHLA would be good for that?

 

NESHLA 65HLA will have it's own Lynx library written using the 65HLA syntax, which is assembly with C calling semantics. But that compiler has so many NES-specific assumptions that making it platform agnostic is a lot of work. I've been slowly chipping away it but it will be some time before it will be usable for Lynx development. It's a pretty low priority for me right now.

 

If we write the entire Lynx runtime library in assembly, how will we conditionally compile a binary that only includes the pieces that the developer wants? Also, because a developer may choose to use C for their game, all library functions written in assembly must follow the C calling convention used by cc65.

 

The only things I care about are this:

 

1. The developer should be able to choose what pieces of the lynx library are included.

2. The developer should be able to choose if the lynx library subsystems should get initialized automatically with default config parameters or let the developer initialize the library subsystems with custom parameters.

3. The lynx library subsystems should be configurable at runtime. If the developer chooses automatic initialization, then the configuration will be default values.

4. The default lynx library build and config should contain code that makes programming the lynx as easy as possible.

5. We need a secondary loader binary that the micro loader will load high up in RAM. the secondary loader will load in the lynx library and the initial executable low into ram and call the _startup function.

6. The cart memory should look like this:

 

+------------------------+------------------+-------------------+---------------+------------ --- -- -
| encrypted micro loader | secondary loader | lynx library code | main game exe | ...data... 
+------------------------+------------------+-------------------+---------------+------------ --- -- -

 

The main game exe could be a super small exe containing only event handlers, a run loop and a loader. It could be completely data driven where it loads data and other blocks of exe code into RAM as needed. That's if your game is complex enough to need tons of code and data. If you're just making a small puzzle game, then the main game exe could contain all of your code. The point is that the dev kit doesn't force anything on the developer. As a developer you should only worry about including <lynx.h> and linking against lynx.lib. I'm hoping that with this new dev kit organizing the example executable will look like this:

 

#include <lynx.h>

void main(void)
{
   while(1)
   {
       /* this is your main game loop */
   }
}

 

And the Makefile will be equally simple:

 


all: main.o lynx.lib micro_loader.bin secondary_loader.bin
   ( command to link the game into a cart image )

main.o: main.c
   ( command to compiler main.c into main.o )

lynx.lib: lynx_config.o
   ( command to link lynx.lib )

lynx_config.o: lynx_config.s
   ( command to compiler the lynx_config.s )

micro_loader.bin: micro_loader.o
   ( command to encrypt micro_loader.o

micro_loader.o: micro_loader.s
   ( command to compile micro_loader.s )

secondary_loader.bin: secondary_loader.s
   ( command to compile and link secondary_loader.s)

 

The lynx_config.s assembly file would use conditional compile directives to directly include the assembly language files that contain the different lynx library subsystems. This allows the programmer to customize what pieces get included into the lynx library.

 

Alright, enough talk, I'm going to start cobbling this together. I'm open for more suggestions but I think this design offers the lowest barrier to entry for any new Lynx programmer while still preserving the ability for experience programmers to customize everything.

 

--Wookie

Link to comment
Share on other sites

What I meant is that the cartridge isn't mapped into the 6502 memory space like most other machines (unless I don't recall correctly), you load data from it one byte at a time into RAM (?).

 

Yep, and data from a CD/DVD comes in one byte at a time but that hasn't stopped any of the CD/DVD based game consoles :-) You just have to think in terms of streaming in data rather than bank switching blocks of data into the address space. My approach is to use blocks of RAM as buffers where I can load different pieces of data into. When a new piece needs to be loaded, I will flush pieces of data from the buffer until I have a space large enough for the new piece of data to be loaded.

 

--Wookie

Link to comment
Share on other sites

Crud. Server down again. LOL

Karri...I like your site. looks really nice! Let me know if you got anywhere with the proto roms. They needed encryption added to them, if I recall correctly.

 

Thanks

 

Thanks. Perhaps I need to add some monitoring script for it :)

 

I should really go through them all now as we understand the boot process.

 

--

Karri

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...