公海赌船网址分别出待操作寄存器后基于操作计数器解析出读写指令,大家一般先规划接口模块或IP核顶层文件

  本博文设计思想采纳明德扬至简设计法。从前都是经过一些完好无损的案例来享受计划经验,而这篇作品以需要安排四个寄存器的场景讲述焦点设计技术。

  本博文设计思想拔取明德扬至简设计法。此前都是通过有些完好的案例来分享计划经验,而那篇著作以需要配置五个寄存器的面貌讲述核心设计技术。

  案例采用明德扬设计思想形成。IIC协议是分外常用的接口协议,在电子类职位招聘要求中平常出现它的身影。关于IIC协议这里只做简单介绍,详细信息请自行百度或查看有关Datasheet,网上资料分外多。该篇博文首要讲怎么利用verilog来讲述IIC协议,以读写EEPROM为例辅导大家探听下明德扬四段式状态机规范和优势,此外还有一些温馨在设计过程中总括的经验技术。

  在规划案例时意识,平常会安排相比较复杂的IP核或驱动一些接口进而操作外设。此时,为了让外设或IP核正常办事,需要对其内部四个寄存器举行恰当配置来保证在所需形式下正规办事。大家一般先规划接口模块或IP核顶层文件,之后经过操纵模块遵照先后顺序自动提交所需指令,如读写等(下边的叙说以只有读写指令为例)。接口模块或IP核顶层模块收到指令后形成相应的操作。

  在规划案例时意识,平日会部署相比复杂的IP核或驱动一些接口进而操作外设。此时,为了让外设或IP核正常干活,需要对其中间六个寄存器举办适当配置来保证在所需形式下正规办事。大家一般先规划接口模块或IP核顶层文件,之后经过决定模块按照先后顺序自动提交所需指令,如读写等(下面的叙说以只有读写指令为例)。接口模块或IP核顶层模块收到指令后完成相应的操作。

  IIC合计时序格式以Datasheet中时序图的花样供大家参考。IIC协议有一条时钟线SCL和一条双线数据总线SDA。SDA在SCL高电通常维持安静,否则就是起初或收尾条件。

  第一个问题:如何落实四个寄存器且每个寄存器多少个指令的自动化配置?

  第一个问题:如何贯彻六个寄存器且每个寄存器四个指令的自动化配置?

公海赌船网址 1

  大家得以在支配模块中确立一个“配置表”,把读写指令以及相应的地点和待写入数据保存其中,然后通过计数器举办指令扫描。这里需要两级计数器,第一级计数一个寄存器的指令数,第二级计数器记录已经操作过的寄存器个数。配置表以always组合逻辑中case语句块情势提交,使用寄存器计数值分别不同寄存器。区分出待操作寄存器后基于操作计数器解析出读写指令。

  我们得以在支配模块中树立一个“配置表”,把读写指令以及对应的地址和待写入数据保存其中,然后通过计数器进行指令扫描。那里需要两级计数器,第超级计数一个寄存器的指令数,第二级计数器记录已经操作过的寄存器个数。配置表以always组合逻辑中case语句块格局提交,使用寄存器计数值分别不同寄存器。区分出待操作寄存器后按照操作计数器解析出读写指令。

公海赌船网址 2

  第二个问题:当控制模块给出指令时,接口模块或IP核一定能有时光响应么?

  第二个问题:当控制模块给出指令时,接口模块或IP核一定能有时光响应么?

  发送端发送1byte数码后,接收端在下一个SCL高电平期间拉低总线表示应答,即接收数据成功。

  那是我们设计时索要三思的题目:如何才能担保交到的吩咐一定会被下一模块有效地响应?为了兑现这一目标,可以在决定(配置)模块和时序接口模块或IP核顶层模块之间放置一个接口衔接模块,结构如下:

  这是大家规划时索要三思的问题:如何才能保证交到的命令一定会被下一模块有效地响应?为了兑现这一目标,可以在控制(配置)模块和时序接口模块或IP核顶层模块之间放置一个接口衔接模块,结构如下:

公海赌船网址 3

公海赌船网址 4

公海赌船网址 5

  以下分别是器件地址为1字节的EEPROM的单字节写和读操作,需要留意的是DEVICE
ADDRESS段中前四位稳定是4’b1010,后三位按照EEPROM地址信号电平决定(这一次实验地点信号引脚均接地,因而后三位为000),最后一位是读写标志位,低电平写。

  遵照上述需求定义衔接模块效率:在下游模块准备好后才让上游模块ctrl给出下一发令,否则等待。并形成读出的灵光数据送到上游模块的任务。很粗略,下游模块输出给控制模块一个信号rdy,当它为高电平常代表当前不曾指令或者上一发令已响应完成。控制模块中指令计数器的原有计数条件和rdy==1条件逻辑与就到位了上述效能。这里需要特别注意的是:rdy信号必须以整合逻辑格局提交,否则由于rdy信号晚一拍输出,上游模块会出现误认处境。主题代码如下:

公海赌船网址,  依照上述需求定义衔接模块功效:在下游模块准备好后才让上游模块ctrl给出下一发令,否则等待。并做到读出的管事数据送到上游模块的天职。很粗略,下游模块输出给控制模块一个信号rdy,当它为高电通常表示当前从不指令或者上一发令已响应完成。控制模块中指令计数器的原有计数条件和rdy==1条件逻辑与就完事了上述效率。这里需要特别注意的是:rdy信号必须以结合逻辑情势提交,否则由于rdy信号晚一拍输出,上游模块会现身误认情形。主题代码如下:

公海赌船网址 6

支配模块中:

决定模块中:

公海赌船网址 7

 1     //读写操作计数器    
 2      always  @(posedge clk or negedge rst_n)begin
 3         if(rst_n==1'b0)begin
 4             rw_cnt <= 0;
 5         end
 6         else if(add_rw_cnt) begin
 7             if(end_rw_cnt)
 8                 rw_cnt <= 0;
 9             else
10                 rw_cnt <= rw_cnt + 1;
11         end
12     end
13 
14     assign  add_rw_cnt = con_flag && rdy;
15     assign  end_rw_cnt = add_rw_cnt && rw_cnt==RW_NUM-1;    
16 
17    //写使能 wr_flag和rd_flag由配置表给出
18     always  @(posedge clk or negedge rst_n)begin
19         if(rst_n==1'b0)begin
20             wr_en <= 1'b0;
21         end
22         else if(add_rw_cnt && rw_cnt==0 && wr_flag)begin
23             wr_en <= 1'b1;
24         end
25         else begin
26             wr_en <= 1'b0;
27         end
28     end
29 
30    //读使能
31     always  @(posedge clk or negedge rst_n)begin
32         if(rst_n==1'b0)begin
33             rd_en <= 1'b0;
34         end
35         else if(add_rw_cnt && rw_cnt==1 && rd_flag)begin
36             rd_en <= 1'b1;
37         end
38         else begin
39             rd_en <= 1'b0;
40         end
41     end
 1     //读写操作计数器    
 2      always  @(posedge clk or negedge rst_n)begin
 3         if(rst_n==1'b0)begin
 4             rw_cnt <= 0;
 5         end
 6         else if(add_rw_cnt) begin
 7             if(end_rw_cnt)
 8                 rw_cnt <= 0;
 9             else
10                 rw_cnt <= rw_cnt + 1;
11         end
12     end
13 
14     assign  add_rw_cnt = con_flag && rdy;
15     assign  end_rw_cnt = add_rw_cnt && rw_cnt==RW_NUM-1;    
16 
17    //写使能 wr_flag和rd_flag由配置表给出
18     always  @(posedge clk or negedge rst_n)begin
19         if(rst_n==1'b0)begin
20             wr_en <= 1'b0;
21         end
22         else if(add_rw_cnt && rw_cnt==0 && wr_flag)begin
23             wr_en <= 1'b1;
24         end
25         else begin
26             wr_en <= 1'b0;
27         end
28     end
29 
30    //读使能
31     always  @(posedge clk or negedge rst_n)begin
32         if(rst_n==1'b0)begin
33             rd_en <= 1'b0;
34         end
35         else if(add_rw_cnt && rw_cnt==1 && rd_flag)begin
36             rd_en <= 1'b1;
37         end
38         else begin
39             rd_en <= 1'b0;
40         end
41     end

  好了,有了上述五张时序图我们便知道要干什么了,就是落实这一个时序嘛!对于这种串行时序,时间有先后且操作差别较大的要用状态机实现。每连串型操作定义在一个处境中,状态之中需要几个操作则配合计数器实现。全部设计思路如下:先构造时钟信号SCL,这里频率定义为200KHz,而系统时钟有频率为200MHz差分晶振提供,显著需要用到分频计数器。由于SCL高电平期间数据要保持平静,所以我们在分频计数器计数到1/4处拉高SCL,3/4处拉低SCL,那样做的补益是在终结计数时正好处在SCL低电平中间点,此处作为数据变化的随时再适合可是。

 衔接模块中:

 衔接模块中:

公海赌船网址 8

 1     //空闲输出
 2     always@(*)begin
 3         if(rd_en || wr_en || rd_com || wr_com)
 4             rdy <= 0;
 5         else 
 6             rdy <= 1;
 7     end
 8     
 9     //命令区间标志位 表示正在响应该命令  状态机实现时序接口模块情况
10     always@(posedge clk or negedge rst_n)begin
11         if(!rst_n)
12             wr_com <= 0;
13         else if(wr_en)
14             wr_com <= 1;
15         else if(wr_com && stop2idle)
16             wr_com <= 0;
17     end    
18     
19     always@(posedge clk or negedge rst_n)begin
20         if(!rst_n)
21             rd_com <= 0;
22         else if(rd_en)
23             rd_com <= 1;
24         else if(rd_com && stop2idle)
25             rd_com <= 0;
26     end    
27     
28     //地址更新
29     always@(posedge clk or negedge rst_n)begin
30         if(!rst_n)
31             addr_tmp <= 0;
32         else if(wr_en || rd_en)
33             addr_tmp <= addr;
34     end
 1     //空闲输出
 2     always@(*)begin
 3         if(rd_en || wr_en || rd_com || wr_com)
 4             rdy <= 0;
 5         else 
 6             rdy <= 1;
 7     end
 8     
 9     //命令区间标志位 表示正在响应该命令  状态机实现时序接口模块情况
10     always@(posedge clk or negedge rst_n)begin
11         if(!rst_n)
12             wr_com <= 0;
13         else if(wr_en)
14             wr_com <= 1;
15         else if(wr_com && stop2idle)
16             wr_com <= 0;
17     end    
18     
19     always@(posedge clk or negedge rst_n)begin
20         if(!rst_n)
21             rd_com <= 0;
22         else if(rd_en)
23             rd_com <= 1;
24         else if(rd_com && stop2idle)
25             rd_com <= 0;
26     end    
27     
28     //地址更新
29     always@(posedge clk or negedge rst_n)begin
30         if(!rst_n)
31             addr_tmp <= 0;
32         else if(wr_en || rd_en)
33             addr_tmp <= addr;
34     end

  有了时钟信号,下一步就是通过不同的事态实现SDA信号线满意上述时序要求。大家先来划分状态(实际上时序图中都给我们标识好了),很强烈综合考虑写和读二种时序,状态应定义为:起先状态、开始、写控制、响应1、写地址、响应2、写多少、响应3、重新起头、读控制、响应4、读数据、不响应、为止。这里写控制和读控制即是DEVICE
ADDRESS阶段,唯一的区别在于读写标志位不同。能来看以上气象划分包括写流程分支和读流程分支,可以遵照指令用一个表明位加以区别。

   到此,寄存器自动化配置中五个举足轻重问题一锤定音解决。本文是我在统筹录像头图像采集和以太网六个案例过程中总计所得。本人觉得这种设计思想分外具有通用性,并不仅局限于这五个案例,由此单独提议,以备今后想起和重用。

   到此,寄存器自动化配置中五个至关首要问题决定解决。本文是自个儿在设计视频头图像采集和以太网两个案例过程中总计所得。本人觉得这种规划思想相当具有通用性,并不仅仅局限于这三个案例,由此单独提议,以备今后回忆和选定。

  定义状态参数并利用独热码举办编码:

 

 

公海赌船网址 9

  IIC协议中老是SCL高电平期间就是两遍操作,因而为了让各类意况都有整数个SCL周期(完整分频计数周期),对各种情状进行比特计数,写控制、地址、写多少、读控制、读数据阶段计数周期是8,其他为1。其它为力保代码的“健壮性”,也就是就是发送1byte数码后没有响应也不一定挂死在守候响应阶段,设定在每一回等待响应阶段若响应才进去下一操作,否则回到初步状态。因此取得气象转移图(只囊括重要流程,转移条件及未响应回到IDLE状态未画出):

公海赌船网址 10

  至此所有的筹划工作都已经完成,接下去就是遵照上述分析编写代码。在编写代码在此之前大概介绍明德扬四段式状态机的设计思想和代码规范:四段式状态机实质上是在三段式状态机基础上单独指出情形转移条件定义的构造。目标是让设计者一个年华段只注意于一件业务,也就是说当设计状态机的时候先把状态转移流程规定,而标准用不同的信号名代替,等情形转移流程规定后再定义转移条件。这样做的另一个利益是用作条件的信号名可以很有利的在继续时序逻辑中行使。其中用于代替条件的信号名要听从类似如下格式:<state_c>2<state_n>。<>处用状态名代替。

完整代码如下:

  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@(*)begin
102         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         endcase
110     end
111     
112     //---------------------iic时序四段式状态机部分-------------------------
113     
114     //时序逻辑描述状态转移
115     always@(posedge clk or negedge rst_n)begin
116         if(!rst_n)
117             state_c <= IDLE;
118         else 
119             state_c <= state_n;
120     end
121     
122     //组合逻辑描述状态转移条件
123     always@(*)begin
124         case(state_c)
125             IDLE:begin       //空闲状态
126                 if(idle2start)
127                     state_n <= START;
128                 else 
129                     state_n <= state_c;
130             end
131             
132             START:begin    //产生开始条件 即SCL高电平期间SDA拉低
133                 if(start2wri_ctrl)
134                     state_n <= WRI_CTRL;
135                 else 
136                     state_n <= state_c;
137             end
138             
139             WRI_CTRL:begin  //写器件地址和写标志位
140                 if(wri_ctrl2ack1)
141                     state_n <= ACK1;
142                 else 
143                     state_n <= state_c;
144             end
145             
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             end
154             
155             ADDR:begin  //写存储单元地址
156                 if(addr2ack2)
157                     state_n <= ACK2;
158                 else 
159                     state_n <= state_c;
160             end
161             
162             ACK2:begin   //等待响应2
163                 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             end
172             
173             WRI_DATA:begin   //写数据 8bit
174                 if(wri_data2ack3)
175                     state_n <= ACK3;
176                 else 
177                     state_n <= state_c;
178             end
179             
180             ACK3:begin   //等待响应3
181                 if(ack32stop)
182                     state_n <= STOP;
183                 else if(ack2idle)
184                     state_n <= IDLE;
185                 else 
186                     state_n <= state_c;
187             end
188             
189             RE_START:begin  //若为读操作在响应2后再次构造开始条件
190                 if(re_start2rd_ctrl)
191                     state_n <= RD_CTRL;
192                 else 
193                     state_n <= state_c;
194             end
195             
196             RD_CTRL:begin   //写入存储单元地址和读标志位
197                 if(rd_ctrl2ack4)
198                     state_n <= ACK4;
199                 else 
200                     state_n <= state_c;
201             end
202             
203             ACK4:begin  //等待响应4
204                 if(ack42rd_data)
205                     state_n <= RD_DATA;
206                 else if(ack2idle)
207                     state_n <= IDLE;
208                 else 
209                     state_n <= state_c;
210             end
211             
212             RD_DATA:begin  //读数据 8bit
213                 if(rd_data2nack)
214                     state_n <= NACK;
215                 else 
216                     state_n <= state_c;
217             end
218             
219             NACK:begin  //不响应 无操作即可
220                 if(nack2stop)
221                     state_n <= STOP;
222                 else 
223                     state_n <= state_c;
224             end
225             
226             STOP:begin  //构造停止条件
227                 if(stop2idle)
228                     state_n <= IDLE;
229                 else 
230                     state_n <= state_c;
231             end
232             
233             default:
234                 state_n <= IDLE;
235         endcase
236     end
237     
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)begin
259         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     end
266     
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)begin
270         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     end
277     
278     //时序逻辑描述状态输出
279     
280     //scl时钟信号
281     always@(posedge clk or negedge rst_n)begin
282         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     end
289     
290     //找到scl高低电平中间点
291     always@(posedge clk or negedge rst_n)begin
292         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     end
299     
300     //三态门输出使能
301     always@(posedge clk or negedge rst_n)begin
302         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     end
309     
310     //数据总线输出寄存器
311     always@(posedge clk or negedge rst_n)begin
312         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     end
337     
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)begin
345         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     end
350     
351     //读数据有效指示
352     always@(posedge clk or negedge rst_n)begin
353         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     end
360     
361     //读数据输出
362     always@(posedge clk or negedge rst_n)begin
363         if(!rst_n)
364             rd_data <= 0;
365         else 
366             rd_data <= rd_buf;
367     end
368     
369     //读标志位
370     always@(posedge clk or negedge rst_n)begin
371         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     end
378     
379     //总线忙信号
380     always@(posedge clk or negedge rst_n)begin
381         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     end
388     
389 endmodule

   可以看到状态机部分逐个分为:时序逻辑描述状态转移,组合逻辑描述状态转移条件,连续赋值定义状态转移条件以及时序逻辑描述状态相关输出。并且至始至终使用state_c和state_n多少个信号表示现态和次态,使逻辑更是显明。接口部分为了有利于仿真和调节,参与状态信号state_c。这里涉及到一个双向端口sda,用五个信号:输出使能sda_en,输出寄存器sda_reg和输入缓存sda_in表示。在顶层模块中使用那五个信号通过三态门的花样提交,关于三态门的应用细节和虚伪艺术稍后讲述。

  先规划其他模块和顶层模块,之后对顶层模块举办虚伪测试,这时观望各种模块中信号数值分析排查问题。有了时序接口模块,在不利无误情形下,已经可以实现对EEPROM的读写操作。现在显著规划目标,咱们要贯彻EEPROM的一字节数据读写,因而得以经过按键发送指令向EEPROM中某地址中写入随便一个数据,之后用另一个按键发送读指令将刚写入地址中数量读出的艺术注解读写操作是否健康办事。编写控制模块(控制模块仅实现IIC总线空闲时才响应操作,实际上用按键方式犹豫时间距离较长,不会并发三个指令抢占总线的气象,这里设计控制模块是为着适应其他场合或效益扩展用途)

 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_wr
12     );
13     
14     wire ready;
15     
16     assign ready = !iic_busy;
17     
18     //写命令
19     always@(posedge clk or negedge rst_n)begin
20         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     end
27     
28     //读命令
29     always@(posedge clk or negedge rst_n)begin
30         if(!rst_n)
31             com_rd <= 0;
32         else if(local_rd && ready)
33             com_rd <= 1;
34         else 
35             com_rd <= 0;
36     end
37     
38     
39 endmodule

   剩下只需参预按键消抖模块,并把按键消抖模块,控制模块还有时序接口模块都例化在顶层文件中即可。按键消抖模块在前头的博文中有描述,这里运用计数器配合情状标志位的点子贯彻。需要表明的是五个按键使用一个按键消抖模块的宏图格局:只需将信号位宽定义为可变参数。

 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)begin
23         if(rst_n==1'b0)
24             cnt <= 0;
25         else if(add_cnt)begin
26             if(end_cnt)
27                 cnt <= 0;
28             else
29                 cnt <= cnt + 1'b1;
30         end
31         else
32             cnt <= 0;
33     end
34     //按下状态才计数,松手清零
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)begin
41             flag <= 1'b0;
42         end
43         else if(end_cnt)begin
44             flag <= 1'b1;
45         end
46         else if(key_in_ff1 == 2'b11)begin//松手重新清零
47             flag <= 1'b0;
48         end
49     end
50     
51     //同步处理
52     always  @(posedge clk or negedge rst_n)begin 
53         if(rst_n==1'b0)begin
54             key_in_ff0 <= 0;
55             key_in_ff1 <= 0;
56         end
57         else begin
58             key_in_ff0 <= key_in    ;
59             key_in_ff1 <= key_in_ff0;
60         end
61     end
62 
63     //输出有效
64     always  @(posedge clk or negedge rst_n)begin 
65         if(rst_n==1'b0)begin
66             key_vld <= 0;
67         end
68         else if(end_cnt)begin
69             key_vld <= ~key_in_ff1;
70         end
71         else begin
72             key_vld <= 0;
73         end
74     end
75     
76 endmodule

 顶层模块例化子模块:

  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

  看一下软件分析出的原理图结构(ILA
IP核是然后加上的):

公海赌船网址 11

  此处详细表明下双向端口使用:顶层模块中确立三态门结构,在出口使能管用时作为出口端口,无效是显现高阻态,此时同日而语输入端口,由sda_in信号读取数值。那双向端口咋样仿真呢?很简短,在测试文件中也协会一个三态门结构,而输出使能信号为统筹中输出使能信号的相反值,这样在统筹中该端口彰显高阻态时,正好在测试文件中相应端口作为出口的等级。可以小心到自我在顶层模块中进入了六个虚假接口:state_c和sda_en,方便在测试文件中找到给出响应的职务。测试文件如下:

  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         end
101         key[1] = 1;
102         #3000;
103     end
104     endtask
105     
106     task press_key_rd;
107     begin
108         repeat(20)begin//模拟抖动过程
109             myrand = {$random}%400;
110             #myrand key[0] = ~key[0];
111         end
112         key[0] = 0;
113         #3000;
114         repeat(20)begin
115             myrand = {$random}%400;
116             #myrand key[0] = ~key[0];
117         end
118         key[0] = 1;
119         #3000;
120     end
121     endtask
122     
123 endmodule

  我的开发板使用差分晶振作为系统时钟,在测试文件中也要以差分信号的款式提交时钟。与单端时钟唯一的分别在于付出多少个开首值不同周期相同的时钟信号。其中为了找到响应位置,引入状态编码,并在急需付出响应的每日拉低总线。运行行为仿真:

完全布局:

公海赌船网址 12

写操作:

公海赌船网址 13

读操作:

公海赌船网址 14

  读写操作过程中状态转移、比特计数器、sda

、scl那个基本信号数据正常,仿真通过。实际上这是计划性过程中相见些小问题,修改代码后的结果。下一步要在线调试了,那里是本篇博文最终一个最重要要注解的情节。以往本人会动用添加属性的艺术(*mark_debug

“true”*)标志要观望的信号,再在综合后拔取debug设置指引引入调试IP核。经过试验发现调试核的引入是由此丰裕约束的措施贯彻的,而且当要观望其余信号时该约束部分必须变更否则报错,所以这边运用IP核例化调试探测流程,直接在IP
catalog中生成ILA
IP核。这里有一个小技巧:生成IP核是只行使一个探针信号,并把位宽装置的较大,且使用OOC形式。在例化IP核后采用那么些信号的不同位宽部分连接需要在线寓目的信号。这样可以制止在屡次综合、布局布线的经过中重新编译ILA
IP核部分,节约时间。

  打开硬件管理器,下载bit流后自行打开调试界面。设置触发条件观望波形,这里能够很有益的拔取境况信号的不同情况设置触发条件。

写操作:

公海赌船网址 15

 读操作:公海赌船网址 16

公海赌船网址 17

  写入数据定义为8’h32,读取bit依次是0011_0010,即为32,表达正确将写入数据读出。大家可以在本次试验基础上扩充,比如实现页写形式,或是使用串口来发送读写指令并读回数据等。经过此次博文,领悟了IIC协议的四段式状态机实现模式,双向端口的三态门结构及假冒伪劣艺术,并可以灵活运用ILA
IP核举办在线调试。希望大家和自己同样取得广大。欢迎交换~

相关文章