• FPGA零基础学习:IIC协议驱动设计


    IIC 即 Inter-Integrated Circuit(集成电路总线),这种总线类型是由飞利浦半导体公司在八十年代初设计出来的一种简单、双向、二线制、同步串行总线,主要是用来连接整体电路(ICS) ,IIC是一种多向控制总线,也就是说多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实时数据传输的控制源。这种方式简化了信号传输总线接口。

    I²C 总线是一种串行数据总线,只有二根信号线,一根是双向的数据线 SDA,另一根是时钟线 SCL,两条线可以挂多个设备。 IIC 设备(绝大多数)里有个固化的地址,只有在两条线上传输的值等于 IIC 设备的固化地址时,其才会作出响应。通常我们为了方便把 IIC 设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。

    A段为空闲段,在不进行数据传输时,SCL 和 SDA 保持为高电平。

    B段为起始段(START CONDITION),在进行任何数据传输前,都应该先有一个起始段。当 SCL 为高电平期间,SDA有一个从高到低的变化视为起始段。

    C段为结束段(STOP CONDITION),在数据传输结束后,都应该有一个结束段。当SCL为高电平期间,SDA有一个从低到高的变化视为结束段。

    D段分为两段数据有效段(ADDRESS OR ACKNOWLEDGE)和数据修改段(DATA ALLOWED TO CHANGE)。在传输数据时,在 SCL 为高电平期间,SDA 不能够发生改变,视为数据有效段;在传输数据时,在 SCL 为低电平期间,SDA 可以发生改变(修改为下一个要发送的bit),视为数据修改段。

    IIC的传输总结为:空闲时,SCL 和 SDA 都是高电平;发送信息前,首先发送起始段(SCL为高时,SDA有一个从高到低的变化);在发送信息时,每一个 SCL 脉冲,发送或者接收1bit,该bit在SCL为低电平时发生改变,在SCL为高电平时,保持稳定;发送信息完成后,发送结束段(SCL 为高时,SDA 有一个从低到高的变化)。

    SDA的数据线是双向传输的。在读取时,SDA 从从设备到主设备;在写入时,SDA从主设备到从设备。

    在 IIC 传输时,为了保证通信的成功。发送方每发送8 bit 数据后,要求接收方回复一个应答位。

    应答位为低电平有效。

    EEPROM(24LC64)简介

    24LC64是容量为8K*8bit(64Kbit)通信接口为IIC的EEPROM。EEPROM拥有掉电不丢失的功能,经常用来保存一些配置信息等等。这个器件可以一次进行最多32字节的页写入和一次进行整片读出的功能。

     

     

     

    A0、A1、A2为用户可配置的芯片选择端。该管脚一般都连接电源或者地,在通信时,24LC64作为从机,主机在发送的命令中存在地址号,当地址号与A0、A1、A2管脚的电平相同时,该芯片视为被选中。

    SDA和SCL为IIC协议中的时钟线和数据线。

    WP为写保护端,当连接高电平时,该芯片只能够被读,不能够写入;当连接低电平或者空置时,该芯片可以被读写。

    24LC64的写入方式有两种:单字节写入和多字节写入。

    单字节写入时序图:

    在控制字节中,1010为EEPROM的标号;A2、A1、A0为主机发送的地址号,接收设备接收到此信息后,会与本身的A2、A1、A0相对比,相同时,视为被选中;最后1bit,0表示写入、1表示读出。

    在高字节地址中,高三位地址无效。24LC64的地址范围为8K,地址位13位即可。13位地址分为高五低八,在IIC中规定每次发送8bit,所以高五的前面必须发送任意3bit。

    多字节写入时序图:

    DATA BYTE 0被写入到指定的地址中,后续的数据写入到指定地址的后续地址中。EEPROM中每32个字节分为一块,写入时不能跨块写入。

    24LC64的读出方式有三种:当前地址单字节读、任意地址单字节读和任意地址多字节读。

    当前地址单字节读的时序图如下:

    当主机接收24LC64的数据后,不发送ACK,而是发送高电平。首先写入指定地址,然后按照当前地址读即可。任意地址多字节读就是在任意地址单字节读的时序中,接收到从机发送的数据后,不要发送NO ACK而是发送ACK,此时从机就会继续发送后续地址的数据,直到读取的长度符合设计要求,然后发送NO ACK以及STOP结束传输。

    根据时序参数,决定将IIC的速率定为50KHz。发送时,数据改变在SCL的低电平的正中间;读取时,在SCL高电平的正中间进行读取。

    IIC一般用在配置或者读取少量的信息,对于速率来说要求相对较低。

    硬件介绍

    在开发板上,搭载了一个24LC64。

    电路原理图如下:

    A0、A1、A2都被接地,主机再发送控制字节时,需要将此三位发送低电平。

    WP管脚被接地,24LC64的整个片子都可以被读写。

    双向IO介绍

    在IIC协议中,SDA作为数据线使用,每一个SCL的脉冲,传递一bit的数据。在读取时,SDA由24LC64发送,FPGA接收;在写入时,SDA由FPGA发送,24LC64接收。所以IIC协议只能实现半双工通信。

    在硬件电路中,一般是通过三态门来进行控制双向IO。

     

    此电路在FPGA的IO中也是存在的。下面来分析具体的工作原理。

    三态门有三个端子,分别是输入端(obuf),输出端(SDA)和控制端(out_en)。工作方式为:当out_en有效时,obuf的值就可以输出到SDA;当out_en无效时,obuf的值不能输出到SDA,那么三态门就会输出高阻态。

    高阻态,字节理解为阻值特别大,也可以理解为断开。高阻态与任何电平值相连接,所连接的线上的电平值都是和对方保持一致。在此可以延伸一下:所有的端口都是输出,包括输入端口在内,也会对外呈现出一种状态,所以输入端口对外呈现的状态就是高阻态。对于双向IO来说,输出时,正常输出即可;输入时,就要呈现为高阻态。

    设计要求

    设计任意地址的单字节读写控制器。大多数是配置或者读取少量的信息,对于多字节的读写,可以采用多次单字节的读写完成。

    设计分析

    24LC64的寻址范围为8K,地址bit共计13bit,所以需要高5bit和低8bit。在有些兼容IIC协议接口的器件中,寻址范围较少,8bit足够表示,所以在设计时,设计出适合两种寻址方式的驱动。

    SCL的实现采用计数器控制,根据规定好的频率,在合适的地方进行拉高或者拉低。由于SDA的变化都发生在SCL为高或者为低的中间,所以还需要产生对应的脉冲。

    SDA根据协议和对应的脉冲进行发送对应的协议段。

    架构设计和信号说明

    本模块命名为iic_drive。

    暂不连接的端口,在后续测试时,会有其他模块来控制或者读取。

    iic_drive设计实现

    iic_scl采用计数器的方式实现。外部的时钟为50MHz,iic_scl为100KHz,所以计数器需要记录500个时钟周期。

    计数器由cnt_en控制。cnt_en有效时,cnt从0到500不断循环;当cnt_en无效时,cnt保持等于0。

    iic_scl在cnt等于0至249时,保持高电平;在250至499时,保持低电平。cnt等于125时,正好是iic_scl高电平的中间,用脉冲flag_high表示;cnt等于375时,正好是iic_scl低电平的中间,用脉冲flag_low表示。

    其他信号采用状态机作为输出。out_en为三态门控制信号,sda_obuf为三态门输出端,drive_cnt为发送或者接收的计数器,temp为发送或者接收的中间寄存器。

    状态转移图如下:

    设计代码为:

    1. module iic_drive (
    2. input wire clk,
    3. input wire rst_n,
    4. input wire start_flag,
    5. input wire [6:0] slave_addr,
    6. input wire wren,
    7. input wire addr_sel,
    8. input wire [15:0] addr,
    9. input wire [7:0] wdata,
    10. output reg iic_scl,
    11. inout wire iic_sda,
    12. output reg iic_done,
    13. output reg [7:0] rdata
    14. );
    15. parameter FREQ_clk = 50_000_000;
    16. parameter FREQ_iic = 100_000;
    17. localparam T_SCL = FREQ_clk/FREQ_iic;
    18. localparam IDLE = 16'b0000_0000_0000_0001;
    19. localparam START = 16'b0000_0000_0000_0010;
    20. localparam CTRL = 16'b0000_0000_0000_0100;
    21. localparam CTRL_ACK = 16'b0000_0000_0000_1000;
    22. localparam HADDR = 16'b0000_0000_0001_0000;
    23. localparam HADDR_ACK = 16'b0000_0000_0010_0000;
    24. localparam LADDR = 16'b0000_0000_0100_0000;
    25. localparam LADDR_ACK = 16'b0000_0000_1000_0000;
    26. localparam WR = 16'b0000_0001_0000_0000;
    27. localparam WR_ACK = 16'b0000_0010_0000_0000;
    28. localparam RD_START = 16'b0000_0100_0000_0000;
    29. localparam RD_CTRL = 16'b0000_1000_0000_0000;
    30. localparam RD_CTRL_ACK = 16'b0001_0000_0000_0000;
    31. localparam RD = 16'b0010_0000_0000_0000;
    32. localparam NO_ACK = 16'b0100_0000_0000_0000;
    33. localparam STOP = 16'b1000_0000_0000_0000;
    34. reg [15:0] c_state;
    35. reg [15:0] n_state;
    36. reg [31:0] cnt;
    37. reg cnt_en;
    38. reg flag_high;
    39. reg flag_low;
    40. reg out_en;
    41. reg sda_obuf;
    42. reg [3:0] drive_cnt;
    43. reg [7:0] temp;
    44. assign iic_sda = (out_en == 1'b1) ? sda_obuf : 1'bz;
    45. always @ (posedge clk, negedge rst_n) begin
    46. if (rst_n == 1'b0)
    47. cnt <= 32'd0;
    48. else
    49. if (cnt < T_SCL - 1'b1 && cnt_en == 1'b1)
    50. cnt <= cnt + 1'b1;
    51. else
    52. cnt <= 32'd0;
    53. end
    54. always @ (posedge clk, negedge rst_n) begin
    55. if (rst_n == 1'b0)
    56. iic_scl <= 1'b1;
    57. else
    58. if (cnt < T_SCL/2)
    59. iic_scl <= 1'b1;
    60. else
    61. iic_scl <= 1'b0;
    62. end
    63. always @ (posedge clk, negedge rst_n) begin
    64. if (rst_n == 1'b0)
    65. flag_high <= 1'b0;
    66. else
    67. if (cnt == T_SCL/4 - 1'b1)
    68. flag_high <= 1'b1;
    69. else
    70. flag_high <= 1'b0;
    71. end
    72. always @ (posedge clk, negedge rst_n) begin
    73. if (rst_n == 1'b0)
    74. flag_low <= 1'b0;
    75. else
    76. if (cnt == (T_SCL * 3)/4 - 1'b1)
    77. flag_low <= 1'b1;
    78. else
    79. flag_low <= 1'b0;
    80. end
    81. always @ (posedge clk, negedge rst_n) begin
    82. if (rst_n == 1'b0)
    83. c_state <= IDLE;
    84. else
    85. c_state <= n_state;
    86. end
    87. always @ * begin
    88. case (c_state)
    89. IDLE : begin
    90. if (start_flag == 1'b1)
    91. n_state = START;
    92. else
    93. n_state = IDLE;
    94. end
    95. START : begin
    96. if (flag_high == 1'b1)
    97. n_state = CTRL;
    98. else
    99. n_state = START;
    100. end
    101. CTRL : begin
    102. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    103. n_state = CTRL_ACK;
    104. else
    105. n_state = CTRL;
    106. end
    107. CTRL_ACK : begin
    108. if (flag_high == 1'b1)
    109. if (iic_sda == 1'b0)
    110. if (addr_sel == 1'b1)
    111. n_state = HADDR;
    112. else
    113. n_state = LADDR;
    114. else
    115. n_state = START;
    116. else
    117. n_state = CTRL_ACK;
    118. end
    119. HADDR : begin
    120. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    121. n_state = HADDR_ACK;
    122. else
    123. n_state = HADDR;
    124. end
    125. HADDR_ACK : begin
    126. if (flag_high == 1'b1)
    127. if (iic_sda == 1'b0)
    128. n_state = LADDR;
    129. else
    130. n_state = START;
    131. else
    132. n_state = HADDR_ACK;
    133. end
    134. LADDR : begin
    135. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    136. n_state = LADDR_ACK;
    137. else
    138. n_state = LADDR;
    139. end
    140. LADDR_ACK : begin
    141. if (flag_high == 1'b1)
    142. if (iic_sda == 1'b0)
    143. if (wren == 1'b1)
    144. n_state = WR;
    145. else
    146. n_state = RD_START;
    147. else
    148. n_state = START;
    149. else
    150. n_state = LADDR_ACK;
    151. end
    152. WR : begin
    153. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    154. n_state = WR_ACK;
    155. else
    156. n_state = WR;
    157. end
    158. WR_ACK : begin
    159. if (flag_high == 1'b1)
    160. if (iic_sda == 1'b0)
    161. n_state = STOP;
    162. else
    163. n_state = START;
    164. else
    165. n_state = WR_ACK;
    166. end
    167. STOP : begin
    168. if (flag_high == 1'b1)
    169. n_state = IDLE;
    170. else
    171. n_state = STOP;
    172. end
    173. RD_START : begin
    174. if (flag_high == 1'b1)
    175. n_state = RD_CTRL;
    176. else
    177. n_state = RD_START;
    178. end
    179. RD_CTRL : begin
    180. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    181. n_state = RD_CTRL_ACK;
    182. else
    183. n_state = RD_CTRL;
    184. end
    185. RD_CTRL_ACK : begin
    186. if (flag_high == 1'b1)
    187. if (iic_sda == 1'b0)
    188. n_state = RD;
    189. else
    190. n_state = START;
    191. else
    192. n_state = RD_CTRL_ACK;
    193. end
    194. RD : begin
    195. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    196. n_state = NO_ACK;
    197. else
    198. n_state = RD;
    199. end
    200. NO_ACK : begin
    201. if (flag_high == 1'b1)
    202. n_state = STOP;
    203. else
    204. n_state = NO_ACK;
    205. end
    206. default : n_state = IDLE;
    207. endcase
    208. end
    209. always @ (posedge clk, negedge rst_n) begin
    210. if (rst_n == 1'b0)
    211. cnt_en <= 1'b0;
    212. else
    213. case (c_state)
    214. IDLE : cnt_en <= 1'b0;
    215. CTRL_ACK,
    216. HADDR_ACK,
    217. LADDR_ACK,
    218. WR_ACK,
    219. RD_CTRL_ACK : begin
    220. if (flag_high == 1'b1)
    221. if (iic_sda == 1'b0)
    222. cnt_en <= 1'b1;
    223. else
    224. cnt_en <= 1'b0;
    225. else
    226. cnt_en <= cnt_en;
    227. end
    228. default : cnt_en <= 1'b1;
    229. endcase
    230. end
    231. always @ (posedge clk, negedge rst_n) begin
    232. if (rst_n == 1'b0)
    233. out_en <= 1'b0;
    234. else
    235. case (c_state)
    236. IDLE : out_en <= 1'b0;
    237. START : out_en <= 1'b1;
    238. CTRL : begin
    239. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    240. out_en <= 1'b0;
    241. else
    242. out_en <= 1'b1;
    243. end
    244. CTRL_ACK : out_en <= 1'b0;
    245. HADDR : begin
    246. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    247. out_en <= 1'b0;
    248. else
    249. if (flag_low == 1'b1)
    250. out_en <= 1'b1;
    251. else
    252. out_en <= out_en;
    253. end
    254. HADDR_ACK : out_en <= 1'b0;
    255. LADDR : begin
    256. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    257. out_en <= 1'b0;
    258. else
    259. if (flag_low == 1'b1)
    260. out_en <= 1'b1;
    261. else
    262. out_en <= out_en;
    263. end
    264. LADDR_ACK : out_en <= 1'b0;
    265. WR : begin
    266. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    267. out_en <= 1'b0;
    268. else
    269. if (flag_low == 1'b1)
    270. out_en <= 1'b1;
    271. else
    272. out_en <= out_en;
    273. end
    274. WR_ACK : out_en <= 1'b0;
    275. STOP : begin
    276. if (flag_low == 1'b1)
    277. out_en <= 1'b1;
    278. else
    279. out_en <= out_en;
    280. end
    281. RD_START : begin
    282. if (flag_low == 1'b1)
    283. out_en <= 1'b1;
    284. else
    285. out_en <= out_en;
    286. end
    287. RD_CTRL : begin
    288. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    289. out_en <= 1'b0;
    290. else
    291. out_en <= 1'b1;
    292. end
    293. RD_CTRL_ACK : out_en <= 1'b0;
    294. RD : begin
    295. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    296. out_en <= 1'b1;
    297. else
    298. out_en <= 1'b0;
    299. end
    300. NO_ACK : out_en <= 1'b1;
    301. default : out_en <= 1'b0;
    302. endcase
    303. end
    304. always @ (posedge clk, negedge rst_n) begin
    305. if (rst_n == 1'b0)
    306. sda_obuf <= 1'b1;
    307. else
    308. case (c_state)
    309. IDLE : sda_obuf <= 1'b1;
    310. START : begin
    311. if (flag_high == 1'b1)
    312. sda_obuf <= 1'b0;
    313. else
    314. sda_obuf <= 1'b1;
    315. end
    316. CTRL : begin
    317. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    318. sda_obuf <= temp[7];
    319. else
    320. sda_obuf <= sda_obuf;
    321. end
    322. CTRL_ACK : sda_obuf <= 1'b0;
    323. HADDR : begin
    324. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    325. sda_obuf <= temp[7];
    326. else
    327. sda_obuf <= sda_obuf;
    328. end
    329. HADDR_ACK : sda_obuf <= 1'b0;
    330. LADDR : begin
    331. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    332. sda_obuf <= temp[7];
    333. else
    334. sda_obuf <= sda_obuf;
    335. end
    336. LADDR_ACK : sda_obuf <= 1'b0;
    337. WR : begin
    338. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    339. sda_obuf <= temp[7];
    340. else
    341. sda_obuf <= sda_obuf;
    342. end
    343. WR_ACK : sda_obuf <= 1'b0;
    344. STOP : begin
    345. if (flag_low == 1'b1)
    346. sda_obuf <= 1'b0;
    347. else
    348. if (flag_high == 1'b1)
    349. sda_obuf <= 1'b1;
    350. else
    351. sda_obuf <= sda_obuf;
    352. end
    353. RD_START : begin
    354. if (flag_low == 1'b1)
    355. sda_obuf <= 1'b1;
    356. else
    357. if (flag_high == 1'b1)
    358. sda_obuf <= 1'b0;
    359. else
    360. sda_obuf <= sda_obuf;
    361. end
    362. RD_CTRL : begin
    363. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    364. sda_obuf <= temp[7];
    365. else
    366. sda_obuf <= sda_obuf;
    367. end
    368. RD_CTRL_ACK : sda_obuf <= 1'b0;
    369. RD : begin
    370. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    371. sda_obuf <= 1'b1;
    372. else
    373. sda_obuf <= sda_obuf;
    374. end
    375. NO_ACK : sda_obuf <= sda_obuf;
    376. default : sda_obuf <= 1'b1;
    377. endcase
    378. end
    379. always @ (posedge clk, negedge rst_n) begin
    380. if (rst_n == 1'b0)
    381. drive_cnt <= 4'd0;
    382. else
    383. case (c_state)
    384. IDLE : drive_cnt <= 4'd0;
    385. START : drive_cnt <= 4'd0;
    386. CTRL : begin
    387. if (flag_low == 1'b1)
    388. if (drive_cnt < 4'd8)
    389. drive_cnt <= drive_cnt + 1'b1;
    390. else
    391. drive_cnt <= 4'd0;
    392. else
    393. drive_cnt <= drive_cnt;
    394. end
    395. CTRL_ACK : drive_cnt <= 4'd0;
    396. HADDR : begin
    397. if (flag_low == 1'b1)
    398. if (drive_cnt < 4'd8)
    399. drive_cnt <= drive_cnt + 1'b1;
    400. else
    401. drive_cnt <= 4'd0;
    402. else
    403. drive_cnt <= drive_cnt;
    404. end
    405. HADDR_ACK : drive_cnt <= 4'd0;
    406. LADDR : begin
    407. if (flag_low == 1'b1)
    408. if (drive_cnt < 4'd8)
    409. drive_cnt <= drive_cnt + 1'b1;
    410. else
    411. drive_cnt <= 4'd0;
    412. else
    413. drive_cnt <= drive_cnt;
    414. end
    415. LADDR_ACK : drive_cnt <= 4'd0;
    416. WR : begin
    417. if (flag_low == 1'b1)
    418. if (drive_cnt < 4'd8)
    419. drive_cnt <= drive_cnt + 1'b1;
    420. else
    421. drive_cnt <= 4'd0;
    422. else
    423. drive_cnt <= drive_cnt;
    424. end
    425. WR_ACK : drive_cnt <= 4'd0;
    426. STOP : drive_cnt <= 4'd0;
    427. RD_START : drive_cnt <= 4'd0;
    428. RD_CTRL : begin
    429. if (flag_low == 1'b1)
    430. if (drive_cnt < 4'd8)
    431. drive_cnt <= drive_cnt + 1'b1;
    432. else
    433. drive_cnt <= 4'd0;
    434. else
    435. drive_cnt <= drive_cnt;
    436. end
    437. RD_CTRL_ACK : drive_cnt <= 4'd0;
    438. RD : begin
    439. if (flag_high == 1'b1 && drive_cnt < 4'd8)
    440. drive_cnt <= drive_cnt + 1'b1;
    441. else
    442. if (flag_low == 1'b1 && drive_cnt == 4'd8)
    443. drive_cnt <= 4'd0;
    444. else
    445. drive_cnt <= drive_cnt;
    446. end
    447. NO_ACK : drive_cnt <= 4'd0;
    448. default : drive_cnt <= 4'd0;
    449. endcase
    450. end
    451. always @ (posedge clk, negedge rst_n) begin
    452. if (rst_n == 1'b0)
    453. temp <= 8'd0;
    454. else
    455. case (c_state)
    456. IDLE : temp <= 8'd0;
    457. START : begin
    458. if (flag_high == 1'b1)
    459. temp <= {slave_addr, 1'b0};
    460. else
    461. temp <= temp;
    462. end
    463. CTRL : begin
    464. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    465. temp <= temp << 1'b1;
    466. else
    467. temp <= temp;
    468. end
    469. CTRL_ACK : begin
    470. if (flag_high == 1'b1 && iic_sda == 1'b0)
    471. if (addr_sel == 1'b1)
    472. temp <= addr[15:8];
    473. else
    474. temp <= addr[7:0];
    475. else
    476. temp <= temp;
    477. end
    478. HADDR : begin
    479. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    480. temp <= temp << 1'b1;
    481. else
    482. temp <= temp;
    483. end
    484. HADDR_ACK : begin
    485. if (flag_high == 1'b1 && iic_sda == 1'b0)
    486. temp <= addr[7:0];
    487. else
    488. temp <= temp;
    489. end
    490. LADDR : begin
    491. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    492. temp <= temp << 1'b1;
    493. else
    494. temp <= temp;
    495. end
    496. LADDR_ACK : begin
    497. if (flag_high == 1'b1 && iic_sda == 1'b0)
    498. if (wren == 1'b1)
    499. temp <= wdata;
    500. else
    501. temp <= {slave_addr, 1'b1};
    502. else
    503. temp <= temp;
    504. end
    505. WR : begin
    506. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    507. temp <= temp << 1'b1;
    508. else
    509. temp <= temp;
    510. end
    511. WR_ACK : temp <= 8'd0;
    512. STOP : temp <= 8'd0;
    513. RD_START : temp <= temp;
    514. RD_CTRL : begin
    515. if (flag_low == 1'b1 && drive_cnt < 4'd8)
    516. temp <= temp << 1'b1;
    517. else
    518. temp <= temp;
    519. end
    520. RD_CTRL_ACK : temp <= 8'd0;
    521. RD : begin
    522. if (flag_high == 1'b1 && drive_cnt < 4'd8)
    523. temp <= {temp[6:0], iic_sda};
    524. else
    525. temp <= temp;
    526. end
    527. NO_ACK : temp <= 8'd0;
    528. default : temp <= 8'd0;
    529. endcase
    530. end
    531. always @ (posedge clk, negedge rst_n) begin
    532. if (rst_n == 1'b0)
    533. iic_done <= 1'b0;
    534. else
    535. if (c_state == STOP && flag_high == 1'b1)
    536. iic_done <= 1'b1;
    537. else
    538. iic_done <= 1'b0;
    539. end
    540. always @ (posedge clk, negedge rst_n) begin
    541. if (rst_n == 1'b0)
    542. rdata <= 8'd0;
    543. else
    544. if (c_state == RD && flag_low == 1'b1 && drive_cnt == 4'd8)
    545. rdata <= temp;
    546. else
    547. rdata <= rdata;
    548. end
    549. endmodule

    RTL仿真设计

    在仿真时,需要外部提供ACK。如果直接仿真的话,由于外部没有提供ACK,导致一直都会重复发送一小段。在仿真中,我们自己给出ACK的难度也比较大。

    24LC64的厂商帮我们解决了这个问题,他们在造出这个芯片的同时,还设计出了可以帮助我们仿真调试的verliog模型。此模型放在msim->24LC64_sim_module中,文件名字为24LC64.v。此模型的sda也是双向IO,所以在硬件设计时,会在此IO上加上拉电阻,在仿真时,我们也要模拟上拉电阻。

    仿真设计如下:

    1. `timescale 1ns/1ps
    2. module iic_drive_tb;
    3. reg clk;
    4. reg rst_n;
    5. reg start_flag;
    6. reg [6:0] slave_addr;
    7. reg wren;
    8. reg addr_sel;
    9. reg [15:0] addr;
    10. reg [7:0] wdata;
    11. wire iic_scl;
    12. wire iic_sda;
    13. wire iic_done;
    14. wire [7:0] rdata;
    15. pullup(iic_sda);
    16. iic_drive iic_drive_inst(
    17. .clk (clk),
    18. .rst_n (rst_n),
    19. .start_flag (start_flag),
    20. .slave_addr (slave_addr),
    21. .wren (wren),
    22. .addr_sel (addr_sel),
    23. .addr (addr),
    24. .wdata (wdata),
    25. .iic_scl (iic_scl),
    26. .iic_sda (iic_sda),
    27. .iic_done (iic_done),
    28. .rdata (rdata)
    29. );
    30. M24LC64 M24LC64_inst(
    31. .A0 (1'b0),
    32. .A1 (1'b0),
    33. .A2 (1'b0),
    34. .WP (1'b0),
    35. .SDA (iic_sda),
    36. .SCL (iic_scl)
    37. );
    38. initial clk = 1'b0;
    39. always # 10 clk = ~clk;
    40. initial begin
    41. rst_n = 1'b0;
    42. start_flag = 1'b0;
    43. slave_addr = 7'b1010_000;
    44. wren = 1'b0;
    45. addr_sel = 1'b1;
    46. addr = 16'h5555;
    47. wdata = 8'haa;
    48. # 201
    49. rst_n = 1'b1;
    50. # 200;
    51. @ (posedge clk);
    52. # 2;
    53. start_flag = 1'b1;
    54. wren = 1'b1;
    55. @ (posedge clk);
    56. # 2;
    57. start_flag = 1'b0;
    58. @ (posedge iic_done);
    59. # 200;
    60. @ (posedge clk);
    61. # 2;
    62. start_flag = 1'b1;
    63. wren = 1'b0;
    64. @ (posedge clk);
    65. # 2;
    66. start_flag = 1'b0;
    67. @ (posedge iic_done);
    68. # 200
    69. $stop;
    70. end
    71. endmodule

    pullup(iic_sda):将iic_sda上加上拉电阻。

    仿真时,在地址16’h5555(虽然高三位没有用),写入了8’ha;写入完成后,又将此数据读出。

    当对24LC64进行写入之后,它会进入到一个内部写循环(和flash类似),在此期间,不接受任何指令。所以在仿真时,需要等待写循环完成,时间为5ms(此参数在仿真模型中,可以进行修改)。

    板级测试

    上述的设计在使用时会有上游控制器进行控制,所以板级测试还需要设计控制器。在控制器中只需要某一个地址写入数据,然后读出即可。测试时,可以多次更换地址和数据,保证设计的正确性。

    在测试时,将slave_addr、addr_sel、addr、wdata设置为定值。slave_addr为7’b1010_000,addr_sel为1’b1,addr为16’h5555,wdata为8’haa。

    将上有控制器命名为test_ctrl,顶层命名为test。

    test_ctrl的设计代码如下:

    1. module test_ctrl (
    2. input wire clk,
    3. input wire rst_n,
    4. output reg start_flag,
    5. output reg wren,
    6. input wire iic_done,
    7. input wire [7:0] rdata
    8. );
    9. localparam WR = 4'b0001;
    10. localparam WR_WAIT = 4'b0010;
    11. localparam RD_WAIT = 4'b0100;
    12. localparam TESTDONE = 4'b1000;
    13. reg [3:0] c_state;
    14. reg [3:0] n_state;
    15. always @ (posedge clk, negedge rst_n) begin
    16. if (rst_n == 1'b0)
    17. c_state <= WR;
    18. else
    19. c_state <= n_state;
    20. end
    21. always @ * begin
    22. case (c_state)
    23. WR : n_state = WR_WAIT;
    24. WR_WAIT : begin
    25. if (iic_done == 1'b1)
    26. n_state = RD_WAIT;
    27. else
    28. n_state = WR_WAIT;
    29. end
    30. RD_WAIT : begin
    31. if (iic_done == 1'b1 && rdata == 8'haa)
    32. n_state = TESTDONE;
    33. else
    34. n_state = RD_WAIT;
    35. end
    36. TESTDONE : n_state = TESTDONE;
    37. default : n_state = WR;
    38. endcase
    39. end
    40. always @ (posedge clk, negedge rst_n) begin
    41. if (rst_n == 1'b0)
    42. start_flag <= 1'b0;
    43. else
    44. if ((c_state == WR) || (c_state == WR_WAIT && iic_done == 1'b1))
    45. start_flag <= 1'b1;
    46. else
    47. start_flag <= 1'b0;
    48. end
    49. always @ (posedge clk, negedge rst_n) begin
    50. if (rst_n == 1'b0)
    51. wren <= 1'b0;
    52. else
    53. if (c_state == WR || c_state == WR_WAIT)
    54. wren <= 1'b1;
    55. else
    56. wren <= 1'b0;
    57. end
    58. endmodule

    test模块负责将test_ctrl和iic_drive模块进行例化连接。将test模块设置为顶层。

    test模块设计代码如下:

    1. module test (
    2. input wire clk,
    3. input wire rst_n,
    4. output wire iic_scl,
    5. inout wire iic_sda
    6. );
    7. wire start_flag;
    8. wire wren;
    9. wire iic_done;
    10. wire [7:0] rdata;
    11. test_ctrl test_ctrl_inst(
    12. .clk (clk),
    13. .rst_n (rst_n),
    14. .start_flag (start_flag),
    15. .wren (wren),
    16. .iic_done (iic_done),
    17. .rdata (rdata)
    18. );
    19. iic_drive iic_drive_inst(
    20. .clk (clk),
    21. .rst_n (rst_n),
    22. .start_flag (start_flag),
    23. .slave_addr (7'b1010_000),
    24. .wren (wren),
    25. .addr_sel (1'b1),
    26. .addr (16'h5555),
    27. .wdata (8'haa),
    28. .iic_scl (iic_scl),
    29. .iic_sda (iic_sda),
    30. .iic_done (iic_done),
    31. .rdata (rdata)
    32. );
    33. endmodule

    板级测试采用逻辑分析仪测试。

    采样时钟选择外部的50MHz,采样深度选择32K。

     

     

     

     

     

     

  • 相关阅读:
    Discuz! X3.4 升级至 Discuz! X3.5 详细教程
    Zemax操作38--POP(物理光学传播)的用法
    佩戴最稳固的蓝牙运动耳机、好用的运动耳机推荐
    【Spring事务】事务和事务传播机制
    nvcc编译器之编译选项(chapter 4)
    kibana 上dashbord 和discover 时间快 or 慢 8小时,处理方案
    Linux中一款超级好用的终端分屏控制工具tmux
    LVS实战笔记-DR单网段
    inux 设备树 (一) 初探
    git修改文件名称提交
  • 原文地址:https://blog.csdn.net/ONEFPGA/article/details/125496122