FPGA——IIC状态机实现及仿真
一、设计思路
二、IIC代码
module iic_module(
rst_n ,
clk ,
waddr_num , //选择两字节地址或单字节地址
device_addr ,
word_addr ,
wr ,
wr_data ,
wr_data_vld ,
rd ,
rd_data ,
rd_data_vld ,
done ,
scl ,
sda
);
//输入输出参数
parameter SYS_CLOCK = 50_000_000;
parameter SCL_CLOCK = 380_000; //快速模式
parameter WADDR_W = 2;
parameter DADDR_W = 3;
parameter WR_ADDR_W = 16;
parameter WDATA_W = 8;
parameter RDATA_W = 8;
//计数器参数
parameter SCL_N = SYS_CLOCK/SCL_CLOCK;
parameter SCL_W = 8;
parameter HABIT_W = 5;
parameter HABIT_N = 18;
parameter BYTE_W = 6;
parameter BYTEN_W = 2;
//状态机参数
parameter IDLE = 9'b000000001; //空闲
parameter S1 = 9'b000000010; //写开始
parameter S2 = 9'b000000100; //写控制
parameter S3 = 9'b000001000; //读开始
parameter S4 = 9'b000010000; //读控制
parameter S5 = 9'b000100000; //读数据
parameter S6 = 9'b001000000; //写地址
parameter S7 = 9'b010000000; //写数据
parameter S8 = 9'b100000000; //结束
parameter STATE_W = 9;
//中间变量参数
parameter SDA_DATA_N = 8;
input rst_n;
input clk;
input [WR_ADDR_W-1:0] word_addr;
input wr;
input [WDATA_W-1:0] wr_data;
input rd;
input [WADDR_W-1:0] waddr_num;
input [DADDR_W-1:0] device_addr;
output wr_data_vld;
output [RDATA_W-1:0] rd_data;
output rd_data_vld;
output done;
output scl;
inout sda;
wire wr_data_vld;
reg [RDATA_W-1:0] rd_data;
wire rd_data_vld;
reg done;
reg scl;
wire sda;
//计数器变量
reg [SCL_W-1:0] cnt_scl;
wire add_cnt_scl;
wire end_cnt_scl;
reg scl_vld;
reg [HABIT_W-1:0] cnt_hlbit;
wire add_cnt_hlbit;
wire end_cnt_hlbit;
reg [BYTE_W-1:0] cnt_bytes;
wire add_cnt_bytes;
wire end_cnt_bytes;
reg [BYTEN_W-1:0] bytes_num;
//中间变量
reg scl_high;
reg scl_low;
reg [SDA_DATA_N-1:0] sda_out_data;
reg wr_flag;
reg rd_flag;
reg ack_flag;
reg noack_flag;
reg r_begin;
reg ack_error;
wire sda_in;
reg sda_en;
reg sda_out;
//状态机变量
reg [STATE_W-1:0] state_c;
reg [STATE_W-1:0] state_n;
wire idle2s1_start;
wire s12s2_start;
wire s22s6_start;
wire s22idle_start;
wire s32s4_start;
wire s42s5_start;
wire s42idle_start;
wire s52s8_start;
wire s62s7_start;
wire s62s3_start;
wire s62idle_start;
wire s72s8_start;
wire s72idle_start;
wire s82idle_start;
// assign sda = sda_en ? sda_out : 1'bz;
assign sda = (sda_en && !sda_out) ? 1'b0 : 1'bz;
assign sda_in = sda;
//SDA接上拉电阻
pullup(sda);
//SCL时钟计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_scl <= 0;
else if(add_cnt_scl)begin
if(end_cnt_scl)
cnt_scl <= 0;
else
cnt_scl <= cnt_scl + 1'b1;
end
end
assign add_cnt_scl = scl_vld;
assign end_cnt_scl = (add_cnt_scl && cnt_scl == SCL_N - 1) || ack_error;
//高低电平计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_hlbit <= 0;
end
else if(add_cnt_hlbit)begin
if(end_cnt_hlbit)
cnt_hlbit <= 0;
else
cnt_hlbit <= cnt_hlbit + 1'b1;
end
end
assign add_cnt_hlbit = (state_c == S2 || state_c == S6 || state_c == S7 || state_c == S4 || state_c == S5) && (scl_low || scl_high);
assign end_cnt_hlbit = (add_cnt_hlbit && cnt_hlbit == HABIT_N - 1) || ack_error;
//字节数计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bytes <= 0;
end
else if(add_cnt_bytes)begin
if(end_cnt_bytes)
cnt_bytes <= 0;
else
cnt_bytes <= cnt_bytes + 1'b1;
end
end
assign add_cnt_bytes = end_cnt_hlbit && state_c == S6;
assign end_cnt_bytes = add_cnt_bytes && cnt_bytes == bytes_num - 1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
bytes_num <= 1;
else if((wr || rd) && !(wr_flag ||rd_flag))
bytes_num <= waddr_num;
end
//scl 时钟高电平中部标志位
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
scl_high <= 0;
else if(cnt_scl == (SCL_N>>2)-1 && add_cnt_scl)
scl_high <= 1;
else
scl_high <= 0;
end
//scl 时钟低电平中部标志位
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
scl_low <= 0;
else if(cnt_scl == (SCL_N>>2) + (SCL_N>>1) -1 && add_cnt_scl)
scl_low <= 1;
else
scl_low <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
scl_vld <= 0;
else if(wr || rd)
scl_vld <= 1;
else if(done)
scl_vld <= 0;
else if(ack_error)
scl_vld <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
scl <= 1;
else if(cnt_scl == (SCL_N>>1)-1 && add_cnt_scl && state_c != S8 && state_c != S1)
scl <= 0;
else if(state_c == S1)begin
if(cnt_scl == (SCL_N>>1) -1 && add_cnt_scl && sda == 0)
scl <= 0;
end
else if(end_cnt_scl)
scl <= 1;
end
//状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
state_c <= IDLE;
else
state_c <= state_n;
end
always @(*)begin
case(state_c)
IDLE:begin
if(idle2s1_start)
state_n = S1;
else
state_n = state_c;
end
S1:begin
if(s12s2_start)
state_n = S2;
else
state_n = state_c;
end
S2:begin
if(s22idle_start)
state_n = IDLE;
else if(s22s6_start)
state_n = S6;
else
state_n = state_c;
end
S3:begin
if(s32s4_start)
state_n = S4;
else
state_n = state_c;
end
S4:begin
if(s42idle_start)
state_n = IDLE;
else if(s42s5_start)
state_n = S5;
else
state_n = state_c;
end
S5:begin
if(s52s8_start)
state_n = S8;
else
state_n = state_c;
end
S6:begin
if(s62idle_start)
state_n = IDLE;
else if(s62s7_start)
state_n = S7;
else if(s62s3_start)
state_n = S3;
else
state_n = state_c;
end
S7:begin
if(s72idle_start)
state_n = IDLE;
else if(s72s8_start)
state_n = S8;
else
state_n = state_c;
end
S8:begin
if(s82idle_start)
state_n = IDLE;
else
state_n = state_c;
end
default:state_n = IDLE;
endcase
end
assign idle2s1_start = state_c == IDLE && (wr || rd);
assign s12s2_start = state_c == S1 && scl_low && !sda && !scl;
assign s22s6_start = state_c == S2 && ack_flag && scl_low;
assign s22idle_start = state_c == S2 && !ack_flag && scl_low && !sda_en;
assign s32s4_start = state_c == S3 && scl_low && !sda && !scl;
assign s42s5_start = state_c == S4 && ack_flag && scl_low;
assign s42idle_start = state_c == S4 && !ack_flag && scl_low && !sda_en;
assign s52s8_start = state_c == S5 && noack_flag && scl_low;
assign s62idle_start = state_c == S6 && end_cnt_bytes && !ack_flag;
assign s62s7_start = state_c == S6 && end_cnt_bytes && ack_flag && wr_flag;
assign s62s3_start = state_c == S6 && end_cnt_bytes && ack_flag && rd_flag;
assign s72s8_start = state_c == S7 && ack_flag && scl_low;
assign s72idle_start = state_c == S7 && !ack_flag && scl_low && !sda_en;
assign s82idle_start = state_c == S8 && end_cnt_scl && sda && scl;
//SDA三态门使能
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
sda_en <= 0;
else if(state_c == IDLE)begin
if(wr || rd)
sda_en <= 1;
else
sda_en <= 0;
end
else if(state_c == S2 || state_c == S6 || state_c == S7 || state_c == S4)begin
if(cnt_hlbit == 16 - 1 && add_cnt_hlbit)
sda_en <= 0;
//读控制接收ACK后不必拉高,因为之后是读数据,使能要一直拉低
else if(end_cnt_hlbit && state_c != S4)
sda_en <= 1;
end
else if(state_c == S5)begin
if(cnt_hlbit >= 0 && cnt_hlbit < 16 - 1)
sda_en <= 0;
else if(cnt_hlbit >= 17 - 1 && cnt_hlbit < 18)
sda_en <= 1;
end
end
//SDA输出
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_out <= 1;
end
//写起始位
else if(state_c == S1)begin
if(end_cnt_scl && sda == 1)
sda_out <= 0;
end
//写控制
else if(state_c == S2)begin
sda_out <= sda_out_data[7];
end
//读起始位
else if(state_c == S3)begin
//使用r_begin是为了,在S3状态初始的时候就是高电平,便于拉低
sda_out <= r_begin;
end
//读控制
else if(state_c == S4)begin
sda_out <= sda_out_data[7];
end
//发送NOACK
else if(state_c == S5)begin
if(cnt_hlbit == 16 - 1 && add_cnt_hlbit)
sda_out <= 1;
else if(cnt_hlbit == 18 - 1 && add_cnt_hlbit)
sda_out <= 0;
end
//写地址
else if(state_c == S6)begin
sda_out <= sda_out_data[7];
end
//写数据
else if(state_c == S7)begin
sda_out <= sda_out_data[7];
end
else if(state_c == S8)begin
if(cnt_scl == (SCL_N >> 1) - 1 && add_cnt_scl)
sda_out <= 1;
end
end
//SDA接收数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rd_data <= 0;
else if(state_c == S5)begin
if(cnt_hlbit < 17 - 1 && cnt_hlbit >= 0 && scl_high)
rd_data <= {rd_data[6:0],sda_in};
end
end
//SDA输出数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
sda_out_data <= 0;
else if(state_c == S1)
sda_out_data <= {4'b1010,device_addr,1'b0}; //写控制
else if(state_c == S2)begin
if(scl_low && cnt_hlbit < 17 - 1 && cnt_hlbit >= 0)
sda_out_data <= {sda_out_data[6:0],1'b0};
else if(scl_low && end_cnt_hlbit)begin
if(bytes_num == 1)
sda_out_data <= word_addr[7:0];
else
sda_out_data <= word_addr[15:8];
end
end
else if(state_c == S3)
sda_out_data <= {4'b1010,device_addr,1'b1}; //读控制
else if(state_c == S4)begin
if(scl_low && cnt_hlbit < 17 - 1 && cnt_hlbit >= 0)
sda_out_data <= {sda_out_data[6:0],1'b0};
end
else if(state_c == S6)begin
if(scl_low && cnt_hlbit < 17 - 1 && cnt_hlbit >= 0)
sda_out_data <= {sda_out_data[6:0],1'b0};
else if(add_cnt_bytes && !end_cnt_bytes)
sda_out_data <= word_addr[7:0];
else if(end_cnt_bytes)
sda_out_data <= wr_data;
end
else if(state_c == S7)begin
if(scl_low && cnt_hlbit < 17 - 1 && cnt_hlbit >= 0)
sda_out_data <= {sda_out_data[6:0],1'b0};
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
done <= 0;
else if(state_c == S8)begin
//提前一拍与SCL计数器结束条件对齐
if(cnt_scl == SCL_N - 2 && add_cnt_scl && scl && sda)
done <= 1;
else
done <= 0;
end
end
//读有效
assign wr_data_vld = state_c == S2 || state_c == S6 || state_c == S7 || state_c == S4;
assign rd_data_vld = state_c == S5;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
wr_flag <= 0;
else if(wr && !(wr_flag || rd_flag))
wr_flag <= 1;
else if(done)
wr_flag <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rd_flag <= 0;
else if(rd && !(wr_flag || rd_flag))
rd_flag <= 1;
else if(done)
rd_flag <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
ack_flag <= 0;
else if(scl_high && cnt_hlbit == 17 - 1 && add_cnt_hlbit && sda == 0)
ack_flag <= 1;
else if(scl_low && end_cnt_hlbit)
ack_flag <= 0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
noack_flag <= 0;
else if(state_c == S5)begin
if(scl_high && cnt_hlbit == 17 - 1 && add_cnt_hlbit && sda == 1)
noack_flag <= 1;
else if(scl_low && end_cnt_hlbit)
noack_flag <= 0;
end
end
//读起始位控制信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
r_begin <= 1;
else if(state_c == S3 && scl_high)begin
r_begin <= 0;
end
else if(state_c == S3 && scl_low)begin
r_begin <= 1;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
ack_error <= 0;
else if(s22idle_start || s62idle_start || s72idle_start || s42idle_start)
ack_error <= 1;
else
ack_error <= 0;
end
endmodule
三、仿真文件
`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2021/02/03 19:53:42
// Design Name:
// Module Name: iic_module_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module iic_module_tb();
parameter SYS_CLOCK = 50_000_000;
parameter SCL_CLOCK = 380_000; //快速模式
parameter RD_DATA_W = 6;
parameter WR_DATA_W = 6;
parameter WADDR_W = 2;
parameter DADDR_W = 3;
parameter WR_ADDR_W = 16;
parameter WDATA_W = 8;
parameter RDATA_W = 8;
parameter SCL_N = SYS_CLOCK/SCL_CLOCK;
parameter SCL_W = 7;
parameter DATA_N = 10;
parameter DATA_W = 4;
parameter BYTE_W = 6;
parameter CTR_W = 8;
reg rst_n;
reg clk;
reg [WR_ADDR_W-1:0] word_addr;
reg wr;
reg [WDATA_W-1:0] wr_data;
reg rd;
wire wr_data_vld;
wire [RDATA_W-1:0] rd_data;
wire rd_data_vld;
wire done;
wire scl;
wire sda;
iic_module iic_module(
.rst_n (rst_n),
.clk (clk),
.waddr_num (3'd2),
.device_addr (3'b000),
.word_addr (word_addr),
.wr (wr),
.wr_data (wr_data),
.wr_data_vld (wr_data_vld),
.rd (rd),
.rd_data (rd_data),
.rd_data_vld (rd_data_vld),
.done (done),
.scl (scl),
.sda (sda)
);
M24LC64 M24LC64(
.A0(1'b0),
.A1(1'b0),
.A2(1'b0),
.WP(1'b0),
.SDA(sda),
.SCL(scl),
.RESET(!rst_n)
);
//时钟周期,单位ns,在这里修改时钟周期
parameter CYCLE = 20;
//复位时间,此时表示复位3个时钟周期的时间
parameter RST_TIME = 3;
//生成本地时钟50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
$display(M24LC64.RdDataByte);
$display(M24LC64.WrDataByte);
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE * RST_TIME);
rst_n = 1;
end
initial begin
rd = 0;
@ (posedge rst_n)
#(10*CYCLE)
word_addr = 0;
wr_data = 8'b1000_0001;
repeat(5)begin
wr = 1'b1;
#(CYCLE);
wr = 1'b0;
#102450;
word_addr = word_addr + 2;
wr_data = wr_data + 2;
end
#200000;
word_addr = 0;
repeat(5)begin
rd = 1'b1;
#(CYCLE);
rd = 1'b0;
#130400;
word_addr = word_addr + 2;
end
#200000;
$stop;
end
endmodule
四、使用M24LC64 EEPROM仿真模型及碰到的问题
https://www.cnblogs.com/cnlntr/p/14258703.html
- 问题一、24LC64报错
问题原因:波形宽度错误,不符合24LC64的时序,第一行错误的意思是 第5450ns到6710ns脉冲的宽度不符合24LC64时序,脉冲至少要1300ns的宽度
解决办法:将SCL时钟计数器频率调小一点
parameter SCL_CLOCK = 380_000; //快速模式
- 问题二、一次读时序之后,第二次的读写时序收不到ACK
原因:仿真模型的内部信号writeactive没有拉低
将仿真模型里面的写数据间隔改成TWC改成500就可以解决问题了