Z80 Space-Time Productions Single Board Computer

Technical Support Information - BASIC Programming Language in Rom

Operation of the Space-Time Productions MCB is often a bit harrowing for some people who are not as interested in the hex monitor-level and assembly language hoops one jumps thru to use this board.
When computers like the Atari 400, 800 and Commodore VIC-20, TRS-80s were first hitting the schools and home consumer scene, more often than not they were being shipped with BASIC-in-Rom.
My intent here is to provide a simple but powerful BASIC language interpreter which while offering the better functions of vintage home-quality BASIC, also allows direct access to the Input/Output devices for the electronics hobbyist.

There are two versions of BASIC being offered here at the moment. Both are port overs from Nascom Z80 Basic produced in 1978 by Microsoft for them. It was one of the only Z80 versions I found that had the better features of BASIC and utilized the Z80 microprocessor. It was also one of the only ones I found that had a well-documented disassembly source file.

BASIC1 is designed to run either on a factory-ram-only (2K of HM2114 chips) MCB, or on an MCB that has expanded memory installed starting at $4000 upward. On startup, it scans the onboard ram which it treats as $3800-$3FFF. Then it continues scanning upward from $4000 thru $FF00 to see if additional ram has been added to MCB, and will allow BASIC to utilize any extra ram it finds. It assumes you have ram from at least $3800-$3FFF (which is the MCB's default onboard ram configuration, see HARDWARE page for more information on the memory layout of this board).

BASIC2 was actually created before BASIC1, and is hard-coded for unmodified MCB that has only the 2K of HM2114 ram chips on board. This is not enough ram to run anything other than very small BASIC programs [STARTREK.BAS will not load for instance], but is useful for hardware testing and I/O operating routines. Small control programs will work, and it has all the same features of BASIC1.
Some people may find using BASIC with their MCB much simpler than the supplied machine language monitor. Does not scan for any expanded memory, uses only ram from $3000-$37FF in its operation.

DOWNLOAD BASIC

  • BASIC1.ASM for the MCB, scans for expanded mem thru $FFFF but will operate with default boards.
  • BASIC1.HEX - the compiled file in Intel Hex format.
  • BASIC1_ROM1.HEX - the lower 4K of the Intel Hex file for burning directly to IC-21 eprom.
  • BASIC1_ROM2.HEX - the upper 4K of the Intel Hex file for burning directly to IC-22 eprom.

  • BASIC2.ASM for the 2K MCB as configured from factory. Does not scan for any memory, uses board hardware assumptions.
  • BASIC2.HEX - the compiled file in Intel Hex format.
  • BASIC2_ROM1.HEX - the lower 4K of the Intel Hex file for burning directly to IC-21 eprom.
  • BASIC2_ROM2.HEX - the upper 4K of the Intel Hex file for burning directly to IC-22 eprom.


The BASIC Language

This version uses old style line numbers and commands, and is one of the earlier simplified variants of the language, similar in user interface to Tandy Level II Basic is some respects.

Command Words

  • Execution group: RUN GOTO GOSUB RETURN STOP ON WAIT EXIT END
  • Program group: FOR TO NEXT STEP IF THEN CONT REM
  • Math group: DIM LET CLEAR DEF FN SGN INT ABS SQR RND LOG EXP COS SIN TAN ATN HEX
  • Data group: DATA READ RESTORE INPUT
  • Logic group: AND OR NOT
  • String group: LEN STR$ VAL ASC CHR$ LEFT$ RIGHT$ MID$
  • Print group: PRINT CLS NULL LINES WIDTH SET RESET POINT LIST TAB SPC POS SCREEN
  • System group: USR WAIT POKE PEEK FRE INP OUT

Uses standard BASIC language conventions, including line numbers:

10 FOR A=1 TO 10
20 PRINT A,;
30 NEXT A
40 END

Line numbers may run in any increments but are limited from 0 to 65529.
A line number "0" is a legitimate line number.

Variables:

  • Integers function as 16-bit signed numbers in the range -32768 to 32767. Memory addresses and parameters are used in this format.
  • Floating-point numbers are stored in 4 byte 32-bit format in the range of 2.9387 E-38 to 1.70141 E38. BASIC formats how these are displayed based on their value. They are stored and displayed as SX.XXXXESTT where S is the sign of the mantissa and exponent, X.XXXX is the mantissa and TT is the exponent.
  • Arrays may be used as far as memory limits permit [ i.e.- 10 LET A(3,2,4,1)=4090 ], but should be DIM'd first. Default is 10 (11 elements).
  • String variables may be from 0 to 255 characters in length. Variable names may be as long as will fit on a line, but BASIC will only recognize the first two characters, so BL$ and BLACK$ will access the same variable in memory.


Communicating with BASIC

The MCB in today's environment will most likely find itself running tied to a PC using Hyperterminal as the interface. Originally they were no doubt tied to Video Data Terminals. Since this BASIC operates in ASCII text, it interfaces easily to most RS232 serial accomodating devices.

There are three routines which handle the transport of ASCII characters:

  • RXA - Receives a single TTY character from the keyboard.
  • TXA - Transmits a single TTY character to the display.
  • CKSIOA - Checks the status of the Z-80 SIO.

The routine CKSIOA loads the Transmit Buffer status bit into the Z flag and the Receive Ready flag into the CY flag. This routine is called several times througout BASIC to test for "ESC/BRK", so you will want to make sure either your routine sets the flags accordingly, or modify the rest of the code so it responds better to your particular UART/SIO device.

The small version, which is designed to run on the MCB which has not had any hardware modifications or memory expansion, bypasses any ram detection routines, and establishes the 2K on board as the entire usable ram area. This setting leaves about 1,500 bytes for user programs {not a lot there}.


Some Notes:

  • The unexpanded BASIC uses practically all the available 2K of ram. You won't be able to write a large program but ok for short routines.
  • SAVE sends the program in INTEL HEX format to the terminal. You must have a text capture file running in your terminal program to save this data. LOAD waits for an Intel Hex File to be sent, this can be a saved BASIC program, or a machine language program, matters not.
  • I have added a function so that you may use hexadecimal numbers within the program ( e.g. 100 OUT $68,$F0 )
  • It is not possible to CLOAD or CSAVE variables, since there is not an identified storage device for this computer board.
  • EXIT restarts BASIC for the time being.
  • Sorry, no printer output commands such as LPRINT or LLIST.

CLEAR command In Nascom BASIC, the CLEAR command has some interesting effects and a parameter not mentioned in their Basic User Manual.

CLEAR 250 sets the string space to 250 bytes.
While evaluating what the code was doing, I noted it can also have a second parameter such as this example:
CLEAR 100, 16283
The first parameter create a string space of 100 bytes, the second undocumented parameter sets the LSTRAM internal variable address at 16283 ($3F9B). This gives 100 additional bytes from $3F9C to $3FFF for storing your own machine language routine, for use with USR(x). Then it takes the 100 bytes of String space out below this new ceiling (16283). The second parameter directly sets LSTRAM in the BASIC internal variables, and resets all the other memory pointers for it. The LSTRAM parameter is what BASIC uses to see where the top limits for it's reach into ram are for storing strings. They do not necessarily reflect the actual ram in the machine. Since you can move LSTRAM with the CLEAR command, it is possible to 'clear' memory above BASIC for your own use.

Nascom BASIC came with several commands that just didn't make the cut. DOKE and DEEK (a double byte POKE and PEEK) used that same crappy signed 16-bit integer format that required you get out your calculator to figure out the actual address or data. DOKE is replaced with VECTOR nnnnn, which sets the location for the next USR(x) jump. DEEK is one of those buggy problems you will read about in just a moment. SET, RESET, and POINT all dealt directly with the graphic screen of the Nascom computer - I have left those commands intact so they may be used later on with a graphic LCD display, etc. but for now they simply collect the parameters and place them into BASIC's internal variable storage, and returns back to BASIC.

A function I added allows the use of hexadecimal values in calcs - Very Handy! If you want to set a VECTOR for the next USR(x) command, you can give it directly in hex (e.g. - VECTOR $0283). Most commands that involve calculations can use this, I have noticed it seems to function without error if you put the hex values in parenthesis for processing [ e.g. - PRINT ($34)+128 , or A=5*($8B) ]. Especially useful for use with the following commands:

  • OUT port,value
  • INP(port)
  • VECTOR address
  • USR(16-bit signed integer)
  • WAIT port,and-value,xor-value
  • AND value, OR value, NOT value
  • PEEK(address)
  • POKE address, byte value


Using Assembly Language with BASIC

BASIC has a portal through which assembly language routines may be called from with a BASIC program. The VECTOR command allows you to set the starting address of your routine, then the USR(x) command will cause BASIC to suspend and call your routine.

When BASIC initializes this jump to the FCERR routine internal to BASIC, so if you execute a USR command without first setting some other VECTOR, you will get an "? Illegal Function Call Error" from BASIC.

Machine code executes many times faster than similar code in BASIC, because of all the conversion and processing that has to be done for each character in code, whereas in Machine Code ("assembled assembly language"), each byte IS the instruction or data, this is processessing at its most fundamental level. There are several reasons that you might want to incorporate machine code into BASIC:

  • The speed to execute a certain function that would drag in BASIC.
  • To allow control over I/O devices of the Z80 board, i.e. SIO, p8253 timing, p8255 PIO, p8279 Keyboard/display interface.
  • You may need to access custom I/O devices you have added to your machine (i.e. LCD display, diskette drive, etc). Those devices may require special timing and status checks that would be cumbersome from BASIC.

The Steps to Use Assembly with BASIC:

1) Load your assembly language program.
- You may use the BASIC 'LOAD' command too bring in a previously assembled machine code program, provided it is saved in Intel Hex format. You will need to have it stored on your host machine and use Hyperterminal to Send Text File to the Z80.
- You may convert all the bytes of your rrooutine into decimal and use BASIC's POKE to place the code into ram.
This is very time consuming, and unless your routine is very short, this will add tons to a BASIC program.

2) Set the Vector
You may simply use the hex address with the VECTOR command as follows:
VECTOR $02BE
The decimal syntax for the vector command is VECTOR nnnnn, where nnnnn is the starting address of your machine language routine.
NOTE: The address is in signed 16-bit format from BASIC so the addresses range from 0 - 32767, -32768 - -1 as follows:
VECTOR 0 -------> $0000
VECTOR 32767 ---> $7FFF
VECTOR -32768 --> $8000
VECTOR -1 ------> $FFFF

It is not necessary to calculate the memory value in decimal, since VECTOR can use hexadecimal, thanks to a routine I added to BASIC. However, if you prefer decimal, one way to calculate addresses over 32767 is to take the decimal equivalent of the desired address, (i.e. 32768) and subtract 65536 from it. Thus 32768-65536=-32768, VECTOR -32768.
An address like $FFFF (65535) would render 65535-65536=-1, VECTOR -1. This is a pain in the rear, but that's how it works. Fortunately, the ROM and the parameter ram is located below 32768, so you shouldn't have to resort to this strange math very often.

The actual USR 'JP' command ($C3) is stored at $3123 ($4203 in the expanded memory version), and the vector right after it at $3124 (LSB) and $3125 (MSB), in the typical Z80 (8080) address format (low address byte, high address byte). A VECTOR 32767 ($7FFF) will be stored as:
$3123 - $C3
$3124 - $FF
$3125 - $7F

Executing the USR from BASIC
Example:
120 VECTOR 32767
130 A=USR(E)

3) Passing Parameters to/from BASIC

In BASIC, the USR command requires a parameter and returns a passed parameter. This makes it convenient to hand off a 16-bit signed integer to the machine language program, and to expect a 16-bit signed integer return.
In the above example:
130 A=USR(E)
E is the variable you send to your assembly program, and A is the variable that is returned. If you do not modify the FP registers, or if you do not care to pass parameters back and forth, you may use dummy arguments in these places. 'E' can also be any numeric expression. (i.e. A=USR(R*2+13), BASIC will calculate the expression, and leave the result in its internal FP registers for your use. If you don't alter the value in the FPREG, USR(E) will return E, unchanged. Bear in mind the parameter you send is in the same 16-bit signed format as the VECTOR address.

In order to properly pass parameters, your machine code needs to access a couple of BASIC internal functions:

DEINT is an internal routine that takes an Integer number from the Floating Point Register which is where the USR(param) is stored, converts and places this 16 bit value into the register pair DE (hence the name).
A jump to DEINT is stored at $1FF6 in the BASIC2 rom 2.
You can make a call to this location to get the USR(param) into register pair DE as a 16-bit signed integer.

ABPASS is an internal BASIC routine that takes a 16-bit signed integer that is in registers A (MSB) and B (LSB). While not a true 'register pair', this routine treats the number that way.
A jump to ABPASS is located at $1FF9.
You will need to make a CALL to this location at the point where you have placed your results in A and B, prior to your final RETurn to BASIC.

Both DEINT and ABPASS must be run from within your Assembly program, you cannot call them from BASIC before switching the vector command around to call your routine. BASIC operations will again alter your registers and parameters before you can get there to intercept them.

Optionally, if you are returning only an 8-bit unsigned you may return it to BASIC using the routine PASSA. A jump to PASSA is located at $1FFC. You should call this with your value in A before issuing your final RETurn to BASIC.

The vectors at this table are:
$1FF0 - JP START (BASIC Cold Start)
$1FF3 - JP WARMST (BASIC Warm Start) - You can use this to start BASIC and retain the program you had in Ram before you "EXIT"ted BASIC.
$1FF6 - JP DEINT (Pass variable from FPREG into DEINT).
$1FF9 - JP ABPASS (Pass 16-bit signed integer in registers A (MSB) and B (LSB) into the FPREG.
$1FFC - JP PASSA (Pass 8-bit unsigned integer in register A into the FPREG.

An alternate way to you may also pass parameters by placing them into ram and then having BASIC 'PEEK' those locations when you are done. This is a good secondary way to pass ASCII characters and other non-16-bit-integer data back into BASIC.


OTHER NOTES:

1) You may call monitor ROM routines with USR, provided VECTOR addresses them correctly.

2) An ? Illegal Function Call Error will result if you try to pass a parameter which is out of the 16-bit signed integer range.

3) When USR is called, BASIC sets up 8 levels (16 bytes) of stack storage area. The Stack area is quite large and should accomodate just about anything you can throw at it.

4) You should always remember that any exit condition to your program should leave the Stack unaltered, in other words you should POP off all the things you PUSHed onto the Stack before finishing up. Otherwise, you may crash BASIC, or some of your program may start acting very strange. Most likely it will crash.

5) You can change memory and registers at will with machine code, so be careful what you change.
The NASCOM BASIC manual suggests any interrupt handling save the Stack, all registers AF,BC,DE,and HL, and be sure to execute an "EI" before you return if you ran a "DI" during your Assembly program.


Memory Locations for Your Code

Since there is so little ram available in the BASIC2 version, I recommend starting BASIC and issuing a CLEAR 100, $3700 command. Then load your machine code program into the area from $3700-$37FF. BASIC, following the CLEAR will not attempt to use the area above the defined ceiling (second paramater of the CLEAR command). This is useful, but strongly recommend you keep it as small as possible to allow as much room for BASIC to operate as possible.

If your routine uses only 30 bytes, you should try CLEAR 100, $37FF-31.
This way, you are only taking away from BASIC what you need. You'll need to assemble your routine so that it starts at the resulting location and end as close to $37FF as possible.


Honorable Mentions - Those who threw in and helped me with this:

Grant Searle - Who fixed my modified commands which allow the use of Hexadecimal numbers in such commands as OUT(port,data) and fixed the conversion of numbers to Hex from within BASIC. Many thanks.

Mike Brearly - Who repaired my poor Intel Hex Save routine and made it less messy. Many thanks.


Click HERE if you do not see a menu frame to the left.

This page created September 18, 2004. Modified April 13, 2011.