CPSC 355: Tutorial 6

Binary Arithmetic

PhD Student

Fall 2017

Outline

  • Assignment 1 breakdown
  • Errata for last tutorial
  • Binary Addition
  • Binary Multiplication
  • Tips for Assignment 2

Assignment 1

Generally very well done : )

A handful of students missed the pre-test loop with a comparison at the bottom of the loop for part b.

            // setup initial regiters here...
            b loop_test                     //branch to loop test    

loop_top:   // compute -4x^3 -27x^2 + 5x + 45
            // check if max etc...
            // increment x_r
    

loop_test:  cmp x_r, 6                      //test x with 6
            b.lt loop_top                   //go to top of loop
    

Assignment 1

.global and .balign statements are misunderstood

fmt_string: .string "String 1"
            .global main
            .balign 4
fmt_string2:.string "String 2"
            .global main
            .balign 4

main:
    // code goes here
fmt_string: .string "String 1"
fmt_string2:.string "String 2"

            .global main
            .balign 4

main:
    // code goes here

What this should look like

Bitwise Shifts

It's also often useful to shift bits left and right in a register, for this we have >> (shift right) and << (shift left). Bits that exceed the width of the register are dropped

    1001 >> 1
--------
    0100

Example

Shift left can be used as a fast "multiply by 2" and shift right can be used as a fast "integer divide by 2" 

    1001 << 1
--------
    0010

Bitwise Shifts

#include <stdio.h>

void main(int argc, char *argv[]) {
    unsigned int a = 0b1100;
    unsigned int b = 0b1001;

    printf("a=%d, b=%d\na >> 2 = %d, b << 2 = %d\n", a, b, a >> 2, b << 2);
}
fmt: .string "a=%d, b=%d\na >> 2 = %d, b << 2 = %d\n"
    .balign 4
    .global main

main:
    stp  x29, x30, [sp, -16]! 
    mov  x29, sp     
    ldr  x0, =fmt           
    mov  x1, 0b1100
    mov  x2, 0b1001
    lsr  x3, x1, 2
    lsl  x4, x2, 2
    bl printf 

    ldp x29, x30, [sp], 16  
    ret 

Bitwise Shifts

For signed quantities, it is often important to keep the sign bit, this is what an arithmetic shift does

    1001 >> 1
--------
    1100

Example

Shift left can be used as a fast "multiply by 2" and shift right can be used as a fast "integer divide by 2" 

Bitwise Shifts

#include <stdio.h>

void main(int argc, char *argv[]) {
    int a = -1; //0b1111...111 in binary
    int b = -1; //

    printf("a=%d, b=%d\na >> 1 = %d, b << 1 = %d\n", a, b, a >> 1, b << 1);
}
fmt: .string "a=%d, b=%d\na >> 2 = %d, b << 2 = %d\n"
    .balign 4
    .global main

main:
    stp  x29, x30, [sp, -16]! 
    mov  x29, sp     
    ldr  x0, =fmt           
    mov  w1, -1
    mov  w2, -1
    asr  w3, w1, 1
    lsl  w4, w2, 1 // LSL is an Arithmetic shift
    bl printf 

    ldp x29, x30, [sp], 16  
    ret 

Arithmetic in binary

As it turns out, all the binary operations shown up until now are easy to implement in transistor logic. 

With a few transistors it's easy to build an AND, OR, XOR operator -- but how do we add numbers? 

Basically, we have to figure out how to use binary operations to perform arithmetic

Addition in binary

Let's start with an 8-bit example, binary addition works the same as base-10 addition

00001100
00001100 00001100
+ 00001010
+00001010+ 00001010
0
00
1
11
0
00
0
00
1
11
0
00
0
00
1
11
1
11
0
00
0
00
0
00
0
00
0
00
0
00

Addition in binary

Moving on to a more general case

c_4
c4c_4
c_1
c1c_1
c_3
c3c_3
c_5
c5c_5
c_2
c2c_2
c_6
c6c_6
c_7
c7c_7
b_4
b4b_4
b_1
b1b_1
b_3
b3b_3
b_5
b5b_5
b_2
b2b_2
b_6
b6b_6
b_7
b7b_7
b_0
b0b_0
a_4
a4a_4
a_1
a1a_1
a_3
a3a_3
a_5
a5a_5
a_2
a2a_2
a_6
a6a_6
a_7
a7a_7
a_0
a0a_0
+
++
r_4
r4r_4
r_1
r1r_1
r_3
r3r_3
r_5
r5r_5
r_2
r2r_2
r_6
r6r_6
r_7
r7r_7
r_0
r0r_0

As it turns out, we can very easily express the results and carries

r_i = a_i \hat{} b_i \hat{} c_{i}
ri=ai^bi^cir_i = a_i \hat{} b_i \hat{} c_{i}
c_i = ((a_i \hat{} b_i) \& \ c_{i-1}) | (a_i \& \ b_i)
ci=((ai^bi)& ci1)(ai& bi)c_i = ((a_i \hat{} b_i) \& \ c_{i-1}) | (a_i \& \ b_i)

Assuming

c_0 = 0
c0=0c_0 = 0

This was c_i-1 in the video, but it should be c_i

Addition in binary

#include <stdio.h>

int main() 
{
	unsigned int a, b, result = 0;
	unsigned int carry = 0, i = 0;

	a = 100;
	b = 123;

	printf("%d + %d = \n", a, b);
	for(i = 0; i < 32; i++) 
        {
		int ai = a & 0x1;
		int bi = b & 0x1;

		result = result >> 1;
		if(ai ^ bi ^ carry) {
			result |= 0x80000000;
		} else {
			result &= 0x7FFFFFFF;
		}

		carry = ((ai ^ bi) & carry) | (ai & bi);
		a = a >> 1;
		b = b >> 1;
	}
	printf("%d\n", result);
}

Addition in binary

#include <stdio.h>

int main() 
{
	unsigned int a, b, result = 0;
	unsigned int carry = 0, i = 0;

	a = -100; // <---- This is now negative
	b = 123;

	printf("%d + %d = \n", a, b);
	for(i = 0; i < 32; i++) 
        {
		int ai = a & 0x1;
		int bi = b & 0x1;

		result = result >> 1;
		if(ai ^ bi ^ carry) {
			result |= 0x80000000;
		} else {
			result &= 0x7FFFFFFF;
		}

		carry = ((ai ^ bi) & carry) | (ai & bi);
		a = a >> 1;
		b = b >> 1;
	}
	printf("%d\n", result);
}

Multiplication in binary

Let's do another example, with an unsigned 4-bit number. Unsurprisingly, this works like base-10 arithmetic

1101
11011101
\times 0101
×0101\times 0101
1101
11011101
0000
00000000
1101
11011101
0000
00000000
+
++
1000001
10000011000001

Multiplication in binary

This is a little trickier to write out a recurrence relation between bits as we did before, but it's easy to see how to write an algorithm that does this.

1101
11011101
\times 0101
×0101\times 0101
1101
11011101
0000
00000000
1101
11011101
0000
00000000
+
++
1000001
10000011000001

Multiplication in binary

#include <stdio.h>

int main() 
{
	int multiplier, multiplicand, result_high, result_low, i, negative;
	long int result, temp1, temp2;

	multiplicand = -8192;
	multiplier = 99;

	result_high = 0;
	result_low = 0;

	printf("multiplier = 0x%08x (%d) multiplicand = 0x%09x (%d) \n\n",
		multiplier, multiplier, multiplicand, multiplicand);

	negative = multiplier < 0;

	for(i = 0; i < 32; i++) {
		if(multiplier & 1) {
			result_high = result_high + multiplicand;
		}

		multiplier = multiplier >> 1;
		result_low = result_low >> 1;

		if(result_high & 1) {
			result_low = result_low | 0x80000000;
		} else {
			result_low = result_low & 0x7FFFFFFF;
		}
		result_high = result_high >> 1;
	}

	if(negative) {
		result_high = result_high - multiplicand;
	}

	printf("result_high = 0x%08x, result_low = 0x0%08x\n", result_high, result_low);

	temp1 = (long int) result_high;
	temp1 = temp1 << 32;
	temp2 = (long int) result_low & 0xFFFFFFFF;
	result = temp1 + temp2;

	printf("64-bit result: 0x%016lx (%ld)\n", result, result);
}

Sign extension


	temp1 = (long int) product;
	temp1 = temp1 << 32;
	temp2 = (long int) multiplier & 0xFFFFFFFF;
	result = temp1 + temp2;

Problem: temp1 is a long register (Xn) and product is a word register (Wd).

Luckily, there's an instruction that keeps the value of a word length register, and extends its sign bit

    sxtw  temp1, product // Sign extend product, and put it in temp1

Tips for Debugging

for(i = 0; i < 32; i++) {
	if(multiplier & 1) {
		product += multiplicand;
	}
        
	multiplier = multiplier >> 1;

	if(product & 1) {
		multiplier = multiplier | 0x80000000;
	} else {
		multiplier = multiplier & 0x7FFFFFFF;
	}
	product = product >> 1;
        // print out product and multiplier every iteration of the loop
        printf("product = 0x%08x, multiplier = 0x0%08x\n", product, multiplier);
}

CPSC 355: Tutorial 6

By Joshua Horacsek

CPSC 355: Tutorial 6

  • 1,528