Showing posts with label HW-CPU. Show all posts
Showing posts with label HW-CPU. Show all posts

Friday, December 27, 2013

GDB Server於Linux - LEON2 platform的設定

由於LEON2 CPU中有DSU,預設中會攔截所有的debugger所設的break point,目的是讓DSU當做debug monitor,直接由DSU跟debugger溝通。

但是Linux user mode application on LEON2 CPU的debug...
不需要使用到DSU的monotor,完全需要由gdbserver跟host的debugger溝通,

所以LEON2中DSU controller中有一個控制flags,使DSU放棄breakpoint的monitor,交由gdbserver來控制breakpoint。

0x90000000 DSU control register


Tuesday, December 24, 2013

Gailser GRMON

grmon可以將elf的執行檔upload到LEON2/3 platform的target。
PC端可以是COM port,Target端是DSU。

啟動用法:
  • 一般啟動:grmon
  • 啟動linux(with MMU):grmon -nb
  • -nb意思是當發生資料錯誤時grmon不中斷程式,比方說linux所發生的tt=0x09
  • 當作GDB server,可供GDB連接:grmon -gdb
  • target被連上之後,target會被reset
  • target被連上之後,被設定的register有
    • mcfg1 0x80000000
    • mcfg2 0x80000004
    • mcfg3 0x80000008
    • timer 1 0x80000040, 0x80000044, 0x80000048, 
    • Prescaler 0x80000060, 0x80000064
    • UART 1 0x80000070, 0x80000074, 0x80000078, 0x8000007C,

GRMON命令:
  • mem ADDRESS:讀取address的資料
  • wmem ADDRESS VALUE:將VALUE寫入ADDRESS中
  • batch FILE:執行BATCH檔
  • load FILE:將elf格式的FILE,upload到LEON2/3 target中
  • run:根據load所上載的程式,從ELF中code區段開始執行
  • continue:根據PC所在連續執行
  • step:單步執行
  • hbreak ADDRESS:設置中斷點
  • break:list出所有中斷點
  • del NUM:取消編號NUM的中斷點
  • sys info:list出SOC的資料
  • mcfg1:讀寫MCG1值
  • mcfg2:讀寫MCG2值
  • mcfg3:讀寫MCG3值
  • reg:dump register資料

GRMON,GDB結合:
  • grmon server(架設IP 192.168.1.10)中下"grmon -gdb"命令
  • gdb client中下"gsb --debugger sparc-linux-gdb"
  • (GDB)file ELF
  • (GDB)target extended-remote 192.168.1.10:2222
  • (GDB)load
  • (GDB)run
  • (GDB)monitor GRMON_COMMAND:於GDB中執行GRMON的命令

Saturday, December 21, 2013

Hazard對CPU performance影響實驗

在做實驗之前,先提到3點
1. 這是延續之前討論課題
X86 Instruction - TEST
X86 TEST, Branch Predication, GCC __builtin_expect() function
branch於不同CPU的作法(X86, ARM, SPARC)比較
2. 參考資料來源
Wikipedia Hazard (computer architecture)
3. 做此實驗須注意事項...
因為發生Hazard的條件太多,比方說在一般OS執行狀況下,有time slice/context switch機制,每發生一次context switch,Hazard就發生了

所以實驗環境要單純

不能有不能控制的狀況,比方不能有interrupt/trap產生

要有實驗組、對照組:兩組不一樣的地方只能在branch發生的地方出現


所以,以我現有的設備,我選擇Leon2 CPU,配合sparc-elf-3.4.4 toolchain...
現有LEON2 CPU速度夠慢,比較容易觀察到結果

它有完整的POSIX API支援

且是OS-less,但有完整的start-up code

可以用C語言很容易做I/O部分

不會有任何意外的interrupt產生

利用sparc-elf-3.4.4 toochain配合組合語言,我可以很精確的控制到實驗組跟對照組不一樣的地方只有branch部分,其餘code部分的行為模式是一模一樣。

首先先列出Leon2 CPU pipeline架構...

它是5級pipeline架構
分別是Fetch, Decode, Execute, Memory, Write,如下圖



於code比較部份,我用組合語言寫了兩個function,其pseudo code...
/* func 1 */
int func1(int x)
{
    if (x != 0)
        return 1;
    else
        return 2;
}

/* func 2 */
int func2(int x)
{
    if (x == 0)
        return 2;
    else
        return 1;
}
呼叫這兩個function的main函數pseudo code...
 20     /*
 21      * This will cause less branch miss
 22      */
 23     times (&time_info);
 24     time_b = (long) time_info.tms_utime;
 25
 26     seed = 0;
 27     for (i = 0; i < NUM_1; i++)
 28     {
 29         seed += func1(i);
 30     }
 31
 32     times (&time_info);
 33     time_e = (long) time_info.tms_utime;
 34
 35     printf("func1...%d, %d\n", seed, time_e - time_b);
 36
 37     /*
 38      * This will cause more branch miss
 39      */
 40     times (&time_info);
 41     time_b = (long) time_info.tms_utime;
 42
 43     seed = 0;
 44     for (i = 0; i < NUM_1; i++)
 45     {
 46         seed += func2(i);
 47     }
 48
 49     times (&time_info);
 50     time_e = (long) time_info.tms_utime;
 51
 52     printf("func2...%d, %d\n", seed, time_e - time_b);
27~30以及44~47行為測試主要部份,呼叫func1(), func2()的參數大部分不為0,所以func1()為實驗組,func2為對照組。 其整個編譯過程...
~/hazard $ ls -l
total 9
-rw-r--r-- 1 LungsWu None  287 Oct  6 13:09 Makefile
-rw-r--r-- 1 LungsWu None 1250 Oct  6 12:59 func.S
-rw-r--r-- 1 LungsWu None  945 Oct  6 12:29 main.c

~/hazard $ make
sparc-elf-gcc -c -mv8 -msoft-float main.c -o main.o
sparc-elf-gcc -c -mv8 -msoft-float func.S -o func.o
sparc-elf-gcc -mv8 -msoft-float main.o func.o -o hazard

~/hazard $
再來用sparc-elf-objdump觀察實驗組func1()... 14~24...x不為0,沒發生branch 28~38...x為0,發生branch
00000000 :
   0:   9d e3 bf 98     save  %sp, -104, %sp
   4:   f0 27 a0 44     st  %i0, [ %fp + 0x44 ]
   8:   c2 07 a0 44     ld  [ %fp + 0x44 ], %g1
   c:   80 a0 60 00     cmp  %g1, 0
  10:   02 80 00 06     be  28 
  14:   82 10 20 01     mov  1, %g1
  18:   b0 10 00 01     mov  %g1, %i0
  1c:   81 c7 e0 08     ret
  20:   81 e8 00 00     restore
  24:   01 00 00 00     nop

00000028 :
  28:   82 10 20 02     mov  2, %g1     ! 2 
  2c:   b0 10 00 01     mov  %g1, %i0
  30:   81 c7 e0 08     ret
  34:   81 e8 00 00     restore
  38:   01 00 00 00     nop
再來用sparc-elf-objdump觀察對照組func2()... 50~60...x為0,發生branch 64~74...x不為0,沒發生branch
0000003c :
  3c:   9d e3 bf 98     save  %sp, -104, %sp
  40:   f0 27 a0 44     st  %i0, [ %fp + 0x44 ]
  44:   c2 07 a0 44     ld  [ %fp + 0x44 ], %g1
  48:   80 a0 60 00     cmp  %g1, 0
  4c:   12 80 00 06     bne  64 
  50:   82 10 20 02     mov  2, %g1
  54:   b0 10 00 01     mov  %g1, %i0
  58:   81 c7 e0 08     ret
  5c:   81 e8 00 00     restore
  60:   01 00 00 00     nop

00000064 :
  64:   82 10 20 01     mov  1, %g1     ! 1 
  68:   b0 10 00 01     mov  %g1, %i0
  6c:   81 c7 e0 08     ret
  70:   81 e8 00 00     restore
  74:   01 00 00 00     nop
func1()跟func2控制到流程一模一樣,只有cmp之後的be,bne發生branch的條件不一樣,所以執行結果....
func2...5000001, 337
func1...5000001, 328
func2...5000001, 337
func1...5000001, 328
func1...5000001, 328
func2...5000001, 337
func1...5000001, 328
func2...5000001, 337
func1()果然比func1慢了(337-328)/328=0.027439左右,每次運算結果一樣精確一樣。

branch於不同CPU的作法(X86, ARM, SPARC)比較

######################################################
# func.c, source code
######################################################

  1 int func(int x)
  2 {
  3 
  4     if (__builtin_expect(x, 0))
  5     {
  6         x += 5;
  7     }
  8     else
  9     {
 10         x += 6;
 11     }
 12 
 13     return x;
 14 }


######################################################
# func.c, X86
######################################################
$ gcc -c -O2 func.c -o func.o
$ objdump -S func.o

func.o:     file format elf32-i386

Disassembly of section .text:

00000000 :
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 08                mov    0x8(%ebp),%eax
   6:   85 c0                   test   %eax,%eax
   8:   75 07                   jne    11
   a:   5d                      pop    %ebp
   b:   b8 06 00 00 00          mov    $0x6,%eax
  10:   c3                      ret    
  11:   5d                      pop    %ebp
  12:   83 c0 05                add    $0x5,%eax
  15:   c3                      ret    

這邊可以發現,X86的作法最為直覺,很接近一般人的思路。

######################################################
# func.c, ARM
######################################################
$ arm-linux-gcc -c -O2 func.c -o func.arm.o
$ arm-linux-objdump -S func.arm.o

func.arm.o:     file format elf32-littlearm

Disassembly of section .text:

00000000 :
   0:   e3500000        cmp     r0, #0  ; 0x0
   4:   12800005        addne   r0, r0, #5      ; 0x5
   8:   02800006        addeq   r0, r0, #6      ; 0x6
   c:   e1a0f00e        mov     pc, lr

ARM的理念是善用根據flags條件去決定是否要執行一指令(addne, addeq)



######################################################
# func.c, SPARC
######################################################
$ sparc-linux-gcc -c -O2 func.c -o func.sparc.o
$ sparc-linux-objdump -S func.sparc.o

func.sparc.o:     file format elf32-sparc

Disassembly of section .text:

00000000 :
   0:   80 a2 20 00     cmp  %o0, 0
   4:   12 80 00 03     bne  10
   8:   90 02 20 05     add  %o0, 5, %o0
   c:   90 10 20 06     mov  6, %o0
  10:   81 c3 e0 08     retl 
  14:   01 00 00 00     nop 

SPARC則利用NPC的特性,於BNE之後先執行ADD再說,如果BNE成立,再補執行mov指令。

X86 TEST, Branch Predication, GCC __builtin_expect() function

由此篇文章X86 Instruction - TEST所編繹出來的code,用C語言及組合語言左右對照結果...
01 /*********************************
02  * File Name: func.c
03  *********************************/
04                    0:   55             push   %ebp
05                    1:   89 e5          mov    %esp,%ebp
06 int func (int x)   3:   8b 45 08       mov    0x8(%ebp),%eax
07 {                  6:   85 c0          test   %eax,%eax
08     if (x == 0)    8:   75 07          jne    11
09     {              a:   5d             pop    %ebp     
10         x += 5;    b:   b8 05 00 00 00 mov    $0x5,%eax
11     }              10:   c3            ret
12     else           
13     {              
14         x += 6;    11:   5d            pop    %ebp
15     }              12:   83 c0 06      add    $0x6,%eax
16                          
17     return (x);    15:   c3            ret
18 }
這裡面牽涉到一點,一般在user mode或者一般使用者、programmer是感受不到這差異,就是branch導致的pipeline失效...

所謂brach導致的pipeline失效是指當CPU在執行程式時,CPU會將執行instruction的過程分為幾個步驟...Fetch, Decode, Execute, Write back
clock cycle
     Fetxh  Decode  Execute  Write Baxk
I0   XX
I1          XX 
I2                  XX
I3                            XX
I4                                          (Completed)
以上PCU都是根據Instruction在記憶體中的連續位址排列,循序由CPU做不同階段的處理。
但是....C語言中當遇到branch時,比方說,此篇C語言例子當中(x值不為0)...

I3是一個JNE指令,且ZF(Zero flag)為0

I2, I1, I0指令為處理 x += 5;

I3執行完之後應當執行x += 6;而不是執行pipeline中的x += 5;

所以I2, I1, I0指令於pipeline是沒用的

這種pipeline的預測失誤,稱做為Hazard
(Hazard分好幾種)

為了避免此pipeline的浪費,我們於coding時可以先考慮哪一種case被執行的機率較高...
可以緊接著在 if (x == 0) 之後。

但一般programmer並無此習慣、概念,或者說是這方面管理作的很差,GCC提供了__builtin_expect()函數,利用此函數告訴compile哪一種狀況發生的機率比較叫高,且不影響執行結果...
/****************
 * CODE A
 ****************/
08     if (__builtin_expect(x, 0))
09     {
10         x += 5;
11     }        
12     else
13     {
14         x += 6
15     }
以上同等於...
/****************
 * CODE B
 ****************/
08     if (x)
09     {
10         x += 5;
11     }        
12     else
13     {
14         x += 6
15     }
若x不為0時,執行x += 5; 反之執行x += 6;

但是CODE A中__builtin_expect(x, 0)表示...
x的值為0的機率較高,所以告訴編譯器x += 6的敘述最好安排於if (x)之後,以減少branch的發生,以提高CPU的使用率。
這邊做一個比較
$ cat func.c
/**************************
 * CODE 001
 **************************/
int func(int x)
{

    if (__builtin_expect(x, 1))  /* 注意 */
    {
        x += 5;
    }
    else
    {
        x += 6;
    }

    return x;
}
$
$ gcc -O2 -c func.c -o func.o
$
$ objdump -S func.o

func.o:     file format elf32-i386

Disassembly of section .text:

00000000 :
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 08                mov    0x8(%ebp),%eax
   6:   85 c0                   test   %eax,%eax
   8:   74 05                   je     f /* 注意 */
   a:   5d                      pop    %ebp
   b:   83 c0 05                add    $0x5,%eax /* x += 5 */
   e:   c3                      ret    
   f:   5d                      pop    %ebp
  10:   b8 06 00 00 00          mov    $0x6,%eax /* x += 6 */
  15:   c3                      ret    

另一個檔案
$ cat func.c
/**************************
 * CODE 002
 **************************/
int func(int x)
{

    if (__builtin_expect(x, 0)) /* 注意 */
    {
        x += 5;
    }
    else
    {
        x += 6;
    }

    return x;
}
$
$ gcc -O2 -c func.c -o func.o
$
$ objdump -S func.o

func.o:     file format elf32-i386

Disassembly of section .text:

00000000 :
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 08                mov    0x8(%ebp),%eax
   6:   85 c0                   test   %eax,%eax
   8:   75 07                   jne    11 /* 注意 */
   a:   5d                      pop    %ebp
   b:   b8 06 00 00 00          mov    $0x6,%eax /* x += 6 */
  10:   c3                      ret    
  11:   5d                      pop    %ebp
  12:   83 c0 05                add    $0x5,%eax /* x += 5 */
  15:   c3                      ret  

X86 Instruction - TEST

於X86 platform中,以下的一段C語言...
/*********************************
 * File Name: func.c
 *********************************/
int func (int x)
{
    if (x == 0)
    {
        x += 5;
    }
    else
    {
        x += 6;
    }

    return (x);
}

經過以下的編譯,及觀察結果
$ gcc -O2 func.c -o func.o
$
$ objdump -S func.o
func.o:     file format elf32-i386

Disassembly of section .text:

00000000 :
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 08                mov    0x8(%ebp),%eax
   6:   85 c0                   test   %eax,%eax
   8:   75 07                   jne    11
   a:   5d                      pop    %ebp
   b:   b8 05 00 00 00          mov    $0x5,%eax
  10:   c3                      ret    
  11:   5d                      pop    %ebp
  12:   83 c0 06                add    $0x6,%eax
  15:   c3                      ret    
$
$
以上位址6,8中(test, jne)是C語言編譯成組合語言
if (...) .... else ...很典型的編譯方式....(這是經過最佳化)

instruction jne, je 會根據Zero flags來做跳躍(換句話說是PC的重新指定)...
這邊可以"想像"為...je, jne會把zero flags當作之前的運算結果是否為true,false。
所謂"之前的運算"是指test instruction。

1. 當Zero flag為0時,執行
je 11
會跳躍到11的位址
jne 11
不會跳躍到11的位址,而是繼續執行下一行。

2. 當Zero flag不為0時,執行
je 11
不會跳躍到11的位址,而是繼續執行下一行。
jne 11
會跳躍到11的位址。

3. Zero flags的設定...
In the x86 assembly language, the TEST instruction performs a bitwise AND on two operands. The flags SF, ZF, PF, CF, OF and AF are modified while the result of the AND is discarded.
Instruction TEST Operation

TEMP <-- and="" font="" src1="" src2="">
SF <-- font="" msb="">

IF TEMP = 0
    THEN ZF <-- 1="" font="">
    ELSE ZF <-- 0="" font="">
FI:

PF <-- bitwisexnor="" font="">
CF <-- 0="" font="">
OF <-- 0="" font="">
(*AF is Undefined*)
所以C語言跟組合語言的對照如下...
int func1 (int x)    /* mov 0x8(%ebp), %eax       */
{
    if (x == 0)     /* test %eax, %eax; jne 0x11 */
    {
        x += 5;
    }
    else            /* address 0x11              */
    {
        x += 6;
    }

    return (x);
}

int func2 (int x)    /* mov 0x8(%ebp), %eax      */
{
    if (x != 0)     /* test %eax, %eax; je 0x11*/
    {
        x += 5;
    }
    else            /* address 0x11             */
    {
        x += 6;
    }

    return (x);
}