本文内容:FPGA 作为主机,控制 EEPROM 芯片,进行数据读写,同时将写入或读出的数据和地址显示在数码管上,并有标记
IIC_top.v
/*========================================*\
filename : IIC_top.v
description : IIC顶层模块
up file :
reversion :
v1.0 : 2022-8-22 16:15:32
author : 张某某
\*========================================*/
module IIC_top (
input clk ,
input rst_n ,
input [ 1:0] key_in ,
inout SDA ,
output SCL ,
output [ 5:0] SEL ,
output [ 7:0] DIG
);
// Parameter definition
wire [ 1:0] press ;
wire SDA_in ;
wire SDA_out ;
wire SDA_oe ;
wire [ 7:0] data ;
wire [ 7:0] address ;
wire [ 1:0] eeprom_state ;
// Signal definition
// Module calls
key_filter U_key_filter(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input [ 1:0]*/ .key_in (key_in ),
/*output reg [ 1:0]*/ .press (press )
);
iic_eeprom U_iic_eeprom(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input [ 1:0]*/ .press (press ),
/*input */ .SDA_in (SDA_in ),
/*output reg */ .SCL (SCL ),
/*output reg */ .SDA_out (SDA_out ),
/*output reg */ .SDA_oe (SDA_oe ),
/*output reg [ 7:0]*/ .data_out (data ),
/*output reg [ 7:0]*/ .address_out (address ),
/*output reg [ 1:0]*/ .state_out (eeprom_state ) // 0表示空闲,1表示写,2表示读
);
display U_display(
/*input */ .clk (clk ), // 50MHz
/*input */ .rst_n (rst_n ), // 复位信号
/*input [ 1:0]*/ .eeprom_state (eeprom_state ),
/*input [ 7:0]*/ .data (data ),
/*input [ 7:0]*/ .address (address ),
/*output reg [ 5:0]*/ .SEL (SEL ), // SEL信号
/*output reg [ 7:0]*/ .DIG (DIG ) // DIG信号
);
// Logic description
assign SDA_in = SDA;
assign SDA = SDA_oe ? SDA_out : 1'bz;
endmodule
key_filter.v
/*========================================*\
filename : key_filter.v
description : 按键消抖模块
up file : IIC_top.v
reversion :
v1.0 : 2022-8-22 16:15:09
author : 张某某
\*========================================*/
module key_filter #(parameter MS_20 = 20'd1000_000)(
input clk ,
input rst_n ,
input [ 1:0] key_in ,
output reg [ 1:0] press
);
// 全局变量定义
// 信号定义
reg [ 1:0] key_0 ; // 按键信号当前时钟周期电平
reg [ 1:0] key_1 ; // 按键信号下一个时钟周期电平
wire [ 1:0] key_nedge ; // 下降沿使能信号
reg add_flag ; // 计数使能信号
reg [19:0] delay_cnt ; // 延时计数器
// 模块功能
//打拍器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_0 <= 'b1;
key_1 <= 'b1;
end
else begin
key_0 <= key_in;
key_1 <= key_0;
end
end
// 检测下降沿
assign key_nedge = ~key_0 & key_1;
// 计数使能信号
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
add_flag <= 'b0;
end
else if (key_nedge) begin
add_flag <= 'b1;
end
else if (delay_cnt >= MS_20 - 1) begin
add_flag <= 'b0;
end
else begin
add_flag <= add_flag;
end
end
// 计数20ms
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
delay_cnt <= 20'd0;
end
else if (add_flag) begin
if (delay_cnt >= MS_20 - 1) begin
delay_cnt <= 20'd0;
end
else begin
delay_cnt <= delay_cnt + 1;
end
end
else begin
delay_cnt <= 20'd0;
end
end
// 输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
press <= 'd0;
end
else if (delay_cnt >= MS_20 - 1) begin
press <= ~key_in;
end
else begin
press <= 'd0;
end
end
endmodule
iic_eeprom.v
/*========================================*\
filename : iic_eeprom.v
description : IIC控制EEPROM
up file : IIC_top.v
reversion :
v1.0 : 2022-8-22 16:14:06
author : 张某某
\*========================================*/
module iic_eeprom (
input clk ,
input rst_n ,
input [ 1:0] press ,
input SDA_in ,
output reg SCL ,
output reg SDA_out ,
output reg SDA_oe ,
output reg [ 7:0] data_out ,
output reg [ 7:0] address_out ,
output reg [ 1:0] state_out // 0表示空闲,1表示写,2表示读
);
// Parameter definition
parameter IDLE = 7'b000_0001 ,
START = 7'b000_0010 ,
WR_DATA = 7'b000_0100 ,
RD_DATA = 7'b000_1000 ,
CHECK_ACK = 7'b001_0000 ,
SEND_NACK = 7'b010_0000 ,
STOP = 7'b100_0000 ;
// Signal definition
reg [ 6:0] state_c ;
reg [ 6:0] state_n ;
wire idle2start ;
wire start2wr_data ;
wire wr_data2check_ack ;
wire check_ack2start ;
wire check_ack2wr_data ;
wire check_ack2rd_data ;
wire rd_data2send_nack ;
wire check_ack2stop ;
wire send_nack2stop ;
wire stop2idle ;
reg [ 6:0] cnt_400KHz ; // SCL频率计数器
wire add_cnt_400KHz ;
wire end_cnt_400KHz ;
reg SCL_0 ; // SCL打拍器
wire SCL_podge ; // SCL上升沿
wire SCL_nedge ; // SCL下降沿
reg [ 2:0] cnt_bit ; // 比特计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [ 1:0] cnt_byte ; // 字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [ 1:0] eeprom_state ; // 0表示空闲,1表示写,2表示读
reg [ 7:0] data_buffer ; // 数据缓冲器
reg [ 7:0] pointer_wr ; // 写数据指针
reg [ 7:0] pointer_rd ; // 读数据指针
reg [ 7:0] data_generate ; // 生成写入EEPROM的数据
wire add_data_generate ;
wire end_data_generate ;
// Logic description
/*************************************
第一段 状态转移
*************************************/
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
/*************************************
第二段 状态转移规律
*************************************/
always @(*) begin
case (state_c)
IDLE :
begin
if (idle2start) begin
state_n = START;
end
else begin
state_n = state_c;
end
end
START :
begin
if (start2wr_data) begin
state_n = WR_DATA;
end
else begin
state_n = state_c;
end
end
WR_DATA :
begin
if (wr_data2check_ack) begin
state_n = CHECK_ACK;
end
else begin
state_n = state_c;
end
end
RD_DATA :
begin
if (rd_data2send_nack) begin
state_n = SEND_NACK;
end
else begin
state_n = state_c;
end
end
CHECK_ACK :
begin
if (check_ack2start) begin
state_n = START;
end
else if (check_ack2rd_data) begin
state_n = RD_DATA;
end
else if (check_ack2wr_data) begin
state_n = WR_DATA;
end
else if (check_ack2stop) begin
state_n = STOP;
end
else begin
state_n = state_c;
end
end
SEND_NACK :
begin
if (send_nack2stop) begin
state_n = STOP;
end
else begin
state_n = state_c;
end
end
STOP :
begin
if (stop2idle) begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
endcase
end
assign idle2start = state_c == IDLE && press;
assign start2wr_data = state_c == START && SCL_nedge;
assign wr_data2check_ack = state_c == WR_DATA && end_cnt_bit && cnt_byte <= 'd3;
assign check_ack2start = state_c == CHECK_ACK && cnt_byte == 'd1 && SCL_nedge && eeprom_state == 'd2;
assign check_ack2wr_data = state_c == CHECK_ACK && SCL_nedge && cnt_byte < 'd2;
assign check_ack2rd_data = state_c == CHECK_ACK && cnt_byte == 'd2 && SCL_nedge && eeprom_state == 'd2;
assign rd_data2send_nack = state_c == RD_DATA && end_cnt_bit && cnt_byte <= 'd3;
assign check_ack2stop = state_c == CHECK_ACK && cnt_byte == 'd2 && SCL_nedge && eeprom_state == 'd1;
assign send_nack2stop = state_c == SEND_NACK && cnt_byte == 'd3 && SCL_nedge;
assign stop2idle = state_c == STOP && cnt_400KHz == 'd57;
/*************************************
第三段 描述输出
*************************************/
// 按键控制EEPROM读写
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
eeprom_state <= 'd0;
end
else if (stop2idle) begin
eeprom_state <= 'd0;
end
else if (state_c == IDLE) begin
case (press)
2'b10 : eeprom_state <= 'd1; // 写状态
2'b01 : eeprom_state <= 'd2; // 读状态
default: eeprom_state <= eeprom_state;
endcase
end
else begin
eeprom_state <= eeprom_state;
end
end
// 按键控制指针
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pointer_wr <= 'd0;
pointer_rd <= 'd0;
end
else if (stop2idle) begin
case (eeprom_state)
'd1 : pointer_wr <= pointer_wr + 'd1;
'd2 : pointer_rd <= pointer_rd + 'd1;
default: begin pointer_wr <= pointer_wr; pointer_rd <= pointer_rd;end
endcase
end
else begin
pointer_wr <= pointer_wr;
pointer_rd <= pointer_rd;
end
end
// SCL打拍器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SCL_0 <= 'd1;
end
else begin
SCL_0 <= SCL;
end
end
assign SCL_podge = SCL & ~SCL_0; // SCL上升沿
assign SCL_nedge = ~SCL & SCL_0; // SCL下降沿
// SCL计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_400KHz <= 'd0;
end
else if (add_cnt_400KHz) begin
if (end_cnt_400KHz) begin
cnt_400KHz <= 'd0;
end
else begin
cnt_400KHz <= cnt_400KHz + 'd1;
end
end
else begin
cnt_400KHz <= 'd0;
end
end
assign add_cnt_400KHz = state_c != IDLE;
assign end_cnt_400KHz = add_cnt_400KHz && cnt_400KHz >= 'd124;
// SCL输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SCL <= 'd1;
end
else begin
case (cnt_400KHz)
'd0 : SCL <= 'd1;
'd59 : SCL <= 'd0;
default: SCL <= SCL;
endcase
end
end
// 比特计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bit <= 'd0;
end
else if (add_cnt_bit) begin
if (end_cnt_bit) begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 'd1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = (state_c == WR_DATA || state_c == RD_DATA) && SCL_nedge;
assign end_cnt_bit = add_cnt_bit && cnt_bit >= 'd7;
// 字节计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_byte <= 'd0;
end
else if (check_ack2stop || send_nack2stop) begin
cnt_byte <= 'd0;
end
else if (add_cnt_byte) begin
if (end_cnt_byte) begin
cnt_byte <= 'd0;
end
else begin
cnt_byte <= cnt_byte + 'd1;
end
end
else begin
cnt_byte <= cnt_byte;
end
end
assign add_cnt_byte = check_ack2wr_data || check_ack2rd_data;
assign end_cnt_byte = add_cnt_byte && cnt_byte >= 'd3;
// 生成顺序数据
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_generate <= 'd0;
end
else if (add_data_generate) begin
if (end_data_generate) begin
data_generate <= 'd1;
end
else begin
data_generate <= data_generate + 'd1;
end
end
else begin
data_generate <= data_generate;
end
end
assign add_data_generate = state_c == IDLE && press == 2'b10;
assign end_data_generate = add_data_generate && data_generate >= 8'hff;
// 数据缓冲器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_buffer <= 'd0;
end
else if (state_c == WR_DATA || state_c == RD_DATA) begin
case (eeprom_state)
'd1 : // 1表示写数据到EEPROM中
begin
case (cnt_byte)
'd0 : data_buffer <= 8'ha0;
'd1 : data_buffer <= pointer_wr;
'd2 : data_buffer <= data_generate;
default: data_buffer <= 'd0;
endcase
end
'd2 : // 2表示从EEPROM中读数据
begin
case (cnt_byte)
'd0 : data_buffer <= 8'ha0;
'd1 : data_buffer <= pointer_rd;
'd2 : data_buffer <= 8'ha1;
'd3 :
begin
if (SCL) begin
data_buffer[7 - cnt_bit] <= SDA_in;
end
else begin
data_buffer <= data_buffer;
end
end
default: data_buffer <= 'd0;
endcase
end
default: data_buffer <= 'd0;
endcase
end
else begin
data_buffer <= 'd0;
end
end
// SDA_out输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SDA_oe <= 'd1;
SDA_out <= 'd1;
end
else begin
case (state_c)
START :
begin
if (cnt_400KHz == 'd29) begin
SDA_oe <= 'd1;
SDA_out <= 'd0;
end
else if (cnt_400KHz == 'd69) begin
SDA_oe <= 'd1;
SDA_out <= 'd1;
end
else begin
SDA_oe <= SDA_oe;
SDA_out <= SDA_out;
end
end
WR_DATA :
begin
if (cnt_400KHz == 'd69) begin
SDA_oe <= 'd1;
SDA_out <= data_buffer[7 - cnt_bit];
end
else begin
SDA_oe <= SDA_oe;
SDA_out <= SDA_out;
end
end
RD_DATA :
begin
SDA_oe <= 'd0;
SDA_out <= 'd0;
end
CHECK_ACK :
begin
if (cnt_400KHz == 'd69) begin
SDA_oe <= 'd0;
SDA_out <= 'd0;
end
else begin
SDA_oe <= SDA_oe;
SDA_out <= SDA_out;
end
end
SEND_NACK :
begin
if (cnt_400KHz == 'd69) begin
SDA_oe <= 'd1;
SDA_out <= 'd1;
end
else begin
SDA_oe <= SDA_oe;
SDA_out <= SDA_out;
end
end
STOP :
begin
if (cnt_400KHz == 'd69) begin
SDA_oe <= 'd1;
SDA_out <= 'd0;
end
else if (cnt_400KHz == 'd29) begin
SDA_oe <= 'd1;
SDA_out <= 'd1;
end
else begin
SDA_oe <= SDA_oe;
SDA_out <= SDA_out;
end
end
default:
begin
SDA_oe <= 'd1;
SDA_out <= 'd1;
end
endcase
end
end
// 输出数据给数码管显示
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_out <= 'd0;
address_out <= 'd0;
state_out <= 'd0;
end
else if (eeprom_state == 'd1 && wr_data2check_ack && cnt_byte == 'd2) begin
data_out <= data_buffer;
address_out <= pointer_wr;
state_out <= 'd1;
end
else if (rd_data2send_nack) begin
data_out <= data_buffer;
address_out <= pointer_rd;
state_out <= 'd2;
end
else begin
data_out <= data_out;
address_out <= address_out;
state_out <= state_out;
end
end
endmodule
display.v
/*========================================*\
filename : display.v
description : 滚动显示输入的数据
up file :
reversion :
v1.0 : 2022-7-27 18:49:34
author : 张某某
\*========================================*/
module display #(parameter MS_1= 16'd50000)(
input clk , // 50MHz
input rst_n , // 复位信号
input [ 1:0] eeprom_state ,
input [ 7:0] data ,
input [ 7:0] address ,
output reg [ 5:0] SEL , // SEL信号
output reg [ 7:0] DIG // DIG信号
);
// 信号定义
reg [15:0] cnt_flicker ; // 计数1ms
wire SEL_change ; // cnt_flicker计满使能信号
reg [ 4:0] tmp_data ; // 当前DIG的值
wire [ 3:0] num_2 ; // 百位
wire [ 3:0] num_1 ; // 十位
wire [ 3:0] num_0 ; // 个位
reg pointer ;
// 逻辑描述
// 闪烁频率计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_flicker <= 'd0;
end
else if (SEL_change) begin
cnt_flicker <= 'd0;
end
else begin
cnt_flicker <= cnt_flicker + 'd1;
end
end
assign SEL_change = cnt_flicker >= MS_1 - 'd1 ? 1'b1 : 1'b0;
// SEL信号输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
SEL <= 6'b011_111;
end
else if (SEL_change) begin
SEL <= {SEL[4:0], SEL[5]};
end
else begin
SEL <= SEL;
end
end
// 获取地址的百位、十位、个位
assign num_2 = address / 100;
assign num_1 = address % 100 / 10;
assign num_0 = address % 100 % 10;
// tmp_data当前SEL位选所对应的DIG十进制值
always @(*) begin
case (SEL)
6'b011_111 : begin tmp_data = data[3:0]; pointer = 'd1;end
6'b101_111 : begin tmp_data = data[7:4]; pointer = 'd1;end
6'b110_111 : begin tmp_data = num_0; pointer = 'd0;end
6'b111_011 :
begin
pointer = 'd1;
if (num_2 == 'd0 && num_1 == 'd0) begin
tmp_data = 'd16;
end
else begin
tmp_data = num_1;
end
end
6'b111_101 :
begin
pointer = 'd1;
if (num_2) begin
tmp_data = num_2;
end
else begin
tmp_data = 'd16;
end
end
6'b111_110 :
begin
pointer = 'd1;
case (eeprom_state)
'd1 : tmp_data = 'd17;
'd2 : tmp_data = 'd18;
default: tmp_data = 'd16;
endcase
end
default: begin tmp_data = 'd0; pointer = 'd1;end
endcase
end
// DIG输出各数字对应的二进制
always @(*) begin
case (tmp_data)
5'd0 : DIG = {pointer, 7'b100_0000};
5'd1 : DIG = {pointer, 7'b111_1001};
5'd2 : DIG = {pointer, 7'b010_0100};
5'd3 : DIG = {pointer, 7'b011_0000};
5'd4 : DIG = {pointer, 7'b001_1001};
5'd5 : DIG = {pointer, 7'b001_0010};
5'd6 : DIG = {pointer, 7'b000_0010};
5'd7 : DIG = {pointer, 7'b111_1000};
5'd8 : DIG = {pointer, 7'b000_0000};
5'd9 : DIG = {pointer, 7'b001_0000};
5'd10: DIG = {pointer, 7'b000_1000};
5'd11: DIG = {pointer, 7'b000_0011};
5'd12: DIG = {pointer, 7'b100_0110};
5'd13: DIG = {pointer, 7'b010_0001};
5'd14: DIG = {pointer, 7'b000_0110};
5'd15: DIG = {pointer, 7'b000_1110};
5'd16: DIG = {pointer, 7'b111_1111}; // 全黑
5'd17: DIG = {pointer, 7'b001_1101}; // 写数据标识符
5'd18: DIG = {pointer, 7'b010_1011}; // 读数据标识符
default : DIG = 8'b1111_1111;
endcase
end
endmodule
这里只需要对 iic_eeprom.v 文件进行仿真即可
tb_iic_eeprom.v
/*========================================*\
filename : tb_iic_eeprom.v
description : iic_eeprom仿真
up file :
reversion :
v1.0 : 2022-8-22 16:13:37
author : 张某某
\*========================================*/
`timescale 1ns/1ns
module tb_iic_eeprom;
// Parameter definition
parameter CYC_CLK = 20 ;
// Drive signal
reg tb_clk ;
reg tb_rst_n ;
reg [ 1:0] tb_press ;
reg tb_SDA_in ;
// Observation signal
wire tb_SCL ;
wire tb_SDA_out ;
wire tb_SDA_oe ;
wire [ 7:0] tb_data_out ;
// Module calls
iic_eeprom U_iic_eeprom(
/*input */ .clk (tb_clk ),
/*input */ .rst_n (tb_rst_n ),
/*input [ 1:0]*/ .press (tb_press ),
/*input */ .SDA_in (tb_SDA_in ),
/*output reg */ .SCL (tb_SCL ),
/*output reg */ .SDA_out (tb_SDA_out ),
/*output reg */ .SDA_oe (tb_SDA_oe ),
/*output reg [ 7:0]*/ .data_out (tb_data_out ),
/*output reg [ 7:0]*/ .address_out (tb_address_out ),
/*output reg [ 7:0]*/ .state_out (tb_state_out )
);
// System initialization
initial begin
tb_clk = 1'b1;
tb_rst_n = 1'b0;
#(CYC_CLK) tb_rst_n = 1'b1;
end
always #(CYC_CLK >> 1) tb_clk = ~tb_clk;
initial begin
tb_press = 2'b00;
tb_SDA_in = 1'b0;
#(1000 * CYC_CLK);
// 写数据
tb_press = 2'b10;
#(CYC_CLK) tb_press = 2'b00;
#(5000 * CYC_CLK);
// 读数据
tb_press = 2'b01;
#(CYC_CLK) tb_press = 2'b00;
#(10000 * CYC_CLK);
$stop;
end
endmodule
tcl 文件
set_location_assignment PIN_E1 -to clk
set_location_assignment PIN_E15 -to rst_n
set_location_assignment PIN_E16 -to key_in[0]
set_location_assignment PIN_M15 -to key_in[1]
set_location_assignment PIN_L1 -to SCL
set_location_assignment PIN_L2 -to SDA
set_location_assignment PIN_A4 -to SEL[0]
set_location_assignment PIN_B4 -to SEL[1]
set_location_assignment PIN_A3 -to SEL[2]
set_location_assignment PIN_B3 -to SEL[3]
set_location_assignment PIN_A2 -to SEL[4]
set_location_assignment PIN_B1 -to SEL[5]
set_location_assignment PIN_B7 -to DIG[0]
set_location_assignment PIN_A8 -to DIG[1]
set_location_assignment PIN_A6 -to DIG[2]
set_location_assignment PIN_B5 -to DIG[3]
set_location_assignment PIN_B6 -to DIG[4]
set_location_assignment PIN_A7 -to DIG[5]
set_location_assignment PIN_B8 -to DIG[6]
set_location_assignment PIN_A5 -to DIG[7]
IIC控制EEPROM