11. FPGA実装 デザインウェーブ 付録基板
DW誌基板に実装してみます。
調達部品としては、24MHzクロック、UART ICと RS232Cコネクタ等です。部品は、秋月から調達しました。
部品代としては、1000円もあれば十分でしょう。
筆者は、RS232Cのドライプポート基板がありましたので、それを流用しました。
DWM誌基板に載せるために、記述のダイエットに苦労しました。それでも、5段パイプライン、32x32ビット=64ビット乗算付NATIVE32ビットのマイクロプロセッサです。ドライストーンベンチはRAMが足りず実行できませんが、それでも50DMIPS相当のプロセッサになります。オリジナルより高速化しているH8プロジェクトの10倍近くのパフォーマンスになります。
折角、32x32=64ビット乗算器を積んでいるので、アプリケーションプログラムは、電卓にしましょう。(乗算に32クロックかかるとはいえ、AREAの30%もこれに食っていてダイエットに時間をとりましたので執着があります。)
電卓の端末は、テラタームを使いました。FPGAのIFは、下図のようにASYNC_RESET,RXD,TXD,CLOCKの4本だけです。
論理合成
このデバイスにフィットさせるため、IFDEF文を多用しなんとかフィットさせました。AREA 最小で合成しています。NORMAL、SPEED優先では、フィットしません。
また、内部のFFを1ビットいじっただけでも、前のピンアサインでは、フィットしなくなってしまいます。リソース使用率が、99%なのでいたしかたありません。RTLシミュレーションをがっちりやってハードのデバッグはないように一発で動かしましょう。論理合成は、筆者のマシンで20分程でした。
もし、SPEED優先記述でフィットする同じSPEEDランクのCYCLONEなら、80数MHzは可能だと思います。
FPGAのプログラム
TOOLS->PROGRAMMERでFPGAにプログラムを送ります。yacc.sofファイルを指定します。
アプリケーション
テラタームの設定
テラターム設定は以下のように、します。Windows付属のハイパーターミナルでもOKですが、同じようにターミナルの設定をします。
電卓の操作 テラターム画面
リセット後に、オープニングメッセージを表示したあとYACC>のプロンプトが表示されます。
普通の電卓のように式をいれてEnterで、計算式=結果が表示され、再びプロンプト表示されるプログラムです。
数値以外に括弧が使えます。Cプログラムで、int宣言しているので、32ビットまでの計算が可能です。(long longで書けば64ビットになります。ハードの割り算も32ビットに対応したので、ライブラリを整えればの話ですが。加減算と乗算に関してはライブラリなしで可能です。)
Cソースプログラム
YACCプロジェクトなので、FLEXやBISONを使いたいところですが、RAMが4Kしかありませんので、無理な話です。人手による構文解析を行います。なお、割り込み(RX割り込み)もCで記述しています。逆アセンブルリストを見て使用しているスタック量や、$raのセーブ先を調べてフィードバックして書いています。他に注意する点は、volatile宣言でしょう。メインループは、無限ループですが、これがないと無限ループには違いないのですが、Cコンパイラの最適化が働いてしまって意図したように動いてはくれません。
乗除算器のテストもはいって見にくいソースですが、参考まで。
//YACC Project on CYCLONE 4KRAM //Jul.15.2004 Simple 32bit calculator // //Verilog Specific Routines #define print_port 0x0ff0 #define print_char_port 0x0ff1 #define print_int_port 0x0ff2 #define print_long_port 0x0ff4 #define print_long_port 0x0ff4 // #define uart_wport 0x0fff #define uart_rport 0x0ffc #define int_set_address 0x0ff8 #define BUFFER_SIZE 120 unsigned char * read_ptr; char buffer[BUFFER_SIZE];//="abc" はNG char result_buffer[8];//8+1 unsigned char sym; unsigned char* char_ptr; long term(void); long factor(void); long expression(void); void calculator(); int volatile int_flag=0;//volatile は必須。ないとcalculatorに行かない char buf[2]; #define ECHO //#define DEBUG void print_uart(unsigned char* ptr)// { while (*ptr) { *(volatile unsigned char*)uart_wport=*(ptr++); } //*(volatile unsigned char*)uart_wport=0x00;//Write Done } void putc_uart(unsigned char c)// { *(volatile unsigned char*)uart_wport=c; } unsigned char read_uart()//Verilog Test Bench Use { return *(volatile unsigned char*)uart_rport; } 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 } // インタラプトサービスルーチン //#pragma interrupt //0D/0A が着たら、0をbufferに書き込み、計算する。 //それ以外は、READ_PTRをUP //Overflow だったらMessageOUT void interrupt(void) { char c; #define SAVE_REGISTERS (13*4) asm("addiu $sp,$sp,-52 ;");//SAVE_REGISTERS asm(" sw $a0,($sp)");//使用しているレジスタ退避 asm(" sw $v0,4($sp)"); asm(" sw $v1,8($sp)"); asm(" sw $a1,12($sp)"); asm(" sw $s0,16($sp)"); asm(" sw $s1,20($sp)"); asm(" sw $s2,24($sp)"); asm(" sw $a3,28($sp)"); asm(" sw $s4,32($sp)"); asm(" sw $s5,36($sp)"); asm(" sw $s6,40($sp)"); asm(" sw $s7,44($sp)"); asm(" sw $a2,48($sp)"); c=read_uart();//uart read port から1バイト読み込み if ( c == 0x0a || c==0x0d ) { *read_ptr = 0;//string 終端 read_ptr=buffer;//read_ptr 初期化 putc_uart(0x0a); putc_uart(0x0d); if (int_flag) print("PError!\n"); else int_flag=1; } else if ( c == '\b' && read_ptr > buffer ){//バックスペース処理 putc_uart('\b'); read_ptr--; }else if ( read_ptr>= buffer+BUFFER_SIZE){// overflow //とりあえず *read_ptr = 0;//string 終端 read_ptr=buffer;//read_ptr 初期化 print_uart("Sorry Overflow..!\n"); }else {//ポインタインクリメント putc_uart(c); *(read_ptr++) = c; } #ifdef DEBUG print(buffer); print("\n\n"); #endif //使用したレジスタを復元 asm(" lw $a0,($sp)"); asm(" lw $v0,4($sp)"); asm(" lw $v1,8($sp)"); asm(" lw $a1,12($sp)"); asm(" lw $s0,16($sp)"); asm(" lw $s1,20($sp)"); asm(" lw $s2,24($sp)"); asm(" lw $a3,28($sp)"); asm(" lw $s4,32($sp)"); asm(" lw $s5,36($sp)"); asm(" lw $s6,40($sp)"); asm(" lw $s7,44($sp)"); asm(" lw $a2,48($sp)"); asm("addiu $sp,$sp,52 ;");//SAVE_REGISTERS //割り込みルーチンをいじったら必ず調整すること asm("lw $ra,20($sp);");//逆アセンブルリストを見て調整する。 asm("addiu $sp,$sp,24 ;");//逆アセンブルリストを見て調整する asm("jr $26");//Return Interrupt asm("nop");//遅延スロット // //逆アセンブルリストを見て調整する //Interrupt 内部でサブルーチンをCALLしている場合、Return AddressをSTACKから元に戻す。 //スタック使用量の分を戻す //Interuupt されて実行されなかった命令は、$26に入っている。 //上記のように書くと、アセンブラが遅延SLOTに置いてくれるようだ。 } inline void set_interrupt_address() { *(volatile unsigned long*)int_set_address=(unsigned long)interrupt; read_ptr=buffer; } void print_longlong(long long val)//Little Endian write out 32bit number { #ifdef DOS printf("%x",val); #else *(volatile unsigned long*)print_long_port=val>>32; *(volatile unsigned long*)print_long_port=val; #endif } /* void long_long_check_test() { long ii; long n; long i; print("Testing longlong test"); n=10; ii =-1; for (i=2; i<=n;i++){ ii -=i; } print("1+2+3...+100="); print_longlong(ii); print("\n"); print("n*(n+1)/2="); print_longlong(-n*(n+1)/2); } */ /* void mod_test(long i) { if (i<0) i=-1; print_long(i%10); print_long(i/10); } */ void getsym() { while ( *char_ptr==' ' || *char_ptr=='\n' || *char_ptr=='\r' ) char_ptr++; if (*char_ptr ==0) { sym=0; }else { sym=*(char_ptr++); } } inline void init_parser() { char_ptr=buffer; getsym(); } long evaluate_number(void) { long x ; x=sym-'0'; while(*char_ptr >='0' && *char_ptr <='9') { x = x * 10 + *char_ptr - '0'; char_ptr++; } getsym(); return x; } long expression(void) { long term1,term2; unsigned char op; op=sym; if (sym=='+' || sym=='-') getsym(); term1=term(); if (op=='-') term1=-term1; while (sym=='+' || sym=='-') { op=sym; getsym(); term2=term(); if (op=='+') term1= term1+term2; else term1= term1-term2; } return term1; } long term(void) { unsigned char op; long factor1,factor2; factor1=factor(); while ( sym=='*' || sym=='/' || sym=='%'){ op=sym; getsym(); factor2=factor(); switch (op) { case '*': factor1= factor1*factor2; break; case '/': factor1= factor1/factor2; break; case '%': factor1= factor1%factor2; break; } } return factor1; } inline long parse_error() { print_uart("\n parse error occurred\n"); return 0; } long factor(void) { int i; if (sym>='0' && sym <='9') return evaluate_number(); else if (sym=='('){ getsym(); i= expression(); if (sym !=')'){ parse_error(); } getsym(); return i; }else if (sym==0) return 0; else return parse_error(); } /* 文字列の並びを逆順にする */ char *strrev(char *s) { char *ret = s; char *t = s; char c; while( *t != '\0' )t++; t--; while(t > s) { c = *s; *s = *t; *t = c; s++; t--; } return ret; /* 並べ替えた文字列の先頭へのポインタを返す */ } /* 整数を文字列に変換する */ void itoa(int val, char *s) { char *t; int mod; if(val < 0) { *s++ = '-'; val = -val; } t = s; while(val) { mod = val % 10; *t++ = (char)mod + '0'; val /= 10; } if(s == t) *t++ = '0'; *t = '\0'; strrev(s); } void calculator() { long result; //パーサ初期化 init_parser(); //計算 result=expression(); //結果表示 #ifdef DEBUG print("\n"); print(buffer); print("="); print_long(result); print("[Hex] "); itoa(result,result_buffer); print(result_buffer); print("[Dec]\n"); #else print_uart(buffer); putc_uart('='); print_uart(result_buffer); putc_uart(0x0a); putc_uart(0x0a); putc_uart(0x0d); #endif } void strcpy(char* dest,char* source) { char* dest_ptr; dest_ptr=dest; while(*source) { *(dest++) =*(source++); } ; *dest=0;//Write Done } void calculator_test(char* ptr) { strcpy(buffer,ptr); calculator(); } void main() { unsigned i; long long k; set_interrupt_address(); /* calculator_test("105*31"); calculator_test("-105/31"); calculator_test("(-105)/(-31)"); calculator_test("(-105)/(31)"); calculator_test("(1050)/(-31)"); calculator_test("105%31"); calculator_test("-105%31"); calculator_test("(-105)%(-31)"); calculator_test("(-105)%(31)"); calculator_test("(1050)%(-31)"); calculator_test("2147483647/13213210"); calculator_test("(-2147483647)/1654760"); calculator_test("(-2147483647)/(-14320)"); calculator_test("(+2147483647)/(-153420)"); calculator_test("2147483647%14324320"); calculator_test("(-2147483647)%14320"); calculator_test("(-2147483647)%(-1540)"); calculator_test("(+2147483647)%(-143220)"); /* calculator_test("-17/(1)"); calculator_test("117/(-1)"); calculator_test("-(-13432234)/(-4323437)"); calculator_test("-(-143243243)/(743243)"); calculator_test("-123459*(-3454)"); calculator_test("-1321*(73213)"); calculator_test("+543541*(7213)"); calculator_test("+1432*(-7322)"); calculator_test("-165*(-7232)"); calculator_test("-(1321)*(-7111)"); calculator_test("-(-1543)*(-743243)"); */ // uart test do not forget \n /* print_uart("1+1\n"); print_uart("1-2\n"); print_uart("10*200\n"); */ putc_uart(0x0a); putc_uart(0x0d); print_uart("Welcome to YACC World.Jul.15.2004 www.sugawara-systems.com"); putc_uart(0x0a); putc_uart(0x0d); print_uart("YACC>"); label: if (int_flag){ int_flag=0; calculator(); print_uart("YACC>"); } goto label; // mod_test(109); // long_long_check_test(); // strcpy(buffer,"2+4"); print("$finish"); } |
アプリケーションその2
CPUなので、アプリケーションもソフト次第です。
数当てゲームです。馬鹿馬鹿しいゲームですが、ハイクラスC言語という本のサンプルに載っていたのを参考にコーディングしました。
Cコンパイラが使えると本当に便利ですね。上のアプリケーションを改造して書きました。(RTLシミュレーションはしていません。)
ある程度、ハードのバグが取れてきたら、Cの方がデバッグも簡単にできますし、アプリケーションしかりです。
ところで、Quartusの4.0では、未だRAMの初期化が単独で、出来ないので、
という手順を踏んで、ようやく、アプリケーションが動き出します。(この他に最初はシリアルターミナルの設定があります。)
このうちQuartusのコンパイル部分が、20分位あります。OSを準備して、ターミナルから、アプリケーションプログラムを読み出せばいいのですが、4KBのスペースのなかで四苦八苦するのも面倒なので、やっていません。Quartas4.1では、RAMの初期化ができるようになるらしいのでそれを待ちたいと思います。
ランダム関数をPowerOnからの64ビットカウンタで作りたかったのですが、LUT切れでかないませんでした。
Cソースコード
//YACC Project on CYCLONE 4KRAM //Jul.15.2004 Simple 32bit calculator // //Verilog Specific Routines #define print_port 0x0ff0 #define print_char_port 0x0ff1 #define print_int_port 0x0ff2 #define print_long_port 0x0ff4 #define print_long_port 0x0ff4 // #define uart_wport 0x0fff #define uart_rport 0x0ffc #define int_set_address 0x0ff8 #define BUFFER_SIZE 120 unsigned char * read_ptr; char buffer[BUFFER_SIZE];//="abc" はNG char result_buffer[8];//8+1 unsigned char sym; unsigned char* char_ptr; long term(void); long factor(void); long expression(void); void calculator(); int volatile int_flag=0;//volatile は必須。ないとcalculatorに行かない char buf[2]; #define ECHO //#define DEBUG void print_uart(unsigned char* ptr)// { while (*ptr) { *(volatile unsigned char*)uart_wport=*(ptr++); } //*(volatile unsigned char*)uart_wport=0x00;//Write Done } void putc_uart(unsigned char c)// { *(volatile unsigned char*)uart_wport=c; } unsigned char read_uart()//Verilog Test Bench Use { return *(volatile unsigned char*)uart_rport; } 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 } // インタラプトサービスルーチン //#pragma interrupt //0D/0A が着たら、0をbufferに書き込み、計算する。 //それ以外は、READ_PTRをUP //Overflow だったらMessageOUT void interrupt(void) { char c; #define SAVE_REGISTERS (13*4) asm("addiu $sp,$sp,-52 ;");//SAVE_REGISTERS asm(" sw $a0,($sp)");//使用しているレジスタ退避 asm(" sw $v0,4($sp)"); asm(" sw $v1,8($sp)"); asm(" sw $a1,12($sp)"); asm(" sw $s0,16($sp)"); asm(" sw $s1,20($sp)"); asm(" sw $s2,24($sp)"); asm(" sw $a3,28($sp)"); asm(" sw $s4,32($sp)"); asm(" sw $s5,36($sp)"); asm(" sw $s6,40($sp)"); asm(" sw $s7,44($sp)"); asm(" sw $a2,48($sp)"); c=read_uart();//uart read port から1バイト読み込み if ( c == 0x0a || c==0x0d ) { *read_ptr = 0;//string 終端 read_ptr=buffer;//read_ptr 初期化 putc_uart(0x0a); putc_uart(0x0d); if (int_flag) print("PError!\n"); else int_flag=1; } else if ( c == '\b' && read_ptr > buffer ){//バックスペース処理 putc_uart('\b'); read_ptr--; }else if ( read_ptr>= buffer+BUFFER_SIZE){// overflow //とりあえず *read_ptr = 0;//string 終端 read_ptr=buffer;//read_ptr 初期化 print_uart("Sorry Overflow..!\n"); }else {//ポインタインクリメント putc_uart(c); *(read_ptr++) = c; } #ifdef DEBUG print(buffer); print("\n\n"); #endif //使用したレジスタを復元 asm(" lw $a0,($sp)"); asm(" lw $v0,4($sp)"); asm(" lw $v1,8($sp)"); asm(" lw $a1,12($sp)"); asm(" lw $s0,16($sp)"); asm(" lw $s1,20($sp)"); asm(" lw $s2,24($sp)"); asm(" lw $a3,28($sp)"); asm(" lw $s4,32($sp)"); asm(" lw $s5,36($sp)"); asm(" lw $s6,40($sp)"); asm(" lw $s7,44($sp)"); asm(" lw $a2,48($sp)"); asm("addiu $sp,$sp,52 ;");//SAVE_REGISTERS //割り込みルーチンをいじったら必ず調整すること asm("lw $ra,20($sp);");//逆アセンブルリストを見て調整する。 asm("addiu $sp,$sp,24 ;");//逆アセンブルリストを見て調整する asm("jr $26");//Return Interrupt asm("nop");//遅延スロット // //逆アセンブルリストを見て調整する //Interrupt 内部でサブルーチンをCALLしている場合、Return AddressをSTACKから元に戻す。 //スタック使用量の分を戻す //Interuupt されて実行されなかった命令は、$26に入っている。 //上記のように書くと、アセンブラが遅延SLOTに置いてくれるようだ。 } inline void set_interrupt_address() { *(volatile unsigned long*)int_set_address=(unsigned long)interrupt; read_ptr=buffer; } void getsym() { while ( *char_ptr==' ' || *char_ptr=='\n' || *char_ptr=='\r' ) char_ptr++; if (*char_ptr ==0) { sym=0; }else { sym=*(char_ptr++); } } inline void init_parser() { char_ptr=buffer; getsym(); } long evaluate_number(void) { long x ; x=sym-'0'; while(*char_ptr >='0' && *char_ptr <='9') { x = x * 10 + *char_ptr - '0'; char_ptr++; } getsym(); return x; } long expression(void) { long term1,term2; unsigned char op; op=sym; if (sym=='+' || sym=='-') getsym(); term1=term(); if (op=='-') term1=-term1; while (sym=='+' || sym=='-') { op=sym; getsym(); term2=term(); if (op=='+') term1= term1+term2; else term1= term1-term2; } return term1; } long term(void) { unsigned char op; long factor1,factor2; factor1=factor(); while ( sym=='*' || sym=='/' || sym=='%'){ op=sym; getsym(); factor2=factor(); switch (op) { case '*': factor1= factor1*factor2; break; case '/': factor1= factor1/factor2; break; case '%': factor1= factor1%factor2; break; } } return factor1; } inline long parse_error() { print_uart("\n parse error occurred\n"); return 0; } long factor(void) { int i; if (sym>='0' && sym <='9') return evaluate_number(); else if (sym=='('){ getsym(); i= expression(); if (sym !=')'){ parse_error(); } getsym(); return i; }else if (sym==0) return 0; else return parse_error(); } void main() { unsigned i; long long k; int number[4]={949,233,102,444}; int game_number=0; int success=0; int counter=0; int result; set_interrupt_address(); putc_uart(0x0a); putc_uart(0x0d); print_uart("ようこそYACCプロジェクトへ .Jul.17.2004 www.sugawara-systems.com"); putc_uart(0x0a); putc_uart(0x0d); main_loop: success=0; game_number++; if (game_number>=4) game_number=0; counter=0; print_uart("数当てゲームだぜ。1から1000までの数字をあててみな! "); putc_uart(0x0a); putc_uart(0x0d); print_uart("YACC>"); label: if (int_flag){ int_flag=0; counter++; //パーサ初期化 init_parser(); //計算 result=expression(); if (result < number[game_number]) print_uart("ちいさぇな "); else if ( result> number[game_number]){ print_uart("でっけぇな "); }else { print_uart("********* 大当たり。******* "); success=1; } putc_uart(0x0a); putc_uart(0x0d); if (success){ print_uart("判定:"); if (counter <5) print_uart("大天才"); else if (counter <7) print_uart("天才"); else if (counter <11 ) print_uart("非凡"); else if (counter <13 ) print_uart("凡人"); else print_uart("判定できん。。。。!"); putc_uart(0x0a); putc_uart(0x0d); putc_uart(0x0a); putc_uart(0x0d); } print_uart("YACC>"); } if (success) goto main_loop; goto label; // mod_test(109); // long_long_check_test(); // strcpy(buffer,"2+4"); print("$finish"); } |