眾所周知,用於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 ® Hardware Description Language》官方標準文件。
良好的程式碼規範可以提高程式碼的可讀性、可複用性、簡潔清晰,這也是一種職業素質的體現。
我們的目標是:
Write Nowhere,Read Everywhere
都有哪些內容?
命名
檔案命名
埠命名
變數命名
引數命名
結構
整體結構
埠宣告
空格和縮排讓程式碼更清晰
小括號增加可讀性
例化
註釋
其他
IEEE-2005標準下載
嵌入式物聯網需要學的東西真的非常多,千萬不要學錯了路線和內容,導致工資要不上去!
無償分享大家一個資料包,差不多150多G。裡面學習內容、面經、專案都比較新也比較全!某魚上買估計至少要好幾十。
點選這裡找小助理0元領取:
嵌入式物聯網學習資料(頭條)
命名
命名主要包括檔案和模組命名,埠命名,變數命名,引數命名等,概括來說就是:
有意義,簡短易讀
,重要的是
不要使用拼音
!
檔案命名
檔名和模組名保持一致,一個檔案只寫一個模組。
檔案命名
檔案命名要有含義,且簡短易讀,檔名統一使用小寫字母,並使用下劃線分割檔名。
底層驅動類模組命名,使用
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
clock
時鐘
rd
read
讀取
wr
write
寫入
addr
address
地址
ack
acknowledge
響應
引數命名
Verilog中的引數類似於C語言中的define,主要有以下兩類
localparam
和
parameter
,兩者的區別是前者不可以在例化時進行引數傳遞,而後者可以在例化時進行引數傳遞。
其他的變數,檔名都是統一小寫,只有引數定義有
全部大寫的待遇
,當需要定義一些常量時,可以透過引數宣告指定一個有意義的名稱。如:
parameter LED_ON = 1‘b0;parameter LED_OFF = !LED_ON;parameter BAUD_RATE = 115200;parameter TIME_100MS = 25_000_000;
狀態機的狀態通常使用localparam來定義,並指定有意義的名稱,統一使用Sn_NAME的格式,並指定位寬。如:
localparam S0_IDLE = 4’d0;localparam S1_START = 4‘d1;localparam S2_DOING = 4’d2;localparam S3_END = 4‘d3;localparam S_FINISH = 4’d4;localparam S_ERROR = ‘d5;//auto adaptive
結構
整體結構
建議Verilog模組檔案按照如下結構進行書寫。
/* 1。檔案頭: 說明版權資訊,檔案功能,作者,版本歷史等 *//* 2。埠宣告: input/output/inout *//* 3。宏定義: `define *//* 4。引數定義: localparam/parameter *//* 5。暫存器定義: reg *//* 6。線網型別定義: wire *//* 7。互聯定義: assign *//* 8。時序邏輯描述: always */
示例:
/* 1。file head *//***********************************************************Copyright 2021 ’wechat:mcu149‘。 All rights reserved。FileName: drv_led。vFunction: led driverAuthor : mcu149SVN : SVN Revision: 1490 SVN Date: 2021-06-05 19:49:49Revision: 2020-09-09: Rev 1。0 2020-10-01: Rev 1。1************************************************************//* 2。input/output/inout */module drv_led( //Inputs input rst_n, input clk_50m, input en, //Outputs output led1, output reg led2 = 0 //define initial value is 0);/* 3。define *//* 4。parameter/localparam */parameter TIME_500MS = 32’d25_000_000;/* 5。reg */reg [31:0] cnt = 0;/* 6。wire */wire flag_toggle = (cnt == TIME_500MS);/* 7。assign */assign led1 = (en == 0) ? 0 : cnt[20];/* 8。always block */always @ (posedge clk_50m) begin if(!rst_n) cnt <= 0; else if(flag_toggle) cnt <= 0; else cnt <= cnt + 1;endalways @ (posedge clk_50m) begin if(!rst_n) led2 <= 0; else if(!en) led2 <= 0; else if(en) begin if(flag_toggle) led2 <= !led2; endendendmodule
埠宣告
輸入埠放在一起,輸出埠放在一起,雙向埠放在一起。如果某個輸出訊號需要確定初始值,可以在埠定義時直接進行指定,這也是Verilog-2005新新增的功能。
埠命名
這一點有些朋友可能是按照功能進行劃分,如連線同一晶片的放在一起。
輸入輸出分開放的好處是,在例化時可以很方便的區分哪些是輸入哪些是輸出。
空格和縮排讓程式碼更清晰
運算子兩端增加一個空格,可以讓程式結構更清晰,可讀性更高
縮排風格採用KR風格,即begin寫在行尾,不佔用單獨一行,end單獨佔用一行
縮排統一使用4個空格來代替TAB鍵
if/else等語句只有一行時,可以省略begin-end
合理新增空行進行塊區分,不同的always塊進行換行隔開
以下是兩種程式碼的書寫規範,合理縮排,合理增加空格大大增加了可讀性。
合理縮排
小括號增加可讀性
在學校裡有些考試題,為了考察學生對各種運算子優先順序的掌握程度,出一些反人類的題目。
而做實際專案不像考試,追求的是可讀性和易用性,所以當使用多個運算子時,為了增強可讀性,避免歧義,不要吝嗇使用小括號來表示運算的優先順序。
運算子優先順序
例化
例化可以認為是FPGA開發的靈魂所在了,例化的過程其實就是硬體模組的呼叫過程,比如我們用Verilog描述了一個3-8譯碼器的模組,可以在不同的地方去使用(例化)它,並分別命名為ut0/ut1/ut2等等,而且還可以在例化時指定引數,如串列埠傳送和接收模組,透過指定不同的引數來實現不同波特率的相容。
例化和埠宣告順序保持一致,輸入埠放在一起,輸出埠放在一起;
多位元訊號,在例化時需要指定位寬,以增加可讀性;
頂層模組只進行模組例化,不寫任何控制語句。
示例:
wire [7:0] rx_data;wire rx_done;wire rx_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型別變數,根據需要看是否鎖存。
原文連結:https://mp。weixin。qq。com/s/Gx-iFGETTf8EBWgn8dgO8A
轉載自:嵌入式微處理器
原文連結:如何寫出易於維護的Verilog程式碼?
本文來源網路,免費傳達知識,版權歸原作者所有。如涉及作品版權問題,請聯絡我進行刪除