Successful Tests (not in List of Bugs)

Discussions about testing new clib
Post Reply
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Successful Tests (not in List of Bugs)

Post by alvin »

A program testing C11 extension memstreams, this time the open_memstream() function.

This is the example program given in the memstreams technical document:

Code: Select all

// zcc +zx -vn -clib=new open_memstream.c -o open_memstream

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
   FILE *stream;
   char *buf;
   size_t len;

   stream = open_memstream(&buf, &len);

   if (stream == NULL)
      perror("open_memstream");
   else
   {
      fprintf(stream, "hello my world");
      fflush(stream);
      
      printf("buf=%s, len=%zu\n", buf, len);
      
      fseek(stream, 0, SEEK_SET);
      fprintf(stream, "good-bye cruel world");
      
      fclose(stream);
      printf("buf=%s, len=%zu\n", buf, len);
      
      free(buf);
   }

   return 0;
}
memstreams allows FILE i/o to be performed on a vector<char> in memory. The open_memstream() function opens a write only vector which expands in size (via realloc) as needed.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Three terminal windows.

This is a test program that statically instantiates three terminal windows on screen, using one as edit window, another as main window and the last as sidebar window. -startup=100 us used to select a temporary custom startup that statically declares these windows.

Code: Select all

// zcc +zx -vn -startup=100 -clib=new terminals_3.c -o terminals_3

#include <stdio.h>
#include <sys/ioctl.h>
#include <arch/spectrum.h>
#include <stdlib.h>

extern FILE *edit;
extern FILE *sidebar;

char buffer[100];  // max edit buffer size is 64

main()
{
   unsigned int i;

   zx_cls(INK_BLACK | PAPER_RED);
   zx_border(INK_CYAN);
   
   ioctl(1, IOCTL_OTERM_CLS);
   ioctl(3, IOCTL_OTERM_CLS);
   ioctl(4, IOCTL_OTERM_CLS);

   for (i=0; ; ++i)
   {
      printf("%u Hello World!\n", i);
      printf("%u Goodbye World!\n", i);
      
      if (rand() > 32100)
      {
         fprintf(edit, "\nEnter a message: ");
         
         fflush(stdin);
         scanf("%[A-Za-z0-9 ]", buffer);
         
         fprintf(sidebar, "\n%s\n", buffer);
      }
   }
}
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Program testing use of sp1 sprite library. This is ex_1 of sp1's example directory. The main change is that sp1's dynamic memory requests now go directly to malloc() rather than through a user-supplied function. In this example, I am specifically increasing the size of the malloc heap to accommodate the sprites being created and I am using -startup=31 so there are no statically initialized FILEs (stdin, stdout, stderr do not exist). This will save some memory.

File: sp1_ex1.c

Code: Select all

/////////////////////////////////////////////////////////////
// EXAMPLE PROGRAM #1
// 04.2006 aralbrec
//
// Ten software-rotated masked sprites move in straight lines
// at various speeds, bouncing off screen edges.  All ten
// share the same sprite graphic which is not animated in
// this first test program.
/////////////////////////////////////////////////////////////

// zcc +zx -vn -clib=new -startup=31 sp1_ex1.c gr_window.asm -o sp1_ex1

#include <games/sp1.h>
#include <arch/spectrum.h>


#pragma output REGISTER_SP           = 53248     // place stack at $d000 at startup

#pragma output CLIB_MALLOC_HEAP_SIZE = 10000     // malloc heap size (not sure what is needed exactly)
#pragma output CLIB_STDIO_HEAP_SIZE  = 0         // no memory needed to create file descriptors


// Clipping Rectangle for Sprites

struct sp1_Rect cr = {0, 0, 32, 24};             // rectangle covering the full screen

// Table Holding Movement Data for Each Sprite

struct sprentry
{
   struct sp1_ss  *s;                            // sprite handle returned by sp1_CreateSpr()
   char           dx;                            // signed horizontal speed in pixels
   char           dy;                            // signed vertical speed in pixels
};

struct sprentry sprtbl[] = {
   {0,1,0}, {0,0,1}, {0,1,2}, {0,2,1}, {0,1,3},
   {0,3,1}, {0,2,3}, {0,3,2}, {0,1,1}, {0,2,2}
};

// A Hashed UDG for Background

unsigned char hash[] = {0x55,0xaa,0x55,0xaa,0x55,0xaa,0x55,0xaa};

// Attach C Variable to Sprite Graphics Declared in ASM at End of File

extern unsigned char gr_window[];      // gr_window will hold the address of the asm label _gr_window

main()
{
   static struct sp1_ss *s;
   static unsigned char i;
   static struct sprentry *se;
   
   #asm
   di
   #endasm

   // Initialize SP1.LIB
   
   zx_border(INK_BLACK);
   
   sp1_Initialize(SP1_IFLAG_MAKE_ROTTBL | SP1_IFLAG_OVERWRITE_TILES | SP1_IFLAG_OVERWRITE_DFILE, INK_BLACK | PAPER_WHITE, ' ');
   sp1_TileEntry(' ', hash);   // redefine graphic associated with space character

   sp1_Invalidate(&cr);        // invalidate entire screen so that it is all initially drawn
   sp1_UpdateNow();            // draw screen area managed by sp1 now
   
   // Create Six Masked Software-Rotated Sprites
   
   for (i=0; i!=6; i++) {

      s = sprtbl[i].s = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, i);
      sp1_AddColSpr(s, SP1_DRAW_MASK2, 0, 48, i);
      sp1_AddColSpr(s, SP1_DRAW_MASK2RB, 0, 0, i);
      sp1_MoveSprAbs(s, &cr, gr_window, 10, 14, 0, 4);

   };
   
   while (1)
   {
   
      sp1_UpdateNow();                          // draw screen now
      
      for (i=0; i!=6; i++)
      {                                         // move all sprites
 
         se = & sprtbl[i];
         
         sp1_MoveSprRel(se->s, &cr, 0, 0, 0, se->dy, se->dx);
         
         if (se->s->row > 21)                   // if sprite went off screen, reverse direction
            se->dy = - se->dy;
            
         if (se->s->col > 29)                   // notice if coord moves less than 0, it becomes
            se->dx = - se->dx;                  //   255 which is also caught by these cases

      }
      
   }  // end main loop

}
File: gr_window.asm

Code: Select all

SECTION user_data

   defb @11111111, @00000000
   defb @11111111, @00000000
   defb @11111111, @00000000
   defb @11111111, @00000000
   defb @11111111, @00000000
   defb @11111111, @00000000
   defb @11111111, @00000000

; ASM source file created by SevenuP v1.20
; SevenuP (C) Copyright 2002-2006 by Jaime Tejedor Gomez, aka Metalbrain

;GRAPHIC DATA:
;Pixel Size:      ( 16,  24)
;Char Size:       (  2,   3)
;Sort Priorities: Mask, Char line, Y char, X char
;Data Outputted:  Gfx
;Interleave:      Sprite
;Mask:            Yes, before graphic

PUBLIC _gr_window

_gr_window:

        DEFB        128,127,  0,192,  0,191, 30,161
        DEFB         30,161, 30,161, 30,161,  0,191
        DEFB          0,191, 30,161, 30,161, 30,161
        DEFB         30,161,  0,191,  0,192,128,127
        DEFB        255,  0,255,  0,255,  0,255,  0
        DEFB        255,  0,255,  0,255,  0,255,  0
        
        DEFB          1,254,  0,  3,  0,253,120,133
        DEFB        120,133,120,133,120,133,  0,253
        DEFB          0,253,120,133,120,133,120,133
        DEFB        120,133,  0,253,  0,  3,  1,254
        DEFB        255,  0,255,  0,255,  0,255,  0
        DEFB        255,  0,255,  0,255,  0,255,  0
Notice how the assembler is assigned to a section so that it become part of the resulting binary.

The usual method of embedding assembler into the C listing is not currently working due to the mixture of old z80asm directives generated by sccz80 and new ones. That will be fixed in time but this method of separating asm from C source is a much better way to organize code anyway and I would encourage anyone to do the same.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

The low level fzx functions were added for proportional font printing.

A lot of changes were made to the original driver, among them addition of colour, print modes (or, xor, reset) and, of course, the self-modifying code was removed to allow fzx to be used in (upcoming) output terminals.

fzx_modes.c tests each of these print modes with a variety of fonts. The first demo screen creates four coloured squares on screen and sets the fzx_state so that it only changes the INK to black so that the paper colour remains unchanged. Black text is rendered on whatever underlying paper colour is present.

There is one change that might occur. For the time being, the maximum attribute squares surrounding an individual character is affected. This means if you have a large font and print a lowercase "b", the top vertical line will affect attribute squares extending to the maximum right distance where the round part of the lower b extends to. I am thinking only the attribute squares with actual pixels should be affected but changing to this would slow printing several times so I am still undecided on this point.

Code: Select all

// zcc +zx -vn -startup=31 -clib=new fzx_modes.c -o fzx_modes

#include <font/fzx.h>
#include <rect.h>
#include <arch/spectrum.h>
#include <input.h>
#include <stdlib.h>
#include <z80.h>

#pragma output CLIB_EXIT_STACK_SIZE = 0   // no exit stack

#pragma output CLIB_MALLOC_HEAP_SIZE = 0  // no user heap
#pragma output CLIB_STDIO_HEAP_SIZE = 0   // no stdio heap for fd structures

#pragma output CLIB_FOPEN_MAX = 0         // no allocated FILE structures
#pragma output CLIB_OPEN_MAX = 0          // no fd table

// fzx state

struct fzx_state fs;

// rectangles defining areas on screen

struct r_Rect16 screen = { 0, 256, 0, 192 };

struct r_Rect8 tl = { 1, 14, 1, 10 };
struct r_Rect8 tr = { 17, 14, 1, 10 };
struct r_Rect8 bl = { 1, 14, 13, 10 };
struct r_Rect8 br = { 17, 14, 13, 10 };

// some text

char *txt_intro = "press any key to move to next section";
char *txt_hello = "Hello World!";
char *txt_xor   = "xor mode activated";
char *txt_cpl   = "invert mode activated";
char *txt_reset = "reset mode activated";

// hash udg

char hash[] = { 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa };

void print_random_location(char *s)
{
   int len;
   
   len = fzx_string_extent(fs.font, s);
   
   if ((len < (fs.window.width - fs.left_margin)) && (fs.font->height < fs.window.height))
   {
      fs->x = rand() % (fs->window.width - fs.left_margin - len);
      fs->y = rand() % (fs->window.height - fs.font->height);
      
      fzx_puts(&fs, s);
   }
}

main()
{
   int i;

   // introduction
   
   zx_border(INK_BLACK);
   zx_cls(INK_WHITE | PAPER_BLACK);
   
   fzx_state_init(&fs, ff_ao_Dutch, &screen);   // sets or mode by default
   
   fs.fgnd_attr = INK_WHITE | PAPER_BLACK;
   fs.fgnd_mask = 0;
   
   fs.y = (fs.window.height - fs.font->height) / 2;
   fs.x = (fs.window.width - fs.left_margin - fzx_string_extent(fs.font, txt_intro)) / 2;
   
   fzx_puts(&fs, txt_intro);
   
   in_wait_nokey();
   in_wait_key();
   in_wait_nokey();
   
   // or mode, fzx text printed ink-only

   fzx_state_init(&fs, ff_ao_Cayeux, &screen);
      
   fs.fgnd_attr = INK_BLACK;
   fs.fgnd_mask = 0x38;        // do not change the paper!

   do
   {
      zx_cls(INK_BLACK | PAPER_BLACK);
      
      zx_cls_wc(&tl, INK_RED | PAPER_RED);
      zx_cls_wc(&tr, INK_GREEN | PAPER_GREEN);
      zx_cls_wc(&bl, INK_BLUE | PAPER_BLUE);
      zx_cls_wc(&br, INK_YELLOW | PAPER_YELLOW);
      
      for (i=0; i!=20; ++i)
      {
         if (in_test_key()) break;
         print_random_location(txt_hello);
         z80_delay_ms(250);        // so that the loop takes ~5s
      }
      
   } while (!in_test_key());
   
   in_wait_nokey();
   
   // xor mode
   
   zx_border(INK_MAGENTA);
   
   fzx_state_init(&fs, ff_utz_Phraktur, &screen);
   
   fs.fzx_draw = _fzx_draw_xor;
   fs.fgnd_attr = INK_WHITE | PAPER_BLACK;
   fs.fgnd_mask = 0;

   do
   {
      zx_cls(INK_MAGENTA | PAPER_MAGENTA);

      for (i=0; i!=20; ++i)
      {
         if (in_test_key()) break;
         print_random_location(txt_xor);
         z80_delay_ms(250);        // so that the loop takes ~5s
      }
      
   } while (!in_test_key());
   
   in_wait_nokey();

   // reset mode
   
   zx_border(INK_BLACK);
   
   fzx_state_init(&fs, ff_ao_Sinclair, &screen);
   
   fs.fzx_draw = _fzx_draw_reset;
   fs.fgnd_attr = INK_BLUE | PAPER_CYAN;
   fs.fgnd_mask = 0;

   do
   {
      zx_cls(INK_BLACK | PAPER_GREEN);
      
      for (i = 0x4000; i != 0x5800; ++i)
         *(char *)(i) = hash[zx_saddr2py((void *)(i)) & 0x07];

      for (i=0; i!=20; ++i)
      {
         if (in_test_key()) break;
         print_random_location(txt_reset);
         z80_delay_ms(250);        // so that the loop takes ~5s
      }
      
   } while (!in_test_key());
   
   in_wait_nokey();
}
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Program testing the fzx justification functions.

fzx_reader is a simple "e-reader" that pours text into two windows on screen forming two columns. Text printed in each column is justified.

As of now, the word-wrap partition functions will indicate none of a string will fit into a given pixel width if a long sequence of non-space characters is provided for partitioning. The program uses a regular partition function if this happens to find the maximum string length of consecutive non-space characters that will fit into the window horizontally. The fzx_write_justified function will not insert spacing unless the string contains spaces so in this circumstance, a line will not be justified if it contains solely non-space characters. I'm considering whether it should be expected that the justify functions instead insert spaces between individual characters to justify.

File: fzx_reader.c

Code: Select all

// zcc +zx -vn -startup=31 -clib=new fzx_reader.c The_Jungle.c -o fzx_reader

#include <font/fzx.h>
#include <rect.h>
#include <arch/spectrum.h>
#include <string.h>
#include <input.h>

#pragma output REGISTER_SP = -1           // set to 0 (top of memory)

#pragma output CRT_ENABLE_RESTART = 1     // not returning to basic
#pragma output CRT_ENABLE_CLOSE = 0       // do not close files on exit

#pragma output CLIB_EXIT_STACK_SIZE = 0   // no exit stack

#pragma output CLIB_MALLOC_HEAP_SIZE = 0  // no user heap
#pragma output CLIB_STDIO_HEAP_SIZE = 0   // no stdio heap for fd structures

#pragma output CLIB_FOPEN_MAX = 0         // no allocated FILE structures
#pragma output CLIB_OPEN_MAX = 0          // no fd table

#define FONT_CHOICE ff_ao_Orion

// fzx state

struct fzx_state fs;

// rectangles defining columns on screen

int active_window;
struct r_Rect16 window[] = { { 0, 120, 0, 192 }, { 136, 120, 0, 192 } };

// book pointers

extern char book[];
char *p_start, *p_end, *p_part;

void newline(void)
{
   fs.x  = fs.left_margin;
   fs.y += fs.font->height * 3/2;   // 1.5 line spacing
}

main()
{
   int res;

   zx_border(INK_MAGENTA);
   
   // portion of fzx_state that is target-dependent
   // not initialized by fzx_state_init()
   
   fs.fgnd_attr = INK_BLACK | PAPER_RED;
   fs.fgnd_mask = 0;

   while(1)
   {
      // start of book
         
      p_start = strstrip(book);

      // clear screen
      
      active_window = 0;
      fzx_state_init(&fs, FONT_CHOICE, &window[0]);
      
      zx_cls(INK_WHITE | PAPER_MAGENTA);

      // print book into columns

      while (*p_start)
      {            
         // delimit paragraph
            
         p_end = strchrnul(p_start, '\n');
            
         // print paragraph
            
         while (p_start < p_end)
         {
            // find line that will fit into window
               
            p_part = fzx_buffer_partition_ww(fs->font, p_start, p_end - p_start, fs.window.width - fs.left_margin);
            
            if (p_part == p_start)
            {
               // solid text without spaces exceeds allowed width
               // this text will not be justified -- should the justify function insert spaces between characters??
               
               p_part = fzx_buffer_partition(fs->font, p_start, p_end - p_start, fs.window.width - fs.left_margin);
            }
            
            // print line

            if (p_part == p_end)
            {
               // last line of paragraph
               
               res = fzx_write(&fs, p_start, p_part - p_start);
            }
            else
            {
               res = fzx_write_justified(&fs, p_start, p_part - p_start, fs.window.width - fs.left_margin);
            }

            if (res < 0)
            {
               // bottom of window reached
                  
               if (++active_window >= (sizeof(window) / sizeof(struct r_Rect16)))
               {
                  // page full
                     
                  active_window = 0;
                     
                  in_wait_nokey();
                  in_wait_key();
                     
                  zx_cls(INK_WHITE | PAPER_MAGENTA);
               }
                  
               // move to top left of next window
                                     
               fzx_state_init(&fs, FONT_CHOICE, &window[active_window]);
            }
            else
            {
               // move pointer to beginning of next line
                 
               p_start = strstrip(p_part);
               newline();
            }
         }
            
         // end of paragraph, process newlines
            
         if (*p_end)
         {
            while (*(++p_end) == '\n')
            {
               newline();
               if (fs.y >= fs.window.height) break;
            }
         }
            
         // move past paragraph
            
         p_start = strstrip(p_end);
      }

      // end of book
      
      in_wait_nokey();
      in_wait_key();
   }
}
Source text: The_Jungle.c (excerpt from "The Jungle" by Upton Sinclair, copyright free)

Code: Select all

char book[] = "THE JUNGLE\n\nby Upton Sinclair\n\n\n(1906)\n\n\n\nChapter 1\n\n"
   "It was four o'clock when the ceremony was over and the carriages began "
   "to arrive. There had been a crowd following all the way, owing to the "
   "exuberance of Marija Berczynskas. The occasion rested heavily upon "
   "Marija's broad shoulders--it was her task to see that all things went in "
   "due form, and after the best home traditions; and, flying wildly "
   "hither and thither, bowling every one out of the way, and scolding and "
....
The text file is long. This might be a good time to point out you can download all these example programs from:
https://drive.google.com/open?id=0B6XhJ ... authuser=0
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

In the fzx_reader.c test of the previous post, the book is stored in a char array:

Code: Select all

char book[] = "...";
This causes the book text to be stored in a DATA segment which means if the program is targetted at a ROM, the entire book text will be stored in the rom and then copied into ram by the crt.

This simple change:

Code: Select all

char *book = "...";
places the book text in an RODATA segment which would be placed in rom and read from rom by the program. This is the way the book text should be declared.
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Program testing the recently completed fzx output terminals.

FZX is a proportional font printing system and the fzx output terminals allow printing and editing using a selection of about 100 fonts.

This demo is a repeat of a simple earlier test program but using fzx output instead of one of the fixed width terminals. By specifying "-startup=8" on the compile line, a crt instantiating an fzx output terminal using font "ff_ao_Soxz" can be used.

Code: Select all

// zcc +zx -vn -clib=new test.c -o test
// zcc +zx -vn -startup=4 -clib=new test.c -o test
// zcc +zx -vn -startup=8 -clib=new test.c -o test

#include <stdio.h>
#include <stropts.h>
#include <arch/spectrum.h>
#include <string.h>
#include <stdlib.h>

char buffer[100];  // max edit buffer size is 64

main()
{
   unsigned int i;
   
   zx_border(INK_WHITE);
   ioctl(1, IOCTL_OTERM_CLS);
   
   for (i=0; ; ++i)
   {
      printf("%5u %0#5x %I\n", i, i, i + ((unsigned long)(~i) << 16));
      
      if (rand() > 32100)
      {
         printf("\nEnter a message:\n");
         
         fflush(stdin);
         scanf("%[^\n]", buffer);
         
         printf("\nMessage received and reversed:\n%s\n\n", strrev(buffer));
      }
   }
}
Snapshots of the program compiled using three different fonts can be found in the 'test' directory of this zip file:

https://drive.google.com/open?id=0B6XhJ ... authuser=0
alvin
Well known member
Posts: 1872
Joined: Mon Jul 16, 2007 7:39 pm

Post by alvin »

Creation of an if2 cartridge for the zx target.

The test program is a simple implementation of a LISP interpreter. The source is lengthy so grab it from the clisp subdirectory in this zip file:

https://drive.google.com/open?id=0B6XhJ ... authuser=0

The first step was to simply compile the program for one of the if2 targets:

zcc +zx -vn -startup=40 -clib=new clisp.c -o clisp

"-startup=40" selects an if2 target with an fzx driver instantiated on standard output and an in_inkey() driver on stdin. The entire clib is independent of the spectrum rom so there are no concerns about having to have the zx rom paged in while the program runs. The crt for the if2 target has an org of 0 and fills in the typical z80 restarts (by default they just 'ret') which can be customized by your program. We don't need any restarts so we just take the default.

The currenty pre-made crts and their corresponding startup numbers are all enumerated in the target's main crt file. For the zx, that's:

http://z88dk.cvs.sourceforge.net/viewvc ... iew=markup

You can scroll down to "startup=40" to read the crt description.


The result of the compile is three files:

2015-03-25 11:23 PM 9,996 clisp_BSS.bin
2015-03-25 11:23 PM 20,101 clisp_CODE.bin
2015-03-25 11:23 PM 429 clisp_DATA.bin

The if2 target uses the compressed rom model. What this means is it generates separate CODE, DATA and BSS segments. The compressed rom model expects the DATA segment to be compressed and appended to the CODE segment to form the binary image. The BSS segment is mapped to ram and will be automatically initialized to zero by the crt. So the BSS.bin file is irrelevant other than to show you how much ram is taken by the bss variables (the initially zeroed variables). Likewise, the DATA segment contains the initialized variables and its size is how much space it occupies in RAM. Because the DATA segment contains non-zero data, the crt must copy this into RAM to properly initialize the program. That is why the rom image will contain first the CODE.bin file and then the compressed DATA.bin file appended to it. That image is what would be placed in the if2 cartridge.

However, we have a problem. The CODE binary is 20101 bytes and the DATA binary (before compression) is 429 bytes. IF2 cartridges are only 16k in size. We could try to scrape together 4k in savings somehow but there is another way to fit this program onto the cartridge that would be even easier.

We can compile the program to run in RAM, and then store a compressed version of the program in the cartridge that is decompressed into ram before execution.

In the interest of avoiding too much effort, let's try that.

Since we want to execute the program in ram we're not using an if2 target anymore. We'll compile assuming we're running the program in ram with a few pragmas added:

#pragma output CRT_MODEL = 1

#pragma output CRT_ORG_CODE = 28000
#pragma output CRT_ORG_DATA = 50000

#pragma output REGISTER_SP = 0
#pragma output CRT_ENABLE_RESTART = 1

#pragma output CLIB_EXIT_STACK_SIZE = 0
#pragma output CLIB_MALLOC_HEAP_SIZE = 0
#pragma output CLIB_STDIO_HEAP_SIZE = 0


We're choosing the uncompressed ROM model (CRT_MODEL = 1) which means the crt will initialize the DATA segment from an uncompressed stored copy appended to the CODE segment and it will zero the BSS segment at startup. These two things will allow the program to execute more than once since on execution it always initializes all C variables to their expected values before calling main. This clisp program does exit if a CTRL-D (end of file) is entered at the keyboard. The clib allows a CTRL-D to be generated with CAPS+SYM+D on the zx target. So we also enable "CRT_ENABLE_RESTART" which indicates to the crt that the C program does not exit to basic but instead restarts. Since the basic ROM is not going to be paged in (this is an if2 cart remember), trying to exit to basic would be disastrous and instead the CTRL-D exit option will cause the program to reset and restart.

We set the CODE org for where our program will be run once it is decompressed into RAM. Similarly we specify the location in RAM of the DATA segment. The BSS segment will immediately follow the DATA segment since it's org is not specified. These numbers were picked out of thin air and we will have to confirm those are viable addresses after the compile.

Other pragmas place the stack at the top of ram and eliminate the heaps since clisp.c neither performs an memory allocation or opens any files.

Next we compile using the typical zx ram model:

zcc +zx -vn -startup=8 -clib=new clisp.c -o clisp

2015-03-25 11:45 PM 9,998 clisp_BSS.bin
2015-03-25 11:45 PM 20,056 clisp_CODE.bin
2015-03-25 11:45 PM 429 clisp_DATA.bin

The rom model expects the final binary to consist of the CODE section with the DATA section appended (this is the uncompressed rom model so the DATA section is not compressed first).

copy /b clisp_CODE.bin+clisp_DATA.bin clisp_IMG.bin
(non windows users can use an equivalent cp)

2015-03-25 11:47 PM 20,485 clisp_IMG.bin

This is our executable image that needs to be loaded at address 28000 (CODE org). It will occupy addresses 28000-48484. The DATA segment in RAM was org'd at 50000 and will occupy addresses 50000-50428. The BSS segment will immediately follow occupying addresses 50429-60426. The stack will be located at the top of memory and grow downward toward the end of the BSS segment.

This is all satisfactory as no segments overlap and there is plenty of stack space. Had the memory map been unsatisfactory we could use the now-known sizes of the CODE, DATA and BSS segments to choose appropriate addresses for each.

z88dk comes with a compression tool written by Einar Saukus called zx7. It consists of a data compression tool run on the PC and a data decompression routine written in z80 and made available in the c library. So the magic moment has come -- can this image fit into a 16k cartridge if compressed?

zx7 clisp_IMG.bin

2015-03-25 11:54 PM 10,407 clisp_IMG.bin.zx7

And the answer is: with room to spare!


Next step is how do we deal with this compressed image? The idea is to write a short assembly stub that will execute from address 0 and decompress this image into ram. Here's the stub:

"clisp_if2.asm"

Code: Select all

; z80asm -b -ic:\z88dk\libsrc\_DEVELOPMENT\lib\zx_asm.lib clisp_if2.asm

org 0

EXTERN asm_dzx7_standard

main:

   di
   
   ld hl,clisp_image
   ld de,28000
   
   call asm_dzx7_standard
   
   jp 28000

clisp_image:

   BINARY "clisp_IMG.bin.zx7"
The program is simple. After disabling interrupts (this is necessary as we don't have an interrupt service routine nor are we sure what the interrupt enable state is when the cartridge is started), we decompress the stored compressed image to its start location at address 28000 and then run it.

This is an assembly program so we invoke z80asm directly to assemble it. We have to link to the zx library explicitly since that's not done for us automatically unless we use zcc to compile. The library being linked to is "zx_asm" which is the asm-only zx library. It contains all the same functions as the c library but without any c headers.

z80asm -b -ic:\z88dk\libsrc\_DEVELOPMENT\lib\zx_asm.lib clisp_if2.asm
(non-windows users will have to specify the correct path to the library)

2015-03-26 12:02 AM 10,489 clisp_if2.bin
2015-03-26 12:02 AM 2,124 clisp_if2.map
2015-03-26 12:02 AM 10,593 clisp_if2.obj
2015-03-26 12:02 AM 392 clisp_if2.sym

The assembler generated several files we don't care about. "clisp_if2.bin" is the binary we are interested in.

Next we create an if2 cartridge rom. An if2 cartridge rom is simply a 16k rom with the executable org'd at 0 inside it. To make this rom, appmake provides some helpful tools.

appmake +rom -s 16384 -o if2_blank.rom

This creates a 16k if2 blank:

2015-03-26 12:04 AM 16,384 if2_blank.rom

Next we insert our if2 image into the blank at address 0:

appmake +inject -b if2_blank.rom -i clisp_if2.bin -s 0 -o if2_clisp.rom

And that creates our final if2 cartridge image:

2015-03-26 12:07 AM 16,384 if2_clisp.rom


Many spectrum emulators recognize the ".rom" suffix as an if2 cartridge or rom replacement. Dragging and dropping the if2 image into many emulators will automatically execute it. Other emulators may require you to load it via a menu option.


Here is a short lisp program that can compute factorials:

Code: Select all

(defun fact (n)
  (cond ((< n 1)
  1)
 (t
  (* n (fact (- n 1))))))
 
(fact 6)
Also try entering "caps+sym+d" at the keyboard to cause the lisp interpretter to exit and the crt to restart the program.
Post Reply