Built an RC2014 pro system. Some notes:

Clock board provides "CLK2" and "RESET2". Remove JP1 on SIO/2 to use CLK2 for port B. (Port A is hard-wired to CLK1 so 119200 baud). Remove P1 (reset) since RESET2 shares PAGE pin.

Assemble 64K RAM card with expansion bus connector type.

ROM is:

Labl Addr  A15 A14 A13  What
  2  0000   0   0   0   MS Basic SIO/2 32K RAM
  4  2000   0   0   1   MS BASIC SIO/2 65K RAM
  8  4000   0   1  --   SCM universal w/BASIC, CP/M
  8  6000  
  6  8000   1   0   0   CP/M monitor ACIA CF module
  0  A000   1   0   1   -empty-
  0  C000   1   1   0   -empty-
  9  E000   1   1   1   SCM any


RAM card: all to R


Address  | Board | Function      | Notes
$80-$83  | SIO2  | Z80 SIO ports | A4=0, A4..A6 decoded, A0..A1 local
$10-$17  | CF    | Compact Flash | A6=0, A3..A5 decoded, A0..A2 local
$20-$23  | IDE   | Hard disk     | A2...A7 fully decoded
$30, $38 | ROM   | Paged Rom     | A6=0, A3..A5 decoded.  Use only $30, $38


  • SIO2: pretty normal
  • CF: 8 ports decoded at $10
  • ROM: Weird. $30 resets page register, $38 toggles it!

To Do (general)

  • Modify EEPROM UMON for auto-boot (Console timeout on SIO port A? Respect console switch?)
  • expand to 16 disks (7 for now)
  • write backup utility to clone a CP/M drive to another HD/location
  • (implement sector blocking for 512 byte sectors)
  • (Make a CP/M version of UMON)

Client/Server project

  • Change directory listing to line-by-line so client buffer can be small
  • Check for file overwrite in client
  • Wildcards
  • Fix checksum errors
  • Implement "put" command (this can serve as backup for CP/M)
  • Ordered directory listing with size


  • SIO A/B console assignment (IOBYTE?)
    • Initial version just looks for input from both
  • Stripped-down IDE code and possibly SIO to shrink size
  • Modify IDE code to use only a 128 byte buffer.

Hard Disk Layout

  A0: basic utilities
  B0: Worstar 4
  C0: Games
  D0: Vedit plus
  E0: ZCPR1 sources, MAC, RMAC
  F0: Astec-C
  G0: HiTech C


HOWTO spin down HD:

> R AF E000
> F 10


Dual-clock was a bad idea, due to required CDC at UARTs. Ditched it. Back to single 25.000MHz clock. Here are the freqs and errors for standard rates. Note that the error is freq error, so cumulative error for last bit is 10X. Using 9600 for now.

	;; for 25.000 MHz pixel clock
	;; 4800   = 0x146   (0.15% err)
	;; 9600   = 0x0a3   (0.15% err)
	;; 19200  = 0x051   (0.47% err)
	;; 38400  = 0x029   (0.75% err)
	;; 57600  = 0x01b   (4.7% err!)
	;; 115200 = 0x00e   (3.3% err!)

Built new pico_control_multi_uart with up to 4 UARTs (currently 3 configured). Ports are:

  1. USB console (currently 9600 baud)
  2. RC2014 (jumper sel, current SIO port B)
  3. Keyboard at 4800 baud (input only for now)

Comms tested and working using debug monitors on both ends (that sure is convenient!). Now pondering what the "real" terminal code should do:

  • Sample KB and copy to port 1 Tx
  • Sample port 1 Rx and process for VGA
  • Watch for CR on port 0 and exit to monitor


Terminal on RC2014 PCB is working! Weird Vivado issue with timing constraints. It somehow inferred two clocks which were slightly different freq. Deleted my clock constraint and pasted in the constraint from the clock IP core... now all is good. Using BRAM for the text memory... seems to work fine.

(Note: somehow HS wasn't making it through but now it's OK. Possibly an intermittent somewhere)

Next steps:

  • Implement 4800 baud keyboard input to 115200. 25.000MHz doesn't really work for a 16X clock.

Used clock wizard to make a 7.375MHz clock which is almost exactly 115200*16*4. This involves changes to pico_control so for it as pico_control_xbaud.

Building a new version with console at 115200 baud using a dual output MMCM generating 25.0xx and 7.37xx clocks. Freqs are not exact but should be OK. We'll see if it makes timing. Probably we have to tell it to ignore the relative clock timing.


Got VGA PCBs. Working on porting terminal to CMOD. Doesn't fit as-is in 7A15T. Turns out the text memory was implemented in LUTs and filled most of the 7A35T chip. Sigh; should have ordered the bigger one. Converting text memory to BRAM. Now it fails timing. Using a clock wiz thing for 12MHz -> 25.162MHz.

Remove the BRAM, attach a constant to the TEXT_D input to VGA. Still fails timing?! Try with fake 25.175MHz input at top; builds fine with BRAM. Try again with 12->25.00 MHz clock wiz. Still fails timing. Something is up.


CBIOS issues all sorted? Now we have: 128-byte sectors, disk spin-down, serial port swap selected by switch. Top currently at FEB5.

Problem again with upper disk numbers. Turns out there were a few issues: PUTSYS was not writing enough sectors (26*2 not enough, changed to 64). hexload.asm also had an issue with overwriting the first byte, probably due to the stack. Moved the stack down a few bytes. All seems OK now.

Wordstar screen update sucks with out the VT102-specific line delete ("ESC [ M") and line insert ("ESC [ L"). Probably need to implement these.


Almost working. VEDIT is OK, wordstar is ok-ish when set for 38x79. Debug plan: implement 2nd serial port on picoblaze and send some debug output there. Or, do it on linux.


Occasional disk corruption on A: has me worried. Should implement auto disk spin-down. This works:

     ld c, IDE_NSEC
     ld a,1                ; timout in 5s units
     call IDE_Byte_Write
     ld a, 0e2h            ; spin down
     call IDE_Do_Cmd

Implemented a 10 minute timeout in the CBIOS and also shrink the sector buffer to 128 bytes by way of a conditional change to disk_ide.asm.

Wrote G:DIFF.C (trivial text file compare).

Wired up an I/O board. Only one switch for now, at bit 0 of I/O address 00. 2021-12'13

Back to Video. Have a very preliminary terminal thing working in new github repo "vga_terminal". Found another one online with included VT100! See (vga.vhd).


Keyboard working 100% (hopefully) at 4800 baud. Now I need some CBIOS tweaks so I can run the keyboard on SIO-B and display on SIO-A. For now a mode where input can come from either one seems OK.



Keyboard hardware built and verified. Working on code in repo. Preliminary main.c works for non-shifted characters but starting on re-write. N.B. that 1MHz AVR can manage only 4800 baud serial without significant errors (see tables).

This implies a modified SIO port with different Tx/Rx? rates, a new port or some other solution (see below).


Working on FPGA VGA in repo. Seems as if a terminal emulator written in Picoblaze assembly with a serial port is the way to go. Just as an example, Picoblaze code to scroll a 40-line screen with 100MHz clock takes about 0.5ms. Not bad!

The FPGA could also provide a "fast" console interface at e.g. 115200 baud and translate the keyboard hits. Seems as if a new PCB with CMOD-A7 is in order! Note that this really needn't be an RC2014 board, but it should be no problem to fit since it requires only the VGA DAC.

Ordered a CMOD-A7-15 today.


Keyboard assembled... just need diodes, connectors and firmware.

Video ideas: Use an Artix-7 (Basys3 or PMOD) to address video RAM on the FPGA. Either 12 bits address plus 8 bits data, or just one 8-bit port with a few control signals. Then the Z80 would manage the RAM. Probably also need a programmable start address or firmware support for scrolling.

Temporary solution:

Build a 7-bit parallel port port with strobe on a vectorboard. For output only a 74xx138 and 74xx374 would work. Wire to a ribbon cable to a PMOD.

Longer term: Make a proper PCB with a CMOD-A7 and both address decoding for access to the video RAM and a serial port option.


How to call assembly from C? Seems as if the args are passed on the stack. For example, let's see strlen().

	psect	text
	global	_strlen
	pop	hl            ; return address?
	pop	de            ; argument
	push	de            ; put it back
	push	hl            ; and the return address
	ld	hl,0          ; initialize length to zero

1:	ld	a,(de)        ; get a character
	or	a             ; is zero?
	ret	z             ; yes, return length in HL
	inc	hl            ; increment length
	inc	de            ; point to next
	jr	1b            ; and loop

So we should be able to write a very similar sio_gets( buff) function where the user passes the address of the buffer. Done! Created G:SIOB.AS with siorxv(), sioputs() and siogets().

Wrote a preliminary file server simple_server.c with CP/M client net.c. Includes:

  • Directory path navigation (via chdir and getcwd)
  • Convert filename to CP/M style


Reasonably happy with Pico-Pi stand-alone console for now. Need a file transfer utility on SIO/B. An idea... xfer client on CP/M which understands commands like "ls", "cd", "get" and "put". The server would do most of the work. Wildcards supported, files sent back with compliant CP/M filenames in hex.

Ideally, this would be in 'C'. First challenge is to write a terminal program for CP/M. Probably easiest if the CBIOS supports the port as a Punch/Reader?.

Wrote a terminal program (G:TERM.C) and simple_server.c. Loses characters. Clearly C is too slow on the CP/M side.


  • Ordered two more AT28C256 EEPROMS from Jameco.
  • Programmed an EEPROM with a reloc-d UMON (boots at 0000, copies itself to 8100). Works! Created a boot loader boot.asm and typed in the hex. Boots CP/M OK.
  • Proposal:

Add "GETSYS" to UMON. Maybe:

> gs                        # default, load 64 sectors to E000
> gs <lba_h> <lba_l> <addr> <count>   # specify all

The Pi Pico terminal has issues with VEDIT. Reverse video doesn't work, and the cursor leaves a trail of white character cells when it moves. Possibly this could be fixed, but really we want a proper, color VGA terminal.


Soldered up Pi PICO terminal. Works fine. In a non-extended slot it works on SIO-A. Only issue is that we've been downloading stuff on port A... need to modify the loaders hexload and hexlcpm to work with port B.

Also really need to program an EEPROM boot loader so the thing can be standalone.


A: is now 8MB disk, with one reserved track. OS starts at sector 0, including CBIOS. Modified cbios_hd and putsys accordingly.

Configured and assembled ZCPR1 from cpmish sources (assemble using MAC.COM under CP/M). Integrate it using and write to A: using putsys. Now drive A user 0 is searched for commands.

Also, configure VEDIT with emacs-like keys so I can more or less use it, and switch to terminal incantation minicom -m so most keystrokes get through.

Configured now for 7 disks (A-G) all 8 MiB. More than that causes the CBIOS to overflow due to the large disk tables. Currently the CBIOS ends at FFE5. Note that the 512 byte disk buffer could be shrunk to 128 bytes with modified IDE functions.


Working on hexlcpm problem. Turns out the disk change caused a delay longer than the timeout in Fixed now. One can do things like ./send_hex zout/xyzzy.hex


Finally we have a reliable file transfer into CP/M: hexlcpm.asm uses the sio.asm for direct SIO access, and some version of Grant Searle's download disk writing code. Works on drive A but not (yet) on other drives. Transmit program re-written as Sends {{{A:HEXLOAD FILE.

Abandoning (for now) the combo BIOS. Going back to cbios_hd. Some changes:

  • Add 2 additional drives (C, D) both also 8 MiB.
  • Reduce directory entries from 1024 to 256 which should hopefully speed up directory ops.

Procedure to make a new system:

  • Edit system size (currently 63K) in cbios_hd.asm
  • Edit system size in putsys.asm to match
  • zmac cbios_hd.asm
  • zmac putsys.asm
  • Reset system and run which does:
    • Load hexload at C000
    • Load umon at 8100
    • Load cbios_hd at e.g. F600
    • Load cpm_63
    • Load putsys at 100
  • Connect terminal, type "G 100" at UMON prompt to run putsys
  • Type "G F600" to start 63K CP/M

Annoyingly, after a reboot with the new cbios disk A seems a bit hosed. Going to re-format and re-download things.

Fixed up send_hex so it can send to either the C000 memory-resident loader or to HEXLOAD.COM under CP/M. All loading is much faster now :)

Wrote a simple bootloader to boot from HD drive A. Wrote getsys which seems to work, after rearranging the cbios_hd.asm so the disk tables are at the end. As of now BIOS code runs to F91A and initialized data to F9B8. Memory used to FDE7, leaving (currently) 218H free bytes above BIOS in a 63K system.

N.B. the BIOS could likely be shrunk to allow a 64K system. Some ideas:

  • Modify the IDE code to avoid the host buffer
  • Customize the serial and IDE code, eliminating spurious stuff


Hybrid BIOS cbios_combo.asm has both IDE and remote disks. Doesn't work reliably though. Seems as if the IDE disks are OK but there is some issue with the remote disks. Could be the DISKDEF macros?

Working on a new version of hexlcpm.asm. Works OK, but needs some tweaking of load_hex.jl to wait for a '+' before sending each hex record. The all should be good.

Need to write a CP/M hex dump thing too to send data back to Linux.


Working with two drives as below. Current BIOS is cbios_hd.asm. Fixed sectran to recognize null table. Drive B maxes out 8-bit track and sector counts; only superficially tested. format_hd.asm is an ORG-1000 tool to format (write E5 to track 0) of drive B.

Still the only way to get files in is to use hexload.asm at C000 and then CP/M SAVE command.


  • Revive the remote disk BIOS on drive C using SIO port B and the Julia disk server. This may require shrinking CP/M a bit.
  • Write or adapt another disk transfer tool which uses SIO port B
A>stat a:dsk:

    A: Drive Characteristics
 1944: 128 Byte Record Capacity
  243: Kilobyte Drive  Capacity
   64: 32  Byte Directory Entries
   64: Checked  Directory Entries
  128: Records/ Extent
    8: Records/ Block
   26: Sectors/ Track
    2: Reserved Tracks

A>stat b:dsk:

    B: Drive Characteristics
65536: 128 Byte Record Capacity
 8192: Kilobyte Drive  Capacity
 1024: 32  Byte Directory Entries
    0: Checked  Directory Entries
  512: Records/ Extent
   64: Records/ Block
  256: Sectors/ Track
    0: Reserved Tracks


Working on IDE CBIOS. For now, LBA is set as (0, disk, track, sector) and only 128 bytes of 512 byte sector are used. Having various issues but found a typo in the 1:1 sector translation table, which explains why the no-translation disk wasn't working reliably. Reformatting drive A.

Downloading STAT.HEX, save with "SAVE 22 STAT.COM". Seems to work!


Mofified IDE_Word_Read to turn off /RD leave /CS0 on. Now it works. (for now Byte_Read just calls Word_Read).

FIXME: switch to using the 8255 bitwise operations for everything. Also need to fix the write functions.


Narrowing it down:

  • Reads fail intermittently, skip first 16-bit word
  • Read ID also fails
  • Doing by hand: setup_LBA, do CMD EC, read word also fails sometimes

Several tries show that only the first word is missing; the remaining 254 bytes are fine. So I think this rules out a glitchy electrical signal. Seems like an order/timing thing when issuing the read or read ID commands.

So what exactly happens?

  • Setup LBA shouldn't be a problem, since it is ~ ignored for READ_ID
  • set AF=EC00, func=IDE_Do_CMD
    • IDE_Wait_Ready
      • IDE_Get_Status
        • IDE_Byte_Read
          • set PPIC = Addr+/CS0
          • Set PPIC |= /RD
          • Grab data
          • set PPIC = 0
    • set C=7, Jr IDE_Byte_Write
      • set PPICTL = out mode
      • set PPIA = data
      • set PPIC = Addr+/CS0
      • set PPIC |= /WR
      • set PPIC = Addr (no /CS0, no /WR) set PPICTL = in mode


Disk writing fails for multi-sector loops. Added a small delay in umon but still not working.


Some edits lost. CP/M working with remote serial disk (...CPM_OS/disk_server.jl). Various softwares tested (MBASIC, ZORK1) seem to work.

Assembled and installed RC2014 IDE interface. Two HD available; one works (IBM-DJNA-352030) while another (Seagate ST340016) has problems with mis-aligned data on write. Tried writing e.g. UMON based at 9100 to the IMB and reading it back and executing... seems good.

New UMON features:

  • IDE functions
  • Jump table at beginning now accessible using "F" command
  • Register save/restore working, "R" command display/edit registers
  • "G" command restores registers
  • "X" and "Y" commands read and write to IDE disk


This fails:

   B> A:
   A> DDT

So apparently the BIOS can't write correctly to disk A. Clear out some space on the cpm22-src-1.disk from yaze. Copy DDT to it: all fine. Copy DDT to A: and back to C: and it doesn't work.


Some issues: My home-made disk boots fine but using cpmtools to copy big files fails (e.g. MBASIC.COM). Can run the cpm22 distro disks I found fine.

Grant Searle's DOWNLOAD doesn't work because 115200 baud overruns the CP/M console. Would like to slow the console to 9600 baud, but it is stuck on SIO port A which is hard-wired to the 7.6MHz system clock. Sigh.


  • Hack the SIO/2 board to allow CLOCK2 on either port.
  • Reprogram the EEPROM with UMON or something using the other port
  • Use the ROM '9' option which supports SCM switching ports, then write some code to send a console switch to port A, then use port B as the console and port A for the disk server

The final option is software-only and so preferred. Let's try CLK2=1.228MHz for 19200 baud for the console.


CP/M 2.2 is now running. Disk I/O a bit slow but working at 115200 baud. Currently the BIOS is set to expect up 4 IBM format drives (77x26x128). Drive A has no skew, drives B,C,D have the "standard" 6-sector skew.

To start:

  Power up board to SCM
  sh                 this loads UMON at 8100H, hex loader at C000
  sh               this loads 20k CP/M CCP, BDOS, CBIOS
  screen /dev/ttyUSB1 115200    connect to the console
   --- on another terminal ---
  julia disk_server.jl /dev/ttyACM0 115200 cpm.dsk cpm22-src-1.dsk cpm22-src-2.dsk 
   --- back on the console ---
  >G 4A00

Note that the script expects zout/cbios_disk.hex to contain current BIOS and ../CPM_OS/zout/cpm_20.hex to contain CCP, BDOS built from source.

2021-10-09 II

Starting on CP/M from scratch, planning to use remote serial disk.

Protocol overview:

   > R tt ss           # read track tt (0-based) sector ss (1-based) 128 bytes
   < ...128 hex bytes followed by checksum...
   < OK
   > W tt ss
   > ...128 hex bytes followed by checksum...
   < OK


Seems as if the CF is just flaky. Have tried two cards (used a USB thing and dd to make a copy). Both are flaky. Tried a couple of arrangements of cards on the bus. Still flaky.

Ordered an IDE interface, looking to move to a hard drive.

Investigating the 512K RAM/ROM card. Looks interesting, though by default in ROMWBW the ROM is read-only.

Thinking about a new card: 2MB Flash drive using (4) 512Kx8 flash chips with paging scheme compatible with the 512K card. It would need two '670 register files plus a decoder and a page disable latch of some sort. It just about all fits!


CP/M boots but there are plenty of odd things. I'm suspicious of the CF adapter. Would really prefer to have a way to back up and restore disks for CP/M. Ordered a cheap USB universal SD/CF card reader, maybe this will work.

Wrote a simple loader in julia to read an Intel hex file and send it as a series of memory edit commands to SCM:


Preparing for power-up. Install "5V DIRECT" jumper. Now 5V comes in from screw terminals.

Remove JP1 on SIO/2 and P1 from clock board. Set jumper on clock board for 9600 baud on SIO port B.

Set ROM jumpers to 8K page size, address 0000 for 32K Basic. (hope it uses SIO port B!) Investigating RTS on SIO... It's an output, so we can ignore it for now.

PAGE=0 on RESET, and stays low. Should set PAGE=1 on OUT to 56(10). So, need bottom jumper to the R on the RAM card.

It works! Running the "SCM" universal 16K with BASIC and it works OK. So does the image 9 SCM. Neither stand-alone BASIC seems to work.

SCM: Seems to run from ROM at 0000, with RAM at 8000.

Last modified 4 months ago Last modified on Apr 27, 2022, 6:15:48 PM