(This is part 4 of the VTL series.)
The
MC-10
is a very stripped-down computer based on the 6801 microprocessor and the 6847
video display generator that Tandy/Radio Shack produced in 1983, a couple of
years too late and priced too high to compete in its target market, against
the Commodore VIC-20 and the Timex/Sinclair ZX81.
Yes, if Radio Shack management had recognized the market , they could have
released essentially the same computer in 1981, maybe with a non-Microsoft
BASIC. I think it would have been very competitive in 1981.
Part of the reason I find it interesting is that it is similar to Motorola's
Micro Chroma 68 prototyping kit, which was my first computer back in 1981.
Anyway, I have been working over the last several weeks, on adding functionality
to my assembler for the 6800/6801,
asm68c,
so that it can produce the .c10 format file that the MC-10 can read. That is in
itself worth a post, but not now. My purpose in doing so was to make it possible
to install
VTL-2 (Very Tiny Language)
and
fig-Forth
on the MC-10.
In order to get either running on the MC-10, I need to arrange to get
input from the keyboard and output to the display. In fact, pretty much
anything you want to do beyond graphics is going to pretty much have you using
the keyboard and the display.
Output to the raw display in assembler is actually not all that hard, and
input from the raw keyboard is not particularly impossible, but especially the
keyboard would require writing and debugging a lot of code. If the BASIC ROM
provides routines I can use I might as well use them at this level of work.
Writing a simple set of BIOS class routines can be a project for a
hypothetical 'nother day. Others have done something along this line,
providing a more capable BASIC interpreter that doesn't get in the way of high
resolution (for the 6847 controller) graphics and such.
I remembered that someone had posted information on using the BASIC ROM
routines in the
MC-10 group
Facebook group, so I went hunting and found a disassembly file of the BASIC
ROM that Simon Jonassen had posted, mc10.txt. And then Greg Dionne told me
about a full assembly listing that is also available, MC10_ROM.txt.
In these files, I found a list of useful hooks into the ROM just under the
interrupt vectors, starting at address $FFDC. The first two hooks are
FFDC | F8 83 POLCAT fdb KEYIN ; read keyboard
FFDE | F9 C6 CHROUT fdb PUTCHR ; console out
But that's not enough information to be confident I can use them.
You can go to the labels KEYIN and PUTCHR in the listing and find more
complete descriptions. For PUTCHR, you find this (plus a bit more):
* Send character in ACCA to the current output device.
Likewise, for the KEYIN routine, you find this:
* Poll the keyboard for a key-down transition.
* Return ASCII/control
code in ACCA or 0 if no key-down transitions.
It looks straightforward, but I have found there are often a lot of hidden
assumptions behind such routines. It's usually wisest to test them with simple
code before you go trying to wrap whole parsers and interpreters around
them.
And I was, for some reason, a little suspicious of the POLCAT routine -- too
suspicious, maybe.
And that's the subject of this post.
My first attempt was pretty simple. Scan the keyboard, output the result, wait
a while so I have time to see the results, repeat. Except that I put in code
that adapted it to my source for VTL-2, which wants to use the B accumulator
to get the characters in and out instead of the A accumulator:
* INPUT ONE CHAR INTO B ACCUMULATOR
INCH PSHA
PSHX
LDX INCHV
JSR 0,X
PULX
TAB
PULA
RTS
*
* OUTPUT ONE CHAR
OUTCH PSHA
PSHX
LDX OUTCHV
TBA
JSR 0,X
PULX
PULA
RTS
Then I ran out of time. (I'm working on this in the evenings and on the
weekends).
And when I came back I had forgotten that the B accumulator shims were in
there.
Wasted a whole evening "discovering" that the BASIC ROM was actually using the
B accumulator, in contradiction to all claims and appearances! And started
planning this post to explain how I discovered this pseudo-fact.
(Bleaugh!)
I suppose I could retrace my steps in those "discoveries". Outside of the fact
that I was ignoring the shims that were right in front of my nose, I was
actually using useful debugging techniques. But since the whole process was
based on false assumptions, that would be confusing. And somebody might end up
thinking those pseudo-facts were facts. So I won't.
Instead, in this post, I'll walk through the tests as I should have done them.
Or at least a few of the steps.
(And, again, I ran out of time. Too many distractions. I hope I can remember
what I was doing next time I pick this up.)
Okay, so I tried this, somewhere along the line:
OPT 6801
ORG $4C00
* MC-10 BASIC ROM vectors
POLCATV EQU $FFDC ; Scan keyboard
CHROUTV EQU $FFDE ; Write char to screen
TEST LDX POLCATV
JSR 0,X ; scan
TSTA ; 0 means no key pressed
BEQ TEST ; That's going to play Hobb with CTL-@.
LDX CHROUTV
JSR 0,X ; print to screen and advance
* CLRB
* CLRA
*LOOOP SUBD #1 ; wait a sizeable fraction of a second
* BNE LOOOP
BRA TEST ; get more
*
ORG TEST
You'll notice that I also tried a wait loop, which is now commented out.
Without the wait loop, it sits there and waits for me to hit a key and then
outputs it at the current location on the screen.
With a little more code and testing, I was able to convince myself that it
does what the programmer who wrote the comments considered polling (although
it isn't what I consider polling) and I figured out how to work the interface:
* MC-10 BASIC ROM vectors
INCHV EQU $FFDC ; Scan keyboard
OUTCHV EQU $FFDE ; Write char to screen
*
* RECEIVER POLLING
POLCAT PSHA
PSHX
LDX INCHV ; at any rate, don't wait.
JSR 0,X ;
TAB ; MC-10 ROM says NUL is not input.
SEC
BNE POLCATR ; Don't wait.
CLC
POLCATR PULX
PULA
RTS
*POLCAT LDAB ACIACS
* ASRB
* RTS
*
* INPUT ONE CHAR INTO B ACCUMULATOR
INCH BSR POLCAT
INC $400F ; DBG
BCC INCH ; Wait here.
INC $4010 ; DBG
STAB $4011 ; DBG
RTS
*
* OUTPUT ONE CHAR
OUTCH PSHA
PSHX
LDX OUTCHV
TBA
JSR 0,X
PULX
PULA
RTS
But when I tried to load VTL-2 with this (CLOADM), BASIC gave me I/O errors.
And I noticed that the output object was relatively huge.
And I remembered that the ORG and the RMBs that set up the direct page
variables will cause the .c10 file to include object where there is no memory
on the MC-10 -- in the range from $0100 to $4000. Trying to CLOADM binary code
where there is no memory will cause I/O errors from BASIC.
So I decided, as a quick fix, to change the direct page declarations to EQUs
instead of RMBs:
* ORG $C0 ; Move this according to your environment's needs.
DPBASE EQU $C0 ; Change this to move the registers.
* PARSET RMB 2 ; Instead of SAVE0 in TERM/NXTRM
PARSET EQU DPBASE+2
* CVTSUM RMB 2 ; Instead of SAVE1 in CBLOOP
CVTSUM EQU PARSET+2
* MLDVCT EQU CVTSUM ; Instead of SAVE1 in mul/div (1 byte only)
MLDVCT EQU CVTSUM
* DIVQUO RMB 2 ; Instead of SAVE2 in DIV
DIVQUO EQU MLDVCT+2
* MPLIER EQU DIVQUO ; Instead of SAVE2 in MULTIP
MPLIER EQU DIVQUO
* EVALPT RMB 2 ; Instead of SAVE3
EVALPT EQU MPLIER+2
* CNVPTR RMB 2 ; Instead of SAVE4
CNVPTR EQU EVALPT+2
* VARADR RMB 2 ; Instead of SAVE6
VARADR EQU CNVPTR+2
* OPRLIN RMB 2 ; Instead of SAVE7
OPRLIN EQU VARADR+2
* EDTLIN RMB 2 ; Instead of SAVE8
EDTLIN EQU OPRLIN+2
* INSPTR RMB 2 ; Instead of SAVE10 (maybe? Will some VTL programs want it back?)
INSPTR EQU EDTLIN+2
* SAVLIN RMB 2 ; Instead of SAVE11
SAVLIN EQU INSPTR+2
* SRC RMB 2 ; For copy routine
SRC EQU SAVLIN+2
* DST RMB 2 ; ditto
DST EQU SRC+2
STKMRK EQU DST+2 ; to restore the stack on each pass.
DPALLOC EQU STKMRK+2 ; total storage declared in the direct page
That's a little tiresome work, adding the previous label plus two in place of
all the RMBs, but it does the job.
(I think I'm going to regret doing it this way, later.)
That loaded, but then when I tried to EXEC it, it just went away and didn't
come back.
After some thought, I decided to use the BASIC stack instead of making a
private stack for VTL-2. I'll need to check that it really is enough stack,
but I thought that was the first thing to try, and that's what the
second-to-last line there defining STKMRK is for. (Okay, that's two steps in
one I'm showing.)
The interpreter wants to reset the stack each time through, so it needs
something to reset the stack to, and that is why I'll save the BASIC stack
position to STKMRK on entry from the COLD label.
And that worked well enough to give me a cursor. But what I typed at the
keyboard did not show on the screen.
So I added some debugging assembly language equivalent of POKEs to the screen.
With the right debugging output to screen
(see the source of a later step at
https://osdn.net/users/reiisi/pastebin/8573, the lines with DBG in the comments),
I was able to see that the commands were actually going in, and that they were
being recognized, because I was able to get good output from typing
?=*
even though the command itself did not show on the screen. And that quickly
led to recognizing that the MC-10 does not echo what you type until after
BASIC parses it. So I needed to decide where to add code to echo the input.
The EXORsim code simply assumes that INCH will echo for me, so I decided to
follow that approach and just add an output JSR in the INCH routine. Should
work.
Let's publish this rant-in-progress to my blog while I see if it works.
And, it did.
Okay, now the echo works. And I added what I thought would store the stack
pointer in the Z variable so I could examine it, but I did it like this:
START
* LDS #STACK ; re-initialize at beginning of each evaluate
LDS STKMRK ; from mark instead of constant
STS VARS+25 ; DBG so we can get a look at the stack pointer BASIC gives us.
That should have been VARS+25*2. So, instead of in Z, it shows up spread
across L and M, which is an eye-crosser to read. That's easy to fix. And I
should remove the other DBG lines before I forget.
Now I can see where the stack is when BASIC passes control to VTL-2.
Why should I worry?
The last byte assembled by the above source is at $4F67. The end of memory for
a 4K MC-10 is $4FFF. I think that should be enough space between the code and
what BASIC is using at the end of RAM, but putting that stack pointer
somewhere I can see it helps me figure if it really is enough.
But checking the memory bounds variables shows that my memory probe code is not
working right. & is set right, but * ends up with the probe having not run
at all. Still, if I overwrite the * variable with 19456 ($4C00), I can enter a
simple program, list it, and run it.
Hmm. It looks like I have the branch upside down in the probe somehow. How did
I have that working before? Did I?
[Sort of. But, yes, the test is upside down. I am embarrassed. More below
when I get it fixed. It's lousy, still having to work a day job instead of
devoting full time to playing games like this.]
Somewhat of success, but I think that's all I have time for tonight. (What day
was this?)
Picking this back up on the 3rd of September, I'll give you the summary
version of what happened with the probe test that was upside down. Yes, this
is embarrassing.
You know how I got all enthusiastic about the improved CPX in my explanation
of the
software differences between the 6800 and the 6801? Well, when I was adapting Joe H. Allen's simulator, I forgot to make sure
the carry flag got set in the CPX emulation routine when running as a 6801.
Probably also failed to set the other flags right. So, in my initial
successful code for EXORsim running a 6801 core, I had the test inverted.
So I had to go back and fix that in EXORsim6801, and test it a little, and
move things around a bit in my blogs and OSDN stuff, and that's where I've
been for three weeks or however long it took.
I should describe how to get this stuff running on the Xroar MC-10 emulator at
this point, I think. Get the source from the pastebuffer in my OSDN pages:
https://osdn.net/users/reiisi/pastebin/8607 (But see notes for 20220904 and 20220911 below.)
Assemble with the switch to output the .c10 file. The command line
asm68c -l2 -c10 VTL_6801_mc10.asm
should give you the listing to the screen so you can check for errors. Or you
can redirect the listing to a file
asm68c -l2 -c10 VTL_6801_mc10.asm > VTL_6801_mc10.list
and pull the listing file up in a text editor.
Run Xroar with the command line
xroar -machine mc10
You may need to add some to the configuration file to get that to work, I
don't remember. (I put an ampersand on the end to tell the bash shell to run
it concurrently with the terminal session, so I don't have to open another
terminal.)
Selecting the tool menu will allow you to select keyboard translation, which I
need (with my Japanese keyboard), and, more importantly, open up the tape
control:
Click the insert button in the tape control dialogue and select the file
VTL_6801_mc10.c10 (assuming you've given the assembler file the same name as I
show above).
Now click the emulator window to make it active again, and type in the CLOADM
command.
Go back to the tape control dialogue and hit the play button, and the MC-10
emulator should load the virtual tape for something like twenty virtual
seconds (virtual seconds! Goes by very quickly, less than a real second.) and then tell you it got it. Below the file name, type in the EXEC
command with no start address.
I'm not sure whether the CLOADM command in the stock MC-10 BASIC allows
specifying a load address. Nor am I sure whether the EXEC command allows
specifying an execute address. But the lowest ORG directive in the assembler
file sets the load address for the C10 file. And the ORG directive at the end
of the assembler file sets the starting address in the S-record output, with
the C10 output setting the same starting address. So you don't need to tell
BASIC where to load or where to execute.
Anyway, entering the EXEC command should give you an OK prompt and a cursor without a cursor,
and you may think nothing happened until you try typing something.
[202209111413 add:]
No cursor in the source I have up. Adding a cursor is pretty simple if you look through the listing of BASIC A flashing cursor takes a little more thought and effort. I'll leave that as an exercise for the reader, since I want to move ahead to the 6809 transliteration.
[202209111413 add end.]
VTL doesn't
give many error messages, it mostly just doesn't do anything it doesn't
understand. So if you type "ZZ" and hit enter, rather than a cryptic BASIC
error message, you just get another OK and more cursor:
Test it by typing in a math expression, like
?= 1+1*2
The ?= is like BASIC's PRINT comman. But you'll quickly notice that VTL's
calculator parsing is not the algebraic infix you learned in school and get
from BASIC. When you need to alter the left-to-right parsing, use parenthesis:
?= 1+(1*2)
The ampersand variable & tells you where in memory your program will get
stored. It's kind of the bottom of currently available memory. (Kind
of.)
The asterisk variable * tells you what the highest available address for VTL
programs is. (The way I've set it up, it's the byte before the VTL interpreter
itself starts.)
Both the & and the * are supposed to be set by hand in the original VTL-2,
but I had to move so much around that it just made more sense to have the
interpreter probe and set them for you.
I did check whether it should run in a 4K MC-10.
xroar -machine mc10 -ram 4K &
With that, VTL-2 returns these values for the beginning and end of the
programming area, and I ended up putting the BASIC stack pointer marker in the
semicolon variable:
- ?=&
17416
- ?=*
19455
- ?=;
20375
That's
- $4408 begining of VTL programming area
- $4BFF end of VTL programming area
- $4F97 BASIC stack mark
The last address assembled in the code as it stands now is $4F67. So you have
$4F98-$4F68 or $30 (decimal 48) bytes of stack space before the stack
overwrites the return from OUTCH, and VTL crashes. That should be enough,
especially for programs that fit in the very small programming area.
It's plenty to run a short test program:
10 A=0
20 A=A+1
30 ?=A
40 ?=""
50 #=(A<20)*20
60
?="DONE"
If you want to give VTL-2 more room to work, you can set the ORG before COLD
to the highest address your memory configuration allows, about one or two
kilobytes less than your end of memory. And that should be the only thing you
have to change, if I did my job right in the source.
Uh, yeah. The object image for VTL is less than a kilobyte. Just so you know.
Just so you're prepared for the limits.
This is not thoroughly tested, and has some known problems -- like random
number generation is not quite functional.
[202209041331: add (more embarrassment!)]
I made a bad optimization to the random function in the above, and in
the EXORciser versions. Don't know how I missed this one, either. Blame it on my
age?
At the label AR2 in the source code, I falsely corrected, without thinking, the
addition of the low byte to the high, and vice-versa. My false correction
looks like this:
AR2 STD 0,X ; STORE NEW VALUE
ADDD QUITE ; RANDOMIZER
STD QUITE
RTS
Looks reasonable, right? As long as you ignore the comment about randomizer?
Well, here's what it looked like in the original 6800 code:
AR2 STAA 0,X ; STORE NEW VALUE
STAB 1,X
ADDB QUITE ; RANDOMIZER
ADCA QUITE+1
STAA QUITE
STAB QUITE+1
RTS
See what was going on?
Well, I probably should fix the pastebuffer, but OSDN won't let me today.
Maybe I have too many. But I don't want to publish this code in a repository
without some explicit permission from the authors. Anyway, the fix I recommend
is below, with a couple of lines of code to detect lack of initialization and
semi-auto initialize it:
AR2 STD 0,X ; STORE NEW VALUE
BNE AR2RND ; Initialize/don't get stuck on zero.
INCB ; Keep it known cheap.
* ADDD QUITE ; RANDOMIZER ; NO! Don't do this.
AR2RND ADDB QUITE ; RANDOMIZER ; Adding the low byte to the high byte
ADCA QUITE+1 ; ; is cheap but intentional.
STD QUITE
RTS
Just search the code for "RANDOMIZER", cut the bad code out, and paste in the
fix. Or, better yet, edit it with your own, improved random function.
Initialization -- yeah, this is another place where the original code left
initialization out to keep the code tiny.
[202209041331: add end]
[2022091112291333: add]
While transliterating for the 6809, I discovered that I missed some more opportunities to optimize for the 6801.
If you look for the label
SUBTR SUBD 0,X
you'll
notice that, in the 6800 source, it was a very short routine to subtract
whatever X pointed to from D. Every call to SUBTR can be replaced with
the actual body of the subroutine in the 6801 source, with no increase of code size. You
can search for BSR SUBTR and JSR SUBTR and replace each with SUBD 0,X.
(I don't think there will be any JSR SUBTR, but if there are, you can replace that, two, for a byte of code saved.)
Really wish I had more time after work to focus on this stuff.
[2022091112291333: add end]
[JMR202209231810: add]
I have written up a post on VTL expressions, here: https://joels-programming-fun.blogspot.com/2022/09/short-description-vtl-2-expressions-very-tiny-language-p1.html
, which will help in testing and otherwise making use of the language. I
should also shortly have a post up introducing programming in VTL-2.
JMR202209231810: add end]
[JMR202210011737: add]
I now have my work on VTL-2 up in a private repository:
https://osdn.net/users/reiisi/pf/nsvtl/wiki/FrontPage
There
is a downloadable version of VTL-2 for the Tandy MC-10 (6801) in the
stock 4K RAM configuration in there, with source, executable as a .c10
file, and assembly listing for reference. Look for it in the directory
mc10:
https://osdn.net/users/reiisi/pf/nsvtl/files/
[JMR202210011737: add end]