Зачастую если в устройстве есть программируемая логика, присутствует и процессор/микроконтроллер.

В какой-то момент мне надоело разводить на платах разъем JTAG, он занимает много места на плате и по сути нужен только для разработки. В конечном устройстве он вообще без надобности.

Очень часто для проверки правильности реализации Verilog кода или вообще "посмотреть как сигнальчики бегают" я использую SignalTap II Logic Analyzer, штука удобная и наглядная, я думаю многие сразу узнают по изображению:

Но как же отлаживать само устройство и в частности программируемую логику без JTAG?

Я уж молчу о записи прошивок в CPLD на этапе производства.

А у нас есть микроконтроллер.

Возьмем к примеру микроконтроллер STM32F103RCT6 и реализуем в нем USB Byte Blaster. И возьмем CPLD EPM3064.

Разумеется я имею ввиду что микроконтроллер уже подключен к соответствующим выводам программируемой логики, например вот так:

PC12->TDI

PC13->TMS

PC14->TCK

PC15->TDO

USB Устройство начинается с дескриптора, опишем его:

USB Descriptor
/* USB Standard Device Descriptor */
const BYTE USB_DeviceDescriptor[] = {
  USB_DEVICE_DESC_SIZE,              /* bLength */
  USB_DEVICE_DESCRIPTOR_TYPE,        /* bDescriptorType */
  WBVAL(0x0110), /* 1.10 */          /* bcdUSB */
    0x00,                   // Class Code
    0x00,                   // Subclass code
    0x00,                   // Protocol code
    USB_MAX_PACKET0,          // Max packet size for EP0, see usbcfg.h
  WBVAL(0x09FB),                     /* idVendor */
  WBVAL(0x6001),                     /* idProduct */
  WBVAL(0x0400), /* 1.00 */          /* bcdDevice */
    0x01,                   // Manufacturer string index
    0x02,                   // Product string index
    0x03,                   // Device serial number string index
    0x01                    // Number of possible configurations	
};

/* USB Configuration Descriptor */
/*   All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
const BYTE USB_ConfigDescriptor[] = {
    /* Configuration Descriptor */
  USB_CONFIGUARTION_DESC_SIZE,       /* bLength */
  USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType */
/* Configuration 1 */
  WBVAL(                            /* wTotalLength */
    (1*USB_CONFIGUARTION_DESC_SIZE) +
    (1*USB_INTERFACE_DESC_SIZE) +
    (2*USB_ENDPOINT_DESC_SIZE)
  ),
    1,                      // Number of interfaces in this cfg
    1,                      // Index value of this configuration
    0,                      // Configuration string index
  USB_CONFIG_SELF_POWERED, /*|*/       /* bmAttributes */
  USB_CONFIG_POWER_MA(80),          /* bMaxPower */

    /* Interface Descriptor */
  USB_INTERFACE_DESC_SIZE,           /* bLength */
  USB_INTERFACE_DESCRIPTOR_TYPE,     /* bDescriptorType */
/* Interface 0, Alternate Setting 0, MSC Class */
  0x00,                              /* bInterfaceNumber */
  0x00,                              /* bAlternateSetting */
  0x02,                              /* bNumEndpoints */
    0xFF,                   // Class code
    0xFF,                   // Subclass code
    0xFF,                   // Protocol code
    0,                      // Interface string index
    
    /* Endpoint Descriptor */
/* Bulk In Endpoint */
  USB_ENDPOINT_DESC_SIZE,            /* bLength */
  USB_ENDPOINT_DESCRIPTOR_TYPE,      /* bDescriptorType */
  USB_ENDPOINT_IN(BLST_EP_IN & 0x0F),                /* bEndpointAddress */
  USB_ENDPOINT_TYPE_BULK,            /* bmAttributes */
  WBVAL(BLST_MAX_PACKET),                     /* wMaxPacketSize */
    10,                         //Interval
    
  USB_ENDPOINT_DESC_SIZE,            /* bLength */
  USB_ENDPOINT_DESCRIPTOR_TYPE,      /* bDescriptorType */
  USB_ENDPOINT_OUT(BLST_EP_OUT & 0x0F),               /* bEndpointAddress */
  USB_ENDPOINT_TYPE_BULK,            /* bmAttributes */
  WBVAL(BLST_MAX_PACKET),                     /* wMaxPacketSize */
    10                          //Interval	
/* Terminator */
  ,0                                  /* bLength */
};

//Language code string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[1];}sd000={
sizeof(sd000),USB_STRING_DESCRIPTOR_TYPE,{0x0409}};

//Manufacturer string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[6];}sd001={
sizeof(sd001),USB_STRING_DESCRIPTOR_TYPE,
{'A','l','t','e','r','a'}};

//Product string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[11];}sd002={
sizeof(sd002),USB_STRING_DESCRIPTOR_TYPE,
{'U','S','B','-','B','l','a','s','t','e','r'}};

//Serial string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[8];}sd003={
sizeof(sd003),USB_STRING_DESCRIPTOR_TYPE,
{'0','0','0','0','0','0','0','0'}};

//Array of configuration descriptors
ROM BYTE *ROM USB_CD_Ptr[]=
{
    (ROM BYTE *)&USB_ConfigDescriptor
};
//Array of string descriptors
ROM BYTE *ROM USB_SD_Ptr[]=
{
    (ROM BYTE *)&sd000,
    (ROM BYTE *)&sd001,
    (ROM BYTE *)&sd002,
    (ROM BYTE *)&sd003
};

/* USB String Descriptor (optional) */
const BYTE *USB_StringDescriptor = (const BYTE *)USB_SD_Ptr;

Далее нам нужно прописать саму "начинку" Byte Blaster'a:

blaster.c
BYTE fifo_wp,fifo_rp;
BYTE InFIFO[256];

BYTE nCS = 0;

DWORD PacketPos = 0;
BYTE InPacket[64];
BYTE OutPacket[128];
BYTE ep1_ready = 1;

void EP2CallBack(void)
{
	ProcBlasterData();
}

void ProcBlasterData(void)
{
	int bufptr = 0;
	static BYTE jtag_byte = 0, read = 0, aser_byte = 0;
	DWORD recv_byte;
	BYTE acc0, acc1;
	
	recv_byte = USB_ReadEP(BLST_EP_OUT, OutPacket);

	bufptr = 0;
	
	if(!recv_byte) return;
	LED_RD_ON();
	
	do
	{
		if (jtag_byte)
		{
			if (!read)
			{
				do
				{

					acc0 = OutPacket[bufptr++];
					JTAG_Write(acc0);
					jtag_byte--;
					recv_byte--;
				} while (jtag_byte && recv_byte);
			}
			else
			{
				do
				{

					acc0 = OutPacket[bufptr++];
					acc1 = JTAG_RW(acc0);
					enqueue(acc1);
					jtag_byte--;
					recv_byte--;
				} while (jtag_byte&&recv_byte);
			}

		}
		else if (aser_byte)
		{
			if (!read)
			{

				do
				{

					acc0 = OutPacket[bufptr++];
					JTAG_Write(acc0);
					aser_byte--;
					recv_byte--;
				} while (aser_byte&&recv_byte);
			}
			else
			{
				do {
					acc0 = OutPacket[bufptr++];
					acc1 = ASer_RW(acc0);
					enqueue(acc1);
					aser_byte--;
					recv_byte--;
				} while (aser_byte&&recv_byte);
			}
		}
		else
		{
			do
			{
				acc0 = OutPacket[bufptr++];
				_bitcopy(bitmask(acc0, 6), read);
				if (bitmask(acc0, 7))
				{		//EnterSerialMode
					LTCK(0);		//bug fix
					
					if (nCS & 0x8) 
					{	//nCS=1:JTAG
						jtag_byte = acc0 & 0x3F;
					}
					else 
					{		//nCS=0:ActiveSerial
						aser_byte = acc0 & 0x3F;
					}
					/* Always JTAG Made */
					recv_byte--;
					break;
				}
				else
				{			//BitBangMode
					OUTP(acc0);
					if (read) {
						acc1 = 0;
						if (PADO) acc1 |= 0x02;
						if (PTDO) acc1 |= 0x01;
						enqueue(acc1);
					}
					recv_byte--;
				}
			} while (recv_byte);
		}
	} while (recv_byte);

	/* Disable RD LED */
	LED_RD_OFF();
	return;
}

void EP1CallBack(void)
{
	ep1_ready = 1;

	return;
}

void OUTP(BYTE b)
{
	unsigned int uiPortState = GPIOC->ODR;
	/* 	TCK - 0
			TMS - 1
			nCE - 2
			nCS - 3
	x - 5,6
			TDI - 4,7
	*/
	if(b & (1 << 3)) /* nCS */
		nCS = 0x08;
	else
		nCS = 0;
	
	if(b & (1 << 0)) /* TCK */
		uiPortState |= (1 << 14);
	else
		uiPortState &= ~(1 << 14);

	if(b & (1 << 1)) /* TMS */
		uiPortState |= (1 << 13);
	else
		uiPortState &= ~(1 << 13);

	if(b & (1 << 4)) /* TDI */
		uiPortState |= (1 << 12);
	else
		uiPortState &= ~(1 << 12);
	
	GPIOC->ODR = uiPortState;
	
//	Nop();
	
	return;
}

void JTAG_Write(BYTE a) 
{
	int i;
	
	for(i = 0;i < 8;i ++)
	{
		bitcopy(a & (0x01 << i),LTDI);
		tck();
	}
}

BYTE JTAG_RW(BYTE a)
{
	BYTE bRet=0;
	int i = 0;
	
	for(i = 0;i < 8;i++)
	{
		bitcopy(a & (0x01 << i),LTDI);
		if(PTDO) bRet |= (0x01 << i);
		tck();
	}
	
	return bRet;
}

BYTE ASer_RW(BYTE a) {
	BYTE bRet=0;
	int i = 0;
	
	for(i = 0;i < 8;i++)
	{
		bitcopy(a & (0x01 << 1),LTDI);
		if(PADO) bRet|= (0x01 << 1);
		tck();
	}		
	
	return bRet;
}

extern USB_EP_DATA EP0Data;
BYTE bBuffer[10];

void USB_EP0BlasterReq(USB_SETUP_PACKET *SetupPacket)
{
	BYTE bIndex;

	if (SetupPacket->bmRequestType.BM.Dir == 0)
	{	//0utput
		//Responce by sending zero-length packet
		//I don't know if this way is right, but working:)
		USB_WriteEP(0x80, NULL, 0);
		return;
	}

	if (SetupPacket->bRequest == 0x90)
	{
		bIndex = (SetupPacket->wIndex.WB.L << 1) & 0x7E;
		bBuffer[0] = eeprom_read(bIndex);
		bBuffer[1] = eeprom_read(bIndex + 1);
	}
	else
	{
		bBuffer[0] = 0x36;
		bBuffer[1] = 0x83;
	}

	EP0Data.pData = bBuffer;
	EP0Data.Count = 2;
	//		USB_WriteEP(0x80, bBuffer, 2);

	return;
}

И в финале добавим обработчик в нашу программу. Можно это сделать конечно и в таймере, но для наглядности я добавил функцию обработки в main() в бесконечном цикле.

Смотрим код:

main.c
  while (1) 
	{
		if(USB_Configuration)
		{
			if(ep1_ready)
			{
				acc0 = fifo_used();
				if (62 <= acc0) 
				{		//send full packet to host
					LED_WR_ON();
					ep1_ready = 0;
					dequeue(&InPacket[2], 62);
					USB_WriteEP(BLST_EP_IN, InPacket, 64);
					ChargeTimer_ms(10);
				}
				else if(acc0 || IsTimerArrived())
				{
					if(acc0)
						LED_WR_ON();
					else						
						LED_WR_OFF();
					ep1_ready = 0;
					if(acc0)
						dequeue(&InPacket[2], acc0);
					USB_WriteEP(BLST_EP_IN, InPacket, acc0 + 2);
					ChargeTimer_ms(10);
				}
			}
		}
  } // end while

Собираем всё вместе и компилируем и прошиваем, используя вот такой игровой набор:

И подключаем наше устройство к USB.

И это же конечно ничего не значит. Потому что установка происходит только на основании дескриптора устройства, теперь проверим действительно ли это у нас "настоящий" Byte Blaster.

Выбираем:

И запускаем JTAG Chain Debugger:

Работает!!! Можем писать CPLD прям на борту нашего устройства.

К слову сказать если подключить эту реализацию к FPGA, будет доступна запись в FPGA и SignalTap II. У меня всё.

Спасибо за внимание! Успехов в делах и отличного настроения!