uplm80 - PL/M-80 Compiler

153 views
Skip to first unread message

aaron wohl

unread,
Dec 3, 2025, 11:02:25 AMDec 3
to retro-comp
A modern PL/M-80 compiler targeting Intel 8080 and Zilog Z80 assembly language.

PL/M-80 was the primary systems programming language for CP/M and other
8080/Z80 operating systems. This compiler can rebuild original CP/M utilities
from their PL/M source code.

Repository: https://github.com/avwohl/uplm80
PyPI: https://pypi.org/project/uplm80/
License: GPL-3.0-or-later

FEATURES

- Full PL/M-80 language support
- Targets both 8080 and Z80 instruction sets
- Multiple optimization passes (peephole, post-assembly tail merging)
- Generates relocatable object files compatible with standard CP/M linkers
- Produces code competitive with the original Digital Research compiler

CODE QUALITY

Compiled output is comparable to the original Digital Research PL/M-80 compiler:

  Program      DR PL/M-80     uplm80        Difference
  PIP.COM      7424 bytes     7127 bytes    -4.0%

INSTALLATION

Install from PyPI:

  pip install uplm80

Or install from source:

  git clone https://github.com/avwohl/uplm80.git
  cd uplm80
  pip install -e .

USAGE

Compile PL/M-80 to Assembly:

  uplm80 input.plm -o output.mac

Or run as a module:

  python -m uplm80.compiler input.plm -o output.mac

Options:
  -t 8080 or -t z80   Target CPU (default: Z80)
  -o output.mac       Output file name

Post-Assembly Optimization (Optional):

  python -m uplm80.postopt output.mac -o output_opt.mac

Performs multi-pass tail merging and skip trick optimizations.

Assemble and Link:

Use your preferred 8080/Z80 assembler and linker. Example with um80/ul80:

  um80 output.mac                              # Assemble to .rel
  ul80 -o program.com output.rel runtime.rel   # Link to CP/M .com

LANGUAGE REFERENCE

PL/M-80 is a typed systems programming language with:

- Data types: BYTE (8-bit), ADDRESS (16-bit)
- Variables: Scalars, arrays, structures, BASED variables (pointers)
- Control flow: DO/END, DO WHILE, DO CASE, IF/THEN/ELSE
- Procedures: With parameters, local variables, recursion
- Built-in functions: HIGH, LOW, DOUBLE, SHL, SHR, ROL, ROR, etc.
- I/O: INPUT, OUTPUT for port access

Example:

  hello: DO;
      DECLARE message DATA ('Hello, World!$');
      DECLARE i BYTE;

      print: PROCEDURE(addr) PUBLIC;
          DECLARE addr ADDRESS;
          /* CP/M BDOS print string */
          CALL mon1(9, addr);
      END print;

      CALL print(.message);
  END hello;

RUNTIME LIBRARY

The compiler generates calls to these runtime routines (provide in a
separate .rel file):

  Routine     Description
  ??MUL       16-bit unsigned multiply
  ??DIV       16-bit unsigned divide
  ??MOD       16-bit unsigned modulo
  ??SHL       16-bit shift left
  ??SHR       16-bit logical shift right
  ??SHRS      16-bit arithmetic shift right
  ??MOVE      Block memory move

CP/M PROGRAMS

For CP/M programs, provide stubs for:

- MON1, MON2, MON3 - BDOS calls
- BOOT - Warm boot
- BDISK, MAXB, FCB, BUFF, IOBYTE - System variables

PROJECT STRUCTURE

  uplm80/
    compiler.py    Main compiler driver
    lexer.py       Tokenizer
    parser.py      PL/M-80 parser
    ast_nodes.py   AST definitions
    codegen.py     Code generator
    peephole.py    Peephole optimizer
    postopt.py     Post-assembly optimizer
    symbols.py     Symbol table

LICENSE

This project is licensed under the GNU General Public License v3.0 or later.
See the LICENSE file for details.

CONTRIBUTING

Contributions are welcome! Please feel free to submit issues and pull requests.

ACKNOWLEDGMENTS

- Intel for creating PL/M-80
- Digital Research for creating CP/M
- The CP/M source code preservation efforts that made the original PL/M
  sources available

ladislau szilagyi

unread,
Dec 3, 2025, 11:08:58 PMDec 3
to retro-comp
Hi Aaron,

well done!

I used PL/M in the 70's to write apps for my real-time multitasking kernel running on 8080...

My RTM/Z80 ( https://github.com/Laci1953/RTM-Z80 ) is a replica for that old kernel...

I will certainly install & test uplm80, I'm interested on porting PL/M projects to Z80 CP/M machines...

Once again, congratulations...

thanks,
Ladislau

Richard Deane

unread,
Dec 6, 2025, 12:44:31 PM (12 days ago) Dec 6
to aaron wohl, retro-comp
I am trying to use uplm80 on a Raspberry Pi 500plus which runs rpi os
based on debian 13 (trixie) and python is in a managed environment.

The uplm80 will have to be installed and run from a python virtual
environment and I am having problems with that, in particular getting
"pip install" to work out of a configured and activated virtual
environment.

I hope that you can offer some advice on how to get it to work.

Cheers
Richard
> --
> You received this message because you are subscribed to the Google Groups "retro-comp" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to retro-comp+...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/retro-comp/17d7b014-f550-4e84-8d9c-21455a8f01c8n%40googlegroups.com.

aaron wohl

unread,
Dec 7, 2025, 3:38:34 AM (11 days ago) Dec 7
to retro-comp
Do let me know how the following works on 13, only tested 12 so far. I build a 13 soon

If you havent used a venv before, the main thing after creating it is to cd to the directory it makes and do "source bin/activate". You have to do that every time you login.

I just got it tested and working on debian 12.  Let me know if there are any 13 issues. 
First if you need a CP/M emulator to run built plm compiler output then see
that will install a static executable that should work on 13.  I will upgrade a pi to 13 and try it.

The following script will create a venv , install plm and the assembler and linker in it and build 
a cpm program that prints "3<return>"
The script contents follow:
#!/bin/bash set -o errexit #set -o verbose s#set -o xtrace python -m venv myuplm80 cd myuplm80/ source bin/activate PIP_INDEX_URL=https://pypi.org/simple/ PIP_EXTRA_INDEX_URL="" \ pip install --upgrade --force-reinstall --no-cache-dir um80 upeep80 uplm80 cat > print3.plm << 'EOF' /* * SIMPLE TEST */ 0100H: MON1: PROCEDURE(FUNC, PARM) EXTERNAL; DECLARE FUNC BYTE, PARM ADDRESS; END MON1; /* PRINT SINGLE CHARACTER */ PRINT$CHAR: PROCEDURE(C); DECLARE C BYTE; CALL MON1(2, C); END PRINT$CHAR; /* PRINT DIGIT 0-9 */ PRINT$DIGIT: PROCEDURE(D); DECLARE D BYTE; CALL PRINT$CHAR(D + '0'); END PRINT$DIGIT; /* SIMPLE MAIN */ MAIN: PROCEDURE; DECLARE X ADDRESS; X = 3; CALL PRINT$DIGIT(X); CALL PRINT$CHAR(13); CALL PRINT$CHAR(10); END MAIN; CALL MAIN; EOF uplm80 print3.plm um80 print3.mac ul80 print3.rel ls -l

Richard Deane

unread,
Dec 7, 2025, 4:48:06 AM (11 days ago) Dec 7
to aaron wohl, retro-comp
Making progress. Had to install python3-full, what's there by default
is less than full.
I had to set execute permission on pip in ~/venv/bin

I tried a quick and simple build of cpm3 rename.plm to create
rename.mac. Looked OK

Thanks

Richard
> To view this discussion visit https://groups.google.com/d/msgid/retro-comp/d0e70616-b946-4cef-ab3d-d25d5d8cd103n%40googlegroups.com.

aaron wohl

unread,
Dec 7, 2025, 8:51:14 AM (11 days ago) Dec 7
to retro-comp
I added a doc on how to install and use on a raspberry PI:

Richard Deane

unread,
Dec 7, 2025, 3:31:02 PM (10 days ago) Dec 7
to aaron wohl, retro-comp
Thanks for the update.

A couple of questions for UPLM80:

1) PIP.PLM in the CPM3 sources contains a number of IFDEFs to control
CPM 2.2 , CPM3 and MPM - is there a command tail om UPLM80 to pass in
a DEFINE e.g. CPM3 so that an IFDEF is resolved?
2) Attempting to compile PIP.PLM having embedded a $DEFINE CPM3 in the
source, I get the following error, is this caused by a bug in UPLM80?
(venv) rwdeane@raspberrypi:~/Downloads/cpm3-src/cpm3-master/cpm3src_unix
$ uplm80 pip.plm
pip.plm:168:13: error: Expected dimension

Cheers
Richard
> To view this discussion visit https://groups.google.com/d/msgid/retro-comp/07a8c426-c9e8-4b4a-ba40-ea2ea260cb71n%40googlegroups.com.
pip.plm

aaron wohl

unread,
Dec 8, 2025, 12:23:15 AM (10 days ago) Dec 8
to retro-comp
uplm80 compiles https://www.awohl.com/9800268B_PLM80_Programming_Manual_Jan80.pdf
So far, the only change is to add a setting for cpm (org 100, stack under bdos, jp 0 after main) and bare (traditional lxi, sp).
Searching the manual, I don't see this ifdef.  Is there a later spec on PLM?
Might some pre-processor do it?
Given the only use of PLM is to compile CP/M... I added the ifdef stuff without a formal spec.
pip.plm from cpm3 compiles now
To force an upgrade to the latest version:
pip install --upgrade --no-cache-dir uplm80

 Directives:
  - /** $set (name) **/ - Define a symbol
  - /** $reset (name) **/ - Undefine a symbol
  - /** $cond **/ - Enable conditional compilation
  - /** $if name **/ - Include code if symbol is defined
  - /** $else **/ - Else branch
  - /** $endif **/ - End conditional

  Command-line option:
  python -m uplm80.compiler input.plm -D CPM3 -D MPM  # Define symbols

  Example from pip3.plm:
  /** $set (mpm) **/
  /** $reset (cpm3) **/
  /** $cond **/

  /** $if mpm **/
      VERSION  LITERALLY '0031H',
  /** $else **/
  /** $endif **/

aaron wohl

unread,
Dec 8, 2025, 9:20:27 AM (10 days ago) Dec 8
to retro-comp
Note the file still doesn't compile after adding support for conditional compilation.   It appears to be a PIP.plm from mp/m.  The added defines at the front and $set (mpm) dont work together to get it compile for CPM3.  CLAUDE.ai suggested these changes:
  The issue isn't case sensitivity - it's that line 42 /** $reset (cpm3) **/ explicitly
  undefines the symbol that line 2 $DEFINE CPM3 defined. The file is intentionally configured
  for MPM mode (sets mpm, resets cpm3).

  To compile for CPM3 mode, you'd need to either:
  1. Remove/comment line 42 ($reset (cpm3))
  2. Or change line 41-42 to $reset (mpm) and $set (cpm3)


Richard Deane

unread,
Dec 8, 2025, 11:51:23 AM (10 days ago) Dec 8
to aaron wohl, retro-comp
Strangely, it does build under Isis and original plm but I have not deduced the mechanism by which the cpm3 or mpm defines get passed.

Plenty for me to consider in the dark winter evenings.
Richard 


aaron wohl

unread,
Dec 8, 2025, 3:29:57 PM (9 days ago) Dec 8
to retro-comp
 '  same problem trying to install uplm80 with python being "externally
managed environment" under Linux Mint on amd64 PC.
Richard  '
With APT you install python3 and pip and pypy3-venv as root globally.
After that you use venv to create a pip enviornment for your project. The rest of the python packages like umd80 goes like:
apt install python3 pip apt-venv
wohl@r5:~ $ python -m venv my80
wohl@r5:~ $ cd my80/
wohl@r5:~/my80 $ source bin/activate
(my80) wohl@r5:~/my80 $ pip install um80
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting um80
  Downloading https://www.piwheels.org/simple/um80/um80-0.3.9-py3-none-any.whl (76 kB)
Installing collected packages: um80
Successfully installed um80-0.3.9
(my80) wohl@r5:~/my80 $

Everytime you login you need to cd and then do source bin/activate

Richard Deane

unread,
Dec 9, 2025, 3:46:30 PM (8 days ago) Dec 9
to aaron wohl, retro-comp
I have edited pip.plm to remove all ifdefs, creating pip3.plm which
builds ok under original plm under isis. Runs ok on cpm3.

When I compile this on UPLM80 i get the error
((venv) rwdeane@raspberrypi:~/Downloads/cpm3-master/cpm3src_unix $
uplm80 pip3.plm
pip3.plm:157:13: error: Expected dimension

where line 157 is the "fcb(FRSIZE) byte," line

source structure (
fcb(FRSIZE) byte,
pwnam(nsize) byte,
pwmode byte,
user byte,
type byte ),

This seems likely to be a UPLM80 compiler bug.

Cheers
Richard


On Mon, 8 Dec 2025 at 20:30, aaron wohl <aaw...@gmail.com> wrote:
>
> ' same problem trying to install uplm80 with python being "externally
> To view this discussion visit https://groups.google.com/d/msgid/retro-comp/7442fbe4-6d84-4897-860b-48f660d012ebn%40googlegroups.com.
pip3.plm

aaron wohl

unread,
Dec 9, 2025, 7:50:55 PM (8 days ago) Dec 9
to retro-comp
You are correct, it was a compiler bug.  It was only accepting a number for a dimension not a literal.  
Fixed now,  pip install --no-cache-dir -U uplm80
Reply all
Reply to author
Forward
0 new messages