JTAG: non solo test

Il Joint Test Action Group (JTAG) ha sviluppato un sistema di test boundaryscan standardizzato poi nelle specifiche IEEE 1149.1 nato, in prima battuta, per effettuare test esaustivi da un punto di vista elettrico, ma ora sempre più utilizzato anche per condurre vere e proprie sessioni di collaudo del software.

Le attività di debug del software per sistemi embedded presentano delle difficoltà aggiuntive rispetto alle normali attività previste per sistemi desktop; inoltre, di solito, un’applicazione di tipo embedded non è generalmente verificata utilizzando la stessa macchina, ma si ha sempre più spesso l’esigenza di strumenti che garantisco il controllo hardware dell’apparecchiatura sotto esame. Esistono differenti metodi e strumenti utilizzati per condurre il collaudo del software/hardware, a questo riguardo possiamo senz’altro citare il  trace mode, il  JTAG, il BDM, l’ICE o un logic analyzer. Il JTAG nasce tra il 1985 e il 1990 come gruppo di lavoro che si proponeva di mettere a punto un nuovo protocollo di test comune. Nel tempo poi, l’insieme delle norme è divenuto uno standard con l’identificativo IEEE Std 1149.1: oggi molti circuiti integrati contengono un modulo di supervisione e di controllo remoto accessibile tramite l’interfaccia JTAG. Una grande varietà di schedine a microcontrollori dispongono di interfacce BDM/JTAG e con un’opportuna selezione, magari attraverso un jumper, diventa possibile selezionare il tipo di protocollo da utilizzare. Attraverso queste interfacce, e con driver aggiuntivi, è possibile utilizzare un’opportuna interfaccia grafica per condurre la sessione di collaudo nel modo più semplice possibile. È possibile, infatti, scrivere un flusso di dati nella memoria flash della scheda o effettuare  il debug del software caricato in memoria senza per questo ricorrere a costosi emulatori. Per effettuare queste operazioni si ricorre a un interprete di comandi in grado di comunicare con il dispositivo per mezzo di un protocollo. È necessario, poi, disporre di una serie di comandi per scrivere o leggere dati dai pin di I/O del chip in maniera sincrona con un segnale di clock generato dal chip stesso.

Jtag

Dopo l’introduzione dei componenti a montaggio superficiale, i  circuiti stampati sono diventati sempre più piccoli e, di conseguenza,  i metodi tradizionali di test, come il sistema a letto di chiodi, sono ora sempre di difficile applicazione. L’architettura di boundary-scan permette di testare in modo efficiente  i circuiti integrati presenti sul circuito stampato, mentre il sistema è in funzione, con un numero ridotto di connessioni esterne. La circuiteria definita da questo standard permette al le istruzioni, e agli associati dati di test, di essere introdotti nel componente per poi, successivamente, permettere la rilettura dei risultati. La modalità di trasferimento delle informazioni (istruzioni o dati) avviene secondo lo standard seriale. Tutte le operazioni sono controllate mediante opportuni segnali di ingresso ai vari componenti connessi al controllore del bus. In una procedura tipica di test per prima cosa si deve caricare, in forma seriale, il codice dell’istruzione relativa all’operazione da svolgere e, se occorre, sono anche introdotti dati di inizializzazione. Successivamente, la circuiteria di test selezionata è abilitata a rispondere. Una volta terminata l’istruzione di test che doveva essere eseguita, i  risultati prodotti sono messi in forma seriale all’uscita del componente sotto test. All’interno dei circuiti integrati che supportano il boundary-scan vengono aggiunte delle celle che permettono di pilotare i pin del dispositivo, oppure di catturare dati dai pin stessi o dalla logica del circuito. I  dati da e verso il dispositivo, come si è scritto, sono trasmessi con un protocollo seriale. Le connessioni che implementano il protocollo JTAG sono 5 di cui 4 obbligatorie. Internamente al componente tutti i pin dei vari moduli presenti sono con nessi in un unico registro a scorrimento. In base a questa configurazione è possibile iniziare diverse attività di test. Questa possibilità viene offerta dal TAP controller (Test Access Port controller) che, in base a un particolare protocollo, permette di definire delle configurazioni di connessioni. La figura 1 mostra la macchina a stati del TAP, come possiamo notare questo lavora con sei stati e il reset è attivato da 5 periodi di clock consecutivi con TMS pari a 1. Il TAP è definito sempre in accordo allo standard IEEE 1149.1.

Figura 1: TAP controller.

Figura 1: TAP controller.

I vari segnali coinvolti sono precisamente il Test Clock Input (TCK), un segnale di ingresso di clock per la logica di boundary-scan per istruzioni e dati. Il TDI, test data input, è utilizzato per ricevere serialmente dati e istruzioni;  i dati, poi, vengono campionati sul fronte di salita di TCK. Il TDO, test data output, segnale di uscita per le istruzioni e dati; i dati sono scritti sul fronte di discesa di TCK. Questo segnale riporta in uscita i risultati  del test del boundary-scan e si trova in uno stato definito come tri-state se non utilizzato. Il segnale di ingresso TMS, test mode select, che permette di controllare la macchina a stati della logica JTAG (tap controller): in pratica, commuta il  sistema dal modo funzionale a quello di test. Le transizioni di stato avvengono sul fronte di salita di TCK. Infine, il segnale opzionale, il  TRST, test reset, o test reset input. Questo segnale è un reset asincrono del TAP e si occupa del reset asincrono della logica di boundary-scan. Lo standard IEEE 1149.1 richiede poi la presenza di alcuni registri, figura 2 e 3.

Figura 2: i registri dati.

Figura 2: i registri dati.

 

Figura 3: IDCODE.

Figura 3: IDCODE.

Per esempio il registro bypass è un registro di un solo bit tra TDI e TDO per togliere dalla catena i componenti già testati. Il registro instruction  register determina l’azione da eseguire e il registro dati coinvolto: in pratica, il registro seleziona quale registro dati è connesso tra l’ingresso TDI e l’uscita TDO. Il boundary-scan register risulta composto da tutte le celle boundary-scan. La procedura di boundary-scan è controllata dal TAP controller. I  pin TMS, TRST e TCK operano sul TAP controller, mentre TDI e TDO vengono utilizzati per la lettura/scrittura di dati. Attraverso TDI viene scritto l’instruction register. Le istruzioni implementate dai dispositivi conformi alle specifiche IEEE 1149.1 sono riportate in tabella 1.

Tabella 1 – Alcuni istruzioni dello standard JTAG IEEE 1149.1

Tabella 1 – Alcuni istruzioni dello standard JTAG IEEE 1149.1

L’esempio  H-Jtag

Esistono diverse proposte, anche open, che permettono di utilizzare questa tecnologia e condurre il test di sistemi embedded, tra questi possiamo senz’altro ricordare H-Jtag. In realtà H-Jtag è composto di tre differenti strumenti: H-Jtag server, H-Flasher e H-Converter. Ognuno si occupa di un particolare compito, HConverter è un tool di conversione, mentre H-Flasher è un programmatore di memorie flash. H-Jtag supporta ARM7, ARM9, XSCALE e Cortex-M3 e può essere utilizzato con diversi debugger, tra cui ADS, RVDS, IAR e Keil. H-Jtag permette, grazie alla sua flessibilità, di essere utilizzato con Wiggler, SDT-Jtag o altre tipologie di interfacce. A questo punto vediamo in che modo è possibile sfruttare la tecnologia JTAG su ARM, in modo particolare con ARM7TDMI (figura 4); infatti, H-Jtag server può essere tranquillamente connesso.

Figura 4: ARM7TDMI.

Figura 4: ARM7TDMI.

H-Jtag, inoltre, supporta l’interfaccia RDI e, per mezzo di questa interfaccia, il tool può gesire la maggior parte dei debugger disponibili in commercio. Il server  H-Jtag accede alle porte JTAG per mezzo del JTAG controller connesso alla porta LPT/USB. Con delle idonee procedure di configurazioni, H-Jtag può lavorare tranquillamente con differenti emulatori o su LPT o USB. Le figure 5 e 6 mostrano un tipico circuito con i relativi collegamenti.

Figura 5: Wiggler.

Figura 5: Wiggler.

 

Figura 6: sezione porta parallela.

Figura 6: sezione porta parallela.

Il primo passo per utilizzare l’interfaccia JTAG è di mettere a punto una libreria software per controllare e gestire i segnali. Comandi che permettono di inviare serialmente byte sul pin di TDI ed eventualmente la lettura di TDO. In seguito, occorrono comandi per la scrittura di singoli bit su TDI. Anche in questo caso, se necessario, è possibile leggere lo stato di TDO. Infine, la gestione del segnale TMS, si agisce in questo modo sulla macchina a stati della logica di controllo dell’interfaccia JTAG (tap controller). Per fare questo è necessario definire un modulo software per la gestione del JTAG. Il  listato 1 mostra alcune funzioni necessarie insieme alle due routine per la gestione della porta parallela.

#define PARAL_DATA_PORT                   0x378
#define PARAL_STAT_PORT                   0x379
#define PARAL_INIT_DATA                   0x0 
#define PARAL_PIN_11                      0x80 //A special pin (Hardware
Inverted)
static void paral_output(u8 data)
{ _outp(PARAL_DATA_PORT, data);
}

static u8 paral_input(void)
{ u8 data;
  data = _inp(PARAL_STAT_PORT);
  return data;
}

int jtag_init(void)
{ FILE *fp = NULL;
  if( jtag_load_giveio() == FALSE)
                                          return XJERR_GIVEIO_FAIL;
  fp = fopen(“\\\\.\\giveio”,”wb”);
  paral_output(PARAL_INIT_DATA);
  SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
  return XJ_OK;
}

void jtag_wri_tms(u8 new_val)
{ port_data &= (~JTAG_TCK_MASK);
  if(new_val)
                                            port_data |= JTAG_TMS_MASK;
  else
                                            port_data &=
(~JTAG_TMS_MASK);

paral_output(port_data);
}

void jtag_wri_tdi(u8 new_val)
{ port_data &= (~JTAG_TCK_MASK);
  if(new_val)                                port_data |= JTAG_TDI_MASK;
  else                                       port_data &=
(~JTAG_TDI_MASK);
  paral_output(port_data);
}

int jtag_rd_rdo(void)
{ u8 data;
  data = paral_input();
  if(JTAG_TDO_MASK == PARAL_PIN_11){         //Invert before return
                                    if(data & JTAG_TDO_MASK)
                                          return 0;
                                    else
                                          return 1;
  }else{
  //Don’t need to invert
                                    if(data & JTAG_TDO_MASK)
                                          return 1;
                                     else
                                          return 0;
  }
}
Listato 1 – JTAG: estratto

Per gestire la porta parallela direttamente senza intermediazioni del sistema operativo è necessario utilizzare il driver giveio.sys. L’interfaccia  JTAG è inizializzata attraverso la funzione jtag_init(), come possiamo vedere la funzione si preoccupa di inizializzare la porta parallela e caricare  il device driver. Vediamo dalla funzione jtag_wri_tdi() la gestione dei segnali TDI per mezzo dei pin della parallela come posti in evidenza nello schema elettrico. Il listato 2 mostra un possibile esempio sull’uso di un’interfaccia del genere.

u8 ads[100]=
{68,0,159,229,255,16,160,227,0,16,128,229,60,0,159,229,
126,16,160,227,125,32,160,227,123,48,160,227,119,64,160,227,
238,80,160,227,222,96,160,227,190,112,160,227,0,16,128,229,
0,32,128,229,0,48,128,229,0,64,128,229,0,80,128,229,
0,96,128,229,0,112,128,229,254,255,255,234,0,80,255,3,
8,80,255,3};

  //Init ...
  jtag_init();
  tapctrl_init();
  arm7tdmi_init();

/**************************TESTING****************************/

  /*
   * Enter DEBUG state by force to download debug program
   */
  status = arm7tdmi_enter_dbgstat(&pc);
  if(status != XJ_OK)
     printf(“Failed to enter DEBUG state\n”);
 
  //Write the program to memory location 0x200000
  status = arm7tdmi_mem_wri32(0x200000, (u32*)ads, 25);
  if(status != XJ_OK)
     printf(“Failed to download program\n”);

  //Before start, set a breakpt @ 0x200000
  status = arm7tdmi_set_breakpt(0x200000);
  if(status != XJ_OK)
     printf(“Failed to set breakpt\n”);

  //Exit from DEBUG state and and start the program at 0x200000
  status = arm7tdmi_exit_dbgstat(0x200000);
  if(status != XJ_OK)
     printf(“Failed to resume the program\n”);

  //Single step debug...
  for(i = 0; i < 5; ){
     Sleep(500);

     //Enter DEBUG state?
     status = arm7tdmi_check_dbgstat(&pc);

     if(status == XJ_OK){                         //Enter DEBUG state by breakpt
        i++;
        arm7tdmi_clr_breakpt(pc);                 //Clear the breakpt first
        if(i != 5)
                 arm7tdmi_set_breakpt(pc + 4);    //Set a new breakpt
        arm7tdmi_exit_dbgstat(pc);                //Resume the program
   }
}
return 0;
Listato 2 – collaudo del codice

La variabile ads rappresenta un file binario di un possibile  eseguibile.  Il codice sorgente è caricato direttamente dal programma all’indirizzo 0x200000. Il programma al listato 2 per prima cosa inizializza il JTAG, il TAP controller  e attiva la funzionalità ARM7TDMI (presente nel listato 4) e a questo riguardo è opportuno consultare la necessaria documentazione.

 int arm7tdmi_init(void)
{
arm7tdmi_status.by = -1;
arm7tdmi_status.scanchain = 3; //On reset, scan chain 3 is selected
by default.
arm7tdmi_status.from = ARM7TDMI_FROM_ARM;
arm7tdmi_status.state = ARM7TDMI_SYSTEM_STATE;
arm7tdmi_status.endian = ARM7TDMI_LITTLE_END;

return XJ_OK;
}
Listato 3 – Inizializzazione della parte ARM7TDMI

Invece,  il listato 3 mette in evidenza l’implementazione del breakpoint software.

static int arm7tdmi_set_swbreakpt(u32 addr)
{
  int status;
  u32 temp;
  u32 instruct;
  u32 bit_pattern = 0xDEEEDEEE;
  arm7tdmi_breakpt_list_t *breakpt_new;

  //In DEBUG state?
  if(arm7tdmi_status.state != ARM7TDMI_DEBUG_STATE)
                                      return XJERR_TARGET_RUNNING;
  status = arm7tdmi_connect_scanchain(2);
  if(status != XJ_OK)
                                       return status;
 /*
  * Check the possibility.
  * If watchpt-0 is already used as a hardware break point,
  * return ERROR.
  */
  if( (arm7tdmi_breakpt_head != NULL) &&
                                       arm7tdmi_breakpt_head->type == HARDWARE_BREAKPT)
                                       return XJERR_SET_BREAKPT_FAIL;

  //First disable WP0
  arm7tdmi_ice_write(ARM7TDMI_WP0_CTRLVAL, 0x0);
 /*
  * Try to set it as software break point by replacing the instruction
  * at addr by a specific bit pattern
  */
  status = arm7tdmi_connect_scanchain(1);
  arm7tdmi_mem_rd32(addr, &instruct, 1);
  arm7tdmi_mem_wri32(addr, &bit_pattern, 1);
  arm7tdmi_mem_rd32(addr, &temp, 1);
  status = arm7tdmi_connect_scanchain(2);

  //Fail, the target instruction can’t be replaced
  if(temp != bit_pattern){
                                          arm7tdmi_ice_write(ARM7TDMI_WP0_CTRLMSK, 0x0F7);
                                          arm7tdmi_ice_write(ARM7TDMI_WP0_CTRLVAL, 0x100);
                                          return XJERR_SET_BREAKPT_FAIL;
  }

  //Succesful
  breakpt_new =
(arm7tdmi_breakpt_list_t*)malloc(sizeof(arm7tdmi_breakpt_list_t));
  if(breakpt_new == NULL){
                                          arm7tdmi_ice_write(ARM7TDMI_WP0_CTRLMSK, 0x0F7);
                                          arm7tdmi_ice_write(ARM7TDMI_WP0_CTRLVAL, 0x100);
                                          return XJ_ERROR;
  }
  breakpt_new->address = addr;
  breakpt_new->instruct = instruct;
  breakpt_new->type = SOFTWARE_BREAKPT;
  breakpt_new->next = NULL;

  breakpt_new->next = arm7tdmi_breakpt_head;
  arm7tdmi_breakpt_head = breakpt_new;
  //Configure WP0
  arm7tdmi_ice_write(ARM7TDMI_WP0_ADDRMSK, 0xFFFFFFFF);
  arm7tdmi_ice_write(ARM7TDMI_WP0_DATAVAL, bit_pattern);
  arm7tdmi_ice_write(ARM7TDMI_WP0_DATAMSK, 0x00000000);
  arm7tdmi_ice_write(ARM7TDMI_WP0_CTRLMSK, 0x0F7);

//nOPC = 0, Enable WP0 b4 exit
  arm7tdmi_ice_write(ARM7TDMI_WP0_CTRLVAL, 0x102);

return XJ_OK;
}
Listato 4 – routine breakpoint software

 

Scrivi un commento

EOS-Academy
Abbonati ora!