马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
x
案例采用明德扬设计思想完成。IIC协议是非常常用的接口协议,在电子类岗位招聘要求中经常出现它的身影。关于IIC协议这里只做简要介绍,详细信息请自行百度或查阅相关Datasheet,网上资料非常多。该篇博文主要讲如何使用verilog来描述IIC协议,以读写EEPROM为例带领大家了解下明德扬四段式状态机规范和优势,另外还有一些自己在设计过程中总结的经验技巧。 IIC协议时序格式以Datasheet中时序图的形式供大家参考。IIC协议有一条时钟线SCL和一条双线数据总线SDA。SDA在SCL高电平时保持稳定,否则视为开始或结束条件。 发送端发送1byte数据后,接收端在下一个SCL高电平期间拉低总线表示应答,即接收数据成功。 以下分别是器件地址为1字节的EEPROM的单字节写和读操作,需要注意的是DEVICE ADDRESS段中前四位固定是4'b1010,后三位根据EEPROM地址信号电平决定(本次实验地址信号引脚均接地,因此后三位为000),最后一位是读写标志位,低电平写。 好了,有了以上五张时序图我们便知道要干什么了,就是实现这些时序嘛!对于这种串行时序,时间有先后且操作差异较大的要用状态机实现。每种类型操作定义在一个状态中,状态内部需要多个操作则配合计数器实现。整体设计思路如下:先构造时钟信号SCL,这里频率定义为200KHz,而系统时钟有频率为200MHz差分晶振提供,显然需要用到分频计数器。由于SCL高电平期间数据要保持稳定,所以我们在分频计数器计数到1/4处拉高SCL,3/4处拉低SCL,这样做的好处是在结束计数时正好处于SCL低电平中间点,此处作为数据变化的时刻再合适不过。 有了时钟信号,下一步就是通过不同的状态实现SDA信号线满足上述时序要求。我们先来划分状态(实际上时序图中都给我们标识好了),很明显综合考虑写和读两种时序,状态应定义为:初始状态、开始、写控制、响应1、写地址、响应2、写数据、响应3、重新开始、读控制、响应4、读数据、不响应、停止。这里写控制和读控制即是DEVICE ADDRESS阶段,唯一的区别在于读写标志位不同。能看出以上状态划分包括写流程分支和读流程分支,可以根据指令用一个标志位加以区分。 定义状态参数并采用独热码进行编码: IIC协议中每次SCL高电平期间视为一次操作,因此为了让每个状态都有整数个SCL周期(完整分频计数周期),对每个状态进行比特计数,写控制、地址、写数据、读控制、读数据阶段计数周期是8,其他为1。另外为保证代码的“健壮性”,也就是即使发送1byte数据后没有响应也不至于挂死在等待响应阶段,设定在每次等待响应阶段若响应才进入下一操作,否则回到初始状态。由此得到状态转移图(只包括主要流程,转移条件及未响应回到IDLE状态未画出): 至此所有的设计工作都已经完成,接下来就是根据上述分析编写代码。在编写代码之前简要介绍明德扬四段式状态机的设计思想和代码规范:四段式状态机实质上是在三段式状态机基础上单独提出状态转移条件定义的结构。目的是让设计者一个时间段只专注于一件事情,也就是说当设计状态机的时候先把状态转移流程确定,而条件用不同的信号名代替,等状态转移流程确定后再定义转移条件。这样做的另一个好处是作为条件的信号名可以很方便的在后续时序逻辑中使用。其中用于代替条件的信号名要遵循类似如下格式:<state_c>2<state_n>。<>处用状态名代替。 整体代码如下: [url=][/url]
1 `timescale 1ns / 1ps 2 3 module iic_interface#(parameter SCL_CYC = 1000)//200KHz 4 ( 5 input clk, 6 input rst_n, 7 8 //用户侧接口 9 input write_en,//写指令 10 input read_en, //读指令 11 input [7:0]share_addr, //读写复用地址 12 input [7:0] wri_data,//代写入数据 13 input wri_data_vld, 14 15 output reg busy,//总线忙信号 16 output reg [7:0] rd_data,//读回数据 17 output reg rd_data_vld, 18 19 //仿真用接口 20 output reg [13:0] state_c, 21 22 //eeprom侧接口 23 output reg scl, //时钟 24 input sda_in, 25 output reg sda_en, 26 output reg sda_reg 27 28 ); 29 30 reg [11:0] div_cnt; 31 reg high_middle,low_middle; 32 reg [3:0] bit_cnt; 33 reg [3:0] N; 34 //(*keep = "true"*)reg [13:0] state_c; 35 reg [13:0] state_n; 36 reg [7:0] wri_byte; 37 reg rd_flag; 38 reg [7:0] rd_buf; 39 reg [13:0] state_c_tmp; 40 reg [7:0] device_addr_wr_shift; 41 42 wire add_bit_cnt,end_bit_cnt; 43 wire add_div_cnt,end_div_cnt; 44 wire idle2start,start2wri_ctrl,wri_ctrl2ack1,ack12addr,addr2ack2,ack22wri_data; 45 wire wri_data2ack3,ack32stop,ack22re_start,re_start2rd_ctrl,rd_ctrl2ack4; 46 wire ack42rd_data,rd_data2nack,nack2stop,stop2idle,ack2idle; 47 reg ack_valid,ack_invalid; 48 wire [2:0] cs; 49 wire wri_vld; 50 wire [7:0] device_addr_rd,device_addr_wr; 51 wire [7:0] word_addr; 52 53 //状态编码 54 localparam IDLE = 14'b00_0000_0000_0001,//1 55 START = 14'b00_0000_0000_0010,//2 56 WRI_CTRL = 14'b00_0000_0000_0100,//4 57 ACK1 = 14'b00_0000_0000_1000,//8 58 ADDR = 14'b00_0000_0001_0000,//10 59 ACK2 = 14'b00_0000_0010_0000,//20 60 WRI_DATA = 14'b00_0000_0100_0000,//40 61 ACK3 = 14'b00_0000_1000_0000,//80 62 RE_START = 14'b00_0001_0000_0000,//100 63 RD_CTRL = 14'b00_0010_0000_0000,//200 64 ACK4 = 14'b00_0100_0000_0000,//400 65 RD_DATA = 14'b00_1000_0000_0000,//800 66 NACK = 14'b01_0000_0000_0000,//1000 67 STOP = 14'b10_0000_0000_0000;//2000 68 69 //分频计数器 在响应操作直到完成或退出到IDLE中间都计数 70 always@(posedge clk or negedge rst_n)begin 71 if(!rst_n) 72 div_cnt <= 0; 73 else if(add_div_cnt)begin 74 if(end_div_cnt) 75 div_cnt <= 0; 76 else 77 div_cnt <= div_cnt + 1'b1; 78 end 79 else 80 div_cnt <= 0; 81 end 82 83 assign add_div_cnt = busy == 1; 84 assign end_div_cnt = add_div_cnt && div_cnt == SCL_CYC - 1; 85 86 //比特计数器 87 always@(posedge clk or negedge rst_n)begin 88 if(!rst_n) 89 bit_cnt <= 0; 90 else if(add_bit_cnt)begin 91 if(end_bit_cnt) 92 bit_cnt <= 0; 93 else 94 bit_cnt <= bit_cnt + 1'b1; 95 end 96 end 97 98 assign add_bit_cnt = end_div_cnt; 99 assign end_bit_cnt = add_bit_cnt && bit_cnt == N - 1;100 101 always@(*)begin102 case(state_c)103 WRI_CTRL:N <= 8;104 ADDR:N <= 8;105 WRI_DATA:N <= 8;106 RD_CTRL:N <= 8;107 RD_DATA:N <= 8;108 default:N <= 1;109 endcase110 end111 112 //---------------------iic时序四段式状态机部分-------------------------113 114 //时序逻辑描述状态转移115 always@(posedge clk or negedge rst_n)begin116 if(!rst_n)117 state_c <= IDLE;118 else 119 state_c <= state_n;120 end121 122 //组合逻辑描述状态转移条件123 always@(*)begin124 case(state_c)125 IDLE:begin //空闲状态126 if(idle2start)127 state_n <= START;128 else 129 state_n <= state_c;130 end131 132 START:begin //产生开始条件 即SCL高电平期间SDA拉低133 if(start2wri_ctrl)134 state_n <= WRI_CTRL;135 else 136 state_n <= state_c;137 end138 139 WRI_CTRL:begin //写器件地址和写标志位140 if(wri_ctrl2ack1)141 state_n <= ACK1;142 else 143 state_n <= state_c;144 end145 146 ACK1:begin //等待响应147 if(ack12addr)148 state_n <= ADDR;149 else if(ack2idle)150 state_n <= IDLE;151 else 152 state_n <= state_c;153 end154 155 ADDR:begin //写存储单元地址156 if(addr2ack2)157 state_n <= ACK2;158 else 159 state_n <= state_c;160 end161 162 ACK2:begin //等待响应2163 if(ack22wri_data) //写操作164 state_n <= WRI_DATA;165 else if(ack22re_start)//读操作166 state_n <= RE_START;167 else if(ack2idle)168 state_n <= IDLE;169 else 170 state_n <= state_c;171 end172 173 WRI_DATA:begin //写数据 8bit174 if(wri_data2ack3)175 state_n <= ACK3;176 else 177 state_n <= state_c;178 end179 180 ACK3:begin //等待响应3181 if(ack32stop)182 state_n <= STOP;183 else if(ack2idle)184 state_n <= IDLE;185 else 186 state_n <= state_c;187 end188 189 RE_START:begin //若为读操作在响应2后再次构造开始条件190 if(re_start2rd_ctrl)191 state_n <= RD_CTRL;192 else 193 state_n <= state_c;194 end195 196 RD_CTRL:begin //写入存储单元地址和读标志位197 if(rd_ctrl2ack4)198 state_n <= ACK4;199 else 200 state_n <= state_c;201 end202 203 ACK4:begin //等待响应4204 if(ack42rd_data)205 state_n <= RD_DATA;206 else if(ack2idle)207 state_n <= IDLE;208 else 209 state_n <= state_c;210 end211 212 RD_DATA:begin //读数据 8bit213 if(rd_data2nack)214 state_n <= NACK;215 else 216 state_n <= state_c;217 end218 219 NACK:begin //不响应 无操作即可220 if(nack2stop)221 state_n <= STOP;222 else 223 state_n <= state_c;224 end225 226 STOP:begin //构造停止条件227 if(stop2idle)228 state_n <= IDLE;229 else 230 state_n <= state_c;231 end232 233 default:234 state_n <= IDLE;235 endcase236 end237 238 //连续赋值语句定义状态转移条件239 assign idle2start = state_c == IDLE && (write_en || read_en);240 assign start2wri_ctrl = state_c == START && end_bit_cnt; 241 assign wri_ctrl2ack1 = state_c == WRI_CTRL && end_bit_cnt;242 assign ack12addr = state_c == ACK1 && ack_valid && end_bit_cnt;243 assign addr2ack2 = state_c == ADDR && end_bit_cnt;244 assign ack22wri_data = state_c == ACK2 && ack_valid && !rd_flag && end_bit_cnt;245 assign wri_data2ack3 = state_c == WRI_DATA && end_bit_cnt;246 assign ack32stop = state_c == ACK3 && ack_valid && end_bit_cnt;247 assign ack22re_start = state_c == ACK2 && ack_valid && rd_flag && end_bit_cnt;248 assign re_start2rd_ctrl = state_c == RE_START && end_bit_cnt;249 assign rd_ctrl2ack4 = state_c == RD_CTRL && end_bit_cnt;250 assign ack42rd_data = state_c == ACK4 && ack_valid && end_bit_cnt;251 assign rd_data2nack = state_c == RD_DATA && end_bit_cnt;252 assign nack2stop = state_c == NACK && ack_invalid && end_bit_cnt;253 assign stop2idle = state_c == STOP && end_bit_cnt;254 assign ack2idle = ack_state && ack_invalid;255 256 257 258 always@(posedge clk or negedge rst_n)begin259 if(!rst_n)260 ack_valid <= 0;261 else if(ack12addr || ack22wri_data || ack32stop || ack22re_start || ack42rd_data || ack2idle)262 ack_valid <= 0;263 else if(ack_state && high_middle && !sda_en && !sda_in)264 ack_valid <= 1;265 end266 267 assign ack_state = state_c == ACK1 || state_c == ACK2 || state_c == ACK3 || state_c == ACK4;268 269 always@(posedge clk or negedge rst_n)begin270 if(!rst_n)271 ack_invalid <= 0;272 else if(state_c == NACK && high_middle && !sda_en && sda_in)273 ack_invalid <= 1;274 else if(end_bit_cnt)275 ack_invalid <= 0;276 end277 278 //时序逻辑描述状态输出279 280 //scl时钟信号281 always@(posedge clk or negedge rst_n)begin282 if(!rst_n)283 scl <= 0;284 else if(add_div_cnt && div_cnt == SCL_CYC/4 - 1)285 scl <= 1;286 else if(add_div_cnt && div_cnt == SCL_CYC/4 + SCL_CYC/2 - 1)287 scl <= 0;288 end289 290 //找到scl高低电平中间点291 always@(posedge clk or negedge rst_n)begin292 if(!rst_n)293 high_middle <= 0;294 else if(add_div_cnt && div_cnt == SCL_CYC/2 - 1)295 high_middle <= 1;296 else 297 high_middle <= 0;298 end299 300 //三态门输出使能301 always@(posedge clk or negedge rst_n)begin302 if(!rst_n)303 sda_en <= 1;304 else if(idle2start || ack12addr || ack22wri_data || ack32stop || ack22re_start || nack2stop)305 sda_en <= 1;306 else if(wri_ctrl2ack1 || addr2ack2 || wri_data2ack3 || rd_ctrl2ack4 || rd_data2nack || ack2idle || stop2idle)307 sda_en <= 0;308 end309 310 //数据总线输出寄存器311 always@(posedge clk or negedge rst_n)begin312 if(!rst_n)313 sda_reg <= 1;314 else if(idle2start)315 sda_reg <= 1;316 else if((state_c == START || state_c == RE_START) && high_middle)317 sda_reg <= 0;318 else if(state_c == WRI_CTRL)319 sda_reg <= device_addr_wr[7-bit_cnt];320 else if(state_c == ADDR)321 sda_reg <= word_addr[7 - bit_cnt];322 else if(state_c == WRI_DATA)323 sda_reg <= wri_data[7 - bit_cnt];324 else if(state_c == STOP && high_middle)325 sda_reg <= 1;326 else if(ack22re_start)327 sda_reg <= 1;328 else if(state_c == RE_START && high_middle)329 sda_reg <= 0;330 else if(state_c == RD_CTRL)331 sda_reg <= device_addr_rd[7- bit_cnt];332 else if(ack_state)333 sda_reg <= 0;334 else if(nack2stop)335 sda_reg <= 0;336 end337 338 assign device_addr_wr = {4'b1010,cs,1'b0};339 assign cs = 3'b000;340 assign word_addr = share_addr;341 assign device_addr_rd = {4'b1010,cs,1'b1};342 343 //读取数据缓存344 always@(posedge clk or negedge rst_n)begin345 if(!rst_n)346 rd_buf <= 0;347 else if(state_c == RD_DATA && high_middle)348 rd_buf <= {rd_buf[6:0],sda_in};349 end350 351 //读数据有效指示352 always@(posedge clk or negedge rst_n)begin353 if(!rst_n)354 rd_data_vld <= 0;355 else if(rd_data2nack)356 rd_data_vld <= 1;357 else 358 rd_data_vld <= 0;359 end360 361 //读数据输出362 always@(posedge clk or negedge rst_n)begin363 if(!rst_n)364 rd_data <= 0;365 else 366 rd_data <= rd_buf;367 end368 369 //读标志位370 always@(posedge clk or negedge rst_n)begin371 if(!rst_n)372 rd_flag <= 0;373 else if(read_en)374 rd_flag <= 1;375 else if(rd_flag && (stop2idle || state_c == IDLE))376 rd_flag <= 0;377 end378 379 //总线忙信号380 always@(posedge clk or negedge rst_n)begin381 if(!rst_n)382 busy <= 0;383 else if(write_en || read_en)384 busy <= 1;385 else if(busy == 1 &&(stop2idle || state_c == IDLE))386 busy <= 0;387 end388 389 endmodule[url=][/url]
可以看出状态机部分依次分为:时序逻辑描述状态转移,组合逻辑描述状态转移条件,连续赋值定义状态转移条件以及时序逻辑描述状态相关输出。并且至始至终使用state_c和state_n两个信号表示现态和次态,使逻辑更加清晰。接口部分为了方便仿真和调试,加入状态信号state_c。这里涉及到一个双向端口sda,用三个信号:输出使能sda_en,输出寄存器sda_reg和输入缓存sda_in表示。在顶层模块中使用这三个信号通过三态门的形式给出,关于三态门的使用细节和仿真方式稍后讲述。 先设计其他模块和顶层模块,之后对顶层模块进行仿真测试,这时观察各个模块中信号数值分析排查问题。有了时序接口模块,在正确无误情况下,已经可以实现对EEPROM的读写操作。现在明确设计目的,我们要实现EEPROM的一字节数据读写,因此可以通过按键发送指令向EEPROM中某地址中写入任意一个数据,之后用另一个按键发送读指令将刚写入地址中数据读出的方式验证读写操作是否正常工作。编写控制模块(控制模块仅实现IIC总线空闲时才响应操作,实际上用按键方式犹豫时间间隔较长,不会出现多个指令抢占总线的情况,这里设计控制模块是为了适应其他场合或功能扩展用途) [url=][/url]
1 `timescale 1ns / 1ps 2 3 module iic_ctrl( 4 input clk, 5 input rst_n, 6 input local_rd, 7 input local_wr, 8 9 input iic_busy,10 output reg com_rd,11 output reg com_wr12 );13 14 wire ready;15 16 assign ready = !iic_busy;17 18 //写命令19 always@(posedge clk or negedge rst_n)begin20 if(!rst_n)21 com_wr <= 0;22 else if(local_wr && ready)//iic总线空闲时才响应操作23 com_wr <= 1;24 else 25 com_wr <= 0;26 end27 28 //读命令29 always@(posedge clk or negedge rst_n)begin30 if(!rst_n)31 com_rd <= 0;32 else if(local_rd && ready)33 com_rd <= 1;34 else 35 com_rd <= 0;36 end37 38 39 endmodule[url=][/url]
剩下只需加入按键消抖模块,并把按键消抖模块,控制模块还有时序接口模块都例化在顶层文件中即可。按键消抖模块在之前的博文中有讲述,这里使用计数器配合状态标志位的方式实现。需要说明的是多个按键使用一个按键消抖模块的设计方式:只需将信号位宽定义为可变参数。 [url=][/url]
1 `timescale 1ns / 1ps 2 3 module key_filter 4 #(parameter DATA_W = 24, 5 KEY_W = 2, 6 TIME_20MS = 4_000_000) 7 ( 8 input clk , 9 input rst_n ,10 input [KEY_W-1 :0] key_in , //按键 按下为低电平11 output reg [KEY_W-1 :0] key_vld 12 );13 14 reg [DATA_W-1:0] cnt;15 reg flag;16 reg [KEY_W-1 :0] key_in_ff1;17 reg [KEY_W-1 :0] key_in_ff0;18 19 wire add_cnt,end_cnt;20 21 //延时计数器22 always @(posedge clk or negedge rst_n)begin23 if(rst_n==1'b0)24 cnt <= 0;25 else if(add_cnt)begin26 if(end_cnt)27 cnt <= 0;28 else29 cnt <= cnt + 1'b1;30 end31 else32 cnt <= 0;33 end34 //按下状态才计数,松手清零35 assign add_cnt = flag == 1'b0 && (key_in_ff1 != 2'b11); 36 assign end_cnt = add_cnt && cnt == TIME_20MS - 1;37 38 //计数标志位,0有效 为了只计数一个周期39 always @(posedge clk or negedge rst_n)begin 40 if(rst_n==1'b0)begin41 flag <= 1'b0;42 end43 else if(end_cnt)begin44 flag <= 1'b1;45 end46 else if(key_in_ff1 == 2'b11)begin//松手重新清零47 flag <= 1'b0;48 end49 end50 51 //同步处理52 always @(posedge clk or negedge rst_n)begin 53 if(rst_n==1'b0)begin54 key_in_ff0 <= 0;55 key_in_ff1 <= 0;56 end57 else begin58 key_in_ff0 <= key_in ;59 key_in_ff1 <= key_in_ff0;60 end61 end62 63 //输出有效64 always @(posedge clk or negedge rst_n)begin 65 if(rst_n==1'b0)begin66 key_vld <= 0;67 end68 else if(end_cnt)begin69 key_vld <= ~key_in_ff1;70 end71 else begin72 key_vld <= 0;73 end74 end75 76 endmodule[url=][/url]
顶层模块例化子模块: [url=][/url]
1 `timescale 1ns / 1ps 2 3 module eeprom_top( 4 5 input sys_clk_p, 6 input sys_clk_n, 7 input rst_n, 8 input [1:0] key, 9 //仿真接口 10 output sda_en, 11 output [13:0] state_c, 12 13 //EEPROM接口 14 output scl, 15 inout sda 16 ); 17 18 wire sys_clk_ibufg; 19 (*keep = "true"*)wire busy; 20 (*keep = "true"*)wire read,write; 21 wire [7:0] rd_data; 22 wire rd_data_vld; 23 (*keep = "true"*)wire sda_reg,sda_in; 24 (*keep = "true"*)wire [1:0] key_vld; 25 //(*keep = "true"*)wire sda_en; 26 //(*keep = "true"*)wire [13:0] state_c; 27 wire [39:0] probe0; 28 29 IBUFGDS # 30 ( 31 .DIFF_TERM ("FALSE"), 32 .IBUF_LOW_PWR ("FALSE") 33 ) 34 u_ibufg_sys_clk 35 ( 36 .I (sys_clk_p), //差分时钟的正端输入,需要和顶层模块的端口直接连接 37 .IB (sys_clk_n), // 差分时钟的负端输入,需要和顶层模块的端口直接连接 38 .O (sys_clk_ibufg) //时钟缓冲输出 39 ); 40 41 42 key_filter 43 #(.DATA_W(24), 44 .KEY_W(2), 45 .TIME_20MS(4_000_000)) 46 key_filter 47 ( 48 .clk (sys_clk_ibufg) , 49 .rst_n(rst_n) , 50 .key_in (key), //按键 按下为低电平 51 .key_vld(key_vld) 52 ); 53 54 iic_ctrl iic_ctrl( 55 .clk(sys_clk_ibufg), 56 .rst_n(rst_n), 57 .local_wr(key_vld[1]), 58 .local_rd(key_vld[0]), 59 60 .iic_busy(busy), 61 .com_rd(read), 62 .com_wr(write) 63 ); 64 65 iic_interface 66 #(.SCL_CYC(1000)) 67 iic_interface( 68 .clk(sys_clk_ibufg), 69 .rst_n(rst_n), 70 71 //用户侧接口 72 .write_en(write), //写指令 73 .read_en(read), //读指令 74 .share_addr(8'h15),//读写复用地址 75 .wri_data(8'h32), //待写入数据 76 .wri_data_vld(1'b1), 77 .busy(busy), //总线忙信号 78 .rd_data(rd_data), //读回数据 79 .rd_data_vld(rd_data_vld), 80 //仿真接口 81 .state_c(state_c), 82 //eeprom侧接口 83 .scl(scl), //时钟 84 .sda_in(sda_in), 85 .sda_en(sda_en), 86 .sda_reg(sda_reg) 87 ); 88 89 //三态门 90 assign sda = sda_en ? sda_reg : 1'bz; 91 assign sda_in = sda; 92 93 ila_0 ila_0 ( 94 .clk(sys_clk_ibufg), // input wire clk 95 .probe0(probe0) // input wire [39:0] probe0 96 ); 97 98 assign probe0[13:0] = state_c; //14bit 99 assign probe0[14] = busy;100 assign probe0[15] = scl;101 assign probe0[16] = sda_en;102 assign probe0[17] = sda_reg;103 assign probe0[18] = sda_in;104 assign probe0[19] = write;105 assign probe0[20] = read;106 assign probe0[39:21] = 0;107 108 endmodule[url=][/url]
看一下软件分析出的原理图结构(ILA IP核是之后添加的):
此处详细说明下双向端口使用:顶层模块中建立三态门结构,在输出使能有效时作为输出端口,无效是呈现高阻态,此时作为输入端口,由sda_in信号读取数值。那双向端口如何仿真呢?很简单,在测试文件中也构造一个三态门结构,而输出使能信号为设计中输出使能信号的相反值,这样在设计中该端口呈现高阻态时,正好在测试文件中相应端口作为输出的阶段。可以注意到我在顶层模块中加入了两个仿真接口:state_c和sda_en,方便在测试文件中找到给出响应的位置。测试文件如下: [url=][/url]
1 `timescale 1ns / 1ps 2 3 module eeprom_top_tb; 4 5 reg sys_clk_p,sys_clk_n; 6 reg rst_n; 7 reg [1:0] key; 8 9 wire scl; 10 wire sda; 11 wire sda_en;//高电平时待测试文件为输出 12 13 reg [15:0] myrand; 14 reg sda_tb_out; 15 wire [13:0] state_c; 16 17 eeprom_top eeprom_top( 18 .sys_clk_p(sys_clk_p), 19 .sys_clk_n(sys_clk_n), 20 .rst_n(rst_n), 21 .key(key), 22 .sda_en(sda_en), 23 .state_c(state_c), 24 .scl(scl), 25 .sda(sda) 26 ); 27 28 assign sda = (!sda_en) ? sda_tb_out : 1'bz; 29 30 parameter CYC = 5, 31 RST_TIME = 2; 32 33 defparam eeprom_top.key_filter.TIME_20MS = 200; 34 35 initial begin 36 sys_clk_p = 0; 37 forever #(CYC/2) sys_clk_p = ~sys_clk_p; 38 end 39 40 initial begin 41 sys_clk_n = 1; 42 forever #(CYC/2) sys_clk_n = ~sys_clk_n; 43 end 44 45 localparam IDLE = 14'b00_0000_0000_0001, 46 START = 14'b00_0000_0000_0010, 47 WRI_CTRL = 14'b00_0000_0000_0100, 48 ACK1 = 14'b00_0000_0000_1000, 49 ADDR = 14'b00_0000_0001_0000, 50 ACK2 = 14'b00_0000_0010_0000, 51 WRI_DATA = 14'b00_0000_0100_0000, 52 ACK3 = 14'b00_0000_1000_0000, 53 RE_START = 14'b00_0001_0000_0000, 54 RD_CTRL = 14'b00_0010_0000_0000, 55 ACK4 = 14'b00_0100_0000_0000, 56 RD_DATA = 14'b00_1000_0000_0000, 57 NACK = 14'b01_0000_0000_0000, 58 STOP = 14'b10_0000_0000_0000; 59 60 initial begin 61 rst_n = 1; 62 #1; 63 rst_n = 0; 64 #(CYC*RST_TIME); 65 rst_n = 1; 66 end 67 68 initial begin 69 #1; 70 key = 2'b11; 71 #(CYC*RST_TIME); 72 #(CYC*10); 73 74 press_key_wr; 75 #120_000; 76 press_key_rd; 77 #80_000; 78 $stop; 79 end 80 81 //构造响应条件 82 always@(*)begin 83 if(state_c == ACK1 || state_c == ACK2 || state_c == ACK3 || state_c == ACK4) 84 sda_tb_out <= 0; 85 else 86 sda_tb_out <= 1; 87 end 88 89 task press_key_wr; 90 begin 91 repeat(20)begin//模拟抖动过程 92 myrand = {$random}%400; 93 #myrand key[1] = ~key[1]; 94 end 95 key[1] = 0; 96 #3000; 97 repeat(20)begin 98 myrand = {$random}%400; 99 #myrand key[1] = ~key[1];100 end101 key[1] = 1;102 #3000;103 end104 endtask105 106 task press_key_rd;107 begin108 repeat(20)begin//模拟抖动过程109 myrand = {$random}%400;110 #myrand key[0] = ~key[0];111 end112 key[0] = 0;113 #3000;114 repeat(20)begin115 myrand = {$random}%400;116 #myrand key[0] = ~key[0];117 end118 key[0] = 1;119 #3000;120 end121 endtask122 123 endmodule[url=][/url]
我的开发板使用差分晶振作为系统时钟,在测试文件中也要以差分信号的形式给出时钟。与单端时钟唯一的区别在于给出两个初始值不同周期相同的时钟信号。其中为了找到响应位置,引入状态编码,并在需要给出响应的时刻拉低总线。运行行为仿真: 整体结构:
写操作:
读操作:
读写操作过程中状态转移、比特计数器、sda 、scl这些核心信号数据正常,仿真通过。实际上这是设计过程中遇到些小问题,修改代码后的结果。下一步要在线调试了,这里是本篇博文最后一个重点要说明的内容。以往我会使用添加属性的方式(*mark_debug = "true"*)标志要观察的信号,再在综合后使用debug设置向导引入调试IP核。经过实验发现调试核的引入是通过添加约束的方式实现的,而且当要观察别的信号时该约束部分必须改动否则报错,所以这里使用IP核例化调试探测流程,直接在IP catalog中生成ILA IP核。这里有一个小技巧:生成IP核是只使用一个探针信号,并把位宽设置的较大,且使用OOC方式。在例化IP核后使用这个信号的不同位宽部分连接需要在线观察的信号。这样可以避免在反复综合、布局布线的过程中重新编译ILA IP核部分,节约时间。 打开硬件管理器,下载bit流后自动打开调试界面。设置触发条件观察波形,这里可以很方便的利用状态信号的不同状态设置触发条件。 写操作:
读操作:
写入数据定义为8'h32,读取bit依次是0011_0010,即为32,说明正确将写入数据读出。大家可以在本次实验基础上扩展,比如实现页写模式,或是使用串口来发送读写指令并读回数据等。经过本次博文,掌握了IIC协议的四段式状态机实现方式,双向端口的三态门结构及仿真方法,并能够灵活运用ILA IP核进行在线调试。 分享来源:https://www.cnblogs.com/moluoqishi/p/7434902.html 欢迎交流Q2122516396 |