本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。后续会陆续更新 Xilinx 的 Vivado、ISE 及相关操作软件的开发的相关内容,学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性的培训诱导,真正的去学习去实战应用,这种快乐试试你就会懂的。话不多说,上货。
IIC协议驱动设计
作者:郝旭帅 校对:陆辉
本篇实现基于叁芯智能科技的SANXIN -B01 FPGA开发板,以下为配套的教程,如有入手开发板,可以登录官方淘宝店购买,还有配套的学习视频。
SANXIN-B01开发板verilog教程V3电子版
FPGA就业班,2023.07.09开班,系统性学习FPGA,高薪就业,线上线下同步!
I²C 即 Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司在八十年代初设计出来的一种简单、双向、二线制、同步串行总线,主要是用来连接整体电路(ICS) ,I²C是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。这种方式简化了信号传输总线接口。
I²C 总线是一种串行数据总线,只有二根信号线,一根是双向的数据线 SDA,另一根是时钟线 SCL,两条线可以挂多个设备。 I²C 设备(绝大多数)里有个固化的地址,只有在两条线上传输的值等于 I²C 设备的固化地址时,其才会作出响应。通常我们为了方便把 I²C 设备分为主设备和从设备,基本上谁控制时钟线(即控制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) 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
在仿真时,需要外部提供ACK。如果直接仿真的话,由于外部没有提供ACK,导致一直都会重复发送一小段。在仿真中,我们自己给出ACK的难度也比较大。
24LC64的厂商帮我们解决了这个问题,他们在造出这个芯片的同时,还设计出了可以帮助我们仿真调试的verliog模型。此模型放在msim->24LC64_sim_module中,文件名字为24LC64.v。此模型的sda也是双向IO,所以在硬件设计时,会在此IO上加上拉电阻,在仿真时,我们也要模拟上拉电阻。
仿真设计如下:
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),
(rst_n),
(start_flag),
(slave_addr),
(wren),
(addr_sel),
(addr),
(wdata),
(iic_scl),
(iic_sda),
(iic_done),
(rdata)
);
M24LC64 M24LC64_inst(
(1'b0),
(1'b0),
(1'b0),
(1'b0),
(iic_sda),
(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;写入完成后,又将此数据读出。
当对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) 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模块负责将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),
(rst_n),
(start_flag),
(wren),
(iic_done),
(rdata)
);
iic_drive iic_drive_inst(
(clk),
(rst_n),
(start_flag),
(7'b1010_000),
(wren),
(1'b1),
(16'h5555),
(8'haa),
(iic_scl),
(iic_sda),
(iic_done),
(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交流群
备注地区+职业+方向+名字进群