*** Settings ***
Test Setup                          Create Machine
Test Tags                           atomics

*** Variables ***
${MEMORY_START}                     0x80000000
${PLATFORM_STRING}                  SEPARATOR=\n
...                                 dram: Memory.MappedMemory @ sysbus ${MEMORY_START} {
...                                 ${SPACE*4}size: 0x80000000
...                                 }
...                                 mmio: Memory.ArrayMemory @ sysbus 0x100000000 {
...                                 ${SPACE*4}size: 0x10000
...                                 }
...                                 mtvec: Memory.MappedMemory @ sysbus 0x1000 { size: 0x40000 }
...
...                                 cpu: CPU.RiscV64 @ sysbus {
...                                 ${SPACE*4}cpuType: "rv64gc_zicsr_zifencei_zacas";
...                                 ${SPACE*4}hartId: 1;
...                                 ${SPACE*4}privilegedArchitecture: PrivilegedArchitecture.Priv1_10;
...                                 ${SPACE*4}timeProvider: empty;
...                                 ${SPACE*4}CyclesPerInstruction: 8;
...                                 ${SPACE*4}allowUnalignedAccesses: true
...                                 }
...
...                                 cpu32: CPU.RiscV32 @ sysbus {
...                                 ${SPACE*4}cpuType: "rv32gc_zicsr_zifencei_zacas";
...                                 ${SPACE*4}hartId: 2;
...                                 ${SPACE*4}privilegedArchitecture: PrivilegedArchitecture.Priv1_10;
...                                 ${SPACE*4}timeProvider: empty;
...                                 ${SPACE*4}CyclesPerInstruction: 8;
...                                 ${SPACE*4}allowUnalignedAccesses: true
...                                 }
${PROGRAM_COUNTER}                  0x80000000
${PROGRAM_COUNTER_32}               0x80000100
${ORDINARY_ADDRESS}                 0x80001000
${MAX_PAGE_SIZE}                    0x40000000  # 1 GiB
${PAGE_SPANNING_ADDRESS}            ${{str(${MEMORY_START} + ${MAX_PAGE_SIZE} - 1)}}  # str necessary since robot's XML-RPC library doesn't support >32-bit integers
${MMIO_ADDRESS}                     0x0000000100001000

${mtvec}                            0x1010
${illegal_instruction}              0x2

# Registers used
${x0}                               0
${a0}                               10
${a1}                               11
${a2}                               12
${a3}                               13
${a4}                               14
${s2}                               18

# 32-, 64- and 128-bit constants
${wrong_expected_128}               0x12345678910111213141516171819202
${wrong_expected_64}                0x1234567891011
${wrong_expected_32}                0x1234
${expected_128}                     0x2badc00010ffb0ba1afedeadbeefd00d
${expected_64}                      0x1afedeadbeefd00d
${expected_32}                      0xbeefd00d
${new_128}                          0x216b00b5d0d0caca1eeff00dbabedead
${new_64}                           0xbeeff00dbabedead
${new_32}                           0xbeefbabe

*** Keywords ***
Create Machine
    Execute Command                 mach create
    Execute Command                 machine LoadPlatformDescriptionFromString """${PLATFORM_STRING}"""
    Execute Command                 cpu ExecutionMode SingleStep
    Execute Command                 cpu PC ${PROGRAM_COUNTER}
    Execute Command                 cpu32 ExecutionMode SingleStep
    Execute Command                 cpu32 PC ${PROGRAM_COUNTER}

Get Cpu On ${platform:(RV32|RV64)}
    IF  "${platform}" == "RV32"
        ${cpu}=                         Set Variable  cpu32
    ELSE IF  "${platform}" == "RV64"
        ${cpu}=                         Set Variable  cpu
    END
    [return]                        ${cpu}

Amocas.${size:(w|d|q)} ${rd} ${rs2} ${rs1} On ${platform:(RV32|RV64)} Should Throw Illegal Instruction
    ${cpu}=                         Get Cpu On ${platform}

    # Should have jumped to mtvec
    PC Should Be Equal              ${mtvec}  cpuName=${cpu}

    # The cause should be illegal instruction.
    ${mcause}=                      Execute Command  ${cpu} MCAUSE
    Should Be Equal As Numbers      ${mcause}  ${illegal_instruction}

    # MTVAL should be the opcode that caused the fault.
    ${mtval}=                       Execute Command  ${cpu} MTVAL
    ${illegal_amocas_opcode}=       Assemble Amocas.${size} ${rd} ${rs2} ${rs1}
    Should Be Equal As Numbers      ${mtval}  ${illegal_amocas_opcode}

    # MEPC should point to the illegal instruction.
    ${mepc}=                        Execute Command  ${cpu} MEPC
    Should Be Equal As Numbers      ${mepc}  ${PROGRAM_COUNTER}

Amocas.${size:(w|d|q)} Memory Location ${address} Should Now Be Set To ${value}
    IF  "${size}" == "w"
        ${current_value}=               Execute Command  sysbus ReadDoubleWord ${address}
    ELSE IF  "${size}" == "d"
        ${current_value}=               Execute Command  sysbus ReadQuadWord ${address}
    ELSE IF  "${size}" == "q"
        ${current_value_lower}=         Execute Command  sysbus ReadQuadWord ${address}
        ${current_value_upper}=         Execute Command  sysbus ReadQuadWord ${${address} + 8}
    END

    IF  "${size}" == "q"
        ${new_value_lower}=             Set Variable  ${{str(int(${value}) & 0xFFFFFFFFFFFFFFFF)}}
        ${new_value_upper}=             Set Variable  ${{str((int(${value}) >> 64) & 0xFFFFFFFFFFFFFFFF)}}
        Should Be Equal As Integers     ${current_value_lower}  ${new_value_lower}  "Memory location lower should now be set to ${new_value_lower}"
        Should Be Equal As Integers     ${current_value_upper}  ${new_value_upper}  "Memory location upper should now be set to ${new_value_upper}"
    ELSE
        Should Be Equal As Integers     ${current_value}  ${value}  "Memory location ${address} should now be set to ${value} but it's ${current_value}"
    END

Amocas.${size:(w|d|q)} Register ${register} On ${platform:(RV32|RV64)} Should Contain ${expected_value}
    ${cpu}=                         Get Cpu On ${platform}

    IF  "${platform}" == "RV32" and "${size}" == "d"
        # str necessary since robot's XML-RPC library doesn't support >32-bit integers
        ${expected_value_lower}=        Set Variable  ${{str(int(${expected_value}) & 0xFFFFFFFF)}}
        ${expected_value_upper}=        Set Variable  ${{str((int(${expected_value}) >> 32) & 0xFFFFFFFF)}}
        Register Should Be Equal        ${register}  ${expected_value_lower}  cpuName=${cpu}
        Register Should Be Equal        ${${register} + 1}  ${expected_value_upper}  cpuName=${cpu}
    ELSE IF  "${platform}" == "RV64" and "${size}" == "q"
        # str necessary since robot's XML-RPC library doesn't support >32-bit integers
        ${expected_value_lower}=        Set Variable  ${{str(int(${expected_value}) & 0xFFFFFFFFFFFFFFFF)}}
        ${expected_value_upper}=        Set Variable  ${{str((int(${expected_value}) >> 64) & 0xFFFFFFFFFFFFFFFF)}}
        Register Should Be Equal        ${register}  ${expected_value_lower}  cpuName=${cpu}
        Register Should Be Equal        ${${register} + 1}  ${expected_value_upper}  cpuName=${cpu}
    ELSE
        Register Should Be Equal        ${register}  ${expected_value}  cpuName=${cpu}
    END

Amocas.${size:(w|d|q)} Set Register ${register} On ${platform:(RV32|RV64)} To ${value}
    ${cpu}=                         Get Cpu On ${platform}

    IF  "${register}" == "0"
        Return From Keyword
    END

    IF  "${platform}" == "RV32" and "${size}" == "d"
        ${value_lower}=                 Set Variable  ${{${value} & 0xFFFFFFFF}}
        ${value_upper}=                 Set Variable  ${{(${value} >> 32) & 0xFFFFFFFF}}
        Execute Command                 ${cpu} SetRegister ${register} ${value_lower}
        Execute Command                 ${cpu} SetRegister ${${register} + 1} ${value_upper}
    ELSE IF  "${platform}" == "RV64" and "${size}" == "q"
        # str necessary since robot's XML-RPC library doesn't support >32-bit integers
        ${value_lower}=                 Set Variable  ${{str(int(${value}) & 0xFFFFFFFFFFFFFFFF)}}
        ${value_upper}=                 Set Variable  ${{str((int(${value}) >> 64) & 0xFFFFFFFFFFFFFFFF)}}
        Execute Command                 ${cpu} SetRegister ${register} ${value_lower}
        Execute Command                 ${cpu} SetRegister ${${register} + 1} ${value_upper}
    ELSE
        Execute Command                 ${cpu} SetRegister ${register} ${value}
    END

Assemble Amocas.${size:(w|d|q)} ${rd} ${rs2} ${rs1}
    # Hand-assembled instructions necessary due to
    # our version of the LLVM assembler not supporting the Zacas extension. (issue #74345)

    # The machine code for an amocas instruction with its operands and size zeroed out.
    ${amocas_base}=                 Set Variable  0b00101_0_0_00000_00000_000_00000_0101111

    # Translate size mnemonic to corresponding bit pattern.
    IF  "${size}" == "w"
        ${size_bits}=                   Set Variable  0b010
    ELSE IF  "${size}" == "d"
        ${size_bits}=                   Set Variable  0b011
    ELSE IF  "${size}" == "q"
        ${size_bits}=                   Set Variable  0b100
    END

    # Insert size into instruction
    ${amocas_sized}=                Set Variable  ${{${amocas_base} | (${size_bits} << 12)}}

    # Insert rd operand
    ${amocas_sized_rd}=             Set Variable  ${{${amocas_sized} | (${rd} << 7)}}

    # Insert rs1 operand
    ${amocas_sized_rd_rs1}=         Set Variable  ${{${amocas_sized_rd} | (${rs1} << 15)}}

    # Insert rs2 operand
    ${amocas_complete}=             Set Variable  ${{${amocas_sized_rd_rs1} | (${rs2} << 20)}}

    [return]                        ${amocas_complete}

Amocas.${size:(w|d|q)} On ${platform:(RV32|RV64)} ${should:(Should|Shouldn't)} Set Value At ${variable_address} To ${new_value} If Expecting ${expected_value}
    [Arguments]
    ...                             ${rd}=${a0}
    ...                             ${rs1}=${a3}
    ...                             ${rs2}=${s2}
    ...                             ${original_value_upper}=0x2badc00010ffb0ba
    ...                             ${original_value_lower}=${expected_64}

    # Place value in memory.
    Execute Command                 sysbus WriteQuadWord ${variable_address} ${original_value_lower}
    Execute Command                 sysbus WriteQuadWord ${${variable_address} + 8} ${original_value_upper}

    ${cpu}=                         Get Cpu On ${platform}

    # Construct amocas instruction.
    ${MACHINE_CODE_AMOCAS}=         Assemble Amocas.${size} ${rd} ${rs2} ${rs1}

    IF  "${size}" == "w"
        # str necessary since robot's XML-RPC library doesn't support >32-bit integers
        ${ORIGINAL_VALUE_MASKED}=       Set Variable  ${{str(${original_value_lower} & 0xFFFFFFFF)}}
    ELSE IF  "${size}" == "d"
        ${ORIGINAL_VALUE_MASKED}=       Set Variable  ${original_value_lower}
    ELSE IF  "${size}" == "q"
        # str necessary since robot's XML-RPC library doesn't support >32-bit integers
        ${ORIGINAL_VALUE_MASKED}=       Set Variable  "${{str((${original_value_upper} << 64) | ${original_value_lower})}}"
    END

    # Place machine code at PC.
    Execute Command                 sysbus WriteDoubleWord ${PROGRAM_COUNTER} ${MACHINE_CODE_AMOCAS}

    # Set operand values.
    Amocas.${size} Set Register ${rd} On ${platform} To ${expected_value}
    Amocas.${size} Set Register ${rs1} On ${platform} To ${variable_address}
    Amocas.${size} Set Register ${rs2} On ${platform} To ${new_value}

    # Remember previous value.
    ${result_upper_original_value}=  Execute Command  ${cpu} GetRegister ${${rd} + 1}
    ${rs2_upper_original_value}=    Execute Command  ${cpu} GetRegister ${${rs2} + 1}

    # Perform amocas.
    Execute Command                 ${cpu} Step

    IF  "${rd}" != "0"
        # After amocas, rd register should have the original memory value (before the cas)...
        Amocas.${size} Register ${rd} On ${platform} Should Contain ${ORIGINAL_VALUE_MASKED}
    ELSE IF  ("${platform}" == "RV32" and "${size}" == "d") or ("${platform}" == "RV64" and "${size}" == "q")
        # Unless rd is x0, in which case the original memory value is discarded and neither result register is written.
        # Ensure rd+1 isn't written to.
        Register Should Be Equal        ${${rd} + 1}  ${result_upper_original_value}  cpuName=${cpu}
    END
    # and the others should remain unchanged
    IF  "${rs2}" != "0"
        Amocas.${size} Register ${rs2} On ${platform} Should Contain ${new_value}
    ELSE IF  ("${platform}" == "RV32" and "${size}" == "d") or ("${platform}" == "RV64" and "${size}" == "q")
        # Unless rs2 is x0, in which case rs2+1 is interpreted as 0 no matter its contents.
        # Ensure rs2+1 remains unchanged.
        Register Should Be Equal        ${${rs2} + 1}  ${rs2_upper_original_value}  cpuName=${cpu}
    END
    Register Should Be Equal        ${rs1}  ${variable_address}  cpuName=${cpu}

    IF  "${should}" == "Should"
        # Now value in memory should have been set.
        Amocas.${size} Memory Location ${variable_address} Should Now Be Set To ${new_value}
    ELSE
        # Value in memory should remain unchanged.
        Amocas.${size} Memory Location ${variable_address} Should Now Be Set To ${ORIGINAL_VALUE_MASKED}
    END

*** Test Cases ***
# w should tests
Amocas.w On RV64 Should Set Value At Single-Page Memory Location
    Amocas.w On RV64 Should Set Value At ${ORDINARY_ADDRESS} To ${new_32} If Expecting ${expected_32}

Amocas.w On RV64 Should Set Value At Page-Spanning Memory Location
    Amocas.w On RV64 Should Set Value At ${PAGE_SPANNING_ADDRESS} To ${new_32} If Expecting ${expected_32}

Amocas.w On RV64 Should Set Value At MMIO Memory Location
    Amocas.w On RV64 Should Set Value At ${MMIO_ADDRESS} To ${new_32} If Expecting ${expected_32}

Amocas.w On RV32 Should Set Value At Single-Page Memory Location
    Amocas.w On RV32 Should Set Value At ${ORDINARY_ADDRESS} To ${new_32} If Expecting ${expected_32}

# w shouldn't tests

Amocas.w On RV64 Shouldn't Set Value At Single-Page Memory Location
    Amocas.w On RV64 Shouldn't Set Value At ${ORDINARY_ADDRESS} To ${new_32} If Expecting ${wrong_expected_32}

Amocas.w On RV64 Shouldn't Set Value At Page-Spanning Memory Location
    Amocas.w On RV64 Shouldn't Set Value At ${PAGE_SPANNING_ADDRESS} To ${new_32} If Expecting ${wrong_expected_32}

Amocas.w On RV64 Shouldn't Set Value At MMIO Memory Location
    Amocas.w On RV64 Shouldn't Set Value At ${MMIO_ADDRESS} To ${new_32} If Expecting ${wrong_expected_32}

Amocas.w On RV32 Shouldn't Set Value At Single-Page Memory Location
    Amocas.w On RV32 Shouldn't Set Value At ${ORDINARY_ADDRESS} To ${new_32} If Expecting ${wrong_expected_32}

# d should tests

Amocas.d On RV64 Should Set Value At Single-Page Memory Location
    Amocas.d On RV64 Should Set Value At ${ORDINARY_ADDRESS} To ${new_64} If Expecting ${expected_64}

Amocas.d On RV32 Should Set Value At Single-Page Memory Location
    Amocas.d On RV32 Should Set Value At ${ORDINARY_ADDRESS} To ${new_64} If Expecting ${expected_64}

Amocas.d On RV32 Should Handle Zero Source Register
    # Place value in rs2+1 which should be ignored.
    Execute Command                 cpu32 SetRegister ${${x0} + 1} ${wrong_expected_32}

    Amocas.d On RV32 Should Set Value At ${ORDINARY_ADDRESS} To 0 If Expecting ${expected_64}
    ...                             rs2=${x0}

Amocas.d On RV32 Should Handle Zero Destination Register
    # Place value in rd+1 which shouldn't be overwritten.
    Execute Command                 cpu32 SetRegister ${${x0} + 1} ${wrong_expected_32}

    Amocas.d On RV32 Should Set Value At ${ORDINARY_ADDRESS} To ${new_64} If Expecting 0
    ...                             rd=${x0}
    ...                             original_value_lower=0

Amocas.d On RV64 Should Set Value At Page-Spanning Memory Location
    Amocas.d On RV64 Should Set Value At ${PAGE_SPANNING_ADDRESS} To ${new_64} If Expecting ${expected_64}

Amocas.d On RV64 Should Set Value At MMIO Memory Location
    Amocas.d On RV64 Should Set Value At ${MMIO_ADDRESS} To ${new_64} If Expecting ${expected_64}

# d shouldn't tests

Amocas.d On RV64 Shouldn't Set Value At Single-Page Memory Location
    Amocas.d On RV64 Shouldn't Set Value At ${ORDINARY_ADDRESS} To ${new_64} If Expecting ${wrong_expected_64}

Amocas.d On RV32 Shouldn't Set Value At Single-Page Memory Location
    Amocas.d On RV32 Shouldn't Set Value At ${ORDINARY_ADDRESS} To ${new_64} If Expecting ${wrong_expected_64}

Amocas.d On RV64 Shouldn't Set Value At Page-Spanning Memory Location
    Amocas.d On RV64 Shouldn't Set Value At ${PAGE_SPANNING_ADDRESS} To ${new_64} If Expecting ${wrong_expected_64}

Amocas.d On RV64 Shouldn't Set Value At MMIO Memory Location
    Amocas.d On RV64 Shouldn't Set Value At ${MMIO_ADDRESS} To ${new_64} If Expecting ${wrong_expected_64}

# q should tests

Amocas.q On RV64 Should Set Value At Single-Page Memory Location
    Amocas.q On RV64 Should Set Value At ${ORDINARY_ADDRESS} To ${new_128} If Expecting ${expected_128}

Amocas.q On RV64 Should Set Value At Page-Spanning Memory Location
    Amocas.q On RV64 Should Set Value At ${PAGE_SPANNING_ADDRESS} To ${new_128} If Expecting ${expected_128}

Amocas.q On RV64 Should Set Value At MMIO Memory Location
    Amocas.q On RV64 Should Set Value At ${MMIO_ADDRESS} To ${new_128} If Expecting ${expected_128}

Amocas.q On RV64 Should Handle Zero Source Register
    # Place value in rs2+1 which should be ignored.
    Execute Command                 cpu SetRegister ${${x0} + 1} ${wrong_expected_32}

    Amocas.q On RV64 Should Set Value At ${ORDINARY_ADDRESS} To 0 If Expecting ${expected_128}
    ...                             rs2=${x0}

Amocas.q On RV64 Should Handle Zero Destination Register
    # Place value in rd+1 which shouldn't be overwritten.
    Execute Command                 cpu SetRegister ${${x0} + 1} ${wrong_expected_32}

    Amocas.q On RV64 Should Set Value At ${ORDINARY_ADDRESS} To ${new_128} If Expecting 0
    ...                             rd=${x0}
    ...                             original_value_lower=0
    ...                             original_value_upper=0

# q shouldn't tests

Amocas.q On RV64 Shouldn't Set Value At Single-Page Memory Location
    Amocas.q On RV64 Shouldn't Set Value At ${ORDINARY_ADDRESS} To ${new_128} If Expecting ${wrong_expected_128}

Amocas.q On RV64 Shouldn't Set Value At Page-Spanning Memory Location
    Amocas.q On RV64 Shouldn't Set Value At ${PAGE_SPANNING_ADDRESS} To ${new_128} If Expecting ${wrong_expected_128}

Amocas.q On RV64 Shouldn't Set Value At MMIO Memory Location
    Amocas.q On RV64 Shouldn't Set Value At ${MMIO_ADDRESS} To ${new_128} If Expecting ${wrong_expected_128}

# illegal instructions

Amocas.d On RV32 Using Odd Registers Should Throw Illegal Instruction
    ${odd_rd}=                      Set Variable  ${${a0} + 1}
    ${odd_rs2}=                     Set Variable  ${${s2} + 1}

    Amocas.d On RV32 Shouldn't Set Value At ${ORDINARY_ADDRESS} To ${new_64} If Expecting ${expected_64}
    ...                             rd=${odd_rd}
    ...                             rs2=${odd_rs2}

    Amocas.d ${odd_rd} ${odd_rs2} ${a3} On RV32 Should Throw Illegal Instruction

Amocas.q On RV64 Using Odd Registers Should Throw Illegal Instruction
    ${odd_rd}=                      Set Variable  ${${a0} + 1}
    ${odd_rs2}=                     Set Variable  ${${s2} + 1}

    Amocas.q On RV64 Shouldn't Set Value At ${ORDINARY_ADDRESS} To ${new_128} If Expecting ${expected_128}
    ...                             rd=${odd_rd}
    ...                             rs2=${odd_rs2}

    Amocas.q ${odd_rd} ${odd_rs2} ${a3} On RV64 Should Throw Illegal Instruction
