
本系列將帶來FPGA的系統性學習,從最基本的數字電路基礎開始,最詳細操作步驟,最直白的言語描述,手把手的“傻瓜式”講解,讓電子、信息、通信類專業學生、初入職場小白及打算進階提升的職業開發者都可以有系統性學習的機會。
系統性的掌握技術開發以及相關要求,對個人就業以及職業發展都有著潛在的幫助,希望對大家有所幫助。后續會陸續更新 Xilinx 的 Vivado、ISE 及相關操作軟件的開發的相關內容,學習FPGA設計方法及設計思想的同時,實操結合各類操作軟件,會讓你在技術學習道路上無比的順暢,告別技術學習小BUG卡破腦殼,告別目前忽悠性的培訓誘導,真正的去學習去實戰應用,這種快樂試試你就會懂的。話不多說,上貨。
IIC協議驅動設計
作者:郝旭帥 校對:陸輝
本篇實現基于叁芯智能科技的SANXIN -B01 FPGA開發板,以下為配套的教程,如有入手開發板,可以登錄官方淘寶店購買,還有配套的學習視頻。
SANXIN-B01開發板verilog教程V3電子版
FPGA工程師線上研修班,常年開班,隨時報名,新增高速接口、光纖通信等內容!
I2C 即 Inter-Integrated Circuit(集成電路總線),這種總線類型是由飛利浦半導體公司在八十年代初設計出來的一種簡單、雙向、二線制、同步串行總線,主要是用來連接整體電路(ICS) ,I2C是一種多向控制總線,也就是說多個芯片可以連接到同一總線結構下,同時每個芯片都可以作為實時數據傳輸的控制源。這種方式簡化了信號傳輸總線接口。
I2C 總線是一種串行數據總線,只有二根信號線,一根是雙向的數據線 SDA,另一根是時鐘線 SCL,兩條線可以掛多個設備。 I2C 設備(絕大多數)里有個固化的地址,只有在兩條線上傳輸的值等于 I2C 設備的固化地址時,其才會作出響應。通常我們為了方便把 I2C 設備分為主設備和從設備,基本上誰控制時鐘線(即控制SCL的電平高低變換)誰就是主設備。

A段為空閑段,在不進行數據傳輸時,SCL 和 SDA 保持為高電平。
B段為起始段(START CONDITION),在進行任何數據傳輸前,都應該先有一個起始段。當 SCL 為高電平期間,SDA有一個從高到低的變化視為起始段。
C段為結束段(STOP CONDITION),在數據傳輸結束后,都應該有一個結束段。當SCL為高電平期間,SDA有一個從低到高的變化視為結束段。
D段分為兩段數據有效段(ADDRESS OR ACKNOWLEDGE)和數據修改段(DATA ALLOWED TO CHANGE)。在傳輸數據時,在 SCL 為高電平期間,SDA 不能夠發生改變,視為數據有效段;在傳輸數據時,在 SCL 為低電平期間,SDA 可以發生改變(修改為下一個要發送的bit),視為數據修改段。
IIC的傳輸總結為:空閑時,SCL 和 SDA 都是高電平;發送信息前,首先發送起始段(SCL為高時,SDA有一個從高到低的變化);在發送信息時,每一個 SCL 脈沖,發送或者接收1bit,該bit在SCL為低電平時發生改變,在SCL為高電平時,保持穩定;發送信息完成后,發送結束段(SCL 為高時,SDA 有一個從低到高的變化)。
SDA的數據線是雙向傳輸的。在讀取時,SDA 從從設備到主設備;在寫入時,SDA從主設備到從設備。
在 IIC 傳輸時,為了保證通信的成功。發送方每發送8 bit 數據后,要求接收方回復一個應答位。

應答位為低電平有效。

24LC64是容量為8K*8bit(64Kbit)通信接口為IIC的EEPROM。EEPROM擁有掉電不丟失的功能,經常用來保存一些配置信息等等。這個器件可以一次進行最多32字節的頁寫入和一次進行整片讀出的功能。


A0、A1、A2為用戶可配置的芯片選擇端。該管腳一般都連接電源或者地,在通信時,24LC64作為從機,主機在發送的命令中存在地址號,當地址號與A0、A1、A2管腳的電平相同時,該芯片視為被選中。
SDA和SCL為IIC協議中的時鐘線和數據線。
WP為寫保護端,當連接高電平時,該芯片只能夠被讀,不能夠寫入;當連接低電平或者空置時,該芯片可以被讀寫。
24LC64的寫入方式有兩種:單字節寫入和多字節寫入。
單字節寫入時序圖:

在控制字節中,1010為EEPROM的標號;A2、A1、A0為主機發送的地址號,接收設備接收到此信息后,會與本身的A2、A1、A0相對比,相同時,視為被選中;最后1bit,0表示寫入、1表示讀出。
在高字節地址中,高三位地址無效。24LC64的地址范圍為8K,地址位13位即可。13位地址分為高五低八,在IIC中規定每次發送8bit,所以高五的前面必須發送任意3bit。
多字節寫入時序圖:

DATA BYTE 0被寫入到指定的地址中,后續的數據寫入到指定地址的后續地址中。EEPROM中每32個字節分為一塊,寫入時不能跨塊寫入。
24LC64的讀出方式有三種:當前地址單字節讀、任意地址單字節讀和任意地址多字節讀。
當前地址單字節讀的時序圖如下:

當主機接收24LC64的數據后,不發送ACK,而是發送高電平。
任意地址單字節讀取時序圖,如下:

首先寫入指定地址,然后按照當前地址讀即可。
任意地址多字節讀,時序如下:

任意地址多字節讀就是在任意地址單字節讀的時序中,接收到從機發送的數據后,不要發送NO ACK而是發送ACK,此時從機就會繼續發送后續地址的數據,直到讀取的長度符合設計要求,然后發送NO ACK以及STOP結束傳輸。
下圖為24LC64的時序參數圖。



根據時序參數,決定將IIC的速率定為50KHz。發送時,數據改變在SCL的低電平的正中間;讀取時,在SCL高電平的正中間進行讀取。
IIC一般用在配置或者讀取少量的信息,對于速率來說要求相對較低。

在開發板上,搭載了一個24LC64。
電路原理圖如下:

A0、A1、A2都被接地,主機再發送控制字節時,需要將此三位發送低電平。
WP管腳被接地,24LC64的整個片子都可以被讀寫。

在IIC協議中,SDA作為數據線使用,每一個SCL的脈沖,傳遞一bit的數據。在讀取時,SDA由24LC64發送,FPGA接收;在寫入時,SDA由FPGA發送,24LC64接收。所以IIC協議只能實現半雙工通信。
在硬件電路中,一般是通過三態門來進行控制雙向IO。

此電路在FPGA的IO中也是存在的。下面來分析具體的工作原理。
三態門有三個端子,分別是輸入端(obuf),輸出端(SDA)和控制端(out_en)。工作方式為:當out_en有效時,obuf的值就可以輸出到SDA;當out_en無效時,obuf的值不能輸出到SDA,那么三態門就會輸出高阻態。
高阻態,字節理解為阻值特別大,也可以理解為斷開。高阻態與任何電平值相連接,所連接的線上的電平值都是和對方保持一致。在此可以延伸一下:所有的端口都是輸出,包括輸入端口在內,也會對外呈現出一種狀態,所以輸入端口對外呈現的狀態就是高阻態。對于雙向IO來說,輸出時,正常輸出即可;輸入時,就要呈現為高阻態。
設計任意地址的單字節讀寫控制器。大多數是配置或者讀取少量的信息,對于多字節的讀寫,可以采用多次單字節的讀寫完成。
24LC64的尋址范圍為8K,地址bit共計13bit,所以需要高5bit和低8bit。在有些兼容IIC協議接口的器件中,尋址范圍較少,8bit足夠表示,所以在設計時,設計出適合兩種尋址方式的驅動。
SCL的實現采用計數器控制,根據規定好的頻率,在合適的地方進行拉高或者拉低。由于SDA的變化都發生在SCL為高或者為低的中間,所以還需要產生對應的脈沖。
SDA根據協議和對應的脈沖進行發送對應的協議段。
本模塊命名為iic_drive。


暫不連接的端口,在后續測試時,會有其他模塊來控制或者讀取。
iic_scl采用計數器的方式實現。外部的時鐘為50MHz,iic_scl為100KHz,所以計數器需要記錄500個時鐘周期。
計數器由cnt_en控制。cnt_en有效時,cnt從0到500不斷循環;當cnt_en無效時,cnt保持等于0。
iic_scl在cnt等于0至249時,保持高電平;在250至499時,保持低電平。cnt等于125時,正好是iic_scl高電平的中間,用脈沖flag_high表示;cnt等于375時,正好是iic_scl低電平的中間,用脈沖flag_low表示。
其他信號采用狀態機作為輸出。out_en為三態門控制信號,sda_obuf為三態門輸出端,drive_cnt為發送或者接收的計數器,temp為發送或者接收的中間寄存器。
狀態轉移圖如下:

設計代碼為:
module iic_drive (input wire clk,input wire rst_n,input wire start_flag,input wire [6:0] slave_addr,input wire wren,input wire addr_sel,input wire [15:0] addr,input wire [7:0] wdata,output reg iic_scl,inout wire iic_sda,output reg iic_done,output reg [7:0] rdata);parameter FREQ_clk = 50_000_000;parameter FREQ_iic = 100_000;localparam T_SCL = FREQ_clk/FREQ_iic;localparam IDLE = 16'b0000_0000_0000_0001;localparam START = 16'b0000_0000_0000_0010;localparam CTRL = 16'b0000_0000_0000_0100;localparam CTRL_ACK = 16'b0000_0000_0000_1000;localparam HADDR = 16'b0000_0000_0001_0000;localparam HADDR_ACK = 16'b0000_0000_0010_0000;localparam LADDR = 16'b0000_0000_0100_0000;localparam LADDR_ACK = 16'b0000_0000_1000_0000;localparam WR = 16'b0000_0001_0000_0000;localparam WR_ACK = 16'b0000_0010_0000_0000;localparam RD_START = 16'b0000_0100_0000_0000;localparam RD_CTRL = 16'b0000_1000_0000_0000;localparam RD_CTRL_ACK = 16'b0001_0000_0000_0000;localparam RD = 16'b0010_0000_0000_0000;localparam NO_ACK = 16'b0100_0000_0000_0000;localparam STOP = 16'b1000_0000_0000_0000;reg [15:0] c_state;reg [15:0] n_state;reg [31:0] cnt;reg cnt_en;reg flag_high;reg flag_low;reg out_en;reg sda_obuf;reg [3:0] drive_cnt;reg [7:0] temp;assign iic_sda = (out_en == 1'b1) ? sda_obuf : 1'bz;always @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)cnt <= 32'd0;elseif (cnt < T_SCL - 1'b1 && cnt_en == 1'b1)cnt <= cnt + 1'b1;elsecnt <= 32'd0;endalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)iic_scl <= 1'b1;elseif (cnt < T_SCL/2)iic_scl <= 1'b1;elseiic_scl <= 1'b0;endalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)flag_high <= 1'b0;elseif (cnt == T_SCL/4 - 1'b1)flag_high <= 1'b1;elseflag_high <= 1'b0;endalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)flag_low <= 1'b0;elseif (cnt == (T_SCL * 3)/4 - 1'b1)flag_low <= 1'b1;elseflag_low <= 1'b0;endalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)c_state <= IDLE;elsec_state <= n_state;endalways @ * begincase (c_state)IDLE : beginif (start_flag == 1'b1)n_state = START;elsen_state = IDLE;endSTART : beginif (flag_high == 1'b1)n_state = CTRL;elsen_state = START;endCTRL : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)n_state = CTRL_ACK;elsen_state = CTRL;endCTRL_ACK : beginif (flag_high == 1'b1)if (iic_sda == 1'b0)if (addr_sel == 1'b1)n_state = HADDR;elsen_state = LADDR;elsen_state = START;elsen_state = CTRL_ACK;endHADDR : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)n_state = HADDR_ACK;elsen_state = HADDR;endHADDR_ACK : beginif (flag_high == 1'b1)if (iic_sda == 1'b0)n_state = LADDR;elsen_state = START;elsen_state = HADDR_ACK;endLADDR : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)n_state = LADDR_ACK;elsen_state = LADDR;endLADDR_ACK : beginif (flag_high == 1'b1)if (iic_sda == 1'b0)if (wren == 1'b1)n_state = WR;elsen_state = RD_START;elsen_state = START;elsen_state = LADDR_ACK;endWR : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)n_state = WR_ACK;elsen_state = WR;endWR_ACK : beginif (flag_high == 1'b1)if (iic_sda == 1'b0)n_state = STOP;elsen_state = START;elsen_state = WR_ACK;endSTOP : beginif (flag_high == 1'b1)n_state = IDLE;elsen_state = STOP;endRD_START : beginif (flag_high == 1'b1)n_state = RD_CTRL;elsen_state = RD_START;endRD_CTRL : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)n_state = RD_CTRL_ACK;elsen_state = RD_CTRL;endRD_CTRL_ACK : beginif (flag_high == 1'b1)if (iic_sda == 1'b0)n_state = RD;elsen_state = START;elsen_state = RD_CTRL_ACK;endRD : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)n_state = NO_ACK;elsen_state = RD;endNO_ACK : beginif (flag_high == 1'b1)n_state = STOP;elsen_state = NO_ACK;enddefault : n_state = IDLE;endcaseendalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)cnt_en <= 1'b0;elsecase (c_state)IDLE : cnt_en <= 1'b0;CTRL_ACK,HADDR_ACK,LADDR_ACK,WR_ACK,RD_CTRL_ACK : beginif (flag_high == 1'b1)if (iic_sda == 1'b0)cnt_en <= 1'b1;elsecnt_en <= 1'b0;elsecnt_en <= cnt_en;enddefault : cnt_en <= 1'b1;endcaseendalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)out_en <= 1'b0;elsecase (c_state)IDLE : out_en <= 1'b0;START : out_en <= 1'b1;CTRL : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)out_en <= 1'b0;elseout_en <= 1'b1;endCTRL_ACK : out_en <= 1'b0;HADDR : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)out_en <= 1'b0;elseif (flag_low == 1'b1)out_en <= 1'b1;elseout_en <= out_en;endHADDR_ACK : out_en <= 1'b0;LADDR : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)out_en <= 1'b0;elseif (flag_low == 1'b1)out_en <= 1'b1;elseout_en <= out_en;endLADDR_ACK : out_en <= 1'b0;WR : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)out_en <= 1'b0;elseif (flag_low == 1'b1)out_en <= 1'b1;elseout_en <= out_en;endWR_ACK : out_en <= 1'b0;STOP : beginif (flag_low == 1'b1)out_en <= 1'b1;elseout_en <= out_en;endRD_START : beginif (flag_low == 1'b1)out_en <= 1'b1;elseout_en <= out_en;endRD_CTRL : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)out_en <= 1'b0;elseout_en <= 1'b1;endRD_CTRL_ACK : out_en <= 1'b0;RD : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)out_en <= 1'b1;elseout_en <= 1'b0;endNO_ACK : out_en <= 1'b1;default : out_en <= 1'b0;endcaseendalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)sda_obuf <= 1'b1;elsecase (c_state)IDLE : sda_obuf <= 1'b1;START : beginif (flag_high == 1'b1)sda_obuf <= 1'b0;elsesda_obuf <= 1'b1;endCTRL : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)sda_obuf <= temp[7];elsesda_obuf <= sda_obuf;endCTRL_ACK : sda_obuf <= 1'b0;HADDR : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)sda_obuf <= temp[7];elsesda_obuf <= sda_obuf;endHADDR_ACK : sda_obuf <= 1'b0;LADDR : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)sda_obuf <= temp[7];elsesda_obuf <= sda_obuf;endLADDR_ACK : sda_obuf <= 1'b0;WR : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)sda_obuf <= temp[7];elsesda_obuf <= sda_obuf;endWR_ACK : sda_obuf <= 1'b0;STOP : beginif (flag_low == 1'b1)sda_obuf <= 1'b0;elseif (flag_high == 1'b1)sda_obuf <= 1'b1;elsesda_obuf <= sda_obuf;endRD_START : beginif (flag_low == 1'b1)sda_obuf <= 1'b1;elseif (flag_high == 1'b1)sda_obuf <= 1'b0;elsesda_obuf <= sda_obuf;endRD_CTRL : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)sda_obuf <= temp[7];elsesda_obuf <= sda_obuf;endRD_CTRL_ACK : sda_obuf <= 1'b0;RD : beginif (flag_low == 1'b1 && drive_cnt == 4'd8)sda_obuf <= 1'b1;elsesda_obuf <= sda_obuf;endNO_ACK : sda_obuf <= sda_obuf;default : sda_obuf <= 1'b1;endcaseendalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)drive_cnt <= 4'd0;elsecase (c_state)IDLE : drive_cnt <= 4'd0;START : drive_cnt <= 4'd0;CTRL : beginif (flag_low == 1'b1)if (drive_cnt < 4'd8)drive_cnt <= drive_cnt + 1'b1;elsedrive_cnt <= 4'd0;elsedrive_cnt <= drive_cnt;endCTRL_ACK : drive_cnt <= 4'd0;HADDR : beginif (flag_low == 1'b1)if (drive_cnt < 4'd8)drive_cnt <= drive_cnt + 1'b1;elsedrive_cnt <= 4'd0;elsedrive_cnt <= drive_cnt;endHADDR_ACK : drive_cnt <= 4'd0;LADDR : beginif (flag_low == 1'b1)if (drive_cnt < 4'd8)drive_cnt <= drive_cnt + 1'b1;elsedrive_cnt <= 4'd0;elsedrive_cnt <= drive_cnt;endLADDR_ACK : drive_cnt <= 4'd0;WR : beginif (flag_low == 1'b1)if (drive_cnt < 4'd8)drive_cnt <= drive_cnt + 1'b1;elsedrive_cnt <= 4'd0;elsedrive_cnt <= drive_cnt;endWR_ACK : drive_cnt <= 4'd0;STOP : drive_cnt <= 4'd0;RD_START : drive_cnt <= 4'd0;RD_CTRL : beginif (flag_low == 1'b1)if (drive_cnt < 4'd8)drive_cnt <= drive_cnt + 1'b1;elsedrive_cnt <= 4'd0;elsedrive_cnt <= drive_cnt;endRD_CTRL_ACK : drive_cnt <= 4'd0;RD : beginif (flag_high == 1'b1 && drive_cnt < 4'd8)drive_cnt <= drive_cnt + 1'b1;elseif (flag_low == 1'b1 && drive_cnt == 4'd8)drive_cnt <= 4'd0;elsedrive_cnt <= drive_cnt;endNO_ACK : drive_cnt <= 4'd0;default : drive_cnt <= 4'd0;endcaseendalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)temp <= 8'd0;elsecase (c_state)IDLE : temp <= 8'd0;START : beginif (flag_high == 1'b1)temp <= {slave_addr, 1'b0};elsetemp <= temp;endCTRL : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)temp <= temp << 1'b1;elsetemp <= temp;endCTRL_ACK : beginif (flag_high == 1'b1 && iic_sda == 1'b0)if (addr_sel == 1'b1)temp <= addr[15:8];elsetemp <= addr[7:0];elsetemp <= temp;endHADDR : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)temp <= temp << 1'b1;elsetemp <= temp;endHADDR_ACK : beginif (flag_high == 1'b1 && iic_sda == 1'b0)temp <= addr[7:0];elsetemp <= temp;endLADDR : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)temp <= temp << 1'b1;elsetemp <= temp;endLADDR_ACK : beginif (flag_high == 1'b1 && iic_sda == 1'b0)if (wren == 1'b1)temp <= wdata;elsetemp <= {slave_addr, 1'b1};elsetemp <= temp;endWR : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)temp <= temp << 1'b1;elsetemp <= temp;endWR_ACK : temp <= 8'd0;STOP : temp <= 8'd0;RD_START : temp <= temp;RD_CTRL : beginif (flag_low == 1'b1 && drive_cnt < 4'd8)temp <= temp << 1'b1;elsetemp <= temp;endRD_CTRL_ACK : temp <= 8'd0;RD : beginif (flag_high == 1'b1 && drive_cnt < 4'd8)temp <= {temp[6:0], iic_sda};elsetemp <= temp;endNO_ACK : temp <= 8'd0;default : temp <= 8'd0;endcaseendalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)iic_done <= 1'b0;elseif (c_state == STOP && flag_high == 1'b1)iic_done <= 1'b1;elseiic_done <= 1'b0;endalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)rdata <= 8'd0;elseif (c_state == RD && flag_low == 1'b1 && drive_cnt == 4'd8)rdata <= temp;elserdata <= rdata;endendmodule

在仿真時,需要外部提供ACK。如果直接仿真的話,由于外部沒有提供ACK,導致一直都會重復發送一小段。在仿真中,我們自己給出ACK的難度也比較大。
24LC64的廠商幫我們解決了這個問題,他們在造出這個芯片的同時,還設計出了可以幫助我們仿真調試的verliog模型。此模型放在msim->24LC64_sim_module中,文件名字為24LC64.v。此模型的sda也是雙向IO,所以在硬件設計時,會在此IO上加上拉電阻,在仿真時,我們也要模擬上拉電阻。
仿真設計如下:
`timescale 1ns/1psmodule iic_drive_tb;reg clk;reg rst_n;reg start_flag;reg [6:0] slave_addr;reg wren;reg addr_sel;reg [15:0] addr;reg [7:0] wdata;wire iic_scl;wire iic_sda;wire iic_done;wire [7:0] rdata;pullup(iic_sda);iic_drive iic_drive_inst(.clk (clk),.rst_n (rst_n),.start_flag (start_flag),.slave_addr (slave_addr),.wren (wren),.addr_sel (addr_sel),.addr (addr),.wdata (wdata),.iic_scl (iic_scl),.iic_sda (iic_sda),.iic_done (iic_done),.rdata (rdata));M24LC64 M24LC64_inst(.A0 (1'b0),.A1 (1'b0),.A2 (1'b0),.WP (1'b0),.SDA (iic_sda),.SCL (iic_scl));initial clk = 1'b0;always # 10 clk = ~clk;initial beginrst_n = 1'b0;start_flag = 1'b0;slave_addr = 7'b1010_000;wren = 1'b0;addr_sel = 1'b1;addr = 16'h5555;wdata = 8'haa;# 201rst_n = 1'b1;# 200;@ (posedge clk);# 2;start_flag = 1'b1;wren = 1'b1;@ (posedge clk);# 2;start_flag = 1'b0;@ (posedge iic_done);# 200;@ (posedge clk);# 2;start_flag = 1'b1;wren = 1'b0;@ (posedge clk);# 2;start_flag = 1'b0;@ (posedge iic_done);# 200$stop;endendmodule
pullup(iic_sda):將iic_sda上加上拉電阻。
仿真時,在地址16’h5555(雖然高三位沒有用),寫入了8’ha;寫入完成后,又將此數據讀出。
當對24LC64進行寫入之后,它會進入到一個內部寫循環(和flash類似),在此期間,不接受任何指令。所以在仿真時,需要等待寫循環完成,時間為5ms(此參數在仿真模型中,可以進行修改)。
寫入的仿真圖如下:

讀出的仿真圖如下:

上述的設計在使用時會有上游控制器進行控制,所以板級測試還需要設計控制器。在控制器中只需要某一個地址寫入數據,然后讀出即可。測試時,可以多次更換地址和數據,保證設計的正確性。
在測試時,將slave_addr、addr_sel、addr、wdata設置為定值。slave_addr為7’b1010_000,addr_sel為1’b1,addr為16’h5555,wdata為8’haa。
將上有控制器命名為test_ctrl,頂層命名為test。

test_ctrl模塊采用狀態機設計實現。

module test_ctrl (input wire clk,input wire rst_n,output reg start_flag,output reg wren,input wire iic_done,input wire [7:0] rdata);localparam WR = 4'b0001;localparam WR_WAIT = 4'b0010;localparam RD_WAIT = 4'b0100;localparam TESTDONE = 4'b1000;reg [3:0] c_state;reg [3:0] n_state;always @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)c_state <= WR;elsec_state <= n_state;endalways @ * begincase (c_state)WR : n_state = WR_WAIT;WR_WAIT : beginif (iic_done == 1'b1)n_state = RD_WAIT;elsen_state = WR_WAIT;endRD_WAIT : beginif (iic_done == 1'b1 && rdata == 8'haa)n_state = TESTDONE;elsen_state = RD_WAIT;endTESTDONE : n_state = TESTDONE;default : n_state = WR;endcaseendalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)start_flag <= 1'b0;elseif ((c_state == WR) || (c_state == WR_WAIT && iic_done == 1'b1))start_flag <= 1'b1;elsestart_flag <= 1'b0;endalways @ (posedge clk, negedge rst_n) beginif (rst_n == 1'b0)wren <= 1'b0;elseif (c_state == WR || c_state == WR_WAIT)wren <= 1'b1;elsewren <= 1'b0;endendmodule
test模塊負責將test_ctrl和iic_drive模塊進行例化連接。將test模塊設置為頂層。
test模塊設計代碼如下:
module test (input wire clk,input wire rst_n,output wire iic_scl,inout wire iic_sda);wire start_flag;wire wren;wire iic_done;wire [7:0] rdata;test_ctrl test_ctrl_inst(.clk (clk),.rst_n (rst_n),.start_flag (start_flag),.wren (wren),.iic_done (iic_done),.rdata (rdata));iic_drive iic_drive_inst(.clk (clk),.rst_n (rst_n),.start_flag (start_flag),.slave_addr (7'b1010_000),.wren (wren),.addr_sel (1'b1),.addr (16'h5555),.wdata (8'haa),.iic_scl (iic_scl),.iic_sda (iic_sda),.iic_done (iic_done),.rdata (rdata));endmodule
板級測試采用邏輯分析儀測試。
采樣時鐘選擇外部的50MHz,采樣深度選擇32K。

觸發位置選擇post。

將iic_sda、iic_scl、test_ctrl模塊中的RD_WAIT和TESTDONE兩個狀態,共計四個信號進行觀測。

將RD_WAIT設置為上升沿觸發。進入此狀態時,剛剛寫入完成。

點擊觸發后,按一下復位鍵。

從波形圖中,可以分析出,在地址16’h5555中寫入了8’haa。
將RD_WAIT設置為donot care,將TESTDONE設置為上升沿,此時讀出剛剛完成。

觸發后,按一下復位按鍵。

可以看出在地址16’h5555的位置,讀出aa。
注意發送時,是在SCL的低電平的正中間;接收是在SCL的高電平的正中間;觀看波形時,要和標準的時序圖進行對比。
證明設計正確。

- End -

往期精選




FPGA技術江湖廣發江湖帖
無廣告純凈模式,給技術交流一片凈土,從初學小白到行業精英業界大佬等,從軍工領域到民用企業等,從通信、圖像處理到人工智能等各個方向應有盡有,QQ微信雙選,FPGA技術江湖打造最純凈最專業的技術交流學習平臺。
FPGA技術江湖微信交流群

加群主微信,備注姓名+學校/公司+專業/崗位進群
FPGA技術江湖QQ交流群

備注姓名+學校/公司+專業/崗位進群
