完整版請點擊 https://hifpga.com/問題/719
索取源碼,向博主本人提問FPGA相關問題
作者:Rock.Ding(萊迪思半導體公司)
關鍵字:MCU, JTAG, 在線編程, CPLD。
前言
CPLD(Complex Programmable Logic Device)複雜可編程邏輯器件,是從PAL和GAL器件發展出來的器件,相對而言規模大,結構複雜,屬於大規模集成電路範圍。是一種用戶根據各自需要而自行構造邏輯功能的數字集成電路。其基本設計方法是藉助集成開發軟件平臺,用原理圖、硬件描述語言等方法,通過編譯,映射,佈局佈線,最後生成相應的JED文件,然後通過CPLD廠家提供的下載電纜和下載軟件把JED文件下載器件裏面,實現用戶的功能。在實際應用中,根據設計需要或修復之前設計中的Bug,常常需要在線升級CPLD的內容。目前各廠家都有提供相應的方案,可以在嵌入式系統中通過CPU來更新CPLD。廠家考慮到方案的兼容性,往往實現編程的代碼都比較複雜,而且佔用內存資源較大。不適合MCU這樣的小系統。本文以Lattice XO2系列的CPLD爲例,詳細介紹JTAG的編程原理及如何用MCU來模擬JTAG編程Lattice XO2 CPLD。理解了JTAG編程原理後,也可以很容易實現模擬JTAG編程其它的CPLD和FPGA。
一,JTAG介紹
JTAG最初是用來對芯片進行測試的,JTAG的基本原理是在器件內部定義一個TAP(Test Access Port;測試訪問口)通過專用的JTAG測試工具對內部節點進行測試。JTAG測試允許多個器件通過JTAG接口串聯在一起,形成一個JTAG鏈,能實現對各個器件分別測試。如今,JTAG接口還常用於實現ISP(In-System Programmer,在系統編程),對CPLD,FPGA等器件進行編程。
JTAG編程方式是在線編程,傳統生產流程中先對芯片進行預編程然後再裝到板上,簡化的流程爲先固定器件到電路板上,再用JTAG編程,從而大大加快工程進度及編程的靈活性。
JTAG引腳定義
具有JTAG口的芯片都有如下JTAG引腳定義:
- TCK——測試時鐘/編程時鐘輸入;
- TDI——測試數據/編程數據輸入,數據通過TDI輸入JTAG口;
- TDO——測試數據/編程數據輸出,數據通過TDO從JTAG口輸出;
- TMS——測試模式選擇/改變TAP內部的狀態機的狀態。
- TDI, TMS上的數據輸入及TDO上的數據輸出都是在TCK時鐘的作用下完成的。
二,JED文件介紹
JED文件是一個包含了器件的編程內容的文本文件。本文以Lattice XO2系列的CPLD的JED文件爲例,簡約介紹JED文件的格式,下面是Lattice的開發工具生成的JED文件中截取的一部分:
NOTE Diamond_1.2_Production (92) JEDEC Compatible Fuse File.*
NOTE Copyright (C), 1992-2010, Lattice Semiconductor Corporation.*
QP132*
QF343936*
G0*
F0*
L000000
1111111111111111101111011011001111111111111111110011101100000000000000000
NOTE EBR_INIT DATA*
L137984
1111111111111111111111111111111111110110000000000000000000000000000000000
NOTE END CONFIG DATA*
L184832
0000000000000000000000000000000000000000000000000000000000000000000000000
NOTE TAG DATA*
L343808
0100010001000100000010001000000000000000000100010000000000000000000000000
C5CC8*
NOTE FEATURE_ROW*
E0000000000000000000000000000000000100001000010000000000000000000
NOTE User Electronic Signature Data*
UHCAFEBABE*
6243
...
三,模擬JTAG編程。
1,怎樣獲得JTAG的編程細節?
如果手上沒有某個CPLD的具體JTAG編程指導文檔,可以用廠家的編程工具生成SVF(Serial Vector Format)文件,該文件包含JTAG編程的詳細流程和編程類容。以Lattice最新的編程工具Diamond Programmer爲例,它附帶了一個小工具,叫做Deployment Tool(圖1),就可以生成SVF文件。
SVF文件是文本文件,裏面包含定義好的各種命令,詳細描述了JTAG的編程行爲。具體可參考Serial Vector Format Specification標準。SVF文件裏面的命令很容易理解,下面從SVF文件中摘取一段擦除器件的SVF代碼,做簡單介紹:
! Erase the Flash
! Shift in ISC ERASE(0x0E) instruction
SIR 8 TDI (0E); SDR 8 TDI (0E);
RUNTEST IDLE 2 TCK;
! Shift in LSC_CHECK_BUSY(0xF0) instruction
SIR 8 TDI (F0);
LOOP 350 ;
RUNTEST IDLE 2 TCK 1.00E-002 SEC;
SDR 1 TDI (0) TDO (0);
ENDLOOP ;
感嘆號後面的類容都是註釋,只是爲了更好的理解SVF命令。
...
2,解析SVF命令。
如果用廠家的編程工具來編程,是由廠家的軟件來控制JTAG口上的輸入/輸出數據的時序。現在我們通過SVF文件瞭解JTAG編程具體流程後,也可以解析SVF 命令來模擬這個時序。仔細觀察SVF文件,裏面主要包括三類指令,SIR,SDR,控制狀態機的指令,SDR常用的有三種方式:
-
1,只在TDI上送入數據,例如:
SDR 128 TDI (0000B280071D000000623FFC0DB01216);
-
2,送入數據同時,在TDO上讀取數據,並判斷讀取的數據是否是期望的值,例如:
SDR 128 TDI (00000000000000000000000000000000)
TDO (414900000040000000DCFFFFCDBDFFFF); -
3,是在TDO上讀取數據時,只判斷MASK相應Bit位是1的TDO的值是否是期望的值,例如:
SDR 32 TDI (00000000) TDO (012B2043) MASK (FFFFFFFF);
下面是解析SVF命令的C51函數,可以直接在Keil C51環境下編譯。
通過在TMS管腳上輸入不同的01序列來實現JTAG狀態機的控制,下面控制狀態機的代碼是在Lattice編程工具提供的參考代碼修改而來。
typedef struct
{
BYTE CurState;/*** From this state ***/
BYTE NextState;/*** Step to this state ***/
BYTE Pattern;/*** The pattern of TMS ***/
BYTE Pulses;/*** The number of steps ***/
}JTAGState;
JTAGState code JTAGStateTable[25]=
{
{DRPAUSE, SHIFTDR, 0x80, 2},
{IRPAUSE, SHIFTIR, 0x80, 2},一共25個狀態,篇幅影響,這裏只列了2個狀態。
};
void Set_JTAG_State_Machine(BYTE nextstate)
{
BYTE data i,j,temp;
if((CurState == nextstate)&&(CurState != RESET))return;
for(i = 0;i < 25;i++)
{
if((CurState == JTAGStateTable[i].CurState)&&(nextstate ==
JTAGStateTable[i].NextState))break;
}
CurState = nextstate;
temp = JTAGStateTable[i].Pattern;
for(j = 0;j < JTAGStateTable[i].Pulses;j++)
{
if((temp & 0x80)== 0x80)TMSPin = 0x01; else TMSPin = 0x00;
Send_1Clk(); temp = temp << 1;
}
TDIPin = 0x00; TMSPin = 0x00;
}
首先定義一個數據結構,....
2,解析SIR(Scan Instruction Register)命令。
SIR是往指令寄存器中送入數據,縱觀整個SVF文件,SIR命令送入的數據都是8Bit,實現SIR命令的C51函數如下:
BYTE Excute_SIR(BYTE instruction)
{
BYTE data i;
....
for(i = 0;i < 8;i++)
{
...
}
Set_JTAG_State_Machine(IRPAUSE); return(SUCCESS);
}
該函數只需輸入一個參數instruction,函數開始會把JTAG狀態機轉換到IRPAUSE狀態,然後再轉換到SHIFTIR狀態,再把8Bit 的instruction從TDI管腳送入JTAG,先送低Bit。最後再把JTAG狀態機轉換到IRPAUSE狀態。注意在for循環中,在TCK上只需送入7個時鐘週期,也就是說這裏送入的時鐘週期比數據少一個。
3,解析SDR(Scan Data Register)命令
SDR命令往數據寄存器中送入和讀出數據。編程時的數據輸入和輸出都是通過這個命令來實現。實現SDR的C51函數如下:
BYTE Excute_SDR(BYTE bit_len,BYTE TDI_mark,BYTE TDO_mark,BYTE mask_mark)
{
BYTE data I, TDI_data, TDO_data, mask_data, ptr, result;
ptr = 0; result = SUCCESS;
...
for(i = 0;i < bit_len;i++)
{
...
}
Set_JTAG_State_Machine(DRPAUSE);
return(result);
}
上小節說到SDR命令可能有3種模式,不同的模式可能需要用到TDI,TDO,MASK數據,在調用這個函數前,根據需要先把數據存入TDI_Buffer,TDO_Buffer,MASK_Buffer裏面,然後調用函數時,把需要送入JTAG的 Bit數據長度,是否有TDI數據,TDO數據及MASK數據四個參數傳送給函數。在TDI上送入數據前,先把JTAG狀態機轉換到DRPAUSE,再轉換到SHIFTDR,然後開始送入數據,同樣需要注意在送數據時,在TCK上時鐘週期數是送入數據Bit數少一個,例如送入128Bit數據,只送127個時鐘週期。送完數據後,再把狀態機轉換到DRPAUSE,這樣整個操作完成。函數執行成功,返回1,否則返回0。
四, MCU模擬JTAG編程CPLD完整例子。
上一節已經講了實現JTAG編程的3個主要函數,本節主要是怎樣調用這三個函數實現對CPLD的編程。本例子用到MCU是STC89LE516RD+,CPLD是Lattice LCMXO1200ZE。
Lattice LCMXO1200ZE大致編程流程是:
- 1,檢查器件ID,2,編程BSCAN 寄存器,3,使能編程,4,擦除Flash,5,編程Flash,一次編程1行128Bit,一種2175行,6, 編程user code,7,校驗Flash, 8,校驗User code,9,編程Done Bit,10, 退出編程模式, 11,Refresh。每一個操作可以用一個函數來實現,這裏只講解兩個典型的操作:
-
2, 編程Flash ,編程Flash前首先要復位Flash的地址,這個操作只執行一次,接着每編程一行,Flash的地址會自動加一,函數如下:
BYTE Init_Address(void) { BYTE result; Excute_SIR(0x46); TDI_Buffer[0] = 0x04; result = Excute_SDR(8,1,0,0); Set_JTAG_State_Machine(IDLE); Send_Num_Clk(2);Wait_ms(10); return(result); }
然後以行爲單位,編程Flash。調用函數前,需要把編程數據放入TDO_Buffer中。函數執行過程,先送PROG_INCR_NV指令,然後送入編程數據,最後檢測編程狀態,直到Busy Bit爲0後才表示編程成功。成功函數返回1,否則返回0。下面是編程一行的函數,根據器件大小,Flash的行數不同,反覆調用這個函數來編程Flash。
BYTE Program_Row(void) { BYTE data loop,result; Excute_SIR(0x70); Excute_SDR(128,1,0,0); Set_JTAG_State_Machine(IDLE); Send_Num_Clk(2); Excute_SIR(0xf0); TDI_Buffer[0] = 0x00; TDO_Buffer[0] = 0x00; for(loop = 0;loop < 10;loop++) { Set_JTAG_State_Machine(IDLE); Send_Num_Clk(2); Wait_ms(10); result = Excute_SDR(1,1,1,0); if(result == SUCCESS)break; } return(result); }
-
3, 校驗 Flash ,校驗Flash時,也需要先調用復位Flash 地址的函數Init_Address;然後輸入LSC_READ_INCR_NV指令,表示開始讀取Flash的數據,最後循環調用Verify_Row函數,來讀取數據並校驗是否正確,在每次調用Verify_Row函數前,需把校驗數據放入TDO_Buffer中。相關函數如下:
BYTE Verify_Row(void) { BYTE data result; result = Excute_SDR(128,0,1,0); Set_JTAG_State_Machine(IDLE); Send_Num_Clk(2); Wait_ms(1); return(result); }
-
4, 本例子中所有的命令和數據來自串口,每接收到一個命令執行一次操作。串口函數根據約定好的協議,從上位機接收命令和數據,接收到成功後,把相關命令傳送給主函數,主函數調用相關函數執行相應的操作,執行完成後,又回到等待狀態,等待下一個命令。上位機界面如下,只需要選擇好串口,選好下載文件,點“Start”按鈕就可以了。這裏下載的文件是從JED文件轉換而來,因爲JED文件是文本文件,而且包含了很多註釋信息,這裏只提取了Flash行的類容,並轉化爲BIN文件。
總結
本例子模擬JTAG編程有關的代碼大約只有300行,只要理解JTAG編程原理,就可以化繁爲簡。根據自己的需求,定製編程流程,不需完全照搬廠家提供的代碼,這樣在調試或功能改進中都更加容易和靈活。有需要源代碼的可以先在下面回覆然後發郵件給我,發郵件時記得寫上論壇用戶名,我的郵箱是:
[email protected]
參考文獻:
1, MachXO2 Family Handbook,HB1010.pdf。
2, Serial Vector Format Specification,SVF_Spec_RevE.pdf。
3, Lattice VME source code。