iic协议--Verilog及仿真
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
iic协议--Verilog及仿真
1、协议原理:
IIC(Inter-Integrated Circuit),i2c总线由数据线sda和时钟线scl这两条构成的串⾏总线,主机和从机可以在i2c总线上发送和接收数据。
scl时钟线作为控制,sda则包含有ack、nack、设备地址、字节地址、8bits数据。
起始信号(scl为⾼电平时,sda变成低电平)与结束信号(scl为⾼电平时,sda变成⾼电平)的状态:
IIC单字节写时序有两种:1字节地址段器件单字节写时序、2字节地址段器件单字节写时序。
IIC单字节读时序有两种:1字节地址段器件单节读时序、2字节地址段器件单节读时序
字节地址⾼三位xxx:这⾥使⽤的EEPROM的存储容量只有8192bits(1024bits*8)=210*23=213,所以16位的字节地址就多余了三位。
2、协议代码:
1、这⾥实现的是2字节单次读写。
2、开始和结束时,虽然scl为⾼电平,sda仍要变化;接下来传输字节,scl为低电平,sda才能变化。
这⾥采取在scl⾼电平和低电平中线产⽣标志。
3、通过状态机来实现读写。
综合代码:
module IIC_AT24C64(
input sys_clk,
input sys_rst_n,
input iic_en,
input [2:0]cs_bit,//可编程地址
input [12:0]byte_address,//字节地址
input write,
input read,
input [7:0]write_data,
output reg[7:0]read_data,
output reg scl,
inout sda,
output reg done
);
parameter
SYS_CLK=50_000_000,//系统时钟50MHz
SCL_CLK=200_000;//scl时钟200KHz
reg [7:0]scl_cnt;//时钟计数
parameter div_cnt=SYS_CLK/SCL_CLK;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
scl_cnt<=8'd0;
else if(scl_cnt == div_cnt-1'b1)
scl_cnt<=8'd0;
else
scl_cnt<=scl_cnt+1'b1;
end
//⽣成scl时钟线
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
scl<=1'b1;
else if(scl_cnt == (div_cnt>>1)-1'b1)
scl<=1'b0;
else if(scl_cnt == div_cnt-1'b1)
scl<=1'b1;
else
scl<=scl;
end
//scl电平中线
reg scl_high_middle;//scl⾼电平中线
reg scl_low_middle;//scl低电平中线
always @(posedge sys_clk or negedge sys_rst_n)begin
scl_high_middle<=1'b0;
scl_low_middle<=1'b0;
end
else if(scl_cnt == (div_cnt>>2))
scl_high_middle<=1'b1;
else if(scl_cnt == (div_cnt>>1)+(div_cnt>>2))
scl_low_middle<=1'b1;
else begin
scl_high_middle<=1'b0;
scl_low_middle<=1'b0;
end
end
reg [15:0]state;
parameter
idle=16'd1,//空闲状态
w_or_r_start=16'd2,//设备地址
device_ADDR=16'd3,//发送
ACK1=16'd4,
byte_ADDR_high=16'd5,//字节地址⾼8位
ACK2=16'd6,
byte_ADDR_low=16'd7,//字节地址低8位
ACK3=16'd8,
w_data=16'd9,//写数据
ACK4=16'd10,
r_start=16'd11,//读开始
device_ADDR_r=16'd12,//设备地址读
ACK5=16'd13,
r_data=16'd14,//读数据
NACK=16'd15,//⾮应答位
stop=16'd16;
reg sda_en;//sda数据线使能
reg sda_reg;//sda数据暂存位
reg [7:0]sda_data_out;//sda数据发给从机暂存
reg [7:0]sda_data_in;//sda数据取之从机暂存
reg [3:0]bit_cnt;//每⼀bit
assign sda=sda_en?sda_reg:1'bz;
//读写标志位
reg w_flag;
reg r_flag;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
state<=idle;
w_flag<=1'b0;
r_flag<=1'b0;
sda_reg<=1'b1;
done<=1'b0;
sda_en<=1'b0;
end
else begin
case(state)
idle:begin
sda_reg<=1'b1;
w_flag<=1'b0;
r_flag<=1'b0;
sda_en<=1'b0;
sda_reg<=1'b1;
done<=1'b0;
if(iic_en && write)begin
w_flag<=1'b1;
sda_en<=1'b1;
sda_reg<=1'b1;
state<=w_or_r_start;
end
else if(iic_en && read)begin
r_flag<=1'b1;
sda_en<=1'b1;
sda_reg<=1'b1;
state<=w_or_r_start;
end
else
state<=idle;
end
w_or_r_start:begin
if(scl_high_middle)begin
sda_reg<=1'b0;
sda_data_out<={4'b1010,cs_bit,1'b0};//在这⾥装好设备地址bit_cnt<=4'd8;
state<=device_ADDR;
end
else begin
sda_reg<=1'b1;
state<=w_or_r_start;
end
end
device_ADDR:begin
if(scl_low_middle)begin
sda_reg<=sda_data_out[7];
sda_data_out<={sda_data_out[6:0],1'b0};//在这⾥发出设备地址。
其他的也是在上⼀状态装好值,下⼀个状态发出if(bit_cnt==0)begin
state<=ACK1;
sda_en<=1'b0;
end
else
state<=device_ADDR;
end
else
state<=device_ADDR;
end
ACK1:begin
if(scl_high_middle)begin
if(sda==1'b0)begin
state<=byte_ADDR_high;
sda_data_out<={3'bxxx,byte_address[12:8]};
bit_cnt<=4'd8;
end
else
state<=idle;
end
else
state<=ACK1;
end
byte_ADDR_high:begin
if(scl_low_middle)begin
sda_en<=1'b1;
bit_cnt<=bit_cnt+1'b1;
sda_reg<=sda_data_out[7];
sda_data_out<={sda_data_out[6:0],1'b0};
if(bit_cnt==0)begin
state<=ACK2;
sda_en<=1'b0;
end
else
state<=byte_ADDR_high;
end
else
state<=byte_ADDR_high;
end
ACK2:begin
if(scl_high_middle)begin
if(sda==1'b0)begin
state<=byte_ADDR_low;
sda_data_out<=byte_address[7:0];
bit_cnt<=4'd8;
end
else
state<=idle;
end
else
state<=ACK2;
end
byte_ADDR_low:begin
if(scl_low_middle)begin
sda_en<=1'b1;
bit_cnt<=bit_cnt-1'b1;
sda_reg<=sda_data_out[7];
sda_data_out<={sda_data_out[6:0],1'b0};
if(bit_cnt==0)begin
state<=ACK3;
sda_en<=1'b0;
end
else
state<=byte_ADDR_low;
end
else
state<=byte_ADDR_low;
end
ACK3:begin
if(scl_high_middle)begin
if(sda==1'b0)begin
if(w_flag)begin
sda_data_out<=write_data;
bit_cnt<=4'd8;
state<=w_data;
end
else if(r_flag)begin
sda_reg<=1'b1;
state<=r_start;
end
end
else
state<=ACK3;
end
end
w_data:begin
if(scl_low_middle)begin
sda_en<=1'b1;
bit_cnt<=bit_cnt-1'b1;
sda_reg<=sda_data_out[7];
sda_data_out<={sda_data_out[6:0],1'b0};
if(bit_cnt==0)begin
state<=ACK4;
sda_en<=1'b0;
end
else
state<=w_data;
end
else
state<=w_data;
end
ACK4:begin
if(scl_high_middle)begin
if(sda==1'b0)
state<=stop;
else
state<=idle;
end
else
state<=ACK4;
end
r_start:begin
if(scl_low_middle)begin
sda_en<=1'b1;
end
else if(scl_high_middle)begin
sda_reg<=1'b0;
state<=device_ADDR_r;
sda_data_out<={4'b1010,cs_bit,1'b1};
bit_cnt<=4'd8;
end
else begin
sda_reg<=1'b1;
state<=r_start;
end
end
device_ADDR_r:begin
if(scl_low_middle)begin
bit_cnt<=bit_cnt-1'b1;
sda_reg<=sda_data_out[7];
sda_data_out<={sda_data_out[6:0],1'b0};
if(bit_cnt==0)begin
state<=ACK5;
sda_en<=1'b0;
end
else
state<=device_ADDR_r;
end
else
state<=device_ADDR_r;
end
ACK5:begin
if(scl_high_middle)begin
if(sda==1'b0)begin
state<=r_data;
sda_en<=1'b0;
bit_cnt<=4'd8;
end
else
state<=idle;
end
else
state<=ACK5;
end
r_data:begin
if(scl_high_middle)begin
sda_data_in<={sda_data_in[6:0],sda};
bit_cnt<=bit_cnt-1'b1;
state<=r_data;
end
else if(scl_low_middle && bit_cnt==0)
state<=NACK;
else
state<=r_data;
end
NACK:begin
read_data<=sda_data_in;
if(scl_high_middle)begin
state<=stop;
sda_reg<=1'b0; //为什么这⾥要为0?因为你发现其他的应答位都是判断if(sda==1'b0),⽽这⾥由于最后⼀个是主机发出的应答,⽽不是像之前那些是从机发出的。
end//也可以看出主机应答的是直接让output sda_reg为0,⽽前⾯的应答是等待从机input sda的值来判断应答位
else
state<=NACK;
end
stop:begin
if(scl_low_middle)begin
sda_en<=1'b1;
sda_reg<=1'b1;
state<=idle;
done<=1'b1;
end
else
state<=stop;
end
default:begin
state<=idle;
sda_en<=1'b0;
sda_reg<=1'b1;
w_flag<=1'b0;
r_flag<=1'b0;
done<=1'b0;
end
endcase
end
end
endmodule
仿真代码:直接⽤了别⼈的代码。
仿真部分结果:设备地址:1010 001 0 字节地址⾼8位:xxx 00000 字节地址低8位:1100 1000 数据:1100 1000
主机是fpga,从机是EEPROM。
仿真⼀个EEPROM存储器:
`timescale 1ns/1ns
`define timeslice 1250
module EEPROM_AT24C64(
scl,
sda
);
input scl; //串⾏时钟线
inout sda; //串⾏数据线
reg out_flag; //SDA数据输出的控制信号
reg[7:0] memory[8191:0]; //数组模拟存储器
reg[12:0]address; //地址总线
reg[7:0]memory_buf; //数据输⼊输出寄存器
reg[7:0]sda_buf; //SDA数据输出寄存器
reg[7:0]shift; //SDA数据输⼊寄存器
reg[7:0]addr_byte_h; //EEPROM存储单元地址⾼字节寄存器
reg[7:0]addr_byte_l; //EEPROM存储单元地址低字节寄存器
reg[7:0]ctrl_byte; //控制字寄存器
reg[1:0]State; //状态寄存器
integer i;
//---------------------------
parameter
r7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7
r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6
r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5
r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4
r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3
r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2
r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1
r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0
//---------------------------
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;
//------------寄存器和存储器初始化---------------
initial
begin
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2'b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i<=8191;i=i+1)
memory[i] = 0;
end
//启动信号
always@(negedge sda)
begin
if(scl == 1)
begin
State = State + 1;
if(State == 2'b11)
disable write_to_eeprom;
end
end
//主状态机
always@(posedge sda)
begin
if(scl == 1) //停⽌操作
stop_W_R;
else
begin
casex(State)
2'b01:begin
read_in;
if(ctrl_byte == w7 || ctrl_byte == w6
|| ctrl_byte == w5 || ctrl_byte == w4 || ctrl_byte == w3 || ctrl_byte == w2 || ctrl_byte == w1 || ctrl_byte == w0)
begin
State = 2'b10;
write_to_eeprom; //写操作
end
else
State = 2'b00;
end
2'b11:
read_from_eeprom;
default:
State = 2'b00;
endcase
end
end//主状态机结束
//操作停⽌
task stop_W_R;
begin
State = 2'b00;
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask
//读进控制字和存储单元地址
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte_h);
shift_in(addr_byte_l);
end
endtask
//EEPROM的写操作
task write_to_eeprom;
begin
shift_in(memory_buf);
address = {addr_byte_h[4:0], addr_byte_l};
memory[address] = memory_buf;
State = 2'b00;
end
endtask
//EEPROM的读操作
task read_from_eeprom;
begin
shift_in(ctrl_byte);
if(ctrl_byte == r7 || ctrl_byte == w6
|| ctrl_byte == r5 || ctrl_byte == r4
|| ctrl_byte == r3 || ctrl_byte == r2
|| ctrl_byte == r1 || ctrl_byte == r0)
begin
address = {addr_byte_h[4:0], addr_byte_l};
sda_buf = memory[address];
shift_out;
State = 2'b00;
end
end
endtask
//SDA数据线上的数据存⼊寄存器,数据在SCL的⾼电平有效
task shift_in;
output[7:0]shift;
begin
@(posedge scl) shift[7] = sda;
@(posedge scl) shift[6] = sda;
@(posedge scl) shift[5] = sda;
@(posedge scl) shift[4] = sda;
@(posedge scl) shift[3] = sda;
@(posedge scl) shift[2] = sda;
@(posedge scl) shift[1] = sda;
@(posedge scl) shift[0] = sda;
@(negedge scl)
begin
#`timeslice;
out_flag = 1; //应答信号输出
sda_buf = 0;
end
@(negedge scl)
begin
#`timeslice;
out_flag = 0;
end
end
endtask
//EEPROM存储器中的数据通过SDA数据线输出,数据在SCL低电平时变化task shift_out;
begin
out_flag = 1;
for(i=6; i>=0; i=i-1)
begin
@(negedge scl);
#`timeslice;
sda_buf = sda_buf << 1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1; //⾮应答信号输出
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule
仿真iic:
`timescale 1ns/1ns
`define clk_period 20
module iic_tb;
reg clk50M;
reg reset;
reg iic_en;
reg [12:0]address;
reg write;
reg [7:0]write_data;
reg read;
wire [7:0]read_data;
wire scl;
wire sda;
wire done;
integer i;
IIC_AT24C64 u_IIC_AT24C64(
.sys_clk(clk50M),
.sys_rst_n(reset),
.iic_en(iic_en),
.cs_bit(3'b001),
.byte_address(address),
.write(write),
.write_data(write_data),
.read(read),
.read_data(read_data),
.scl(scl),
.sda(sda),
.done(done)
);
EEPROM_AT24C64 EEPROM(
.scl(scl),
.sda(sda)
);
initial clk50M = 1'b1;
always #(`clk_period/2)clk50M = ~clk50M; initial
begin
reset = 1'b0;
iic_en = 1'b0;
address = 13'h0;
write = 1'b0;
write_data = 1'b0;
read = 1'b0;
#(`clk_period*200 + 1)
reset = 1'b1;
#200;
write = 1'b1;
address = 200;
write_data = 200;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
for(i=199;i>0;i=i-1)
begin
@(posedge done);
#2000;
address = i;
write_data = i;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
end
@(posedge done);
#2000;
write = 1'b0;
#5000;
//200???
read = 1'b1;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
for(i=200;i>0;i=i-1)
begin
@(posedge done);
#2000;
address = i;
iic_en = 1'b1;
#(`clk_period)
iic_en = 1'b0;
end
@(posedge done);
#20000;
read = 1'b0;
#5000;
$stop;
end
endmodule。