導讀
串口的出現是在1980年前后,數據傳輸率是115kbps~230kbps。串口出現的初期是為了實現連接計算機外設的目的,初期串口一般用來連接鼠標和外置Modem以及老式攝像頭和寫字板等設備。串口也可以應用于兩臺計算機(或設備)之間的互聯及數據傳輸。由于串口(COM)不支持熱插拔及傳輸速率較低,部分新主板和大部分便攜電腦已開始取消該接口。串口多用于工控和測量設備以及部分通信設備中。
串口是串行接口的簡稱,也稱串行通信接口或串行通訊接口(通常指COM接口),是采用串行通信方式的擴展接口。串行接口(Serial Interface)是指數據一位一位地順序傳送。其特點是通信線路簡單,只要一對傳輸線就可以實現雙向通信(可以直接利用電話線作為傳輸線),從而大大降低了成本,特別適用于遠距離通信,但傳送速度較慢。
通信協議是指通信雙方的一種約定。約定包括對數據格式、同步方式、傳送速度、傳送步驟、檢糾錯方式以及控制字符定義等問題做出統一規定,通信雙方必須共同遵守。串口通信的兩種最基本的方式為:同步串行通信方式和異步串行通信方式。
同步串行通信是指SPI(Serial Peripheral interface)的縮寫,顧名思義就是串行外圍設備接口。SPI是一種高速的全雙工通信總線。封裝芯片上總共有四根線,PCB布局布線也簡單,所以現在很多芯片集成了這個協議。主要用于CPU和各種外圍器件進行通信,TRM450是SPI接口。
異步串行通信是指UART(Universal Asynchronous Receiver/Transmitter),通用異步接收/發送。UART是一個并行輸入成為串行輸出的芯片,通常集成在主板上。UART包含TTL電平的串口和RS232電平的串口。RS232也稱標準串口,也是最常用的一種串行通訊接口。RS-232-C 標準對兩個方面作了規定,即信號電平標準和控制信號線的定義。RS-232-C 采用負邏輯規定邏輯電平,信號電平與通常的TTL電平也不兼容,RS-232-C 將-5V~-15V 規定為“1”,+5V~+15V 規定為“0”。
一般情況下外設不能直接和主機直接相連,它們之間的信息交換主要存在以下問題:
? 速度不匹配 通常情況下外設的工作速度會比主機慢許多,而且外設之間的速度差異也比較大。
? 數據格式不匹配 不同的外設在進行信息存儲和處理時的數據單元可能不同,例如最基本的數據格式可以分為并行數據和串行數據。
? 信息類型不匹配 不同的外設可能采用不同類型的信號,有些是模擬信號,有些是數字信號,因此所采用的處理方式也不同。
為了解決外設和主機之間信息交換的問題,就需要設計一個信息交換的中間環節——接口。本篇將首先對接口技術進行簡要的介紹,然后以接口中最常用的 UART 控制器為例,詳細介紹用 FPGA 實現 UART 控制器的方法。
第三篇內容摘要:本篇會介紹使用 FPGA 實現 UART,包括UART設計框架、UART 工作流程、信號監測器模塊的實現、波特率發生器模塊的實現、移位寄存器模塊的實現、奇偶校驗器模塊的實現、總線選擇器模塊的實現、計數器模塊的實現、UART 內核模塊的實現、UART 頂層模塊的實現 、測試平臺的編寫和仿真等相關內容。
三、使用 FPGA 實現 UART
3.1 UART 設計框架
UART 主要由 UART 內核、信號監測器、移位寄存器、波特率發生器、計數器、總線選擇器和奇偶校驗器總共 7 個模塊組成,如圖 5 所示。
圖 5 UART 實現框架圖
UART 各個模塊的功能如下。
1)UART 內核模塊
UART 內核模塊是整個設計的核心。在數據接收時,UART 內核模塊負責控制波特率發生器和移位寄存器,使得移位寄存器在波特率時鐘的驅動下同步地接收并且保存 RS-232 接收端口上的串行數據。在數據發送時,UART 內核模塊首先根據待發送的數據和奇偶校驗位的設置產生完整的發送序列(包括起始位、數據位、奇偶校驗位和停止位),之后控制移位寄存器將序列加載到移位寄存器的內部寄存器里,最后再控制波特率發生器驅動移位寄存器將數據串行輸出。
2)信號監測器模塊
信號監測器用于對 RS-232 的輸入信號進行實時監測,一旦發現新的數據則立即通知 UART內核。
注意:這里所說的 RS-232 輸入、輸出信號都指的是經過電平轉換后的邏輯信號,而不是 RS-232 總線上的信號。絕對不能直接將 RS-232 總線的信號連接到 FPGA 管腳上,否則很容易造成 FPGA芯片的損壞。
3)移位寄存器模塊
移位寄存器的作用是存儲輸入或者輸出的數據。當 UART 接收 RS-232 輸入時,移位寄存器在波特率模式下采集 RS-232 輸入信號,并且保存結果;當 UART 進行 RS-232 輸出時,UART 內核首先將數據加載到移位寄存器內,再使移位寄存器在波特率模式下將數據輸出到 RS-232 輸出端口上。
注意:波特率模式指的是模塊的輸入時鐘是符合 RS-232 傳輸波特率的時鐘,與波特率模式對應的就是系統時鐘模式,即模塊是工作在系統時鐘下。
4)波特率發生器模塊
由于 RS-232 傳輸必定是工作在某種波特率下,比如 9600,為了便于和 RS-232 總線進行同步,需要產生符合 RS-232 傳輸波特率的時鐘,這就是波特率發生器的功能。
5)奇偶校驗器模塊
奇偶校驗器的功能是根據奇偶校驗的設置和輸入數據計算出相應的奇偶校驗位,它是通過純組合邏輯實現的。
6)總線選擇模塊
總線選擇模塊用于選擇奇偶校驗器的輸入是數據發送總線還是數據接收總線。在接收數據時,總線選擇模塊將數據接收總線連接到奇偶校驗器的輸入端,來檢查已接收數據的奇偶校驗位是否正確;而在發送數據時,總線選擇模塊將數據發送總線連接到奇偶校驗器的輸入端,UART內核模塊就能夠獲取并且保存待發送序列所需的奇偶校驗位了。
7)計數器模塊
計數器模塊的功能是記錄串行數據發送或者接收的數目,在計數到某數值時通知 UART 內核模塊。
3.2 UART 工作流程
UART 的工作流程可以分為接收過程和發送過程兩部分。
接收過程指的是 UART 監測到 RS-232 總線上的數據,順序讀取串行數據并且將其輸出給CPU 的過程。當信號監測器監測到新的數據(RS-232 輸入邏輯變為 0,即 RS-232 傳輸協議的起始位)就會觸發接收過程,其流程圖如圖 6 所示。
圖6 UART 接收數據流程圖
首先 UART 內核會重置波特率發生器和移位寄存器,并且設置移位寄存器的工作模式為波特率模式,以準備接收數據。其次,移位寄存器在波特率時鐘的驅動下工作,不斷讀取 RS-232 串行總線的輸入數據,并且將數據保存在內部的寄存器內。接收完成后,UART 內核會對已接收的數據進行奇偶校驗并且輸出校驗結果。最后,UART 內核會重置信號監測器,以準備進行下一次數據接收。
發送過程是由加載和發送兩個步驟組成,如圖 7 所示。加載步驟是 UART 內核按 RS-232串行發送的順序將起始位、數據位、奇偶校驗位和停止位加載到移位寄存器內,這個過程工作在系統時鐘下,相對于 RS-232 的傳輸速度來說非常快。完成加載步驟后,UART 內核會重置波特率發生器,并且設置移位寄存器工作在波特率模式下,于是移位寄存器便在波特率時鐘的驅動下依次將加載的數據發送到 RS-232 的發送端 TxD,這樣便產生了 RS-232 的數據發送時序。
圖 7 UART 發送數據流程圖
下面依次介紹一下 UART 各個模塊的實現方法。
3.3 信號監測器模塊的實現
信號監測器模塊的功能是監測 RS-232 輸入端的信號,當有新的數據傳輸時通知 UART 內核開始接收數據,其端口定義如表 5 所示。
表 5 信號監測器端口定義表
在監測到傳輸的起始位后,信號監測器需要將自己鎖定,即不對輸入信號進行監測,直到UART 內核將其復位。信號監測器的實現代碼如下:
-- 庫聲明 library IEEE; use IEEE.STD_LOGIC_1164.all; use WORK.UART_PACKAGE.ALL; -- 實體聲明 entity detector is port ( clk : in std_logic; reset_n : in std_logic; RxD : in std_logic; new_data : out std_logic ); end detector; --}} End of automatically maintained section -- 結構體 architecture detector of detector is -- 信號監測器狀態機 signal state : dt_state; begin -- enter your statements here -- -- 主過程 main : process(reset_n, clk) begin -- 復位信號 if reset_n = '0' then state <= dt_unlock; new_data <= '0'; elsif rising_edge(clk) then -- 檢查輸入信號和狀態,當輸入為低并且不在鎖定狀態時,輸出 new_data 信號 if state = dt_unlock and RxD = '0' then new_data <= '1'; state <= dt_lock; else new_data <= '0'; end if; end if; end process; end detector;
代碼中的狀態機 dt_state 是在 UART_PACKAGE 包中定義的,如下:
-- 信號監測器狀態 typedt_stateis( dt_unlock, -- 未鎖定狀態 dt_lock -- 鎖定狀態 );
為了驗證信號監測器模塊的實現,需要編寫一個測試平臺測試其功能。測試平臺的代碼請參考 UART 工程源代碼中的 detector_tb.vhd 文件,測試的結果如圖 8 所示。
圖 8 信號監測器仿真時序圖
其中,RxD 第一次變為低時,new_data 信號產生輸出;之后,RxD 又變為低,但由于信號監測器處于鎖定狀態,所以 new_data 并沒有輸出;最后,由于 reset_n 信號將信號監測器復位了,RxD 再次變為低時,new_data 上又有輸出了。可見,信號監測器的實現完全正確,其功能完全符合設計的要求。
3.4 波特率發生器模塊的實現
波特率發生器的功能是產生和 RS-232 通信所采用的波特率同步的時鐘,這樣才能方便地按照 RS-232 串行通信的時序要求進行數據接收或者發送。圖 9 表示了波特率時鐘和 RS-232接收端信號 RxD 之間的時序關系,波特率時鐘的頻率就是波特率。比如,波特率為 9600,即每秒傳輸 9600 位數據,則同步的波特率時鐘頻率為 9600,周期為 1/9600=0.10417 毫秒。
圖 9 波特率時鐘與 RxD 時序圖
實現上述的波特率時鐘的基本思路就是設計一個計數器,該計數器工作在速度很高的系統時鐘下,當計數到某數值時將輸出置高,再計數一定數值后再將輸出置低,如此反復便能夠得到所需的波特率時鐘。假如 FPGA 的系統時鐘為 50MHz,RS-232 通信的波特率為 9600,則波特率時鐘的每個周期相當于
個系統時鐘的周期。假如要得到占空比為 50%的波特率時鐘,只要使得計數器在計數到時將輸出置高,之后在計數到 5208 時將輸出置低并且重新計數,就能夠實現和 9600 波特率同步的時鐘,原理圖如圖 10 所示。
圖 10 波特率時鐘實現原理
波特率發生器的端口定義如表 6 所示。
表 6 波特率發生器端口定義表
波特率發生器在復位后,將內部計數器置為“0”,如果使能信號有效,則在每個系統時鐘的上升沿工作,將計數器計數增加一。當輸出一個完整的波特率時鐘脈沖后,波特率發生器會自動將內部計數器置為零,同時開始進行下一個脈沖的計數。還有一個 indicator 信號,每產生一個完整的波特率時鐘周期,indicator 信號會輸出一個寬度的高電平。indicator 信號用于表示產生了一個完整的波特率時鐘周期,UART 通過此信號來了解波特率發生器已輸出的脈沖個數。
波特率發生器的 實現代碼如下,其中在實體聲明中聲明了兩個類屬參數,FULL_PULSE_COUNT 表示一個波特率時鐘完整的周期所對應的計數器計數, RISE_PULSE_COUNT表示波特率時鐘信號上升時刻所對應的計數器計數,這樣波特率時鐘的占空比可以表示為:
。
-- 庫聲明 library IEEE; use IEEE.STD_LOGIC_1164.all; use WORK.UART_PACKAGE.ALL; -- 實體聲明 entity baudrate_generator is -- 類屬參數聲明 generic ( FULL_PULSE_COUNT : BD_COUNT := BD9600_FPC; RISE_PULSE_COUNT : BD_COUNT := BD9600_HPC ); -- 端口聲明 port ( clk : in std_logic; reset_n : in std_logic; ce : in std_logic; bd_out : out std_logic; indicator : out std_logic ); end baudrate_generator; --}} End of automatically maintained section -- 結構體 architecture baudrate_generator of baudrate_generator is begin -- enter your statements here -- -- 主過程 -- main process main : process( clk, reset_n ) variable clk_count : BD_COUNT; begin -- 判斷復位信號 if reset_n = '0' then bd_out <= '0'; indicator <= '0'; clk_count := 0; -- 在時鐘信號的上升沿動作 elsif rising_edge(clk) then -- 判斷使能信號 if ce = '1' then -- 經過了 RISE_PULSE_COUNT 個計數,數脈沖上升 if clk_count = RISE_PULSE_COUNT-1 then -- pulse rise bd_out <= '1'; clk_count := clk_count+1; -- 經過了 FULL_PULSE_COUNT 個計數,數脈沖下降 ????????????elsif?clk_count?=?FULL_PULSE_COUNT-1?then?--?indicator?output?and?pulse fall -- 輸出提示信號,使其為高 indicator <= '1'; bd_out <= '0'; -- 重置計數器計數為 0 clk_count := 0; -- 恢復提示信號為低 elsif clk_count = 0 then indicator <= '0'; clk_count := clk_count+1; else clk_count := clk_count+1; end if; end if; end if; end process; end baudrate_generator;
以上代碼中的 BD_COUNT 是在 UART_PACKAGE 庫中定義的,它代表范圍從 0~65535 的整數(即16 位整數);BD9600_FPC 代表波特率時鐘完整周期對應的計數,而 BD9600_HFC 代表的是波特率時鐘半周期對應的計數,它們也是在 UART_PACKAGE 庫中定義的,如下所示:
-- 計數器計數范圍 type BD_COUNT is range 65535 downto 0; -- 9600 波特率對應參數 constant BD9600_FPC : BD_COUNT := 5208; constant BD9600_HPC : BD_COUNT := 2604;
下面介紹一下波特率發生器的仿真測試過程。由于 9600 波特率對應的參數數值比較大,所 以 為 了 便 于 觀 察 仿 真 的 波 形 , 可 以 首 先 選 用 數 值 較 小 的 測 試 數 據 。比 如 , 可 以 在UART_PACKAGE 庫中定義完整波特率時鐘周期對應計數為 10,半周期對應計數為 5,代碼如下:
-- 波特率測試參數 constant BDTEST_FPC : BD_COUNT := 10; constant BDTEST_HPC : BD_COUNT := 5;
在測試平臺文件中,聲明波特率發生器實例時應該將其類屬參數設置為測試參數,如下所示:
-- 波特率發生器實例 UUT : baudrate_generator generic map ( FULL_PULSE_COUNT => BDTEST_FPC, RISE_PULSE_COUNT => BDTEST_HPC ) port map ( clk => clk, reset_n => reset_n, ce => ce, bd_out => bd_out, indicator => indicator );
完整的波特率發生器測試平臺請參考 UART 工程源代碼中的 baudrate_generator_tb.vhd文件,使用測試參數仿真得到的波形如圖 11 所示。觀察波形可以看到波特率發生器每經過10 個時鐘周期輸出一個完整的波特率時鐘周期,占空比為 1/2,并且在每次輸出完整脈沖后輸出一個系統時鐘脈寬的提示信號,可見波特率發生器的工作完全滿足設計的要求。
圖 11 波特率時鐘實現原理
使用測試參數仿真正常后,可以使用實際的參數進行測試。
3.5 移位寄存器模塊的實現
移位寄存器在整個設計中非常關鍵,無論是數據接收還是數據發送都需要使用到移位寄存器。移位寄存器的基本工作原理是在觸發信號的驅動下將內部寄存器序列的最高位輸出,將次高位到最低位向高位移一位,并且讀取輸入端的數據保存到最低位。圖 12 所示為移位寄存器的工作原理。
圖 12 移位寄存器工作原理圖
由于 RS-232 通信在不同的傳輸設置下(比如奇偶校驗位、停止位)總的數據位不同,所以為了能夠靈活的配置移位寄存器,可以在聲明移位寄存器實體的時候添加一個表示寄存器序列總長度的類屬參數,代碼如下:
entity shift_register is -- generic generic ( TOTAL_BIT : integer := 10 ); -- port port ( clk : in std_logic; reset_n : in std_logic; din : in std_logic; regs : out std_logic_vector(TOTAL_BIT-1 downto 0); dout : out std_logic ); end shift_register;
以上代碼中的 TOTAL_BIT 表示的就是寄存器序列的長度,默認為 10,對應的傳輸設置是 8位數據位、奇偶校驗位、1 位停止位。移位寄存器的端口定義如表 7 所示。
表 7 移位寄存器端口定義表
完整的移位寄存器實現代碼如下:
-- 庫聲明 library IEEE; use IEEE.STD_LOGIC_1164.all; -- 實體聲明 entity shift_register is -- 類屬參數 generic ( TOTAL_BIT : integer := 10 ); -- 端口 port ( clk : in std_logic; reset_n : in std_logic; din : in std_logic; regs : out std_logic_vector(TOTAL_BIT-1 downto 0); dout : out std_logic ); end shift_register; --}} End of automatically maintained section -- 結構體 architecture shift_register of shift_register is -- 內部寄存器序列 signal shift_regs : std_logic_vector(TOTAL_BIT-1 downto 0) := (others => '1'); begin -- 寄存器輸出 regs <= shift_regs; -- 主過程 main : process(reset_n, clk) begin -- 檢查復位信號 if reset_n = '0' then dout <= '1'; -- 在時鐘上升沿動作 elsif rising_edge(clk) then -- 將最高位輸出到 dout dout <= shift_regs(TOTAL_BIT-1); -- 次高位到最低位都向高位移一位 shift_regs(TOTAL_BIT-1 downto 1) <= shift_regs(TOTAL_BIT-2 downto 0); -- 讀取輸入端口信號并且保存到寄存器序列的最低位 shift_regs(0) <= din; end if; end process; end shift_register
編寫一個測試平臺對上述的代碼進行測試,得到的仿真波形如圖 13 所示。移位寄存器復位后在每個時鐘的上升沿工作,輸出寄存器最高位,將寄存器移位并且將輸入端保存到寄存器最低位。由于輸入信號 din 以時鐘周期為 “0”和“1”之間交替變化,所以移位寄存器每次保存到最低位的數據也是“0”、“1”交替變化,最后其內部寄存器也會呈現“0”、“1”交替的情況。所以上述的代碼符合設計的要求。
圖 13 移位寄存器仿真時序圖
3.6 奇偶校驗器模塊的實現
奇偶校驗器根據奇偶校驗的設置和輸入數據計算出奇偶校驗位,所以在定義其實體時需要添加兩個類屬參數 DATA_LENGTH 和 PARITY_RULE,分別表示校驗數據的長度和校驗規則,代碼如下:
entity parity_verifier is generic ( DATA_LENGTH : integer := 8; PARITY_RULE : PARITY := NONE ); port ( source : in std_logic_vector(DATA_LENGTH-1 downto 0); parity : out std_logic ); end parity_verifier;
其中,PARITY 是在 UART_PACKAGE 庫中定義的,代碼如下:
-- 奇偶校驗規則定義 typePARITYis( NONE, -- 無奇偶校驗 ODD, -- 奇校驗 EVEN -- 偶校驗 );
奇偶校驗器的端口定義如表 8 所示。
表 8 奇偶校驗器端口定義表
奇偶校驗器的實現代碼如下:
-- 庫聲明 library IEEE; use IEEE.STD_LOGIC_1164.all; use WORK.UART_PACKAGE.ALL; -- 實體聲明 entity parity_verifier is -- 類屬參數 generic ( DATA_LENGTH : integer := 8; PARITY_RULE : PARITY := NONE ); -- 端口 port ( source : in std_logic_vector(DATA_LENGTH-1 downto 0); parity : out std_logic ); end parity_verifier; --}} End of automatically maintained section -- 結構體 architecture parity_verifier of parity_verifier is begin -- enter your statements here -- -- 按照校驗規則計算校驗位 with PARITY_RULE select parity <= MultiXOR(source) when ODD, -- 奇校驗 ( not MultiXOR(source) ) when EVEN, -- 偶校驗 '1' when others; end parity_verifier;
上述奇偶校驗器實現代碼中的 MultiXOR 函數也是在 UART_PACKAGE 庫中定義的,其功能是對輸入序列進行異或計算。MultiXOR 的函數聲明如下:
function MultiXOR( din : in std_logic_vector ) return std_logic;
MultiXOR 的函數實現如下:
function MultiXOR( din : in std_logic_vector ) return std_logic is variable check : std_logic; begin check := din(din'LOW); for i in 1 to (din'HIGH) loop check := check xor din(i); end loop; return check; end MultiXOR;
對上述奇偶校驗器進行仿真測試,在偶校驗設置下,仿真得到的波形如圖 14 所示。
圖 14 奇偶校驗器仿真波形
注意:偶校驗和奇校驗的計算有一個技巧。如果使用偶校驗,在待校驗序列中有偶數個“1”,則得到的結果為“1”,反之為“0”;如果使用奇校驗,在待校驗序列中有奇數個“1”,則得到的結果為“1”,反之為“0”。
3.7 總線選擇器模塊的實現
總線選擇器模塊的功能是通過一個選擇信號控制兩個輸入信號,將其輸出,也就是一個二選一的選擇器。圖 15 是總線選擇模塊的框圖,當選擇信號 sel 為低時,將會把輸入總線一din1 輸出,否則在選擇信號 sel 為高時,將會把輸入總線二 din2 輸出。
圖 15 總線選擇器框圖
為了能夠使總線選擇器適應不同寬度的總線,在聲明其實體的時候可以添加一個BUS_WIDTH 的類屬參數,它表示總線的寬度,代碼如下:
entity switcher_bus is -- 類屬參數 generic ( BUS_WIDTH : integer := 8 ); -- 總線寬度 port ( din1 : in std_logic_vector(BUS_WIDTH-1 downto 0); -- 輸入總線一 din2 : in std_logic_vector(BUS_WIDTH-1 downto 0); -- 輸入總線二 sel : in std_logic; -- 選擇信號 dout : out std_logic_vector(BUS_WIDTH-1 downto 0) ); -- 輸出總線 end switcher_bus;
總線選擇器的端口定義如表 9 所示。
表 9 總線選擇器端口定義表
總線選擇器的實現代碼如下:
-- 庫聲明 library IEEE; use IEEE.STD_LOGIC_1164.all; -- 實體聲明 entity switcher_bus is -- 類屬參數 generic ( BUS_WIDTH : integer := 8 ); -- 總線寬度 port ( din1 : in std_logic_vector(BUS_WIDTH-1 downto 0); -- 輸入總線一 din2 : in std_logic_vector(BUS_WIDTH-1 downto 0); -- 輸入總線二 sel : in std_logic; -- 選擇信號 dout : out std_logic_vector(BUS_WIDTH-1 downto 0) ); -- 輸出總線 end switcher_bus; --}} End of automatically maintained section -- 結構體 architecture switcher_bus of switcher_bus is begin -- 使用 select 語句 with sel select dout <= din1 when '0', din2 when others; end switcher_bus;
編寫一個測試平臺對上述代碼進行測試,使得輸入總線一上的數據為“11110000”,輸入總線二上的數據為“00001111”,再分別使選擇信號為“0”和“1”。得到的仿真波形如圖 16所示,輸出總線在選擇信號為“0”時是“11110000”,即輸入總線一的數據,而當選擇信號變為“1”后,輸出總線上的數據也變為輸入總線二的數據“00001111”,可見上述代碼完全實現了設計所需的功能。
圖 16 總線選擇器仿真波形
3.8 計數器模塊的實現
計數器模塊的功能是可控地在輸入時鐘驅動下進行計數,當到達計數上閾時給 UART 內核一個提示信號。在不同的工作狀態下,計數器模塊的輸入時鐘是不同的。UART 在數據發送之前需要進行數據加載(即將串行序列保存到移位寄存器內),在此過程中計數器模塊的輸入時鐘就是系統時鐘,因為此時移位寄存器也是工作在系統時鐘下的。
除了數據加載,另外兩個需要計數器模塊的過程是數據接收和數據發送,由于這兩個過程中移位寄存器是工作在波特率時鐘下的,所以計數器模塊的時鐘就是和波特率時鐘同步的波特率發生器提示信號 indicator,這樣每輸出一個完整的波特率時鐘周期計數器就能增加一。
計數器計數的上閾是在實體聲明中定義的,代碼如下。代碼中的 MAX_COUNT 類屬參數就是計數上閾。
entity counter is generic ( MAX_COUNT : integer := 10 ); port ( clk : in std_logic; reset_n : in std_logic; ce : in std_logic; overflow : out std_logic ); end counter;
計數器模塊的端口定義如表 10 所示。
表 10 計數器端口定義表
計數器模塊的實現代碼如下:
-- 庫聲明 library IEEE; use IEEE.STD_LOGIC_1164.all; -- 實體聲明 entity counter is generic ( MAX_COUNT : integer := 10 ); port ( clk : in std_logic; reset_n : in std_logic; ce : in std_logic; overflow : out std_logic ); end counter; --}} End of automatically maintained section -- 結構體 architecture counter of counter is signal count : integer; begin -- enter your statements here -- -- 主過程 main: process( clk, reset_n ) begin -- 判斷復位信號 if reset_n = '0' then count <= 0; overflow <= '0'; -- 時鐘信號的上升沿動作 elsif rising_edge(clk) and ce = '1' then -- 在計數上閾時候輸出提示信號 overflow if count = MAX_COUNT-1 then count <= 0; overflow <= '1'; -- 恢復提示信號 overflow 為低 elsif count = 0 then count <= count+1; overflow <= '0'; else count <= count+1; end if; end if; end process; end counter
對上述代碼進行仿真測試,得到的仿真波形如圖 17 所示。計數器在復位后并且 ce 有效時開始計數,并且在第 10 個時鐘周期輸出提示信號 overflow。
圖 17 計數器仿真波形
3.9 UART 內核模塊的實現
UART 內核模塊是整個設計的核心,所以它也是整個設計中最為復雜的模塊。由于 UART 內核模塊的整體結構比較復雜,下面的內容將從模塊接口、狀態機設計和實現代碼 3 方面介紹UART 內核的實現方法。
1)UART 內核模塊的接口
(1)CPUUART 內核模塊提供的 CPU 接口就是 UART 模塊的 CPU 接口,如圖 18 虛線框中所示。
圖 18 UART 內核模塊的 CPU 接口
這些端口又可以分為兩組:第一組是與發送相關的,包括 send、send_bus 和 send_over,其中 send 信號是發送控制的信號,send_bus 是待發送數據的總線,send_over 是發送完成的提示信號;第二組是與接收相關的,包括 recv、recv_bus 和 error,其中 recv 信號表示有新的數據被接收,recv_bus 是接收數據的總線,error 信號表示數據接收產生錯誤。CPU 接口的端口定義如表 11 所示。
表 11 CPU 接口端口定義表
其中,DATA_BIT 是 UART 內核模塊實體聲明中定義的類屬參數,表示數據位的長度。
(2)奇偶校驗器UART
內核模塊通過總線選擇模塊和奇偶校驗器模塊實現奇偶校驗,它們之間的連接方式如圖 19 所示。
圖 19 奇偶校驗相關模塊連接示意圖
總線選擇器的兩個輸入端分別連接到數據發送總線 send_bus 和數據接收總線 recv_bus上,它的輸出連接到奇偶校驗器的輸入端,最后奇偶校驗器的校驗結果輸出連接到 UART 的一個端口 parity 上。這樣的連接方式有一個好處,就是在發送和接收的不同過程中,只要通過一個總線選擇信號 sel_pv 就能夠選擇不同的奇偶校驗內容,sel_pv 信號是由 UART 內核的一個端口連接到總線選擇器的選擇信號端口上。
由上述內容可知, UART 使用的端口除了上面已經介紹的數據發送總線和數據接收總線外,就是總線選擇信號 sel_pv 和奇偶校驗信號 parity 了。它們的定義如表 12 所示。
表 12 奇偶校驗端口定義表
(3)計數器模塊
計數器模塊的功能是在輸入時鐘的驅動下進行計數,當到達計數上閾時給 UART 內核一個提示信號,它們兩者之間的連接方法如圖 20 所示。
圖 20 UART 內核模塊和計數器模塊連接示意圖
在數據接收、數據加載和數據發送過程中,都需要使用到計數器,并且不同的過程中提供給計數器的時鐘信號是不一樣的。要選擇計數器時鐘信號,可以通過一個二選一選擇器實現。二選一選擇器的兩個輸入端分別連接到波特率發生器的 indicator 提示信號和系統時鐘信號,同時其信號選擇端口連接 UART 內核的一個 sel_clk 端口,這樣便可以通過控制 sel_clk 端口實現對計數器模塊時鐘信號的控制。另一方面,要正確使用計數器需要對其進行復位和使能,所以 UART 內核還提供了兩個端口 reset_parts 和 ce_parts,作為子模塊復位信號和使能信號的端口。以上介紹的 4 個端口定義如表 5-13 所示。
表 13 奇偶校驗端口定義表
(4)移位寄存器UART
內核和移位寄存器之間的接口主要可以分為 3 個作用:第一,UART 內核需要控制數據加載過程,所以具有向移位寄存器發送串行數據的接口;第二,已接收的數據是保存在移位寄存器內部的,所以移位寄存器具有提供內部寄存器數據的接口;第三,在不同的工作流程中,移位寄存器的工作時鐘也不同,可能是波特率時鐘,也可能是系統時鐘,所以 UART 內核還需要有控制移位寄存器輸入時鐘的信號接口。圖 21 是 UART 內核和移位寄存器之間的連接示意圖。
圖 21 UART 內核與移位寄存器連接示意圖
UART 內核對數據加載過程的控制是通過 send_si 信號、sel_si 信號和一個二選一選擇器實現的。圖 21 中左側的二選一選擇器的作用即是串行數據選擇,它的輸入端分別接到 UART內核的串行數據發送端口 send_si 和 RS-232 的數據接收端口 RxD,信號選擇端口則和 UART 內核的 sel_si 端口相連,輸出端口連接到移位寄存器的數據輸入端口。
這樣,在進行數據加時,UART 內核可以通過 sel_si 信號控制 UART 內核的串行數據輸入端口作為移位寄存器的輸入;當進行數據接收時,UART 內核又可以將 RS-232 的接收端口 RxD 選擇為移位寄存器的輸入。
UART 內核對移位寄存器輸入時鐘的控制方法和對計數器的控制方法一樣,也是利用了一個二選一選擇器,再通過選擇信號控制。圖 5-21 中右側的二選一選擇器的作用便是實現對移位寄存器輸入時鐘的選擇,它的兩個輸入信號分別是波特率時鐘 bd_clk 和系統時鐘,選擇信號連接到 UART 內核的一個端口 sel_clk 上,輸出和移位寄存器的輸入時鐘端口相連。
移位寄存器內部寄存器的數據是通過一個 regs 端口發送給 UART 內核的,regs 端口是多位信號,其寬度就是 RS-232 串行通信的總位數(起始位、數據位、奇偶校驗位和停止位)。表5-14 所示為 UART 內核和移位寄存器之間的端口。
表 14 UART 內核與移位寄存器間端口定義表
除了表 14 所列的端口,UART 內核的 reset_parts 端口還和移位寄存器的復位端口相連,作為它的復位信號。
(5)波特率發生器
UART 內核和波特率發生器之間的接口比較簡單,只有復位和使能兩個信號,即圖 22 所示中的 reset_parts 和 ce_parts 信號。
圖 22 UART 內核與波特率發生器連接示意圖
波特率發生器的復位、使能信號與計數器的相同,其端口定義參考表 13 所示。
(6)信號監測器
UART 內核不但需要接收信號監測器的指示信號,同時還需要在完成數據接收后控制信號監測器復位。所以,UART 內核和信號監測器之間有兩個接口,第一個是監測到數據傳輸的提示信號接口 new_data,另一個是用于復位信號監測器的 reset_dt 信號。圖 23 所示是 UART內核與信號監測器連接示意圖。
圖 23 UART 內核與信號監測器連接示意圖
UART 內核和信號監測器的端口定義如表 15 所示。
表 15 UART 內核與信號監測器間端口定義表
(7)RS-232 串行發送端口
移位寄存器在進行移位的時候,會將最高位輸出,但是只有在發送數據的時候才需要將移位寄存器的數據串行輸出,所以移位寄存器的輸出端不能直接連到 RS-232 串行發送端口上,它們之間需要添加一個二選一選擇器,如圖 24 所示。
圖 24 RS-232 串行發送端口連接示意圖
圖 24 中的二選一選擇器的輸入信號分別是高電平 VCC(即邏輯“1”)和移位寄存器輸出 dout,選擇信號連接到 UART 內核的一個端口 set_out,輸出連接到 RS-232 串行發送端口TxD 上。這樣,UART 內核就可以通過 sel_out 信號選擇向 TxD 發送的數據,在發送過程中將移位寄存器輸出 dout 送到 TxD 上,在其他的過程中則將高電平送到 TxD 上。
2)UART 內核模塊的狀態機設計
UART 內核模塊的功能是控制數據接收、數據加載和數據發送的過程,這可以用狀態機來實現。下面就按接收和發送的過程來介紹 UART 內核模塊狀態機的實現。
(1)數據接收過程
數據接收過程的流程圖如圖 6 所示,可以定義 3 個狀態——空閑、接收和接收完成,其狀態變換圖如圖 25 所示。
UART 內核模塊在復位后進入空閑狀態。如果信號監測器監測到數據傳輸,會給 UART 內核發送一個提示信號,UART 內核監測到此信號就會進入接收狀態。在 UART 內核由空閑狀態轉為接收狀態過程中,需要進行一系列的接收預備操作,包括將子模塊復位、選擇移位寄存器串行輸入數據、選擇移位寄存器時鐘等。
進入接收狀態后,波特率發生器開始工作,其輸出波特率時鐘驅動移位寄存器同步地存儲 RS-232 接收端口上的數據,并且其提示信號驅動計數器進行計數。當所有數據接收完成,計數器也達到了其計數的上閾,它會給 UART 內核發送一個信號,使得 UART 內核進入接收完成的狀態。
UART 內核進入接收完成狀態的同時,會檢查奇偶校驗的結果,同時使得子模塊使能信號無效以停止各個子模塊。UART 內核的接收完成狀態僅僅保持一個時鐘周期,設置這個狀態的作用是借用一個時鐘周期復位信號監測器,準備接收下次數據傳輸。
圖 25 UART 內核數據接收狀態轉換圖
(2)數據加載和發送過程
數據加載和發送的過程都是為了發送數據而設定的,所以將它們放在一起進行介紹。可以用 4 個狀態來實現上述的過程,即空閑、加載、發送和發送完成,其中的空閑狀態就是 UART內核復位后的空閑狀態,和上面介紹的數據接收過程的空閑狀態一致。數據加載和發送過程的狀態轉換圖如圖 26 所示。
圖 26 UART 內核數據加載和發送狀態轉換圖
數據加載過程在數據發送過程之前進行。UART 內核復位后進入空閑狀態,當探測到發送控制信號有效時,便會進入加載狀態開始數據加載。在進入加載狀態的同時,UART 內核會將移位寄存器、計數器復位,并且通過選擇信號使得移位寄存器的輸入為 UART 內核產生的串行數據序列,使得移位存器和計數器的工作時鐘為系統時鐘。
進入加載狀態后,UART 內核會將完整的待發送序列加載到移位寄存器的數據輸入端,發送的序列是和系統時鐘同步的,移位寄存器則在系統時鐘的驅動下不斷讀入輸入端數據并且保存在內部寄存器內。在移位寄存器加載數據的同時,計數器也在時鐘的驅動下進行計數,由于都是工作在系統時鐘下,所以當所有數據被加載時,計數器也達到了計數的上閾(即串行數據的總量),它會產生一個提示信號使得UART 內核進入發送狀態。
UART 內核進入發送狀態的同時會改變幾個選擇信號,比如將移位寄存器時鐘設為波特率時鐘,將計數器時鐘設為波特率的提示信號,最重要的是將輸出信號送到 RS-232 的發送端口TxD 上。發送的過程和接收類似,移位寄存器在波特率時鐘的驅動下內部寄存器的數據串行的發送出去,同時計數器在波特率發生器的提示信號驅動下進行計數。UART 內核在計數器到達計數上閾后便進入發送完成模式,并且輸出發送完成信號。
3)UART 內核模塊的實現代碼
由于 UART 內核控制著所有的處理過程,并且還要跟大部分模塊進行通信,所以它的實現代碼比較復雜。為了能夠便于讀者理解,下面將分 5 部分對其進行介紹。
(1)實體聲明
上面的內容已經介紹了 UART 內核和其他模塊之間的接口,在實體聲明中,需要將所有的接口都包括進去。表 16 所示總結了所有的 UART 內核接口。
表 16 UART 內核端口定義表
除了上述的端口,UART 內核模塊的聲明中還需要聲明 3 個類屬參數,分別是 DATA_BIT、TOTAL_BIT 和 PARITY_RULE,分別表示數據位個數、總數據個數、奇偶校驗規則。
UART 內核的實體聲明代碼如下:
entity uart_core is generic ( -- 數據位個數 DATA_BIT : integer := 8; -- 總數據個數 TOTAL_BIT : integer := 10; -- 奇偶校驗規則 PARITY_RULE : PARITY := NONE ); port ( -- 時鐘和復位信號 clk : in std_logic; reset_n : in std_logic; -- 和信號監測器的接口信號 new_data : in std_logic; reset_dt : out std_logic; -- 復位、使能子模塊的信號 reset_parts : out std_logic; ce_parts : out std_logic; -- 和移位寄存器的接口信號 send_si : out std_logic; sel_si : out std_logic; regs : in std_logic_vector(TOTAL_BIT-1 downto 0); -- 計數器時鐘選擇信號和計數器計數到達上閾的指示信號 sel_clk : out std_logic; overflow : in std_logic; -- 和奇偶校驗器的接口信號 sel_pv : out std_logic; parity : in std_logic; -- 輸出選擇信號 sel_out : out std_logic; -- 提供給 CPU 的接口信號 send : in std_logic; send_bus : in std_logic_vector(DATA_BIT-1 downto 0); send_over : out std_logic; recv : out std_logic; recv_bus : out std_logic_vector(DATA_BIT-1 downto 0); error : out std_logic ); end uart_core;
(2)內部信號定義
在 UART 內核模塊內部需要定義 3 個信號,如下:
signal state : UART_STATE := UART_IDLE; signal send_buf : std_logic_vector(TOTAL_BIT-1 downto 0); signal si_count : integer range 0 to 15 := 0;
其中 state 信號是狀態機狀態信號;send_buf 表示待發送串行序列的緩沖寄存器;si_count 是發送序列的索引信號,在生成加載的串行發送序列時候需要使用到。
(3)串行加載序列的生成方法
串行加載序列的生成有兩個步驟,第一個步驟是將起始位、數據位、奇偶校驗的結果等存儲到待發送串行序列的緩存寄存器內。這是通過一個過程來實現的,過程的觸發信號是數據發送總線和奇偶校驗輸入信號,代碼如下。此過程的功能除了存儲奇偶校驗結果外,還包括存儲起始位的功能。
-- 生成串行加載序列 send_buffer: process(send_bus, parity) begin -- 存儲起始位 send_buf(0) <= '0'; -- 存儲數據位 send_buf(DATA_BIT downto 1) <= send_bus(DATA_BIT-1 downto 0); -- 存儲奇偶校驗位和停止位 if PARITY_RULE = ODD or PARITY_RULE = EVEN then send_buf(DATA_BIT+1) <= parity; send_buf(TOTAL_BIT-1 downto DATA_BIT+2) <= (others => '1'); else send_buf(TOTAL_BIT-1 downto DATA_BIT+1) <= (others => '1'); end if; end process;
第二個步驟是將 send_buf 寄存器序列中的數據發送到 send_si 端口上,發送的時序應該和系統時鐘同步。此步驟也是利用一個過程實現的,代碼如下。其中 si_count 是加載串行序列的索引,UART 內核在加載過程中,每經過一個時鐘就會將 si_count 增加 1。
-- serial input switch si_switch: process(reset_n, si_count) begin -- 復位 if reset_n = '0' then send_si <= '1'; else -- 將 send_buf 里面的數據送到 send_si 端口上 send_si <= send_buf(si_count); end if; end process;
(4)復位處理
UART 內核模塊是由 reset_n 信號控制復位的,此信號為低即表示復位有效。復位的處理是在一個 UART 內核的主過程中實現的,代碼如下:
-- 主過程 main: process(clk, reset_n) begin if reset_n = '0' then -- 信號監測器復位信號 reset_dt <= '1'; -- 其他模塊的復位和使能信號 reset_parts <= '0'; ce_parts <= '0'; -- 移位寄存器輸入 sel_si <= '0'; -- 波特率發生器和計數器的時鐘選擇信號 sel_clk <= '0'; -- 奇偶校驗器的輸入 sel_pv <= '0'; -- 選擇 TxD 輸出 sel_out <= '0'; -- 與 CPU 之間的接口信號 send_over <= '0'; recv <= '0'; error <= '0'; -- 狀態機 state <= UART_IDLE; -- 串行加載的計數 si_count <= 0; elsif rising_edge(clk) then -- 狀態機實現 end if end process;
(5)UART 內核模塊的狀態機實現
UART 內核的主過程除了處理復位信號外,還控制了數據發送和數據接收的狀態轉換,即實現了狀態機,代碼如下:
-- 主過程 main: process(clk, reset_n) begin if reset_n = '0' then -- 復位處理 elsif rising_edge(clk) then case state is -- 空閑狀態 when UART_IDLE => -- 當信號監測器監測到數據時,new_data 變為‘1’ if new_data = '1' then -- 復位子模塊 reset_parts <= '0'; -- 子模塊使能無效 ce_parts <= '0'; -- 選擇移位寄存器串行輸入為 RxD sel_si <= '1'; -- 選擇移位寄存器的時鐘為波特率始終 -- 選擇計數器的時鐘為波特率發生器的指示信號 sel_clk <= '0'; -- 使得輸出保持為’1’ sel_out <= '0'; -- 設置奇偶校驗的數據源為數據發送總線 sel_pv <= '1'; -- 改變狀態為接收 state <= UART_RECV; -- 當 send 信號變為‘1’,表示 CPU 要求發送數據 elsif send = '1' then -- 復位子模塊 reset_parts <= '0'; -- 子模塊使能無效 ce_parts <= '0'; -- 選擇移位寄存器串行輸入為串行加載序列 sel_si <= '0'; -- 選擇移位寄存器的時鐘為波特率始終 -- 選擇計數器的時鐘為波特率發生器的指示信號 sel_clk <= '0'; -- 使得輸出保持為‘1’ sel_out <= '0'; -- 設置奇偶校驗的數據源為數據發送總線 sel_pv <= '0'; -- 初始化串行加載序列的索引變量 si_count <= TOTAL_BIT-1; -- 改變狀態為加載 state <= UART_LOAD; else -- 停止對信號監測器的復位 reset_dt <= '1'; end if; -------- 數據加載和發送狀態-------- -- 加載狀態 when UART_LOAD => -- 如果 overflow 信號為‘1’,表示數據加載完成 if overflow = '1' then -- 復位子模塊 reset_parts <= '0'; -- 子模塊使能信號無效 ce_parts <= '0'; -- 選擇移位寄存器串行輸入為串行加載序列 sel_si <= '0'; -- 選擇移位寄存器的時鐘為波特率始終 -- 選擇計數器的時鐘為波特率發生器的指示信號 sel_clk <= '0'; -- 使得輸出保持為‘1’ sel_out <= '0'; -- 設置奇偶校驗的數據源為數據發送總線 sel_pv <= '0'; -- 改變狀態為發送 state <= UART_SEND; else -- 選擇移位寄存器的時鐘為系統時鐘 -- 選擇計數器的時鐘為系統時鐘 sel_clk <= '1'; -- 通過增加 si_count,生成串行加載序列 if not(si_count = TOTAL_BIT-1) then si_count <= si_count+1; else si_count <= 0; end if; -- 子模塊復位信號無效 reset_parts <= '1'; -- 子模塊使能信號有效 ce_parts <= '1'; end if; -- 發送狀態 when UART_SEND => -- 如果 overflow 為‘1’,表示發送完成 if overflow = '1' then -- 輸出發送完成的指示信號 send_over <= '1'; -- 改變狀態為發送完成 state <= UART_END_SEND; else -- 子模塊復位信號無效 reset_parts <= '1'; -- 子模塊使能信號有效 ce_parts <= '1'; end if; -- 發送完成狀態 when UART_END_SEND => -- 子模塊使能信號無效 ce_parts <= '0'; -- 復位信號監測器 reset_dt <= '0'; -- 恢復發送完成指示信號 send_over <= '0'; -- 改變狀態為空閑 state <= UART_IDLE; -------- 數據接收狀態-------- -- 接收狀態 when UART_RECV => -- 如果 overflow 變為“1”,表示接收完成 if overflow = '1' then -- 輸出接收指示信號 recv <= '1'; -- 改變狀態為接收完成 state <= UART_END_RECV; else -- 子模塊復位信號無效 reset_parts <= '1'; -- 子模塊使能信號有效 ce_parts <= '1'; end if; -- 接收完成狀態 when UART_END_RECV => -- 進行奇偶校驗 if not(regs(0) = parity) then error <= '1'; end if; -- 子模塊使能信號無效 ce_parts <= '0'; -- 復位信號監測器 reset_dt <= '0'; -- 恢復接收完成指示信號 recv <= '0'; -- 改變狀態為空閑 state <= UART_IDLE; -- 如果產生未知狀態,輸出錯誤信息 when others => error <= '1'; -- 恢復到空閑狀態 state <= UART_IDLE; end case; end if; end process;
3.10 UART 頂層模塊的實現
上面介紹了 UART 各個模塊的基本原理和實現方法,要實現 UART 還需要將所有的模塊連接起來,即需要編寫一個頂層模塊。頂層模塊實現了所有 UART 和外部器件之間的通信接口,端口定義如表 17 所示。
表 17 UART 頂層模塊端口定義表
由于 UART 頂層模塊包括了所有的子模塊,所以其實現代碼也比較復雜,為了便于讀者理解,下面分 3 個部分進行介紹。
1)實體聲明
UART頂層模塊的實體聲明中除了端口的聲明外,還需要聲明所有子模塊需要使用的類屬參數,包括 DATA_BIT(數據位個數)、TOTAL_BIT(總數據個數)、PARITY_RULE(奇偶校驗規則)、FULL_PULSE_COUNT(完整波特率時鐘對應的計數)和 RISE_PULSE_COUNT(波特率時鐘上升沿對應的計數)。
實體聲明部分的代碼如下:
library IEEE; use IEEE.std_logic_1164.all; use WORK.UART_PACKAGE.all; entity UART is generic( -- 數據位個數 DATA_BIT : integer := 8; -- 總數據個數 TOTAL_BIT : integer := 10; -- 奇偶校驗規則 PARITY_RULE : PARITY := NONE; --完整波特率時鐘對應的計數 FULL_PULSE_COUNT : BD_COUNT := BD9600_FPC; --波特率時鐘上升沿對應的計數 RISE_PULSE_COUNT : BD_COUNT := BD9600_HPC ); port( -- 時鐘信號 clk : in STD_LOGIC; -- 復位信號 reset_n : in STD_LOGIC; -- 發送控制信號 send : in STD_LOGIC; -- 數據發送總線 send_bus : in STD_LOGIC_VECTOR(7 downto 0); -- 發送完成信號 send_over : out STD_LOGIC; -- 錯誤提示信號 error : out STD_LOGIC; -- 接收提示信號 recv : out STD_LOGIC; -- 數據接收總線 recv_buf : out STD_LOGIC_VECTOR(7 downto 0); -- RS-232 數據接收端口 RxD : in STD_LOGIC; -- RS-232 數據發送端口 TxD : out STD_LOGIC; ); end UART;
2)子模塊和內部信號聲明
子模塊聲明就是將各個子模塊實體端口、類屬參數的定義方式按照組件的格式聲明一遍。聲明組件的格式和聲明實體完全一致,惟一的差別在于實體聲明使用 entity 和 end entity,而組件聲明使用 component 和 end component,所以實際編寫過程中沒必要完整地書寫一遍聲明內容,只需要將實體聲明的代碼拷貝過來并將 entity 修改為 component 即可。
UART 頂層模塊中實體聲明的代碼如下:
-- 波特率發生器組件聲明 component baudrate_generator generic( FULL_PULSE_COUNT : BD_COUNT := BD9600_FPC; RISE_PULSE_COUNT : BD_COUNT := BD9600_HPC ); port ( ce : in STD_LOGIC; clk : in STD_LOGIC; reset_n : in STD_LOGIC; bg_out : out STD_LOGIC; indicator : out STD_LOGIC ); endcomponent; -- 計數器組件聲明 component counter generic( MAX_COUNT : INTEGER := 10 ); port ( ce : in STD_LOGIC; clk : in STD_LOGIC; reset_n : in STD_LOGIC; overflow : out STD_LOGIC ); end component; -- 信號監測器 component detector port ( RxD : in STD_LOGIC; clk : in STD_LOGIC; reset_n : in STD_LOGIC; new_data : out STD_LOGIC ); end component; -- 奇偶校驗器 component parity_verifier generic( DATA_LENGTH : INTEGER := 8; PARITY_RULE : PARITY := NONE ); port ( source : in STD_LOGIC_VECTOR(DATA_LENGTH-1 downto 0); parity : out STD_LOGIC ); end component; -- 移位寄存器 component shift_register generic( TOTAL_BIT : INTEGER := 10 ); port ( clk : in STD_LOGIC; din:inSTD_LOGIC; reset_n : in STD_LOGIC; dout:outSTD_LOGIC; regs : out STD_LOGIC_VECTOR(TOTAL_BIT-1 downto 0) ); end component; -- 二選一選擇器 component switch port ( din1 : in STD_LOGIC; din2 : in STD_LOGIC; sel : in STD_LOGIC; dout : out STD_LOGIC ); end component; -- 總線選擇器 component switch_bus generic( BUS_WIDTH : INTEGER := 8 ); port ( din1 : in STD_LOGIC_VECTOR(BUS_WIDTH-1 downto 0); din2 : in STD_LOGIC_VECTOR(BUS_WIDTH-1 downto 0); sel : in STD_LOGIC; dout : out STD_LOGIC_VECTOR(BUS_WIDTH-1 downto 0) ); end component; -- UART 內核 component uart_core generic( DATA_BIT : INTEGER := 8; PARITY_RULE : PARITY := NONE; TOTAL_BIT : INTEGER := 10 ); port ( clk : in STD_LOGIC; new_data : in STD_LOGIC; overflow : in STD_LOGIC; parity : in STD_LOGIC; regs : in STD_LOGIC_VECTOR(TOTAL_BIT-1 downto 0); reset_n : in STD_LOGIC; send : in STD_LOGIC; send_bus : in STD_LOGIC_VECTOR(DATA_BIT-1 downto 0); ce_parts : out STD_LOGIC; error : out STD_LOGIC; recv : out STD_LOGIC; recv_bus : out STD_LOGIC_VECTOR(DATA_BIT-1 downto 0); reset_dt : out STD_LOGIC; reset_parts : out STD_LOGIC; sel_clk : out STD_LOGIC; sel_out : out STD_LOGIC; sel_pv : out STD_LOGIC; sel_si : out STD_LOGIC; send_over : out STD_LOGIC; send_si : out STD_LOGIC ); end component;
完成組件聲明后,需要對內部信號進行聲明。內部信號的主要作用有兩種,第一種是作為各個模塊(組件)之間的連接信號,第二種是作為寄存器使用。在 UART 頂層模塊中的內部信號主要用于連接各個組件(模塊),即作為連接信號使用。
內部信號聲明的代碼如下:
---- 常數 ----- constant VCC_CONSTANT : STD_LOGIC := '1'; ---- 內部信號聲明 ---- signal bg_clk : STD_LOGIC; signal bg_out : STD_LOGIC; signal ce_parts : STD_LOGIC; signal clk_inv : STD_LOGIC; signal counter_clk : STD_LOGIC; signal indicator : STD_LOGIC; signal new_data : STD_LOGIC; signal overflow : STD_LOGIC; signal parity : STD_LOGIC; signal reset_dt : STD_LOGIC; signal reset_parts : STD_LOGIC; signal sel_clk : STD_LOGIC; signal sel_out : STD_LOGIC; signal sel_pv : STD_LOGIC; signal sel_si : STD_LOGIC; signal send_si : STD_LOGIC; signal sr_in : STD_LOGIC; signal sr_out : STD_LOGIC; signal VCC : STD_LOGIC; signal pv_source : STD_LOGIC_VECTOR (DATA_BIT-1 downto 0); signal recv_parity_source : STD_LOGIC_VECTOR (DATA_BIT-1 downto 0); signal regs : STD_LOGIC_VECTOR (TOTAL_BIT-1 downto 0); signal send_parity_source : STD_LOGIC_VECTOR (DATA_BIT-1 downto 0);
3)子模塊實例化
子模塊實例化表示的就是根據子模塊(組件)的聲明定義一個子模塊實例,同時定義此實例的信號連接方式以及類屬參數等。
UART 頂層模塊的子模塊實例化代碼如下:
-- 波特率發生器實例 U_BG : baudrate_generator port map( bg_out => bg_out, ce => ce_parts, clk => clk, indicator => indicator, reset_n => reset_parts ); -- 總線選擇器實例 U_BusSwitch : switch_bus port map( din1 => send_parity_source( DATA_BIT-1 downto 0 ), din2 => recv_parity_source( DATA_BIT-1 downto 0 ), dout => pv_source( DATA_BIT-1 downto 0 ), sel => sel_pv ); -- UART 內核實例 U_Core : uart_core port map( ce_parts => ce_parts, clk => clk, error => error, new_data => new_data, overflow => overflow, parity => parity, recv => recv, recv_bus => recv_parity_source( DATA_BIT-1 downto 0 ), regs => regs( TOTAL_BIT-1 downto 0 ), reset_dt => reset_dt, reset_n => reset_n, reset_parts => reset_parts, sel_clk => sel_clk, sel_out => sel_out, sel_pv => sel_pv, sel_si => sel_si, send => send, send_bus => send_parity_source( DATA_BIT-1 downto 0 ), send_over => send_over, send_si => send_si ); -- 計數器實例 U_Counter : counter port map( ce => ce_parts, clk => counter_clk, overflow => overflow, reset_n => reset_parts ); -- 計數器時鐘源選擇器 U_CounterClkSwitch : switch port map( din1 => indicator, din2 => clk_inv, dout => counter_clk, sel => sel_clk ); -- 信號監測器 U_Detector : detector port map( RxD => RxD, clk => clk, new_data => new_data, reset_n => reset_dt ); -- 奇偶校驗器 U_ParityVerifier : parity_verifier port map( parity => parity, source => pv_source( DATA_BIT-1 downto 0 ) ); -- 移位寄存器輸入源選擇器實例 U_SISwitch : switch port map( din1 => send_si, din2 => RxD, dout => sr_in, sel => sel_si ); -- 移位寄存器實例 U_SR : shift_register port map( clk => bg_clk, din => sr_in, dout => sr_out, regs => regs( TOTAL_BIT-1 downto 0 ), reset_n => reset_parts ); -- 移位寄存器時鐘源選擇器實例 U_SRClkSwitch : switch port map( din1 => bg_out, din2 => clk_inv, dout => bg_clk, sel => sel_clk ); -- 輸出選擇器實例 U_TXDSwitch : switch port map( din1 => VCC, din2 => sr_out, dout => TxD, sel => sel_out );
以上便是 UART 頂層模塊的實現方法, UART 頂層模塊就是將 UART 內核和其他模塊連接起來組成一個完成的模塊。
3.11 測試平臺的編寫和仿真
為了驗證 UART 實現的正確性,需要設計一個仿真平臺對 UART 頂層模塊進行仿真,下面就介紹一下 UART 仿真平臺的編寫方法和仿真結果的分析。仿真平臺是一個 VHDL 文件,其本身也是一個實體(entity)。仿真平臺除了包含了實體聲明(entity)和結構體(architecture)以外,還需要有一個配置(configuration)。例如,針對 UART 頂層模塊 uart_top.vhd 編寫的測試平臺就有如下的結構:
-- 庫聲明 library ieee; use work.uart_package.all; use ieee.std_logic_1164.all; -- 實體聲明 entity uart_top_tb is -- 實體聲明內容(略) end uart_top_tb; -- 結構體 architecture TB_ARCHITECTURE of uart_top_tb is -- 結構體內容(略) end TB_ARCHITECTURE; -- 配置 configuration TESTBENCH_FOR_uart_top of uart_top_tb is for TB_ARCHITECTURE for UUT : uart_top use entity work.uart_top(uart_top); end for; end for; end TESTBENCH_FOR_uart_top;
從上面的代碼可以看出,配置的作用就是為測試對象指定一個結構體,下面從 3 個方面介紹測試平臺的實現代碼。
1)實體聲明
一般來說,測試平臺的實體聲明中不會有輸入/輸出信號,僅包括其測試對象所需要的類屬參數。UART 測試平臺的實體聲明如下:
entity uart_top_tb is -- 定義類屬參數 generic( DATA_BIT : INTEGER := 8; TOTAL_BIT : INTEGER := 10; PARITY_RULE : PARITY := none; FULL_PULSE_COUNT : BD_COUNT := 5208; RISE_PULSE_COUNT : BD_COUNT := 2604 ); end uart_top_tb;
2)組件和信號聲明
組件聲明就是對測試對象的聲明。在測試平臺中,測試對象是作為一個組件來呈現的。比如 UART 測試平臺中對 UART 頂層模塊的組件聲明如下:
-- UART 頂層模塊組件聲明 component uart_top generic( DATA_BIT : INTEGER := 8; TOTAL_BIT : INTEGER := 10; PARITY_RULE : PARITY := none; FULL_PULSE_COUNT : BD_COUNT := 5208; RISE_PULSE_COUNT : BD_COUNT := 2604 ); port( RxD : in std_logic; clk : in std_logic; reset_n : in std_logic; send : in std_logic; send_bus : in std_logic_vector(7 downto 0); TxD : out std_logic; error : out std_logic; recv : out std_logic; send_over : out std_logic; recv_buf : out std_logic_vector(7 downto 0) ); end component;
測試對象肯定有一些輸入/輸出信號,它們在測試平臺中是定義為內部信號的,可以直接對這些內部信號進行賦值來控制測試對象的輸入信號。實際上,一般來說測試平臺的內部信號都是和測試對象的輸入/輸出信號一一對應的,代碼如下:
-- 內部信號 signal RxD : std_logic := '1'; signal clk : std_logic := '0'; signal reset_n : std_logic := '0'; signal send : std_logic := '0'; signal send_bus : std_logic_vector(7 downto 0) := (others => '0'); signal TxD : std_logic := '1'; signal error : std_logic := '0'; signal recv : std_logic := '0'; signal send_over : std_logic := '0'; signal recv_buf : std_logic_vector(7 downto 0) := (others => '0');
3)測試流程的控制
編寫測試流程的第一個步驟是對測試對象的實例化,即將 UART 頂層模塊實例化,實現代碼如下:
-- 測試對象實例化 UUT : uart_top generic map ( DATA_BIT => DATA_BIT, TOTAL_BIT => TOTAL_BIT, PARITY_RULE => PARITY_RULE, FULL_PULSE_COUNT => FULL_PULSE_COUNT, RISE_PULSE_COUNT => RISE_PULSE_COUNT ); port map ( RxD => RxD, clk => clk, reset_n => reset_n, send => send, send_bus => send_bus, TxD => TxD, error => error, recv => recv, send_over => send_over, recv_buf => recv_buf )
第二個步驟是產生時鐘信號,由于時鐘信號比較有規律,所以可以用一個過程(process)來實現,代碼如下:
-- 產生時鐘信號 clk_gen : process begin clk <= not clk; wait for 10 ns; end process;
最后一個步驟就是實現測試的主流程,一般是在一個過程(Process)中實現。對于 UART的測試,主要的內容就是數據發送的測試和數據接收的測試,測試主流程的流程圖如圖 27所示。
圖 27 UART 測試流程圖
測試主流程的實現代碼如下:
-- 測試主流程 main: process begin -- 復位 reset_n <= '0'; wait for 100 ns; -- 結束復位 reset_n <= '1'; wait for 100 ns; -- 測試數據發送 wait for 10 ns; -- 發送數據為 01010101 send_bus <= "01010101"; -- send 為高激活數據發送 send <= '1'; wait for 20 ns; send <= '0'; -- 測試數據接收 -- 使用測試用波特率 if FULL_PULSE_COUNT = BDTEST_FPC then wait for 2500 ns; -- 仿真 RS-232 輸入信號 RxD for i in 0 to 9 loop RxD <= test_si_none(i); -- 測試波特率為 10,所以輸入間隔 10 個時鐘,總共 200ns wait for 200 ns; end loop; -- 使用實際波特率 9600 elsif FULL_PULSE_COUNT = BD9600_FPC then wait for 1.2 ms; -- 仿真 RS-232 輸入信號 RxD for i in 0 to 9 loop RxD <= test_si_none(i); -- 測試波特率為 9600,所以輸入間隔 9600 個時鐘,總共 104.17μs wait for 104.17 us; end loop; end if; wait ; end process;
上面代碼中的 test_si_none 是在 UART_PACKAGE 庫中定義的輸入測試數據串行序列(無奇偶教研),此外還定義了奇校驗和偶校驗對應的序列,代碼如下:
-- 類型聲明 type test_vectors is array (0 to 10) of std_logic; -- 無奇偶校驗測試序列 constant test_si_none : test_vectors := ('0', '1', '0', '1', '0', '1', '0', '1', '0', others => '1'); -- 奇校驗測試序列 constant test_si_odd : test_vectors := ('0', '1', '0', '1', '0', '1', '1', '1', '0', '1', others => '1'); -- 偶校驗測試序列 constant test_si_even : test_vectors := ('0', '1', '0', '1', '0', '1', '0', '1', '0', '0', others => '1');
在波特率為 9600 情況下利用上述測試平臺對 UART 進行仿真,得到數據發送的仿真結果分別如圖 28 所示。
圖 5-28 UART 數據發送仿真結果
從圖 28 可以看出,待發送的數據是 0x55(十六進制,即 send_bus 總線上的數據),由send 信號觸發后,RS-232 的 TxD 端輸出為序列 001010101(二進制),其中第一位是起始位,中間的八位正是待發送的數據 0xFF,最后再發送完成后輸出提示信號 send_over。可見,發送的結果符合 RS-232 的時序要求,UART 的發送功能完全正確。
同樣測試條件下數據接收的仿真結果如圖 29 所示。首先,RxD 上的數據序列為0101010010(二進制),表示起始位 0,之后數據位是 10101010(二進制),所以待接收的數據是 0xAA(十六進制)。recv_buf 是數據接收總線,可以看到其最終得到的數據正是 0xAA(十六進制),并且,在接收完成后 recv 信號會輸出一個脈寬的高電平作為提示。由上述可知,數據接收的過程也完全正確。
圖 29 UART 數據接收仿真結果
審核編輯:劉清
-
FPGA
+關注
關注
1629文章
21752瀏覽量
604116 -
控制器
+關注
關注
112文章
16390瀏覽量
178442 -
uart
+關注
關注
22文章
1239瀏覽量
101469 -
SPI接口
+關注
關注
0文章
258瀏覽量
34412 -
PCB布局
+關注
關注
9文章
183瀏覽量
27863
原文標題:往期精選:基于 FPGA 的 UART 控制器設計(附代碼)
文章出處:【微信號:HXSLH1010101010,微信公眾號:FPGA技術江湖】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論