В этой статье речь пойдет о демо-платке от китайской конторы canton-electronics. На борту дешевенькая FPGA xc6slx9 семейства Spartan6 в корпусе tqg-144. Обвязка минимальная - пару регуляторов питания, кварцевый генератор 25 МГц, SPI-флешка M25P40 объемом 4 Мбит для загрузки прошивки, светодиоды и кнопки. Питается от разъема USB (информационные линии USB не подключены). Платка стоит 35$ + 5$ доставка. Купить можно тут . 

В принципе платку можно накатить lut-ом или домашним фоторезистом - особых мелочей нет. Схему покажу в статье. Отдельный чип xc6slx9 стоит порядка 9$, но покупать все же скорее всего придется чайне, потому что в наших магазинах такие специализированные микросхемы обычно найти сложно.  

Все незадействованные пины выведены на штырьки, так что можно развести шилд с собственной периферией.

Создавать проект я буду в среде Xininx ISE, как с ней работать я пытался рассказать в этой статье. Я напишу светодиодную мигалку и прошью её в плату по JTAG с помощью FPGALink . 

Схема отладочной платы

Чип xc6slx9t144 для удобства на схеме представлен тремя символами U1A, U1B и U1C.

Внешне ПЛИС чуть менее чем полностью состоит из пинов ввода-вывода (IO):

cantonlx9_1(1).jpg

Сигнал SYS_RST (типа сброс активируемый низким уровнем сигнала) заводится на обычный IO, поэтому на самом деле может быть использован как обычная кнопка а не обязательно как сброс - конфигурацию в ПЛИС опеределяет разработчик. А вот сигнал SYS_CLK заводится не на обычный IO а на IO с функцией GCLK(Global clock). Все синхросигналы в ПЛИС желательно заводить через пины GCLK из-за особенностей внутренней организации в которые пока не будем влазить.

Интерфейс конфигурирования ПЛИС

cantonlx9_2(1).jpg

Семейство Spartan6 относится к классу ПЛИС FPGA (Field-Programmable Gate Array, Программируемая полем вентильная матрица). Характерной особенностью данного класса является то, что конфигурация ПЛИС хранится в энергозависимой ОЗУ(RAM). Первым способом загрузки конфигурации является загрузка через JTAG (через пины TCK, TDO, TDI, TMS, выведенные на разъем). При таком способе конфигурация непосредственно попадает в RAM. Несложно догадаться, что при выключении/включении питания конфигурация пропадет. Чтобы функциональность устройства оставалась от запуска к запуску обычно используется внешнее постоянное запоминающее устройство. В данной отладочной плате в его роли используется флешка с интерфейсом SPI, объемом в 4 Мбит (около 490 кБ). При включении питания ПЛИС как бы "сама себя загружает" конфигурацией из флешки. Рассмотрим этот процесс более подробно.

Во время подачи питания на ПЛИС, ее конфигурационная часть прежде всего захватывает состояния пинов М0 (нога 69) и М1 (60). Состояние этих двух пинов определяют режим конфигурации из нескольких возможных (SPI, BPI, Master, Slave). Для нашей флешки нужен режим SPI Master, определяемый значениями M1=0, M0=1. Как видим на схеме M0 жестко посажен на 3.3 (то есть 1), а вот на М1 предусмотрен двухпозиционный джампер Р1, позволяющий переключать 3.3/GND (хотя на плате я его не заметил, а M1 просто заземлен). После того как М1-М0 захвачены и определено необходимость выполнить чтение с флешки, конфигурационный интерфейс ПЛИС по SPI посылает флешке команду "читать", и читает ответы с конфигурационными байтами. По окончании конфигурирования, пин DONE(71) становится в 1. Иногда на этот пин вешают светодиод, но в отладочной плате поставили просто подтягивающий резистор, который в случае чего можно хотя-бы пощупать тестером. После конфигурации большинство задействованные пинов доступны для использования пользовательской прошивкой(в том числе все пины SPI для флешки, что дает возможность работать с ней и после конфигурации).

Рассмотрим еще несколько пинов конфигурационного интерфейса:

  • Пин INIT_B (39) во время конфигурации опускается в 0, если при ее выполнении произошла ошибка контрольной суммы, иначе пин остается в 1, аналогично DONE - просто подтянут.
  • Пин PROGRAM_B (37) служит "глобальным сбросом" конфигурационного интерфейса - прижимая этот пин к земле можно начать конфигурацию заново.
  • Пин HSWAP(144) - включает (0) или выключает (1) подтяжки к 3.3 других пинов конфигурационного интерфейса (в том числе линии SPI). В плате как видно прижат к 0, то есть подтяжки включены. Имеет значения для линий CS, И SCK флешки, поскольку резисторов-подтяжек на схеме не установлено.

Система питания

cantonlx9_3(2).jpg

Пины питания можно разделить на 3 группы:

  1. VCCO - питание банков ввода/вывода, то есть напряжение которое будет использоваться собственно для формирования логических уровней на IO ногах ПЛИС. В нашем случае 3.3 В.
  2. VCCINT - питание внутренней логики. От этого напряжения будут питаться примитивы внутри устройства, то есть вся запрограммированная логика. Обычно 1.2 В. Собственно банки ввода/вывода внутри ПЛИС занимаются преобразованием уровней этого напряжения в уровни VCCO.
  3. VCCAUX - вспомогательное напряжение, иногда для него используют 2.5В. У нас 3.3.


Порты расширения, светодиоды и кнопки

cantonlx9_4(1).jpg

Тестовый проект и генерация SVF файла

Как создать проект и симулировать его, я рассказывал в статье про основы работы в Xilinx ISE. Создаем новый проект с такими настройками (такие же как вышеуказанной статье):

slx9board_runled1.jpg

Добавляем файл описания устройства runled.vhdl, в файле опишем логику "бегущего светодиода":

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
entity runled is
    Port ( clk : in  STD_LOGIC;
           nreset : in  STD_LOGIC;
           leds : out  STD_LOGIC_VECTOR (7 downto 0)
           );
end runled;
architecture Behavioral of runled is
    constant DELAY_TACKTS : natural := natural(0.2 * 25e6); -- count this constant at 25MHz to delay 0.2 seconds
    signal leds_internal : std_logic_vector (7 downto 0) := (0 => '1', others => '0');
    signal delay_cntr : natural range 1 to DELAY_TACKTS := 1;
begin
    leds <= leds_internal;
    process (clk, nreset)
    begin
        if (nreset = '0') then
            leds_internal <= (0 => '1', others => '0');
            delay_cntr <= 1;
        elsif rising_edge(clk) then
            if (delay_cntr = DELAY_TACKTS) then
                delay_cntr <= 1;
                leds_internal <= leds_internal(6 downto 0) & leds_internal(7);  -- cyclic shift
            else
                delay_cntr <= delay_cntr + 1;
            end if;
        end if;
    end process;
end Behavioral;

Описанная конфигурация через каждые две десятые секунды сдвигает засвечивает следующий светодиод в линейке из восьми светодиодов и тушит текущий (по сути выполняет сдвиг). Небольшие пояснения кода: константа DELAY_TACKTS определяет сколько тактов частоты в 25 МГц мы должны подождать что бы выдержать задержку в 0.2 с (так как одна секунда длится 25е6 тактов, то n секунд длится n * 25e6). 8и-разрядный сигнал leds_internal описывает значение светодиодов. Этот сигнал постоянно присваивается в порт светодиодов led. Константа (0 => '1', others => '0'), которая присваивается данному сигналу определяет что в нулевом индексе будет единичный бит, а в остальных  нулевые, по сути эту сложную на первый взгляд конструкцию можно было бы записать константой "00000001", но считать нули не очень удобно. delay_cntr - это счетчик который будет насчитывать такты синхросигнала, чтобы сформировать задержку. Здвиг выполняется путем отрезания старшего бита и приклеивание его справа(то есть старший бит стает младшим).

Симуляция

Сохраняем, выполняем поведенческую симуляцию (behevioral). В тестбенче нужно сменить уровень ресета, поскольку наше устройство использует сброс "нулем" соответственно в несброшеном состоянии ресет должен находится в "1".  Для этого в строке тестбенча:

   signal nreset : std_logic := '0';

нужно заменить '0' на '1'. Также нужно установить частоту сигнала в 25 МГц, для этого изменим строку:

constant clk_period : time := 10 ns;

 заменив в ней значение в 10 ns на 40 ns. 

После запуска симулятора на панели вверху вводим время симуляции (2 секунды) и нажимаем на кнопку "Run for time specified on the toolbar":

В симуляции вы должны увидеть что-то вроде такого:

slx9board_runled2.jpg

Симуляция Post-route должна дать такой же результат, но ее выполнение для такого большого интервала времени будет весьма долгим.

Назначение пинов

После того как в имуляции мы убидились что устройство работает в симуляторе переходим к подготовке загрузки конфигурации в ПЛИС. Назначение пинов представляет собой процесс наложения так называемых констреинтов (ограничений). Ограничение заключается в том, что вы говорите синтезатору, что бы он обязательно повесил такую-то цепь на такой-то пин микросхемы ПЛИС. 

Констреинты назначаются в файле .ucf. Создадим его, для этого в контекстном меню в окне Design делаем Add New -> Implementation Constraints File -> File name: runled.ucf -> Next -> Finish. Вводим в файл следующее:

NET "nreset"    LOC = P84;
NET "clk"         LOC = P85;
NET "leds[0]"   LOC = P1;
NET "leds[1]"   LOC = P2;
NET "leds[2]"   LOC = P5;
NET "leds[3]"   LOC = P6;
NET "leds[4]"   LOC = P7;
NET "leds[5]"   LOC = P8;
NET "leds[6]"   LOC = P9;
NET "leds[7]"   LOC = P10;
NET "nreset"    IOSTANDARD = LVCMOS33;
NET "clk"         IOSTANDARD = LVCMOS33;
NET "leds[0]"   IOSTANDARD = LVCMOS33;
NET "leds[1]"   IOSTANDARD = LVCMOS33;
NET "leds[2]"   IOSTANDARD = LVCMOS33;
NET "leds[3]"   IOSTANDARD = LVCMOS33;
NET "leds[4]"   IOSTANDARD = LVCMOS33;
NET "leds[5]"   IOSTANDARD = LVCMOS33;
NET "leds[6]"   IOSTANDARD = LVCMOS33;
NET "leds[7]"   IOSTANDARD = LVCMOS33;

Во второй половине файла задаются уровни 3.3 В.

Констреинты учитываются на стадиях процесса имплементации, то есть после их введения мы должны как минимум выполнить заново имплементацию проекта. ISE уведомляет нас об эьтом показывая около группы процессов имплементации знаки вопроса в оранжевых кругах вместо галочки:

После успешного выполнения повторной имплементации можно приступать к созданию файла прошивки, это файл .bit (bitstream). Для его генерации даблкликаем на процесс Generate Programming File в окне процессов (сразу под имплементацией). В папке проекта будет создан runled.bit.

Создание xsvf файла

Созданный bit файл содежит прошивку для ПЛИС, но для программатора FPGALink его нужно преобразовать к формату .xsvf (файлы этого формата состоят из последовательностей JTAG-комманд).

Запускаем iMPACT (в меню ISE выбираем Tools -> iMPACT). Создаем в нем новый проект: File -> New Project, YES -> Prepare a boundary-Scan File: XSVF -> OK. В диалоге сохраняем как runled.xsvf (в папке проекта). OK. В сдедующем диалоге выбираем файл битстрима runled.bit (который был получен ранее). 

В главном окне iMPACT вызываем контекстное меню по иконке чипа и нажимаем Program:

ОК. Ctrl+S и выходим из iMPACT. В корне проекта должен быть файл runled.xsvf.

Конфигурация при помощи FPGALink по JTAG

Первым делом подключаем к плате линии JTAG: TMS, TDI, TDO, TCK + GND. Можно соорудить переходник IDC10 <-> IDC14:

IMG_20140630_175022.jpg

Подаем на плату питание и подключаем FPGALink.

Открываем командную строку и в ней выполняем:

flcli -v 1d50:602b:0001 -p J:B3B2B0B1:runled.xsvf

В linux нужно выполнять от root (например через sudo), либо можно добавить файл правил в UDEV:

sudo bash
echo SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1d50\", ATTRS{idProduct}==\"602b\", ACTION==\"add\", MODE=\"0666\" > /etc/udev/rules.d/70-fpgalink.rules

На этом пока все. Теоретически, таким образом можно прошить любую FPGA ПЛИС Xilinx, практически нужно пробовать. Я проверял на данной моделе ПЛИС а также на xc3s500 (семейства Spartan-3e).

Видео (качество не торт):