7.ベンチマーク
7.1リードソロモン
例によって特製ベンチマークです。RTLシミュレーション50MHzで駆動しています。
本ベンチマークでは、乗算器を性能を強く受けますが、乗算8クロックバージョンでの値です。
約660μsecです。Opencoresのなかでは、最速になります。
PLASMAとの違いは以下の通りです。
Plasma/YACC比較 | PLASMA | YACC |
パイプライン | 3Stage | 5Stage |
乗算 | 16クロック | 32クロックから2クロックまでスケーラブル |
除算 | 32クロック | 32クロック |
レジスタファイル クロックエッジ | Neg | Pos |
メモリR/W | 2-3クロック | 1クロック |
分岐 | 2クロック | 最大4クロック |
また、Plasmaでは、Alteraレジスタファイルに対し、Negedgeを使用しています。許される遅延は、最も深いロジック箇所において半クロックになってしまいます。結果的に駆動周波数もネックになってしまいます。
7.2 ドライストーンベンチマーク
このベンチは乗算器の性能をあまり受けません。
下図は、上側がXilinx版 下側は、StratixU版で、どちらも乗算2クロック(Xilinx)での結果です。
XilinxStarterkKitは、テストスタートが、329850ns テストストップが498050nsですから、
1サイクルは、(498050-329850)/10 =168200ns
これは、1秒あたり、594530hrystones/Secになります。
基準コンピュータVAXは、1757Dhrysones/Secですから
約 33.8DMIPSになります。RTLシミュレーションは、50MHzで駆動していますから、駆動可能周波数の25MHz駆動では、約17DMIPSになります
一方、StratixUではRTLシミュレーションは、StratixU駆動可能周波数の165MHz駆動では、約110DMIPSになります。
。
SH2の実測例で比較しましょう。ドライストーンは最適化されたCコンパイラとでは結果がかなり違います。(たとえばhttp://www.apnet.co.jp/techinfo/bench/bench.html参照)
これから、SH2 GNU PRO実測を拾ってきて、比較してみました。
<考察>
デバイスはStratixU最下位機種(最速)ですが、FPGAのLUTリソースの23%しか消費していません。まだ、DSP機能を追加できる余地は十分に残っています。H8プロジェクトでは、おおよそ、8DMIPS程度と推定されます。。そのときのデバイスリソースは、50%程度です。H8プロジェクトと比較すると10倍のSPEED・リソース消費量が半分ですから、コスト性能比は20倍近いことになります。アーキテクチャがいかに性能に影響するかが分かります。
PLASMAはCYCLONEによるRTLシミュレーション値ですが、思ったほど上がらないのは、デバイスの性能差(約50%)以外にメモリR/W性能(1CLK:2-3CLK)と最大駆動周波数の差によるものでしょう。
SH2は、(http://www.apnet.co.jp/techinfo/bench/bench.html)からの引用で特に7046Fは、CPU単体の性能としての比較可能です。CPU駆動周波数で正規化してみると、(どちらもシングルパイプラインなので1CLK1命令に収束する)YACCとほぼ同じDMIPSになります。外付けがあると128MHzSH3でもYACCより落ちてしまっています。これは、外部バス幅による転送SPEEDがボトルネックになるためです。FPGA内では、ハーバードアーキテクチャによりプログラム、データ共独立に32ビットでフェッチできるところが違います。ドライストーンは、SRAM16KBあれば、まるまる収まるのでこのような結果になってしまいます。逆にFGPA内SRAMに入るアプリケーションであれば、有り余るリソースをDSP機能に振り分けさらなる高速化をすることができます。自作ロボット等に最適ではないでしょうか?
例によってテストベンチVerilogソースです。リードソロモン、ドライストーン共通です。
`timescale 1ns/1ps `define PORT6000 module yacc_test; reg clock=0; reg Reset=0; reg int_req_usr=0; wire [31:0] mem_data_w; wire mem_write; wire [15:0] mem_address; always #10 clock=~clock; initial begin Reset=0; #800 Reset=1; end yacc cpu(.clock(clock),.Async_Reset(Reset),.MemoryWData(mem_data_w), .MWriteFF(mem_write), .data_port_address(mem_address)); `ifdef PORT6000 `define Print_Port_Address 16'h6000 //ATMEL Big Endian `define Print_CAHR_Port_Address 16'h6001 `define Print_INT_Port_Address 16'h6002 //First ADDRESS `define Print_LONG_Port_Address 16'h6004 //First ADDRESS `else `define Print_Port_Address 16'h3ff0 //ATMEL Big Endian `define Print_CAHR_Port_Address 16'h3ff1 `define Print_INT_Port_Address 16'h3ff2 //First ADDRESS `define Print_LONG_Port_Address 16'h3ff4 //First ADDRESS `endif task Cprint;// String OUT until the byte 00 or xx detected with least Byte first and justified. integer i; begin :Block i=0; while (1) begin if (char_buffer[i*8 +:8] ===8'h00 || char_buffer[i*8 +:8]===8'hxx) begin disable Block; end $write("%c",char_buffer[i*8 +:8]); i=i+1; end end endtask reg [0:639] char_buffer; integer counter=0; always @ (posedge clock ) begin if ((mem_write === 1'b1)) begin // if (mem_data_w==32'h0101_0101) $stop; if (mem_address==`Print_Port_Address) begin if (mem_data_w[7:0]===8'h00) begin char_buffer[counter*8 +:8]=mem_data_w[7:0]; if (char_buffer[0 +:8*7]==="$finish") begin $stop; end else if (char_buffer[0 +:8*5]==="$time") begin $display("Current Time on Simulation=%d",$time); end else Cprint;//$write("%s",char_buffer); for (counter=0; counter< 80; counter=counter+1) begin char_buffer[counter*8 +:8]=8'h00; end counter=0; end else begin char_buffer[counter*8 +:8]=mem_data_w[7:0]; counter=counter+1; end end else if (mem_address==`Print_CAHR_Port_Address) begin $write("%h ",mem_data_w[7:0]); end else if (mem_address==`Print_INT_Port_Address) begin $write("%h ",mem_data_w[15:0]);//Little Endian end else if (mem_address==`Print_LONG_Port_Address) begin $write("%h ",mem_data_w[31:0]);//Big Endian end end //if end //always endmodule |
ドライストーンCプログラムソースです。
ベンチマークでは、stcpyは、C、strcmpはアセンブラで記述しています。(strcmpをCで書くと結果がまったく奮いませんでした。Cソースのアセンブルリストを見ると遅延スロットにうまく命令を埋め込めずNOPが目立ちます。また、分岐も多くなっていました。 こういう最適化をしまくるのはベンチマークの意図から反するのですが、ここはよしとしましょう。)
記法がかなり古いですが、GCCならコンパイルできます。
/* ************************************************************************* * * "DHRYSTONE" Benchmark Program * ----------------------------- * * Version: C, Version 2.1 * * File: dhry_1.c (part 2 of 3) * * Date: May 25, 1988 * * Author: Reinhold P. Weicker * ************************************************************************* * Jun.30.2004 Add strcmp,strcpy Tak.Sugawara print("$time"); print("$finish"); / /* #include <io.h> #include <interrupt.h> #include <progmem.h> #include <stddef.h> #ifdef __STDC__ #include <stdarg.h> #else #include <varargs.h> #endif #include <stdlib.h> #include <string.h> #include "ap_uart.h" #include "printf_P.h" */ #include "dhry.h" /* Global Variables: */ Rec_Pointer Ptr_Glob, Next_Ptr_Glob; int Int_Glob; Boolean Bool_Glob; char Ch_1_Glob, Ch_2_Glob; int Arr_1_Glob [50]; int Arr_2_Glob [50] [50]; char Reg_Define[] = "Register option selected."; extern int strcmp(char* a, register char*b);//Jul.12.2004 /* forward declaration necessary since Enumeration may not simply be int */ #ifndef ROPT #define REG /* REG becomes defined as empty */ /* i.e. no register variables */ #else #define REG register #endif //Verilog Specific Routines #define PORT6000 #ifdef PORT6000 #define print_port 0x6000 #define print_char_port 0x6001 #define print_int_port 0x6002 #define print_long_port 0x6004 #else #define print_port 0x3ff0 #define print_char_port 0x3ff1 #define print_int_port 0x3ff2 #define print_long_port 0x3ff4 #endif int array[100]; int marray[5][5][5]; char buffer[100]; void print(unsigned char* ptr)//Verilog Test Bench Use { #ifdef DOS printf("%s ",ptr); #else while (*ptr) { *(volatile unsigned char*)print_port=*(ptr++); } *(volatile unsigned char*)print_port=0x00;//Write Done #endif } void print_char(unsigned char val)//Little Endian write out 16bit number { #ifdef DOS printf("%x ",val); #else *(volatile unsigned char*)print_char_port=(unsigned char)val ; #endif } void print_short(short val)//Little Endian write out 16bit number { #ifdef DOS printf("%x",val); #else *(volatile unsigned short*)print_int_port=val ; #endif } void print_long(unsigned long val)//Little Endian write out 32bit number { #ifdef DOS printf("%x",val); #else *(volatile unsigned long*)print_long_port=val; #endif } void strcpy(register char* dest,register char* source) { // print(" printing source="); // print(source); while(*source) { *(dest++) =*(source++); } *dest=0;//Write Done // print("buffer address="); // print_long(dest_ptr); // print("final address="); // print_long(dest); // print(dest_ptr); } /* int strcmp( register char* a, register char*b) { // return -1; // print(" printing a="); // print(a); // print(" printing b="); // print(b); // print("\n"); while (1){ if (*a !=0){ //print("case265 "); if (*b==0) return 1;//return a >b //print("case267 "); if (*a==*b){ // print("case269 "); a++;b++; }else { // print("case273 "); // print("a="); // print_char(*a); // print("b="); // print_char(*b); // return 0; // print_char(*b); // print(" *a< *b"); // print_long(*a < *b); // print("\n"); return (*a < *b) ? -1:+1; } } else {//a==0 // print("case277 "); if (*b ==0) { // print("case277 "); return 0x0;//return == } //print("case275 "); return -1; //a<b } } } */ /* unsigned long int p1,p2,p3; /* end of variables for time measurement */ /* void chip_init(void) { outp(0,TCCR1A); outp(0x05,TCCR1B); outp(0,TCNT1H); outp(0,TCNT1L); } unsigned long int dtime(void) { unsigned long int x; int xh,xl; outp(0x0,TCCR1B); xh = inp(TCNT1H); xl = inp(TCNT1L); outp(0x05,TCCR1B); x = (unsigned long int)xl + (unsigned long int)xh * 256L; return x; } */ void main (void) /*****/ /* main program, corresponds to procedures */ /* Main and Proc_0 in the Ada version */ { One_Fifty Int_1_Loc; REG One_Fifty Int_2_Loc; One_Fifty Int_3_Loc; REG char Ch_Index; Enumeration Enum_Loc; Str_30 Str_1_Loc; Str_30 Str_2_Loc; REG int Run_Index; REG int Number_Of_Runs; Rec_Type rta,rtb; unsigned int _d1,_d2; /* chip_init(); uart_init(); sei(); for(_d1=0;_d1<100;_d1++) for(_d2=0;_d2<100;_d2++) { asm ("nop");}; */ // printf ("\n\rDhrystone Benchmark\n\r"); print ("\n\rDhrystone Benchmark\n\r"); Next_Ptr_Glob = &rta; Ptr_Glob = &rtb; Ptr_Glob->Ptr_Comp = Next_Ptr_Glob; Ptr_Glob->Discr = Ident_1; Ptr_Glob->variant.var_1.Enum_Comp = Ident_3; Ptr_Glob->variant.var_1.Int_Comp = 40; strcpy (Ptr_Glob->variant.var_1.Str_Comp, "DHRYSTONE PROGRAM, SOME STRING"); strcpy (Str_1_Loc, "DHRYSTONE PROGRAM, 1'ST STRING"); Arr_2_Glob [8][7] = 10; /* Was missing in published program. Without this statement, */ /* Arr_2_Glob [8][7] would have an undefined value. */ /* Warning: With 16-Bit processors and Number_Of_Runs > 32000, */ /* overflow may occur for this array element. */ { int n; n =10; Number_Of_Runs = n; } // printf ("Execution starts, %d runs through Dhrystone\n\r",Number_Of_Runs); print ("Execution starts"); print_long(Number_Of_Runs); print(" runs through Dhrystone\n\r"); /***************/ /* Start timer */ /***************/ /* for(_d1=0;_d1<100;_d1++) for(_d2=0;_d2<100;_d2++) { asm ("nop");}; p1 = dtime(); */ print("$time");//For Bench Mark print("\n"); for (Run_Index = 1; Run_Index <= Number_Of_Runs; ++Run_Index) { // print ("Execution: "); // print_long(Run_Index); // print("\n\n"); Proc_5(); // print("proc5 finished\n"); Proc_4(); // print("proc4 finished\n"); /* Ch_1_Glob == 'A', Ch_2_Glob == 'B', Bool_Glob == true */ Int_1_Loc = 2; Int_2_Loc = 3; strcpy (Str_2_Loc, "DHRYSTONE PROGRAM, 2'ND STRING"); Enum_Loc = Ident_2; Bool_Glob = ! Func_2 (Str_1_Loc, Str_2_Loc); // print("bool finished\n"); /* Bool_Glob == 1 */ while (Int_1_Loc < Int_2_Loc) /* loop body executed once */ { Int_3_Loc = 5 * Int_1_Loc - Int_2_Loc; /* Int_3_Loc == 7 */ Proc_7 (Int_1_Loc, Int_2_Loc, &Int_3_Loc); /* Int_3_Loc == 7 */ Int_1_Loc += 1; } /* while */ /* Int_1_Loc == 3, Int_2_Loc == 3, Int_3_Loc == 7 */ Proc_8 (Arr_1_Glob, Arr_2_Glob, Int_1_Loc, Int_3_Loc); // print("proc8 finished"); /* Int_Glob == 5 */ Proc_1 (Ptr_Glob); // print("proc1 finished"); for (Ch_Index = 'A'; Ch_Index <= Ch_2_Glob; ++Ch_Index) /* loop body executed twice */ { if (Enum_Loc == Func_1 (Ch_Index, 'C')) /* then, not executed */ { Proc_6 (Ident_1, &Enum_Loc); strcpy (Str_2_Loc, "DHRYSTONE PROGRAM, 3'RD STRING"); Int_2_Loc = Run_Index; Int_Glob = Run_Index; } } // print("for loop finished\n"); /* Int_1_Loc == 3, Int_2_Loc == 3, Int_3_Loc == 7 */ Int_2_Loc = Int_2_Loc * Int_1_Loc; Int_1_Loc = Int_2_Loc / Int_3_Loc; Int_2_Loc = 7 * (Int_2_Loc - Int_3_Loc) - Int_1_Loc; // print("arith finished\n"); /* Int_1_Loc == 1, Int_2_Loc == 13, Int_3_Loc == 7 */ Proc_2 (&Int_1_Loc); // print("proc_2 finished\n"); /* Int_1_Loc == 5 */ } /* loop "for Run_Index" */ print("$time");//For Bench Mark print("\n"); print("Execution ends\n\r"); print("$finish"); /**************/ /* Stop timer */ /**************/ /* p3 = dtime(); p2 = dtime(); p3 = p2 - p1; printf ("Execution ends\n\r"); printf("TMR1 = %lu\n\r",(unsigned long int)p3); */ } Proc_1 (Ptr_Val_Par) /******************/ REG Rec_Pointer Ptr_Val_Par; /* executed once */ { REG Rec_Pointer Next_Record = Ptr_Val_Par->Ptr_Comp; /* == Ptr_Glob_Next */ /* Local variable, initialized with Ptr_Val_Par->Ptr_Comp, */ /* corresponds to "rename" in Ada, "with" in Pascal */ structassign (*Ptr_Val_Par->Ptr_Comp, *Ptr_Glob); Ptr_Val_Par->variant.var_1.Int_Comp = 5; Next_Record->variant.var_1.Int_Comp = Ptr_Val_Par->variant.var_1.Int_Comp; Next_Record->Ptr_Comp = Ptr_Val_Par->Ptr_Comp; // print("In proc1"); Proc_3 (&Next_Record->Ptr_Comp); /* Ptr_Val_Par->Ptr_Comp->Ptr_Comp == Ptr_Glob->Ptr_Comp */ // print("In proc1 proc3 finished"); if (Next_Record->Discr == Ident_1) /* then, executed */ { // print(" In proc1 if\n"); Next_Record->variant.var_1.Int_Comp = 6; // print(" In proc1 before proc6 finished\n"); Proc_6 (Ptr_Val_Par->variant.var_1.Enum_Comp, &Next_Record->variant.var_1.Enum_Comp); // print(" In proc1 proc6 finished\n"); Next_Record->Ptr_Comp = Ptr_Glob->Ptr_Comp; Proc_7 (Next_Record->variant.var_1.Int_Comp, 10, &Next_Record->variant.var_1.Int_Comp); } else /* not executed */ structassign (*Ptr_Val_Par, *Ptr_Val_Par->Ptr_Comp); } /* Proc_1 */ Proc_2 (Int_Par_Ref) /******************/ /* executed once */ /* *Int_Par_Ref == 1, becomes 4 */ One_Fifty *Int_Par_Ref; { One_Fifty Int_Loc; Enumeration Enum_Loc; Int_Loc = *Int_Par_Ref + 10; do /* executed once */ if (Ch_1_Glob == 'A') /* then, executed */ { Int_Loc -= 1; *Int_Par_Ref = Int_Loc - Int_Glob; Enum_Loc = Ident_1; } /* if */ while (Enum_Loc != Ident_1); /* true */ } /* Proc_2 */ Proc_3 (Ptr_Ref_Par) /******************/ /* executed once */ /* Ptr_Ref_Par becomes Ptr_Glob */ Rec_Pointer *Ptr_Ref_Par; { if (Ptr_Glob != Null) /* then, executed */ *Ptr_Ref_Par = Ptr_Glob->Ptr_Comp; Proc_7 (10, Int_Glob, &Ptr_Glob->variant.var_1.Int_Comp); } /* Proc_3 */ Proc_4 () /* without parameters */ /*******/ /* executed once */ { Boolean Bool_Loc; Bool_Loc = Ch_1_Glob == 'A'; Bool_Glob = Bool_Loc | Bool_Glob; Ch_2_Glob = 'B'; } /* Proc_4 */ Proc_5 () /* without parameters */ /*******/ /* executed once */ { Ch_1_Glob = 'A'; Bool_Glob = false; } /* Proc_5 */ Proc_6 (Enum_Val_Par, Enum_Ref_Par) /*********************************/ /* executed once */ /* Enum_Val_Par == Ident_3, Enum_Ref_Par becomes Ident_2 */ Enumeration Enum_Val_Par; Enumeration *Enum_Ref_Par; { // print("proc6 entered\n"); *Enum_Ref_Par = Enum_Val_Par; if (! Func_3 (Enum_Val_Par)) /* then, not executed */ *Enum_Ref_Par = Ident_4; switch (Enum_Val_Par) { case Ident_1: *Enum_Ref_Par = Ident_1; break; case Ident_2: if (Int_Glob > 100) /* then */ *Enum_Ref_Par = Ident_1; else *Enum_Ref_Par = Ident_4; break; case Ident_3: /* executed */ *Enum_Ref_Par = Ident_2; break; case Ident_4: break; case Ident_5: *Enum_Ref_Par = Ident_3; break; } /* switch */ } /* Proc_6 */ Proc_7 (Int_1_Par_Val, Int_2_Par_Val, Int_Par_Ref) /**********************************************/ /* executed three times */ /* first call: Int_1_Par_Val == 2, Int_2_Par_Val == 3, */ /* Int_Par_Ref becomes 7 */ /* second call: Int_1_Par_Val == 10, Int_2_Par_Val == 5, */ /* Int_Par_Ref becomes 17 */ /* third call: Int_1_Par_Val == 6, Int_2_Par_Val == 10, */ /* Int_Par_Ref becomes 18 */ One_Fifty Int_1_Par_Val; One_Fifty Int_2_Par_Val; One_Fifty *Int_Par_Ref; { One_Fifty Int_Loc; Int_Loc = Int_1_Par_Val + 2; *Int_Par_Ref = Int_2_Par_Val + Int_Loc; } /* Proc_7 */ Proc_8 (Arr_1_Par_Ref, Arr_2_Par_Ref, Int_1_Par_Val, Int_2_Par_Val) /*********************************************************************/ /* executed once */ /* Int_Par_Val_1 == 3 */ /* Int_Par_Val_2 == 7 */ Arr_1_Dim Arr_1_Par_Ref; Arr_2_Dim Arr_2_Par_Ref; int Int_1_Par_Val; int Int_2_Par_Val; { REG One_Fifty Int_Index; REG One_Fifty Int_Loc; Int_Loc = Int_1_Par_Val + 5; Arr_1_Par_Ref [Int_Loc] = Int_2_Par_Val; Arr_1_Par_Ref [Int_Loc+1] = Arr_1_Par_Ref [Int_Loc]; Arr_1_Par_Ref [Int_Loc+30] = Int_Loc; for (Int_Index = Int_Loc; Int_Index <= Int_Loc+1; ++Int_Index) Arr_2_Par_Ref [Int_Loc] [Int_Index] = Int_Loc; Arr_2_Par_Ref [Int_Loc] [Int_Loc-1] += 1; Arr_2_Par_Ref [Int_Loc+20] [Int_Loc] = Arr_1_Par_Ref [Int_Loc]; Int_Glob = 5; } /* Proc_8 */ Enumeration Func_1 (Ch_1_Par_Val, Ch_2_Par_Val) /*************************************************/ /* executed three times */ /* first call: Ch_1_Par_Val == 'H', Ch_2_Par_Val == 'R' */ /* second call: Ch_1_Par_Val == 'A', Ch_2_Par_Val == 'C' */ /* third call: Ch_1_Par_Val == 'B', Ch_2_Par_Val == 'C' */ Capital_Letter Ch_1_Par_Val; Capital_Letter Ch_2_Par_Val; { Capital_Letter Ch_1_Loc; Capital_Letter Ch_2_Loc; Ch_1_Loc = Ch_1_Par_Val; Ch_2_Loc = Ch_1_Loc; if (Ch_2_Loc != Ch_2_Par_Val) /* then, executed */ return (Ident_1); else /* not executed */ { Ch_1_Glob = Ch_1_Loc; return (Ident_2); } } /* Func_1 */ Boolean Func_2 (Str_1_Par_Ref, Str_2_Par_Ref) /*************************************************/ /* executed once */ /* Str_1_Par_Ref == "DHRYSTONE PROGRAM, 1'ST STRING" */ /* Str_2_Par_Ref == "DHRYSTONE PROGRAM, 2'ND STRING" */ Str_30 Str_1_Par_Ref; Str_30 Str_2_Par_Ref; { REG One_Thirty Int_Loc; Capital_Letter Ch_Loc; Int_Loc = 2; while (Int_Loc <= 2) /* loop body executed once */ if (Func_1 (Str_1_Par_Ref[Int_Loc], Str_2_Par_Ref[Int_Loc+1]) == Ident_1) /* then, executed */ { Ch_Loc = 'A'; Int_Loc += 1; } /* if, while */ if (Ch_Loc >= 'W' && Ch_Loc < 'Z') /* then, not executed */ Int_Loc = 7; if (Ch_Loc == 'R') /* then, not executed */ return (true); else /* executed */ { if (strcmp (Str_1_Par_Ref, Str_2_Par_Ref) > 0) /* then, not executed */ { Int_Loc += 7; Int_Glob = Int_Loc; return (true); } else /* executed */ return (false); } /* if Ch_Loc */ } /* Func_2 */ Boolean Func_3 (Enum_Par_Val) /***************************/ /* executed once */ /* Enum_Par_Val == Ident_3 */ Enumeration Enum_Par_Val; { Enumeration Enum_Loc; Enum_Loc = Enum_Par_Val; if (Enum_Loc == Ident_3) /* then, executed */ return (true); else /* not executed */ return (false); } /* Func_3 */ /* Procedure for the assignment of structures, */ /* if the C compiler doesn't support this feature */ #ifdef NOSTRUCTASSIGN memcpy (d, s, l) register char *d; register char *s; register int l; { while (l--) *d++ = *s++; } #endif |