Tabla de contenido
Tres, implementación FPGA del protocolo I2C
2. Verificación de la simulación
El enlace de descarga del proyecto para la realización de funciones generales del sistema de lectura y escritura EEPROM (explicado en el próximo blog, incluido el contenido de este artículo) de este tema es el siguiente:
https://download.csdn.net/download/qq_33231534/12503289
Tres, implementación FPGA del protocolo I2C
El blog anterior dio una explicación general del protocolo de bus I2C, así como una explicación detallada del tiempo de lectura y escritura de la EEPROM del dispositivo de bus I2C (AT24C64). A continuación, se explica cómo leer y escribir el dispositivo EEPROM en la FPGA, así como el diseño de la interfaz y el sistema de depuración. Dé una descripción específica. Esta plataforma experimental utiliza la placa de desarrollo AC620 de Xiaomeige, y el chip FPGA es el ciclón IV EP4CE10F17C8N.
1. Diseño de interfaz I2C (EEPROM)
De acuerdo con los cuatro diagramas de tiempo de lectura y escritura anteriores, escriba el tiempo de la interfaz I2C y el diseño de la interfaz del protocolo I2C. Aquí, la máquina de estado se utiliza para diseñar todo el diseño en 9 estados, a saber, IDLE, WR_START, WR_DEVICE, WR_ADDR, WR_DATA, RD_START, RD_DEVICE, RD_DATA, DETENER. El diagrama de transición de estado es el siguiente:
El significado de cada estado y el nombre y función de la señal de interfaz del módulo son los siguientes:
Escriba aquí las señales de la interfaz en una tabla para que quede más claro. como sigue:
Nombre de la señal | E / S | Dígitos | Función descriptiva |
clk | yo | 1 | Reloj del sistema 50MHz |
rst_n | yo | 1 | Reinicio de sistema |
rd_data_num | yo | 6 | Número de datos leídos, máximo 32 |
wr_data_num | yo | 6 | Número de datos a escribir, máximo 32 |
word_addr | yo | dieciséis | Dirección de palabra de datos, AT24C64 es una dirección de 16 bits |
device_addr | yo | 3 | La dirección variable del dispositivo está determinada por la conexión del hardware. La plataforma experimental es 3'b001 |
reyezuelo | yo | 1 | Activar escritura de datos |
wr_data | yo | 8 | Escribir datos |
rd_en | yo | 1 | Leer datos habilitar |
wr_data_vaild | EL | 1 | Escribir un bit válido de datos, leer varios datos es generar un bit válido cuando se lee cada dato |
rd_data | EL | 8 | Leer datos |
rd_data_vaild | EL | 1 | Leer datos de bits válidos, escribir varios datos es generar un bit válido cuando se escriben todos los datos |
hecho | EL | 1 | Indicador de fin de operación de lectura y escritura |
scl | EL | 1 | Línea de reloj serial |
sda | yo | 1 | Línea de datos en serie |
Directamente en el código:
//-------------------------------------------------------------------
//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. Verificación de la simulación
El modelo de simulación EEPROM utilizado aquí se utiliza para la verificación de la simulación, y se utiliza el modelo de simulación EEPROM de Micron. Aquellos que lo necesiten pueden descargarlo de mi proyecto. Consulte el enlace de descarga anterior para ver el código fuente completo del proyecto. No entraré en detalles aquí. El código de prueba es el siguiente:
`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
La siguiente figura es el resultado de la simulación de AT24C64 EEPROM:
La siguiente figura es el resultado de la simulación de la secuencia de escritura en AT24C64 EEPROM:
La siguiente figura es el resultado de la simulación de la secuencia de lectura de AT24C64 EEPROM:
Los resultados de la simulación cumplen con los resultados esperados. El próximo artículo llevará a cabo la depuración a nivel de placa del módulo de interfaz I2C y diseñará un experimento de sistema de lectura y escritura EEPROM para verificar la exactitud del diseño.