How to resolve the algorithm Subleq step by step in the 8080 Assembly programming language
How to resolve the algorithm Subleq step by step in the 8080 Assembly programming language
Table of Contents
Problem Statement
Subleq is an example of a One-Instruction Set Computer (OISC).
It is named after its only instruction, which is SUbtract and Branch if Less than or EQual to zero.
Your task is to create an interpreter which emulates a SUBLEQ machine.
The machine's memory consists of an array of signed integers. These integers may be interpreted in three ways:
Any reasonable word size that accommodates all three of the above uses is fine.
The program should load the initial contents of the emulated machine's memory, set the instruction pointer to the first address (which is defined to be address 0), and begin emulating the machine, which works as follows:
Your solution may initialize the emulated machine's memory in any convenient manner, but if you accept it as input, it should be a separate input stream from the one fed to the emulated machine once it is running. And if fed as text input, it should be in the form of raw subleq "machine code" - whitespace-separated decimal numbers, with no symbolic names or other assembly-level extensions, to be loaded into memory starting at address 0 (zero).
For purposes of this task, show the output of your solution when fed the below "Hello, world!" program.
As written, this example assumes ASCII or a superset of it, such as any of the Latin-N character sets or Unicode; you may translate the numbers representing characters (starting with 72=ASCII 'H') into another character set if your implementation runs in a non-ASCII-compatible environment. If 0 is not an appropriate terminator in your character set, the program logic will need some adjustment as well.
The above "machine code" corresponds to something like this in a hypothetical assembler language for a signed 8-bit version of the machine:
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Subleq step by step in the 8080 Assembly programming language
Source code in the 8080 programming language
;;; ---------------------------------------------------------------
;;; SUBLEQ for CP/M. The word size is 16 bits, and the program
;;; is given 16 Kwords (32 KB) of memory. (If the system doesn't
;;; have enough, the program will not run.)
;;; I/O is via the console; since it cannot normally be redirected,
;;; CR/LF translation is on by default. It can be turned off with
;;; the 'R' switch.
;;; ---------------------------------------------------------------
;;; CP/M system calls
getch: equ 1h
putch: equ 2h
puts: equ 9h
fopen: equ 0Fh
fread: equ 14h
;;; RAM locations
fcb1: equ 5ch ; FCB 1 (automatically preloaded with 1st file name)
fcb2: equ 6ch ; FCB 2 (we're abusing this one for the switch)
dma: equ 80h ; default DMA is located at 80h
bdos: equ 5h ; CP/M entry point
memtop: equ 6h ; First reserved memory address (below this is ours)
;;; Constants
CR: equ 13 ; CR and LF
LF: equ 10
EOF: equ 26 ; EOF marker (as we don't have exact filesizes)
MSTART: equ 2048 ; Reserve 2K of memory for this program + the stack
MSIZE: equ 32768 ; Reserve 32K of memory (16Kwords) for the SUBLEQ code
PB: equ 0C6h ; PUSH B opcode.
org 100h
;;; -- Memory initialization --------------------------------------
;;; The fastest way to zero out a whole bunch of memory on the 8080
;;; is to push zeroes onto the stack. Since we need to do 32K,
;;; and it's slow already to begin with, let's do it that way.
lxi d,MSTART+MSIZE ; Top address we need
lhld memtop ; See if we even have enough memory
call cmp16 ; Compare the two
xchg ; Put top address in HL
lxi d,emem ; Memory error message
jnc die ; If there isn't enough memory, stop.
sphl ; Set the stack pointer to the top of memory
lxi b,0 ; 2 zero bytes to push
xra a ; Zero out A.
;;; Each PUSH pushes 2 zeroes. 256 * 64 * 2 = 32768 zeroes.
;;; In the interests of "speedy" (ha!) execution, let's unroll this
;;; loop a bit. In the interest of the reader, let's not write out
;;; 64 lines of "PUSH B". 'PB' is set to the opcode for PUSH B, and
;;; 4*16=64. This costs some memory, but since we're basically
;;; assuming a baller >48K system anyway to run any non-trivial
;;; SUBLEQ code (ha!), we can spare the 64 bytes.
memini: db PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB
db PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB
db PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB
db PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB, PB,PB,PB,PB
inr a ; This will loop around 256 times
jnz memini
push b
;;; This conveniently leaves SP pointing just below SUBLEQ memory.
;;; -- Check the raw switch ---------------------------------------
;;; CP/M conveniently parses the command line for us, under the
;;; assumption that there are two whitespace-separated filenames,
;;; which are also automatically made uppercase.
;;; We only have to see if the second filename starts with 'R'.
lda fcb2+1 ; Filename starts at offset 1 in the FCB
cpi 'R' ; Is it 'R'?
jnz readfl ; If not, go read the file (in FCB1).
lxi h,chiraw ; If so, rewrite the jumps to use the raw fns
shld chin+1
lxi h,choraw
shld chout+1
;;; -- Parse the input file ---------------------------------------
;;; The input file should consist of signed integers written in
;;; decimal, separated by whitespace. (For simplicity, we'll call
;;; all control characters whitespace). CP/M can only read files
;;; 128 bytes at a time, so we'll process it 128 bytes at a time
;;; as well.
readfl: lda fcb1+1 ; See if a file was given
cpi ' ' ; If not, the filename will be empty (spaces)
lxi d,eusage ; Print the usage string if that is the case
jz die
mvi c,fopen ; Otherwise, try to open the file.
lxi d,fcb1
call bdos
inr a ; FF is returned on error
lxi d,efile ; Print 'file error' and stop.
jz die
;;; Start parsing 16-bit numbers
lxi h,MSTART ; Start of SUBLEQ memory
push h ; Keep that on the stack
skipws: call fgetc ; Get character from file
jc rddone ; If EOF, we're done
cpi ' '+1 ; Is it whitespace?
jc skipws ; Then get next character
rdnum: lxi h,0 ; H = accumulator to store the number
mov b,h ; Set B if number should be negative.
cpi '-' ; Did we read a minus sign?
jnz rddgt ; If not, then this should be a digit.
inr b ; But if so, set B,
call fgetc ; and get the next character.
jc rddone
rddgt: sui '0' ; Make ASCII digit
cpi 10 ; Which should now be less than 10
jnc fmterr ; Otherwise, print an error and stop
mov d,h ; Set HL=HL*10
mov e,l ; DE = HL
dad h ; HL *= 2
dad h ; HL *= 4
dad d ; HL *= 5
dad h ; HL *= 10
mvi d,0 ; Add in the digit
mov e,a
dad d
call fgetc ; Get next character
jc rdeof ; EOF while reading number
cpi ' '+1 ; Is it whitespace?
jnc rddgt ; If not, then it should be the next digit
xchg ; If so, write the number to SUBLEQ memory
pop h ; Number in DE and pointer in HL
call wrnum ; Write the number
push h ; Put the pointer back
jmp skipws ; Then skip to next number and parse it
rdeof: xchg ; EOF, but we still have a number to write
pop h ; Number in DE and pointer in HL
call wrnum ; Write the number
push h
rddone: pop h ; We're done, discard pointer
;;; -- Run the SUBLEQ code ----------------------------------------
lxi h,MSTART ; Initialize IP
;;; At the start of step, HL = IP (in system memory)
step: mov e,m ; Load A into DE
inx h
mov d,m
inx h
mov c,m ; Load B into BC
inx h
mov b,m
inx h
mov a,e ; Check if A=-1
ana d
inr a
jz sbin ; If so, read input
mov a,b ; Otherwise, check if B=-1
ana c
inr a
jz sbout ; If so, write output
;;; Perform the SUBLEQ instruction
push h ; Store the IP (-2) on the stack
mov a,d ; Obtain [A] (set DE=[DE])
ani 3Fh ; Make sure address is in 16K words
mov d,a
lxi h,MSTART ; Add to start address twice
dad d ; (SUBLEQ addresses words, we're addressing
dad d ; bytes)
mov e,m ; Load low byte
inx h
mov d,m ; Load high byte
mov a,b ; Obtain [B] (set BC=[BC])
ani 3Fh ; This adress should also be in the 16K words
mov b,a
lxi h,MSTART ; Add to start address twice, again
dad b
dad b
mov c,m ; Load low byte
inx h
mov b,m ; Load high byte
mov a,c ; BC (B) -= DE (A)
sub e ; Subtract low bytes
mov c,a
mov a,b ; Subtract high bytes
sbb d
mov b,a
mov m,b ; HL is still pointing to the high byte of [B]
dcx h
mov m,c ; Store the low byte back too
pop h ; Restore IP
ral ; Check sign bit of [B] (which is still in A)
jc sujmp ; If set, it's negative, and we need to jump
rar
ora c ; If we're still here, it wasn't set. OR with
jz sujmp ; low bit, if zero then we also need to jump
inx h ; We don't need to jump, so we should ignore C;
inx h ; increment the IP to advance past it.
jmp step ; Next step
sujmp: mov c,m ; We do need to jump, load BC=C
inx h
mov a,m ; High byte into A
ral ; See if it is negative
jc quit ; If so, stop
rar
ani 3Fh ; Don't jump outside the address space
mov b,a ; High byte into B
lxi h,MSTART ; Calculate new IP
dad b
dad b
jmp step ; Do next step
;;; Input: A=-1
sbin: inx h ; Advance IP past C
inx h
xchg ; IP in DE
mov a,b ; Calculate address for BC (B)
ani 3Fh
mov b,a
lxi h,MSTART
dad b
dad b
call chin ; Read character
mov m,a ; Store in low byte
inx h
mvi m,0 ; Store zero in high byte
xchg ; IP back in HL
jmp step ; Next step
;;; Output: B=-1
sbout: inx h ; Advance IP past C
inx h
xchg ; IP in DE and A in HL
mov a,h ; Calculate address for A
ani 3Fh
mov h,a
dad h
lxi b,MSTART
dad b
mov a,m ; Retrieve low byte (character)
call chout ; Write character
xchg ; IP back in HL
jmp step ; Next step
quit: rst 0
;;; -- Write number to SUBLEQ memory ------------------------------
;;; Assuming: DE holds the number, B=1 if number should be negated,
;;; HL holds the pointer to SUBLEQ memory.
wrnum: dcr b ; Should the number be negated?
jnz wrpos ; If not, just write it
dcx d ; Otherwise, negate it: decrement,
mov a,e ; Then complement low byte,
cma
mov e,a
mov a,d ; Then complement high byte
cma
mov d,a ; And then write it
wrpos: mov m,e ; Write low byte
inx h ; Advance pointer
mov m,d ; Write high byte
inx h ; Advance pointer
ret
;;; -- Read file byte by byte -------------------------------------
;;; The next byte from the file in FCB1 is returned in A, and all
;;; other registers are preserved. When 128 bytes have been read,
;;; the next record is loaded automatically. Carry set on EOF.
fgetc: push h ; Keep HL registers
lda fgptr ; Where are we in the record?
ana a
jz nxtrec ; If at 0 (rollover), load new record.
frecc: mvi h,0 ; HL = A
mov l,a
inr a ; Next A
sta fgptr ; Write A back
mov a,m ; Retrieve byte
pop h ; Restore HL registers
cpi EOF ; Is it EOF?
rnz ; If not, we're done (ANA clears carry)
stc ; But otherwise, set carry
ret
nxtrec: push d ; Keep the other registers too
push b
mvi c,fread ; Read record from file
lxi d,fcb1
call bdos
dcr a ; A=1 on EOF
jz fgeof
inr a ; A<>0 = error
lxi d,efile
jnz die
mvi a,80h ; If we're still here, record read correctly
sta fgptr ; Set pointer back to beginning of DMA.
pop b ; Restore B and D
pop d
jmp frecc ; Get first character from the record.
fgeof: stc ; On EOF (no more records), set carry
jmp resbdh ; And restore the registers
fgptr: db 0 ; Pointer (80h-FFh) into DMA area. Reload on 0.
;;; -- Compare DE to HL -------------------------------------------
cmp16: mov a,d ; Compare high bytes
cmp h
rnz ; If they are not equal, we know the ordering
mov a,e ; If they are equal, compare lower bytes
cmp l
ret
;;; -- Register-preserving I/O routines ---------------------------
chin: jmp chitr ; These are rewritten to jump to the raw I/O
chout: jmp chotr ; instructions to turn translation off.
;;; -- Read character into A with translation ---------------------
chitr: call chiraw ; Get raw character
cpi CR ; Is it CR?
rnz ; If not, return character unchanged
mvi a,LF ; Otherwise, return LF (terminal sends only CR)
ret
;;; -- Read character into A. -------------------------------------
chiraw: push h ; Save all registers except A
push d
push b
mvi c,getch ; Get character from terminal
call bdos ; Character ends up in A
jmp resbdh ; Restore registers afterwards
;;; -- Write character in A to terminal with translation ----------
chotr: cpi LF ; Is it LF?
jnz choraw ; If not, just print it
mvi a,CR ; Otherwise, print a CR first,
call choraw
mvi a,LF ; And then a LF. (fall through)
;;; -- Write character in A to terminal ---------------------------
choraw: push h ; Store all registers
push d
push b
push psw
mvi c,putch ; Write character to terminal
mov e,a
call bdos
;;; -- Restore registers ------------------------------------------
restor: pop psw ; Restore all registers
resbdh: pop b ; Restore B D H
pop d
pop h
ret
;;; -- Make parse error message and stop --------------------------
;;; A should hold the offending character _after_ '0' has already
;;; been subtracted.
fmterr: adi '0' ; Undo subtraction of ASCII 0
lxi h,eiloc ; Write the characters in the error message
mov m,a
inx h
mvi b,4 ; Max. 4 more characters
fmtelp: call fgetc ; Get next character
jc fmtdne ; If EOF, stop
mov m,a ; If not, store the character
inx h ; Advance pointer
dcr b ; Should we do more characters?
jnz fmtelp ; If so, go get another
fmtdne: lxi d,einv ; Print 'invalid integer' error message.
;;; -- Print an error message and stop ----------------------------
die: mvi c,puts
call bdos
rst 0
;;; -- Error messages ---------------------------------------------
eusage: db 'SUBLEQ [R]: Run the SUBLEQ program in .$'
efile: db 'File error$'
emem: db 'Memory error$'
einv: db 'Invalid integer: '
eiloc: db ' $'
You may also check:How to resolve the algorithm Hello world/Text step by step in the AutoIt programming language
You may also check:How to resolve the algorithm FizzBuzz step by step in the MMIX programming language
You may also check:How to resolve the algorithm Determinant and permanent step by step in the Raku programming language
You may also check:How to resolve the algorithm Tic-tac-toe step by step in the M2000 Interpreter programming language
You may also check:How to resolve the algorithm Power set step by step in the Lua programming language