IBM 5100 Programming Examples

BACK TO IBM 5100

(work in progress!)

The examples below are actually for the IBM 5110. The IBM 5100 is largely compatible (same PALM opcodes), but does have different keyboard scan codes and different character codes for the display.

VCCC2022

In December 2022, a “Christmas Challenge” was hosted – open to all platforms. Here are some example entries, as a simple exercise to get oriented with using this system.

See YouTube: IBM 5110 Interactive Star PALM capability presentation (VCCC2022) – YouTube

Christmas Star BASIC

Long version:

This approach decomposes the STAR image into a set of 8 commonly re-used patterns. It is sort of a form of “RLE compression.” The full STAR image is 17×17 (289 bytes) whereas the compressed version is 5×17 (85 bytes, with the patterns consuming an additional 8×4=32 bytes, which is still altogether about a 60% compression).

190 REM PREPARED FOR IBM 5100 BASIC (1-BASED ARRAY INDEX)
200 REM DEFINE 4-CHARACTER PADDING
210 DIM A$4(8)
220 REM DEFINE 1-CHARACTER PADDING
230 DIM B$1(2)
234 REM DEFINE 23-CHARACTER PADDING (FOR CENTERING)
235 DIM C$23(1)
239 REM 8 UNIQUE 'STAR' STRING OPTIONS
240 READ A$(1),A$(2),A$(3),A$(4),A$(5),A$(6),A$(7),A$(8)
248 REM B$ USED FOR SINGLE '*' VS ' ' OPTION
249 REM C$ USED FOR LEFT PAD FOR CENTERING
250 READ B$(1),B$(2),C$(1)
260 READ N
270 FOR I = 1 TO N STEP 5
280   READ A1,A2,B1,A4,A5
290   PRINT C$(1)||A$(A1)||A$(A2)||B$(B1)||A$(A4)||A$(A5)
295   REM || IS CONCATE CODE FOR IBM 5110
300 NEXT I
310 END
400 REM ===========================
405 REM UNIQUE 'STAR' SEQUENCES (GROUPS OF 4)
410 DATA '    '
420 DATA '****'
430 DATA ' ***'
440 DATA '  **'
450 DATA '   *'
460 DATA '*   '
470 DATA '**  '
480 DATA '*** '
485 REM PLACEHOLDER FOR ON/OFF CENTER PORTION
490 DATA ' '
500 DATA '*'
504 REM BLANK CONTENT (DIM SIZE WILL GET PADDED)
505 DATA ''
510 REM HOW MANY SECTORS (HOW MUCH DATA)
520 DATA 85
530 REM DATA SECTOR ABBREVIATIONS
540 REM A-SERIES INDEXES
550 REM 1=0000
560 REM 2=1111
570 REM 3=0111
580 REM 4=0011
590 REM 5=0001
600 REM 6=1000
610 REM 7=1100
620 REM 8=1110
630 REM B-SERIES INDEXES
640 REM 1=0
650 REM 2=1
660 REM  A A B A A
670 DATA 1,6,1,5,1
680 DATA 1,7,1,4,1
690 DATA 1,8,1,3,1
700 DATA 1,2,1,2,1
710 DATA 2,2,2,2,2
720 DATA 3,2,2,2,8
730 DATA 4,2,2,2,7
740 DATA 5,2,2,2,6
760 DATA 1,2,2,2,1
770 DATA 5,2,2,2,6
780 DATA 4,2,2,2,7
790 DATA 3,2,2,2,8
800 DATA 2,2,2,2,2
810 DATA 1,2,1,2,1
820 DATA 1,8,1,3,1
830 DATA 1,7,1,4,1
840 DATA 1,6,1,5,1

Short version:

This version is identical to the above but is formatted to be more concise. For example, the “REM” comments removed, and more DATA values are placed on the same line number. On the IBM 5110, line numbers are limited to 64 characters (they can’t wrap past the edge of the screen). To make this slightly shorter, the use of C$ could be removed (as it is used only to center the star for a better presentation but was not a strict requirement).

21 DIM A$4(8),B$1(2),C$23(1)
24 READ A$(1),A$(2),A$(3),A$(4),A$(5),A$(6),A$(7),A$(8)
25 READ B$(1),B$(2),C$(1),N
27 FOR I = 1 TO N STEP 5
28   READ A1,A2,B1,A4,A5
29   PRINT C$(1)||A$(A1)||A$(A2)||B$(B1)||A$(A4)||A$(A5)
30 NEXT I
41 DATA '    ','****',' ***','  **','   *','*   '
47 DATA '**  ','*** ',' ','*','',85
67 DATA 1,6,1,5,1,1,7,1,4,1,1,8,1,3,1,1,2,1,2,1,2,2
71 DATA 2,2,2,3,2,2,2,8,4,2,2,2,7,5,2,2,2,6,1,2,2,2
76 DATA 1,5,2,2,2,6,4,2,2,2,7,3,2,2,2,8,2,2,2,2,2,1
80 DATA 2,1,2,1,1,8,1,3,1,1,7,1,4,1,1,6,1,5,1

An algorithm approach:

The following is an IBM 5110 BASIC compatible solution provided by “Peter De Wachter” (who submitted the APL solution for VCCC2022).

010 DATA 4,3,2,1,-4,-3,-2,-1,0,-1,-2,-3,-4,1,2,3,4
020 DIM V(17)
030 MAT READ V
040 FOR I=1 TO 17
050 FOR J=1 TO 17
060 IF V(I)<=-V(J) THEN 90
070 PRINT ' ';
080 GOTO 100
090 PRINT '*';
100 NEXT J
110 PRINT
120 NEXT I

It is a valid 17×17 solution. Typing it into the online Norbert 5110 emulator may requiring using the mouse to click the virtual keyboard (modern keyboards might not map ;, -, and (, ) as expected).

Here is my attempt at describing this approach: think of a scale of peaks (higher values) and valley’s (lower values), which we can call “weight.” Then define a filter that indicates how much of this scale is applicable (a kind of “water-level” on how high a peak need to be in order to get selected or revealed). Using a dynamic filter over time, our desired shape can be revealed (and we can use rows on a screen to represent that “over time” dimension).

As mentioned earlier, the problem is very symmetric. As depicted below, the top left quadrant is one quarter of the problem. But look within that quadrant, it is again symmetric within itself (the “sub-quadrants” are themselves inverses of each other). That may be a clue to help decide how to set the weights (where you see they are also a sequence of inverses).

With this in mind, one can experiment with different weights and filters to experiment on what new patterns can be revealed.

Christmas Star PALM Assembly

I had been studying for a way to detect keystrokes on the IBM 5110 using PALM, since it is an essential need towards making an interactive game. Then, I happened to come across the VCCC2022 notice just a few days before its end date. So, I adapted Corti’s approach he used for “building walls” in the Ball Bounce demo to draw the star and added what I had hoped would be a way to detect if SPACEBAR has been pressed. And, it worked! I had noticed that whenever a key is pressed, its corresponding scan code ends up in some L3 registers.

But, since the CRT is 64×16, that means the full 17×17 star can’t be drawn. So, this ends up as a casual “wild” submission with a 17×16 star. Still, it was a fun exercise with a few lessons learned: MOVE applies both HI and LO (16-bit), you can’t BRAch more than 255 addresses (so you have to buffer R0 (Program Counter) to jump back later, and confirmation that Alfred Arnold Macro Assembler works splendidly for producing PALM machine code.

This is by no means an “optimal” approach for this platform. As I learn more about PALM, I hope to revisit this challenge and present something that is more optimal.

; For Vintage Computing Christmas Challenge 2022
; written by voidstar 12/20/2022
; https://voidstar.blog/ 
;
; Assembled using Alfred Arnold Macro Assembler 1.42 Beta [Bld 231]
; (Alfred added PALM support earlier this year by my request)
; http://john.ccac.rwth-aachen.de:8000/as/
;
; asw -i ..\include program_star.asm
;
; remove header and convert .p to a raw binary:
; p2bin program_star.p
;
; demo using VEMU 5110 emulator:
; emu5110_release_x64 a -1 command_input_ASM_GO2000.txt program_star.bin
; NOTE: a (APL) or b (BASIC) will work at 0x2000 offset.  If using 0x0B00, sometimes BASIC
; won't work (it writes 0x0000 to 0xB2E0, depending on when and how long you let it run).
;
        cpu IBM5110

        include     "ebcdic.inc"
        intsyntax	  +$hex,-x'hex'  ; < support $-style hex instead of IBM 0x style
        codepage    cp037          ; < activate the EBCDIC mapping of chars
        
; BEGIN: CLEAR SCREEN
        LWI	        R14,  #$0200   ; set start of CRT screen address
        LWI         R9,   #$4040   ; let R9 be the space/blank screen character  (for IBM 5110 use $4040, for IBM 5100 use $0000)
cs_1:	
        MOVE        (R14)+, R9     ; move all of R9 to address of R14, then increment R14+2
        LBI         R1,   #$06     ; load Lo(R1) back to #$06 (can't guarantee R1 wasn't modified by interrupt)
        SBSH        R14,  R1       ; skip if Hi(R14) == R1 (i.e. have we reached #$0600, which is one past the memory-mapped screen addresses)
        BRA         cs_1           ; if did not skip, then keep clearing next two characters

; BEGIN: MAKE STAR
        ; prepare some registers to hold the screen character values that will be drawn
        LWI         R7, #$5C5C            ; draw "two" **'s at a time
        LWI         R8, #$5C40            ; left STAR then space/blank
        LWI         R9, #$405C            ; space/blank then RIGHT star

        MOVE        R11,  R0              ; R11 = R0  (store starting addressing)        
        
; do vertical lines first
; vertical 1
bw_v1:
        LWI         R5, #$0200+(1*64)+29  ; set R5 to address of row 1, col 29  (0-based index)
        LBI         R6, #14               ; 14 vertical rows to draw
bw_v1a:
        MOVE        (R5), R7              ; draw at whatever screen address that R5 is pointing at
        SUB         R6, #1                ; decrement "number of rows drawn" counter
        SNZ         R6                    ; skip if not-zero
        BRA         bw_v2                 ; go to the next block (if R6 is zero)
        ADD         R5, #64               ; increment screen address to the next row address (64 characters per row)
        BRA         bw_v1a                ; continue drawing vertically at the next row

; vertical 2
bw_v2:
        LWI         R5, #$0200+(3*64)+31  ; set R5 to address of row 3, col 31  (0-based index)
        LBI         R6, #10               ; 10 vertical rows to draw
bw_v2a:
        MOVE        (R5), R7
        SUB         R6, #1
        SNZ         R6                    ; skip if not-zero
        BRA         bw_v3                 ; go to the next block
        ADD         R5, #64               ; go to next row (address)
        BRA         bw_v2a

; vertical 3
bw_v3:
        LWI         R5, #$0200+(4*64)+33
        LBI         R6, #8
bw_v3a:
        MOVE        (R5), R7
        SUB         R6, #1
        SNZ         R6                    ; skip if not-zero
        BRA         bw_v4                 ; go to the next block
        ADD         R5, #64               ; go to next row (address)
        BRA         bw_v3a

; vertical 4
bw_v4:
        LWI         R5, #$0200+(2*64)+35
        LBI         R6, #12
bw_v4a:
        MOVE        (R5), R7
        SUB         R6, #1
        SNZ         R6                    ; skip if not-zero
        BRA         bw_v5                 ; go to the next block
        ADD         R5, #64               ; go to next row (address)
        BRA         bw_v4a

; vertical 5
bw_v5:
        LWI         R5, #$0200+(4*64)+37
        LBI         R6, #8
bw_v5a:
        MOVE        (R5), R7
        SUB         R6, #1
        SNZ         R6                    ; skip if not-zero
        BRA         bw_h1                 ; go to the next block
        ADD         R5, #64               ; go to next row (address)
        BRA         bw_v5a

; now draw the horizontals
; the vertical loops above has drawn the majority of the star.
; the following just adds in the few missing stars row-by-row as needed.
; (horizontals)
bw_h1:
; row 0: top left tip
        LWI         R5, #$0200+(0*64)+29
        MOVE        (R5),  R8
; row 0: top right tip
        LWI         R5, #$0200+(0*64)+37
        MOVE        (R5),  R8
; row 1: top right
        LWI         R5, #$0200+(1*64)+35
        MOVE        (R5)+, R9              ; "draw" and then increment R5 by two
        MOVE        (R5),  R8              ; "draw" the next pair 
; row 2
        LWI         R5, #$0200+(2*64)+31
        MOVE        (R5),  R8
        LWI         R5, #$0200+(2*64)+37
        MOVE        (R5),  R8
; row 3
        LWI         R5, #$0200+(3*64)+33
        MOVE        (R5),  R9
        LWI         R5, #$0200+(3*64)+37
        MOVE        (R5),  R8
; row 4: left side
        LWI         R5, #$0200+(4*64)+25
        MOVE        (R5)+, R7
        MOVE        (R5), R7
; row 4: right side   
        LWI         R5, #$0200+(4*64)+39
        MOVE        (R5)+, R7
        MOVE        (R5), R8
; row 5: left side
        LWI         R5, #$0200+(5*64)+25
        MOVE        (R5)+, R9
        MOVE        (R5), R7
; row 5: right side   
        LWI         R5, #$0200+(5*64)+39
        MOVE        (R5), R7
; row 6: left side
        LWI         R5, #$0200+(6*64)+27
        MOVE        (R5), R7
        ADD         R5, #64   ; row 7 left side
        MOVE        (R5), R9  ; row 8 left side
        ADD         R5, #64
        MOVE        (R5), R9
; row 6: right side   
        LWI         R5, #$0200+(6*64)+39
        MOVE        (R5), R8

; row 7 already handled inline above
; row 8 already handled inline above

; row 9: left side
        LWI         R5, #$0200+(9*64)+27
        MOVE        (R5), R7
; row 9: right side   
        LWI         R5, #$0200+(9*64)+39
        MOVE        (R5), R8

; row 10: left side
        LWI         R5, #$0200+(10*64)+25
        MOVE        (R5)+, R9
        MOVE        (R5), R7
; row 10: right side   
        LWI         R5, #$0200+(10*64)+39
        MOVE        (R5), R7

; row 11: left side
        LWI         R5, #$0200+(11*64)+25
        MOVE        (R5)+, R7
        MOVE        (R5), R7
; row 11: right side   
        LWI         R5, #$0200+(11*64)+39
        MOVE        (R5)+, R7
        MOVE        (R5), R8

; row 12
        LWI         R5, #$0200+(12*64)+33
        MOVE        (R5),  R9
        LWI         R5, #$0200+(12*64)+37
        MOVE        (R5),  R8

; row 13
        LWI         R5, #$0200+(13*64)+31
        MOVE        (R5),  R8
        LWI         R5, #$0200+(13*64)+37
        MOVE        (R5),  R8

; row 14: bottom right
        LWI         R5, #$0200+(14*64)+35
        MOVE        (R5)+, R9
        MOVE        (R5),  R8
        
; row 15: bottom left tip
        LWI         R5, #$0200+(15*64)+29
        MOVE        (R5),  R8
; row 15: bottom right tip
        LWI         R5, #$0200+(15*64)+37
        MOVE        (R5),  R8
        
; BEGIN: MAIN LOOP
        LWI         R13,   #$39      ; space bar scancode
        LWI         R5,    #$69      ; keyboard scancode address, can also use #$6B or #$B0
poll:
        MOVE        R12,   (R5)      ; let R12 = MEM[addr @ R5]
        SBS         R13,   R12       ; does R13 == R12 (key code for spacebar)        
        BRA         poll             ; no (not skipped), keep polling

; increment R7, R8, R9
;        MOVE        (R5),  R5        ; clear key stroke  (comment this out to loop continuously)
        
        ADD         R7,    #1        ; increment lo[R7]  (does carry, but doesn't matter since we do MLH below)
        MLH         R7,    R7        ; copy, hi[R7] = lo[R7]

        MLH         R8,    R7        ; copy, hi[R8] = lo[R7]  (update R8 to be whatever R7 became)   R8 == "xx40"

        ADD         R9,    #1        ; increment lo[R9]  (this does a carry which will "destroy" the space in hi[R9])
        MLH         R9,    R8
        
        MOVE        R0,    R11       ; restart the program
        
        HALT

Reserved.

TBD: How to obtain and run/use 5110VEMU for both BASIC and ASM inputs.

Addition Notes on Algorithm Approach

Printing the “Christmas Star” to the screen seems like a straight-forward and simple task. The most “brute-force” approach is to simply use 17 PRINT statements. This type of approach requires around 500 characters.

But what if you wanted to do things like rotate/spin the star? Shrink or enlarge (scale) the star? Draw different portions of the star in different colors? Rather than just a “2-D grid of characters,” one can imagine the star as a geometric object. From there, a variety of approaches can begin to formulate on how to render this shape using an algorithm approach. The specifics will depend on the available instruction set, which the VCCC2022 submissions showed a wide variety of solutions across platforms and programming languages available to those platforms.

Many clever solutions were in the 50-100 byte range, with the most optimal solutions being around 30 bytes. Because the problem is symmetric vertically and horizontally, that should suggest you only need to solve “1/4th” of the problem — once you have an approach to one corner, then that same approach should apply to the others (by applying an inverse).

%d bloggers like this: