• Please review our updated Terms and Rules here

Palo Alto Tiny Basic: Download

mmruzek

Experienced Member
Joined
Sep 28, 2011
Messages
225
Location
Michigan, USA
Hi,

For the last couple of months I have been working with the assembly source code for Palo Alto Tiny Basic (PATB) for the X86 CPU family. The PATB has a long history going back to Dr. Dobb's Journal in the 1970's. The code was an open source alternative for hobbyists to get into the game programming on the cheap.

After the initial publication of the source code a myriad of tweaks, changes and ports appeared. My interest in the 8088 CPU in the IBM 5150 PC and the PC-RETRO (an exact clone) prompted me to investigate creating a version of PATB that could work as a stand-alone boot, much like the CASSETTE BASIC provided by IBM, but with the important difference that we have the source code to modify for our projects and applications.

My coding project began with a version of PATB released by Michael Sullivan for the 8086. His version used DOS INT 21H for Input and Output services. My project replaced those INT 21H calls with calls directly to the BIOS services. I had help with this project, and it was my first major experience with Assembly Language programming.

Right now I am at the point that I want to post an early Beta version of the progress to date, for people to play with. I have managed to get this code to the point that I can burn an EPROM for socket U29 in the 5150 PC and have PATB boot. I have created a compressed zip file of the files and posted it here:

http://www.mtmscientific.com/PATB.zip

Here is a listing from the README file in the ZIP file package:

Here is an explanation of the files in this compressed folder.

PATB.JPG is a photo of the cover of the Interface Age Book
PATBS-SM.JPG is the same photo in a smaller format.

PATB.PDF is a scan of the original source code for Palo Alto
Tiny Basic, from the Interface Age book.

PATB-MTM.ASM is the source code for the MTM version of PATB.
The source code will compile with NASM. Note that no DOS
interrupts are used in the code.

PATB-MTM.COM is a compiled version of the MTM version of PATB.
This version will run on any PC compatible computer. A good way
to start playing with the program...

PATB-MAN.TXT is a short manual about how to use PATB. Note
that the main working difference is how to SAVE and LOAD files.
Note: This would really benefit from a good file system, and
coding modifications to work with larger files.

PATB-ROM.BIN is a binary file, that if burned and placed in
IBM PC socket U-29 will boot PATB instead of CASSETTE BASIC.
This code will fit in a 27128 EPROM without modification, by
using an adapter: http://www.minuszerodegrees.net/5150_u33.htm

RELOC.TXT is the source code for the relocation code in the ROM.
This was compiled using DEBUG. Simply change source and destination
addresses as your application warrants.

README.TXT is this file.

This code has not been extensively error checked. I would appreciate
any and all help to improve the project. Thank You!

PS. PATB expects commands in upper caps only. Here are a few examples:

PRINT "HELLO"
PRINT 2+2
etc.

Michael
 
Excellent! It works well for me running the .COM version. It also seemed to take lowercase commands just fine, btw. I'd like to try it on an EEPROM, but I'll need to find a couple blank ones. Shouldn't I only need one? I know it's a 16 KB ROM file, but if I look at it in a hex editor, it's all nulls until offset 8192. I did try it in my emulator, loading it at F600:0000 but the generic XT BIOS doesn't seem to recognize it as ROM BASIC. I also tried jumping to F600:0000 from DOS, but it just hangs.
 
Going through the code -- a couple observations...

Int10h doesn't corrupt BX, CX or DX -- since you're not changing CX or DX inside chrOut, you don't need to push/pop them. Same for int16h which preserves everything except AX and FLAGS.

This part:
Code:
ps1:
 mov si,dx
 lodsb           ;get a char
 lahf            ;preserve flags
 inc dx
 sahf            ;restore flags
 cmp al,ch       ;same as old a?
 jnz ps2         ;yes, return
 ret

If you are doing a immediate compare, which changes all the flags, why are you bothering to store the flags in AH and then restore them?!?

I would also give some serious thought to switching how strings work, possibly for pascal style (length first) -- I realize this is an early port, but that's something I'd address right quick. The speed increases and simpler code would more than make up for the increase in data sizes.

Also... it might really help if you stopped using such needlessly cryptic labels :D
 
As it is currently written the random number generator will begin
failing between 4 and 5 minutes after a cold boot...

Code:
 mov ax, 40h
 mov ds, ax
 mov ax, [06ch]
 mov dl, 20
 div dl

Once the timer (40:6c) reaches 0x1400 (20*256) ticks, the quotient (AL)
will overflow and generate a divide error (interrupt 0).

Also you are seeding the prng with the timer on each call to rnd(), thus
subsequent calls to rnd() will return identical values unless the timer
has advanced (not the desired behaviour I assume!).


typo: PATB-MAN.TXT line 276 mached->matched
 
Good catch b44ccd21!!!

I'd suggest simply doing

and ax,$03FF

Before the divide, neutering the result to 0..1023 and as such the max divide result of AL 51 AH 3. I usually restrict the values that way any time I know the divisor -- though in this case the impact on the RNG might be unexpected. Might be time to lift a better RNG from some existing library.
 
I'd suggest simply doing

and ax,$03FF

Before the divide

true; although 0000111111111111b may give a wider range.

Might be time to lift a better RNG from some existing library.

The algorithm used by borland c for dos is fairly lightweight and maybe
that compatability would be a plus?
Code:
// ----------------------------------------------------------------------------
struct PRNG
{
  long s;
  long m;
  long i;

  int  g( void ) { return( ( ( s = s * m + i  ) >> 0x0010 ) & 0x7fff ); }
};
// ----------------------------------------------------------------------------
PRNG bcc = { 0x00000001, 0x015a4e35, 0x00000001 };
PRNG msc = { 0x00000001, 0x000343fd, 0x00269ec3 };
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
#include <iostream.h>
#include <stdlib.h>

typedef  unsigned int    WORD;
#define  PTR(s,o)        ((void _seg*)(s)+(void near*)(o))
#define  WORD_PTR(s,o)   ((WORD*)PTR(s,o))

void main( int, char*[] )
{
  bcc.s = WORD_PTR( 0x0040, 0x006c )[0];
  srand( bcc.s );

  for( int i = 0; i < 0x0010; i++ )
  {
    cout << bcc.g() << " " << rand() << endl;
  }
}
// ----------------------------------------------------------------------------

however for "tiny" basic perhaps an lfsr would suffice?( nods to trixter... ).
Code:
G goto10_lfsr/13_inescapable         // d1eed6730233f404ddcd29ebf3
G                                    //
G in:    sp       0xfffe             // .com entry
G        si       0x0100             // ; sp = polynomial, si = seed
G                                    //
Z .goto10_lfsr_prng:                 //
G                                    //
Q %      shr      si, 0x01           // galois_lfsr( 0xfffe, 0x0100 )
Q %      db       0xd6               // : (salc) cf->al [0x00,0xff] for plot
Q %      jnc      .goto10_lfsr_plot  // : pseudo-random bit, period 0x12d4
Q %      xor      si, sp             // : sp = polynomial, si = state
G                                    // '
Z .goto10_lfsr_plot:                 // plot
G                                    // :
Q %      add      al, 0xdd           // : al = [0xdc,0xdd] 
Q %      int      0x29               // ' dos::fast_console_output(al)
Q %      in       al, 0x60           // kb
Q %      dec      ax                 // : escape?
Q %      jnz      .goto10_lfsr_prng  // '
Q %      retn                        // exit

NEC: salc->sbb al, al (+1b)

obviously for use in PATB, one would use a polynomial that generates a
maximal length sequence so a modulo of the lfsr's state would give
appropriate ranged return values for RND().
 
Yeah, there are many ways to skin this cat. I've played with this before:

Code:
        mov ax, word ptr randseed[2]
        mov dx, word ptr randseed[0]
        mov bx, ax
        mov cx, dx
        shl cx, 3
        shr bh, 4
        or cl, bh
        xor dx, cx
        not dx
        shl dx, 1
        rcl ax, 1
        shr dx, 1
        mov word ptr randseed[0], ax
        mov word ptr randseed[2], dx

This claims 2^31-1 repeatability but I've never verified it.

Another way, if you don't care too much about periodicity and just want small fast results:

Code:
        mov     ax,seed
        add     ax,9248h        ;; 1001001001001000b (visual rep)
        ror     ax,1
        ror     ax,1
        ror     ax,1
        mov     seed,ax

...which seems to have a good spread. Yet another:

Code:
    mov cl,121 ; initial seed
...
    rol cx,cl ;this is the
    add cx,7  ;random function

...if you want something super-small and don't care about quality.

BTW definitely never, ever use the "SALC" undocumented opcode in 8088-era code, since it is very, very common to have the 8088/8086 replaced with an NEC V20/v30 where this breaks. Always use SBB AL,AL instead.
 
BTW definitely never, ever use the "SALC" undocumented opcode in 8088-era code, since it is very, very common to have the 8088/8086 replaced with an NEC V20/v30 where this breaks. Always use SBB AL,AL instead.

heh... preaching to the converted my friend ( I did mention NEC:
salc->sbb al, al in my original post... ).

Are there any other NEC related pitfalls one needs to be aware of?

I seem to remember the undocumented non-decimal form of the AAM opcode
being unsupported by the V30 (amstrad shipped some of its PPC's with
V30s, some with 8086s).

Have you verified AAM's behaviour on the NEC V-series?, If so, do you
know of any trivial workarounds as it frequently crops up in hex to
ascii code.

vide:

Code:
G
G #############################################################################
G
Z .h_dword:                     // IN: dx:ax
G                               //
Q %      call     .h_dword_hi   //
G                               //
Z .h_dword_hi:                  //
G                               //
Q %      xchg     ax, dx        //
G
G #############################################################################
G
Z .h_word:                      // IN: ax
G                               //
Q %      push     ax            //
Q %      xchg     ah, al        //
Q %      call     .h_byte       //
Q %      pop      ax            //
G
G #############################################################################
G
Z .h_byte:                      // IN: al
G                               //
Q %      db       0xd4, 0x10    // aam 0x10: al -> nibbles in ah/al
Q %      call     .h_nibble     //
G                               //
Z .h_nibble:                    //
G                               //
Q %      xchg     ah, al        //
Q %      cmp      al, 0x0a      //
Q %      sbb      al, 0x69      //
Q %      das                    //
G                               //
Q %      int      0x29          // DOS::fast_console_output
Q %      retn                   //
G
G #############################################################################
G
:
:
:
Q %      mov      al, 0x12      //
Q %      call     .h_byte       //
G                               //
Q %      mov      ax, 0x1234    //
Q %      call     .h_word       //
G                               //
Q %      mov      dx, 0x1234    //
Q %      mov      ax, 0x5678    //
Q %      call     .h_dword      //
:
:
:

replacing
Code:
Q %      db       0xd4, 0x10    // aam 0x10: al -> nibbles in ah/al 2
Q %      call     .h_nibble     //                                  3
G                               //
Z .h_nibble:                    //
G                               //
Q %      xchg     ah, al        //                                  2
with
Code:
Q %      mov      cl, 0x04      // ax == ????????hhhhllll           2
Q %      ror      ax, cl        // ax == llll????????hhhh           2
Q %      and      al, 0x0f      // ax == llll????0000hhhh           2
Q %      shr      ah, cl        // ax == 0000llll0000hhhh           2
Q %      call     .h_nibble     //                                  3
Q %      xchg     ah, al        //                                  2
G                               //
Z .h_nibble:                    //
G                               //

works, but at +6bytes and clobbering CL, it leaves somewhat of a nasty
taste in the mouth...
 
heh... preaching to the converted my friend ( I did mention NEC:
salc->sbb al, al in my original post... ).

I know, I was reiterating the point in case someone thought they should do it anyway. You can do it on NEC v20/v30 but on those chips it aliases to XLAT so the program won't crash but it certainly won't work properly.

Are there any other NEC related pitfalls one needs to be aware of?

Don't use the AAM/AAD supply-your-own-operand trick as it doesn't work. Perform a full mul/div or, if the values are small enough, use a lookup table. There is a lot of AAM in hex conversion code but, really, burning up 16 bytes for a lookup table is not horrible and XLAT is faster anyway.

POP CS works on 8088/8086 but is a prefix on NEC for extended NEC opcodes, so don't use it. 80186 and later POP CS is invalid so really don't use it.

I think that's it.

On the flip side, there are some neat NEC opcodes I wish I had on 808x, such as the REP prefixes "repeat while carry" and "repeat while no carry", and the ability to rotate a byte-sized reg by 4 without needing CL=4. And of course NEC v20/v30 can do shift and rotate by more than 1 (ie. SHL AX,5) just like 8018x. I read somewhere that IBM PC DOS 7.x has NEC detection and will install some NEC-specific code if found, but I don't know to what extent.
 
BTW definitely never, ever use the "SALC" undocumented opcode in 8088-era code, since it is very, very common to have the 8088/8086 replaced with an NEC V20/v30 where this breaks. Always use SBB AL,AL instead.

Be careful--the SALC does not affect any flags, while SBB AL,AL does. So check the preceding and following code to see if the original depends upon a flag being preserved across the SALC.
 
Sorry to necropost, but this was referenced in another thread: mmruzek, did you ever review some of the bugs (ie. random number generator will cause a divide by zero) mention in this thread and come out with a new version of your 8086 PATB?
 
I have a text based "Star Treck" game for the PA BT, someplace. If interested, let me know and I'll look for it.
Dwight
 
Sorry to necropost, but this was referenced in another thread: mmruzek, did you ever review some of the bugs (ie. random number generator will cause a divide by zero) mention in this thread and come out with a new version of your 8086 PATB?

Hi! I regret that I have not had time to make any updates on this project. The posted version is the most recent. PATB is really an interesting part of the early years of computer history. I posted a link in the Publications part of the forum to scans of early Dr. Dobb's journals, if anyone cares to read more. My original thinking was that PATB burned to ROM would be a nice addition to the PC-Retro Kits that I offer. If anyone improves the code please advise. Thanks! Michael
 
One other thought, if you used the PATB listing off the web that ran on the Poly88, it has the ability to extend the instruction set. I'd added a PEEK, POKE and a SAVE. Since i used this on the Poly 88, I used the monitor's load to read the files, from cassette. If it is similar, there is a part after reset that would sample a value in memory. If it were the right value, it would attach the code to the instruction decode.
Although, the code part might not be of much use, being 8080 code, the part that added instruction might be of some use. As I recall, the instructions were in two types. One was one that returned a value and the other was a type that took parameters. This required adding to two different parts of the instruction decode.
Dwight
 
Last edited:
Hi! I regret that I have not had time to make any updates on this project. The posted version is the most recent. PATB is really an interesting part of the early years of computer history. I posted a link in the Publications part of the forum to scans of early Dr. Dobb's journals, if anyone cares to read more. My original thinking was that PATB burned to ROM would be a nice addition to the PC-Retro Kits that I offer. If anyone improves the code please advise. Thanks! Michael

Hi,

I'm currently building a 8088 based computer... on breadboard
BB-88.jpg
and your translation of PATB for x86 was a very nice touch to add. Thank you so much for your work !

However, it was more of a translation than a real port, so I'm now taking the time to rewrite it from scratch in a way that better fits with the x86 architecture, while still keeping the original structure and logic of Li-Chen Wang's work.

That means for instance redoing the logic behind the conditional returns into conditionnal jumps. Also, the original source code often uses a pattern where execution skips some instructions after return. This is also a construction that I do not want to reproduce.

I'm also simplifying or removing several parts of the code that have become somewhat useless on the x86 architecture. Routines like DIVIDE, CHGSGN, and others can simply go away because corresponding instructions and 16 bits registers are immediately available. MVUP and MVDOWN can now be replaced with "rep movs". Etc.

I'll also have a look at the suggestions above regarding the implementation of RND().

I've already done a good part, but I'm far from finished. As soon as I have something working, I'll upload it here !
 
As promised, here is my take at the x86 port of the Palo Alto Tiny Basic.

Input/Output use the serial port. Changing that to use keyboard and screen should be fairly straighforward.
I've implemented two random number generators :D A %define statement selects between them.

startrek.bas (from http://www.dunnington.info/public/startrek/ ) is included in the archive. It helped me find the last bugs in the interpreter (which is nice) but also prevented me from getting enough sleep these last two nights. :rolleyes:

I've also included three super simple examples: the mandelbrot set, a game of life, and a bouncing "ball". All in text mode. The last two make use of ANSI control sequences to clear the screen and change the cursor position.

Of course, any comment is more than welcome.
 

Attachments

  • patb86.zip
    19.9 KB · Views: 4
Looks good from scanning the source, nothing obvious sticks out. Love the comments. However, it's difficult to test as-is as you built it for ROM installation using fixed segments; for most people to test it, it would need:

1. Compiled as a DOS .COM file (CS=DS=ES=SS so no need to define segments, except maybe a separate 64k segment to hold source code)
2. The ability to load and save .BAS files
 
And here it is, buildable as a .COM file, all in a single segment.

It is using int 16h for input and int 10h/0eh for output, but that means the ANSI sequences in my examples will not work.
Startrek.bas does work, though, and so does the small mandelbrot set :)

Still no save/load functions because I wanted to stay as close as possible to the original in this first release. Nothing less, but nothing more either. I wanted to keep it simple. (Also, BB-88, my breadboard computer, does not have mass storage yet so I have nowhere to load from :rolleyes:)

To do my tests, I just copy/paste the content of a source file into the interpreter. I works in a serial terminal, and I tested it with dosemu as well. I'm not familiar with Windows' console, but I suppose it would work just as well.
 

Attachments

  • patb86_DOS.zip
    19.9 KB · Views: 5
Thanks for this. I've been building a DOS compilers/interpreters repository, and finding new stuff is very difficult.
Obviously not common, but i aim to a completionist collection.
 
Back
Top