https://nanochess.org/ecs_basic_2.html
"
[logo]
Main page
Intel 8080 emulator
Chess programs
Contests
Store
Retrogaming
FAQ
Links
About me
Ver en espanol
Completing a BASIC language interpreter in 2025
The printed source code of my UFO game. The paper roll is really old.
This is a follow-up to my previous article Developing a BASIC
language in 2025, where I describe how I got inspired to start coding
a new BASIC interpreter for the 1983 Mattel ECS add-on for
Intellivision.
Although my interpreter was already pretty fast and with enough
statements to build games, I wasn't satisfied because it still missed
one thing that the ECS BASIC implements: text strings. Only two, A$
and B$, with GET and PUT, for things like getting a name from the
keyboard or showing a name.
I thought about strings for four days, then I decided to code things
like I know what I was doing. I added a string stack pointer
bas_strptr where any created string is added.
The first thing to implement was an array for the string variables
(A$-Z$) each element pointing to the current string contained (or
zero if none). I modified the whole of the expression parser to
insert the type in the Carry flag (Clear = it is a number, Set = it
is a string), then I made the first string support in the language
where it detects if a string name appears (letter plus the $ sign)
and reads it and copies it to a new string on the stack, returning
this pointer as expression value (and of course the carry flag set)
The next step was assigning string variables, it simply took the
pointer and stored it into the respective string variable pointer. Of
course, I was afraid that I was creating a monster because I wasn't
planning for the garbage collector.
Then I went full-steam ahead and put support in INPUT, PRINT, and
added the concatenation of strings using the plus operator, also the
functions ASC, CHR$, LEN, LEFT$, RIGHT$, MID$, INSTR, VAL, and STR$.
Now I had string support in my BASIC language for the ECS, but at
some point in the execution it would fill up the stack, and crash
with an "Out of Memory" error.
Garbage collection
It was kind of crazy having a BASIC with string support but no
garbage collection. I needed a way to copy strings into the
respective variable, and delete the work-in-progress strings created
as expressions are evaluated.
It would be easy when having a C memory management system as you only
have to replace the pointer, and free the original. But any memory
management comes with headers and linked lists, extra memory
requirements, and slowness. Given the Intellivision CP1610 processor
is already slow enough (894 khz), I decided against it.
However, I noticed that temporary strings are only created inside the
expression parser. So what about a double stack? One stack for
strings in variables, and one stack for temporary strings.
I added a secondary pointer bas_strbase (I like how it sounds like
star base)
At the start of each statement, bas_strbase is copied to bas_strptr
(thus effectively erasing the temporary strings) A problem needed to
be solved: growing bas_strbase on each string assignment.
I was going to implement the most simple solution: go over the 26
string variables doing comparison and movement of pointers, and
insert the new string in its place.
Just as I was coding this, I noticed I had an easier solution. As I
was working with 16-bit words, not all values are used. I could use a
value like 0xcafe to mark a non-used space, and boom! I had an idea.
When doing the assignment, delete the original string (fill it with
0xcafe words), now explore the strbase area to find a string of
0xcafe words big enough to save the new string.
The better part is when there is no space for the string, I simply
copy the string pointer as the new bas_strbase pointer (effectively
growing the base memory area), and all words between the end of the
string and the previous bas_strbase pointer (ahead in memory) are
filled with 0xcafe words.
Full string support with garbage collection at very small price of
performance. Exactly what a CP1610 processor needs.
STRING_TRASH: EQU $CAFE
;
; String assign.
; R1 = Pointer to string variable.
; R3 = New string.
;
string_assign: PROC
PSHR R5
MVII #STRING_TRASH,R4
;
; Erase the used space of the stack.
;
MOVR R3,R2
MVI@ R2,R0
INCR R2
ADDR R0,R2
MVI bas_strbase,R0
CMPR R0,R2
BC @@3
@@4:
MVO@ R4,R2
INCR R2
CMPR R0,R2
BNC @@4
@@3:
;
; Erase the old string.
;
MVI@ R1,R2
TSTR R2
BEQ @@1
MVI@ R2,R0
MVO@ R4,R2
INCR R2
TSTR R0
BEQ @@1
@@2:
MVO@ R4,R2
INCR R2
DECR R0
BNE @@2
;
; Search for space at higher-addresses.
;
@@1:
MVII #start_strings-1,R2
CMP bas_strbase,R2 ; All examined?
BNC @@6 ; Yes, jump.
@@5:
CMP@ R2,R4 ; Space found?
BNE @@7 ; No, keep searching.
CLRR R5
@@8:
INCR R5
DECR R2
CMP bas_strbase,R2
BNC @@9
CMP@ R2,R4
BEQ @@8
@@9:
INCR R2
MVI@ R3,R0
INCR R0
CMPR R0,R5 ; The string fits?
BNC @@7
;
; The string fits in previous space.
;
MOVR R3,R4
MOVR R2,R5
MVO@ R2,R1 ; New address.
@@10:
MVI@ R4,R2
MVO@ R2,R5
DECR R0
BNE @@10
PULR PC
@@7:
DECR R2
CMP bas_strbase,R2
BC @@5
;
; No space available.
;
@@6:
MVO R3,bas_strbase ; Grow space for string variables.
MVO@ R3,R1
PULR PC
ENDP
Program for my ECS BASIC interpreter. [ecsbasic_15]
Example of a parser for a text adventure game using string functions.
Going mathematic
BASIC mathematical functions on my ECS BASIC interpreter.
Since my floating-point library was complete with the four
operations, I had an ace under the sleeve: I already had tested sin
and cos functions with it, but for some reason these had a bug. For
sin(1deg) the resulting value was 0.0172.
These functions were ported from my Pascal compiler for transputer.
As Pascal happens to have exactly the same mathematical function set
as a BASIC interpreter.
After a whole day examining the operation instruction-by-instruction
(the jzintv debugger shines here), I discovered that I did a
comparison in the wrong way and corrected it.
I was so happy that I went immediately to port the remaining
mathematical functions (ATN, TAN, LOG, EXP, and derived SQR and the
power-of ^ operator). There were no pitfalls along the way, except
one, my BASIC has a mantissa with one extra bit of precision, and EXP
(LOG(64)) returned 63.999999
Both operations use a multiplication with a constant (log does it at
the end, and exp in the start). I noticed that the value was
misrounded for 25 bits of mantissa, so I calculated a better
constant, and et voila! EXP(LOG(64)) returned 64.
Making it easier for the user
A lot of BASIC interpreters in the eighties didn't supported
instructions for graphics. The Commodore 64 was particularly known
for requiring POKE for almost anything, unless you had the somewhat
expensive Simon BASIC cartridge.
However, in the Intellivision you have few graphics capabilities. In
the Color Stack mode you have something called Colored Squares. This
means each tile on the screen (20x12) can have four colors. This
means a bloxel resolution of 40x24, and each bloxel can have one of
eight colors (one being the background).
I implemented PLOT with these limitations, and also added PRINT AT
(for putting text at any screen position), and TIMER to measure time.
One of the most difficult things was implementing the floating-point
number parsing. I finally decided to approach it like parsing an
integer, taking note of the number of digits parsed, and take note of
the position of a period. Once it reaches the biggest number it can
represent (9,999,999) then it starts ignoring any further digit (but
it keeps counting them)
The final calculation step is to multiply it, or divide it taking in
account the period position. Also taking in account any exponent
present (for example, e+1 or e-3)
It wasn't so expensive in computation time. I added along a FRE(0)
function to know how much space remains for writing programs.
It is the eighties
Let's suppose we are working to make this BASIC interpreter really
useful for the Mattel ECS. We still need two things: cassette, and
printer.
Fortunately, a lot of people at Atariage Forums have worked along the
years to decipher the ECS hardware (thanks to intvnut, lathe26, and
decle)
The ECS contains the hardware to interface to a cassette recorder/
player at 300 bauds with FSK (Frequency-Shift Keying) of 2400/4800 hz
(technically this is a modem) and it also includes a UART (Universal
Asynchronous Receiver/Transmitter) patterned losely after a Motorola
MC6850, but the frequency selector is separated, allowing to turn on/
off a relay (cassette remote control), and to switch between two
ports (the cassete and the AUX port for the printer)
Now for the cassette, I was going to use 300 bauds, this means around
30 characters per second. Do you remember your 56K modem? It was 186
times faster! I needed to optimize my BASIC as I was using token
numbers above $0100, so I moved them to the area $0080-$00ff. Now all
the words are only used in the lower 8 bits, and the tokenized
program can be saved as bytes.
I coded the cassette routines based on code published by decle in his
article ECS Text Editor written in IntyBASIC with tape support and
added LOAD, SAVE, and VERIFY.
I was very happy when these cassette routines worked in emulation,
and I ordered cables from Amazon for trying to record and play in my
cellphone.
For some reason probably related to audio levels and automatic
compression, I could record audio from the ECS in my cellphone, but
playing it back never resulted in anything.
I was tired, and I decided to try my PC. I connected the ECS to the
Mic In, and Line Out, and same problem. Besides the Windows utilities
make amazingly hard to change the source and playing line. I got the
Audacity program, and it has the line input/output options easily
selectable. Again no results.
I wrote a small program to read the UART continuously, and I couldn't
see anything. I decided to try the Audacity's amplify effect, and et
voila! My UART program started throwing decoded bytes. I stopped the
program, and I tried the VERIFY command (remember I had just saved
the same program), but it didn't worked. Worst, when I ran again my
test program, I didn't got any data!
I revised my setup values for the UART, but nothing. I was mystified
for some hours until I got memories of a chip that basically went
nuts if you accessed it too fast. Could it be that? Is the CP1610 so
fast? I added a delay after every access to the UART chip.
I typed again my test program, I did SAVE, recorded on the PC,
amplified it, I RUN my program, and played the audio back. Ok, UART
was reading things. Now I stopped the test program, I did VERIFY, and
I played the audio back from my PC. The longest 20 seconds of my
life. And it worked!
Immediately I resetted the ECS, losing the program, and I did LOAD
(of course, playing back the audio), and again it worked!
My UART test program after a successful LOAD statement.
My UART test program after a successful LOAD statement.
Notice that although it saves BASIC programs, these programs aren't
compatible with the original ECS BASIC because it is a completely
different BASIC language.
Break time: As I couldn't lose programs anymore, I decided to test my
BASIC language with a "real" long program. So I took Tim Hartnell's
Giant Book of Computer Games (Mexican edition), and I typed the
Reversi game. I had to adapt it, because my BASIC doesn't allow for
multidimensional arrays, and the screen positioning. I found a few
bugs in my interpreter (INPUT W$ still wasn't written with the
garbage-collector support, and the variables weren't deleted properly
on RUN), but it was amazing to watch the Reversi game playing against
me. I've put a WAV file recording hhere.
Reversi game from the Tim Hartnell's Giant Book of Computer Games
book working with my BASIC interpreter for the Mattel ECS.
Reversi game from the Tim Hartnell's Giant Book of Computer Games
book working with my BASIC interpreter for the Mattel ECS.
The printer is in the room
After reading Aquarius Printer Technical Info and Reverse Engineering
and the jzintv ECS document, I decided using the printer was very
easy, and I went to buy a Mattel Aquarius sourced locally because it
included the printer and some thermal paper.
While the printer was in shipping process, I implemented LLIST, and
LPRINT. I modified the core of both statements to access the output
through an indirect function. So you only change the pointer to
target the screen or the printer. I detected here a bug in jzintv
that prevents it from outputting the printer data to a file.
I got the Mattel Aquarius along the printer a few days later. I had
to clean it because it was pretty dusty. The printer doesn't have the
top cover that protected the paper roll, but it included a paper
roll, and fortunately it still had the cylinder that helps the papel
to roll.
Mattel Aquarius computer with expansion board, two games, cables, and
the Aquarius printer.
Mattel Aquarius computer with expansion board, two games, cables, and
the Aquarius printer.
I adjusted the paper, powered on the printer, and verified I could
advance the paper (having a working motor is 90% of the printer).
I built the cable with the instructions from lathe26's article, and
the first time it didn't worked (I grounded the CTS cable
accidentally), but after correcting it, I expected trash for my first
print, instead, I got a pretty nice printing!
Of course, I couldn't resist printing some listings, and a sine wave.
The printed source code of my UFO game. The paper roll is really old.
The printed source of my UFO game. The paper roll is really old.
What remains to do?
I added the DRAW and CIRCLE statements, and POINT functions to
complete the graphics support. These are enough to make some nice
games without using sprites. I made a graphics demo for filling the
screen with lines, and I noticed my pseudo random number generator
didn't covered the screen, so I had to improve it.
DRAW program for my ECS BASIC interpreter.
DRAW program for my ECS BASIC interpreter.
Also I added the POS and LPOS functions to know the horizontal
position of the cursor. The SPC and TAB functions for PRINT. Plus a
HEX$ function to ease system programming.
In the tokenization table, I added placeholders to expand the
language and don't break compatibility with any cassette tape being
created.
With this it has become a full-fledged BASIC interpreter for the
Mattel ECS that uses 19 kilowords, instead of the 24 kilowords of the
slow and limited Mattel ECS BASIC interpreter.
I don't see anything more I could do in the near future, except maybe
expanding the editor to be a full-screen editor. Currently, it is a
line editor that reads its input from the screen.
At this point, it is a fun experience the process of typing BASIC
programs in the ECS, and watch the results back. You can save the
programs, or print it. And of course, you can only imagine the
success that Mattel Electronics would have enjoyed if they put
together a good BASIC with its Mattel ECS.
Small statistics of the assembler code:
* basic.asm: 5333 lines.
* fplib.asm: 718 lines.
* fpio.asm: 462 lines.
* fpmath.asm: 516 lines.
* uart.asm: 341 lines.
* Total of 7370 lines of assembler code written between Sep/17 and
Oct/12, around 300 lines written daily.
The source code is released at https://github.com/nanochess/ecsbasic.
I tried to release it so early as possible, so you can get a glance
of how it was growing in the commits.
Enjoy it! Did you like this article? Invite me a coffee on ko-fi!
Related links
* My BASIC interpreter for Mattel ECS, full assembler source code,
and ROM for running on Flash cartridges (don't forget to enable
the 8K of extra RAM)
* Typing BASIC on my ECS. The original thread where I posted my
experiences typing a game on the ECS, and later started posting
about my BASIC interpreter.
* The IntyBASIC cross-compiler.
* The history of the ECS from the people who developed it.
Last modified: Oct/12/2025