A project log for Suite-16

Suite-16 is a 16-bit cpu built entirely from TTL. It is a personal exploration of how hardware and software interact.

monsonitemonsonite 10/24/2019 at 11:580 Comments

Here's the second major routine needed to make working with decimal numerical input possible.

Get_Number parses through the text input buffer looking for numerical ascii codes that fall in the range 0x30 to 0x39.  

These correspond to the digits 0 to 9, so the first thing you must do is subtract 0x30 (decimal 48) from the incoming character to turn it into a decimal digit. It is convenient to preload one of the working registers with 0x30 (and keep it there) and in this application I choose R3 to contain 0x30.

Once you have your first decimal digit in the accumulator, you need to multiply it by 10. Then you take your second digit, add it to the sum in the accumulator and multiply by 10 again. You continue this process until you have added the last digit to the total, and the accumulator will hold the decimal equivalent of the numerical string in the text buffer.

However, you have to know where to stop multiplying by 10, so that the last digit in the string is just added . To do this you have to continuously look ahead at the next character in the input buffer and determine if it is a numerical character or not.  If it's not a number, you skip the times 10 multiplication and jump to a final addition.

Here is the resulting code - hand assembled for Suite -16.

     // 0x0090 --------------------------------NUMBER------------------------------------- 

        0x1300,     // SET R3 0x30   Preload R3 with decimal 48
        0x1200,     // SET R2, 0x0A  Preload R2 with decimal 10
        0x1100,     // SET R1, 0x0200    text buffer start
        0x4100,     // LD AC, @R1  get first character from buffer
        0x3400,     // Store R0 in R4
        0xE100,     // INC R1
        0x4100,     // LD AC, @R1  get next character - and test to see if it is a number
        0xB300,     // Subtract R3  Is it bigger than 0x30?
        0x02AA,     // BLT Not a Number
        0xB200,     // Subtract R2  0x0A
        0x03AA,     // BGE Not a Number
        0x2400,     // Get original character  R0 back from R4
        0xB300,     // Subtract R3  to form a digit

     // 0x00A0 --------------------------------------------------------------------- 
        0xA700,     // get the accumulating total  from R7 - ready to multiply
        0x3500,     // Store R0 in R5  Digit is now in R5
        0xA500,     // ADD R5  Accumulator R0 = (2 * digit)  
        0x3600,     // Store R0 in R6  - this is the "Times 10" routine
        0xA600,     // ADD R6   4 X
        0xA600,     // ADD R6   6 X        
        0xA600,     // ADD R6   8 X 
        0xA600,     // ADD R6   10 X 
        0x3700,     // Store R0 in R7   R7 is accumulation of all the digits multiplied by powers of 10        
        0x0096,     // BRA 0x0096 Get the next digit
        0x0F00,     // Not a number  address = 0xAA
        0x2400,     // Get last digit  R0 back from R4
        0xB300,     // Subtract R3
        0xA700,     // Add the accumulated sum from R7 - decimal number is now it R0
        0x1700,     // Don't forget to clear R7

     // 0x00B0 --------------------------------------------------------------------- 
        0x0802,     // CALL 0x0002  PRINTNUM
        0x0000,      // BRA START

Incidentally, a functionally similar routine coded in MSP430 assembly language took 25 16-bit words (although in MSP430 ASM it's only 16 instructions). I'm quite happy with this implementation by comparison - as I use 8 words to initialise registers.

One thing that would be useful in the instruction set - would be immediate operations on the accumulator with the short-constant held in the Payload-byte.

There are several occasions when handling ascii characters, that you want to test against some value and modify the contents of the accumulator.  Being able to add or subtract an 8-bit literal from the accumulator without having to use another register would be a big advantage.

Whilst some might think that hand-assembly must be some form of mental masochism, it's actually not that difficult.

Actually writing the code takes surprisingly little time.  What does take the time is thinking how to write the algorithm efficiently using the instructions and registers available, and the time spent recompiling and testing the code.

You will see that I am listing my code in little blocks of 4 instructions, with a commented dividing line between every 16 instructions. 

This allows me to arrange the code into easily manageable blocks and keep tally of exactly what address the instruction will be coded at.

Routines must for the moment be placed at fixed addresses ( non-relocatable) as I only have absolute branching.  It's a minor inconvenience for the moment, and it will probably be changed when I revisit the branching mechanism.