KB-9, MOS TECH BASIC for the 6502

With a scan of manuals by Gerben Voort, and a third photo of a cassette with KB-9, time to refresh the KB-9 pages.

MOS TECH BASIC versions

Despite all the versions you see on the download page, there is only one official and original binary known of the MOS TECH BASIC for the 6502 KIM and that is my V1.1 (ID 01) loaded from my original tape. There is another tape known recently, owned by my friend Gerben Voort, and we hope to be able to read that tape.

Every other version is a derivate, in the 80ties or in the past years.

There are also sources of Microsoft Basic for the 6502. The official source, in PDP-11 MACRO source format has been declared open not so long ago.
The title is ‘M6502 8K VER 1.1 BY MICRO-SOFT’. Yes the name of the company was originally MICRO-SOFT!

Many years before the official source, Michael Steyl of pagetable.com reconstructed the source, based upon my binary dump. A perfect start to create derivate versions!

Versions known in the 80ties

  • KB-9
    Created by Microsoft, only distributed by Johnson Computers (afaik). Binary available.
    On the tape label: 2000-4260 ID #01 St 4065
    Oldest known documentation available, scanned.
  • KB-6
    Mentioned in the documentation of ID #01. Not available.
    6 digit precision floating point, smaller in size.
  • KB-9 ID #101
    Distributed by Johnson Computers
    On the tape label: 2000-437D ID #101 St 4065 S/N 217 and 233. Binary not available (yet).
    Mentioned in the documentation of the versions distributed by Johnson Computer with ID #101. Binary not available. Two versions of the documentation known and scanned.
    This version seems to be the V1.1 KB-9 with added Hypertape and BLOCKMOVE routines at the end of the binary. With the alternative start addresses the hypertape routine can be moved to $0200 or $0300 and the SAVE command patched to the hypertape routine. Or with the standard start at $4065 hypertape can be skipped.
    All KB-9 internal addresses are the same.
  • KB-9P
    Mentioned in the documentation of the versions distributed by Johnson Computer with ID #101, Binary not available.
    KB-9P is a stripped version of KB-9 fit for burning into a ROM. SIN-COS-ATN is not available.

My MSBasic for the KIM-1 cassette


Another MOS TECH BASIC for KIM-1, lower serial number


Thanks Gerben Voort


How to build and adapt KB-9 and KB-6

Sources of KB-9 Microsoft Basic v1.1

Adapt KB-9, first step make it faster and smaller

In the previous section the pagetable article was shown, with resources to recreate from source many 6502 Basic’s, like KB-9.

Here an example how I, quick and dirty, used this to create a KB-9 named V1.2 which is smaller and faster than the original.

This is how to prepare for it (Windows, can be done also on Linux)

  1. Download and unpack the archive of pagetable in a folder on your PC.
  2. Download and unpack the CC65 package, a C compiler, from which only the assembler and linker is used. I used the Windows binaries.
  3. Copy CA65.EXE, LD65.EXE and longbranch.mac from the CC65 package to the folder where you unpacked the MS Basic source.

Do the adaptations as described below or your own:

  1. To save you the work I have collected the adaptations described above in this archive for your convenience here.
  2. Change whatever you like in the source. It is quite a complicated construction, with macros for every variant, so look carefully at the listing file what really is produced.
    Start with no adaptations and then go on studying the listing file and testing. The KIM-1 Simulator is a good tool for testing! Load the symbol table file to see what is where.
  3. Assemble and link with this simple batch file makekb9v2.bat, resulting in an object, a binary, a listing file and a symbol label file.
ca65 -D kb9 msbasic.s -o tmp/kb9v2.o -l tmp/kb9v2.lst
ld65 -C kb9.cfg tmp/kb9v2.o -o tmp/kb9v2.bin -Ln tmp/kb9v2.lbl
  • Repeat step 4 and 5 until you are satisfied with the adaptations. The articles listed are a good source of inspiration.
  • These are the adaptations done in V1.2 use the ROR instruction and suppress nulls sent to the terminal and Clear decimal and fix GET
    I changed this:

      • In define_kim.s make a comment of the following two lines:
    ;CONFIG_NULL := 1                     
    ;CONFIG_ROR_WORKAROUND := 1             ; patch HO 2021
    
    • In init.s add this line at label COLD_START
      COLD_START:
      .ifdef SYM1
              jsr     ACCESS
      .endif
      .ifdef KBD
        .
        .
        .
      .else
        .ifndef CBM2
              cld                     ; patch for KIM-1 HO 2021
              ldx     #$FF
              stx     CURLIN+1
      

      Change line 493 init.s

        .ifdef KIM
              .byte   "MOS TECH 6502 BASIC V1.2"
      
    • do the fix in the “GET handling (see below)
    • Add backspace handling to correct typing errors
    • Change the version number in inline.s line 493 to “MOS TECH 6502 BASIC V1.2

    Assemble and link with the batch file makekb9v2.bat, this will deliver in the folder tmp
    – kb9v2.bin file : load as usual at $20001
    – kb9v12.lbl text file
    – kb9v12lst textfile

    Start KB9 V2 now at location $3FB1, label COLD_START (used to be $4065, so we gained some RAM), see the lbl file for other addresses.

    KB-6

    I know KB-6 existed. The ‘6’ stands for the precision in digits of the floating point number. In the documentation KB-6 is described.
    Never seen a version in the wild. So the reconstruction here is not checked with the original, addresses in the reconstruction from the linker differ from the documentation.
    Perhaps the ROR workaround or the insertion of CLD in the init.s caused this.

    KB-6 can be ‘reconstructed’ since other versions of 6 digit Microsoft Basic are in the ‘pagetable sources’.
    It takes one define added in define_kim.s, changes on the original file are now:

    ; CONFIG_NULL := 1                      ; patch HO 2021
    ;CONFIG_ROR_WORKAROUND := 1             ; patch HO 2021
    CONFIG_SMALL := 1                       ; patch H0 2021
    

    Assemble and link as above, replace kb9v12 with kb6v12 in the batch file. See the archive for a working batch file.
    COLD_START moves to $3D50, size shrinks to 8K.

    As you can see in the following screenshots it works! Note the number of digits is less, as expected. It should be faster also.

    Microsoft Basic for the KIM-1 KB-9

    Microsoft Basic for the KIM-1 KB-6, less precision, smaller program size


    The GET bug

    The bug was described and fixed first by an article in the KIM User notes 17

    From the pagetable sources:
    BUG: The beq/bne L2AF8 below is supposed to be always taken. For this to happen, the last load must be a 0 for beq and != 0 for bne.
    The original Microsoft code had ldx/ldy/bne here, which was only correct for a non-ZP INPUTBUFFER. Commodore fixed it in CBMBASIC V1 by swapping the ldx and the ldy. It was broken on KIM, but okay on APPLE and CBM2, because these used a non-ZP INPUTBUFFER. Microsoft fixed this somewhere after KIM and before MICROTAN, by using beq instead of bne in the ZP case.

      .ifdef CBM1
         ldy  >(INPUTBUFFER-1)
         ldx  <(INPUTBUFFER-1)
      .else 
         ldx  >(INPUTBUFFER-1)
         ldy  <(INPUTBUFFER-1)
    ..
         beq 08
    

    You can easily fix this in KB9 by changing the branch in $2AEE from $D0 (bne) to $F0 (beq).
    I have fixed this in the source of KB9V2 (KB6 does not have the GET statement) .

    Use the backspace key to correct typing errors

    Correcting typing errors can be done with the _ key ($5F). On a video terminal, like we use nowadays it can be done with backspace.
    The way characters are handled by the input routine do not allow to just replace the compare with _ (C9 5F) with 08 for backspace.

    A trick by Jim W4JBM can be used to reuse the BELL handling (07) to a backspace.
    Replace in inline.s

    INLINAIM:
        .endif
        .ifndef CONFIG_NO_LINE_EDITING
            cmp     #$07
            beq     L2443
    

    with

    INLINAIM:
        .ifndef CONFIG_NO_LINE_EDITING
            cmp     #$08 
            beq     L2420
    
    In the original KB9.BIN you can do that with
    poke 9260,8
    poke 9262,241
    

    V2 adaptations to V1.2 by Eduardo Casino
    (after applying all above)
    The start address is moved to $2000 by changing this in header.s

    		.segment "HEADER"
    .ifdef KBD
            jmp     LE68C
            .byte   $00,$13,$56
    .endif
    .ifdef KIM
            jmp     COLD_START
    .endif
    .ifdef AIM65
            jmp     COLD_START
            jmp     RESTART
            .word   AYINT,GIVAYF
    .endif
    .ifdef SYM1
            jmp     PR_WRITTEN_BY
    .endif
    

    Change line 493 init.s

      .ifdef KIM
            .byte   "MOS TECH 6502 BASIC V2.0"
    

    Assemble and link with

    ca65 -D kb9 msbasic.s -o tmp/kb9v2.o -l tmp/kb9v2.lst
    ld65 -C kb9.cfg tmp/kb9v2.o -o tmp/kb9v2.bin -Ln tmp/kb9v2.lbl
    

    KB6 V2
    Change this in defines_kim.s (after applying all above)
    CONFIG_SMALL := 1

    Assemble and link with

    ca65 -D kb9 msbasic.s -o tmp/kb6v2.o -l tmp/kb6v2.lst
    ld65 -C kb9.cfg tmp/kb6v2.o -o tmp/kb6v2.bin -Ln tmp/kb6v2.lbl
    

    See the downloads for the binary files.

    Another MOS TECH BASIC for KIM-1, lower serial number


    Thanks Gerben Voort

    The KIM-1, TIM and Jolt explained

    During the hot summer days, locked up in house because of the heat, I made a deep dive into the KIM-1 and the TIM/Jolt software and hardware.

    50 years of working with a KIM-1:
    The KIM-1 Explained

    The Jolt, one of the first 6502 SBC’s, so nicely reproduced now:
    The TIM/Jolt explained.

    Jolt manuals and documents

    Documents

    Jolt Replica User manual
    Jolt User Newsletter
    DEMON software manual
    (this manual has an alternative listing of the TIM 6530-004 monitor)

    Alter memory or registers

    Sample session

     *  7052 B0 00 FF 00 FF
    .M  0200 00 00 00 00 00 00 00 00
    .:  0200 33 44 55
    .M  0200 33 44 55 00 00 00 00 00
    .R  7052 B0 00 FF 00 FF
    .:  3455 55 55 55 55 55
    .R  3455 55 55 55 55 55
    .M  00F6 55 34 55 55 55 55 55 02
    .M  7000 85 F9 A9 23 D0 55 A9 16
    .:  7000 77?
    .?
    .
    

    The ‘:’ command reacts different upon previous actions:

    • if the previous command was M, the current address types in with M is printed and bytes can be typed in that will be placed at the current PC. The current address is incremented and the next byte can be entered, till maximum 8 are entered 85
    • if the previous command was R the program counter has to be typed and the 5 registers PS A X Y Z (or less by pressing ENTER).
    • after a ‘:’ command, the next ‘:’ will behave the same as following the M command

    Note the current address and the Program Counter are not the same. The GO command requires the Program Counter to be set to the desired address.

    0282   70E0             ;
    0283   70E0             ;  READ AND STORE BYTE.  NO STORE IF SPACE OR RCNT=0.
    0284   70E0             ;
    0285   70E0 20 B3 73    BYTE   JSR RDOB          ; CHAR IN A, CY=0 IF SP
    0286   70E3 90 10              BCC BY3           ; SPACE
    0287   70E5             ;
    0288   70E5 A2 00              LDX #0            ; STORE BYTE
    0289   70E7 81 EE              STA (TMP0,X)
    0290   70E9             ;
    0291   70E9 C1 EE              CMP (TMP0,X)      ; TEST FOR VALID WRITE (RAM)
    0292   70EB F0 05              BEQ BY2
    0293   70ED 68                 PLA               ; ERR, CLEAR JSR ADR IN STACK
    0294   70EE 68                 PLA
    0295   70EF 4C BA 70           JMP ERROPR
    0296   70F2             ;
    0297   70F2 20 7C 72    BY2    JSR CADD          ; INCR CKSUM
    0298   70F5 20 97 73    BY3    JSR INCTMP        ; GO INCR TMPO ADR
    0299   70F8 C6 FE              DEC RCNT
    0300   70FA 60                 RTS
    

    BYTE

    What is happening here?

    • Read a byte (two hex characters) (285)
    • if space skip writing databyte (286, 289)
    • store as binary in current address and increment current address (289)
    • if databyte read back is not equal databyte then error out of caller also (291-295)
    • add to checksum (checksum is part of L command) (297)
    • increment address to store into (298)
    • decrement number of bytes to write (299)
    0272   70D0 A5 EE       PUTP   LDA TMP0          ; MOVE TMP0 TO PCH,PCL
    0273   70D2 85 F6              STA PCL
    0274   70D4 A5 EF              LDA TMP0+1
    0275   70D6 85 F7              STA PCH
    0276   70D8 60                 RTS
    
    0302   70FB A9 F8       SETR   LDA #FLGS         ; SET TO ACCESS REGS
    0303   70FD 85 EE              STA TMP0
    0304   70FF A9 00              LDA #0
    0305   7101 85 EF              STA TMP0+1
    0306   7103 A9 05              LDA #5
    0307   7105 60                 RTS
    

    PUTP
    Move current address in TMP0 to Program Counter PCL

    SETR

    • The address of the saved registers is loaded in TMP0, TMP0+1
    • A is laoded with 5, to let DSPLYM show the registers SP A X Y Z
    0349   713A             ;  ALTER LAST DISPLAYED ITEM (ADR IN TMPC)
    0350   713A             ;
    0351   713A C6 E9       ALTER  DEC PREVC         ; R INDEX = 1
    0352   713C D0 0D              BNE A3
    0353   713E             ;
    0354   713E 20 A4 73           JSR RDOA          ; CY=0 IF SP
    0355   7141 90 03              BCC A2            ; SPACE
    0356   7143 20 D0 70           JSR PUTP          ; ALTER PC
    0357   7146 20 FB 70    A2     JSR SETR          ; ALTER R*S
    0358   7149 D0 05              BNE A4            ; JMP A4 (SETR RETURNS ACC = 5)
    0359   714B 20 9A 72    A3     JSR WROA          ; ALTER M, TYPE ADR
    0360   714E A9 08              LDA #8            ; SET CNT=8
    0361   7150             ;
    0362   7150 85 FE       A4     STA RCNT
    0363   7152 20 77 73    A5     JSR SPACE         ; PRESERVES Y
    0364   7155 20 E0 70           JSR BYTE
    0365   7158 D0 F8              BNE A5
    0366   715A F0 D8       A9     BEQ BEQS1
    
    0345   7134 4C 86 70    BEQS1  JMP START
    

    What is happening here?

    • decrement previous command index(set in START) :=1 R=2 M=3 (351-352)
    • if > 0 then M command, also resets R command mode (358)
    • else previous was R command: read address and put in Program counter (354-358, 362)
    • set current address to registers (FLGS) and 5 bytes to write (357)
    • M command: show address, 8 bytes to write (360-361
    • loop bytes to write
      • print space (363)
      • read and store byte (if not space) (364)
      • if error entering byte then error out via BYTE
      • until all bytes to write are done(365)
    • back to START for next command 366, 345)

    ASCII Conversion Routine

    A very compact and obscure looking routine. But a clever use of the Carry flag underneath!

    0673   7358 18          ASCII  CLC
    0674   7359 69 06              ADC #6
    0675   735B 69 F0              ADC #$F0
    0676   735D 90 02              BCC ASC1
    0677   735F 69 06              ADC #$06
    0678   7361             ;
    0679   7361 69 3A       ASC1   ADC #$3A
    0680   7363 48                 PHA           ; TEST FOR LETTER B IN ADR DURING WBNPF
    0681   7364 C9 42              CMP 'B'       ;
    0682   7366 D0 0A              BNE ASCX
    0683   7368 A5 FD              LDA SAVX
    0684   736A C9 07              CMP #NCMDS
    0685   736C D0 04              BNE ASCX      ; NOT WB CMD
    0686   736E 68                 PLA
    0687   736F A9 20              LDA ' '       ; FOR WB, BLANK 0&#039;S IN ADR
    0688   7371 48                 PHA
    0689   7372 68          ASCX   PLA
    0690   7373 60                 RTS
    

    ASCII
    Convert the low nibble in A to ASCII hex. Call it with the upper nibble containing 0.
    A returned has the ASCII equivalent of the byte value.

    What is happening here?

    • clear the Carry flag (673)
    • A is shifted into a range that later allows the Carry flag to distinguish between numeric digits (0–9) and alphabetic digits (A–F) (674)
    • Adding $F0 causes only values A–F to generate a carry.(675)
      Input Accumulator Carry
      0–9 $F6–$FF 0
      A–F $00–$05 1
    • Carry is clear, the value was 0–9. Otherwise an extra ADC $06 is executed before the final addition.(677)
    • Add $3A to make it ASCII ((679)
    • if the character is ‘B” and the current command is WH BNPF repalce with ‘ ‘
      to suppress illegal characters in the BNPF format

    Note that suppressing ‘F’ is also part of the BNPF format definition. It is not done on the TIM for unknown reasons.

    Examples

    Input = 5

    5 + 6      = 11
    11 + $F0   = $FB   Carry=0
    $FB + $3A  = $35   ASCII '5'
    

    Input = A (10)

    10 + 6     = 16
    16 + $F0   = $00   Carry=1
    00 + 6 + C = $07
    07 + $3A   = $41   ASCII 'A'
    

    Write hex routines

    These routines are called from the command handlers to display data.

    0537   729A             ;  WRITE ADR FROM TMP0 STORES
    0538   729A             ;
    0539   729A A2 01       WROA   LDX #1
    0540   729C D0 0A              BNE WROA1
    0541   729E A2 05       WROA4  LDX #5
    0542   72A0 D0 06              BNE WROA1
    0543   72A2 A2 07       WROA6  LDX #7
    0544   72A4 D0 02              BNE WROA1
    0545   72A6 A2 09       WRPC   LDX #9
    0546   72A8 B5 ED       WROA1  LDA TMP0-1,X
    0547   72AA 48                 PHA
    0548   72AB B5 EE              LDA TMP0,X
    0549   72AD 20 B1 72           JSR WROB
    0550   72B0 68                 PLA
    

    [/code]
    0551 72B1 ;
    0552 72B1 ; WRITE BYTE – A = BYTE
    0553 72B1 ; UNPACK BYTE DATA INTO TWO ASCII CHARS: A=BYTE; X,A=CHARS
    0554 72B1 ;
    0555 72B1 48 WROB PHA
    0556 72B2 4A LSR A
    0557 72B3 4A LSR A
    0558 72B4 4A LSR A
    0559 72B5 4A LSR A
    0560 72B6 20 58 73 JSR ASCII ; CONVERT TO ASCII
    0561 72B9 AA TAX
    0562 72BA 68 PLA
    0563 72BB 29 0F AND #$0F
    0564 72BD 20 58 73 JSR ASCII
    [/code]

    0565   72C0             ;
    0566   72C0             ;  WRITE 2 CHARS - X,A = CHARS
    0567   72C0             ;
    0568   72C0 48          WRTWO  PHA
    0569   72C1 8A                 TXA
    0570   72C2 20 C6 72           JSR WRT
    0571   72C5 68                 PLA
    

    Additions to the EMMA system documentation

    Additions to the EMMA system documentation, thanks Keith Norman

    Emma Hardware Manual
    Eprom Programmer
    Mac User Manual

    CRLF to serial

    
    0104   0000             CRDLY  =227  $E3         ;DELAY FOR CR IN BIT-TIMES
    
    0162   701E A0 01              LDY #1            ; SET TO MEASURE 2 BITS
    0163   7020 84 E3              STY CRDLY         ;INIT OR DELAY TIME PARAMETER
    
    0565   72C0             ;
    0566   72C0             ;  WRITE 2 CHARS - X,A = CHARS
    0567   72C0             ;
    0568   72C0 48          WRTWO  PHA
    0569   72C1 8A                 TXA
    0570   72C2 20 C6 72           JSR WRT
    0571   72C5 68                 PLA
    0572   72C6             ;
    0573   72C6             ;  WRITE SERIAL OUTPUT
    0574   72C6             ;  A = CHAR TO BE OUTPUT
    0575   72C6             ;
    0576   72C6 20 1D 73    WRT    JSR DLY2
     ...
    
    
    0528   728A A2 0D       CRLF   LDX #$0D
    0529   728C A9 0A              LDA #$0A
    0530   728E 20 C0 72           JSR WRTWO
    0531   7291 A6 E3              LDX CRDLY         ;BIT-TIME COUNT FOR DELAY
    0532   7293 20 1D 73    CR1    JSR DLY2          ;DELAY OF ONE BIT-TIME
    0533   7296 CA                 DEX
    0534   7297 D0 FA              BNE CR1
    0535   7299 60                 RTS
    

    WRTWO
    – print characters in X and A, A first via WRT
    – fall through in WRT.

    CRLF
    – print CR and LF via WRTWO
    – wait (CRDLY * serial bit time)

    The wait can be extended by increasing the default of CRDLY of 1, obviously to give a slow teletype a chance to perform a CRLF.