So far, we have used QEMU’s TCG generation functions to implement the operations of AVR32 instructions. In theory (and in practice), you can implement every functionality with the TCG functions. However, working with the TCG functions is like writing assembler code. You can write everything in assembler. But at a certain point, it gets complicated and enlarges the room for mistakes. I implemented complex instructions this way, so I know what I’m talking about.

Wouldn’t it be nice to have the full power of C to implement an instruction? This is where QEMU helpers come into play.

Preparing the helper

This week, I implemented the MACSATHH.W instruction. As some operations are challenging to implement with TCG operations, I decided to use a QEMU helper.

The Multiply-Accumulate Halfwords with Saturation into Word instruction uses two operands that are multiplied and then added to the destination register. The operands are the lower or upper half of two registers. If both operands are equal to -1, the multiplication product is set to 0x7fffffff. This is called saturation. Basically, if an arithmetic operation exceeds the MAX_INT range, the result is set to MAX_INT. At least, this is how I understand the architecture document. You can look up the full definition there.

I implemented the operand preparation with TCG function and only used the helper for the saturation and the calculation. To use a custom helper, we first need to define it. I placed the definition in target/avr32/helper.h:

DEF_HELPER_4(macsathhw, void, env, i32, i32, i32)

The first argument for this operation is the name of the helper. It is followed by the return type. As we do not intend to change the execution flow or do anything besides the register manipulation, we set it to void. Then, we set four arguments (notice that the DEF_HELPER ends with 4).

The first argument is a pointer to the emulated CPU environment. Then, we use i32 to indicate that we need three 32-bit integer values. The first one will be the number of the destination register. Two and three are used for the operands of the instruction.

Using C-code to directly manipulate QEMU TCGv-contents and registers

After defining the helper, we can implement the actual function. Add the following conde to the helper.c file:

void helper_macsathhw(CPUAVR32AState *env, uint32_t rd, uint32_t op1,  uint32_t op2){
    uint32_t prod = 0;
    if(op1 == -1 && op2 == -1){
        prod = 0x7fffffff;
        env->sflags[sflagQ] = 1;
    }
    //...

First, we initiate a variable for the multiplication product. Then, we check if both operands are -1 and set the product to the corresponding saturation value. In the last code line, you can see how we change the value of the status register.

We can use the CPU environment pointer to access and manipulate register values. Here, we read out the destination register, check for a saturation, and apply the final result of the operation:

    //...
    uint32_t res = prod + env->r[rd];
    if((prod >>31) && (env->r[rd] >>31) && !(res >>31)){
        //...
    }
    env->r[rd] = res;

Especially the if-operations are easily implemented in C-code. Achieving the same result with only TCG functions would require the use of multiple labels and jump marks. Of course, such an implementation is possible and works. But it takes some thinking and time, and it increases the risk of implementation errors. Additionally, there could be a performance advantage when using c-code over TCG functions.

Using the helper

To use our first QEMU helper, we need to call it inside the MACSATHHW translation function. You can find the full code in my GitHub repository. For now, I assume that you prepared the function as I explained in the previous articles.

tatic bool trans_MACSATHHW(DisasContext *ctx, arg_MACSATHHW *a) {
    TCGv operand1 = tcg_temp_new_i32();
    TCGv operand2 = tcg_temp_new_i32();

    //.. Prepare operands

    gen_helper_macsathhw(cpu_env, tcg_constant_i32(a->rd), operand1, operand2);

    ctx->base.pc_next += 4;
    return true;
}

QEMU helpers are called by the gen_helper_NAME function. We call our helper by its name from the definition.

We also set the necessary arguments:

  • The environment pointer
  • The number of the destination register (not the register itself)
  • Operand 1
  • Operand 2

I added a few tests for the MACSATHH.W instruction to my testing repository. You can verify that the helper is working as intended by executing the tests or by writing your own.

Conclusion

TCG helpers are a nice tool if you have to implement a more complex instruction in QEMU. It saves you a lot of hassle with nested if-statements, loops, and much more. I recommend using helpers if you need more than a few dozen lines of code to implement an instruction with TCG functions. Personally, I started way too late to use them. Currently, I’m working on updates for the AVR32 QEMU implementation to replace overlong translation code with helpers.

The next part of this series will cover the emulation of coprocessor operations.