I have been working on an assignment where I must convert a given number into different bases in the LC3. To do this, I need to use division, and while I have had an assignment where I did this same thing, the division algorithm for this one is different.
I am expected to use extended division in this assignment. The basic algorithm is:
Given a 32 bit number, store the first 16 bits into a memory address (called the high numerator), the last 16 bits (the low numerator) into another one, and the divisor, another 16 bit number, into another one. So, in the subroutine, the numbers would be fetched from memory into different registers and the algorithm would work as follows:
For simplicity, our instructor is letting us use 0 as the high numerator. So, in order to fulfill the algorithm, I came up with the following procedure:
Below is the full code I have so far:
.ORIG x3000
LEA R0, MPROMPT
TRAP x22
LOOP
JSR GETDEC ;Input an unsigned (decimal) integer
ADD R0, R0, #0 ;Exit if the input integer is 0
BRZ EXIT
JSR NEWLN ;Move cursor to the start of a new line
AND R1, R1, #0 ;R1 = 10 (output base 10 DECIMAL)
ADD R1, R1, #10
JSR PRINT ;Print the integer in decimal
JSR NEWLN ;Move cursor to the start of a new line
AND R1, R1, #0 ;R1 = 2 (output base 2 BINARY)
ADD R1, R1, #2
JSR PRINT ;Print the integer in binary
JSR NEWLN ;Move cursor to the start of a new line
AND R1, R1, #0 ;R1 = 8 (output base 8 OCTAL)
ADD R1, R1, #8
JSR PRINT ;Print the integer in octal
JSR NEWLN ;Move cursor to the start of a new line
AND R1, R1, #0 ;R1 = 16 (output base 16 HEXADECIMAL)
ADD R1, R1, #15
ADD R1, R1, #1
JSR PRINT ;Print the integer in hexadecimal
JSR NEWLN ;Move cursor to the start of a new line
BRNZP LOOP
EXIT ;Loop exit point
TRAP x25 ;HALT program execution
MPROMPT .STRINGZ "Enter a sequence of unsigned integer values\nEnter 0 To Quit\n"
;Subroutine NEWLN*************************************************************
;Advances the console cursor to the start of a new line
NEWLN
ST R7, NEW7 ;Save working registers
ST R0, NEW0
LD R0, NL ;Output a newline character
TRAP x21
LD R0, NEW0 ;Restore working registers
LD R7, NEW7
RET ;Return
;Data
NL .FILL x000A ;Newline character
NEW0 .BLKW 1 ;Save area - R0
NEW7 .BLKW 1 ;Save area - R7
;Subroutine GETDEC************************************************************
;Inputs an unsigned integer typed at the console in decimal format
;The input value is returned in R0
GETDEC
;Save Values of ALL Registers that don't need to change
ST R1, GET1
ST R2, GET2
ST R3, GET3
ST R4, GET4
ST R5, GET5
ST R6, GET6
ST R7, GET7 ;Save R7 to be able to return to main
AND R3, R3, #0 ;Prepare a value
LEA R0, GPROMPT
TRAP x22
GETDKEY
TRAP x20 ;To input a keystroke
ADD R2, R0, #-10 ;Subtract new keystroke by 10 (new line, or enter)
BRZ GFINIS ;If R2 is 0 we just pressed enter, so we break out
TRAP x21 ;Echo keystroke
ADD R2, R0, #0 ;Get the number back
AND R2, R2, #15 ;We mask to only get the first four bits
ADD R3, R3, R3 ;1st addition: x + x = 2x
ADD R4, R3, #0 ;Store the 2x
ADD R3, R3, R3 ;2nd addition: 2x + 2x = 4x
ADD R3, R3, R3 ;3rd addition: 4x + 4x = 8x
ADD R3, R3, R4 ;4th addition: 8x + 2x = 10x
ADD R3, R3, R2 ;10x + new number
BRNZP GETDKEY ;Go back to get next number
GFINIS
ADD R0, R3, #0 ;Put number into R0
;Restore the values of registers that don't need change
LD R1, GET1
LD R2, GET2
LD R3, GET3
LD R4, GET4
LD R5, GET5
LD R6, GET6
LD R7, GET7 ;Get back the value of R7
RET
;Data
GPROMPT .STRINGZ "Enter an unsigned integer> " ;Input Prompt
GET1 .BLKW 1
GET2 .BLKW 1
GET3 .BLKW 1
GET4 .BLKW 1
GET5 .BLKW 1
GET6 .BLKW 1
GET7 .BLKW 1
;Subroutine PRINT*************************************************************
;Displays an unsigned integer in any base up to 16, e.g. binary, octal, decimal
;Parameters - R0: the integer - R1: the base
PRINT
ST R0, PR0
ST R1, PR1
ST R2, PR2
ST R3, PR3
ST R4, PR4
ST R5, PR5
ST R6, PR6
ST R7, PR7
LEA R7, ARGLIST ;Point to ARGLIST to start populating the args
STR R0, R7, #0 ;Put R0 to be the low numerator
AND R0, R0, #0 ;R0 is not needed after this, so it is cleared
ADD R7, R7, #1 ;Go down on pointer once to store next arg
STR R0, R7, #0 ;Put R0, now x0000, to be the high numerator
ADD R7, R7, #1
STR R1, R7, #0 ;Store the base, R1, to be the DVR
ADD R7, R7, #1 ;Have R7 point to Quotient
LEA R6, BUFFER
AND R5, R5, #0 ;To make sure R5 is cleared
LEA R4, DIGITS ;Prepare pointer to the digits
SDIV
JSR DIVIDE
LDR R2, R7, #0 ;Since the addresses should now be ready,
;R7 would point to quotient, passed to R2
LDR R3, R7, #1 ;Pass remainder to R3
ADD R3, R3, R4
LDR R5, R3, #0
ADD R6, R6, #-1 ;Decrement to get to the next one
STR R5, R6, #0 ;Store number into buffer
LEA R7, ARGLIST ;Go back to ARGLIST to store new numerator
STR R2, R7, #0
ADD R7, R7, #3 ;Point to quotient
ADD R0, R2, #0 ;Pass quotient into R0
BRP SDIV ;If number is at least 1, then divide again
ADD R0, R6, #0 ;If we got here, then we should have the converted number
TRAP x22 ;Last but not least we display
LD R0, PR0
LD R1, PR1
LD R2, PR2
LD R3, PR3
LD R4, PR4
LD R5, PR5
LD R6, PR6
LD R7, PR7
RET
;Data
DIGITS .STRINGZ "0123456789ABCDEF" ;Digits
.BLKW 18 ;Output Buffer
BUFFER .FILL x0000 ;Null
PR0 .BLKW 1
PR1 .BLKW 1
PR2 .BLKW 1
PR3 .BLKW 1
PR4 .BLKW 1
PR5 .BLKW 1
PR6 .BLKW 1
PR7 .BLKW 1
;Subroutine DIVIDE************************************************************
;Extended division is done here
DIVIDE
ST R1, DIV1
ST R2, DIV2
ST R3, DIV3
ST R4, DIV4
ST R5, DIV5
ST R6, DIV6
ST R7, DIV7
LEA R6, ARGLIST ;Point straight to where arguments start
LDR R5, R6, #0 ;Load NUMLOW into R5
ADD R6, R6, #1 ;Go down one step in pointer
LDR R4, R6, #0 ;Load NUMHIGH into R4
ADD R6, R6, #1 ;Go down once more
LDR R3, R6, #0 ;Load DVR into R3
ADD R6, R6, #1 ;Go down yet once more to be ready when we
;store results
LD R1, M8BIT ;Make R1 be equal to x0080 to get last bit
;from NUMLOW
AND R2, R2, #0 ;Set R2 as counter to decrement with each
ADD R2, R2, #10 ;iteration
ADD R2, R2, #6
ITER ADD R4, R4, R4 ;Shift NUMHIGH to the left once
AND R7, R5, R1 ;Masking. If R5[7] == 1, then R7 == x0080
BRNZ NOADD ;(or positive)
ADD R4, R4, #1 ;If this happens then R5[7] was set, so we're
;adding the bit that would be lost if R5
;shifts left
NOADD ADD R5, R5, R5 ;Shift NUMLOW to the left once
NOT R7, R3 ;Subtract NUMHIGH and DVR. If 0 or positive
ADD R7, R7, #1 ;then subtract and increment NUMLOW
ADD R7, R4, R7
BRN DECCNT ;If negative then DVR is greater than NUMHIGH,
;go straight to decrement the loop count
ADD R4, R7, #0
ADD R5, R5, #1
DECCNT ADD R2, R2, #-1
BRP ITER ;If count is positive, go back to ITER
STR R5, R6, #0 ;Store NUMLOW for the Quotient Address
ADD R6, R6, #1 ;Increment to store in the next one
STR R4, R6, #0 ;Store NUMHIGH for the Remainder Address
LD R1, DIV1
LD R2, DIV2
LD R3, DIV3
LD R4, DIV4
LD R5, DIV5
LD R6, DIV6
LD R7, DIV7
RET ;Return
;Data
ARGLIST .BLKW 5 ;Here we will put the LOWNUM, HIGHNUM,
;DVR, QUO, and REM
M8BIT .FILL x0080
DIV1 .BLKW 1
DIV2 .BLKW 1
DIV3 .BLKW 1
DIV4 .BLKW 1
DIV5 .BLKW 1
DIV6 .BLKW 1
DIV7 .BLKW 1
.END
I think the algorithm should be working, but as I run it on the LC3, I get the following error:
A trap was executed with an illegal vector number
And the processor is halted. For some reason, I noticed that when I checked my code in the LC3 it gets erased right after this line:
ADD R0, R2, #0
Which would be right before branching to call the DIVIDE subroutine again.
Why would this be happening? As far as I am able to tell, I have been saving and restoring the registers properly (last time I had a similar issue to this, and someone pointed out I was saving to the same .BLKW which caused an error), but the error persists. Please let me know if anyone has any idea of what could be malfunctioning, or if there's any more information you need. Thank you for any help in advance
Solved. There were two main errors:
1) There wasn't a need to use a mask. If anything, the mask was wrong in itself, as the registers hold 16 bit values. If I had wanted to use a mask, a more appropriate value would have been x8000
, which still cannot be used as this is actually the opcode for RTI
. All I had to do was make sure to get R5, the register with the low numerator, into the branch calculation. If it was negative
, then the high bit obviously is a 1!
2) This is what was breaking the program. Note that on the PRINT
function, I was using a LEA
with R7
. This means I was holding this register responsible for pointing to the arguments. But some lines after this I call a JSR
:
JSR DIVIDE
I failed to realize that internally when such a jump happens the PC value needed to be able to return to normal program execution is stored inside a register. Where? R7. So even if on the beginning and end of DIVIDE
I had an ST
and an LD
to restore the value of R7, upon returning to PRINT
it would have been the address of the next instruction, not the pointer to ARGLIST
. All it took for the program to run properly was to add a simple LEA
, add 3 to point to the quotient and remainder, and modify the manipulation of the pointer a bit farther down to store the updated low numerator. In all, the bit of code right after the DIVIDE
call would look like this:
SDIV
JSR DIVIDE
LEA R7, ARGLIST
ADD R7, R7, #3
LDR R2, R7, #0 ;Since the addresses should now be ready,
;R7 would point to quotient, passed to R2
LDR R3, R7, #1 ;Pass remainder to R3
ADD R3, R3, R4
LDR R5, R3, #0
ADD R6, R6, #-1 ;Decrement to get to the next one
STR R5, R6, #0 ;Store number into buffer
LEA R7, ARGLIST ;Go back to ARGLIST to store new numerator
STR R2, R7, #0
ADD R0, R2, #0 ;Pass quotient into R0
BRP SDIV ;If number is at least 1, then divide again
ADD R0, R6, #0 ;If we got here, then we should have the converted number
TRAP x22 ;Last but not least we display