作者:郝旭帥??校對:陸輝
大俠好,歡迎來到FPGA技術(shù)江湖,江湖偌大,相見即是緣分。大俠可以關(guān)注FPGA技術(shù)江湖,在“闖蕩江湖”、"行俠仗義"欄里獲取其他感興趣的資源,或者一起煮酒言歡。
本系列將帶來FPGA的系統(tǒng)性學(xué)習(xí),從最基本的數(shù)字電路基礎(chǔ)開始,最詳細操作步驟,最直白的言語描述,手把手的“傻瓜式”講解,讓電子、信息、通信類專業(yè)學(xué)生、初入職場小白及打算進階提升的職業(yè)開發(fā)者都可以有系統(tǒng)性學(xué)習(xí)的機會。
系統(tǒng)性的掌握技術(shù)開發(fā)以及相關(guān)要求,對個人就業(yè)以及職業(yè)發(fā)展都有著潛在的幫助,希望對大家有所幫助。后續(xù)會陸續(xù)更新 Xilinx 的 Vivado、ISE 及相關(guān)操作軟件的開發(fā)的相關(guān)內(nèi)容,學(xué)習(xí)FPGA設(shè)計方法及設(shè)計思想的同時,實操結(jié)合各類操作軟件,會讓你在技術(shù)學(xué)習(xí)道路上無比的順暢,告別技術(shù)學(xué)習(xí)小BUG卡破腦殼,告別目前忽悠性的培訓(xùn)誘導(dǎo),真正的去學(xué)習(xí)去實戰(zhàn)應(yīng)用,這種快樂試試你就會懂的。話不多說,上貨。
本篇實現(xiàn)基于叁芯智能科技的SANXIN -B01 FPGA開發(fā)板,以下為配套的教程,如有入手開發(fā)板,可以登錄官方淘寶店購買,還有配套的學(xué)習(xí)視頻。
SANXIN-B01開發(fā)板verilog教程V3電子版
FPGA就業(yè)班,2023.07.09開班,系統(tǒng)性學(xué)習(xí)FPGA,高薪就業(yè),線上線下同步!
I2C 即 Inter-Integrated Circuit(集成電路總線),這種總線類型是由飛利浦半導(dǎo)體公司在八十年代初設(shè)計出來的一種簡單、雙向、二線制、同步串行總線,主要是用來連接整體電路(ICS) ,I2C是一種多向控制總線,也就是說多個芯片可以連接到同一總線結(jié)構(gòu)下,同時每個芯片都可以作為實時數(shù)據(jù)傳輸的控制源。這種方式簡化了信號傳輸總線接口。
I2C?總線是一種串行數(shù)據(jù)總線,只有二根信號線,一根是雙向的數(shù)據(jù)線?SDA,另一根是時鐘線?SCL,兩條線可以掛多個設(shè)備。?I2C?設(shè)備(絕大多數(shù))里有個固化的地址,只有在兩條線上傳輸?shù)闹档扔?I2C?設(shè)備的固化地址時,其才會作出響應(yīng)。通常我們?yōu)榱朔奖惆?I2C?設(shè)備分為主設(shè)備和從設(shè)備,基本上誰控制時鐘線(即控制SCL的電平高低變換)誰就是主設(shè)備。
A段為空閑段,在不進行數(shù)據(jù)傳輸時,SCL 和 SDA 保持為高電平。
B段為起始段(START CONDITION),在進行任何數(shù)據(jù)傳輸前,都應(yīng)該先有一個起始段。當(dāng) SCL 為高電平期間,SDA有一個從高到低的變化視為起始段。
C段為結(jié)束段(STOP CONDITION),在數(shù)據(jù)傳輸結(jié)束后,都應(yīng)該有一個結(jié)束段。當(dāng)SCL為高電平期間,SDA有一個從低到高的變化視為結(jié)束段。
D段分為兩段數(shù)據(jù)有效段(ADDRESS OR ACKNOWLEDGE)和數(shù)據(jù)修改段(DATA ALLOWED TO CHANGE)。在傳輸數(shù)據(jù)時,在 SCL 為高電平期間,SDA 不能夠發(fā)生改變,視為數(shù)據(jù)有效段;在傳輸數(shù)據(jù)時,在 SCL 為低電平期間,SDA 可以發(fā)生改變(修改為下一個要發(fā)送的bit),視為數(shù)據(jù)修改段。
IIC的傳輸總結(jié)為:空閑時,SCL 和 SDA 都是高電平;發(fā)送信息前,首先發(fā)送起始段(SCL為高時,SDA有一個從高到低的變化);在發(fā)送信息時,每一個 SCL 脈沖,發(fā)送或者接收1bit,該bit在SCL為低電平時發(fā)生改變,在SCL為高電平時,保持穩(wěn)定;發(fā)送信息完成后,發(fā)送結(jié)束段(SCL 為高時,SDA 有一個從低到高的變化)。
SDA的數(shù)據(jù)線是雙向傳輸?shù)?。在讀取時,SDA 從從設(shè)備到主設(shè)備;在寫入時,SDA從主設(shè)備到從設(shè)備。
在 IIC 傳輸時,為了保證通信的成功。發(fā)送方每發(fā)送8 bit 數(shù)據(jù)后,要求接收方回復(fù)一個應(yīng)答位。
應(yīng)答位為低電平有效。
EEPROM(24LC64)簡介
24LC64是容量為8K*8bit(64Kbit)通信接口為IIC的EEPROM。EEPROM擁有掉電不丟失的功能,經(jīng)常用來保存一些配置信息等等。這個器件可以一次進行最多32字節(jié)的頁寫入和一次進行整片讀出的功能。
A0、A1、A2為用戶可配置的芯片選擇端。該管腳一般都連接電源或者地,在通信時,24LC64作為從機,主機在發(fā)送的命令中存在地址號,當(dāng)?shù)刂诽柵cA0、A1、A2管腳的電平相同時,該芯片視為被選中。
SDA和SCL為IIC協(xié)議中的時鐘線和數(shù)據(jù)線。
WP為寫保護端,當(dāng)連接高電平時,該芯片只能夠被讀,不能夠?qū)懭?;?dāng)連接低電平或者空置時,該芯片可以被讀寫。
24LC64的寫入方式有兩種:單字節(jié)寫入和多字節(jié)寫入。
單字節(jié)寫入時序圖:
在控制字節(jié)中,1010為EEPROM的標(biāo)號;A2、A1、A0為主機發(fā)送的地址號,接收設(shè)備接收到此信息后,會與本身的A2、A1、A0相對比,相同時,視為被選中;最后1bit,0表示寫入、1表示讀出。
在高字節(jié)地址中,高三位地址無效。24LC64的地址范圍為8K,地址位13位即可。13位地址分為高五低八,在IIC中規(guī)定每次發(fā)送8bit,所以高五的前面必須發(fā)送任意3bit。
多字節(jié)寫入時序圖:
DATA BYTE 0被寫入到指定的地址中,后續(xù)的數(shù)據(jù)寫入到指定地址的后續(xù)地址中。EEPROM中每32個字節(jié)分為一塊,寫入時不能跨塊寫入。
24LC64的讀出方式有三種:當(dāng)前地址單字節(jié)讀、任意地址單字節(jié)讀和任意地址多字節(jié)讀。
當(dāng)前地址單字節(jié)讀的時序圖如下:
當(dāng)主機接收24LC64的數(shù)據(jù)后,不發(fā)送ACK,而是發(fā)送高電平。
任意地址單字節(jié)讀取時序圖,如下:
首先寫入指定地址,然后按照當(dāng)前地址讀即可。
任意地址多字節(jié)讀,時序如下:
任意地址多字節(jié)讀就是在任意地址單字節(jié)讀的時序中,接收到從機發(fā)送的數(shù)據(jù)后,不要發(fā)送NO ACK而是發(fā)送ACK,此時從機就會繼續(xù)發(fā)送后續(xù)地址的數(shù)據(jù),直到讀取的長度符合設(shè)計要求,然后發(fā)送NO ACK以及STOP結(jié)束傳輸。
下圖為24LC64的時序參數(shù)圖。
根據(jù)時序參數(shù),決定將IIC的速率定為50KHz。發(fā)送時,數(shù)據(jù)改變在SCL的低電平的正中間;讀取時,在SCL高電平的正中間進行讀取。
IIC一般用在配置或者讀取少量的信息,對于速率來說要求相對較低。
硬件介紹
在開發(fā)板上,搭載了一個24LC64。
電路原理圖如下:
A0、A1、A2都被接地,主機再發(fā)送控制字節(jié)時,需要將此三位發(fā)送低電平。
WP管腳被接地,24LC64的整個片子都可以被讀寫。
雙向IO介紹
在IIC協(xié)議中,SDA作為數(shù)據(jù)線使用,每一個SCL的脈沖,傳遞一bit的數(shù)據(jù)。在讀取時,SDA由24LC64發(fā)送,F(xiàn)PGA接收;在寫入時,SDA由FPGA發(fā)送,24LC64接收。所以IIC協(xié)議只能實現(xiàn)半雙工通信。
在硬件電路中,一般是通過三態(tài)門來進行控制雙向IO。
此電路在FPGA的IO中也是存在的。下面來分析具體的工作原理。
三態(tài)門有三個端子,分別是輸入端(obuf),輸出端(SDA)和控制端(out_en)。工作方式為:當(dāng)out_en有效時,obuf的值就可以輸出到SDA;當(dāng)out_en無效時,obuf的值不能輸出到SDA,那么三態(tài)門就會輸出高阻態(tài)。
高阻態(tài),字節(jié)理解為阻值特別大,也可以理解為斷開。高阻態(tài)與任何電平值相連接,所連接的線上的電平值都是和對方保持一致。在此可以延伸一下:所有的端口都是輸出,包括輸入端口在內(nèi),也會對外呈現(xiàn)出一種狀態(tài),所以輸入端口對外呈現(xiàn)的狀態(tài)就是高阻態(tài)。對于雙向IO來說,輸出時,正常輸出即可;輸入時,就要呈現(xiàn)為高阻態(tài)。
設(shè)計要求
設(shè)計任意地址的單字節(jié)讀寫控制器。大多數(shù)是配置或者讀取少量的信息,對于多字節(jié)的讀寫,可以采用多次單字節(jié)的讀寫完成。
設(shè)計分析
24LC64的尋址范圍為8K,地址bit共計13bit,所以需要高5bit和低8bit。在有些兼容IIC協(xié)議接口的器件中,尋址范圍較少,8bit足夠表示,所以在設(shè)計時,設(shè)計出適合兩種尋址方式的驅(qū)動。
SCL的實現(xiàn)采用計數(shù)器控制,根據(jù)規(guī)定好的頻率,在合適的地方進行拉高或者拉低。由于SDA的變化都發(fā)生在SCL為高或者為低的中間,所以還需要產(chǎn)生對應(yīng)的脈沖。
SDA根據(jù)協(xié)議和對應(yīng)的脈沖進行發(fā)送對應(yīng)的協(xié)議段。
架構(gòu)設(shè)計和信號說明
本模塊命名為iic_drive。
暫不連接的端口,在后續(xù)測試時,會有其他模塊來控制或者讀取。
iic_drive設(shè)計實現(xiàn)
iic_scl采用計數(shù)器的方式實現(xiàn)。外部的時鐘為50MHz,iic_scl為100KHz,所以計數(shù)器需要記錄500個時鐘周期。
計數(shù)器由cnt_en控制。cnt_en有效時,cnt從0到500不斷循環(huán);當(dāng)cnt_en無效時,cnt保持等于0。
iic_scl在cnt等于0至249時,保持高電平;在250至499時,保持低電平。cnt等于125時,正好是iic_scl高電平的中間,用脈沖flag_high表示;cnt等于375時,正好是iic_scl低電平的中間,用脈沖flag_low表示。
其他信號采用狀態(tài)機作為輸出。out_en為三態(tài)門控制信號,sda_obuf為三態(tài)門輸出端,drive_cnt為發(fā)送或者接收的計數(shù)器,temp為發(fā)送或者接收的中間寄存器。
狀態(tài)轉(zhuǎn)移圖如下:
設(shè)計代碼為:
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) begin
if (rst_n == 1'b0)
cnt <= 32'd0;
else
if (cnt < T_SCL - 1'b1 && cnt_en == 1'b1)
cnt <= cnt + 1'b1;
else
cnt <= 32'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
iic_scl <= 1'b1;
else
if (cnt < T_SCL/2)
iic_scl <= 1'b1;
else
iic_scl <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
flag_high <= 1'b0;
else
if (cnt == T_SCL/4 - 1'b1)
flag_high <= 1'b1;
else
flag_high <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
flag_low <= 1'b0;
else
if (cnt == (T_SCL * 3)/4 - 1'b1)
flag_low <= 1'b1;
else
flag_low <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
c_state <= IDLE;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
IDLE : begin
if (start_flag == 1'b1)
n_state = START;
else
n_state = IDLE;
end
START : begin
if (flag_high == 1'b1)
n_state = CTRL;
else
n_state = START;
end
CTRL : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
n_state = CTRL_ACK;
else
n_state = CTRL;
end
CTRL_ACK : begin
if (flag_high == 1'b1)
if (iic_sda == 1'b0)
if (addr_sel == 1'b1)
n_state = HADDR;
else
n_state = LADDR;
else
n_state = START;
else
n_state = CTRL_ACK;
end
HADDR : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
n_state = HADDR_ACK;
else
n_state = HADDR;
end
HADDR_ACK : begin
if (flag_high == 1'b1)
if (iic_sda == 1'b0)
n_state = LADDR;
else
n_state = START;
else
n_state = HADDR_ACK;
end
LADDR : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
n_state = LADDR_ACK;
else
n_state = LADDR;
end
LADDR_ACK : begin
if (flag_high == 1'b1)
if (iic_sda == 1'b0)
if (wren == 1'b1)
n_state = WR;
else
n_state = RD_START;
else
n_state = START;
else
n_state = LADDR_ACK;
end
WR : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
n_state = WR_ACK;
else
n_state = WR;
end
WR_ACK : begin
if (flag_high == 1'b1)
if (iic_sda == 1'b0)
n_state = STOP;
else
n_state = START;
else
n_state = WR_ACK;
end
STOP : begin
if (flag_high == 1'b1)
n_state = IDLE;
else
n_state = STOP;
end
RD_START : begin
if (flag_high == 1'b1)
n_state = RD_CTRL;
else
n_state = RD_START;
end
RD_CTRL : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
n_state = RD_CTRL_ACK;
else
n_state = RD_CTRL;
end
RD_CTRL_ACK : begin
if (flag_high == 1'b1)
if (iic_sda == 1'b0)
n_state = RD;
else
n_state = START;
else
n_state = RD_CTRL_ACK;
end
RD : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
n_state = NO_ACK;
else
n_state = RD;
end
NO_ACK : begin
if (flag_high == 1'b1)
n_state = STOP;
else
n_state = NO_ACK;
end
default : n_state = IDLE;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt_en <= 1'b0;
else
case (c_state)
IDLE : cnt_en <= 1'b0;
CTRL_ACK,
HADDR_ACK,
LADDR_ACK,
WR_ACK,
RD_CTRL_ACK : begin
if (flag_high == 1'b1)
if (iic_sda == 1'b0)
cnt_en <= 1'b1;
else
cnt_en <= 1'b0;
else
cnt_en <= cnt_en;
end
default : cnt_en <= 1'b1;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
out_en <= 1'b0;
else
case (c_state)
IDLE : out_en <= 1'b0;
START : out_en <= 1'b1;
CTRL : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
out_en <= 1'b0;
else
out_en <= 1'b1;
end
CTRL_ACK : out_en <= 1'b0;
HADDR : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
out_en <= 1'b0;
else
if (flag_low == 1'b1)
out_en <= 1'b1;
else
out_en <= out_en;
end
HADDR_ACK : out_en <= 1'b0;
LADDR : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
out_en <= 1'b0;
else
if (flag_low == 1'b1)
out_en <= 1'b1;
else
out_en <= out_en;
end
LADDR_ACK : out_en <= 1'b0;
WR : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
out_en <= 1'b0;
else
if (flag_low == 1'b1)
out_en <= 1'b1;
else
out_en <= out_en;
end
WR_ACK : out_en <= 1'b0;
STOP : begin
if (flag_low == 1'b1)
out_en <= 1'b1;
else
out_en <= out_en;
end
RD_START : begin
if (flag_low == 1'b1)
out_en <= 1'b1;
else
out_en <= out_en;
end
RD_CTRL : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
out_en <= 1'b0;
else
out_en <= 1'b1;
end
RD_CTRL_ACK : out_en <= 1'b0;
RD : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
out_en <= 1'b1;
else
out_en <= 1'b0;
end
NO_ACK : out_en <= 1'b1;
default : out_en <= 1'b0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
sda_obuf <= 1'b1;
else
case (c_state)
IDLE : sda_obuf <= 1'b1;
START : begin
if (flag_high == 1'b1)
sda_obuf <= 1'b0;
else
sda_obuf <= 1'b1;
end
CTRL : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
sda_obuf <= temp[7];
else
sda_obuf <= sda_obuf;
end
CTRL_ACK : sda_obuf <= 1'b0;
HADDR : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
sda_obuf <= temp[7];
else
sda_obuf <= sda_obuf;
end
HADDR_ACK : sda_obuf <= 1'b0;
LADDR : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
sda_obuf <= temp[7];
else
sda_obuf <= sda_obuf;
end
LADDR_ACK : sda_obuf <= 1'b0;
WR : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
sda_obuf <= temp[7];
else
sda_obuf <= sda_obuf;
end
WR_ACK : sda_obuf <= 1'b0;
STOP : begin
if (flag_low == 1'b1)
sda_obuf <= 1'b0;
else
if (flag_high == 1'b1)
sda_obuf <= 1'b1;
else
sda_obuf <= sda_obuf;
end
RD_START : begin
if (flag_low == 1'b1)
sda_obuf <= 1'b1;
else
if (flag_high == 1'b1)
sda_obuf <= 1'b0;
else
sda_obuf <= sda_obuf;
end
RD_CTRL : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
sda_obuf <= temp[7];
else
sda_obuf <= sda_obuf;
end
RD_CTRL_ACK : sda_obuf <= 1'b0;
RD : begin
if (flag_low == 1'b1 && drive_cnt == 4'd8)
sda_obuf <= 1'b1;
else
sda_obuf <= sda_obuf;
end
NO_ACK : sda_obuf <= sda_obuf;
default : sda_obuf <= 1'b1;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
drive_cnt <= 4'd0;
else
case (c_state)
IDLE : drive_cnt <= 4'd0;
START : drive_cnt <= 4'd0;
CTRL : begin
if (flag_low == 1'b1)
if (drive_cnt < 4'd8)
drive_cnt <= drive_cnt + 1'b1;
else
drive_cnt <= 4'd0;
else
drive_cnt <= drive_cnt;
end
CTRL_ACK : drive_cnt <= 4'd0;
HADDR : begin
if (flag_low == 1'b1)
if (drive_cnt < 4'd8)
drive_cnt <= drive_cnt + 1'b1;
else
drive_cnt <= 4'd0;
else
drive_cnt <= drive_cnt;
end
HADDR_ACK : drive_cnt <= 4'd0;
LADDR : begin
if (flag_low == 1'b1)
if (drive_cnt < 4'd8)
drive_cnt <= drive_cnt + 1'b1;
else
drive_cnt <= 4'd0;
else
drive_cnt <= drive_cnt;
end
LADDR_ACK : drive_cnt <= 4'd0;
WR : begin
if (flag_low == 1'b1)
if (drive_cnt < 4'd8)
drive_cnt <= drive_cnt + 1'b1;
else
drive_cnt <= 4'd0;
else
drive_cnt <= drive_cnt;
end
WR_ACK : drive_cnt <= 4'd0;
STOP : drive_cnt <= 4'd0;
RD_START : drive_cnt <= 4'd0;
RD_CTRL : begin
if (flag_low == 1'b1)
if (drive_cnt < 4'd8)
drive_cnt <= drive_cnt + 1'b1;
else
drive_cnt <= 4'd0;
else
drive_cnt <= drive_cnt;
end
RD_CTRL_ACK : drive_cnt <= 4'd0;
RD : begin
if (flag_high == 1'b1 && drive_cnt < 4'd8)
drive_cnt <= drive_cnt + 1'b1;
else
if (flag_low == 1'b1 && drive_cnt == 4'd8)
drive_cnt <= 4'd0;
else
drive_cnt <= drive_cnt;
end
NO_ACK : drive_cnt <= 4'd0;
default : drive_cnt <= 4'd0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
temp <= 8'd0;
else
case (c_state)
IDLE : temp <= 8'd0;
START : begin
if (flag_high == 1'b1)
temp <= {slave_addr, 1'b0};
else
temp <= temp;
end
CTRL : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
temp <= temp << 1'b1;
else
temp <= temp;
end
CTRL_ACK : begin
if (flag_high == 1'b1 && iic_sda == 1'b0)
if (addr_sel == 1'b1)
temp <= addr[15:8];
else
temp <= addr[7:0];
else
temp <= temp;
end
HADDR : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
temp <= temp << 1'b1;
else
temp <= temp;
end
HADDR_ACK : begin
if (flag_high == 1'b1 && iic_sda == 1'b0)
temp <= addr[7:0];
else
temp <= temp;
end
LADDR : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
temp <= temp << 1'b1;
else
temp <= temp;
end
LADDR_ACK : begin
if (flag_high == 1'b1 && iic_sda == 1'b0)
if (wren == 1'b1)
temp <= wdata;
else
temp <= {slave_addr, 1'b1};
else
temp <= temp;
end
WR : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
temp <= temp << 1'b1;
else
temp <= temp;
end
WR_ACK : temp <= 8'd0;
STOP : temp <= 8'd0;
RD_START : temp <= temp;
RD_CTRL : begin
if (flag_low == 1'b1 && drive_cnt < 4'd8)
temp <= temp << 1'b1;
else
temp <= temp;
end
RD_CTRL_ACK : temp <= 8'd0;
RD : begin
if (flag_high == 1'b1 && drive_cnt < 4'd8)
temp <= {temp[6:0], iic_sda};
else
temp <= temp;
end
NO_ACK : temp <= 8'd0;
default : temp <= 8'd0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
iic_done <= 1'b0;
else
if (c_state == STOP && flag_high == 1'b1)
iic_done <= 1'b1;
else
iic_done <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
rdata <= 8'd0;
else
if (c_state == RD && flag_low == 1'b1 && drive_cnt == 4'd8)
rdata <= temp;
else
rdata <= rdata;
end
endmodule
RTL仿真設(shè)計
在仿真時,需要外部提供ACK。如果直接仿真的話,由于外部沒有提供ACK,導(dǎo)致一直都會重復(fù)發(fā)送一小段。在仿真中,我們自己給出ACK的難度也比較大。
24LC64的廠商幫我們解決了這個問題,他們在造出這個芯片的同時,還設(shè)計出了可以幫助我們仿真調(diào)試的verliog模型。此模型放在msim->24LC64_sim_module中,文件名字為24LC64.v。此模型的sda也是雙向IO,所以在硬件設(shè)計時,會在此IO上加上拉電阻,在仿真時,我們也要模擬上拉電阻。
仿真設(shè)計如下:
`timescale 1ns/1ps
module 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 begin
rst_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;
# 201
rst_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;
end
endmodule
pullup(iic_sda):將iic_sda上加上拉電阻。
仿真時,在地址16’h5555(雖然高三位沒有用),寫入了8’ha;寫入完成后,又將此數(shù)據(jù)讀出。
當(dāng)對24LC64進行寫入之后,它會進入到一個內(nèi)部寫循環(huán)(和flash類似),在此期間,不接受任何指令。所以在仿真時,需要等待寫循環(huán)完成,時間為5ms(此參數(shù)在仿真模型中,可以進行修改)。
寫入的仿真圖如下:
讀出的仿真圖如下:
板級測試
上述的設(shè)計在使用時會有上游控制器進行控制,所以板級測試還需要設(shè)計控制器。在控制器中只需要某一個地址寫入數(shù)據(jù),然后讀出即可。測試時,可以多次更換地址和數(shù)據(jù),保證設(shè)計的正確性。
在測試時,將slave_addr、addr_sel、addr、wdata設(shè)置為定值。slave_addr為7’b1010_000,addr_sel為1’b1,addr為16’h5555,wdata為8’haa。
將上有控制器命名為test_ctrl,頂層命名為test。
test_ctrl模塊采用狀態(tài)機設(shè)計實現(xiàn)。
test_ctrl的設(shè)計代碼如下:
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) begin
if (rst_n == 1'b0)
c_state <= WR;
else
c_state <= n_state;
end
always @ * begin
case (c_state)
WR : n_state = WR_WAIT;
WR_WAIT : begin
if (iic_done == 1'b1)
n_state = RD_WAIT;
else
n_state = WR_WAIT;
end
RD_WAIT : begin
if (iic_done == 1'b1 && rdata == 8'haa)
n_state = TESTDONE;
else
n_state = RD_WAIT;
end
TESTDONE : n_state = TESTDONE;
default : n_state = WR;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
start_flag <= 1'b0;
else
if ((c_state == WR) || (c_state == WR_WAIT && iic_done == 1'b1))
start_flag <= 1'b1;
else
start_flag <= 1'b0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
wren <= 1'b0;
else
if (c_state == WR || c_state == WR_WAIT)
wren <= 1'b1;
else
wren <= 1'b0;
end
endmodule
test模塊負責(zé)將test_ctrl和iic_drive模塊進行例化連接。將test模塊設(shè)置為頂層。
test模塊設(shè)計代碼如下:
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。
觸發(fā)位置選擇post。
將iic_sda、iic_scl、test_ctrl模塊中的RD_WAIT和TESTDONE兩個狀態(tài),共計四個信號進行觀測。
將RD_WAIT設(shè)置為上升沿觸發(fā)。進入此狀態(tài)時,剛剛寫入完成。
點擊觸發(fā)后,按一下復(fù)位鍵。
從波形圖中,可以分析出,在地址16’h5555中寫入了8’haa。
將RD_WAIT設(shè)置為donot care,將TESTDONE設(shè)置為上升沿,此時讀出剛剛完成。
觸發(fā)后,按一下復(fù)位按鍵。
可以看出在地址16’h5555的位置,讀出aa。
注意發(fā)送時,是在SCL的低電平的正中間;接收是在SCL的高電平的正中間;觀看波形時,要和標(biāo)準的時序圖進行對比。
證明設(shè)計正確。