• Please review our updated Terms and Rules here

Converting an 8-bit specific algorithm to C

tech58761

Experienced Member
Joined
Nov 10, 2023
Messages
255
Location
Northern Plains, USA
As long as the topic of converting 8-bit assembly to C has been raised in another thread, I thought I'd ask the brain trust for a bit of help.

Most of this code attached I can reasonably rewrite into C, except for the core algorithm, which takes a stream of raw bits, evaluates it for the presence and direction of bit transitions, and eventually decodes it into bits of an incoming message (coherent phase-shift keying, to be precise).

If I can master this bit of code (or come up with an equivalent approach), then I can get going on porting some other code to C.

The segment in question is from the bitTest label to the instruction STAA byteOld.
 

Attachments

There are obviously a ton of ways of approaching this, but when I get overwhelmed I like to go back to basics and just do the most literal thing I can. In this case it would be local variables for the registers, etc. Here's a sketch of the outer structure of the chunk of code you're looking at-- this is not complete (or tested ofc!). My personal strategy would be getting something tricky working in the simplest, most direct way, and then think about how to simplify/optimize because the best version of the C code would not really look like this. :)

This code does some signed computation. I've set up everything here as unsigned 8-bit values, but you will have to noodle on how you want to handle the signed stuff--you might want to rewrite using int8_t signed values, etc. And it depends on how it interfaces with the rest of your C code.

Fortunately, the comment above that section of code is quite clear in explaining what it's doing so you can check yourself regardless of how you take it on.

C:
// trivial function signature with all the memory locations used passed as pointers.
void bitTest (uint8_t * pValueIi, uint8_t * pValueQi, uint8_t * pByteOld, uint8_t * pByteLast)
{
    // sine table as a standard array
    static uint8_t sineTb[] = { 17, 12, 0, 244, 239, 244, 0, 12, 17, 12 };

    uint8_t a, b;   // accumulators
    uint16_t x;     // "index register" could just be an int since used as index
 
    *pValueIi = 0;
 
    // STAB  valueQi: [!] The value in b needs to be passed in somehow if you use this function
    //                  structure. It's weird that the the assembly code does this and I can't really figure
    //                  out if necessary.
    // *ValueQi = b??
 
    a = byteOld;
    x = 7;          // points to the same first entry as the asm does

    // The code starting at bits_01 and before STAA byteOld looks like a while loop
    // with its end condition at the bottom
    do {
        // bits_01
        b = a;
        b ^= (*byteLast);
   
        // The test BPL bits_04 skips down to bits_04 if the result of the XOR has
        //  the high bit clear. Only enter the "if" if it's set.
        if (b >= 0x80) {
       
            // Get 0-degree value
            b = sineTb[x];
       
            // TSTA, BPL bits_02 checks the high bit of A and only does the NEGB if
            //  high bit is set
            if (a >= 0x80) {   
                // NEGB : exercise for the reader :)
            }
            b += *pValueIi;         // you can simplify these lines
            *pValueIi = b;
       
            // Get 90-degree value
            b = sineTb[x+2];
       
            ...
        }
        // bits_04
        a = *pByteLast;
        (*byteLast) <<= 1;
        x--;
 
    } while ( ... )
 
 
    *pByteOld = a;
}
 
Last edited:
Thing is, it's not signed math per se, rolling the bits around into the sign position just happens to be the most convenient way to compare consecutive bits within the bit stream, the byteOld value being used to carry over for comparison against the next 8 bits within byteLast.

If you look further up, you'll see where it reads from Port 1 then does a couple bit rolls to transfer the current value in Port 1, bit 1 (output of the receiver circuit) into the next bit of byteNow.
ctPuls is a running counter that advances one tick each time a bit is gathered. Until ctPuls has advanced to XXXXX000, the interrupt exits via RTI after gathering a bit.

Once ctPuls reaches XXXXX000, we know we have filled byteNow and eventually transfer it into byteLast (and the next pass through, we start capturing the next 8 bits into byteNow).
Once ctPhse (derived from bits 3,4 of ctPuls) similarly advances to XXX00000, byteLast finally drops into this algorithm to calculate the current running values of valueIi and valueQi...

It's more I need a way to get a bit from Port 1.1, hold it, then compare it to the next value I get from Port 1.1 and use that to find and process the bit transitions.

I don't think speed is necessarily crucial (the data being evaluated is transmitted at roughly 72 to 76 baud), just that it needs to be a continuous background process...

I have some much more modern code that will be very similar in function but I have no way to disassemble it properly (and THAT code may well be compiled C!)
 
rcman: Promising. It may take a bit of time to digest, with overtime and the holidays around the corner.
I'm liking what I see, though, and am very tempted to put up a couple copies of the alternate algorithm I have - both the as-dumped version and after I cleaned it up a bit.
 
@rcman2 was this emitted out of LLM? Sorry for asking but readme.md and comments are in that style.
 
If your application is using interrupts (as it is) this presupposes that the C compiler contains support for interrupts (or you will have to roll your own support).

If the C compiler is an optimising compiler, then the use of the volatile keyword is going to be mandatory with I/O devices and interrupts.

The use of the volatile keyword prevents the compiler from assuming that things (including I/O register values) don't change unless the CPU has made them change by assigning a value to them.

Dave
 
daver2: Regarding interrupts, there are two approaches I've seen within this product family - some of the earlier products use the NMI line (!!!) to summon the interrupt routine.
Later generations instead use the MPU's internal timer system to generate the necessary calls to the interrupt routine.

I'm still of the opinion that the algorithm was so structured to take advantage of the MC6801 instruction set, and other approaches may be possible that are more C friendly.

But right now, with a lack of fresh information (there are a few loose ends I'd love to clean up, but I need actual units to study)...
I'm going through my notes (EPROM disassemblys, schematics, etc.) to get it cleaned up before I decide what direction to take.

Let me go through the code I mentioned that has the alternate algorithm and get it posted this week sometime.
 
No problem. I just thought (since I saw interrupt vectors and RTI instructions being used) to put that issue into the mix so you don't fall over it (or are aware of it).

Dave
 
@rcman2 was this emitted out of LLM? Sorry for asking but readme.md and comments are in that style.
Yes, I asked to convert the formulas to C code.

I can't verify if they're correct. Just though I'd try and help out.

RC
 
Yeah LLM emission is really easy to catch. Commonly they'll technobabble about things that are easy to explain with simple language.

The code looks ok, straightforward, should be easily to test it to see if results are good and consistent.
The code is not safe tho, which I presume is down to LLM sourcing a lot of embedded projects to come up with this result. In that area NULL checks and bound checks are sometimes not desirable.
 
Yeah LLM emission is really easy to catch. Commonly they'll technobabble about things that are easy to explain with simple language.

The code looks ok, straightforward, should be easily to test it to see if results are good and consistent.
The code is not safe tho, which I presume is down to LLM sourcing a lot of embedded projects to come up with this result. In that area NULL checks and bound checks are sometimes not desirable.
Oh yes, I have tried many LLM to convert or fix code. The ONLY one I use is Claude and now Only Opus which is smarter than Sonnet. Other LLMs like XAI when it comes to C and especially pointers do a VERY bad job. I don't trust them at all. But claude actually created its own virtual environment and builds the code and checks for issues which I do think is awesome. But like everything Opus is pretty good but does make a lot less mistakes than the rest of them.


RC
 
Just for the sake of comparison, here's the code with the alternate algorithm.
Actually - two different revisions. Rev G is as-dumped from the EPROMs (2x 2732) and Rev H is after I went through and cleaned things up just a hair in hopes of making the function clearer.
While S00045 is from a receive-only unit, S00060 is from a portable tester.
 

Attachments

Last edited:
Just for the sake of comparison, here's the code with the alternate algorithm.
Actually - two different revisions. Rev G is as-dumped from the EPROMs (2x 2732) and Rev H is after I went through and cleaned things up just a hair in hopes of making the function clearer.
While S00045 is from a receive-only unit, S00060 is from a portable tester.
Is this different that the one from earlier?
Thanks
RC
 
It is. S00045A is the firmware for the LMT-1xx series load control receiver. S00060G is the firmware for the Field Configuration Terminal.
Sorry I should have asked, do you want them both converted to C?
 
Back
Top