table of Contents
Three, FPGA implementation of I2C protocol
The project download link for the overall function realization of the EEPROM reading and writing system (explained in the next blog, including the content of this article) of this topic is as follows:
https://download.csdn.net/download/qq_33231534/12503289
Three, FPGA implementation of I2C protocol
The previous blog gave a general explanation of the I2C bus protocol, as well as a detailed explanation of the read and write timing of the I2C bus device EEPROM (AT24C64). The following is how to read and write the EEPROM device on the FPGA, as well as the interface design and debugging system Give a specific description. This experimental platform uses Xiaomeige’s AC620 development board, and the FPGA chip is cyclone IV EP4CE10F17C8N.
1. I2C (EEPROM) interface design
According to the four read and write timing diagrams above, write the I2C interface timing and I2C protocol interface design. Here, the state machine is used to design the entire design into 9 states, namely IDLE, WR_START, WR_DEVICE, WR_ADDR, WR_DATA, RD_START, RD_DEVICE, RD_DATA, STOP. The state transition diagram is as follows:
The meaning of each state and the name and function of the module interface signal are as follows:
Write the interface signals in a table here to make it clearer. as follows:
Signal name | I / O | Digits | Function description |
clk | I | 1 | System clock 50MHz |
rst_n | I | 1 | System reset |
rd_data_num | I | 6 | Number of read data, maximum 32 |
wr_data_num | I | 6 | Number of data to be written, maximum 32 |
word_addr | I | 16 | Data word address, AT24C64 is a 16-bit address |
device_addr | I | 3 | The variable address of the device is determined by the hardware connection. The experimental platform is 3'b001 |
wr_en | I | 1 | Write data enable |
wr_data | I | 8 | Write data |
rd_en | I | 1 | Read data enable |
wr_data_vaild | THE | 1 | Write data valid bit, read multiple data is to generate a valid bit when each data is read |
rd_data | THE | 8 | Read data |
rd_data_vaild | THE | 1 | Read data valid bit, write multiple data is to generate a valid bit when each data is written |
done | THE | 1 | Read and write operation end flag |
scl | THE | 1 | Serial clock line |
sda | I | 1 | Serial data line |
Directly on the code:
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: I2C_ctrl.v
//Last modified Date: 2020/6/6
//Last Version:
//Descriptions: EEPROM(AT24C64)接口设计
//-------------------------------------------------------------------
module I2C_ctrl(
input clk ,//系统时钟50MHz
input rst_n ,//系统复位
input [ 5: 0] rd_data_num ,//读数据个数,最大32
input [ 5: 0] wr_data_num ,//写数据个数,最大32
input [15: 0] word_addr ,//数据字地址,AT24C64是16位地址
input [ 2: 0] device_addr ,//设备可变地址,由硬件连接决定,本实验平台为3‘b001
input wr_en ,//写数据使能
input [ 7: 0] wr_data ,//写数据
input rd_en ,//读数据使能
output reg wr_data_vaild ,//写数据有效位,读多个数据就是读完每个数据时产生一个有效位
output reg [7:0] rd_data ,//读数据
output reg rd_data_vaild ,//读数据有效位,写多个数据就是写完每个数据时产生一个有效位
output reg done ,//读写操作结束标志
output reg scl ,//串行时钟线
inout sda //串行数据线
);
parameter SYS_CLK = 50_000_000 ;//系统时钟频率
parameter SCLK = 100_000 ;//串行时钟线时钟频率
localparam SCLK_CNT = SYS_CLK/SCLK;//产生时钟线的时钟计数次数
//状态机状态定义
parameter IDLE = 4'd0 ;//空闲状态
parameter WR_START = 4'd1 ;//写数据启动状态
parameter WR_DEVICE = 4'd2 ;//发送设备地址状态
parameter WR_ADDR = 4'd3 ;//发送数据字地址状态
parameter WR_DATA = 4'd4 ;//发送数据状态
parameter RD_START = 4'd5 ;//读数据启动
parameter RD_DEVICE = 4'd6 ;//读操作设备地址状态
parameter RD_DATA = 4'd7 ;//读数据状态
parameter STOP = 4'd8 ;//停止状态
reg [ 3: 0] state_c ;//状态机改变后的状态,时序逻辑
reg [ 3: 0] state_n ;//状态机当前状态,组合逻辑
reg add_flag ;//计数条件标志
reg [ 8: 0] sclk_cnt ;//sclk时钟信号计数
reg scl_high ;//scl信号高电平中间信号,1个时钟周期
reg scl_low ;//scl信号低电平中间信号,1个时钟周期
reg wr_flag ;//写标志
reg rd_flag ;//读标志
reg ack ;//响应信号
reg waddr_cnt ;//数据字地址字节计数,共2字节
reg [ 5: 0] wdata_cnt ;//写数据字节计数,最大32个
reg [ 5: 0] rdata_cnt ;//读数据字节计数,最大32个
reg [ 4: 0] bit_cnt ;//对传输的8位数据中的scl_highhe scl_low信号计数
reg sda_en ;//三态信号sda使能
reg sda_reg ;//三态信号sda寄存数据
reg [ 7: 0] device_addr_a ;//传输的8位设备地址
reg [ 2: 0] sda_reg_cnt ;//传输的8位数据传递数
wire [ 7: 0] word_addr_high;//数据字地址高8位
wire [ 7: 0] word_addr_low ;//数据字地址低8位
assign word_addr_high = word_addr[15:8];
assign word_addr_low = word_addr[ 7:0];
assign sda = sda_en ? sda_reg : 1'bz;
//sclk计数器计数标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
add_flag <= 0;
end
else if(wr_en || rd_en) begin
add_flag <= 1;
end
else if(done)begin
add_flag <= 0;
end
end
//scl信号计数器
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sclk_cnt <= 0;
end
else if(add_flag) begin
if((add_flag && sclk_cnt==SCLK_CNT-1) || done)
sclk_cnt <= 0;
else
sclk_cnt <= sclk_cnt + 1'b1;
end
end
//scl信号生成
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl <= 1;
end
else if(sclk_cnt==SCLK_CNT/2-1) begin
scl <= 0;
end
else if(sclk_cnt==1'b0)begin
scl<= 1;
end
else begin
scl <= scl;
end
end
//scl_high信号:scl信号高电平正中间脉冲
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl_high <= 0;
end
else if(sclk_cnt==SCLK_CNT*1/4-1) begin
scl_high <= 1;
end
else begin
scl_high <= 0;
end
end
//scl_low信号:scl信号低电平正中间脉冲
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl_low <= 0;
end
else if(sclk_cnt==SCLK_CNT*3/4-1) begin
scl_low <= 1;
end
else begin
scl_low <= 0;
end
end
//写信号标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_flag <= 0;
end
else if(wr_en) begin
wr_flag <= 1;
end
else if(done) begin
wr_flag <= 0;
end
end
//读信号标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_flag <= 0;
end
else if(rd_en) begin
rd_flag <= 1;
end
else if(done) begin
rd_flag <= 0;
end
end
//状态机第一段
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态机第二段
always @(*)begin
case(state_c)
IDLE :begin
if(wr_en || rd_en)
state_n = WR_START;
else
state_n = state_c;
end
WR_START :begin
if(scl==0 && scl_low)
state_n = WR_DEVICE;
else
state_n = state_c;
end
WR_DEVICE :begin
if(ack==1 && scl_low)
state_n = WR_ADDR;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
WR_ADDR :begin
if(wr_flag && ack==1 && waddr_cnt==1 && scl_low)
state_n = WR_DATA;
else if(rd_flag && ack==1 && waddr_cnt==1 &&scl_low)
state_n = RD_START;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
WR_DATA :begin
if(wdata_cnt==wr_data_num-1 && ack==1 && wr_flag==1 && scl_low)
state_n = STOP;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
RD_START :begin
if(scl==0 && scl_low)
state_n = RD_DEVICE;
else
state_n = state_c;
end
RD_DEVICE :begin
if(ack==1 && scl_low)
state_n = RD_DATA;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
RD_DATA :begin
if(rdata_cnt==rd_data_num-1 && rd_flag==1 && bit_cnt==17 && scl_low)
state_n = STOP;
else
state_n = state_c;
end
STOP :begin
if(scl_high)
state_n = IDLE;
else
state_n = state_c;
end
endcase
end
//在传输设备地址、字地址和数据时要传输8位数据,这里对其计数,计数scl_low和scl_high
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
bit_cnt <= 0;
end
else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
if(scl_high || scl_low)begin
if(bit_cnt==17 && scl_low)
bit_cnt <= 0;
else
bit_cnt <= bit_cnt + 1'b1;
end
end
else begin
bit_cnt <= bit_cnt;
end
end
//ack响应信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
ack <= 0;
end
else if(scl_high && bit_cnt==16 && sda==0) begin
ack <= 1;
end
else if(bit_cnt==17 && scl_low)begin
ack <= 0;
end
else begin
ack <= ack;
end
end
//2个字地址计数器
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
waddr_cnt <= 0;
end
else if(state_c==WR_ADDR && bit_cnt==17 && scl_low) begin
if(waddr_cnt==1)
waddr_cnt <= 0;
else
waddr_cnt <= waddr_cnt + 1'b1;
end
else begin
waddr_cnt <= waddr_cnt;
end
end
//写数据个数计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata_cnt <= 0;
end
else if(state_c==WR_DATA && bit_cnt==17 && scl_low) begin
if(wdata_cnt==wr_data_num-1)
wdata_cnt <= 0;
else
wdata_cnt <= wdata_cnt + 1'b1;
end
else begin
wdata_cnt <= wdata_cnt;
end
end
//读数据个数计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdata_cnt <= 0;
end
else if(state_c==RD_DATA && bit_cnt==17 && scl_low) begin
if(rdata_cnt==rd_data_num-1)
rdata_cnt <= 0;
else
rdata_cnt <= rdata_cnt + 1'b1;
end
else begin
rdata_cnt <= rdata_cnt;
end
end
//sda_en信号输出
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_en <= 0;
end
else begin
case(state_c)
IDLE:sda_en <= 0;
WR_START,RD_START,STOP:sda_en <= 1;
WR_DEVICE,WR_ADDR,WR_DATA,RD_DEVICE:
begin
if(bit_cnt<16)
sda_en <= 1;
else
sda_en <= 0;
end
RD_DATA:
//sda_en <= 0;
begin
if(bit_cnt<16)
sda_en <= 0;
else
sda_en <= 1;
end
default:sda_en <= 0;
endcase
end
end
//wr_data_vaild信号:写数据有效信号标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_data_vaild <= 0;
end
else if(state_c==WR_DATA && ack==1 && scl_low && bit_cnt==17) begin
wr_data_vaild <= 1;
end
else begin
wr_data_vaild <= 0;
end
end
//rd_data信号:读出8位数据
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_data <= 0;
end
else if(state_c==RD_DATA && bit_cnt<15 && scl_high) begin
rd_data <= {rd_data[6:0],sda};
end
else begin
rd_data <= rd_data;
end
end
//rd_data_vaild信号:读出8位数据有效信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_data_vaild <= 0;
end
else if(state_c==RD_DATA && bit_cnt==15 && scl_low) begin
rd_data_vaild <= 1;
end
else begin
rd_data_vaild <= 0;
end
end
//done:I2C工作结束标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
done <= 0;
end
else if(state_c==STOP && scl_high) begin
done <= 1;
end
else begin
done <= 0;
end
end
//设备地址
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
device_addr_a <= 0;
end
else if(rd_flag && (state_c==RD_DEVICE || state_c==RD_START || state_c==RD_DATA))begin
device_addr_a <= {4'b1010,device_addr,1'b1};
end
else begin
device_addr_a <= {4'b1010,device_addr,1'b0};
end
end
//sda_reg_cnt
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_reg_cnt <= 7;
end
else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
if(bit_cnt<16 && scl_low)begin
if(sda_reg_cnt==0)
sda_reg_cnt <= 7;
else
sda_reg_cnt <= sda_reg_cnt - 1'b1;
end
end
else begin
sda_reg_cnt <= sda_reg_cnt;
end
end
//sda_reg
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_reg <= 1;
end
else begin
case(state_c)
WR_START:begin
if(scl_high)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end
WR_DEVICE:begin
sda_reg <= device_addr_a[sda_reg_cnt];
end
WR_ADDR:begin
if(waddr_cnt==0)
sda_reg <= word_addr_high[sda_reg_cnt];
else if(waddr_cnt==1)
if(bit_cnt<16)
sda_reg <= word_addr_low[sda_reg_cnt];
else
sda_reg <= 1;
end
WR_DATA:begin
sda_reg <= wr_data[sda_reg_cnt]; //输入信号wr_data新数据应该wr_data_vaild时给出
end
RD_START:begin
if(scl_high)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end
RD_DEVICE:begin
sda_reg <= device_addr_a[sda_reg_cnt];
end
RD_DATA:/*begin
if(bit_cnt==16 || bit_cnt==17)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end*/
if(rdata_cnt==rd_data_num-1 && bit_cnt>15)
sda_reg <= 1;
else
sda_reg <= 0;
STOP:begin
sda_reg <= 0;
/*if(scl_high)
sda_reg <= 1;
else
sda_reg <= sda_reg;*/
end
default: sda_reg <= 1;
endcase
end
end
endmodule
2. Simulation verification
The EEPROM simulation model used here is used for simulation verification, and Micron's EEPROM simulation model is used. Those who need it can download it from my project. See the download link above for the complete project source code. I won’t go into details here. The test code is as follows:
`timescale 1 ns/ 1 ns
module I2C_ctrl_tb();
// test vector input registers
reg clk;
//reg [2:0] device_addr;
//reg [5:0] rd_data_num;
reg rd_en;
reg rst_n;
//reg treg_sda;
reg [15:0] word_addr;
reg [7:0] wr_data;
//reg [5:0] wr_data_num;
reg wr_en;
// wires
wire done;
wire [7:0] rd_data;
wire rd_data_vaild;
wire scl;
wire sda;
wire wr_data_vaild;
pullup(sda);
// assign statements (if any)
//assign sda = treg_sda;
parameter clk_period = 20;
I2C_ctrl i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.device_addr(3'b000),
.done(done),
.rd_data(rd_data),
.rd_data_num(6'd4),
.rd_data_vaild(rd_data_vaild),
.rd_en(rd_en),
.rst_n(rst_n),
.scl(scl),
.sda(sda),
.word_addr(word_addr),
.wr_data(wr_data),
.wr_data_num(6'd4),
.wr_data_vaild(wr_data_vaild),
.wr_en(wr_en)
);
M24LC64 u_M24LC64(
.A0(1'b0),
.A1(1'b0),
.A2(1'b0),
.WP(1'b0),
.SDA(sda),
.SCL(scl),
.RESET(!rst_n)
);
initial clk = 0;
always #(clk_period/2) clk=~clk;
initial begin
#2;
rst_n = 0;
word_addr = 0;
wr_en = 0;
wr_data = 0;
rd_en = 0;
#(clk_period*200);
rst_n = 1;
#(clk_period*20);
//写入20组数据
word_addr = 0;
wr_data = 0;
repeat(20)begin
wr_en = 1;
#(clk_period);
wr_en = 0;
repeat(4)begin
@(posedge wr_data_vaild)
wr_data = wr_data + 1;
end
@(posedge done);
#(clk_period*200);
word_addr = word_addr + 4;
end
#(clk_period*500);
//读出刚写入的20组数据
word_addr = 0;
repeat(20)begin
rd_en = 1;
#(clk_period);
rd_en = 0;
@(posedge done);
#(clk_period*200);
word_addr = word_addr + 4;
end
#(clk_period*500);
$stop;
end
endmodule
The following figure is the simulation result of AT24C64 EEPROM:
The following figure is the simulation result of writing sequence to AT24C64 EEPROM:
The following figure is the simulation result of read sequence of AT24C64 EEPROM:
The simulation results meet the expected results. The next article will conduct board-level debugging on the I2C interface module, and design an EEPROM read-write system experiment to verify the correctness of the design.