# Blitz-64 Initial Bootstrap Program
#
# Harry Porter III - 21 January 2020
#
# This program is intended to be in the Blitz-64 ROM. On RESET, it will be executed.
# It uses only the serial communication port, which will presumably be via a USB cable
# to a program such as "CoolTerm" running on a nearby laptop. This bootstrap program
# is meant to read in a program and then jump to it.
#
# This program initializes the stack to the very top of the RAM. RAM starts at address 0.
#
#  ********** The size of RAM is hardcoded here  **********
#
     RAM_BEYOND:   .equ  256*1024    # RAM SIZE = 256 KiBytes = 256 * 1024
     STACK_SPACE:  .equ  8*20        # Deduct some space for the stack for this code
#
# RAM may be larger, but this program will not function with data that exceeds this size.
#
# This program prints a welcome message on the serial output. It then waits for and reads
# the incoming program data, which is "typed in" on the serial input. The input data is
# entirely in hex. Presumably, this will be cut-and-pasted; no human would want to type this.
#
# Enter data in this format
#  8 bytes: N=number of data bytes;
#  8 bytes: A=address to place data bytes, normally 0000000000000008;
#  N bytes: Data bytes;
#  1 byte:  Checksum.
# Enter all bytes as 2 hex chars. Case not significant; space, \n, \r, and \t ignored.
# The checksum is the XOR of all N data bytes. A mismatch results in an error message.
# Data is placed at A, followed by JUMP to A. If problems, perform RESTART and try again.
#
# Example input:
#  0000000000000004
#  0000000000000008
#  3100000F
#  3E
#
# On entry:
#    sp = Address just beyond top of memory
#    r1 = Boot_Code = "coldboot"  (0x636F_6C64_626F_6F74)
#    r2 = Boot_Private_Memory_Size
#    r3 = Boot_Shared_Memory_Start
#    r4 = Boot_Shared_Memory_Size
#    r5 = Boot_First_Unused_Addr

        .begin   startaddr=0x4_0000_0000,kernel,gp=undefined

DIGITAL_READ:    .equ   0            # EXAMPLE USAGE: controlu   XXX,r0,DIGITAL_READ
DIGITAL_WRITE:   .equ   1            #                controlu   r0,XXX,DIGITAL_WRITE
HALT:            .equ   2            #                controlu   r0,r0,HALT
SERIAL_STAT:     .equ   3            #                controlu   XXX,r0,SERIAL_STAT
SERIAL_RECV:     .equ   4            #                controlu   XXX,r0,SERIAL_RECV
SERIAL_SEND:     .equ   5            #                controlu   r0,XXX,SERIAL_SEND

############################################################################
############################################################################
###
###  Boot Loader Main
###
###  This program communicates via the serial link. It begins by displaying
###  a message. Then it reads data in via the serial link. Finally, it
###  jumps to the data it just loaded into memory.
###
###  A stack is placed at the top of RAM memory.
###
############################################################################
############################################################################

_entry:
	.export	    _entry

        movi        sp,RAM_BEYOND                # Initialize sp
        movi        r1,String1                   # Print entry message
        call        PrintString                  # .
        movi        r1,TrapHandler               # Initialize csr_trapvec
        csrwrite    csr_trapvec,r1               # .  
# The checksum does not cover the starting addr or the byte count...
        call        GetInt                       # r7 = int (Get number of bytes)
        mov         r7,r3                        # .  (GetInt trashes r1-r4)
        call        GetInt                       # r3 = int (Get starting addr)
        add         r1,r3,r7                     # IF we will go past the available RAM
        blei        r1,RAM_BEYOND-STACK_SPACE,RamOK
        movi        r1,PgmTooLargeError          #   Print error message
        call        PrintString                  #   .
        jump        _entry                       #   Restart program 
RamOK:                                           # ENDIF
        mov         r4,r7                        # r4 = number of bytes
        mov         r5,r3                        # r5 = starting address (to be used as pointer)
        mov         r7,r3                        # r7 = starting address (to be used later)
        movi        r6,0x00                      # r6 = running checksum = 0 (updated in GetInt/GetByte)
mainLoop:                                        # LOOP (r4 times)
        call        GetByte                      #   r1 = GetByte
        store.b     0(r5),r1                     #   store into memory
        addi        r5,r5,1                      #   increment r5 pointer
        addi        r4,r4,-1                     # ENDLOOP
        bgtz        r4,mainLoop                  # .
        call        GetByte                      # Read the CheckSum byte into r1
        controlu    r0,r6,DIGITAL_WRITE          # Send current checksum difference to digital out
        beqz        r6,CheckSumOK                # IF checksum difference is not zero...
        movi        r1,CheckSumError             #   Print error message
        call        PrintString                  #   .
        jump        _entry                       #   Restart program 
CheckSumOK:                                      # ENDIF
        movi        r1,StartingMess              # Print "Starting..."
        call        PrintString                  # .
        movi        r1, 0x636F_6C64_626F_6F74    # r1 = Boot_Code = "coldboot"
        movi        r2, RAM_BEYOND               # r2 = Boot_Private_Memory_Size
        movi        r3, 0                        # r3 = Boot_Shared_Memory_Start
        movi        r4, 0                        # r4 = Boot_Shared_Memory_Size
                                                 # r5 = Boot_First_Unused_Addr (r5 valid from above)
        jr          r7                           # Jump to starting address

String1:
        .string     "\n***** BOOT PROGRAM (MBBooter.s) *****\n"
        .string     "   Enter data in this format:\n"
        .string     "     8 bytes: N=number of data bytes\n"
        .string     "     8 bytes: A=address at which to place the data bytes (normally 0000000000000008)\n"
        .string     "     N bytes: Data bytes\n"
        .string     "     1 byte:  Checksum\n"
        .string     "   Enter all bytes as 2 hex chars; case is not significant; space, \\n, \\r, and \\t ignored.\n"
        .string     "   The checksum is the XOR of all N data bytes. A mismatch results in an error message.\n"
        .string     "   Data is placed at A, followed by JUMP to address A. If problems, perform RESTART and try again.\n"
        .string     "       Example input:\n"
        .string     "         0000000000000004\n"
        .string     "         0000000000000008\n"
        .string     "         3100000F\n"
        .string     "         3E\n"
        .string     "PLEASE ENTER...\n\0"

StartingMess:
        .string     "\n\rExecution beginning...\n\r\0"
CheckSumError:
        .string     "\n\r*****  ERROR: Checksum failure!  *****\n\r\0"
PgmTooLargeError:
        .string     "\n\r*****  ERROR: This program does not fit into available RAM!  *****\n\r\0"
        .align      4


############################################################################
#
# TrapHandler
#
# Print a message and set the digital out to the cause code
#
############################################################################
TrapHandler:
        csrread    r1,csr_cause
        controlu   r0,r1,DIGITAL_WRITE
        movi       r1,ExceptionMess1
        call       PrintString

        movi       r1,ExceptionMess2
        call       PrintString
        csrread    r1,csr_prevpc
        call       PrintInt

        movi       r1,ExceptionMess3
        call       PrintString
        csrread    r1,csr_cause
        call       PrintInt

        movi       r1,ExceptionMess4
        call       PrintString
        csrread    r1,csr_bad
        call       PrintInt

        movi       r1,ExceptionMess5
        call       PrintString
        csrread    r1,csr_addr
        call       PrintInt

        movi       r1,ExceptionMess6
        call       PrintString
        controlu   r0,r0,HALT

ExceptionMess1:
        .string     "\n\r*****  AN EXCEPTION HAS OCCURRED!  *****\n\r\0"
ExceptionMess2:
        .string     "csr_prevpc:  \0"
ExceptionMess3:
        .string     "\n\rcsr_cause:   \0"
ExceptionMess4:
        .string     "\n\rcsr_bad:     \0"
ExceptionMess5:
        .string     "\n\rcsr_addr:    \0"
ExceptionMess6:
        .string     "\n\r\0"
        .align      4



############################################################################
#
# PrintString
#
# Passed:
#    r1 = pointer to an array of bytes, terminated with \0.
# Uses:
#    r1, r2, r3
# Leaf function
#
############################################################################

PrintString:                            # LOOP
        load.b    r2,0(r1)              #   r2 = the next byte from the string
        bnez      r2,printLoop          #   IF r2 == 0
        ret                             #     RETURN
printLoop:                              #   LOOP:
        controlu  r3,r0,SERIAL_STAT     #     Get serial status
        andi      r3,r3,0x02            #     Look at bit [1] only
        beqz      r3,printLoop          #   UNTIL OUTPUT READY = 1
        controlu  r0,r2,SERIAL_SEND     #   Send a byte from r2
        addi      r1,r1,1               #   Increment pointer
        jump      PrintString           # ENDLOOP

############################################################################
#
# PrintChar
#
# Passed:
#    r1 = character -128...+127
# Uses:
#    none; uses stack
#
############################################################################

PrintChar:
        addi      sp,sp,-16           # Function prologue
        store.d   0(sp),r1            # .
        store.d   8(sp),r2            # .
PrintCharLoop:                        # LOOP
	controlu  r2,r0,SERIAL_STAT     #   Get serial status
	andi	  r2,r2,0x02	      #   Look at bit [1] only
	beqz	  r2, PrintCharLoop     # UNTIL Output_Ready = 1
	controlu  r0,r1,SERIAL_SEND     # Print r1
        load.d    r1,0(sp)            # Return Sequence           
        load.d    r2,8(sp)            # .           
        addi      sp,sp,16            # .
        ret                           # .

############################################################################
#
# PrintNibble
#
# Passed:
#    r1 = number in 0...15 (ignores all but LSB 4 bits)
# Uses:
#    none
#
############################################################################

PrintNibble:
        addi      sp,sp,-24           # Function prologue
        store.d   0(sp),lr            # .
        store.d   8(sp),r1            # .
        store.d   16(sp),r2           # .
	andi      r1,r1,0x000F        # Look only at LSB 4 bits
        bgei      r1,10,PrintAlpha    # IF r1 <= 9
        addi      r1,r1,0x30          #   Change to "0"..."9"
        jump      PrintNibbleLoop     # ELSE
PrintAlpha:                           # .
        addi      r1,r1,0x61-10       #   Change to "a"..."f"
PrintNibbleLoop:                      # LOOP
	controlu  r2,r0,SERIAL_STAT   #   Get serial status
	andi	  r2,r2,0x02	      #   Look at bit [1] only
	beqz	  r2,PrintNibbleLoop  # UNTIL Output_Ready = 1
	controlu  r0,r1,SERIAL_SEND   # Print r1
        load.d    lr,0(sp)            # Return           
        load.d    r1,8(sp)            # .           
        load.d    r2,16(sp)           # .           
        addi      sp,sp,24            # .
        ret                           # .

############################################################################
#
# PrintInt
#
# Prints a value in hex, e.g., "abcd1234fedc8765"
#
# Passed:
#    r1 = 64 bit value
# Uses:
#    none
#
############################################################################

PrintInt:
        addi      sp,sp,-24           # Function prologue
        store.d   0(sp),lr            #
        store.d   8(sp),r1            #
        store.d   16(sp),r2           #

	movi	  r2,16		      # LOOP 16 times
PrNibLoop:                            # .
	rotri	  r1,r1,60	      #   Look at next 4 bits (rotate left 4 bits)
	call	  PrintNibble	      #   Print as a hex char
	addi	  r2,r2,-1	      # ENDLOOP
	bgtz	  r2,PrNibLoop	      # .
        load.d    lr,0(sp)            # Return           
        load.d    r1,8(sp)            # .           
        load.d    r2,16(sp)           # .           
        addi      sp,sp,24            # .
        ret                           # .

############################################################################
#
# GetChar
#
# Waits for a character on the Serial Input and returns it.
#    r1 = character (byte)
# Modifies:
#    r1
# Leaf function
#
############################################################################

GetChar:                                  # LOOP
        controlu  r1,r0,SERIAL_STAT       #   Get serial status
        andi      r1,r1,0x01              #   Look at bit [0] only
        beqz      r1,GetChar              # UNTIL INPUT AVAIL = 1
        controlu  r1,r0,SERIAL_RECV       # Recv into r1
        beqi      r1,0x0a,GetChar         # Ignore \n
        beqi      r1,0x0d,GetChar         # Ignore \r
        beqi      r1,0x09,GetChar         # Ignore \t
        beqi      r1,0x20,GetChar         # Ignore space
        ret                               # RETURN

############################################################################
#
# GetHex
#
# Reads one hex characters and returns the value.
#    r1 = value (0..15)
# Modifies:
#    r1 (if error, it modifies more)
# The following characters are ignored: space, \t, \n, \r. Otherwise, it must
# be 0,1,...9,a,b,...f,A,B,...F. If the character is anything else, we print
# a message and HALT execution with DEBUG.
#
############################################################################

GetHex:
        addi     sp,sp,-8            # Function prologue
        store.d  0(sp),lr            # .
        call     GetChar             # r1 = input char
        controlu r0,r1,DIGITAL_WRITE # Display char on digital out
        blti     r1,0x30,tryAlpha    # If "0" <= char <= "9"
        bgti     r1,0x39,tryAlpha    # .
        andi     r1,r1,0x0f          #   convert to 0 ... 9
        jump     GetHexReturn        #   Return
tryAlpha:                            # ELSE IF "a" <= char <= "f"
        blti     r1,0x61,tryUpper    # .
        bgti     r1,0x66,tryUpper    # .
        addi     r1,r1,10-0x61       #   convert to 10 ... 15
        jump     GetHexReturn        #   Return
tryUpper:                            # ELSE IF "A" <= char <= "F"
        blti     r1,0x41,BadHex      # .
        bgti     r1,0x46,BadHex      # .
        addi     r1,r1,10-0x41       #   convert to 10 ... 15
        jump     GetHexReturn        #   Return
BadHex:                              # ELSE
        movi     r1,BadHexStr        #   Print error message
        call     PrintString         #   .
        jump     _entry              #   Restart execution
GetHexReturn:                        # ENDIF
        load.d   lr,0(sp)            # Return           
        addi     sp,sp,8             # .
        ret                          # .
BadHexStr:
        .string  "\n\r*****  ERROR: Invalid hex character!  *****\n\r\0"
        .align   4

############################################################################
#
# GetByte
#
# Reads two hex characters and returns the byte.
#    r1 = byte (-128 ... + 127)
# Modifies:
#    r1, r2
#    r6 = Running CheckSum is updated (the byte is XORed with r6)
# If either character is not a hex character, prints a message and
# does not return.
#
############################################################################

GetByte:
        addi     sp,sp,-8     # Function prologue
        store.d  0(sp),lr     # .
        call     GetHex       # get first nibble
        slli     r2,r1,4      # move it to the left 4 bits
        call     GetHex       # get the second nibble
        or       r1,r1,r2     # combine them
        xor      r6,r6,r1     # Update the checksum
        load.d   lr,0(sp)     # Return           
        addi     sp,sp,8      # .
        ret                   # .

############################################################################
#
# GetInt
#
# Reads 8 hex characters and returns the doubleword.
#    r3 = value returned
# Modifies:
#    r1,r2,r3,r4
#
############################################################################

GetInt:
        addi    sp,sp,-8        # Function prologue
        store.d 0(sp),lr        # .
        movi    r3,0            # r3 = 0
        movi    r4,8            # LOOP 8 times
GetIntLoop:                     # .
        call    GetByte         #   Get next byte
        slli    r3,r3,8         #   Shift it into r3
        or      r3,r3,r1        #   .
        addi    r4,r4,-1        # ENDLOOP
        bnez    r4,GetIntLoop   # .
        load.d  lr,0(sp)        # Return result in r3           
        addi    sp,sp,8         # .
        ret                     # .

