Lecture 4
Programming in C and Assembler
Ingo Sander ingo@kth.se
Outline
Programming in C ! Programming with the HAL Library
!
IL2206 Embedded Systems
Programming in C
The programming language C
C has been developed as a systems programming language around 1970 ! C is a sequential imperative language ! C is often viewed as a macro-assembler , since most C-statements can efficiently be mapped on the instruction set of a von Neumann processor ! Java has inherited the large parts of the Csyntax, but is a more abstract language
!
IL2206 Embedded Systems 4
The programming language C
!
C has been designed with respect to an efficient implementation, unnecessary features have been left out
!
! !
There is no Boolean variable, integers are used instead of True and False (0 is False, all other integers correspond to True) There is no garbage collection Programmer can access memory locations that are not declared ...
IL2206 Embedded Systems 5
The programming language C
The C-programmer has a huge responsibility! ! C-code can be very fast ! BUT it is easy to produce bugs, which are not caught by the compiler
!
! ! ! !
Memory leaks Accessing wrong memory locations Assignment instead of comparison ...
IL2206 Embedded Systems 6
Be extremely careful, when you program with C!
Many C-books in the library!
!
If you do not feel very comfortable with C!
There are many books on C in the library! ! The course page gives recommendations
!
IL2206 Embedded Systems
Data Structures in Memory
!
In order to design an efficient program it is often important to know how data structures are located in memory
!
Number of cache misses can often be reduced
IL2206 Embedded Systems
Arryas in memory
An array is located as a sequence in the memory int x = 5; int y = 6; int a[5];
!
0x80 0x84 0x88 0x8C 0x90 0x94 0x98 5 6 x y a[0] a[1] a[2] a[3] a[4]
IL2206 Embedded Systems
Pointers
!
A pointer holds an address in the memory
!
0x80 0x84 0x88 0x8C 0x90 0x94 0x98
5 6
x y a[0] a[1] a[2] a[3] a[4]
A pointer points to a variable
int int int int
x = 5; y = 6; a[5]; *intpointer;
intpointer
10
IL2206 Embedded Systems
Pointers
/* Point to x */ intpointer = &x; /* Set x to 0 */ *intpointer = 0;
0x80 0x84 0x88 0x8C 0x90 0x94 0x98 0 6 x y a[0] a[1] a[2] a[3] a[4]
0x80
IL2206 Embedded Systems
intpointer
11
Pointers and Arrays
An array is located as a sequence in the memory ! Pointers can be used to access array elements /* Point to first array element */ intpointer = a;
!
0x80 0x84 0x88 0x8C 0x90 0x94 0x98
0 6
x y a[0] a[1] a[2] a[3] a[4]
0x88
IL2206 Embedded Systems
intpointer
12
Pointers and Arrays
intpointer = a; is equivalent to intpointer = &a[0]; intpointer = a + 1; is equivalent to intpointer = &a[1];
!
0x80 0x84 0x88 0x8C 0x90 0x94 0x98
0 6
x y a[0] a[1] a[2] a[3] a[4]
Pointer is incremented the size of its datatype (here one word)
0x88
IL2206 Embedded Systems
intpointer
13
Pointers and Arrays
Multidimensional arrays are stored as a sequence of elements, where the rightmost subject (here the column) varies fastest #define ROW 2 #define COL 3 int a[ROW][COL]; ! Pointers can be used to access the array intpointer = a + 4; *intpointer = 27;
!
0x80 0x84 0x88 0x8C 0x90 0x94 0x98 27
a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]
0x90
intpointer
14
IL2206 Embedded Systems
Refreshing C Bitwise Operators
! ! ! ! !
Bitwise complement (~) Bitwise and (&) Bitwise exclusive or (^) Bitwise inclusive or (|) Left shift (<<)
!
0s are shifted in for unsigned data types 0s are shifted in for signed data types the sign bit is shifted in
IL2206 Embedded Systems 15
Right shift (>>)
! !
Refreshing C
Relational, Equality and Logical Operators
Relational and Equality
! ! !
Logical
! ! !
! !
Less than (<) Greater than (>) Less than or equal (<=) Greater than or equal (>=) Equal to (==) Not equal to (!=)
Negation (!) Logical and (&&) Logical or (||)
IL2206 Embedded Systems
16
Storage Classes
!
A variable in C has two attributes:
! !
type (int, char, float, double, !) storage class (auto, extern, register, static)
IL2206 Embedded Systems
17
Storage Class auto
! !
Default storage class for variables declared in a function An automatic variable is only visible in the block it is declared in
!
System allocates memory when entering the block System releases the memory when leaving the block
!
Variable value is lost when leaving the block
int x; /* storage class extern */ int f(int x) { /* both a and b are */ /* of storage class auto */ int a; auto int b; /* a, b are visible here */ /* x is visible here */ } /* a, b are not visible here */ /* x is visible here */
18
IL2206 Embedded Systems
Storage Class extern
!
Default storage class for variables declared outside a function An extern variable is visible in the whole file after its declaration (global variable)
!
System allocates permanent storage for the variable
int x; /* storage class extern */ int f(int x) { /* both a and b are */ /* of storage class auto */ int a; auto int b; /* a, b are visible here */ /* x is visible here */ } /* a, b are not visible here */ /* x is visible here */
19
IL2206 Embedded Systems
The keyword extern
!
The keyword extern is used to tell the compiler to look for the variable or function elsewhere
! !
File1.c int a; File2.c int f(int x) { /* look for a outside */ /* this function */ extern int a; x = a; }
20
in another file in the same file (seldomly used)
Linkers task to combine the different object files to one executable
IL2206 Embedded Systems
Storage Class register
!
register storage class may only be declared inside functions Behaves in the same way as auto, but gives an additional recommendation to compiler that variable should be placed in register Compiler does not have to follow this advice
!
int f() { register int i; for(i=0; i < 1000; i++) a[i] = i; }
Number of registers is limited in CPU
IL2206 Embedded Systems
21
Storage Class static
!
In contrast to auto variables, static variables retain their values
!
permanent storage is assigned to the variable
int counter(void) { static int i = 0; i++; return i; }
IL2206 Embedded Systems
22
The type qualifier volatile
!
The type qualifier volatile indicates that a variable can be changed by other parts of the hardware in the system
!
Variable should not be put in register Variable can still be put in register or cache! Other directives (HAL functions) have to be used to avoid cache coherence problem
IL2206 Embedded Systems 23
Compiler may ignore volatile declaration
! !
More discussion in lecture on acceleration
If memory size is a constraint
!
Several small variables can be packed into a single variable
! ! !
Set of masks Bit-fields Unions
IL2206 Embedded Systems
24
If memory size is a constraint
Set of Masks #define FLAG1 1 #define FLAG2 2 #define FLAG3 4 char flags; /* contains all flags */ if ((flags & (FLAG1 | FLAG3)) == 0) /* True if both flags are 0 */
!
IL2206 Embedded Systems 25
If memory size is a constraint
!
A bit-field is a packed representation, where several small variables are integrated into one variable
struct myfield { unsigned var1 : 4; /* values from 0 to 15 */ int var2 : 3; /* values from -4 to 3 */ } x; x.var1 = 6;
Implementation of bit-field is machine dependent!
IL2206 Embedded Systems 26
If memory size is a constraint
! !
A union defines a set of alternative values that can be stored in the same portion of memory Programmer has responsibility to keep track of the data type stored in a union The size of the union corresponds to the largest data type
union int_or_float { int i; float f; } u;
IL2206 Embedded Systems
27
If memory size is a constraint
int main(void) { u.i = 0; printf("i: %10d f: %16.10e\n", u.i, u.f); u.i = 11; printf("i: %10d f: %16.10e\n", u.i, u.f); u.f = 11; printf("i: %10d f: %16.10e\n", u.i, u.f); return 0; } Output i: 0 f: 0.0000000000e+00 i: 11 f: 1.5414283108e-44 i: 1093664768 f: 1.1000000000e+01
IL2206 Embedded Systems 28
The sizeof operator
As discussed earlier the sizes of C-datatypes are not standardized ! The sizeof operator can be used to print the sizes of each type
!
printf("Size int*:%3u\n, sizeof(int*));
IL2206 Embedded Systems
29
The sizeof operator
!
Sizes of data types on teachers Linux machine
! ! ! ! ! ! !
char : 1 short : 2 int : 4 long : 8 float : 4 double: 8 int*: 8
(64 bit address space!)
IL2206 Embedded Systems
30
Many C-books in the library!
!
If you do not feel very comfortable with C! There are many books on C in the library!
IL2206 Embedded Systems
31
Nios II Programming with the HAL library
Busy Wait I/O
!
Busy Wait I/O is the most basic way to communicate with an I/O-device The processor wait until the I/ O-device has completed its current task Disadvantage: Processor cannot be used for other tasks during the waiting period! This method is also often called polling!
Example: Sending string via serial link
!
Busy Wait I/O Pseudo Code:
Characters = String; While not all characters sent Send next character; While Sender = Busy Wait; Done!
IL2206 Embedded Systems
33
C-Programming Testing of Bits
In order to test specific bits, it is needed to mask the other bits Example: ! Busy Flag: Busy = 1; Non-Busy = 0
!
7 0x1000 0x1001
IL2206 Embedded Systems
5 BF
0 Status Sender Sender Buffer
34
C-Programming Testing of Bits
define Status 0x1000 define Sender 0x1001 char *myString = Hello World; char *current_char;
7 0x1000 0x1001
IL2206 Embedded Systems
5 BF
0 Status Sender Sender Buffer
35
Accessing Memory Locations in C
Symbolic names can be defined for memory locations #define MEM_LOCATION 0x18 ! Functions can be defined to access memory
!
peek can be used to read a memory location (byte) char peek(char *location) {return *location;} ! poke can be used to write to a memory location (byte) void poke(char *location, char newval) {*location = newval;}
!
IL2206 Embedded Systems 36
Accessing Memory Locations in C
Symbolic names can be defined for memory locations #define MEM_LOCATION 0x18 ! Functions can be defined to access memory
!
!
peek can be used to read a memory location (byte) char peek(char *location) Altera provides the {return *location;} HAL library that abstracts from the underlying location (byte) ! poke can be used to write to a memoryhardware! void poke(char *location, char newval) {*location = newval;}
IL2206 Embedded Systems 37
Dont do this!
C-Programming Testing of Bits
Here you should use HAL functions!
while (current_char != \0) { poke(Sender, *current_char++); while ((peek(Status) & 0x20) != 0) ; } /* Mask needed, since other bits */ /* in status register may not be zero */
7 0x1000 0x1001
IL2206 Embedded Systems
5 BF
0 Status Sender Sender Buffer
38
Memory Locations shouldnt be accessed directly!
!
Software shall be flexible
!
Hardware could change
Programmers may make mistakes that the compiler would not do (e.g. memory alignment) HAL (Hardware Abstraction Layer) offers optimized device drivers to access peripheral devices and memory
IL2206 Embedded Systems 39
Customizing a Nios II Core The Tool SOPC Builder
!
Designer can select and configure
! !
CPU(s) peripherals
SOPC builder creates core that can be instantiated on FPGA
Symbolic names are accessible for software designers!
IL2206 Embedded Systems
40
The role of the HAL in a Nios II project
Your program uses the symbolic addresses and values specified in SOPC builder
Reference: Nios II Software Developers Handbook, Chapter 6
IL2206 Embedded Systems 41
The role of the HAL in a Nios II project
Your program uses the symbolic addresses and values specified in SOPC builder
Reference: Nios II Software Developers Handbook, Chapter 6
IL2206 Embedded Systems 42
The system.h file
The system.h file provides a complete software description of the Nios II system hardware ! The system.h file reflects the actual Nios II hardware, which is given by the *.ptf file. ! A new core means a new *.ptf file and a new system.h file
!
IL2206 Embedded Systems
43
The system.h file
!
The system.h file describes each peripheral and provides the following details:
! ! ! !
The hardware configuration of the peripheral The base address The IRQ (interrupt request) priority A symbolic name for the peripheral
If the hardware changes, the source code is still valid, since it uses another correct system.h file
NEVER edit the system.h file!!!
IL2206 Embedded Systems 44
Example of a system.h file
/* * system configuration * */ #define #define #define #define #define #define #define #define #define #define ALT_SYSTEM_NAME "std_2s60ES" ALT_CPU_NAME "cpu" ALT_CPU_ARCHITECTURE "altera_nios2" ALT_DEVICE_FAMILY "STRATIXII" ALTERA_NIOS_DEV_BOARD_STRATIX_2S60_ES ALT_STDIN "/dev/jtag_uart" ALT_STDOUT "/dev/jtag_uart" ALT_STDERR "/dev/jtag_uart" ALT_CPU_FREQ 50000000 ALT_IRQ_BASE NULL
IL2206 Embedded Systems 45
Example of a system.h file
/* * processor configuration * */ #define NIOS2_CPU_IMPLEMENTATION "fast" #define NIOS2_ICACHE_SIZE 4096 #define NIOS2_DCACHE_SIZE 2048 #define NIOS2_ICACHE_LINE_SIZE 32 #define NIOS2_ICACHE_LINE_SIZE_LOG2 5 #define NIOS2_DCACHE_LINE_SIZE 4 #define NIOS2_DCACHE_LINE_SIZE_LOG2 2 #define NIOS2_FLUSHDA_SUPPORTED #define NIOS2_EXCEPTION_ADDR 0x01000020 #define NIOS2_RESET_ADDR 0x00000000 #define NIOS2_HAS_DEBUG_STUB #define NIOS2_CPU_ID_SIZE 1 #define NIOS2_CPU_ID_VALUE 0
IL2206 Embedded Systems 46
Example of a system.h file
/* * uart1 configuration * */ #define #define #define #define #define #define #define #define #define #define #define #define #define #define UART1_NAME "/dev/uart1" UART1_TYPE "altera_avalon_uart" UART1_BASE 0x02120840 UART1_IRQ 4 UART1_BAUD 115200 UART1_DATA_BITS 8 UART1_FIXED_BAUD 1 UART1_PARITY 'N' UART1_STOP_BITS 1 UART1_USE_CTS_RTS 0 UART1_USE_EOP_REGISTER 0 UART1_SIM_TRUE_BAUD 0 UART1_SIM_CHAR_STREAM "" UART1_FREQ 50000000
IL2206 Embedded Systems 47
Example of a system.h file
/* * button_pio configuration * */ #define #define #define #define #define #define #define #define #define #define #define #define #define BUTTON_PIO_NAME "/dev/button_pio" BUTTON_PIO_TYPE "altera_avalon_pio" BUTTON_PIO_BASE 0x02120860 BUTTON_PIO_IRQ 2 BUTTON_PIO_HAS_TRI 0 BUTTON_PIO_HAS_OUT 0 BUTTON_PIO_HAS_IN 1 BUTTON_PIO_CAPTURE 1 BUTTON_PIO_EDGE_TYPE "ANY" BUTTON_PIO_IRQ_TYPE "EDGE" BUTTON_PIO_DO_TEST_BENCH_WIRING 1 BUTTON_PIO_DRIVEN_SIM_VALUE 0x000F BUTTON_PIO_FREQ 50000000
IL2206 Embedded Systems 48
Example: Using the HAL
!
The Parallel I/O devices can be accessed by functions that are defined in altera_avalon_pio_regs. These functions use only symbolic addresses!
#include "system.h" #include "altera_avalon_pio_regs.h" int buttons_pressed(void) { return IORD_ALTERA_AVALON_PIO_DATA(BUTTON_PIO_BASE); } void show_number_on_leds(int x) { IOWR_ALTERA_AVALON_PIO_DATA(LED_PIO_BASE, x); }
IL2206 Embedded Systems 49
C-Programming Testing of Bits (revisited)
HAL functions used
while (current_char != \0) { IOWR_ALTERA_AVALON_PIO_DATA(SEND_BUF_BASE, current_char); while (IORD_ALTERA_AVALON_PIO_DATA(SEND_STATUS_BASE) & 0x20) != 0) ; } /* Mask needed, since other bits */ /* in status register may not be zero */
7 0x1000 0x1001
5 BF
0 Status Sender Sender Buffer
IL2206 Embedded Systems
50
Programming Interrupt
Foreground Program
Do something! Interrupt Event
!
Interrupt Handler
! ! ! ! !
Save Registers Handle Interrupt Restore Registers Restore PC Clear interrupt disable flag
Interrupt Vector
!
Branch to Interrupt Handler
IL2206 Embedded Systems 51
Registering an ISR with alt_irq_register()
The HAL registers this function pointer in a lookup table. When a specific IRQ occurs, the HAL looks up the IRQ in the lookup table and dispatches the registered ISR. ! The prototype for alt_irq_register() is:
!
int alt_irq_register (alt_u32 id, void* context, void (*isr)(void*, alt_u32));
Read the Altera documentation: Nios II Software Developers Handbook
IL2206 Embedded Systems 52
Registering an ISR with alt_irq_register()
!
The prototype has the following parameters: ! id is the hardware interrupt number for the device, as defined in system.h. Interrupt priority corresponds inversely to the IRQ number. Therefore, IRQ 0 represents the highest priority interrupt and IRQ 31 is the lowest. ! context is a pointer used to pass context-specific information to the ISR, and can point to any sort of ISR-specific information. The context value is opaque to the HAL; it is provided entirely for the benefit of the user-defined ISR ! isr is the function that is called in response to IRQ number id. The two input arguments provided to this function are the context pointer and id. Registering a null pointer for isr results in the interrupt being disabled ! If your ISR is successfully registered, the associated interrupt (as defined by id) is enabled on return from alt_irq_register()
IL2206 Embedded Systems 53
An ISR to service a Button PIO IRQ
#include "system.h" #include "altera_avalon_pio_regs.h" #include "alt_types.h" static void handle_button_interrupts(void* context, alt_u32 id) { /* cast the context pointer to an integer pointer. */ volatile int* edge_capture_ptr = (volatile int*) context; /* * Read the edge capture register on the button PIO. * Store value. */ *edge_capture_ptr = IORD_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE); /* Write to the edge capture register to reset it. */ IOWR_ALTERA_AVALON_PIO_EDGE_CAP(BUTTON_PIO_BASE, 0); /* reset interrupt capability for the Button PIO. */ IOWR_ALTERA_AVALON_PIO_IRQ_MASK(BUTTON_PIO_BASE, 0xf); }
IL2206 Embedded Systems 54
Registering the Button PIO ISR with the HAL
#include "sys/alt_irq.h" #include "system.h" ... /* Declare a global variable to hold the edge capture value. */ volatile int edge_capture; ... /* Register the interrupt handler. */ alt_irq_register(BUTTON_PIO_IRQ, (void*) &edge_capture, handle_button_interrupts);
IL2206 Embedded Systems
55
What happens?
!
1. 2.
Based on this code, the following execution flow is possible:
Button is pressed, generating an IRQ. The HAL exception handler is invoked and dispatches the handle_button_interrupts () ISR. handle_button_interrupts() services the interrupt and returns. Normal program operation continues with an updated value of edge_capture.
IL2206 Embedded Systems 56
3. 4.
Application Binary Interface
!
The Application Binary Interface describes
! ! !
How data is arranged in memory Behavior and structure of the stack Function calling conventions
The ABI is very important when C/C++ and Assembler interact
Reference: Nios II Processor Handbook, Chapter 7
IL2206 Embedded Systems
57
Representation of Data Types
IL2206 Embedded Systems
58
HAL Standard C Data Types
! !
To increase portability the following HAL standard data types are defined There is no defined size for C-data types in ANSI C
!
An int reflect usually the natural size of integers on the host machine (Kernighan and Ritchie, 1988)
IL2206 Embedded Systems
59
Minimizing Code Size
!
Minimize the design costs
!
have always the hardware implementation in mind
! !
an int requires 4 bytes, a char only 1 byte if only values between 0 and 10 are needed use a char (or even better the corresponding Altera type)
Think Hardware!
IL2206 Embedded Systems 60
Memory Alignment
!
Contents in memory are aligned as follows:
! !
! !
A function must be aligned to a minimum of 32-bit boundary. The minimum alignment of a data element is its natural size. A data element larger than 32-bits need only be aligned to a 32-bit boundary. Structures, unions, and strings must be aligned to a minimum of 32 bits. Bit-fields inside structures are always 32-bit aligned.
IL2206 Embedded Systems 61
Memory Alignment
0x00 0x04 0x08 0x0C 0x10 0x14
Byte Byte Short Short int int Byte Byte Short Short Byte
IL2206 Embedded Systems
62
Register Usage
!
The ABI defines how the C compiler uses registers. If C and Assembler shall work together the ABI must be followed:
IL2206 Embedded Systems
63
Register Usage
!
It is the responsibility of the calling function to save the following registers, the called function may freely use these registers without saving them on the stack:
IL2206 Embedded Systems
64
Register Usage
!
If the called function wants to use the following registers, it has to save them on the stack first
IL2206 Embedded Systems
65
Register Usage
!
There are many other important registers, like the stack pointer, and the return address
IL2206 Embedded Systems
66
Register Usage Stack
! ! !
The stack grows downwards (i.e. to lower addresses) The stack pointer points to the last used slot The frame pointer points usually to the same location as the stack pointer
IL2206 Embedded Systems
67
Writing an assembler function called from C
!
The following has to be written in an assembler function that is called from C
Save stack & framepointer on stack Save return address (r31) on stack Save callee-saved registers that are used in the function Save stack-space for temporary variable ... Move return value into return registers (r2 and r3) Restore callee-saved registers Restore return address (r31) Restore stack and frame pointer
This applies for functions with not more than four 32-bit arguments
IL2206 Embedded Systems 68
C and Assembler A small example (Main.c)
#include <stdio.h> extern int addSub(int, int, int); int main() { int mode, a, b, result; while(1 < 2) { printf("Enter Mode (0 = Sub, 1 = Plus): "); scanf("%d", &mode); printf("Enter two numbers: "); scanf("%d %d", &a, &b); result = addSub(mode, a, b); if(mode == 0) printf("The difference is %d\n", result); else printf("The sum is %d\n", result); } return 0; }
IL2206 Embedded Systems 69
Message to the Linker: External Function
C and Assembler A small example (AddSub.s)
.global addSub .text addSub:
addSub shall be visible outside this function
# # # # # # # # # # Parameter r2: Return Value r4: Mode r5: a r6: b if mode is 0 -> Plus result = a - b goto end of subroutine result = a + b return from subroutine
beq sub br Plus: add Back: ret
!
r4, r0, Plus r2, r5, r6 Back r2, r5, r6
Note: No need to save stack pointer or callee-saved registers (not used) or return address (leaf function, it does not call other functions)
IL2206 Embedded Systems
70
Summary
Good understanding of underlying hardware is critical for design of efficient software ! HAL library provides abstraction from hardware and allows fast adaptation to changes in hardware ! Application Binary Interface defines a standard for communication between C- and Assembler parts
!
IL2206 Embedded Systems 71