Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I write an efficient switch() in 8051 assembly?

If I ever want to create a finite state machine in 8051 assembly, I'll need an efficient equivalent of C switch() expression.

[for this question, let us disregard the fall-through behavior, both retaining and dropping it is acceptable].

There are a few ways to achieve that in 8051 assembly, but each of them has their downsides. The ones for short switches of 5-10 cases are simple enough, and clear enough, but if I want a switch of >128, or even >256 cases, things get complicated.

The first one is simple, a chain of CJNE comparing the operand to values and jumping to next case if not equal; equivalent to if(){...}else if(){....} else if(){...}. The upside is simplicity, the downside is obvious: in case of a long switch this will create a very long string of choices. This can be reduced through building a binary tree, using JB for consecutive bits of the control variable. This is still not quite efficient, very hard to maintain and makes sparse keys set difficult to implement (`case 1:...; case 5:...; case 23:...; case 118:...)

Next approach is to multiply, then jump offset. Multiply the control variable by 2, load the result into DPTR, load the accumulator with an offset, then perform a JMP @A+DPTR into a zone preloaded with multitude of AJMP. (or multiply by 3 and jump into zone preloaded with multitude of LJMP. ).

I did that once. Adjusting locations of the jump instruction to the bytes was a puzzle I don't really want to repeat, plus the jump table is unnecessarily large (repeating ajmp each other byte). Maybe I don't know some trick that would make it easy...

And there's the approach to pull an address from a dedicated table, preload the stack and perform RET. It sounds very neat except pulling an address from a dedicated table needs to be done with the horrendous MOVC A, @A+DPTR or MOV A, @A+PC- these addressing modes make me wince so hard I never tried implementing it. If you know a neat way of doing this, please post that as an answer.

In general, I'd like to know if there's a more elegant, efficient way to perform a switch() style jump - one without creating too much overhead, not wasting too much memory and giving freedom of jumping at least AJMP distance, with number of cases going into hundreds.

like image 634
SF. Avatar asked May 24 '14 11:05

SF.


1 Answers

I am not normally a fan of this type of answer, but I feel it's appropriate here.

Don't do it! A very large switch statement is a code smell. It suggests that some poor planning has happened in your code somewhere, or some originally good design has grown out of control as the scope of the project has grown.

You should use a switch statement when you have a choice of several genuinely different options. Like this:

void HandleCommand(unsigned char commadndByte)
{
switch (commandByte)
{
        case COMMAND_RESET:
            Reset();
            break;

        case COMMAND_SET_PARAMETERS:
            SetPID(commandValue[0], commandValue[1], commandValue[2]);
            ResetController();
            SendReply(PID_PARAMETERS_SET);
            break;

        default:
            SendReply(COMMAND_HAD_ERROR);
            break;
    }

Does your switch statement really divert program flow to hundreds of genuinely different options? Or is it more like this?

void HandleCharacter(unsigned char c)
{
switch (c)
    {
        case 'a':    c='A';    break;
        case 'b':    c='B';    break;
        case 'c':    c='C';    break;
        case 'd':    c='D';    break;
        ...
        case 'w':    c='W';    break;
        case 'x':    c='X';    break;
        case 'y':    c='Y';    break;
        case 'z':    c='Z';    break;
    }
}

Whatever it is you're doing, it can probably be done better with an array of some sort. To save writing an answer in assembler, I'll write it in C, but the concept is the same.

If each case of the switch involves calling a different function, then:

const void (*p[256]) (int x, int y) = {myFunction0, myFunction1, ... myFunction255};

void HandleCharacter(unsigned char c)
{
    (*p[c])();
}

You might argue that the array of function pointers takes up lots of memory. If it's const, then it should only take up FLASH, nor RAM, and should take up way less FLASH than the equivalent switch statement;

Or, something like this might be more relevant. If each case of the switch involves assigning a different value, then:

char validResponses[256] = {INVALID, OK, OK, OK, PENDING, OK, INVALID, .... };

void HandleCharacter(unsigned char c)
{
    sendResponse(validResponses[c]);
}

In summary, try to find a pattern in your switch statement; what varies and what stays the same? Put what varies into an array, and what stays the same into code.

like image 122
Rocketmagnet Avatar answered Nov 18 '22 09:11

Rocketmagnet