眾所周知,用于FPGA開發的硬件描述語言(HDL)主要有兩種:Verilog和VHDL,VHDL的出現時間要比Verilog早,Verilog由于其簡單的語法,和C語言的相似性,目前被各大公司廣泛使用。
其實我大學時學習的是VHDL語言,后來由于公司都是使用的Verilog,又重新學習了Verilog,好在有C語言基礎,Verilog很快就上手了。
Verilog標準文檔主要有3個版本,分別是:
Verilog-1995
Verilog-2001
Verilog-2005
都是由IEEE頒布。目前最新的Verilog標準是2005版,相比于前兩個版本,2005更簡潔,更靈活。
雖然一些官方的代碼,如Xilinx一些IP核代碼,為了兼容以前的綜合工具,還是基于Verilog-2001標準,但我還是強烈建議你使用最新的Verilog-2005標準。
IEEE-2005
所以,本文都是基于IEEE-2005語法標準,即《IEEE P1364-2005/D3:Draft Standard for Verilog HardwareDescription Language》官方標準文檔。
良好的代碼規范可以提高代碼的可讀性、可復用性、簡潔清晰,這也是一種職業素質的體現。
我們的目標是:
封面
都有哪些內容?
命名
文件命名
端口命名
變量命名
參數命名
結構
整體結構
端口聲明
空格和縮進讓代碼更清晰
小括號增加可讀性
例化
注釋
其他
IEEE-2005標準下載
命名
命名主要包括文件和模塊命名,端口命名,變量命名,參數命名等,概括來說就是:有意義,簡短易讀,重要的是不要使用拼音!
文件命名
文件名和模塊名保持一致,一個文件只寫一個模塊。
文件命名 文件命名要有含義,且簡短易讀,文件名統一使用小寫字母,并使用下劃線分割文件名。
底層驅動類模塊命名,使用drv_xxx類型,如:drv_led.v,drv_i2c.v,drv_ad7606.v
串口發送一字節:uart_tx_byte.v
異步和同步FIFO,并指示深度和寬度:fifo_async_512_16.v和fifo_sync_256_64.v
TestBench文件名問源文件名后加_tb,如源文件drv_led.v,則對應的testbench文件命名為drv_led_tb.v
頂層模塊統一命名為top.v,或top_project_name.v,如EEPROM讀寫示例工程頂層命名為top_eeprom_demo.v
端口命名
端口命名和文件命名一樣,統一使用小寫字母,并使用下劃線進行分割。
如果是頂層模塊,而且是連接到實際的FPGA管腳,后加_pad_i,_pad_o,_pad_io來命名,表示連接到實際的FPGA硬件管腳上。
頂層端口命名
變量命名
時鐘信號統一使用clk命名,如果是特定時鐘頻率,可以在后面添加時鐘頻率,如clk_50m
復位信號統一使用rst命名,如果是低電平有效,后加_n表示,如rst_n
標志位命名:flag_rise/flag_fall/flag_clr
寄存器打拍信號命名添加_reg:reg rxd_reg
移位寄存器命名添加后綴_sreg:reg [3:0] busy_sreg
部分通用的縮寫:
縮寫 | 全拼 | 含義 |
---|---|---|
rst | reset | 復位 |
clk | cloc | 時鐘 |
rd | read | 讀取 |
wr | write | 寫入 |
addr | address | 地址 |
ack | acknowledge | 響應 |
參數命名
Verilog中的參數類似于C語言中的define,主要有以下兩類localparam和parameter,兩者的區別是前者不可以在例化時進行參數傳遞,而后者可以在例化時進行參數傳遞。
其他的變量,文件名都是統一小寫,只有參數定義有全部大寫的待遇,當需要定義一些常量時,可以通過參數聲明指定一個有意義的名稱。如:
parameterLED_ON=1'b0; parameterLED_OFF=!LED_ON; parameterBAUD_RATE=115200; parameterTIME_100MS=25_000_000;
狀態機的狀態通常使用localparam來定義,并指定有意義的名稱,統一使用Sn_NAME的格式,并指定位寬。如:
localparamS0_IDLE=4'd0; localparamS1_START=4'd1; localparamS2_DOING=4'd2; localparamS3_END=4'd3; localparamS_FINISH=4'd4; localparamS_ERROR='d5;//autoadaptive
結構
整體結構
建議Verilog模塊文件按照如下結構進行書寫。
/*1.文件頭:說明版權信息,文件功能,作者,版本歷史等*/ /*2.端口聲明:input/output/inout*/ /*3.宏定義:`define*/ /*4.參數定義:localparam/parameter*/ /*5.寄存器定義:reg*/ /*6.線網類型定義:wire*/ /*7.互聯定義:assign*/ /*8.時序邏輯描述:always*/
示例:
/*1.filehead*/ /*********************************************************** Copyright2021'wechat:mcu149'.Allrightsreserved. FileName:drv_led.v Function:leddriver Author:mcu149 SVN: SVNRevision:1490 SVNDate:2021-06-051949 Revision: 2020-09-09:Rev1.0 2020-10-01:Rev1.1 ************************************************************/ /*2.input/output/inout*/ moduledrv_led( //Inputs inputrst_n, inputclk_50m, inputen, //Outputs outputled1, outputregled2=0//defineinitialvalueis0 ); /*3.define*/ /*4.parameter/localparam*/ parameterTIME_500MS=32'd25_000_000; /*5.reg*/ reg[31:0]cnt=0; /*6.wire*/ wireflag_toggle=(cnt==TIME_500MS); /*7.assign*/ assignled1=(en==0)?0:cnt[20]; /*8.alwaysblock*/ always@(posedgeclk_50m)begin if(!rst_n) cnt<=?0; ????else?if(flag_toggle) ????????cnt?<=?0; ????else? ????????cnt?<=?cnt?+?1; end always?@?(posedge?clk_50m)?begin ????if(!rst_n) ????????led2?<=?0; ????else?if(!en)? ????????led2?<=?0; ????else?if(en)?begin ????????if(flag_toggle) ????????????led2?<=?!led2; ????end end endmodule
端口聲明
輸入端口放在一起,輸出端口放在一起,雙向端口放在一起。如果某個輸出信號需要確定初始值,可以在端口定義時直接進行指定,這也是Verilog-2005新添加的功能。
端口命名
這一點有些朋友可能是按照功能進行劃分,如連接同一芯片的放在一起。
輸入輸出分開放的好處是,在例化時可以很方便的區分哪些是輸入哪些是輸出。
空格和縮進讓代碼更清晰
運算符兩端增加一個空格,可以讓程序結構更清晰,可讀性更高
縮進風格采用KR風格,即begin寫在行尾,不占用單獨一行,end單獨占用一行
縮進統一使用4個空格來代替TAB鍵
if/else等語句只有一行時,可以省略begin-end
合理添加空行進行塊區分,不同的always塊進行換行隔開
以下是兩種代碼的書寫規范,合理縮進,合理增加空格大大增加了可讀性。
合理縮進
小括號增加可讀性
在學校里有些考試題,為了考察學生對各種運算符優先級的掌握程度,出一些反人類的題目。
而做實際項目不像考試,追求的是可讀性和易用性,所以當使用多個運算符時,為了增強可讀性,避免歧義,不要吝嗇使用小括號來表示運算的優先級。
運算符優先級
例化
例化可以認為是FPGA開發的靈魂所在了,例化的過程其實就是硬件模塊的調用過程,比如我們用Verilog描述了一個3-8譯碼器的模塊,可以在不同的地方去使用(例化)它,并分別命名為ut0/ut1/ut2等等,而且還可以在例化時指定參數,如串口發送和接收模塊,通過指定不同的參數來實現不同波特率的兼容。
例化和端口聲明順序保持一致,輸入端口放在一起,輸出端口放在一起
多比特信號,在例化時需要指定位寬,以增加可讀性
頂層模塊只進行模塊例化,不寫任何控制語句
示例:
wire[7:0]rx_data; wirerx_done; wirerx_err; /*串口接收1字節*/ uart_rx_byte#( .BAUD_RATE(32'd115200), .EN_PARITY(2'd0),//0:無校驗位,1:奇校驗,2:偶校驗 .EN_STOP_2(1'b0)//1:使能2位停止位 )uart_rx_byte_0( //Inputs .clk(clk_32m), .rst_n(rst_n), .rxd(uart_rxd), //Outputs .data_rx(rx_data[7:0]),//指定位寬 .done(rx_done), .err(rx_err) );
注釋
不要相信什么代碼就是最好的注釋!我不否認有些人的代碼寫的就是很規范,命名合理,格式清晰。
但是我覺得你還沒有達到那種程度,不能保證每一個人都能讀懂沒有注釋的代碼。注釋不僅是為了給別人看,更多的也是為了給自己看,好記性不如爛筆頭。
注釋統一使用/**/注釋的方式,或者使用與//混合使用,看個人習慣!
每個變量定義后需要注釋變量的功能
每個always塊功能需要注釋
狀態機狀態含義需要注釋
條件語句的后面需要添加注釋
代碼修改,注釋也要隨之修改
其他
合理使用generate for可以批量化定義和例化模塊,減少代碼量,提高可讀性
testbench中使用task和function可以提高效率
移位操作替換為拼接補0操作,更易讀
時序邏輯統一使用非阻塞賦值,即<=符號
一行字符不要超過80個,過長通過換行來處理
先有頂層設計,然后劃分子模塊功能,或者自底向上方法
模塊化設計,提高模塊可復用程度
能用case的盡量不要用if/else
reg類型變量,根據需要看是否鎖存。
責任編輯:lq6
-
Verilog
+關注
關注
28文章
1345瀏覽量
109989 -
vhdl
+關注
關注
30文章
816瀏覽量
128081
原文標題:如何寫出易于維護的Verilog代碼?
文章出處:【微信號:zhuyandz,微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論