Yes, Microsoft 6502 BASIC was clearly a port of their 8080 BASIC.
Unfortunately the original 8080 source code for Microsoft BASIC isn't easily available, so we can't compare it directly to that without going to the Harvard University library to read the paper copy.
But the Microsoft source code for a later version of their 8080 BASIC,
BASIC-80 5.2 (actually marked 5.11 in the source) is available
online. This is copyright 1982 (from the copyright notice in
INIT.MAC
), but contains material obviously from the original 8080
source code, such as the following comment in BINTRP.MAC
:
;--------- ---- -- ---- ----- --- ---- -----
;COPYRIGHT 1975 BY BILL GATES AND PAUL ALLEN
;--------- ---- -- ---- ----- --- ---- -----
;
;ORIGINALLY WRITTEN ON THE PDP-10 FROM
;FEBRUARY 9 TO APRIL 9 1975
Comparing this with the 6502 source code we see substantial similarities in the overall organization of the code and almost exact correspondence of many routines within it.
START and INIT
For example, the entry point in both versions is at address 0 and
contains JMP INIT
; in both cases the INIT
routine overwrites the
target of this JMP
with the address of a routine called READY
that
does a "warm start". After that there are a couple of words storing
the addresses of routines to convert a floating point number to an
integer and vice versa.
Here's the 8080 version from 1982:
PUBLIC START
START:
PUBLIC JMPINI
JMPINI: JMP INIT ;INIT IS THE INTIALIZATION ROUTINE
;IT SETS UP CERTAIN
;LOCATIONS DELETES FUNCTIONS IF
;DESIRED AND
;CHANGES THIS TO JMP READY
;WARM START FOR ISIS
;OF THE ROUTINE TO CONVERT [A,B]
;TO A FLOATING POINT NUMBER IN THE FAC
DW FRCINT ;TURN FAC INTO AN INTEGER IN [H,L]
DW MAKINT ;TURN [H,L] INTO A VALUE IN THE FAC
;SET VALTYP FOR INTEGER
And the 6502 version from 1978:
START: JMP INIT ;INITIALIZE - SETUP CERTAIN LOCATIONS
;AND DELETE FUNCTIONS IF NOT NEEDED,
;AND CHANGE THIS TO "JMP READY"
;IN CASE USER RESTARTS AT LOC ZERO.
RDYJSR: JMP INIT ;CHANGED TO "JMP STROUT" BY "INIT"
;TO HANDLE ERRORS.
ADRAYI: ADR(AYINT) ;STORE HERE THE ADDR OF THE
;ROUTINE TO TURN THE FAC INTO A
;TWO BYTE SIGNED INTEGER IN [Y,A]
ADRGAY: ADR(GIVAYF)> ;STORE HERE THE ADDR OF THE
;ROUTINE TO CONVERT [Y,A] TO A FLOATING
;POINT NUMBER IN THE FAC.
STROUT
Another example is the STROUT
routine, which clearly works in the
same way, modulo the differences in processor architecture, and even
has some comments that are nearly identical. Here's the 8080 version:
;
; PRINT THE STRING POINTED TO BY [H,L] WHICH ENDS WITH A ZERO
; IF THE STRING IS BELOW DSCTMP IT WILL BE COPIED INTO STRING SPACE
;
STROUI: INX H ;POINT AT NEXT CHARACTER
STROUT: CALL STRLIT ;GET A STRING LITERAL
;
; PRINT THE STRING WHOSE DESCRIPTOR IS POINTED TO BY FACLO.
;
STRPRT: CALL FREFAC ;RETURN TEMP POINTER BY FACLO
CALL GETBCD ;[D]=LENGTH [B,C]=POINTER AT DATA
INR D ;INCREMENT AND DECREMENT EARLY
;TO CHECK FOR NULL STRING
STRPR2: DCR D ;DECREMENT THE LENGTH
RZ ;ALL DONE
LDAX B ;GET CHARACTER TO PRINT
CALL OUTDO
CPI 13
CZ CRFIN
INX B ;POINT TO THE NEXT CHARACTER
JMP STRPR2 ;AND PRINT IT...
PAGE
And here's the 6502 version:
;
; PRINT THE STRING POINTED TO BY [Y,A] WHICH ENDS WITH A ZERO.
; IF THE STRING IS BELOW DSCTMP IT WILL BE COPIED INTO STRING SPACE.
;
STROUT: JSR STRLIT ;GET A STRING LITERAL.
;
; PRINT THE STRING WHOSE DESCRIPTOR IS POINTED TO BY FACMO.
;
STRPRT: JSR FREFAC ;RETURN TEMP POINTER.
TAX ;PUT COUNT INTO COUNTER.
LDYI 0
INX ;MOVE ONE AHEAD.
STRPR2: DEX
BEQ PRTRTS ;ALL DONE.
LDADY INDEX ;PNTR TO ACT STRNG SET BY FREFAC.
JSR OUTDO
INY
CMPI 13
BNE STRPR2
JSR CRFIN ;TYPE REST OF CARRIAGE RETURN.
JMP STRPR2 ;AND ON AND ON.
PTRGET
Even routines that are different due to feature changes are pretty
clearly derived from common original source. For example, the PTRGET
routine that that reads a variable name and fetches its value uses a
pair of zero-page memory locations in the 6502 version to store the
variable name, which is limited to two characters in that older
version of BASIC. The newer 8080 code allows up to 40 characters in a
variable name, but still stores the first two characters of the name
in the C and B registers (memory was used in the 6502 because it has
far fewer registers) and compares those first, only moving on to a
further comparison with the rest of the name in a memory buffer if
that fails.
And of course there's the similarity in the comments and routine name itself. The 8080 version:
; ROUTINE TO READ THE VARIABLE NAME AT THE CURRENT TEXT POSITION
; AND PUT A POINTER TO ITS VALUE IN [D,E]. [H,L] IS UPDATED
; TO POINT TO THE CHARACTER AFTER THE VARIABLE NAME.
; VALTYP IS SETUP. NOTE THAT EVALUATING SUBSCRIPTS IN
; A VARIABLE NAME CAN CAUSE RECURSIVE CALLS TO PTRGET SO AT
; THAT POINT ALL VALUES MUST BE STORED ON THE STACK.
; ON RETURN, [A] DOES NOT REFLECT THE VALUE OF THE TERMINATING CHARACTER
;
PTRGET: XRA A ;MAKE [A]=0
And the 6502 version:
; ROUTINE TO READ THE VARIABLE NAME AT THE CURRENT TEXT POSITION
; AND PUT A POINTER TO ITS VALUE IN VARPNT. [TXTPTR]
; POINTS TO THE TERMINATING CHARCTER.. NOT THAT EVALUATING SUBSCRIPTS
; IN A VARIABLE NAME CAN CAUSE RECURSIVE CALLS TO "PTRGET" SO AT
; THAT POINT ALL VALUES MUST BE STORED ON THE STACK.
;
PTRGET: LDXI 0 ;MAKE [ACCX]=0.
Comparison with Other Object Code
Though we don't have original source for an 8080 version of Microsoft BASIC from the same era as the 6502 source, we do have binaries that can be disassembled. Comparing this disassembly of Altair BASIC 3.2 we can also find substantial similarities in the code (including all the routines mentioned above), though of course there are no comments to compare.
I myself have been working on a disassembly of the 6800 BASIC that Microsoft wrote for the Altair 680B after they'd written the 8080 basic for Altair's 8800 but probably before they started the 6502 version. Both ports were done by Ric Weiland, and again the 6800 version shows the same substantial similarities in code organization and the details of how the particular routines work.