解析來自於OpenCores網站上的I2C元件 maintainers是Richard Herveille 其中spec部分第五章Architecture共分為9個元件: 先探討最基本元件之一 Bit Command Controller
總圖 (取自其說明文件)
┌─────────┐ ┌────────────┐ │Prescale │ │clock │ │Register ├───────────clk_cnt────────────►│generator │ └─────────┘ └────────┬───┘ ▲ clk_en ┌─────────┐ scl_sync ▼ │Command │ ┌────────────┐ ┌──┴─────────┐ │Register ├─────►│ │ │ │ └─────────┘ │ Byte ├─core_cmd─►│ Bit │◄───────► SCL │ Command │ │ Command │ ┌─────────┐ │ Controller │◄─core_ack─┤ Controller │◄───────► SDA │Status │◄─────┤ │ │ │ │Register │ └────────────┘ └────────┬───┘ └─────────┘ ▲ │ │ │ ┌─────────┐ │ │ │Transmit │ ┌────────────┐ │ │ │Register ├─────►│ │ │ │ └─────────┘ │ DataIO │───────────────┘ │ │ Shift │ │ ┌─────────┐ │ Register │◄───────────────────┘ │Receive │◄─────┤ │ │Register │ └────────────┘ └─────────┘
bit controller 區塊圖 ### 訊號捕捉 ┌─────────────┐ ┌─────┐ │ │ │ │ scl_i─────►│ capture ├──►sSCL─►│ ├────►dSCL │ & │ │ dff │ sda_i─────►│ filtered ├──►sSDA─►│ ├────►dSDA │ │ │ │ └─────────────┘ └─────┘ ### 狀態機 ┌─────────────┐ ┌───────────┐ scl_oen nReset────►│ │ clk_en │ │ sda_oen │ │ nReset │ │ cmd_ack rst────►│ clk enable │ rst │ FSM ├──────────► │ ├────────►│ │ sda_chk ena────►│ │ │ ├──────────┐ └─────────────┘ └───────────┘ │ ### 狀況判斷 │ sSCL ┌─────────────┐ sda_oen sSDA │ ─────────►│ │ ────────┐ ──────┐ │ dSCL │ │ ▼ ▼ ▼ ─────────►│ scl_sync │ ┌───────────────────┐ │ │ │ │ ┌──►│ │ │ │ │ └─────────────┘ │ aribitration lost │ al │ │ ├────► │ ┌─────┐ │ │ │ │ │ └───────────────────┘ scl_oen─┼──►│ dff │ ▲ ▲ │ │ │ c_state │ │ │ └─┬───┘ sSCL ────────┘ │ │ │ dscl_oen sSDA ┌───────┐ │ │ ▼ ─────►│ │sta_condition │ │ ┌─────────────┐ │ START ├────────────► │ └──►│ │ dSCL │ & │sto_condition │ sSCL │ slave_wait │ dSDA │ STOP ├────────────────┘ ─────────►│ │ ─────►│ │ └─────────────┘ └───────┘
關於rst 系統中分為high active rst
, 跟low active nReset
, 正確觸發方式為先 nReset
再正 rst
一次
原始碼順序為狀況判斷(1/2), 訊號捕捉, 狀況判斷(2/2), 狀態機 以下根據原始碼順序進行分析
PART1 狀況判斷(1/2)
slave_wait (Clock Stretching) slave 忙碌, 會將SCL拉低, 稱為Clock Stretching
如果masterscl_oen
輸出high, 卻收到低sSCL
, 則slave忙碌中
如果slave忙碌中, sSCL
未拉高之前, 則slave忙碌中
198 199 200 always @(posedge clk or negedge nReset) if (!nReset) slave_wait <= 1'b0 ; else slave_wait <= (scl_oen & ~dscl_oen & ~sSCL) | (slave_wait & ~sSCL);
scl_sync (Clock Synchronization) 線路中存在多個master, 彼此間同步SCL頻率, 稱為 Clock Synchronization
當裝置scl_oen為high, 其他裝置把SCL拉低時, 就會發出 scl_sync 訊號
發出 scl_sync 訊號將導致 clk enable signal 重新計數
204 wire scl_sync = dSCL & ~sSCL & scl_oen;
其實 slave_wait
跟 scl_sync
很相似 只是 slave_wait
是 SCL 想 high 卻還是 low 只是 scl_sync
是 SCL 該 high 時被拉 low master對這兩者採取妥協政策,slave_wait
等待SCL, scl_sync
跟隨 SCL
clk enable signal 除頻 除頻, 給狀態機使用
先放開 nReset, 再放開 rst, 且開啟 en
根據 clk_cnt(clock prescale value) 除頻, 給出 clk_en 供 FSM 使用
根據 scl_sync 實作 Clock Synchronization
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 always @(posedge clk or negedge nReset) if (~nReset) begin cnt <= #1 16'h0 ; clk_en <= #1 1'b1 ; end else if (rst || ~|cnt || !ena || scl_sync) begin cnt <= #1 clk_cnt; clk_en <= #1 1'b1 ; end else if (slave_wait) begin cnt <= #1 cnt; clk_en <= #1 1'b0 ; end else begin cnt <= #1 cnt - 16'h1 ; clk_en <= #1 1'b0 ; end
PART2 訊號捕捉
捕捉 cSCL, cSDA 訊號 捕捉窗格為2個 posedge clk, 減供給後續 filter 使用
輸入訊號 scl_i, sda_i 會慢慢位移進 cSCL, cSDA.
248 249 250 251 252 253 reg [ 1 :0 ] cSCL, cSDA; always @(posedge clk or negedge nReset) cSCL <= {cSCL[0 ],scl_i}; cSDA <= {cSDA[0 ],sda_i};
擷取 fSCL, fSDA 訊號 若系統頻率50MHz, clk_cnt若設為125, 則clk_en為400KHz, filter_cnt再位移2 bit, 則是大約1.6MHz.
將 clock prescale value 除以四當作擷取頻率
輸入訊號 scl_i, sda_i 會慢慢位移進 fSCL, fSDA.
257 258 else if (~|filter_cnt) filter_cnt <= clk_cnt >> 2 ; else filter_cnt <= filter_cnt -1 ;
274 275 276 always @(posedge clk or negedge nReset) fSCL <= {fSCL[1 :0 ],cSCL[1 ]}; fSDA <= {fSDA[1 :0 ],cSDA[1 ]};
擷取完成sSCL, sSDA, 延遲 dSCL, dSDA 產出之訊號可減少 metastability 風險
若 fSCL
中有任意 兩個值為 1,則sSCL
為High
若 fSDA
中有任意 兩個值為 1,則sSDA
為High
產生各自的delay訊號
280 281 282 283 284 always @(posedge clk or negedge nReset) sSCL <= #1 &fSCL[2 :1 ] | &fSCL[1 :0 ] | (fSCL[2 ] & fSCL[0 ]); sSDA <= #1 &fSDA[2 :1 ] | &fSDA[1 :0 ] | (fSDA[2 ] & fSDA[0 ]); dSCL <= #1 sSCL; dSDA <= #1 sSDA;
假設在50MHz的頻率下,cSCL[0]
, cSCL[1]
, fSCL[0]
, fSCL[1]
, fSCL[2]
, 延遲分別為 T, T-20ns, T-1020ns, T-2020ns, T-3020ns 而一個400KHz的I2C訊號間隔至少2500ns, 此處算是抓的細了一點. 此處可以看成很嚴格監測訊號為 0 的狀態, 只要有幾個 1 就不為 0
Part3 狀況判斷(2/2)
開始結束 START, STOP 此處用兩個暫存器監測START, STOP兩個狀態, 訊號是使用過濾好的 sSCL
, sSDA
sSCL
為高,sSDA
由高轉低,稱為STARTsSCL
為高,sSDA
由低轉高,稱為STOP
308 309 310 311 312 reg sta_condition;reg sto_condition;always @(posedge clk or negedge nReset) sta_condition <= #1 ~sSDA & dSDA & sSCL; sto_condition <= #1 sSDA & ~dSDA & sSCL;
忙碌訊號 Busy rst過後不busy START後busy busy時busy STOP後不busy
329 330 331 332 always @(posedge clk or negedge nReset) if (!nReset) busy <= #1 1'b0 ; else if (rst ) busy <= #1 1'b0 ; else busy <= #1 (sta_condition | busy) & ~sto_condition;
爭輸贏Arbitration Lost 仲裁輸掉分為兩個部分:
(僅wr_c狀態, 才做sda_chks
)當master嘗試寫sda_oen
(輸出)為高, 卻發現無法, 並偵測到sSDA
(輸入)為低, 輸掉仲裁(地址小的優先)
或
沒有IDLE時(傳輸途中), 收到STOP訊號(總線上面感測到), 還沒到轉state的段落, 仲裁就輸掉
仲裁輸掉主要導致top部分會將資料重寫或者重讀. 一旦仲裁輸掉就會讓byte-ctrl回到IDLE狀態
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 reg cmd_stop; always @(posedge clk or negedge nReset) if (~nReset) cmd_stop <= #1 1'b0 ; else if (rst) cmd_stop <= #1 1'b0 ; else if (clk_en) cmd_stop <= #1 cmd == `I2C_CMD_STOP; always @(posedge clk or negedge nReset) if (~nReset) al <= #1 1'b0 ; else if (rst) al <= #1 1'b0 ; else al <= #1 (sda_chk & ~sSDA & sda_oen) | (|c_state & sto_condition & ~cmd_stop);
PART4 狀態機 dout iSCL上升時讀取iSDA送進去給SR(ShiftRegister)
358 359 always @(posedge clk) if (sSCL & ~dSCL) dout <= #1 sSDA;
FSM rst 時 不給ack, scl,sda輸出掛z, 不檢查sda 其餘狀態如下:
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 idle : I2C_CMD_START,I2C_CMD_STOP,I2C_CMD_WRITE,I2C_CMD_READ選一個 start_a : SCL x SDA H start_b : SCL H SDA H start_c : SCL H SDA L start_d : SCL H SDA L start_e : SCL L SDA L cmd_ack stop_a : SCL L SDA L stop_b : SCL H SDA L stop_c : SCL H SDA L stop_d : SCL H SDA H cmd_ack rd_a : SCL L SDA H rd_b : SCL H SDA H rd_c : SCL H SDA H rd_d : SCL L SDA H cmd_ack wr_a : SCL L SDA=Din wr_b : SCL H SDA=Din wr_c : SCL H SDA=Din sda_chks wr_d : SCL L SDA=Din cmd_ack