0%

day6-i2c-master_bit_ctrl

解析來自於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_waitscl_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;      // capture SCL and SDA
// capture SDA and SCL
// reduce metastability risk
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; //16x I2C bus frequency
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由高轉低,稱為START
sSCL為高,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

仲裁輸掉分為兩個部分:

  1. (僅wr_c狀態, 才做sda_chks)當master嘗試寫sda_oen(輸出)為高, 卻發現無法, 並偵測到sSDA(輸入)為低, 輸掉仲裁(地址小的優先)

  1. 沒有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