68k-MBC, CP/M 68k, C compiler and floating point

Author: Andre Adrian
Date: 2025-01-16

Introduction

My XMAS present to myself was a 68k-MBC single board computer. Next to the solder yourself kit you need only the following:

I got the kit from SAS ELABORATEC. Just google "ebay elaboratec 68K-MBC". Note: The kit needs the files from the hackaday.io page.
The 68k-MBC is the brain child of "Just For Fun".

Picture: My 68k-MBC. Left module is microSD, right module is USB to UART. This is full system with 1MByte RAM and CP/M 68K from microSD card without GPIO chip.

CP/M 68k Installation

The 68k-MBC uses a micro SD card module and a micro SD card as replacement for floppy drives, floppy disks and hard drives. The hackaday.io page has file "SD-S310121-R231021-v3.zip". Unpack this zip file into the micro SD card. Put the micro SD card into the SD card module and the SD card module into the 68k-MBC. Installation complete.
The 68k-MBC kit has no user manual. The Z80-MBC2 page has an user manual "Z80-MBC2 User Manual - D081023-R280424.pdf". This is helpful for the 68k-MBC, too.

First Boot

The 68k-MBC has two serial ports with RS232 +/-12V level signals at the J4 AUX connector. The first serial port is available with TTL level for an USB to UART module on the J2 SER1 connector. The USB to UART module is provided with the kit. The host computer (e.g. a PC running MS-Windows) is connected to the first serial port through the USB to UART module in most cases.
As terminal emulator program I use TeraTerm. Note: This TeraTerm program is not the original TeraTerm from the 1990s. Let's configure TeraTerm for 68k-MBC with USB UART module. The menu Setup|Serial Port... pop-up is:


The menu Setup|Terminal... pop-up is:

Save the TeraTerm setup with menu Setup|Save setup...

After we connect the 68k-MBC board through the USB to UART module with the host computer running TeraTerm we see the 68k-MBC boot message:

68k-MBC - A091020-R140221
IOS - I/O Subsystem - S310121-R231021

IOS: Full HW configuration detected
IOS: CP/M Autoexec is OFF
IOS: Current Disk Set 0 (CP/M-68K v1.3)
IOS: Loading boot program (CPM68.BIN)... done
IOS: 68008 CPU is running from now

68k-MBC CP/M-68K BIOS - S150321-R311021 - 1024KB
CP/M-68K Version 1.3
Copyright (c) 1985 Digital Research Inc.

If you press RESET key together with USER key on the 68k-MBC and then release only RESET key, you get the boot menu:

68k-MBC - A091020-R140221
IOS - I/O Subsystem - S310121-R231021

IOS: Full HW configuration detected
IOS: CP/M Autoexec is OFF

IOS: Select boot mode or system parameters:
 0: No change (4)
 1: sLoad
 2: Enhanced 68K Basic
 3: Autoboot
 4: Load OS from Disk Set 0 (CP/M-68K v1.3)
 5: Change Disk Set 0 (CP/M-68K v1.3)
 6: Change serial ports speed
 7: Change CP/M Autoexec (->ON)

Enter your choice >

We need option 4, load OS from disk set 0. Leave boot menu with choice 0.

CP/M 68k files on 68k-MBC

The CP/M 68k files are on drive A: to E:.

The contents of drive A:, C: and D: are (your entry is in bold face):

A>dir
A: DDT68000 68K : README   TXT : SETPRINT 68K : SPP      68K : AUTOEXEC TXT
A: AUTOEXEC SUB : CP68     68K : DDT      68K : USERLED  68K : DUMP     68K
A: ED       68K : FIND     68K : LO68     68K : MORE     68K : NM68     68K
A: SENDC68  68K : SIZE68   68K : STAT     68K : RELOC    68K : PIP      68K
A: XM68     68K
A>c:

C>dir
C: AR68     68K : AS68     68K : C068     68K : C168     68K : LINK68   68K
C: CP68     68K : LO68     68K : NM68     68K : SENDC68  68K : SIZE68   68K
C: AS68INIT     : C        SUB : CLINK    SUB : CE       SUB : MORE     C
C: SETJMP   H   : MORE     O   : README   TXT : XFLDBIOS H   : C        OUT
C: CLIB         : AS68SYMB DAT : CLINKF   SUB : CLINKE   SUB : S        O
C: BDOS     S   : CPMLIB       : ASSERT   H   : BIOSTYPS H   : CTYPE    H
C: ERRNO    H   : LOADBIOS H   : NORMBIOS H   : OPTION   H   : OSATTR   H
C: OSIF     H   : OSIFERR  H   : PORTAB   H   : SIGNAL   H   : STDIO    H
C: XFNMBIOS H   : XLOADBIO H   : XNORMBIO H   : BDOS     O   : MORE     68K
C: LIBF     A   : LIBE     A
C>d:

D>dir
D: ANNOUNCE     : CGK      SUB : COPYING      : GCMDLINE C   : GKERMIT  C
D: GKERMIT  DIF : GKERMIT  H   : GKERMIT  NR  : GKERMIT  REL : GPROTO   C
D: GPROTO   W   : GUNIXIO  C   : GWART    C   : GWART    REL : LOGK     SUB
D: LOGW     SUB : MAKEFILE     : README       : README   68K : GKERMIT  68K
D: README   TXT

Sometimes the AS68YMB.DAT file is missing or wrong. Create this file with:

C>as68 -I as68init
68000 assembler initialized


Floating point workarounds

The CP/M 68k 1.3 C compiler has bugs. The floating point libraries LIBF.A and LIBE.A from version 1.3 are buggy. Use the version 1.2 files. The C compiler produces buggy floating point constants. Use long constants as replacement for float constants or use strings and atof() function to create correct float constants.

Replace buggy LIBF.A, LIBE.A

Look for "This is the binary for CP/M-68K version 1.2" file 68kv1_2.zip. In folder DISK4 you find LIBF.A, in folder DISK5 you find LIBE.A. Use program GKERMIT to transfer these files through the serial port from host computer to 68k-MBC. First copy the LIBF.A and LIBE.A files on the host computer to a directory for file transfers like C:\kermit. Use TeraTerm menu File|Change Directory... to inform TeraTerm about this directory:

Change to CP/M drive C: and execute GKERMIT from drive D: to receive file LIBF.A:

C>d:gkermit -r -w
G-Kermit CU-1.00, Columbia University, 1999-12-25
Escape back to your local Kermit and give a SEND command.

KERMIT READY TO RECEIVE...

Start KERMIT file transfer in TeraTerm with menu File|Transfer|Kermit|Send...
Repeat the same procedure with file LIBE.A.
Check file transfer with command STAT:

C>a:stat *.a

 DRIVE C:                         USER :  0
 RECS  BYTES FCBS ATTRIBUTES      NAME
  199    28K    1 DIR RW        C:LIBE    .A
  160    20K    1 DIR RW        C:LIBF    .A
----------------------------------------------
TOTAL:   48K    2
C: RW, FREE SPACE:     7,388K

The column RECS show the file size in 128 Bytes records. 160 is correct for LIBF.A from CP/M 68k 1.2.

Floating point test program

The file name is a.c:

// a.c
// CP/M68k 1.3 Alcyon C Compiler + CP/M68k 1.2 LIBF.A or LIBE.A
#include <stdio.h>

extern float atof();

static float cf[] = {    // fail
    -9.9999999E-01 ,  -1.00000011E00 ,  -1.0000002E00 ,  0 ,
     9.9999999E-01 ,   1.00000011E00 ,   1.0000002E00 ,
    -1e-19, -7e18, 1e-19, 7e18,
};

static long cl[] = {     // okay
    0xFFFFFFC0, 0x800000C1, 0x800001C1, 0,  // libf
    0xFFFFFF40, 0x80000041, 0x80000141,
    0x80000081, 0xFFFFFFFF, 0x80000001, 0xFFFFFF7F,
};

static char cs[][16] = { // okay
    "-9.9999999E-01", "-1.00000011E00", "-1.0000002E00", "0",
     "9.9999999E-01",  "1.00000011E00",  "1.0000002E00",
    "-1e-19", "-7e18", "1e-19", "7e18",
};

// floating point simple "pocket calculator"
void calculator() {
    char c;
    float f1, f2, f3;
   
    printf("\nEnter float op float, e.g. 0.6 + 0.7\n");
    for (;;) {
        scanf("%f %c %f", &f1, &c, &f2);
        switch(c) {
            case '+': f3 = f1 + f2; break;
            case '-': f3 = f1 - f2; break;
            case '*': f3 = f1 * f2; break;
            case '/': f3 = f1 / f2; break;
            default: printf("wrong op: %c\n", c); return; break;
        }   
        printf("%14.7e %c %14.7e = %14.7e\n", f1, c, f2, f3);
        printf("0x%08lx %c 0x%08lx = 0x%08lx\n", f1, c, f2, f3);
    }
}

int main(argc, argv)
    int argc;
    char *argv[];
{
    int i;
    long l;
    float f;
    double d;
   
    printf("sizeof int %d\n", sizeof(i));
    printf("sizeof long %d\n", sizeof(l));
    printf("sizeof float %d\n", sizeof(f));
    printf("sizeof double %d\n", sizeof(d));

    printf("\nfloat constants: fail\n");
    for (i = 0; i < sizeof(cf)/sizeof(cf[0]); ++i) {
        printf("0x%08lx %14.7e\n", cf[i], cf[i]);
    }
    printf("\nstring constants used as float constants: okay\n");
    for (i = 0; i < sizeof(cs)/sizeof(cs[0]); ++i) {
        f = atof(cs[i]);
        printf("%15s 0x%08lx %14.7e\n", cs[i], f, f);
    }
    printf("\nlong constants used as float constants: okay\n");
    for (i = 0; i < sizeof(cl)/sizeof(cl[0]); ++i) {
        printf("0x%08lx %14.7e\n", cl[i], cl[i]);
    }
    calculator();
}

I use copy and paste to transfer the source code from host computer to 68k-MBC. First erase an old version of a.c, then start the editor ED in insert mode with i:

C>era a.c
No file
C>a:ed a.c

NEW FILE
     : *i
    1:

Then copy the source code on the host computer into the clipboard (e.g. CTRl-A, CTRL-C) and paste it into the TeraTerm window with right mouse buttom (RMB). Leave ED insert mode with CTRL-Z, write file to disk with W and terminate ED with E:

   75:          printf("0x%08lx %14.7e\n", cl[i], cl[i]);
   76:      }
   77:      calculator();
   78:  }
   79:
     : *w
     : *e

C>

Now compile and link the program with C A and CLINKF A:

C>c a

C>CP68 -I 0: A.C A.I

C>C068 A.I A.1 A.2 A.3 -F

C>ERA A.I

C>C168 A.1 A.2 A.S

C>ERA A.1

C>ERA A.2

C>AS68 -L -U -S 0: A.S

C>ERA A.S

C>clinkf a

C>LO68 -R -O A.68K 0:S.O A.O .O .O .O .O .O .O .O .O 0:CLIB 0:LIBF.A
: Undefined symbol(s)
__optoff

Check file sizes with STAT:

C>a:stat a.*

 DRIVE C:                         USER :  0
 RECS  BYTES FCBS ATTRIBUTES      NAME
  335    44K    2 DIR RW        C:A       .68K
   19     4K    1 DIR RW        C:A       .C
   28     4K    1 DIR RW        C:A       .O
----------------------------------------------
TOTAL:   52K    4
C: RW, FREE SPACE:     7,388K


Run floating point test program A.68K. User input is bold face.


C>a
sizeof int 2
sizeof long 4
sizeof float 4
sizeof double 4

float constants: fail
0x000074C0 -0.0000692E-01
0x000076C0 -0.0000704E-01
0x000078C0 -0.0000718E-01
0x00000000   0.0000000E00
0x00007440  0.0000692E-01
0x00007640  0.0000704E-01
0x00007840  0.0000718E-01
0x27260884 -1.3264052E-19
0x36DD1EFB  -1.2354214E17
0x27260804  1.3264052E-19
0x36DD1E7B   1.2354214E17

string constants used as float constants: okay
 -9.9999999E-01 0xFFFFFEC0 -9.9999990E-01
 -1.00000011E00 0x800000C1  -1.0000000E00
  -1.0000002E00 0x800001C1  -1.0000001E00
              0 0x00000000   0.0000000E00
  9.9999999E-01 0xFFFFFE40  9.9999990E-01
  1.00000011E00 0x80000041   1.0000000E00
   1.0000002E00 0x80000141   1.0000001E00
         -1e-19 0xEC1E4A81 -1.0000000E-19
          -7e18 0xC249FFFF  -7.0000014E18
          1e-19 0xEC1E4A01  1.0000000E-19
           7e18 0xC249FF7F   7.0000014E18

long constants used as float constants: okay
0xFFFFFFC0 -9.9999990E-01
0x800000C1  -1.0000000E00
0x800001C1  -1.0000001E00
0x00000000   0.0000000E00
0xFFFFFF40  9.9999990E-01
0x80000041   1.0000000E00
0x80000141   1.0000001E00
0x80000081 -5.4210114E-20
0xFFFFFFFF  -9.2233715E18
0x80000001  5.4210114E-20
0xFFFFFF7F   9.2233715E18

Enter float op float, e.g. 0.6 + 0.7
0.6 + 0.7
 6.0000004E-01 +  7.0000000E-01 =   1.2999999E00
0x99999A40 + 0xB3333340 = 0xA6666641
6e-1 - 7e-1
 6.0000004E-01 -  7.0000000E-01 = -9.9999961E-02
0x99999A40 - 0xB3333340 = 0xCCCCC8BD
6 * 7
  6.0000000E00 *   7.0000000E00 =   4.1999998E01
0xC0000043 * 0xE0000043 = 0xA8000046
6e1 / 7e1
  6.0000000E01 /   7.0000000E01 =  8.5714282E-01
0xF0000046 / 0x8C000047 = 0xDB6DB740
^C

The submit files C.SUB and LINKF.SUB use Motorola Fast Floating Point (FFP) format. The submit files CE.SUB and LINKE.SUB use a subset of the IEEE floating point format. Motorola FFP is the better choice.

Mandelbrot program

On "hard disc" B: of the 68k-MBC microSD, there is a Mandelbrot source code "ASCIIART.BAS" for the BASIC compiler. I converted the BASIC source code into C source code to compare BASIC compiler performance and C compiler performance.

// m.c
// MANDLEBRT.BAS
#include <stdio.h>
#ifdef CPM
float atof();
#else
#include <stdlib.h>
#endif // CPM
int main()
{
    int X, Y, I;
    float CA, CB, A, B, T;                  //MANDLEBRT.BAS
    float cx=atof("0.0458");                //workaround Alycon C bugs
    float cy=atof("0.08333");
    float c2=atof("2");
    float c4=atof("4");
    for (Y= -12; Y<=12; ++Y) {              //10 FOR Y=-12 TO 12
        for (X= -39; X<=39; ++X) {          //20 FOR X=-39 TO 39
            CA=X*cx;                        //30 CA=X*0.0458
            CB=Y*cy;                        //40 CB=Y*0.08333
            A=CA;                           //50 A=CA
            B=CB;                           //60 B=CB
            for (I=0; I<=15; ++I) {         //70 FOR I=0 TO 15
                T=A*A-B*B+CA;               //80 T=A*A-B*B+CA
                B=c2*A*B+CB;                //90 B=2*A*B+CB
                A=T;                        //100 A=T
                if ((A*A+B*B)>c4) break;    //110 IF (A*A+B*B)>4 GOTO200
            }                               //120 NEXT I
            if (I>15) {
                printf(" ");                //130 PRINT " ";
            } else {                        //140 GOTO 210
                if (I>9) I=I+7;             //200 IF I>9 THEN I=I+7
                printf("%c", 48+I);         //205 PRINT CHR$(48+I);
            }
        }                                   //210 NEXT X
        printf("\n");                       //220 PRINT
    }                                       //230 NEXT Y
}

To compile the C program enter:
c m
clinkf m
The BASIC source code is not (completely) structured. One additional if() statement makes the C source code structured. There is a little price to pay for structured source code.
There are four floating point constants. I use the atof() workaround.  The C program has a runtime of 25 seconds. The BASIC program needs 5 minutes, 32 seconds. The C program executes 13 times faster then the BASIC program.
Program output is identical:

000000011111111111111111122222233347E7AB322222111100000000000000000000000000000
000001111111111111111122222222333557BF75433222211111000000000000000000000000000
000111111111111111112222222233445C      643332222111110000000000000000000000000
011111111111111111222222233444556C      654433332211111100000000000000000000000
11111111111111112222233346 D978 BCF    DF9 6556F4221111110000000000000000000000
111111111111122223333334469                 D   6322111111000000000000000000000
1111111111222333333334457DB                    85332111111100000000000000000000
11111122234B744444455556A                      96532211111110000000000000000000
122222233347BAA7AB776679                         A32211111110000000000000000000
2222233334567        9A                         A532221111111000000000000000000
222333346679                                    9432221111111000000000000000000
234445568  F                                   B5432221111111000000000000000000
                                              864332221111111000000000000000000
234445568  F                                   B5432221111111000000000000000000
222333346679                                    9432221111111000000000000000000
2222233334567        9A                         A532221111111000000000000000000
122222233347BAA7AB776679                         A32211111110000000000000000000
11111122234B744444455556A                      96532211111110000000000000000000
1111111111222333333334457DB                    85332111111100000000000000000000
111111111111122223333334469                 D   6322111111000000000000000000000
11111111111111112222233346 D978 BCF    DF9 6556F4221111110000000000000000000000
011111111111111111222222233444556C      654433332211111100000000000000000000000
000111111111111111112222222233445C      643332222111110000000000000000000000000
000001111111111111111122222222333557BF75433222211111000000000000000000000000000
000000011111111111111111122222233347E7AB322222111100000000000000000000000000000


CP/M 68k Documentation

The hackaday.io page file "CPM-68k Manuals.zip" contains the following documentation: CPM-68K_Users_Guide, CPM-68K_System_Guide, CPM-68K_Programmers_Guide and CPM-68K_C_Language_Programming_Guide.

A later Digital Research C Language Programming Guide for CP/M-68K you find in section Software Manuals.
The Gem DOS Programmers' Tools (command, as68, ar68, dump, size68) you find in section Software Manuals.