2009年6月24日 星期三
2009年6月10日 星期三
Makefile 語法簡介
有稍稍在 Linux 下碰過程式設計的開發者應該會知道,make 是用來將程式碼、函式庫、標頭檔及其它資源檔 build 成最終成果(即:最終的應用程式)的超強力輔助工具。
當然了,並不是非得動用到 make 才能 build 程式,或許有什麼程式設計魔人喜歡什麼都自己手動進行;但利用 make 及其參考檔(輸入檔案)Makefile 將會讓整個編譯工作輕鬆許多。若您曾經打包過 Debian Package,那麼應該會發現 debuan/rule 這個檔案的語法和 Makefile 幾乎是一模一樣,所以學習 Makefile 的語法對於 Debian Package Maintainer 而言也是一門必要的功課。
Makefile 語法:
以下為 Makefile 的基本語法:
註解:
以 # 開頭的即為註解。變數宣告:(有人稱之為巨集)
語法:
MACRO = value 變數名稱為大小寫相異。在慣例上,Makefile 內部使用的變數名稱使用小寫;而使用者很可能從命令列自行另外指定數值的變數,像是 CFLAGS,則是使用大寫。利用 MACRO = 來取消該變數。
在 Makefile 中,可利用 $(MACRO) 或 ${MACRO} 來存取已定義的變數。例:
targets = foo
$(targets): common.h
gcc -o $(targets) foo.c效果等同:
foo: common.h
gcc -o foo foo.c:= 語法
注意到,make 會將整個 Makefile 展開後,再決定變數的值。也就是說,變數的值將會是整個 Mackfile 中最後被指定的值。例:
x = foo
y = $(x) bar
x = xyz
# y 的值為 xyz bar在上例中,y 的值將會是 xyz bar,而不是 foo bar。
您可以利用 := 來避開這個問題。:= 表示變數的值決定於它在 Makefile 中的位置,而不是整個 Makefile 展開後最終的值。
在上例中,y 的值將會是 foo bar,而不是 xyz bar 了。
x := foo
y := $(x) bar
x := xyz
# y 的值為 foo bar?= 語法:
?= 是一個簡化的語法:若變數未定義,則替它指定新的值。否則,採用原有的值。例:若 FOO 未定義,則 FOO = bar;若 FOO 已定義,則 FOO 的值維持不變。
FOO ?= bar += 語法:
例:
CFLAGS = -Wall -g
CFLAGS += -O2此時 CFLAGS 的值就變成 -Wall -g -O2 了。
define 語法:
使用 define 語法的唯一優點是它可以讓變數直接使用『斷行』。例:上例可以視同於:
define foo
uname -a
echo $$SHELL
endef
all:
$(foo)
foo = uname -a; echo $$SHELL
all:
$(foo)注意到在上例中使用了 $$,讓 '$' 能傳到 Shell 中。
在 target 裡另外指定變數的值
可以在 target 裡另外指定變數的值。例:
foo = abc
all: foo = xyz
all:
echo $(foo)
# 此時,foo 的值為 xyz以下的語法提供了和上例相同的功能:
all: override foo = xyz
all: export foo = xyz
make 也可以存取環境變數。例:
all:
@echo $(CFLAGS)在上例中,雖然在 Makefile 裡雖然沒有指定 CFLAGS 的值,但 make 會試圖以環境變數來代出 CFLAGS 的值。
可搭配 wildcard 指令在變數裡展開 * ? [...] 等萬用字元。例:
objects=$(wildcard *.o) 規則:(Rule)
指示 make 如何進行編譯。
主要語法:
target: dependencies Commands 或
target: dependencies; Commands Commands Rule 指示了 make 如何建立 target;及何時要重新建立 target。
target:所要建立的檔案< /td> dependencies:相依項目。 make 會據此決定是否要重新編譯 target。 Commands:建立 target 的指令。 在 Makefile 裡並沒有限定 Rule 的先後順序。但預設上,make 會參考 all 這個目標項目,並依據它的 dependencies 來決定要建立哪些項目。若沒有 all 項目,則會採用 Makefile 裡的第一個項目。
target:(目標項目)
這個項目所要建立的檔案,必須以 : 結尾。例:其中,foo.o 是這個項目要建立的檔案;common.h 是相依性的項目/檔案;而 gcc -c foo.c則為要產生這個項目所要執行的指令。
foo.o: common.h
gcc -c foo.c
make 在編譯時,若發現 target 比較新,也就是 dependencies 都比 target 舊,那麼將不會重新建立 target,如此可以避免不必要的編譯動作。
若該項目並非檔案,則為 fake 項目。如此一來將不會建立 target 檔案。但為了避免 make 有時會無去判斷 target 是否為檔案或 fake 項目,建議利用 .PHONY 來指定該項目為 fake 項目。例:
.PHONY: clean
clean:
rm *.o在上例中,若不使用 .PHONY 來指定 clean 為 fake 項目的話,若目錄中同時存在了一個名為clean 的檔案,則 clean 這個項目將被視為要建立 clean 這個檔案,但 clean 這個項目卻又沒有任何的 dependencies,也因此,clean 項目將永遠被視為 up-to-date,永遠不會被執行。
因為利用了 .PHONY 來指定 clean 為 fake 項目,所以 make 不會去檢查目錄中是否存在了一個名為 clean 的檔案。如此也可以提昇 make 的執行效率。
其它類以 .PHONY 的語法請參考:
另外,如果某個非 fake 項目的 target 的 dependencies 包含了 fake 項目的話,因為 make 一定會執行 fake 項目,這樣一來,這個非 fake 項目的 target 一定也會被執行。這可能不是理想的做法。
GNU `make': 4.9 Special Built-in Target Names dependencies:(相依性項目,以空白間隔)
dependencies 是指定在建立 target 之前,必須先檢查的項目。可以不指定。例:
foo.o: common.h
gcc -c foo.c上例中是指:檢查 common.h。如果它的建立日期比 foo.o 新,就執行 gcc -c foo.c 來重新產生 foo.o。也就是說,可以依需求建立 dependencies,即使它和 target 一點關係也沒有。
相依性項目可以是 Makefile 中其它的 target。也因此,在建立該 target 之前,它會先檢查在 dependencies 裡所指定的所有 target。
Commands:(即為要執行的 Shell 指令)
必須以開頭。使用 Shell Script 語法。在 Makefile 裡,只要以 開頭都將會被視為 Shell Script 執行。 每條法則必須寫在同一行。每條 Command 會啟動一個新的 Shell,預設為 /bin/sh。若執行完某條 Command 但傳回了錯誤值,make 就會中斷執行。
因為每條 Command 會啟動一個新的 Shell,所以有時執行的指令必須寫在同一行,像是使用if 來進行條件判斷,此時可以用 ; 來分隔指令。例:
all:
if [ -f foo ]; then rm foo; fi而以下是錯誤示範:
這時因為 make 只會檢查最後一個指令的傳回值,所以在以上指令中,即使 subdir 不存在,但 make 並不會因而中斷執行,並會繼續執行 $(MAKE) 指令,而產生了不可預期的結果。
all:
cd subdir; $(MAKE)
為了避免這個問題,可以利用 && 來檢查其中某個指令是否成功執行,再決定是否執行下個指令。例:
all:
cd subdir && $(MAKE)特別字元:
@:不要顯示執行的指令。-:表示即使該行指令出錯,也不會中斷執行。
例:
因為 make 會一行一行將正在執行的 Commands 顯示在螢幕上,但您可以利用 @來暫時關閉這個功能。
.PHONY: clean
clean:
@echo "Clean..."
-rm *.o而 make 只要遇到任何錯誤就會中斷執行。但像是在進行 clean 時,也許根本沒有任何檔案可以 clean,因而 rm 會傳回錯誤值,因而導致 make 中斷執行。我們可以利用 - 來關閉錯誤中斷功能,讓 make 不會因而中斷。
隱性法則:
在上例中的:
foo.o: common.h
gcc -c foo.c由於產生 foo.o 的指令就是 gcc -c foo.c,因此在 Makefile 裡可以將其簡化為:
foo.o: common.h 此時 make 會依據 target 的副檔名來猜測該如何編譯 target。如此可以讓Makefile 更為簡潔。
您可以利用【空白指令】來避免 make 依據隱性法則而進行編譯。例:
foo.o: common.h 內部變數:
$?: 代表已被更新的 dependencies 的值。
也就是 dependencies 中,比 targets 還新的值。$@: 代表 targets 的值。 $<: 代表第一個 dependencies 的值。 $* : 代表 targets 所指定的檔案,但不包含副檔名。
例:這樣會將 foo1.c foo2.c foo3.c 中已有更新的內容印至印表機。
print: foo1.c foo2.c foo3.c
lpr -p $?
touch print內部函數:
您可以在 Makefile 使用 make 所支援的一些內部函數。詳情請參考:GNU `make': 8 Functions for Transforming Text條件判斷:
可以在 Makefile 中使用以下的條件判斷語法。但由於它們不是 rule,所以不可以
開頭;但其後要執行的指令則必須以 開頭,make 才會視其為 Shell 指令。 ifeq:(檢查 value1, value2 是否相等)ifneq:(提供和 ifeq 相反的功能)
ifeq (value1, value2)
...
else
...
endififdef:(檢查 variable 變數是否為空的)
ifneq (value1, value2)
...
else
...
endif
ifdef variable
...
else
...
endif
ifndef:(提供和 ifdef 相反的功能)
ifdef variable
...
else
....
endif引入檔案:
將外部檔案引入 Makefile 中。可以視為直接在此將該檔案內容全數插入 Makefile 中。
例:
將 foo.in 的內容全數引入 Makefile 裡。
include foo.in
可以同時引入多個檔案、使用變數 $(MACRO) 或是使用萬用字元(* ? 或 [...])。例:
include foo.in common*.in $(MAKEINCS) 子目錄:
如果該專案有多個目錄,且每一個目錄中都有 Makefile,則利用以下指令來進入子目錄並進行編譯:例:
cd dir && $(MAKE)
SUBDIRS = dir1 dir2 dir3
all:
for i in $(SUBDIRS); do
(cd $$i && make);
done
clean:
for i in $(SUBDIRS); do
(cd $$i && make clean);
done
install:
for i in $(SUBDIRS); do
(cd $$i && make install);
done
make 參數:
可以用 make 的參數來蓋過 Makefile 裡,用變數所指定的參數。例:
您可以在 Makefile 裡使用 override 來避免變數的值被 make 的參數所取代。例:
make CFLAGS="-g -O2" 可以在 make 後指定要重新建立的 target。例:
override CFLAGS = -Wall -g 以上會執行 Makefile 中的 clean 區段。
make clean
參考資訊:
GNU `make' 說明手冊(英文版)
2009年6月5日 星期五
如何使用C開發Verilog System Task/Function? (SOC) (Verilog) (Verilog PLI)
如何使用C開發Verilog System Task/Function? (SOC) (Verilog) (Verilog PLI)
Abstract
本文介紹使用C開發Verilog System task/function,以彌補Verilog功能的不足。
Introduction
使用環境 : Cadense NC-Verilog 5.4 + Visual C++ 6.0
Verilog PLI(Programming Language Interface)是Verilog所提供的機制,我們可以使用C語言開發自己的system task/function,以彌補在Verilog撰寫testbench的不足。
在此文件,將學習到:
1.如何在Verilog呼叫C function?
2.如何撰寫簡單的calltf routine與register function?
3.如何在Windows平台使用Cadence NC-Verilog編譯與連結?
如下圖所示,當simulator執行自己開發的system task時,會轉而執行C的function,執行完再回到Verilog。

使用C開發Verilog system task的流程如下圖所示:

Step 1:
建立C function
Step 2:
建立C function與Verilog system task的連結資料
hello_world.c / C
2 #include "vpi_user.h" // required by VPI application
3
4 // my own C function for Verilog
5 PLI_INT32 hello_world(PLI_BYTE8 *user_data) {
6 vpi_printf("Hello World from C!!\n");
7
8 return 0;
9 }
10
11 // defined in vpi_user.h
12 /*
13 typedef struct t_vpi_systf_data {
14 PLI_INT32 type; // vpiSysTask, vpiSysFunc
15 PLI_INT32 sysfunctype; // vpiSysTask, vpi[Int,Real,Time,Sized, SizedSigned]Func
16 PLI_BYTE8 *tfname; // first character must be `$'
17 PLI_INT32 (*calltf)(PLI_BYTE8 *);
18 PLI_INT32 (*compiletf)(PLI_BYTE8 *);
19 PLI_INT32 (*sizetf)(PLI_BYTE8 *); // for sized function callbacks only
20 PLI_BYTE8 *user_data;
21 } s_vpi_systf_data, *p_vpi_systf_data;
22 */
23
24 // associating C function with new verilog system task
25 // you can use your favorite function name
26 void register_my_systfs() {
27 s_vpi_systf_data tf_data; // defined in vpi_user.h
28
29 tf_data.type = vpiSysTask; // system task
30 tf_data.tfname = "$hello_world"; // name of system task
31 tf_data.calltf = hello_world; // C function name
32 tf_data.compiletf = NULL; // no compiletf routine
33
34 vpi_register_systf(&tf_data); // register system task to verilog
35 }
第2行
寫PLI一定要include。
第4行
PLI_INT32 hello_world(PLI_BYTE8 *user_data) {
vpi_printf("Hello World from C!!\n");
return 0;
}
自己的C function,僅簡單的顯示Hello World。由於struct s_vpi_systf_data規定自訂的C function的signature必須為PLI_INT32 (*calltf)(PLI_BYTE8 *),所以依照其規定宣告hello_world型別。
24行
// you can use your favorite function name
void register_my_systfs() {
s_vpi_systf_data tf_data; // defined in vpi_user.h
tf_data.type = vpiSysTask; // system task
tf_data.tfname = "$hello_world"; // name of system task
tf_data.calltf = hello_world; // C function name
tf_data.compiletf = NULL; // no compiletf routine
vpi_register_systf(&tf_data); // register system task to verilog
}
建立C function與Verilog system task的連結資料,s_vpi_systf_data定義在vpi_user.h,其完整定義如下:
PLI_INT32 type; // vpiSysTask, vpiSysFunc
PLI_INT32 sysfunctype; // vpiSysTask, vpi[Int,Real,Time,Sized, SizedSigned]Func
PLI_BYTE8 *tfname; // first character must be `$'
PLI_INT32 (*calltf)(PLI_BYTE8 *);
PLI_INT32 (*compiletf)(PLI_BYTE8 *);
PLI_INT32 (*sizetf)(PLI_BYTE8 *); // for sized function callbacks only
PLI_BYTE8 *user_data;
} s_vpi_systf_data, *p_vpi_systf_data;
register_my_systfs() function主要的目的在於將s_vpi_systf_data struct填滿, 名稱不一定要取register_my_systfs,可以自行命名。
29行
定義為System Task。
30行
定義Verilog system task名稱
31行
指定自己寫的C function名稱,為function pointer,為了callback使用。
32行
由於沒用到compiletf,所以設定為NULL。
34行
註冊新的system task。
Step 3:
在simulator註冊新Verilog system task
複製C:\Program Files\Cadence Design Systems\IUS\tools\src\vpi_user.c到目前目錄,將vpi_user.c修改如下:
2 * |-----------------------------------------------------------------------|
3 * | |
4 * | Copyright Cadence Design Systems, Inc. 1985, 1988. |
5 * | All Rights Reserved. Licensed Software. |
6 * | |
7 * | |
8 * | THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF CADENCE DESIGN SYSTEMS |
9 * | The copyright notice above does not evidence any actual or intended |
10 * | publication of such source code. |
11 * | |
12 * |-----------------------------------------------------------------------|
13 */
14
15 /*
16 * |-------------------------------------------------------------|
17 * | |
18 * | PROPRIETARY INFORMATION, PROPERTY OF CADENCE DESIGN SYSTEMS |
19 * | |
20 * |-------------------------------------------------------------|
21 */
22
23 #include <stdarg.h>
24 #include "vpi_user.h"
25 #include "vpi_user_cds.h"
26
27
28 /* extern void setup_test_callbacks();*/
29
30 /* ----------------------------------------------------------------
31 The following is an example of what should be included in this file:
32
33 extern void setup_my_callbacks(); <-- Add a declaration for your routine.
34
35 void (*vlog_startup_routines[])() =
36 {
37 $*** add user entries here ***$
38
39 setup_my_callbacks, <-- Add your routine to the table.
40
41 0 $*** final entry must be 0 ***$
42
43 };
44 ------------------------------------------------------------------ */
45
46 extern void register_my_systfs();
47
48 void (*vlog_startup_routines[VPI_MAXARRAY])() =
49 {
50 register_my_systfs,
51 0 /*** final entry must be 0 ***/
52 };
46行
使用extern宣告在hello_world.c的register_my_systfs。
48行
{
register_my_systfs,
0 /*** final entry must be 0 ***/
};
設定simulator啟動時,載入PLI的array,注意最後一個element必須為0。
Step 4:
使用PLI Wizard產生Dynamic Link Library (libvpi.dll)
啟動PLI Wizard
開始->程式集->Cadence Design Systems->Design & Verification->PLI Wizard
File -> New Session

選擇VPI Application與libvp

加入hello_world.c,vpi_user.c會自動抓進來

加入VC6的include path: C:\Program Files\Microsoft Visual Studio\VC98\Include
選擇C語言

按Finish完成

選(Y)

按Close離開

File -> Exit離開PLI Wizard
Step 5:
設定libvpi.dll路徑
在Path環境變數加上libvpi.dll的路徑,注意必須加在C:\Program Files\Cadence Design Systems\IUS\tools\bin;C:\Program Files\Cadence Design Systems\IUS\tools\lib;C:\Novas\Debussy\bin;之前,否則PLI Wizard啟動會有問題。
設定好後須登出在登入,path才會生效。
若只是為了測試,可將libvpi.dll放在與*.v同目錄下即可。
Step 6:
在Verilog使用新的system task
hello_world.v / Verilog
2
3 initial begin
4 $hello_world;
5 #10 $finish;
6 end
7
8 endmodule
Step 7:
執行NC-Verilog

完整程式碼下載
pli_hello_world.7z
Conclusion
PLI在Verilog的IEEE標準中有明確定義,但如何compiling與linking方面,就與simulator與OS有關,IEEE標準並沒有加以定義。本文雖然只是一個小小的Hello World,已經展示了Verilog PLI如何在Windows平台與Cadence NC-Verilog compiling與linking,並看到Verilog如何呼叫一個C的function。






