ECE 448 Lecture 14 Bare Metal System Software
ECE 448 Lecture 14 Bare Metal System Software Development George Mason University
Required Reading P. Chu, FPGA Prototyping by VHDL Examples 9 Bare Metal System Software Development 3
Source Code Companion Website of FPGA Prototyping by VHDL Examples 2 nd edition https: //academic. csuohio. edu/chu_p/rtl/fpga_mcs_vhdl. html § Source codes read_me file: readme_source_code. pdf source file: fpga_mcs_vhdl_src. zip (last updated 11/10/2017) Folder: fpga_mcs_vhdl_src/cpp 4
Desktop-like System vs. Bare Metal System Desktop-like System with drivers Hosted environment • • Before starting main(), the operating system (OS) allocates necessary resources and initializes system services without drivers Freestanding environment • Application itself is responsible for all resource allocations and initializations • The main() function never exits After the main() function exits, it returns control to the OS 5
Software hierarchy of an FPro So. C system 6
FPro Software Organization main() { sys_init(); while(1) { task_1(); task_2(); . . . task_n(); } super loop 7
Address Map of the FPro System Base address Offset 8
IO Register Map of the Timer Core bit number offset type of access 9
FPro Software Organization • A simple bare metal software scheme - No operating system • The processor boots directly into an infinite main loop • Software hierarchy of an FPro system contains § application layer § driver layer § hardware layer • A boot routine is associated with the processor. It performs the basic initialization process, such as § clearing the caches, § configuring the stack and heap segments § initializing the interrupt, and then transfers control to the main program. • The timer core and UART maintain a system time and assist displaying a debug message on the console. 10
Top-level diagram of an FPro system 11
C Pointers int x=1, y=5, z=8, *ptr; ptr = &x; y = *ptr; *ptr = z; // ptr gets symbolic address of x // y is set to the content pointed by ptr // variable pointed a pointer is set to the content of z 12
Data Types in inttypes. h • • int 8_t: signed 8 -bit integer uint 8_t: unsigned 8 -bit integer int 16_t: signed 16 -bit integer uint 16_t: unsigned 16 -bit integer int 32_t: signed 32 -bit integer uint 32_t: unsigned 32 -bit integer int 64_t: signed 64 -bit integer uint 64_t: unsigned 64 -bit integer 13
14
Address Map of the MMIO Subsystem 1 word = 4 bytes 1 byte C 0000000 slot 1 30000000 slot 1 211 words … slot 63 MMIO Subsystem 64 slots x 32 words per slot = 26 x 25 words = 211 words 213 bytes … MMIO Subsystem 64 slots x 128 bytes per slot = 26 x 27 words = 213 bytes slot 63 15
Accessing Memory Locations at a Particular Address Base address of the GPO (LED) core = 0 xc 0000100 Base address of the GPI (SW) core = 0 xc 0000180 int 16_t pattern = 0 x 0055; int 16_t sw; *((int 16_t *) 0 xc 0000100) = pattern; sw = *((int 16_t *) 0 xc 0000180); 16
Software hierarchy of an FPro So. C system 17
Functions of the Timer Core driver timer_core. h class Timer. Core { public: … void pause(); void go(); void clear(); uint 64_t read_tick(); For the detailed descriptions of all functions, see timer_core. h located in fpga_mcs_vhdl_src/cpp/drv uint 64_t read_time(); void sleep(uint 64_t us); private: … }; 18
Calling functions of the Timer Core driver #include "chu_init. h" Timer. Core timer((get_slot_addr(BRIDGE_BASE, S 0_SYS_TIMER))); int main(){ timer. clear(); timer. go(); timer. pause(); uint 64_t ticks = timer. read_tick(); timer. sleep(1000000); } 19
Timer. Core class definition in timer_core. h. (1) #include "chu_io_rw. h" #include "chu_io_map. h" // to obtain system clock rate class Timer. Core { public: /* register map */ enum { COUNTER_LOWER_REG = 0, // lower 32 bits of counter COUNTER_UPPER_REG = 1, // upper 16 bits of counter CTRL_REG = 2 // control register }; /* masks */ enum { GO_FIELD = 0 x 00000001, // bit 0 of ctrl_reg; enable bit CLR_FIELD = 0 x 00000002 // bit 1 of ctrl_reg; clear bit }; 20
Timer. Core class definition in timer_core. h (2) /* methods */ Timer. Core(uint 32_t core_base_addr); // constructor ~Timer. Core(); // destructor, not used void pause(); void go(); void clear(); uint 64_t read_tick(); uint 64_t read_time(); void sleep(uint 64_t us); private: uint 32_t base_addr; uint 32_t ctrl; }; // pause counter // resume counter // clear the counter to 0 // retrieve # clock cycles elapsed // read time elapsed in microseconds // idle for us microseconds // current state of ctrl_reg 21
Timer. Core class implementation in timer_core. cpp (1) #include "timer_core. h" Timer. Core: : Timer. Core(uint 32_t core_base_addr) { base_addr = core_base_addr; ctrl = 0 x 01; clear(); io_write(base_addr, CTRL_REG, ctrl); // enable the timer } Timer. Core: : ~Timer. Core() { } void Timer. Core: : clear() { uint 32_t wdata; // write clear_bit to generate a 1 -clock pulse // clear bit does not affect ctrl wdata = ctrl | CLR_FIELD; io_write(base_addr, CTRL_REG, wdata); } 22
Timer. Core class implementation in timer_core. cpp (2) void Timer. Core: : pause() { // reset enable bit to 0 ctrl = ctrl & ~GO_FIELD; io_write(base_addr, CTRL_REG, ctrl); } void Timer. Core: : go() { // set enable bit to 1 ctrl = ctrl | GO_FIELD; io_write(base_addr, CTRL_REG, ctrl); } uint 64_t Timer. Core: : read_tick() { uint 64_t upper, lower; lower = (uint 64_t) io_read(base_addr, COUNTER_LOWER_REG); upper = (uint 64_t) io_read(base_addr, COUNTER_UPPER_REG); return ((upper << 32) | lower); } 23
Timer. Core class implementation in timer_core. cpp (3) uint 64_t Timer. Core: : read_time() { // elapsed time in microsecond (SYS_FREQ in MHz) return (read_tick() / SYS_CLK_FREQ); } void Timer. Core: : sleep(uint 64_t us) { uint 64_t start_time, now; start_time = read_time(); // busy waiting do { now = read_time(); } while ((now - start_time) < us); } 24
I/O Macros in chu_io_rw. h (1) #ifndef _CHU_IO_RW_H_INCLUDED #define _CHU_IO_RW_H_INCLUDED #include <inttypes. h> // to use unit. N_t type #ifdef __cplus extern "C" { #endif /*********************************** * generic low-level read and write access * - offset: 32 -bit word offset relative to base * - 4*offset used for byte address * - must bypass data cache for I/O access ***********************************/ 25
I/O Macros in chu_io_rw. h (2) /* Read an io register. * base_addr base address of an io core * offset register word offset * returns 32 -bit data of the register * macro calculates the byte address of the register and then read */ #define io_read(base_addr, offset) (*(volatile uint 32_t *)((base_addr) + 4*(offset))) /* * Write an io register * base_addr base address of an io core * offset register word offset * data 32 -bit data */ #define io_write(base_addr, offset, data) (*(volatile uint 32_t *)((base_addr) + 4*(offset)) = (data)) 26
I/O Macros in chu_io_rw. h (3) /* Calculate base address of a memory mapped io slot. * base-address of FPro system. * slot designated io slot number * returns base address of the slot */ #define get_slot_addr(base, slot) ((uint 32_t)((base) + (slot)*32*4)) #ifdef __cplus } // extern "C" #endif /* _CHU_IO_RW_H_INCLUDED */ 27
Functions of the UART Core driver uart_core. h • void disp(char ch) - display a character • void disp(const char *str) - display a string • void disp(int n, int base, int len) - display an integer in base 2/8/10/16 using len digits • void disp(int n, int base) - display an integer in base 2/8/10/16 with the number of digits determined automatically • void disp(int n) - display an integer in base 10 with the number of digits determined automatically • void disp(double f, int digit) - display a floating-point number with the digit number of fractional digits • void disp(double f) - display a floating-point number with 3 digits in the fractional part 28
Calling functions of the UART Core driver #include "chu_init. h" Uart. Core uart((get_slot_addr(BRIDGE_BASE, S 1_UART 1))); int main(){ uart. disp("ECE 448 FPro rn"); uart. disp(5); int n = 10; uart. disp(n, 16, 4); } 29
chu_init. h #include "chu_io_rw. h" #include "chu_io_map. h" #include "timer_core. h" #include "uart_core. h" No need to include these files on top of chu_init. h 30
Useful Bit Manipulation Macros chu_init. h #define bit_set(data, n) ((data) |= (1 UL << (n))) #define bit_clear(data, n) ((data) &= ~(1 UL << (n))) #define bit_toggle(data, n) ((data) ^= (1 UL << (n))) #define bit_read(data, n) (((data) >> (n)) & 0 x 01) #define bit_write(data, n, bitvalue) (bitvalue ? bit_set((data), n) : bit_clear((data), n)) #define bit(n) (1 UL << (n)) 31
Useful Functions (1) chu_init. h // timing functions unsigned long now_us(); unsigned long now_ms(); void sleep_us(unsigned long int t); void sleep_ms(unsigned long int t); // functions used for debugging void debug_off(); void debug_on(const char *str, int n 1, int n 2); #ifndef _DEBUG #define debug(str, n 1, n 2) debug_off() #endif // not _DEBUG #ifdef _DEBUG #define debug(str, n 1, n 2) debug_on((str), (n 1), (n 2)) #endif // not _DEBUG 32
Debugging Messages : Example #define _DEBUG for (i=0; i<5, i++) { …… debug("timer check – (loop#)/now", i, now_ms()) } Examples of messages displayed: debug: debug: timer timer check check – – – (loop#)/now: (loop#)/now: 0(0 x 00), 1(0 x 01), 2(0 x 02), 3(0 x 03), 4(0 x 04), 1586(0 x 0632) 3045(0 x 0 be 5) 4687(0 x 124 f) 7014(0 x 1 b 66) 8602(0 x 219 a) 33
Useful Functions (1) chu_init. cpp Timer. Core _sys_timer(get_slot_addr(BRIDGE_BASE, TIMER_SLOT)); Uart. Core uart(get_slot_addr(BRIDGE_BASE, UART_SLOT)); // current system time in microsecond unsigned long now_us() { return ((unsigned long) _sys_timer. read_time()); } // current system time in ms unsigned long now_ms() { return ((unsigned long) _sys_timer. read_time() / 1000); } 34
Useful Functions (2) chu_init. cpp // idle for t microseconds void sleep_us(unsigned long int t) { _sys_timer. sleep(uint 64_t(t)); } // idle for t ms void sleep_ms(unsigned long int t) { _sys_timer. sleep(uint 64_t(1000 * t)); } 35
Useful Debugging Functions chu_init. cpp // debug asserted // uart print a 1 -line message: msg + 2 numbers in dec/hex format void debug_on(const char *str, int n 1, int n 2) { uart. disp("debug: "); uart. disp(str); uart. disp(n 1); uart. disp("(0 x"); uart. disp(n 1, 16); uart. disp(") / "); uart. disp(n 2); uart. disp("(0 x"); uart. disp(n 2, 16); uart. disp(") nr"); } void debug_off() { } 36
- Slides: 35