目录
引言
子模块设计
思路
单字节 IIC 发送模块
思路
Verilog 源码
多字节发送控制模块
思路
Verilog 源码
仿真
思路
test bench
仿真结果
参考声明
引言
本篇博文承接前文,继续做 IIC 通信协议 FPGA实现相关的内容。用Verilog 编写一个 IIC 通信控制器,最后用 Microchip公司提供的 IIC 驱动器件的 Verilog 模型 辅助完成 仿真验证。
子模块设计
思路
此处的思路主要是将IIC复杂的通信步骤拆解,因为对器件进行一次读或者写操作,都需要如下步骤:
- 发送起始信号,征用总线;
- 发送器件ID,选择通信的从机;
- 发送要操作的寄存器地址;
- 发送/接收要写入/读出寄存器的数据;
因此为了简化设计的复杂度,将设计分为两大模块:
- 单字节发送模块
- 多字节发送控制模块
单字节 IIC 发送模块
思路
拟采用状态机的方法进行设计。此处仅对状态转换条件进行简述。更为详细的理解需要仔细阅读设计代码。
状态声明:
LP_ST_IDLE:空闲状态
LP_ST_GEN_START:产生起始信号
LP_ST_WR_DATA:写数据
LP_ST_RD_DATA :读数据
LP_ST_GEN_ACK :产生应答
LP_ST_CHECK_ACK :检查器件发出的应答
LP_ST_GEN_STOP:产生停止信号,释放总线状态转移说明:
LP_ST_IDLE:
如果操作使能信号有效,依次判断 写起始信号,写数据,读数据哪个命令有效(有优先级) 如果对应的命令有效则跳转到相应的状态中。
LP_ST_GEN_START:产生起始信号;产生完毕后,根据输入命令继续判断是否需要写/读寄存器,如果需要则跳转到对应的状态。
LP_ST_WR_DATA:写入数据;写数据完成后,检查从机是否给出响应;
LP_ST_RD_DATA :读出数据;读数据完毕后,根据命令给出响应;
LP_ST_GEN_ACK:产生应答;产生结束后,判断是否有结束的命令,如果有则进入结束状态。
LP_ST_CHECK_ACK:检查应答;检查结束后,判断是否有结束的命令,如果有则进入结束状态。
LP_ST_GEN_STOP:产生结束信号;产生完毕后,进入空闲状态;
此IIC发送接收的时序由计数器控制 发送 1比特数据计数器增加 4 .
Verilog 源码
// | ===================================================---------------------------===================================================
// | --------------------------------------------------- IIC 控制器 模块 ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-24
// | 完成时间 : 2022-12-24
// | 作 者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// | -1- IIC 通信速率支持:100KHz、400KHz、3400KHz
// | -2-
// | -3-
// |
// | ================================= 模块修改历史纪录 =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:
module IIC_CTRL_MDL #(
// | ==================================== 模块可重载参数声明 ====================================
parameter P_SYS_CLK_FREQ = 32'd50_000_000,// 系统时钟频率 单位:Hz
parameter P_IIC_CLK_FREQ = 32'd400_000 // IIC 时钟频率 单位:Hz
)(
// | ==================================== 模块输入输出端口声明 ====================================
input I_SYS_CLK ,
input I_SYS_RSTN ,
input [5:0] I_CMD ,
input I_OPR_EN ,
input [7:0] I_TX_DATA ,
output reg [7:0] O_RX_DATA ,
output reg O_OPR_DONE ,
output reg O_ACK ,
output reg O_IIC_SCLK ,
inout IO_IIC_SDAT
);
// | ==================================== 模块内部参数声明 ====================================
// 状态编码
localparam LP_ST_IDLE = 7'b0000_001;
localparam LP_ST_GEN_START = 7'b0000_010;
localparam LP_ST_WR_DATA = 7'b0000_100;
localparam LP_ST_RD_DATA = 7'b0001_000;
localparam LP_ST_GEN_ACK = 7'b0010_000;
localparam LP_ST_CHECK_ACK = 7'b0100_000;
localparam LP_ST_GEN_STOP = 7'b1000_000;
// 命令字编码
localparam LP_CMD_WR_REQ = 6'b000_001;
localparam LP_CMD_RD_REQ = 6'b000_010;
localparam LP_CMD_START_REQ = 6'b000_100;
localparam LP_CMD_STOP_REQ = 6'b001_000;
localparam LP_CMD_ACK_REQ = 6'b010_000;
localparam LP_CMD_NOACK_REQ = 6'b100_000;
// IIC 时钟分频计数器最大值
localparam LP_DIV_FREQ_CNT_MAX = P_SYS_CLK_FREQ/P_IIC_CLK_FREQ/4 ;
// | ==================================== 模块内部信号声明 ====================================
// 状态
reg [6:0] R_STATE;
// 计数器
reg [$clog2(LP_DIV_FREQ_CNT_MAX)-1:0] R_DIV_FREQ_CNT;
reg R_DIV_FREQ_CNT_EN;
reg [4:0] R_BIT_CNT;
// IIC输出数据线
reg R_SDAT_VAL;
reg R_SDAT;
// SCLK 脉冲(频率为 IIC 时钟 4 倍)
wire W_SCLK_PULSE_4;
// | ==================================== 模块内部逻辑设计 ====================================
always @ (posedge I_SYS_CLK)
begin
if(~I_SYS_RSTN)
begin
R_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};
end
else if(R_DIV_FREQ_CNT_EN)
begin
if(R_DIV_FREQ_CNT == LP_DIV_FREQ_CNT_MAX-1)
begin
R_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};
end
else
begin
R_DIV_FREQ_CNT <= R_DIV_FREQ_CNT + 1;
end
end
else
begin
R_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};
end
end
assign W_SCLK_PULSE_4 = (R_DIV_FREQ_CNT == LP_DIV_FREQ_CNT_MAX-1);
assign IO_IIC_SDAT = (R_SDAT_VAL & !R_SDAT) ? 1'b0 : 1'bz;
always @ (posedge I_SYS_CLK)
begin
if(~I_SYS_RSTN)
begin
R_STATE <= LP_ST_IDLE;
O_RX_DATA <= 8'd0;
O_OPR_DONE <= 1'd0;
O_ACK <= 1'd0;
O_IIC_SCLK <= 1'd1;
R_DIV_FREQ_CNT_EN <= 1'd0;
R_BIT_CNT <= 5'd0;
R_SDAT_VAL <= 1'd0;
R_SDAT <= 1'd1;
end
else
begin
case(R_STATE)
LP_ST_IDLE :
begin
if(I_OPR_EN)
begin
R_DIV_FREQ_CNT_EN <= 1'b1;
if(I_CMD & LP_CMD_START_REQ)
begin
R_STATE <= LP_ST_GEN_START;
end
else if(I_CMD & LP_CMD_WR_REQ)
begin
R_STATE <= LP_ST_WR_DATA;
end
else if(I_CMD & LP_CMD_RD_REQ)
begin
R_STATE <= LP_ST_RD_DATA;
end
else
begin
R_STATE <= LP_ST_IDLE;
end
end
else
begin
R_STATE <= LP_ST_IDLE;
R_DIV_FREQ_CNT_EN <= 1'b0;
end
R_BIT_CNT <= 5'd0;
O_ACK <= 1'd0;
O_OPR_DONE <= 1'b0;
end
LP_ST_GEN_START :
begin
if(W_SCLK_PULSE_4)
begin
if(R_BIT_CNT == 3)
begin
R_BIT_CNT <= 0;
end
else
begin
R_BIT_CNT <= R_BIT_CNT + 1;
end
R_SDAT_VAL <= 1'b1;
case(R_BIT_CNT)
0:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
1:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
2:begin R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b1;end
3:begin R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b0;end
default:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
endcase
if(R_BIT_CNT == 3)
begin
if(I_CMD & LP_CMD_WR_REQ)
begin
R_STATE <= LP_ST_WR_DATA;
end
else if(I_CMD & LP_CMD_RD_REQ)
begin
R_STATE <= LP_ST_RD_DATA;
end
else
begin
R_STATE <= LP_ST_GEN_START;
end
end
end
end
LP_ST_WR_DATA :
begin
if(W_SCLK_PULSE_4)
begin
if(&R_BIT_CNT)
begin
R_BIT_CNT <= 5'd0;
end
else
begin
R_BIT_CNT <= R_BIT_CNT + 1;
end
R_SDAT_VAL <= 1'b1;
case(R_BIT_CNT)
0,4,8,12,16,20,24,28 :begin R_SDAT <= I_TX_DATA[3'd7-R_BIT_CNT[4:2]]; O_IIC_SCLK <= 1'b0;end
1,5,9,13,17,21,25,29 :begin R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b1;end
2,6,10,14,18,22,26,30:begin R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b1;end
3,7,11,15,19,23,27,31:begin R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b0;end
default: begin R_SDAT <= 1'b1; O_IIC_SCLK <= 1'b1;end
endcase
if(&R_BIT_CNT)
begin
R_STATE <= LP_ST_CHECK_ACK;
end
end
end
LP_ST_RD_DATA :
begin
if(W_SCLK_PULSE_4)
begin
if(&R_BIT_CNT)
begin
R_BIT_CNT <= 5'd0;
end
else
begin
R_BIT_CNT <= R_BIT_CNT + 1;
end
R_SDAT_VAL <= 1'b0;
case(R_BIT_CNT)
0,4,8,12,16,20,24,28 :begin O_IIC_SCLK <= 1'b0;end
1,5,9,13,17,21,25,29 :begin O_IIC_SCLK <= 1'b1;end
2,6,10,14,18,22,26,30:begin O_RX_DATA <= {O_RX_DATA[6:0],IO_IIC_SDAT};O_IIC_SCLK <= 1'b1;end
3,7,11,15,19,23,27,31:begin O_IIC_SCLK <= 1'b0;end
default:begin O_IIC_SCLK <= 1'b1;end
endcase
if(&R_BIT_CNT)
begin
R_STATE <= LP_ST_GEN_ACK;
end
end
end
LP_ST_GEN_ACK :
begin
if(W_SCLK_PULSE_4)
begin
if(R_BIT_CNT == 3)
begin
R_BIT_CNT <= 0;
end
else
begin
R_BIT_CNT <= R_BIT_CNT + 1;
end
R_SDAT_VAL <= 1'b1;
case(R_BIT_CNT)
0:
begin
if(I_CMD & LP_CMD_ACK_REQ)
R_SDAT <= 1'b0;
else if(I_CMD & LP_CMD_NOACK_REQ)
R_SDAT <= 1'b1;
O_IIC_SCLK <= 1'b0;
end
1:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
2:begin R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b1;end
3:begin R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b0;end
default:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
endcase
if(R_BIT_CNT == 3)
begin
if(I_CMD & LP_CMD_STOP_REQ)
begin
R_STATE <= LP_ST_GEN_STOP;
end
else
begin
R_STATE <= LP_ST_IDLE;
O_OPR_DONE <= 1'b1;
end
end
end
end
LP_ST_CHECK_ACK :
begin
if(W_SCLK_PULSE_4)
begin
if(R_BIT_CNT == 3)
begin
R_BIT_CNT <= 0;
end
else
begin
R_BIT_CNT <= R_BIT_CNT + 1;
end
R_SDAT_VAL <= 1'b0;
case(R_BIT_CNT)
0:begin O_IIC_SCLK <= 1'b0;O_ACK <= 1'b0;end
1:begin O_IIC_SCLK <= 1'b1;O_ACK <= 1'b0;end
2:begin O_IIC_SCLK <= 1'b1;O_ACK <= IO_IIC_SDAT;end
3:begin O_IIC_SCLK <= 1'b0;O_ACK <= O_ACK;end
default:begin O_IIC_SCLK <= 1'b1;O_ACK <= 1'b0;end
endcase
if(R_BIT_CNT == 3)
begin
if(I_CMD & LP_CMD_STOP_REQ)
begin
R_STATE <= LP_ST_GEN_STOP;
end
else
begin
R_STATE <= LP_ST_IDLE;
O_OPR_DONE <= 1'b1;
end
end
end
end
LP_ST_GEN_STOP :
begin
if(W_SCLK_PULSE_4)
begin
if(R_BIT_CNT == 3)
begin
R_BIT_CNT <= 0;
end
else
begin
R_BIT_CNT <= R_BIT_CNT + 1;
end
R_SDAT_VAL <= 1'b1;
case(R_BIT_CNT)
0:begin O_IIC_SCLK <= 1'b0; R_SDAT<= 1'b0;end
1:begin O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b0;end
2:begin O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;end
3:begin O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;end
default:begin O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;end
endcase
if(R_BIT_CNT == 3)
begin
R_STATE <= LP_ST_IDLE;
O_OPR_DONE <= 1'b1;
end
end
end
endcase
end
end
endmodule
多字节发送控制模块
思路
同样是利用状态机实现 IIC 读写;将读和写分别分为三步:
- 发起读/写(给写地址,写数据等)
- 等待读/写完成
- 读/写完成
每个读/写的状态内,利用计数器确定发送/读出操作步骤的先后顺序。因为每次读写过程都包含下面几个步骤:
- 发送起始信号,征用总线;
- 发送器件ID,选择通信的从机;
- 发送要操作的寄存器地址;
- 发送/接收要写入/读出寄存器的数据;
Verilog 源码
// | ===================================================---------------------------===================================================
// | --------------------------------------------------- IIC 通信顶层 模块 ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-24
// | 完成时间 : 2022-12-24
// | 作 者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// | -1- IIC 通信速率支持:100KHz、400KHz、3400KHz
// | -2-
// | -3-
// |
// | ================================= 模块修改历史纪录 =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:
module TOP_IIC_MDL#(
// | ==================================== 模块可重载参数声明 ====================================
parameter P_SYS_CLK_FREQ = 32'd50_000_000,// 系统时钟频率 单位:Hz
parameter P_IIC_CLK_FREQ = 32'd400_000 // IIC 时钟频率 单位:Hz
)(
// | ==================================== 模块输入输出端口声明 ====================================
input I_SYS_CLK ,
input I_SYS_RSTN ,
input I_WR_REQ,
input I_RD_REQ,
input [15:0] I_ADDR,
input I_ADDR_MODE,// 1:16位地址;0:8位地址
input [7:0] I_WR_DATA,
output reg [7:0] O_RX_DATA,
input [7:0] I_DEVICE_ID,
output reg O_RW_DONE,
output reg O_ACK,
output O_IIC_SCLK,
inout IO_IIC_SDAT
);
// | ==================================== 模块内部参数声明 ====================================
// 命令字编码
localparam LP_CMD_WR_REQ = 6'b000_001;
localparam LP_CMD_RD_REQ = 6'b000_010;
localparam LP_CMD_START_REQ = 6'b000_100;
localparam LP_CMD_STOP_REQ = 6'b001_000;
localparam LP_CMD_ACK_REQ = 6'b010_000;
localparam LP_CMD_NOACK_REQ = 6'b100_000;
// 状态编码
localparam LP_ST_IDLE = 7'b0000_001;
localparam LP_ST_WR = 7'b0000_010;
localparam LP_ST_WAIT_WR_DONE = 7'b0000_100;
localparam LP_ST_WR_DONE = 7'b0001_000;
localparam LP_ST_RD = 7'b0010_000;
localparam LP_ST_WAIT_RD_DONE = 7'b0100_000;
localparam LP_ST_RD_DONE = 7'b1000_000;
// | ==================================== 模块内部信号声明 ====================================
reg [2:0] R_CNT;
reg [6:0] R_STATE;
reg [5:0] R_CMD;
reg R_OPR_EN;
reg [7:0] R_TX_DATA;
wire [7:0] W_RX_DATA;
wire W_OPR_DONE;
wire W_ACK;
wire [15:0] W_ADDR;
// | ==================================== 模块内部逻辑设计 ====================================
assign W_ADDR = I_ADDR_MODE ? I_ADDR : {I_ADDR[7:0],I_ADDR[15:8]};
always @ (posedge I_SYS_CLK)
begin
if(~I_SYS_RSTN)
begin
R_CNT <= 3'd0;
R_STATE <= LP_ST_IDLE;
R_CMD <= 6'd0;
R_OPR_EN <= 1'b0;
R_TX_DATA <= 8'd0;
O_RX_DATA <= 8'd0;
O_RW_DONE <= 1'd0;
O_ACK <= 1'd0;
end
else
begin
case(R_STATE)
LP_ST_IDLE :
begin
R_CNT <= 3'd0;
O_ACK <= 1'b0;
O_RW_DONE <= 1'b0;
if(I_WR_REQ)
begin
R_STATE <= LP_ST_WR;
end
else if(I_RD_REQ)
begin
R_STATE <= LP_ST_RD;
end
else
begin
R_STATE <= LP_ST_IDLE;
end
end
LP_ST_WR:
begin
R_STATE <= LP_ST_WAIT_WR_DONE;
case(R_CNT)
3'd0:
begin
TASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID);
end
3'd1:
begin
TASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[15:8]);
end
3'd2:
begin
TASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[7:0]);
end
3'd3:
begin
TASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_STOP_REQ,1'b1,I_WR_DATA);
end
default:
begin
end
endcase
end
LP_ST_WAIT_WR_DONE :
begin
R_OPR_EN <= 1'b0;
if(W_OPR_DONE)
begin
O_ACK <= O_ACK | W_ACK;
case(R_CNT)
3'd0:
begin
R_CNT <= 3'd1;
R_STATE <= LP_ST_WR;
end
3'd1:
begin
if(I_ADDR_MODE)
begin
R_CNT <= 3'd2;
end
else
begin
R_CNT <= 3'd3;
end
R_STATE <= LP_ST_WR;
end
3'd2:
begin
R_CNT <= 3;
R_STATE <= LP_ST_WR;
end
3'd3:
begin
R_STATE <= LP_ST_WR_DONE;
end
default:
begin
R_STATE <= LP_ST_IDLE;
end
endcase
end
end
LP_ST_WR_DONE :
begin
O_RW_DONE <= 1'b1;
R_STATE <= LP_ST_IDLE;
end
LP_ST_RD:
begin
R_STATE <= LP_ST_WAIT_RD_DONE;
case(R_CNT)
3'd0:
begin
TASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID);
end
3'd1:
begin
TASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[15:8]);
end
3'd2:
begin
TASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[7:0]);
end
3'd3:
begin
TASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID | 8'd1);
end
3'd4:
begin
TASK_RD_BYTE(LP_CMD_RD_REQ | LP_CMD_NOACK_REQ | LP_CMD_STOP_REQ,1'b1);
end
default://不必理会
begin
end
endcase
end
LP_ST_WAIT_RD_DONE :
begin
R_OPR_EN <= 1'b0;
if(W_OPR_DONE)
begin
if(R_CNT <= 3'd3)
begin
O_ACK <= O_ACK | W_ACK;
end
case(R_CNT)
3'd0:
begin
R_CNT <= 3'd1;
R_STATE <= LP_ST_RD;
end
3'd1:
begin
if(I_ADDR_MODE)
begin
R_CNT <= 3'd2;
end
else
begin
R_CNT <= 3'd3;
end
R_STATE <= LP_ST_RD;
end
3'd2:
begin
R_CNT <= 3'd3;
R_STATE <= LP_ST_RD;
end
3'd3:
begin
R_CNT <= 3'd4;
R_STATE <= LP_ST_RD;
end
3'd4:
begin
R_STATE <= LP_ST_RD_DONE;
end
default:
begin
R_STATE <= LP_ST_IDLE;
end
endcase
end
end
LP_ST_RD_DONE :
begin
O_RW_DONE <= 1'b1;
R_STATE <= LP_ST_IDLE;
O_RX_DATA <= W_RX_DATA;
end
default :
begin
R_STATE <= LP_ST_IDLE;
end
endcase
end
end
// | ==================================== 模块内部任务定义 ====================================
task TASK_WR_BYTE;
input [5:0] I_TASK_CMD;
input I_TASK_OPR_EN;
input [7:0] I_TASK_WR_DATA;
begin
R_CMD <= I_TASK_CMD;
R_OPR_EN <= I_TASK_OPR_EN;
R_TX_DATA<= I_TASK_WR_DATA;
end
endtask
task TASK_RD_BYTE;
input [5:0] I_TASK_CMD;
input I_TASK_OPR_EN;
begin
R_CMD <= I_TASK_CMD;
R_OPR_EN <= I_TASK_OPR_EN;
end
endtask
// | ==================================== 模块内部模块例化 ====================================
IIC_CTRL_MDL #(
.P_SYS_CLK_FREQ(P_SYS_CLK_FREQ),
.P_IIC_CLK_FREQ(P_IIC_CLK_FREQ)
) INST_IIC_CTRL_MDL (
.I_SYS_CLK (I_SYS_CLK),
.I_SYS_RSTN (I_SYS_RSTN),
.I_CMD (R_CMD),
.I_OPR_EN (R_OPR_EN),
.I_TX_DATA (R_TX_DATA),
.O_RX_DATA (W_RX_DATA),
.O_OPR_DONE (W_OPR_DONE),
.O_ACK (W_ACK),
.O_IIC_SCLK (O_IIC_SCLK),
.IO_IIC_SDAT (IO_IIC_SDAT)
);
endmodule
仿真
思路
利用和支持 IIC 协议的器件完成验证。Microchip 官网提供了多种自产器件的 Verilog 模型。网址如下:
Verilog Modelshttps://www.microchip.com/doclisting/TechDoc.aspx?type=Verilog此处我们选择一个支持IIC通信的存储器,
将该模型添加到设计文件中,在test bench中例化使用,具体见如下代码:
test bench
// |------------------------------ ================================== ------------------------------
// |============================== IIC顶层模块——测试文件 ==============================
// |------------------------------ ================================== ------------------------------
// | 创建时间 : 2022-12-25
// | 完成时间 : 2022-12-25
// | 作 者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :测试功能模块:TOP_IIC_MDL
// | 辅助模块:M24LC04B
// |
// |------------------------------------------------------------------------------------------------
// |---------------------------------------- 模块修改历史 ----------------------------------------
// | 修改日期:
// | 修改作者:
// | 修改注解:
`timescale 1ns / 1ps
module TB_TOP_IIC_MDL();
// | ==================================== 模块可重载参数声明 ====================================
parameter P_SYS_CLK_FREQ = 32'd50_000_000;// 系统时钟频率 单位:Hz
parameter P_IIC_CLK_FREQ = 32'd400_000 ;// IIC 时钟频率 单位:Hz
// | ==================================== 模块输入输出端口声明 ====================================
reg I_SYS_CLK ;
reg I_SYS_RSTN ;
reg I_WR_REQ;
reg I_RD_REQ;
reg [15:0] I_ADDR;
reg I_ADDR_MODE;// 1:16位地址;0:8位地址
reg [7:0] I_WR_DATA;
wire [7:0] O_RX_DATA;
reg [7:0] I_DEVICE_ID;
wire O_RW_DONE;
wire O_ACK;
wire O_IIC_SCLK;
wire IO_IIC_SDAT;
// 双向总线配置上拉
pullup PUP(IO_IIC_SDAT);
// | ======================================= 产生测试激励 =======================================
initial I_SYS_CLK = 1'b0;
always #10 I_SYS_CLK = ~I_SYS_CLK;
initial
begin
I_SYS_RSTN = 1'b0;
I_WR_REQ = 0;
I_RD_REQ = 0;
I_ADDR = 0;
I_ADDR_MODE= 0;
I_WR_DATA = 0;
I_DEVICE_ID= 0;
#(20*100+1);
I_SYS_RSTN = 1;
#(20*100);
TASK_WR_1_BYTE(8'hA0,8'h0A,8'hD1);
TASK_WR_1_BYTE(8'hA0,8'h0B,8'hD2);
TASK_WR_1_BYTE(8'hA0,8'h0C,8'hD3);
TASK_WR_1_BYTE(8'hA0,8'h0F,8'hD4);
TASK_RD_1_BYTE(8'hA0,8'h0A);
TASK_RD_1_BYTE(8'hA0,8'h0B);
TASK_RD_1_BYTE(8'hA0,8'h0C);
TASK_RD_1_BYTE(8'hA0,8'h0F);
$finish;
end
// | ==================================== 模块内部任务定义 ====================================
task TASK_WR_1_BYTE;
input [7:0] I_TASK_DEVICE_ID;
input [7:0] I_TASK_ADDR;
input [7:0] I_TASK_WR_DATA;
begin
I_ADDR = {8'd0,I_TASK_ADDR};
I_DEVICE_ID = I_TASK_DEVICE_ID;
I_ADDR_MODE = 1'b0;
I_WR_DATA = I_TASK_WR_DATA;
I_WR_REQ = 1'b1;
#20;
I_WR_REQ = 1'b0;
@(posedge O_RW_DONE);
#200;
end
endtask
task TASK_RD_1_BYTE;
input [7:0] I_TASK_DEVICE_ID;
input [7:0] I_TASK_ADDR;
begin
I_ADDR = {8'd0,I_TASK_ADDR};
I_DEVICE_ID = I_TASK_DEVICE_ID;
I_ADDR_MODE = 1'b0;
I_RD_REQ = 1'b1;
#20;
I_RD_REQ = 1'b0;
@(posedge O_RW_DONE);
#200;
end
endtask
// | ====================================== 待测试模块例化 ========================================
TOP_IIC_MDL #(
.P_SYS_CLK_FREQ(P_SYS_CLK_FREQ),
.P_IIC_CLK_FREQ(P_IIC_CLK_FREQ)
) INST_TOP_IIC_MDL (
.I_SYS_CLK (I_SYS_CLK),
.I_SYS_RSTN (I_SYS_RSTN),
.I_WR_REQ (I_WR_REQ),
.I_RD_REQ (I_RD_REQ),
.I_ADDR (I_ADDR),
.I_ADDR_MODE (I_ADDR_MODE),
.I_WR_DATA (I_WR_DATA),
.O_RX_DATA (O_RX_DATA),
.I_DEVICE_ID (I_DEVICE_ID),
.O_RW_DONE (O_RW_DONE),
.O_ACK (O_ACK),
.O_IIC_SCLK (O_IIC_SCLK),
.IO_IIC_SDAT (IO_IIC_SDAT)
);
M24LC04B INST_M24LC04B
(
.A0(1'b0),
.A1(1'b0),
.A2(1'b0),
.WP(1'b0),
.SDA(IO_IIC_SDAT),
.SCL(O_IIC_SCLK),
.RESET(~I_SYS_RSTN)
);
endmodule
仿真结果
参考声明
【1】芯路恒开发板教程