*** Variables ***
# RISC-V registers
${a1}                         0xb
${starting_pc}                0x2000
${next_instruction}           0x2004
${mtvec}                      0x1010
${illegal_instruction}        0x2
${load_misaligned}            0x4
${store_misaligned}           0x6
${register_0}                 0x0
${illegal_opcode}             0x00008000
${illegal_csr}                0xf120d073
${nonexisting_csr}            0xfff0d073
${divide_by_zero_flag}        ${{ 1 << 3 }}

*** Keywords ***
Create Machine
    [Arguments]    ${bitness}    ${init_pc}
    Execute Command           using sysbus
    Execute Command           mach create "risc-v"

    Execute Command           machine LoadPlatformDescriptionFromString "clint: IRQControllers.CoreLevelInterruptor @ sysbus 0x44000000 { frequency: 66000000 }"
    Execute Command           machine LoadPlatformDescriptionFromString "cpu: CPU.RiscV${bitness} @ sysbus { timeProvider: clint; cpuType: \\"rv${bitness}gc_Zfh\\" }"
    Execute Command           machine LoadPlatformDescriptionFromString "mem: Memory.MappedMemory @ sysbus 0x1000 { size: 0x40000 }"

    IF    ${init_pc}
       Execute Command           cpu PC ${starting_pc}
    END

Create Machine 32
    Create Machine    bitness=32  init_pc=True

Create Machine 64
    Create Machine    bitness=64  init_pc=True

Write Opcode To
    [Arguments]                         ${adress}   ${opcode}
    Execute Command                     sysbus WriteDoubleWord ${adress} ${opcode}

Load Program With Invalid Instruction
    [Arguments]                         ${address}
    ${addi_opcode}                      Set Variable  0x00050593  #addi        x11 x10 0
    ${vector_opcode}                    Set Variable  0x00000057  #vadd.vv     v0, v0, v0, v0.t
    ${fvf_vector_opcode}                Set Variable  0x00005057  #vfadd.vf    v0, v0, ft0, v0.t
    ${start_pc}=                        Convert To Integer  ${address}

    # Depending on the fact if vector extension is enabled or not, the program will cause
    # invalid instruction exception at ${vector_opcode} or ${fvf_vector_opcode}.
    Write Opcode To  ${start_pc}     ${addi_opcode}            #addi x11 x10 0
    Write Opcode To  ${start_pc+4}   ${addi_opcode}            #addi x11 x10 0
    Write Opcode To  ${start_pc+8}   ${vector_opcode}          #vadd.vv v0, v0, v0, v0.t
    Write Opcode To  ${start_pc+12}  ${addi_opcode}            #addi x11 x10 0
    Write Opcode To  ${start_pc+16}  ${addi_opcode}            #addi x11 x10 0
    Write Opcode To  ${start_pc+20}  ${addi_opcode}            #addi x11 x10 0
    Write Opcode To  ${start_pc+24}  ${fvf_vector_opcode}      #vfadd.vf v0, v0, ft0, v0.t
    Write Opcode To  ${start_pc+28}  ${addi_opcode}            #addi x11 x10 0
    Write Opcode To  ${start_pc+32}  ${vector_opcode}          #vadd.vv v0, v0, v0, v0.t
    Write Opcode To  ${start_pc+36}  ${addi_opcode}            #addi x11 x10 0

Set ResetVector And Verify PC
    [Arguments]    ${reset_vector}    ${should_set_pc}
    IF    ${should_set_pc}
        ${expected_pc}=  Set Variable     ${reset_vector}
    ELSE
        ${expected_pc}=  Execute Command  cpu PC
    END
    Execute Command                cpu ResetVector ${reset_vector}
    Verify PC                      ${expected_pc}

Should Properly Handle ResetVector
    # Setting ResetVector before setting PC directly, with LoadELF etc. should propagate to PC.
    Set ResetVector And Verify PC   0x1234    should_set_pc=True
    Set ResetVector And Verify PC   0x1238    should_set_pc=True

    # Setting PC other than as an effect of ResetVector should stop the propagation.
    ${new_reset_vector_value}=  Set Variable  0x123C
    Execute Command                 cpu PC 0x5678
    Set ResetVector And Verify PC   ${new_reset_vector_value}    should_set_pc=False

    # Reset should set PC to ResetVector and restore PC propagation on ResetVector.
    Execute Command                 cpu Reset
    Verify PC                       ${new_reset_vector_value}
    Set ResetVector And Verify PC   0x1240    should_set_pc=True

Should Properly Handle ResetVector At Init And After Reset
    Verify PC                       0x1000  # Default ResetVector
    Should Properly Handle ResetVector

    ${new_reset_vector_value}=  Set Variable    0x2468
    Execute Command                 cpu ResetVector ${new_reset_vector_value}
    Execute Command                 cpu Reset
    Verify PC                       ${new_reset_vector_value}
    Should Properly Handle ResetVector

Verify PC
    [Arguments]    ${expected_pc}
    ${pc}=  Execute Command        cpu PC
    Should Be Equal As Integers    ${pc}    ${expected_pc}

*** Test Cases ***
Should Handle LB
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000000

    # lb a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00558503

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${next_instruction}

Should Handle LH
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000001

    # lh a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00559503

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${next_instruction}

Should Fail LH
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000000

    # lh a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00559503

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${mtvec}

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}   ${load_misaligned}

Should Handle LW
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000003

    # lw a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x0055a503

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${next_instruction}

Should Fail LW
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000000

    # lw a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x0055a503

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${mtvec}

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}   ${load_misaligned}

Should Handle LD
    Create Machine 64

    Execute Command                 cpu SetRegister ${a1} 0x00000003

    # ld a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x0055b503

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${next_instruction}

Should Fail LD
    Create Machine 64

    Execute Command                 cpu SetRegister ${a1} 0x00000000

    # ld a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x0055b503

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${mtvec}

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}   ${load_misaligned}

Should Handle SB
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000000

    # sb a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00a582a3

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${next_instruction}

Should Handle SH
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000001

    # sh a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00a592a3

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${next_instruction}

Should Fail SH
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000000

    # sh a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00a592a3

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${mtvec}

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}   ${store_misaligned}

Should Handle SW
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000003

    # sw a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00a5a2a3

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${next_instruction}

Should Fail SW
    Create Machine 32

    Execute Command                 cpu SetRegister ${a1} 0x00000000

    # sw a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00a5a2a3

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${mtvec}

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}   ${store_misaligned}

Should Handle SD
    Create Machine 64

    Execute Command                 cpu SetRegister ${a1} 0x00000003

    # sd a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00a5b2a3

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${next_instruction}

Should Fail SD
    Create Machine 64

    Execute Command                 cpu SetRegister ${a1} 0x00000001

    # sd a0, 0x00000005(a1)
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x00a5b2a3

    Execute Command                 cpu Step

    ${pc}=                          Execute Command     cpu PC
    Should Be Equal As Numbers      ${pc}   ${mtvec}

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}   ${store_misaligned}
    
Should Fail On Setting X0 register
    Create Machine 32

    ${msg}=     		    Run Keyword And Expect Error        *   Execute Command       cpu SetRegister ${register_0} 0x00000001
    Should Contain      	    ${msg}      register is read-only

    Register Should Be Equal        0  0x0

Should Set MEPC on Illegal Instruction
    Create Machine 32

    # j .
    Execute Command                 sysbus WriteDoubleWord 0x1010 0x0000006f

    # j 0x4000
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x0000206f
    # nop
    Execute Command                 sysbus WriteDoubleWord 0x4000 0x00000013
    # ILLEGAL INSTRUCTION
    Execute Command                 sysbus WriteDoubleWord 0x4004 ${illegal_opcode}

    Execute Command                 cpu Step 3

    PC Should Be Equal              0x1010

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}  ${illegal_instruction}
    ${mtval}=                       Execute Command     cpu MTVAL
    Should Be Equal As Numbers      ${mtval}   ${illegal_opcode}
    ${mepc}=                        Execute Command     cpu MEPC
    Should Be Equal As Numbers      ${mepc}    0x4004

Should Set MEPC on Illegal CSR access
    Create Machine 32
    Execute Command   cpu CSRValidation Full

    # j .
    Execute Command                 sysbus WriteDoubleWord 0x1010 0x0000006f

    # j 0x4000
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x0000206f
    # nop
    Execute Command                 sysbus WriteDoubleWord 0x4000 0x00000013
    # csrwi marchid, 1 - this is an illegal CSR operation as `marchid` is read-only
    Execute Command                 sysbus WriteDoubleWord 0x4004 ${illegal_csr}

    Execute Command                 cpu Step 3

    PC Should Be Equal              0x1010

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}  ${illegal_instruction}
    ${mtval}=                       Execute Command     cpu MTVAL
    Should Be Equal As Numbers      ${mtval}   ${illegal_csr}
    ${mepc}=                        Execute Command     cpu MEPC
    Should Be Equal As Numbers      ${mepc}    0x4004

Should Set MEPC on Non-Existing CSR access
    Create Machine 32

    # j .
    Execute Command                 sysbus WriteDoubleWord 0x1010 0x0000006f

    # j 0x4000
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x0000206f
    # nop
    Execute Command                 sysbus WriteDoubleWord 0x4000 0x00000013
    # csrwi marchid, 1 - this is an illegal CSR operation as `marchid` is read-only
    Execute Command                 sysbus WriteDoubleWord 0x4004 ${nonexisting_csr}

    Execute Command                 cpu Step 3

    PC Should Be Equal              0x1010

    ${mcause}=                      Execute Command     cpu MCAUSE
    Should Be Equal As Numbers      ${mcause}  ${illegal_instruction}
    ${mtval}=                       Execute Command     cpu MTVAL
    Should Be Equal As Numbers      ${mtval}   ${nonexisting_csr}
    ${mepc}=                        Execute Command     cpu MEPC
    Should Be Equal As Numbers      ${mepc}    0x4004

Should Keep MEPC First Bit As Zero
    ${assembly}=                    Catenate  SEPARATOR=\n
    ...                             li a0, 0xff
    ...                             csrw mepc, a0
    Create Machine 64
    Execute Command                 cpu AssembleBlock ${starting_pc} """${assembly}"""
    Execute Command                 cpu Step 2
    ${mepc}=                        Execute Command  cpu MEPC
    Should Be Equal As Numbers      ${mepc}    0xfe

Should Allow SRET In Machine Mode
    Create Machine 32

    Execute Command                 cpu SEPC 0x1234

    # j 0x4000
    Execute Command                 sysbus WriteDoubleWord ${starting_pc} 0x0000206f
    # nop
    Execute Command                 sysbus WriteDoubleWord 0x4000 0x00000013
    # sret
    Execute Command                 sysbus WriteDoubleWord 0x4004 0x10200073

    Execute Command                 cpu Step 3

    PC Should Be Equal              0x1234

Should Exit Translation Block After Invalid Instruction And Report Single Error
    Create Machine 32
    Load Program With Invalid Instruction               ${starting_pc}
    Create Log Tester               3

    ${start_pc}=                    Convert To Integer  ${starting_pc}
    ${illegal_opcode_1_pc}          Set Variable        ${start_pc+8}
    ${illegal_opcode_1_pc_hex}=     Convert To Hex      ${illegal_opcode_1_pc}

    ${illegal_opcode_2_pc}          Set Variable        ${start_pc+24}
    ${illegal_opcode_2_pc_hex}=     Convert To Hex      ${illegal_opcode_2_pc}

    ${illegal_opcode_3_pc}          Set Variable        ${start_pc+32}
    ${illegal_opcode_3_pc_hex}=     Convert To Hex      ${illegal_opcode_3_pc}

    # It doesn't use single stepping on purpose, as it would cause translation blocks of size 1.
    Execute Command                 cpu PerformanceInMips 1
    Execute Command                 emulation SetGlobalQuantum "0.000020"
    Execute Command                 emulation RunFor "0.000010"
    Wait For Log Entry              PC: 0x${illegal_opcode_1_pc_hex}.* instruction set is not enabled for this CPU!  treatAsRegex=True 
    Should Not Be In Log            PC: 0x${illegal_opcode_2_pc_hex}.* instruction set is not enabled for this CPU!  0  treatAsRegex=True
    Should Not Be In Log            PC: 0x${illegal_opcode_3_pc_hex}.* instruction set is not enabled for this CPU!  0  treatAsRegex=True

Should Properly Handle ResetVector After Creation And After Reset 32
    Create Machine                  bitness=32  init_pc=False
    Should Properly Handle ResetVector At Init And After Reset

Should Properly Handle ResetVector After Creation And After Reset 64
    Create Machine                  bitness=64  init_pc=False
    Should Properly Handle ResetVector At Init And After Reset

Should Set Flag On Single Precision Floating Point Division By Zero
    [Tags]    floating-point
    ${float32_zero}=                Set Variable  0xffffffff00000000
    ${float32_one}=                 Set Variable  0xffffffff3F800000
    ${float32_infinity}=            Set Variable  0xffffffff7f800000

    Create Machine 64
    
    Execute Command                 cpu SetRegister "F0" ${float32_zero}
    Execute Command                 cpu SetRegister "F1" ${float32_one}
    Execute Command                 cpu SetRegister "F2" 0x0  # register where result will be stored
    # Expected result: 1/0 = Infinity and Divide by Zero flag set.
    Execute Command                 cpu AssembleBlock ${starting_pc} "fdiv.s f2, f1, f0"

    Register Should Be Equal        F2  0x0
    ${fflags}=  Execute Command     cpu GetRegister "FFLAGS"
    Should Be True                  (${fflags} & ${divide_by_zero_flag}) == 0

    Execute Command                 cpu Step
    PC Should Be Equal              ${{ ${starting_pc} + 4 }}

    Register Should Be Equal        F2  ${float32_infinity}
    ${fflags}=  Execute Command     cpu GetRegister "FFLAGS"
    Should Be True                  (${fflags} & ${divide_by_zero_flag}) != 0

Should Set Flag On Half Precision Floating Point Division By Zero
    [Tags]    floating-point
    ${float16_zero}=                Set Variable  0xffffffffffff0000
    ${float16_one}=                 Set Variable  0xffffffffffff3c00
    ${float16_infinity}=            Set Variable  0xffffffffffff7c00

    Create Machine 64
    
    Execute Command                 cpu SetRegister "F0" ${float16_zero}
    Execute Command                 cpu SetRegister "F1" ${float16_one}
    Execute Command                 cpu SetRegister "F2" 0x0  # register where result will be stored
    # Expected result: 1/0 = Infinity and Divide by Zero flag set.
    Execute Command                 cpu AssembleBlock ${starting_pc} "fdiv.h f2, f1, f0"

    Register Should Be Equal        F2  0x0
    ${fflags}=  Execute Command     cpu GetRegister "FFLAGS"
    Should Be True                  (${fflags} & ${divide_by_zero_flag}) == 0

    Execute Command                 cpu Step
    PC Should Be Equal              ${{ ${starting_pc} + 4 }}

    Register Should Be Equal        F2  ${float16_infinity}
    ${fflags}=  Execute Command     cpu GetRegister "FFLAGS"
    Should Be True                  (${fflags} & ${divide_by_zero_flag}) != 0

Should Keep SEPC First Bit As Zero
    ${assembly}=                    Catenate  SEPARATOR=\n
    ...                             li a0, 0xff
    ...                             csrw sepc, a0
    Create Machine 64
    Execute Command                 cpu AssembleBlock ${starting_pc} """${assembly}"""
    Execute Command                 cpu Step 2
    ${sepc}=                        Execute Command  cpu SEPC
    Should Be Equal As Numbers      ${sepc}    0xfe

Should Not Read From Csr When CSRRW RD Is Zero
    ${csr_handler}=                 Catenate  SEPARATOR=
    ...  if request.IsRead:                                                  ${\n}${SPACE}
    ...      cpu.DebugLog('CSR read!')                                       ${\n}
    ...  elif request.IsWrite:                                               ${\n}${SPACE}
    ...      cpu.DebugLog('CSR written!')

    Create Machine 64
    Create Log Tester               0
    Register Failing Log String     CSR read!
    Execute Command                 logLevel 0
    Execute Command                 cpu RegisterCSRHandlerFromString 0x7c0 "${csr_handler}"
    Execute Command                 cpu AssembleBlock ${starting_pc} "csrrwi x0, 0x7c0, 13"
    Execute Command                 cpu Step 1
    Wait For Log Entry              CSR written!

Should Not Read From Csr When CSRRWI RD Is Zero
    ${csr_handler}=                 Catenate  SEPARATOR=
    ...  if request.IsRead:                                                  ${\n}${SPACE}
    ...      cpu.DebugLog('CSR read!')                                       ${\n}
    ...  elif request.IsWrite:                                               ${\n}${SPACE}
    ...      cpu.DebugLog('CSR written!')

    Create Machine 64
    Create Log Tester               0
    Register Failing Log String     CSR read!
    Execute Command                 logLevel 0
    Execute Command                 cpu RegisterCSRHandlerFromString 0x7c0 "${csr_handler}"
    Execute Command                 cpu AssembleBlock ${starting_pc} "csrrw x0, 0x7c0, a0"
    Execute Command                 cpu Step 1
    Wait For Log Entry              CSR written!

Should Ignore Writes To MENVCFG Bits Of Disabled Or Unsupported Extensions
    ${assembly}=                    Catenate  SEPARATOR=\n
    ...                             li a0, 0xffffffffffffffff
    ...                             csrw menvcfg, a0
    ...                             csrr a1, menvcfg
    Create Machine 64
    Execute Command                 cpu AssembleBlock ${starting_pc} """${assembly}"""
    Execute Command                 cpu Step 3
    ${menvcfg}=                     Execute Command  cpu GetRegister "a1"
    Should Be Equal As Numbers      ${menvcfg}  0x07fffffcffffff02
