Paolo Amoroso's Journal

Assembly

The short ebooks on Intel 8086 Assembly programming Oscar Toledo self published are some of the best 8086 resources I've seen.

After introducing the 8086 instruction set and Assembly, Programming Boot Sector Games describes the source code of several games each of which fits into the boot sector of a PC, 512 bytes. The sequel, More Boot Sector Games, covers more games.

The introduction to 8086 insructions in the first book is short and to the point. It's enough to code fairly advanced programs without overwhelming with information as processor manuals and full length books typically do. But Oscar also shares his considerable experience and insight by discussing many 8086 tricks, idioms, practical advice, and optimization techniques.

The books cover not only games but also other interesting 8086 programs such as a BASIC interpreter and a tiny operating system.

Since some of the code is dense and advanced, reading it once may not be enough for a full understanding. But a great feature of these programs is they fit into 512 bytes, so there's an upper bound to complexity which rereading the code and the text helps overcome.

The only downside of the books is they are password-protected PDF files. It's an annoyance when opening them in ereading apps but is easy to overcome.

#assembly #books

Discuss... Email | Reply @amoroso@fosstodon.org

Sometimes experimenting with the Z80-MBC2 and V20-MBC homebrew CP/M computers leaves the screen garbled or in an unusable state, usually because some program doesn't correctly handle the terminal. The Minicom terminal emulator I use for CP/M sessions has a command for clearing the screen but it may not be enough.

Since a native CP/M solution is more effective I wrote two short utilities in Assembly for properly clearing and initializing the screen.

These transient programs share the same name, CLS. One runs under CP/M-80 on the Z80-MBC2 with a Z80 processor, the other under CP/M-86 on the V20-MBC with a Nec V20 in 8088 mode. Both assume an ANSI/VT100 terminal and are launched by executing CLS at the command prompt:

A>CLS

The programs work the same way not just due to the similarity of CP/M's design across different architectures, but also because they are variations of a hello world demo that prints a text string to the console.

After defining constants for the CP/M system functions and resources they access, the CLS programs call the write string BDOS function to output a string of ANSI escape codes for clearing the screen and moving the cursor to the home position. The definition of the string ends both programs.

CLS for CP/M-80

The CLS program for CP/M-80 is written in Intel 8080 Assembly:

; Clear the screen.
;
; Runs on CP/M-80 with an ANSI/VT100 terminal.

TPA                 equ     100h
BDOS                equ     05h
WRITESTR            equ     09h             ; Write string


                    org     TPA

                    mvi     c, WRITESTR
                    lxi     d, clshome
                    call    BDOS

                    ret


; ANSI escapes:
;   clear screen      : ESC [ 2 J
;   go to screen home : ESC [ H
clshome:            db      1bh, '[2J', 1bh, '[H$'

                    end

For a short program like this that doesn't need a large stack a ret instruction is adequate to return control to CP/M.

I assembled the program with the asm80 assembler of Suite8080, my suite of 8080 Assembly cross-development tools in Python, and transferred the CLS executable to the Z80-MBC2 over the serial line.

CLS for CP/M-86

CLS for CP/M-86 is written in Intel 8086 Assembly. Aside from the different instruction set and the segmentation directives, this version calls the 00h BDOS function of int 224 to return control as CP/M-86 requires:

; Clear the screen.
;
; Runs on CP/M-86 with an ANSI/VT100 terminal.


WRITESTR            equ     09h             ; BDOS function write string
TERMCPM             equ     00h             ; BDOS function terminate program


                    cseg

                    mov     cl, WRITESTR
                    lea     dx, clshome
                    int     224

                    mov     cl, TERMCPM
                    int     224


                    dseg

                    org     100h

; ANSI escapes:
;    clear screen      : ESC [ 2 J
;    go to screen home : ESC [ H
clshome             db      1bh, '[2J', 1bh, '[H$'

                    end

I transferred the Assembly source to the V20-MBC over the serial line and assembled it with the hosted ASM86 assembler that comes with CP/M-86.

#Assembly #z80mbc2 #v20mbc

Discuss... Email | Reply @amoroso@fosstodon.org

The iLoad feature of the Z80-MBC2 homebrew Z80 computer allows uploading binary code that runs on the bare metal.

I thought it would be fun to try iLoad with some Intel 8080 code generated by the asm80 assembler of Suite8080, the suite of 8080 Assembly cross-development tools I'm writing in Python. But the code crashed the Z80-MBC2, uncovering a major asm80 bug.

It all started when, to practice the toolchain and process, I started with a Z80 Assembly demo that comes with the Z80-MBC2, which prints a message to the console and blinks one of the devices's LEDs. I assembled the program with the zasm Z80 and 8080 assembler, uploaded it via iLoad, and successfully ran it.

Next, I ported the blinking LED demo from Z80 to 8080 code and assembled it with asm80. But when I ran the demo on the Z80-MBC2, it crashed the device.

The baffling crash left me stuck for threee months, as I had no tools for debugging on the bare metal and there were only a few vague clues.

I carefully studied the less than a hundred lines of code and they looked fine. To isolate the issue I cut the code in half, leaving the part that prints a message to the console, and transforming the blinking demo into this bare.asm hello world for the bare metal:

OPCODE_PORT     equ     01h
EXEC_WPORT      equ     00h
TX_OPCODE       equ     01h
EOS             equ     00h
CR              equ     0dh
LF              equ     0ah


                org     0h

                jmp     start

                ds      16
stack:


start:          lxi     sp, start
                lxi     h, message
                call    puts

                hlt


message:        db      CR, LF, 'Greetings from the bare metal', CR, LF, EOS


puts:           push    psw
                push    h
puts_loop:      mov     a, m
                cpi     EOS
                jz      puts_end
                call    putc
                inx     h
                jmp     puts_loop
puts_end:       pop     h
                pop     psw
                ret


putc:           push    psw
                mvi     a, TX_OPCODE
                out     OPCODE_PORT
                pop     psw
                out     EXEC_WPORT
                ret

                end

The constants at the beginning define the addresses of the output ports, the opcode for sending a character over the serial line, and a couple of control characters. Next, the program sets up the stack and iterates over the output string to print every character.

The simplified demo program still crashed the Z80-MBC2, forcing me back to the drawing board.

Then I had an epiphany. What if the binary code asm80 generates is different from zasm's?

I fired up the dis80 disassembler of Suite8080 and compared the output of the assemblers. Sure enough, the difference jumped at me: the destination addresses of all the branches after the label message are off by 5 bytes.

The instructions branch to addresses 5 bytes lower, so the call to puts executes random string data that chrashes the device. The last correct address asm80 outputs is that of the label message. The address of the next one, puts, is wrong and leads to the crash.

Indeed, the same demo code assembled with zasm ran fine on the Z80-MBC2 and printed the expected message. This confirmed my hunch.

What now?

The next step is to find the bug in the Python source of asm80, which I'm developing with Replit. Although Replit provides a debugger, I won't use it. The tool is not well documented and I'm not sure how it works. In addition the Replit debugger is best suited to code started from the run button. This is inconvenient for command line programs like the Python scripts of Suite8080.

Therefore, I'll take the opportunity to use Python's native debugger pdb, which I always wanted to try in a real project. I played with pdb a bit and it looks easy to use, with all the commands and options handy.

Let's see if pdb can help me pinpoint the bug in the Python code.

#Suite8080 #z80mbc2 #Assembly #Python

Discuss... Email | Reply @amoroso@fosstodon.org

I ported to Intel 8080 Assembly the Z80-MBC2 Z80 code of the led blink demo, assembled it with the Suite8080 assembler, and uploaded the HEX binary to the Z80-MBC2. Instead of printing a message to the console and blinking the User led, the program made the led turn on and the board reset, booting up the default operating system.

I have no tools to debug software that runs on the bare metal. So the next step is to investigate these clues, starting from a comparison of the Z80 and 8080 sources.

#Assembly #z80mbc2 #Suite8080

Discuss... Email | Reply @amoroso@fosstodon.org

I'm looking for source code of MS-DOS .COM programs in real-mode 8086 Assembly written in NASM syntax.

Why such weird requirements? I'm learning Assembly programming under MS-DOS and MikeOS, and cross-developing on Linux with NASM. To avoid the complexity of x86 segmentation, I prefer to focus on single-segment programs as I plan to write small applications anyway. So I'd like to study examples of how these programs organize and reference data and code.

Despite the long history and influence of MS-DOS, finding .COM code is proving unexpectedly hard.

Googling doesn't turn up much. I solicited recommendations on comp.os.ms.dos.programmer, comp.lang.asm.x86, the NASM forum, and Mastodon but, aside from a lot of engagement and interest (and the occasional suggestion I change my mind and target Windows), I got few resources.

#Assembly #MSDOS #retrocomputing #x86

Discuss... Email | Reply @amoroso@fosstodon.org

Working on Suite8080 and using the Z80MBC2 encouraged me to learn Intel 8080 and Z80 Assembly.

Now I'm dipping my toes into x86 Assembly, which I've always been curious about and attracted by. An additional motivation is discovering MikeOS, a lovely real-mode operating system in x86 Assembly that's surprisingly easy to read and understand.

Therefore, I put together an environment to cross-develop and run x86 code.

Requirements

My desktop daily driver is an ASUS Chromebox 3, with the Crostini Linux container of chromeOS that lets me run most Linux software. Any emulation and development tools must therefore be available for Linux.

Intel x86 Assembly is close enough to 8080 Assembly that x86 code looks familiar. To leverage this similarity and avoid the complexity of x86 segmentation, for the time being I want to develop 16-bit real-mode programs that fit into the single segment of a .COM executable.

The obvious choice for an operating system to run these programs is MS-DOS. Why? Because its rich environment strikes the right balance between complexity and functionality.

So the emulator must run a full MS-DOS environment and boot from media images. Which enables coding for the bare metal, a PC with no operating system.

As for the Assembly tools, I prefer to cross-develop on Linux. Although I'm a retrocmputing enthusiast, for writing and building code I want the convenience of a modern system.

MS-DOS emulation

QEMU would have been my first choice for x86 emulation but has compatibility issues with Crostini, as virtualizing in a virtual environment is tricky.

A bit of research led me to DOSBox-X, which runs fine on Crostini as it emulates rather than virtualize. DOSBox-X is a fork of DOSBox with a more fleshed out MS-DOS environment. Plus it can boot from media images.

DOSBox-X running under chromeOS on my Chromebox looks like this:

DOSBox-X session under chromeOS on an ASUS Chromebox 3.

Installing DOSBox-X on the Chromebox was straightforward. However, the Flatpak package carried almost a gigabyte worth of dependencies that nearly filled the Crostini file system. I had to resize the container from 7.5 to 10 GB to accommodate all the files and leave enough free space.

A feature I like is the emulator can mount a directory tree of the Linux file system as an MS-DOS disk. This is handy for quickly transferring cross-assembled binaries to MS-DOS.

DOSBox-X has rendering issues on chromOS, i.e. the menu bar and items flicker randomly. A workaround is to turn off the menu bar.

I tested DOSBox-X booting by checking out MikeOS, which runs well.

Assembly development

There's a vast assortment of x86 assemblers but I skipped the research by picking NASM for Linux.

I like this popular tool because it's rich of features and comes with extensive documentation. It also has a clean, non verbose syntax and can generate MS-DOS .COM programs.

To test the full cross-development and emulation toolchain I used NASM to assemble an x86 real-mode hello world demo, copied it to a directory DOSBox-X mounts as a disk, and ran it under MS-DOS. Success! A simple and low friction workflow.

#Assembly #MSDOS #chromeOS #retrocomputing #x86

Discuss... Email | Reply @amoroso@fosstodon.org

I develop programs for the Z80-MBC2 in Intel 8080 Assembly with the Suite8080 8080 assembler. Aside from the binary compatibility, the Z80-MBC2 is a Z80 system, so I wondered what Assembly mnemonics and register set the CP/M development tools work with.

It turns out my favorite CP/M debugger, the SID symbolic debugger, comes in two versions specialized on the CPU, SID and ZSID.

SID accepts as input and outputs 8080 mnemonics and registers, ZSID Z80 mnemonics and registers. For example, in this CP/M 3.0 SID session on the Z80-MBC2 I loaded my Twirl 8080 program and disassembled it with SID's l (list) command:

A>sid f:twirl.com
CP/M 3 SID - Version 3.0
NEXT MSZE  PC  END
0180 0180 0100 D4FF
#l
  0100  MVI  C,09
  0102  LXI  D,0139
  0105  CALL 0005
  0108  LXI  H,0142
  010B  MVI  B,08
  010D  PUSH H
  010E  PUSH B
  010F  MVI  C,09
  0111  LXI  D,013E
  0114  CALL 0005
  0117  POP  B7

The output contains 8080 mnemonics. An analogous ZSID session running the same disassembly command outputs Z80 mnemonics:

A>zsid f:twirl.com
SID VERS 1.4
NEXT  PC  END
0180 0100 CDFF
#l
  0100  LD   C,09
  0102  LD   DE,0139
  0105  CALL 0005
  0108  LD   HL,0142
  010B  LD   B,08
  010D  PUSH HL
  010E  PUSH BC
  010F  LD   C,09
  0111  LD   DE,013E
  0114  CALL 0005
  0117  POP  BC

SID thus lets me work in a 8080 environment on a Z80 system.

#z80mbc2 #Suite8080 #CPM #Assembly

Discuss... Email | Reply @amoroso@fosstodon.org

I've started writing a new Suite8080 demo in Intel 8080 Assembly, a CP/M program to move an ASCII character across the screen with vi's cursor keys h, j, k, and l. It's my first interactive CP/M program and does raw non-blocking I/O to read key presses.

#Assembly #Suite8080

Discuss... Email | Reply @amoroso@fosstodon.org

I ported to CP/M Twirl, a twirling bar animation demo in Intel 8080 Assembly. I originally developed it for emuStudio, an emulator and development environment that recreates a number of classic computers and CPUs. The port runs on any CP/M system with an ANSI terminal or display.

I recorded a screencast that shows Twirl running under CP/M 3.0 on the Z80-MBC2 computer, controlled from a Minicom terminal emulator session under Crostini Linux on my Chromebox.

How it works

The demo displays an ASCII animation of a bar twirling clockwise at the home position of the console, i.e. the top left corner. After clearing the screen, Twirl repeatedly moves the cursor to the console home, prints the current animation frame, and checks for key presses to decide whether to terminate.

Although visually simple, the program demonstrates some key Assembly programming techniques such as looping, calling BDOS functions, and doing output with ANSI escape codes.

Porting

The original Twirl runs on a virtual Altair 8800 computer with a Lear Siegler ADM-3A terminal emulated by emuStudio.

However, I designed the program intending to port it to CP/M. The necessary work consisted in replacing the Altair-specific I/O rutines with equivalent BDOS calls for printing a single character (function 02h) and a string (09h) of ANSI escape codes, and checking the console status for key presses (0bh).

The program logic works as is, but I had to modify the CP/M version for defining the appropriate constants and ANSI escape strings, as well as adapting to the execution environment of the operating system and returning control to it.

Building and running

For me, the whole point of writing programs like Twirl and getting Z80 hardware is to use Suite8080, the suite of Intel 8080 Assembly cross-development tools I'm writing in Python. The CP/M port is a new Suite8080 Assembly demo I built from source on Linux with asm80, the Suite8080 assembler:

$ asm80 twirl.asm

I tested the resulting 74 bytes twirl.com executable under CP/M 2.2 with the CP/M software emulators ANSI CP/M emulator and z80pack.

Running the demo at the CP/M prompt is straightoward:

B>twirl

Pressing any key quits Twirl and returns control to the operating system.

To exeute Twirl on actual hardware I converted twirl.com to Intel HEX format with z80pack's bin2hex tool, then transferred the twirl.hex file to the Z80-MBC2 under CP/M 3.0.

It was really rewarding to see Twirl come to life on a real Z80 computer and get a sense of its performance.

The code

Here is the full code of the CP/M port of Twirl:

; Twirling bar animation.
;
; Runs on CP/M with an ANSI terminal. Press any key to quit the program.


TPA        equ      100h
BDOS       equ      0005h
WRITESTR   equ      09h                ; Write string
WRITECHR   equ      02h                ; Write character
CONSTAT    equ      0bh                ; Console status

FRAMES     equ      8                  ; Number of animation frames


           org      TPA
            
           mvi      c, WRITESTR
           lxi      d, cls             ; Clear screen
           call     BDOS

loop:      lxi      h, anim            ; Initialize frame pointer...
           mvi      b, FRAMES          ; ...and count

loop1:     push     h
           push     b
           mvi      c, WRITESTR
           lxi      d, home            ; Go to screen home
           call     BDOS
           pop      b
           pop      h

           push     h
           push     b
           mvi      c, WRITECHR
           mov      e, m               ; Print current frame
           call     BDOS
           pop      b
           pop      h

           push     h
           push     b
           mvi      c, CONSTAT         ; Get console status
           call     BDOS
           pop      b
           pop      h
           ora      a                  ; Key pressed?
           jnz      exit               ; Yes

           inx      h                  ; Point to next frame
           dcr      b                  ; One fewer frame
           jnz      loop1

           jmp      loop

exit:      ret


cls:       db       1bh, '[2J$'        ; ANSI clear screen: ESC [ 2 J
home:      db       1bh, '[H$'         ; ANSI go to screen home: ESC [ H
anim:      db       '|/-\|/-\'         ; 8 frames

           end

The program represents the animation as the string |/-\|/-\, a sequence of ASCII characters that show the bar at various steps of the rotation.

After clearing the screen, Twirl begins the main loop loop: by initializing the frame pointer and count. An inner loop loop1: prints each animation frame, i.e. the characters of the animation sequence.

The inner loop moves the cursor to the home position and prints the current frame. Then it updates the frame pointer and count, and checks whether a key was pressed. In case of a key press the inner loop exits the program, otherwise it jumps back to the beginning of the main loop.

#Assembly #retrocomputing #Suite8080 #z80mbc2 #CPM

Discuss... Email | Reply @amoroso@fosstodon.org

I'm about to resume working on my Suite8080 project after putting it aside for a few months. And I bought a Z80 homebrew computer.

To refresh my 8080 Assembly to work on the Suite8080 code, and my Z80 to play with the new computer, I'm rereading the book Z-80 and 8080 Assembly Language Programming by Kathe Spracklen (Hayden Books, 1979).

It's a short, clear resource that includes several worked out exercises. It presents and compares the code samples and concepts in both instruction sets. The book also covers how to implement basic data structures in Assembly, which makes it stand out.

#Assembly #intel8080 #z80 #books

Discuss... Email | Reply @amoroso@fosstodon.org