= Checksum calculation =

This example is based on the BUA hac.

The checksum routine is used in several places:

  • to verify that the processor can read every location of the PROM.
  • to verify that the non-volatile ram (the battery backed up stuff) is correct
  • another time it adds up the entire PROM area, from $C000 to $FFFF, and I don't know why

GM was smart though, if the mask ID is $AA, it ignores this check. The engineers were probably lazy and didn't want to update the checksum every time they made a little change to the PROM.

= How the subroutine gets called to check the PROM =

The following code appears in the startup code for the processor:

 ;
 ; Check Socketed device
 ;
 LDX #$C008 ;
 LDD #$3FF8 ; B Cnt'r Last H.U LF32E ; To Ck SUM

'''WARNING''': I think there is a typo in the BUA hac, this line should actually read:

 ;
 ; Check Socketed device
 ;
 LDX #$C008 ;
 LDD #$3FF8 ; B Cnt'r Last H.U 
 JSR LF32E ; To Ck SUM

'''NOTE:''' In some masks, the subroutine is slightly different. The PROM is assumed to end at $FFFF, so the only parameter passed in is the start address, it always runs to $FFFF. Of course, this doesn't work for the NVRAM areas, so there is another smaller routine for doing NVRAM checksums.

Anyway, let's continue.

 LDAA LC008 ; Get Stored CK SUM
 CMPA #$AA ; IF its AN $AA Bypass
 BEQ LC879 ; Xit & Clr ER #51 Flg
 ; ... else
 CMPA #$32 ; Ck for correct EPROM
 BNE LC87E ; If Not exit w/error
 ; ... else
 CPY LC006 ; CK CK SUM VAL
 BNE LC87E ; Bad Ck Sum, Xit w/Error
 ; ... else
 LC879: BCLR L0047,$10 ; Clr Er #51 Flg
 BRA LC881 ; Exit Ck Sum test
 LC87E: BSET L0047,$10 ; Set Err, (#51)

Now let's break this down into individual instructions

LDX #$C008 ;

This loads the value $C008 into the X register. This is the address start parameter for the checksum subroutine.

LDD #$3FF8 ; B Cnt'r Last H.U LF32E? ; To Ck SUM

This loads the value $3FF8 into the D register. This is the count parameter for the checksum.

JSR LF32E? ; To Ck Sum Subroutine

This is starting to get complicated! We need to learn about how subroutines work. For now, you just need to know that the Checksum Subroutine expects 2 things: The start address of the calculation is stored in register $C008 and the length of the EPROM is $3FF8. When it has finished doing its calculation, it will return here and continue calculating. Different subroutines expect different things, so make sure you set it up properly before jumping to it.

Once the subroutine has completed, it stores the result in register Y. Don't touch register Y until we have done our checksum verification! How do you know this, well, you just need to read the code and make sure it doesn't overwrite it when doing other calculations.

LDAA LC008 ; Get Stored CK SUM

This is where it gets interesting. It loads the value from the location $C008 in the memory map and stores it in accumulator A. Now some of you may be puzzled here, thinking that the mask ID is stored in the bin at location $0008 rather than $C008. Well, you're right, but you have to remember that the PROM actually appears in the memory map at an offset of $C000. So you add the two together to get $C0008.

CMPA #$AA ; IF its AN $AA Bypass

Compare the value in the A register to $AA. That's all it does. It has the effect of setting some bits in the condition code register? . More specifically, it sets the Zero? flag to 0 if they are equal, or 1 if they are not equal. This is one of the places that you will start to understand assembly, you have to do some math, and then later check if something happened.

BEQ LC879 ; Xit & Clr ER #51 Flg

Here is where we check the zero flag. If the zero flag is 0, then we've read $AA from the PROM and we want to skip the rest of the calculations. So we will branch to the memory location $C879. If the flag is 1, that means we should continue with our checksum progress. The comment on the end here reminds us what happens when we branch to that new location.

; ... else CMPA #$32 ; Ck for correct EPROM

Now we compare what we read with what we expect (remember this is the BUA hac, mask $32).

BNE LC87E? ; If Not exit w/error

This is similar to the BEQ instruction, but in the reverse. We only branch to this memory location if it is NOT equal. It means that we did don't get what we expected, and there was an error read the PROM!

; ... else CPY LC006 ; CK CK SUM VAL

This is where the actual comparision is done. Remember that the checksum routine has previously stored the calculated checksum value in register Y. Now we read the value stored in the PROM in location $C006 which is the value that our bin editing program has generated, and compare it with register Y.

BNE LC87E? ; Bad Ck Sum, Xit w/Error

If they didn't match, we have a problem! Branch to the section of code that will set an error.

; ... else LC879: BCLR L0047,$10 ; Clr Er #51 Flg

According to our memory map, $0047 is the battery backed up ram area. So now we have a clue about how the error flags work in the $32 memory map!

When we have reached this point, it means that the checksum matched. That means that error flag 51 should not be set. This is what the BCLR instruction does. It does this in a few steps:

  • Read the value in $0047
  • Take that value and clear the bit at location $10.

It may be more helpful to look at it as a binary number. Lets say that the value read was $5D, or in binary %01011101. If you don't know how to convert between binary and hex, there are a lot of tutorials on this. You can even use windows calculator.

So we are clearing any bits that match up between $10 and $5C:

 %00010000
 %01011101

See how the 4th bit (remember we start counting from 0) matches up?

The result is

 %01001101

# We now store this resulting number %01001101, or $4D back in memory location $0047.

So now we know that error flag 51 is stored at the 5th bit in memory location $0047. We can assume that the other bits in $0047 are other error flags!

BRA LC881 ; Exit Ck Sum test

Once we get here, we're done, so jump to the rest of the code.

LC87E? : BSET L0047,$10 ; Set Err, (#51)

BSET is the opposite instruction to BCLR. It sets bit 4 in memory location $0047 to 1, and this indicates that the error 51 flag has been set.

= Operation of the Checksum Routine =

The purpose of the checksum routine is to go through a specified address area and add up the values of each stored byte in a memory location, like the PROM. This can then be checked with a predetermined checksum to test for data corruption. The program enters with the start address and the total number of bytes to check and exits with the checksum in the index register Y as well as the origional start address and total number of bytes that where to be read in. The excerpts following are from the BUA with some of my comments.

The first thing that occurs is the declaration of the start address as well as the value to be assigned to COP.

ORG $F32E ; EQU COP $400B ; Watch dog timer

The ORGin command is for the assembler only. It tells where the assembled machine code is to be located in the prom. The EQUals command tells the assembler to place $400B in the actual assembled code whenever it sees 'COP'.

The following are stack pushes of the needed data in the registers:

 LF32E:   
 PSHX       ;Save start addr of checksup
 PSHB      ;A + B equals total number of bytes in chksum area
 PSHA       ;Save num of bytes to stack

The first stack push is the start address of the area to perform the checksum. This value is stored in the X index register. Both its least and most significant byte are pushed onto the user stack in the RAM and the stack pointer, which points to the next free byte in the stack, is de-incremented by two.

In accumulators A and B, the total number of bytes in the checksum area are pushed to the stack. It is important to note that both A and B also make up the double accumulator D, so these two are one and the same. There is no equivelant command to push D onto the stack, so PSHB and PSHA are used. In a stack push of a 16 bit value, the Least Significant Byte goes in behind the Most Significant Byte. The LSB of the checksum area is in B, so its pushed on before A.

 LSRD            ; /2, two bytes per read
 PSHA            ; push MSB to stack
 LDY #0     ; load ind. reg. y with zero to clear
 TBA       ; xfer LSB of #of checksum bytes/2 to A
 BEQ LF348   ;Bra if ==0, no more bytes to load in

The Logical Shift Right accumulator D instruction shifts all the bits to the right. The uppermost bit is loaded with 0. The effect of shifting all the bits one place to the right is to divide the number by two. This is needed because there are two bytes loaded per pass in the next section of code when the actual checksum is calculated. This means that there will be only half as many read-ins as there are actual bytes. The total number of reads can be a 16 bit value, meaning that the MSB is the total number of needed reads x 256 and the LSB is the remainder of the total needed reads. The number of double byte read-ins / 256 is pushed onto the stack and saved for later use. The LSB contains the number of read-ins, up to 255.

The next line is LoaD? index register Y with the following item, which is 0. The # indicates that this is a numerical value that is to be loaded immediatly. Loading zero clears the index register.

The last command above is Transfer accumulator B to accumulator A. This command takes the contents of B and copies it into A, essentially moving the value from B to A. This value that was moved is the number of two byte read-ins needed for this pass through. This is done to free up accumulator B for other tasks. If the value that was moved is equal to zero, the Z(ero) bit in the condition register is set to one.

The final instruction is Branch if EQual to zero. This tells the computer to branch to the address after the BEQ command if the value moved into A is equal to zero (in other words, branch if the Z bit was set. If this is equal to zero, then there are no more read-ins for this pass and the computer is to jump to that address.

The following is the actual loop that reads in the pairs of bytes and stores them:

 LF33A:    
 LDAB 0,X    ;Load first byte from address pointed to by X
 ABY       ;add it in
 LDAB 1,X    ;load next byte
 ABY       ;add it in
 LDAB #2      ;2 bytes, move to next address
 ABX      ;Point to next pair of bytes
 DECA       ;-1 from # of reads
 BNE LF33A   ;bra if !=0, continue

Remember the index register X? It had the start address of the checksum area in it. This is used in the idexed loads. The first command, LoaD? Accumulator B loads B with the contents of the address pointed to by X. The number before X is the offset, or the number of bytes to read ahead of X. In this case, its equal to zero, meaning just read in whats at the current address. The Add B to Y is add whatever is in B to index register Y. Normally this type of instrucion is used for changing the current address in Y. Say we were at 0xD555 and we added 1 to that using the ABY command, Y would now point to address 0xD556. But in this case, its just used to store the checksum. This type of instruction will cause the Y register to overflow numerous times, but thats not important as any change in one of the bytes or bits due to corruption will show in the final checksum result.

The next command is load B with whats pointed to by X + 1. The one in front of the X tells the computer to offset by one and load the following address. This too is added into the result in Y.

The load B command is used to load an address offset of two into B. This is because we're reading in two bytes at a time, so we need to skip ahead two bytes in the memory to get to the next pair of bytes. In the Add B to X instruction, the offset is added into X. Its important to note that there is no similar instruction for A! This is why B had to be freed up earlier. Next, The DECrement A instruction subtracts one from the number of read-ins that need to be done since we just read in a pair of bytes. The Branch if Not Equal instruction tells the computer to branch to the address that follows if A wasnt equal after it was decremented. This means that the result of the DECA operation wasnt equal to zero. IOW, we still have more pairs of bytes to read in.

 LF348:   
 PSHX       ; Save current Address
 ;
 ;-Do computer operating properly
 ;
 LDX #$FF00    ; COP Time Value
 STX COP    ; Toggle COP (every 512 bytes)

After the comptuer has read in the prescribed number of byte pairs, the Computer Operating Properly timer must be serviced. The COP is there in case the CPU freezes up. This is like the reset button on your PC. If the ECM suddenly gives the 'blue screen of death' and locks up, the COP will reset it. The COP hardware knows to reset when the comptuter stops operating because that value hasnt been loaded in a timely fashion. Conversly, the COP must be serviced within a certain timeframe or the computer will be reset by the COP.

It may seem unusual to have this here since it seems like a short routine, but its not! The checksum routine can take close to 900,000 processor cycles to execute! Thats a long time and if the COP wasnt serviced, the computer would reset before the routine finished.

The next set of instructions can be a bit confusing...

 TSX      ; load in the stack pointer
 DEC 2,X    ; deincrement the MSB of stored reads that need to be performed 
 PULX       ; get the last address in
 BPL LF33A   ; Bra if still need to perform reads

Here, the stack is used as the working area for the operations. The first command, Transfer Stack pointer to X, transfers the pointer that points to the current free byte to be filled in the stack. All the previous values that have been stored in the stack are at higher addresses behind the address pointed to by the stack pointer. The command DEC decrements, or subtracts one, from the value in the stack that is two bytes behind the free byte in the stack. Lets look at the order of what was pushed on the stack:

The last thing pushed on was the current address that the computer was at when it last performed the two byte read-in and added it into the index reg. Y.

Before that, the MSB of the total number of read-ins was pushed onto the stack. This is the value that is having one subtracted from it. This is becasue we are about to perform another 256 two byte read-ins and we must subtract 256. Since the value on the stack was # of read-ins / 256, we only need to subtract one from that, which is 256 read-ins.

Finally, after that value has been manipulated, the Last address to be checked is loaded back into X from the stack. The Bra if PLus refers to the DEC command. A stack pull will not set any condition codes. The command before it, DEC, will. If the value on the stack that was decremented is 0 or some number greater, then we still have more addresses to read in. If this is so, the computer branches to do the read-in loop all over again and adds those values to the index register Y. If not, no branch takes place. Its also important to note that:

DECA ;-1 from # of reads

will reset A to 255. Since A was equal to 0 before, subtracting one from it will cause an underflow, which will set all the bits in A to binary value 1.

Whew... finally we're done when we get to this part.

 INS       ; Discard MSB of the read-in counter
 PULA       ; Restore A, B to previous values
 PULB      ; 
 PULX      ; Restore X with its previous value   
 RTS       ; Return with the checksum in Y

The stack pointer is incremented by one, which points to that read-in counter that had the number of needed two byte read-ins / 256. We dont need this anymore so it will be overwritten the next time the stack is used. Not only that, but the address that the computer was at before it jumped to execute this routine and other info is stored behind it. We need to get that back so it will be pointed to instead. The stack pulls restore the registers to whatever value they had when we entered the routine. Finally the ReTurn? from Subtroutine brings the computer back to the location after the origional Jump to SubRoutine? command. Remember that we havnt touched the index register Y, so it still contains the checksum.

= Summary =

Reading values, comparing them, then branching is a very common thing that happens in assembly. Here is what happened from a slightly higher view:

Calcuate the checksum Do some other processing IF the mask id is $AA don't do any checked ELSE IF we are NOT using the right mask ID Go to the code that sets an error ELSE IF the calculated sum does NOT match what was read Go to the code that sets an error ELSE Clear the error flag Continue to the rest of the initialization Set the error flag The rest of the initialization

-- AlexHarford - 30 Jun 2006

Topic revision: r1 - 30 Jun 2006 - 23:00:30 - AlexHarford
 
This site is powered by the TWiki collaboration platformCopyright © by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback