One kilobyte Tiny BASIC for the 8080

BASIC for the 8080, fits in 1K of memory, runs BASIC games on a 2K system. Features similar to Palo Alto Tiny BASIC.

Similar projects worth following
This project is a birthday present for the Intel 8080. I wanted to investigate writing a BASIC interpreter in assembly language and decided to fit a BASIC interpreter into 1K. It’s not the only sub-1K BASIC that I’ve come across, but as far as I can see it is the most feature rich and also makes efficient use of memory for storing programs. It is probably fast compared to other Tiny BASICs because programs are tokenised. The set of features is similar to Palo Alto Tiny BASIC (PATB) but the syntax is slightly different in a few places. It supports GOSUB/RETURN, FOR/NEXT loops, and functions RND, ABS and USR. It has a single array variable @. Since it fits in 1K, it is about 60% of the size of the smallest PATB implementation. It is written in 8080 assembly language, and developed on a bespoke 8080 emulator. It hasn’t been run on a real 8080 yet. If anybody has a working 8080 system I’d be really interested in seeing it running.
  • Z80 version

    will.stevens2 days ago 1 comment

    I’ve started work on a Z80 version. It will be at least 24 bytes shorter than the 8080 version due to the Z80s two-byte relative jump instructions. There will probably be a few other places where space can be saved due two differences between the 8080 and Z80. For example, the LDIR and LDDR instructions might end up making memory move subroutines that are shorter than the memory rotation subroutine used for program editing. 

    It would be good to be able to implement PEEK, POKE, IN and OUT instructions. 17 bytes will be needed just for the keywords, and maybe 25 bytes to implement all of these, so it probably will be possible to fit them in.

    I’ve made a change to the 8080 version so that it should also run on a Z80. In PrintSub I found a way to avoid using the parity bit without using any more space. This change is committed to GitHub, and when time permits I will rerun all tests and make release v1.0.1 with this fix.

  • Released v1.0.0

    will.stevens7 days ago 0 comments

    The current version of 1K Tiny BASIC passes all tests and runs all example programs. I’ve released it in GitHub as v1.0.0

  • Multi-statement lines and error codes

    will.stevens04/04/2024 at 23:02 0 comments

    I was recently able to reclaim 2 bytes in the integer parsing subroutine. With these two bytes I’ve added support for colon as a statement separator. Multi-statement lines were already allowed with no separator between statements. Adding the optional colon makes this slightly more compatible with existing programs.

    I’ve also added the file errorcodes.txt to the GitHub project, to explain the code and meaning of the 16 different error codes that can occur.

  • Code rearrangement

    will.stevens04/01/2024 at 17:13 0 comments

    During testing I came across an error where -32768/2 was being interpreted as -(32768/2) rather than (-32768)/2. Since I had hit the 1K limit, some significant work was required to free up enough memory to fix this. The changes were:

    • Make all calls to ExpEvaluateNum happen from pages 0,2 or 3, and place all operator subroutines on page 1. So that ‘empty operator stack’ can be detected using that distinction.
    • Also place ABS, USR, RND on page 1, and all other token subroutines on page 2. This makes more space available for implementing token subroutines and reduces the out of page jumps required when all had to start in page 2.
    • Change the way that end-of-TokenList is detected when searching for Tokens, saving 1 byte. This requires the last byte of  TokenList to be at address 03FFh. This fits in well with the other changes listed above.

    These changes made it possible to effectively add a Negation operator and give it highest precedence.

    Several bugs were introduced and subsequently fixed during the course of doing this.

  • Code rearrangement

    will.stevens04/01/2024 at 17:13 0 comments

    During testing I came across an error where -32768/2 was being interpreted as -(32768/2) rather than (-32768)/2. Since I had hit the 1K limit, some significant work was required to free up enough memory to fix this. The changes were:

    • Make all calls to ExpEvaluateNum happen from pages 0,2 or 3, and place all operator subroutines on page 1. So that ‘empty operator stack’ can be detected using that distinction.
    • Also place ABS, USR, RND on page 1, and all other token subroutines on page 2. This makes more space available for implementing token subroutines and reduces the out of page jumps required when all had to start in page 2.
    • Change the way that end-of-TokenList is detected when searching for Tokens, saving 1 byte. This requires the last byte of  TokenList to be at address 03FFh. This fits in well with the other changes listed above.

    These changes made it possible to effectively add a Negation operator and give it highest precedence.

    Several bugs were introduced and subsequently fixed during the course of doing this.

  • Future directions

    will.stevens03/23/2024 at 16:05 0 comments

    Since version 1.0 of the 8080 version is likely to be ready for release soon, I have been thinking about future avenues of work. The following things seem interesting to me at the present time:

    1. A Z80 version. This will be smaller and faster than the 8080 version, primarily because the Z80 has 2-byte relative jump instructions. I think that this will save a few dozen bytes, which could be used to implement extra functionality and remain under 1K.
    2. A minimalistic Z80 version. By removing FOR/NEXT and some other features, and changing how lines are stored in memory, the code size could be reduced by several hundred bytes. I’m interested in seeing whether I can get down below 512 bytes, since the current smallest minimalist BASIC (Oscar Toledo’s bootBASIC) is 512 bytes long.
    3. A 6502 version. It is not likely that 1K Tiny BASIC will fit in 1K when ported to the 6502, but it will probably fit in less than 2K, and there is currently no other 6502 Tiny BASIC that fits in less than 2K.
    4. Tiny BASIC with constant strings. By ‘constant strings’, I mean strings that are defined in the BASIC program code. They can be assigned to variables, be part of expressions involving comparison and substring operations, and printed, but new strings can’t be created at run time and there is no concatenation operator. No string memory management is needed, but lots of useful things can be done with constant strings. (Compared with most Tiny BASICs which usually only allow constant strings in PRINT statements).

  • Recent work

    will.stevens03/16/2024 at 08:09 0 comments

    I fixed the operator precedence issue, so that * and / have equal precedence. I also changed to using an LCG pseudo-random number generator because it was shorter. A consequence is that the RND function only uses the high order byte of the RNG state, because the low order bits have a small period. So RND should not be given a parameter larger than 256.

    I fixed an issue with the expression evaluator where an incomplete expression would cause it to try to RET without a return address on the  stack. 

    I’ve listed the meaning of all error codes in errorcodes.txt

    The current version passes all tests and runs all example programs. If no other errors are found it will become version 1.0

    I’ve also found a way of simulating keyboard input in Stefan Tramm’s emulator using a short JavaScript program that sends key events to the emulator - I’ll make this available when it’s finished. This will be a good way of scripting tests cases that test error handling and error codes.

  • Running it on another emulator

    will.stevens03/01/2024 at 15:20 0 comments

    I recently came across a nice online 8080 emulator written by Stefan Tramm on which I can run 1K Tiny BASIC. Up until now, I have only run it on my phone, on my own emulator written in python. It’s good to see it running on a different emulator. The emulator is here:

    I could not get all of the features of this comprehensive emulator running in my browser, but I did find out how to load a hex file from my PC into the emulator by dragging and dropping into the browser window, then reading it with ‘r ptr:’

    I need to make a change to 1K Tiny BASIC : currently I have used CR as newline, but this emulator requires the sequence CRLF. This change will cost 3 bytes.

    Stefan Tramm has included a version of Palo Alto Tiny BASIC with his emulator.

  • Operators and precedence

    will.stevens02/11/2024 at 07:33 0 comments

    A discussion about this project on a forum on led me to rewrite the code for the comparison operators because they didn’t handle edge cases correctly. E.g. I had implemented a<b as a<=b-1. This doesn’t work if b=-32768 because -32768-1 wraps round to 32767.

    I rewrote the code to handle this correctly. I also came across another more significant error where I had made incorrect assumptions about the behaviour of the carry flag. It took 6 bytes to fix both of these problems: now <= and >= test for equality first, then fall through to < and >. Comparison is now done using SUB and SBB instructions rather than RST_NegateDE and DAD.

    The way that I have implemented operator precedence is a problem. In my implementation no two operators can have the same precedence, whereas in other BASICs operators can have equal precedence and are evaluated in left to right order. E.g. in some other BASICs + and - have the same precedence, and * and / have the same precedence. My operator precedence code is 5 bytes long. Changing it to allow some operators to have equal precedence would increase this. Because BASIC programmers don’t usually use more than one comparison operator in an expression, and because having - as higher precedence than + has not seemed problematic so far, I think it is only * and / that may cause problems when running existing BASIC programs (I discovered this when running Gordon Henderson’s Mandelbrot set program that runs in Tiny BASICs). E.g. 3*4/3 evaluates as 3*(4/3)=3 rather than (3*4)/3=4.

    I notice that some BASICs don’t have equal precedence for * and /. ZX80 integer BASIC gives * higher precedence than /, whereas in ZX81 BASIC they are equal precedence. So I could do the same and make * higher precedence than / at no cost (just rearranging the code). Or I could try to be consistent with other Tiny BASICs. I think that this could be done at a cost of 2 bytes by applying a bit wise OR operation to the operator code before comparing it with the operator on the stack during evaluation, but exactly how this will work will depend on the operator function address. This is the kind of nasty dependency  that occurs if trying to squeeze as much functionality as possible into 1K. I will leave this issue until I have thoroughly tested all other behaviour of operators and expressions, so that I can be reasonably sure that address will remain fixed.

  • Error messages

    will.stevens02/04/2024 at 10:16 0 comments

    Rather than using up memory on lengthy error messages, 1K BASIC displays the address (in decimal) after the call instruction that called the error subroutine. This means that error codes change during development, but has the advantage that you can distinguish one kind of error from another. For example at the time of writing this ‘E706’ means an unrecognised token was encountered during parsing.

View all 20 project logs

  • 1
    Assemble the source code

    Update: A pre-assembled HEX and LST file are available in GitHub.

    Assemble the basic8080.asm source file using an 8080 assembler. I used the online assembler here: . You may need to edit the PutChar subroutine and character input code in NextCharLoop to port it to your system. They currently work with the emulator linked to in the next step.

  • 2
    Load the HEX file

    Load the HEX file into an emulator or suitable hardware. I used from here because it runs on Pythonista on a phone: 

    I recently came across Stefan Tramm’s online 8080 emulator, and I’ve modified 1K Tiny BASIC to run on it. To run it, download the basic8080.hex file from GitHub, and drag it into the terminal window of Stefan’s emulator. This will set up a virtual paper tape reader, and the hex file can be read in using r ptr: Run the program using g 0

    The emulator is here:

View all instructions

Enjoy this project?



Lee Hart wrote 03/16/2024 at 20:49 point

I tried your latest 15 Mar 2024 version, and it works. I was able to successfully type in your LIFE program and run it using the emulator. :-) Two peculiarities, though:

1. The "-" key does not work, but the keypad "-" key does work.

2. LIFE displays a glider the first time it's run, which marches across the screen. The second time it's run, it starts with a different pattern that dies off after one generation. The third time, the glider re-appears and it runs the same as the first time.

3. I can't find a way to break out of the emulator. What is the "control-full-stop" command you mentioned in an earlier comment to get back to the command mode (to enter a BASIC program as a HEX file)?

  Are you sure? yes | no

will.stevens wrote 03/19/2024 at 10:22 point

I found the same issue with the ‘-‘ key. I wasn’t able to reproduce the issue you found with ‘life’ - I can run it okay 3 times in succession. But I loaded it as a hex file rather than type it in, so I’ll try typing it in when time permits. The key combination to get back to command mode is CTRL-. (I.e. hold down control key and press full-stop/period key). After loading life_0x0400.hex, type “g 00de” to get back to BASIC.

  Are you sure? yes | no

will.stevens wrote 03/23/2024 at 15:18 point

I tried typing in life.bas but couldn’t replicate the error you found - after typing it in it ran 3 times in succession with no problems.

  Are you sure? yes | no

SHAOS wrote 03/12/2024 at 04:59 point

Looks great! May it work on more than 1K RAM? What is theoretical size limit for the program?

  Are you sure? yes | no

will.stevens wrote 03/12/2024 at 06:14 point

The RAM size has been set to 1K during development only because I have a lot of old 2K RAM chips and it is nice to be able to have the BASIC interpreter and any of the example programs all fit under 2K. 

RAM_BASE and RAM_TOP can be set to anything up to 63K. Maximum BASIC program size is RAM size minus 64 bytes for variables, minus 8 bytes for a small buffer used by the INPUT statement, minus stack space used by the interpreter and by FOR/NEXT and GOSUB/RETURN, minus a few bytes needed to be able to run direct mode commands without colliding with the stack. So on  1K RAM system this is about 900 bytes and with 63K about 62.9K.

(I wasn’t quite sure if you were asking about size limit for the BASIC program or BASIC interpreter - the interpreter probably can’t be made much less than 1K. Although I am still finding code size savings of 1 or 2 bytes, these are increasingly hard to achieve. It’s possible that a rewrite from scratch might be able to do better than 1K, since the current implementation was developed a feature at a time, rather than being planned. Also, I am planning to make a Z80 version once this version is finished, which should be a few dozen bytes shorter than 1K due to the slightly higher code density of the Z80 compared to the 8080.)

  Are you sure? yes | no

SHAOS wrote 03/13/2024 at 04:35 point

Thanks! Yes, I meant RAM for program and data

  Are you sure? yes | no

Lee Hart wrote 03/07/2024 at 05:06 point

I tried the 6 Mar 2024 build in Stefan Tramm's 8080 emulator. I can load and run Tiny just fine! It certainly is BASIC. :-)

I tried running your LIFE program, but get an E170 error. It appears the array doesn't work. For example, LET @(53)=1, then PRINT @(53) prints 0 (not 1).

Re your 5 Mar 2024 comment on ExpEvaluate at RST 7: My Altaid 8800 calls RST 7 if there's a hardware interrupt (serial input or timer). It would seem I just have to replace your CALL ExpEvaluateNub at 0038 with a JMP to my interrupt handler. It would check for an interrupt, process it if found, restore registers, and jump back to 003B to resume BASIC. Or is there something tricky in your code that I'm missing?

  Are you sure? yes | no

will.stevens wrote 03/08/2024 at 00:08 point

E170 means that it was expecting to  find a variable name but found something else. I haven’t been able to try long programs on Stefan Tramm’s emulator - haven’t been able to work out how to automatically enter a program from a file so can’t easily try to replicate the error.

The array begins at the first byte of free memory after the last thing parsed, so when in direct mode where the array starts depends on the last thing entered. If you try LET @(10)=1234 PRINT @(10) all on one line with no newline between the statements that should work (that is how multi-statement lines are entered in 1K BASIC - no separator between statements). I don’t think that your suggestion regarding the interrupt handler would work because every time there was an interrupt it would try to evaluate an expression. I would like to make the changes that you need for it to work on the Altaid 8800 - it might take me a few days to do. I’ll make a new branch in GitHub. To summarise what I think is needed: lower 4K of memory space is ROM, above that is RAM. Leave three bytes free at address 0038h for jump to Interrupt Service Routine. BASIC will occupy lower 1K plus about 20 bytes after making the changes to accommodate the ISR.

  Are you sure? yes | no

Lee Hart wrote 03/08/2024 at 07:28 point

Yes, I had to start your 1k BASIC, and then type your LIFE program into Stefan Tramm's emulator by hand. Tedious. I couldn't load it as a HEX file, nor cut-and-paste it in.

The E170 error occurred when LIFE tried to execute line 30 LET @(53)=K (or any line with @(number). I didn't realize how immediate mode worked with @ (nor that you could have multiple statements on a line -- clever!)

Regarding interrupts on RST 7: Ah, I see your point. I thought the interrupt handler would read the input port to find the source of the interrupt, service it accordingly, and then return *without* executing BASIC's CALL ExpEvaluateNum and CNC Error instructions. But if *no* interrupt source bit was set, then BASIC must have executed an RST 7, so it would execute BASIC's ExpEvaluateNum and CNC Error instructions before returning.

But, it's possible that BASIC calls RST 7, and *then* an interrupt occurs. So the interrupt handler can't tell whether to execute them or not. More thinking (and testing) is needed.

  Are you sure? yes | no

will.stevens wrote 03/09/2024 at 06:52 point

I’ve made a hex file life_0x1000.hex corresponding to life.bas. I loaded it into Stefan Tramm’s emulator by first of all loading and running basic8080.hex, then when the basic prompt appears press “control full stop” to get back to the emulator command line, then loading life_0x1000.hex then typing “g 00DD” to restart basic from “Ready”. It ran okay when I did that.

  Are you sure? yes | no

Lee Hart wrote 03/01/2024 at 16:12 point

I converted your ASM for the A85 assembler (since I use it for my other projects). I removed the RST macros, changed Labels&0ffh to LOW Labels, fixed commands in leftmost column, and inconsistent upper/lower case labels. Your 2/28/2024 version assembles without errors, but is 1 byte over 1k.

I look forward to seeing your LST and HEX files, so I can compare them to mine for errors. My goal is to get this working in my Altaid 8800 with a 2k ROM (the other half being the monitor and front panel software).

  Are you sure? yes | no

will.stevens wrote 03/02/2024 at 08:40 point

The version from 28th Feb had a few problems. I’ve fixed them and made a Version0.9 branch which contains a LST and HEX file. This version runs all the example and test programs okay. The remaining issue that I need to fix is making * and / equal precedence.

  Are you sure? yes | no

Lee Hart wrote 03/04/2024 at 21:27 point

Thanks! I updated my A85 version to match your 3 Mar 2024 version. It assembles and produces an identical HEX file. :-)

The monitor for my Altaid 8800 is about 3k, so there is room for it and your BASIC in a 4k EPROM. I'm working to move the Monitor up to free the lower 1k for your BASIC (as it looks difficult to re-assemble it to run at a higher address).

 Are there any problems re-assembling your BASIC to use a higher RAM starting address?

Another challenge is that my hardware uses interrupts, which has its vector at 0038h. I'll have to move ExpEvaluate somewhere else. Luckily, it's not essential that your BASIC fits in *exactly* 1k. The same tricks you used will certainly reduce the size of my monitor!

  Are you sure? yes | no

will.stevens wrote 03/05/2024 at 00:14 point

I don’t seem able to post a reply to your most recent reply, so I’m replying to the previous one. I’ve just committed a version with RAM starting at 4K and that seems fine. I made a change to where EndProgram is located so that the high byte of the start of RAM no longer gets treated as an opcode when EndProgram is called. Replacing RST_ExpEvaluate with CALL ExpEvaluate will add about 16 bytes + however many bytes you need for your interrupt routine. Because this will move DivSub up to an address higher than 02FF, something will needed to be moved out of the way. Try turning ‘Error:’ into a subroutine above 1K (and replacing it with ‘CALL Error’ so that the fall throughs still work). The single POP D will need to be replaced with two POP Ds so that Error still works. This will make 7 bytes of space, so DivSub won’t move too much. (but check that LineNumSub is >= 0223). I’m happy to help with making and testing any changes because I’d like to know that it runs on a real 8080!

  Are you sure? yes | no

will.stevens wrote 03/06/2024 at 17:15 point

When thinking more about replacing RST_ExpEvaluate with CALL ExpEvaluate, there are a couple of problematic instances near labels GosubSub and ForWithStep where the straightforward replacement won’t work. I’ll look for an alternative for these two instances.

  Are you sure? yes | no

Lee Hart wrote 02/27/2024 at 05:23 point

BASIC in 1K is an impressive feat!  The only smaller "high" level language I've run across is VTL (Very Tiny Language), which is around 768 bytes.

I'm having trouble figuring out your source. Do you have a LST file, so I can tell what code your macros actually assemble into?

I offer an 8080 kit (google "Altaid 8800"). It would be fun to put your BASIC in its ROM. :-)

  Are you sure? yes | no

will.stevens wrote 02/27/2024 at 15:03 point

Thanks. It’s currently undergoing a few changes (to reclaim a few bytes in order to fix the few remaining issues). When it’s next in a working state I’ll put the LST and HEX file into GitHub. There are probably one or two places where the tricks needed to squeeze it into 1K need attention if burning it into a 2K ROM (one that springs to mind is that the opcode corresponding to the hi byte of address of start of BASIC program must be a single byte instruction that has no harmful side effects - currently this is 04 but 08 should work just as well). The two most complex macros use RSTs like multi-byte instructions. They are RST_JZPage (same page JZ to single-byte address following the RST call) and RST_CompareJump (compare A with byte immediately following RST call and same page jump to single-byte address if equal). Altaid 8800 looks really neat.

  Are you sure? yes | no

zpekic wrote 02/22/2024 at 04:37 point

Very interesting. I am exploring writing or adapting some version of Basic for my custom CPU, currently I am leaning towards which has a "TBIL" - Intermediate Language (sort of p-code but for Basic) with the idea to write interpreter for the TBIL for my processor while keeping the actual TBIL code the same. But here I like the generic parser driven by sort of "regex lite" syntax. If such generic parser could be extended to include pointers to routines that execute and additional data (such as operation priority, availability in program or command mode etc.) it could be even more powerful. A single data table + generic parser / execution engine could be used to implement variety of Basic "dialects".

  Are you sure? yes | no

will.stevens wrote 03/12/2024 at 06:28 point

Thanks. It is interesting that it has ended up smaller than non-tokenised Tiny BASICs. I suspect that tokenisation has actually made it smaller (and faster) than non-tokenised Tiny BASICs, because it only needs to do one task at a time and each task can be done without much register swapping, and without needing RAM-based variables (apart from storing the location of the BASIC program and the RNG state). Whereas non-tokenised Tiny BASICs need to switch between parsing the program and running it.

  Are you sure? yes | no

Boxerbomb wrote 02/21/2024 at 22:03 point

Check out vintage computer federation forum. They have tons of guys running 8080 systems who I am sure would love to try,

  Are you sure? yes | no

al-davis wrote 01/04/2024 at 16:27 point

Debating whether to incorporate this with an ongoing project of mine, a Z80-display-kbd with an  ESP32 front end.  Now most of the way thru converting the GitHub .asm to Z80.

  Are you sure? yes | no

will.stevens wrote 01/04/2024 at 16:44 point

I used this online 8080 assembler, if useful:

  Are you sure? yes | no

al-davis wrote 01/05/2024 at 16:52 point

That's helpful.  I'm converting to Z80 by hand because I can't find a decent 8080 to Z80 converter.

  Are you sure? yes | no

Dr. Cockroach wrote 01/03/2024 at 11:41 point

This brings back a lot of good memories when I had a IMSAI 8080. I wish I still had that good old workhorse and give your BASIC a run.

  Are you sure? yes | no

will.stevens wrote 01/03/2024 at 23:56 point

I’ve begun to appreciate the 8080 CPU a lot as a result of doing this project. I wonder how many working systems using an 8080 CPU exist now?

  Are you sure? yes | no

Ken Yap wrote 01/04/2024 at 01:02 point

It should run on an 8085 which has the same instruction set and which remedies another 8080 failing, the need for 3 voltage rails. Plenty of 8085s ended up in embedded systems.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates