CPSC 355: Tutorial 11

Review

PhD Student

Fall 2017

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

Logical Shift Right, let's assume a 4 bit register shift by one

1 1 0 1
0 1 1 0

Ignored

0

Appropriate for unsigned numbers, signed numbers need an arithmetic shift

Bitwise Shifts

Arithmetic Shift Right, let's assume a 4 bit register shift by one

1 1 0 1
1 1 1 0

Ignored

Keeps the sign of the number

Bitwise Shifts

Logical Shift Left, let's assume a 4 bit register shift by one

1 1 0 1
1 0 1 0

Ignored

0

No such thing as arithmetic shift

Bitwise Shifts

#include <stdio.h>

void main(int argc, char *argv[]) {
    char a = 0b11111100;
    unsigned char 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, 0b11111100
    mov  x2, 0b1001
    asr  x3, x1, 2 // This is because the first variable is signed
    lsr  x4, x2, 2 // This is because the second variable is signed
    bl printf 

    ldp x29, x30, [sp], 16  
    ret 

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

0 + 0 = 0
0+0=00 + 0 = 0
0 + 1 = 1
0+1=10 + 1 = 1
1 + 1 = 10
1+1=101 + 1 = 10
1 + 1 + 1 = 11
1+1+1=111 + 1 + 1 = 11

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

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; // get the first bit
		int bi = b & 0x1;

		result = result >> 1;
		if(ai ^ bi ^ carry) {
			result |= 0x80000000; // set the last bit of result
		} else {
			result &= 0x7FFFFFFF; // clear last bit of result
		}

                // Figure out what the carry should be
		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);
	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;
	}

	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);
}

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);
}

Rounding

What does it mean to be divisible by two in binary?

2_{10} = 10_2
210=1022_{10} = 10_2

Divisibly by 2 if the lowest bit is 0

4_{10} = 100_2
410=10024_{10} = 100_2

What does it mean to be divisible by four in binary?

Divisibly by 4 if the lowest two bits are 0

Rounding Trick

stp lr, fp [sp, -size & -16]!

What's -16 in binary?

16_{10} = 10000_2
1610=10000216_{10} = 10000_2
-16_{10} = (\text{NOT } 10000_2) + 1_2
1610=(NOT 100002)+12-16_{10} = (\text{NOT } 10000_2) + 1_2
-16_{10} = 11101111_2 + 1_2
1610=111011112+12-16_{10} = 11101111_2 + 1_2
-16_{10} = 11110000_2
1610=111100002-16_{10} = 11110000_2

2's complement

Assuming 8bits

Rounding Trick

stp lr, fp [sp, -size & -16]!

What's let's take -size = 4?

4_{10} \ \& -16_{10} = 00000100_2 \& 11110000_2
410 &1610=000001002&1111000024_{10} \ \& -16_{10} = 00000100_2 \& 11110000_2
00000100
00000100 00000100
\& 11110000
&11110000\& 11110000
00000000
00000000 00000000

Rounds downwards to the nearest integer divisible by 16

Rounding Trick

stp lr, fp [sp, -size & -16]!

What's let's take -size = 4?

-1_{10} \ \& -16_{10} = 11111111_2 \& 11110000_2
110 &1610=111111112&111100002-1_{10} \ \& -16_{10} = 11111111_2 \& 11110000_2
11111111
11111111 11111111
\& 11110000
&11110000\& 11110000
11110000
11110000 11110000

Rounds downwards to the nearest integer divisible by 16

The Stack

OS
Program

Heap
 

Free memory
 

Stack
 

low

high

Used to store local variables and arrays whose sizes are known at run time


local vars,
return addrs,
frame pointers
etc...
 

The Stack

void main(int argc, char *argv[]) {
   char sum;
   unsigned short product;
   
   // more code...
}

The Stack

Let's tally what we need to store on the stack

  • old lr and fp (16 bytes)
  • sum (1 byte)
  • product (2 bytes)
stp  x29, x30, [sp, -(16 + 1 + 2)  & -16]!
mov  x29, sp

That's a total of 19 bytes

The Stack

high


Free memory
 
old stack data

 $sp

Free memory
x29 ($fp)
x30 ($lr)
???
???
old stack data

 $sp

$x29

The Stack

high


Free memory
 
old stack data

 $sp

Free memory
x29 ($fp)
x30 ($lr)
sum, product
padding
old stack data

 $sp

$fp

Sum is at fp+ 16, product is at fp+16+1

Choose a layout for variables on the stack

Next Day

Structs and Accessing Struct Members

CPSC 355: Tutorial 11

By Joshua Horacsek

CPSC 355: Tutorial 11

  • 1,514