MIPSfpga и прерывания

В статье приводится несколько примеров настройки и использования прерываний MIPS32 Release 2, включая подробное описание задаваемой при этом конфигурации, описывается работа с контроллером внешних прерываний.

Весь описываемый код опубликован на github в составе проекта mipsfpga-plus [L3].

Введение

Предполагается, что читатель:

  • знаком с предметной областью в объеме учебника Харрис-энд-Харрис [L1];
  • имеет некоторый опыт программирования микроконтроллеров (любой архитектуры), включая использование таймеров и прерываний;
  • имеет доступ к исходным кодам MIPSfpga [L2] и mipsfpga-plus [L3];

Написанием этой статьи я не ставлю цель всесторонне изложить все особенности работы с прерываниями для ядер MIPS microAptiv, т.к. это потребовало бы масштабного перевода документации и написания примерно такого же объема комментариев. Цель — показать работающие примеры настройки и использования прерываний в трех возможных режимах: обратной совместимости, векторном и с использованием внешнего контроллера. Поэтому для того, чтобы читатель чувствовал себя более комфортно, рекомендуется бегло ознакомиться со следующими разделами документации:

  • MIPS32 microAptiv UP Processor Core Family Integrator’s Guide [D1]
    (раздел 4 «Interrupt Interface»);
  • MIPS32 microAptiv UP Processor Core Family Software User’s Manual [D2]
    (разделы: 5.1-5.6 «Exceptions and Interrupts in the microAptiv UP Core», 6.2.13 «Count Register», 6.2.15 «Compare Register», 6.2.16 «Status Register», 6.2.17 «IntCtl Register», 6.2.22 «Cause Register», 6.2.28 «EBase Register», 6.2.33 «Config3 Register»);
  • Codescape GNU Tools for MIPS Programmer’s Guide [D3]
    (разделы: 15.5. «Exceptions», 15.6. «Interrupts»).

Во всех примерах для наглядности описывается запуск на системе mipsfpga-plus, работающей в симуляторе. С равным успехом это может быть выполнено и на железе — работоспособность всего кода проверена на плате Terasic DE10-Lite [L4].

Принятые обозначения

Наименования сигналов выделены курсивом ( SI_Int[7:0] ), если не указано обратное, то все сигналы являются интерфейсными для верхнеуровнего модуля системы MIPSfpga (m14k_top). Для регистров используется насыщенный шрифт ( Count ), названия отдельных битов (полей) приводятся через точку с указанием регистров, к которым они относятся ( Cause.DC ), на отдельных выкопировках из документации поля регистров могут быть обозначены подстрочными символами ( CauseIV ). Для всех регистров предполагается ширина x32, если это не оговаривается отдельно. При указании констант в выкопировках из документации встречается указание системы счисления перед символом «решетка» (16#180, 2#00001).

Т.к. в MIPSfpga, к сожалению, не доступна опция «GPR Shadow Registers» (Shadow Register Set, SRS), то в статье опущены детали, связанные с теневыми регистрами общего назначения. Там, где эта функциональность встречается на схемах — она отражена серым (блеклым) цветом.

Обработка исключений

Под исключениями понимаются все события, которые приводят к переходу процессора в kernel mode: ошибки доступа к памяти, деление на ноль и т.д., включая запросы прерываний от внешних устройств. Обработка всех возможных исключений является обширной темой, которую не корректно рассматривать в отрыве от ядра операционной системы (ОС), что явно выходит за рамки данной статьи. Вместе с тем, даже если использование ОС не предполагается (Bare Metal code), разработчику все же следует предусмотреть минимальный набор обработчиков в памяти, чтобы узнать о факте возникновения исключения и не допустить ухода выполнения программы в «произвольное плавание».

В слегка сокращенном виде алгоритм определения адреса обработчика представлен ниже.

if Status.EXL = 1 then
    vectorOffset ← 16#180
else
    if ExceptionType = TLBRefill then
        vectorOffset ← 16#000
    elseif (ExceptionType = Interrupt) then
        // определение смещения вектора обработчика прерывания
        if (Cause.IV = 0) then
            vectorOffset ← 16#180
        else
            if (Status.BEV = 1) or (IntCtl.VS = 0) then
                vectorOffset ← 16#200
            else
                if Config3.VEIC = 1 then
                    VecNum ← Cause.RIPL
                else
                    VecNum ← VIntPriorityEncoder()
                endif
                vectorOffset ← 16#200 + (VecNum × (IntCtl.VS || 2#00000))
            endif
        endif
    endif
endif

Cause.ExcCode ← ExceptionType
// признак того, что ядро находится в процессе входа в обработчик исключения
Status.EXL ← 1
// вычисление базового адреса, по которому размещена "таблица прерываний"
if Status.BEV = 1 then
    vectorBase ← 16#BFC00200
else
    // фиксированное значение EBase[31:30] = 2'b10
    // ограничивает размещение векторов в сегментах памяти kseg0 или kseg1
    vectorBase ← EBase[31:12] || 16#000
endif

// переход для обработки прерывния
// адрес обработчика вычисляется как сумма vectorBase и vectorOffset
// перенос из разряда 29 в разряд 30 при этом не выполняется
PC ← {vectorBase[31:30], (vectorBase[29:0] + vectorOffset[29:0])}

В приведенном алгоритме не учтены следующие моменты:

  • исключения, которые приводят к сбросу процессора (Reset, SoftReset, NMI — вызывают переход на 16#BFC00000);
  • исключения EJTAG;
  • Cache/SPRAM Parity error, которое при BEV = 0 вместо кэшируемого сегмента kseg0 отображается в некэшируемый сегмент kseg1 (vectorBase[29] = 1’b1) и EBase[29:12] = 0 в этом конкретном случае означает не 16#80000000, а 16#A0000000. В нашем случае это не критично, т.к. в проекте MIPSfpga сегменты памяти пересекаются (см. RTL код модуля mfp_ahb_lite_decoder) и старшие три бита адреса игнорируются при доступе к памяти;
  • исключение XTLB Refil со смещением 0x80, которое не описано в [D2], но упоминается в [D3] и всех поставляемых примерах кода. Если кто-то из читателей может прояснить этот момент — буду благодарен за информацию.

В полном объеме информацию об исключениях можно найти в [D2].

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

  • 16#000 — TLB Refill Handler;
  • 16#080 — XTLB Refil;
  • 16#100 — Cache/SPRAM Parity error;
  • 16#180 — General Exception

Пример простейших обработчиков будет приведен ниже.

Режимы обработки прерываний

Их всего три:

  • Interrupt Compatibility mode — режим совместимости;
  • Vector Interrupt (VI) mode — векторный режим;
  • External Interrupt Controller (EIC) mode — режим контроллера внешних прерываний.

По-умолчанию процессор стартует в режиме совместимости (Compatibility). В дальнейшим он может быть изменен, путем установки соответствующих значений битов (полей):

  • BEV — определяет базовый адрес, от которого высчитывается смещение вектора обработчика прерываний, задается программно, значение после сброса — 1;
  • Cause.IV — определяет смещение вектора обработчика прерываний, задается программно, значение после сброса — не определено;
  • IntCtl.VS — Vector Spacing, определяет смещение между векторами, задается программно, значение после сброса — 0;
  • ConfigVINT — Vectored interrupts, доступен только для чтения, равен 1 для всех microAptiv ядер, в т.ч. и для MIPSfpga;
  • ConfigVEIC — определяет наличие контроллера внешних прерываний, доступен только для чтения, значение задается сигналом SI_EICPresent.

Полное описание перечисленных битов (полей) и регистров, к которым они относятся приведено в документации, отметим влияние совокупности их значений на режим обработки прерываний:

Системный таймер

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

Для работы с ним используются два регистра:

Count Регистр счетчика. Если Cause.DC == 0 (disable count register, default = 0), увеличивается на единицу на каждом такте процессора. Единственная возможность сбросить значения счетчика — записать в него новое значение (например нулевое), сигнал прерывания по таймеру (вызванный равенством Count == Compare) не приводит к автоматическому сбросу счетчика;
Compare Регистр сравнения. Когда Count == Compare и Compare != 0, сигнал SI_TimerInt, который служит источником прерывания, устанавливается в единицу, одновременно выставляется бит Cause.TI (timer interrupt). Для сброса этого сигнала необходимо выполнить запись в Compare.

Для работы с этими регистрами удобно использоваться макросы mips32_setcompare и mips32_setcount, которые объявлены в mips/cpu.h, для инициализации и сброса таймера, фактически, используется один и тот же код [S0]:

mips32_setcompare(MIPS_TIMER_PERIOD);  //set compare (TOP) value to turn on /reset timer
mips32_setcount(0);                    //reset counter

На скриншоте ниже видны моменты инициализации таймера и его сброса при обработке прерывания.

Наличие сигнала SI_TimerInt не является достаточным для вызова прерывания. Для того, чтобы он был обработан, должна быть выполнена его корректная маршрутизация на уровне RTL, которая зависит от текущего режима обработки прерываний (Interrupt mode), о чем будет сказано ниже.

Interrupt Compatibility mode

Режим обратной совместимости с MIPS32 Release 1. Основные особенности:

  • доступно 8 внешних прерываний: входы SI_Int[7:0], которые соответствуют флагам IP9 IP2;
  • доступно 2 программных прерывания: IP1 IP0;
  • вне зависимости от входа, на котором возникло прерывание, управление передается в один единственный обработчик, который, помимо прочего, отвечает за обработку исключений;
  • аппаратная приоритизация прерываний отсутствует;
  • определение наиболее приоритетного прерывания остается за разработчиком и выполняется внутри обработчика, путем анализа IP9 IP0, Cause.TI.
  • выход прерывания по таймеру ( SI_TimerInt ) должен быть ассоциирован с одним из входов SI_Int[7:0]. Для этого не обязательно явно соединять сигналы между собой, можно использовать вход SI_IPTI[2:0], значение на котором 0x2 означает, что SI_TimerInt подключен к SI_Int[0], соответственно 0x7 будет означать подключение к SI_Int[5]. Традиционным считается подключение к SI_Int[5].
  • если говорить про процессоры MIPS32 Release 1, то в них было доступно только 6 внешних прерываний (IP9-IP8 в них отсутствуют) [D4], отсюда унаследована невозможность переключить прерывание таймера средствами SI_IPTI[2:0] на какой-либо порт больше, чем SI_Int[5]. Таким образом, режим обратной совместимости дает несколько больше возможностей, чем было изначально доступно в MIPS32 Release 1.

Те, кто получил первый опыт разработки под встраиваемые системы на относительно современных микроконтроллерах, могут испытать некоторое недоумение от «всего» 6 внешних прерываний и одного общего обработчика на все, включая львиную долю исключений. Здесь следует учесть некоторую историчность архитектуры MIPS: предполагалось, что источники прерываний будут подключаться иерархически, учитывалось, что код входа в (выхода из) прерывание у всех обработчиков общий, а т.к. внешние устройства, по своей сути, медленные (события возникаю редко), то нет и особых потерь в производительности от «ручной» проверки источников и приоритетов, и даже есть некоторый простор для возможных оптимизаций. Пример такого подключения из [L5] приведен ниже.

Даже если считать эти особенности «недостатком», они легко компенсируются возможностью использования внешнего контроллера прерываний, подключенного через соответствующий интерфейс процессорного ядра MIPSfpga, о чем будет рассказано в соответствующем разделе.

Пример

Порядок запуска

  • проверить, что в файле mfp_ahb_lite_matrix_config.vh закомменирована строка [S1]:
//`define MFP_USE_IRQ_EIC

и установлен режим:

`define MFP_USE_WORD_MEMORY
  • перейти в каталог mipsfpga-plus/programs/06_timer_irq/;
  • в файле main.c установить [S2]:
#define RUNTYPE    COMPATIBILITY
  • выполнить сборку программы и ее запуск в симуляторе:
02_compile_and_link.bat
05_generate_verilog_readmemh_file.bat
06_simulate_with_modelsim.bat
  • после завершения работы скрипта симуляции нажать «нет», чтобы предотвратить закрытие симулятора;

Описание программы и конфигурации системы

  • установленные в заголовочных файлах RTL настройки приводят к следующей конфигурации интерфейса прерываний процессорного ядра [S3]:
assign SI_Offset      = 17'b0; //not used
assign SI_EISS        =  4'b0; //not used
assign SI_Int[7:4]    =  4'b0;
assign SI_Int[3]      =  uart_interrupt;
assign SI_Int[2:0]    =  3'b0;
assign SI_EICVector   =  6'b0; //not used
assign SI_EICPresent  =  1'b0; //no external interrupt controller
assign SI_IPTI        =  3'h7; //enable MIPS timer interrupt on HW5
  • файл exceptions.S содержит необходимые для работы векторы прерываний, они все, практически, однотипные [S4]:
    .org    0x200                       # set symbol offset from section beginning
    .weak   __mips_isr_sw0              # if the symbol does not already exist, it will be created
__isr_vec_sw0:
    la      $k1, __mips_isr_sw0         # load interrupt handler (__mips_isr_sw0) addr
    beqz    $k1, __general_exception    # if it is not present then go to generic 
    nop
    jr      $k1                         # jump to irq_sw0
    nop
  • векторы исключений отличаются от векторов прерываний тем, что в случае, если соответствующий обработчик в программе не определен, выполняется зацикливание кода [S5]:
    .org    0x0                     # set symbol offset from section beginning
    .weak   _mips_tlb_refill        # if the symbol does not already exist, it will be created
__tlb_refill:
    la      $k1, _mips_tlb_refill   # load exception handler (_mips_tlb_refill) addr
    beqz    $k1, __tlb_refill       # if _mips_tlb_refill doen not exist then just loop here
    nop
    jr      $k1                     # jump to _mips_tlb_refill.
                                    # we can use 'j _mips_tlb_refill' 
    nop                             # but it works only with 1st 28 bits of addr
  • в файле mian.c осуществляется последовательная инициализация таймера [S0]:
mips32_setcompare(MIPS_TIMER_PERIOD);   //set compare (TOP) value to turn timer on
mips32_setcount(0);                     //reset counter
  • инициализация прерываний [S6]:
//compatibility mode, one common handler
// Status.BEV  0 - place handlers in kseg0 (0x80000000)
mips32_bicsr (SR_BEV);
// Cause.IV,   0 - general exception handler (offset 0x180)
mips32_biccr (CR_IV);
// interrupt enable, HW5, SR_SINT1 - unmasked
mips32_bissr (SR_IE | SR_HINT5 | SR_SINT1);
  • и их обработка в одном обработчике, предполагающая последовательные: проверку, является ли данное исключение прерыванием, определение того, какое именно прерывание необходимо обработать, и непосредственно саму обработку [S7]:
void __attribute__ ((interrupt, keep_interrupts_masked)) _mips_general_exception ()
{
    MFP_RED_LEDS = MFP_RED_LEDS | 0x1;
    uint32_t cause = mips32_getcr();

    //check that this is interrupt exception
    if((cause & CR_XMASK) == 0)
    {
        //check for timer interrupt
        if(cause & CR_HINT5)
        {
            MFP_RED_LEDS = MFP_RED_LEDS | 0x10;
            n++;
            mipsTimerReset();
            mips32_biscr(CR_SINT1);     //request for software interrupt 1
            MFP_RED_LEDS = MFP_RED_LEDS & ~0x10;
        }

        //check for software interrupt 1
        else if (cause & CR_SINT1)
        {
            MFP_RED_LEDS = MFP_RED_LEDS | 0x8;
            mips32_biccr(CR_SINT1);     //clear software interrupt 1 flag
            MFP_RED_LEDS = MFP_RED_LEDS & ~0x8;
        }
    }
    MFP_RED_LEDS = MFP_RED_LEDS & ~0x1;
}
  • в данном примере для проверки прерывания системного таймера используется флаг ассоциированного прерывания (HW5), для этой же цели можно использовать TI (макрос CR_TI);
  • каждый из этапов работы программы отражается соответствующим состоянием MFP_RED_LEDS;
  • в прерывании по таймеру (HW5) осуществляется инкремент счетчика, сброс таймера и выставляется флаг запроса программного прерывания SW1 [S8]:
n++;
mipsTimerReset();
mips32_biscr(CR_SINT1);
  • в прерывании SW1 выполняется только сброс флага прерывания [S9]:
mips32_biccr(CR_SINT1);
  • все используемые при этом макросы для работы с флагами объявлены в mips/cpu.h;
  • в коде функции main выполняется циклический вывод счетчика на 7-сегментные индикаторы [S10]:
for (;;)
MFP_7_SEGMENT_HEX = n;
  • результатом работы программы является диаграмма сигналов, аналогичная приведенной ниже. Обратите внимание на то, что мы можем наблюдать значения регистров, в частности Cause и Status, PC, Count и Сompare, в реальном масштабе времени, что значительно упрощает отладку.

Vector Interrupt mode

Векторный режим обработки прерываний.

Основные особенности:

  • количество и состав обрабатываемых прерываний (8 внешних и 2 программных), а также порядок работы с SI_TimerInt полностью аналогичны Interrupt Compatibility mode;
  • для обработки каждого из прерываний используется свой обработчик (вектор);
  • прерывания имеют приоритеты, которые определяют порядок их обработки процессорным ядром. В порядке возрастания приоритета: SW0, SW1 (программные прерывания), HW0-HW7 (аппаратные прерывания, соответствуют сигналам SI_Int[7:0])

  • первый из обработчиков векторных прерываний размещается на смещении 0x200;
  • смещение между первым и вторым, а также всеми последующими обработчиками определяется полем IntCtl.VS. Так для IntCtl.VS = 1, это смещение будет 0x20:

Пример

Порядок запуска

  • полностью аналогичен таковому для режима обратной совместимости, за исключением того, что необходимо в файле main.c установить [S2]:
#define RUNTYPE    VECTOR

Описание программы и конфигурации системы

  • RTL конфигурация системы аналогична таковой для режима обратной совместимости;
  • используется тот же самый файл exceptions.S [S4, S5]и абсолютно идентичный порядок инициализации таймера [S0];
  • настройка работы прерываний выполняется следующим образом [S11]:
//vector mode, multiple handlers
// Status.BEV  0 - place handlers in kseg0 (0x80000000)
mips32_bicsr (SR_BEV);

// Cause.IV,   1 - special int vector (offset 0x200),
//                 where 0x200 - base for other vectors
mips32_biscr (CR_IV);

// get IntCtl reg value
uint32_t intCtl = mips32_getintctl();

// set interrupt table vector spacing (0x20 in our case)
// see exceptions.S for details
mips32_setintctl(intCtl | INTCTL_VS_32);

// interrupt enable, HW5 and SW0,SW1 - unmasked
mips32_bissr (SR_IE | SR_HINT5 | SR_SINT0 | SR_SINT1);
  • порядок работы программы абсолютно аналогичен ранее описанному, за исключением того, что в прерывании по таймеру (HW5) осуществляется установка флагов не одного, а сразу двух программных прерываний (SW0 и SW1), которые затем будут обработаны в порядке приоритета [S12]:
void __attribute__ ((interrupt, keep_interrupts_masked)) __mips_isr_hw5 ()
{
    MFP_RED_LEDS = MFP_RED_LEDS | 0x4;

    n++;
    mipsTimerReset();

    mips32_biscr(CR_SINT0);     //request for software interrupt 0
    mips32_biscr(CR_SINT1);     //request for software interrupt 1

    MFP_RED_LEDS = MFP_RED_LEDS & ~0x4;
}
  • для обработки прерывания SW0 используется отдельный вектор [S13]:
void __attribute__ ((interrupt, keep_interrupts_masked)) __mips_isr_sw0 ()
{
    MFP_RED_LEDS = MFP_RED_LEDS | 0x2;
    mips32_biccr(CR_SINT0);     //clear software interrupt 0 flag
    MFP_RED_LEDS = MFP_RED_LEDS & ~0x2;
}
  • обработка SW1 осуществляется все тем же _mips_general_exception, что и в случае режима обратной совместимости, но с важным дополнением: переход в эту функцию происходит не потому, что процессор сразу вышел на смещение 0x180. Выполнение передается на 0x220, но т.к. функция __mips_isr_sw1 не объявлена, то уходит на __general_exception (это выполняется ассемблерным кодом из exceptions.S, который приведен выше) [S14].
  • результатом работы программы является диаграмма сигналов, аналогичная следующей:

Контроллер внешних прерываний

Перед тем, как переходить к описанию работы в режиме External Interrupt Controller — контроллера внешних прерываний, рассмотрим как этот модуль взаимодействует с процессорным ядром и что он из себя представляет.

Задача контроллера — выполнять регистрацию внешних прерываний и выдавать на шину (Interrupt Interface) сведения о наиболее приоритетном из них:

  • SI_EICPresent — признак наличия контроллера внешних прерываний;
  • SI_Int[7:0] — приоритет запрашиваемого прерывания;
  • SI_EISS[3:0] — определяет номер набора теневых регистров (shadow set number), в случае MIPSfpga — не используется;
  • SI_EICVector — номер вектора запрашиваемого прерывания;
  • SI_Offset[17:1] — смещение запрашиваемого прерывания;

Принимая в обработку очередной запрос на прерывание, процессор сообщает контроллеру:

  • SI_IAck единичный импульс информирует о начале обработки прерывания;
  • SI_IPL[7:0] — приоритет обрабатываемого прерывания;
  • SI_IVN[5:0] — номер вектора обрабатываемого прерывания;
  • SI_ION[17:0] — смещение обрабатываемого прерывания;

Типовое взаимодействие контроллера и процессорного ядра показано ниже:

Также следует учесть:

  • т.к. приоритизация прерываний в этом случае выполняется не процессорным ядром, то все генерируемые им прерывания также должны поступать на вход контроллера: прерывание системного таймера (сигнал SI_TimerInt флаг Cause.TI), программные прерывания (сигналы SI_SWInt[1:0], флаги Cause.IP1IP0) и др. — более подробно отражено в [D2];
  • то, как ядро вычисляет адрес смещения обработчика: берет его напрямую из SI_Offset[17:1], либо преобразовывает из SI_EICVector определяется текущей активированной опцией: «Option 1 — Explicit Vector Number » либо «Option 2 — Explicit Vector Offset». В первом случае — нам доступно 64 вектора, во втором — у нас есть 256К отсчитываемой от базы памяти, чтобы разместить в ней свои вектора, чего должно хватить даже для самой требовательной к количеству прерываний задачи. Коммерческие заказчики ядра MIPS microAptiv задают данный параметр в графическом конфигураторе. В MIPSfpga он недоступен, но данную настройку можно выполнить в файле m14k_cpz_eicoffset_stub.v:84. Для этого необходимо заменить в нем значение:
assign eic_offset = 1'b1;
  • решение об обработке запрашиваемого контроллером прерывания принимается процессором на основании минимально-допустимого приоритета (устанавливается в IPL), все прерывания с приоритетом ниже заданного — будут проигнорированы;

Контроллер прерываний MIPSfpga-plus

Основные характеристики:

  • является частью системы MIPSfpga-plus [L3];
  • доступно до 64 прерываний, точное количество которых может быть задано в конфигурационном файле (mfp_eic_core.vh) [S15];
  • реализовано два типа каналов ввода: прямой (direct channel) и с настройкой чувствительности (sense channel). Для первого признаком наличия запроса на прерывания является высокий уровень сигнала. Для второго данный параметр может быть настроен: фронт, спад, низкий уровень сигнала, любое изменение сигнала;
  • максимальное количество каналов типа sense channel — 32;
  • простота контроллера позволяет при необходимости реализовать еще больше прерываний, для этого потребуется внести небольшие изменения в код, в частности, добавлять конфигурационные регистры;
  • все сигналы, поступающие на вход контроллера, должны быть синхронизированы;
  • поддерживается работа как в режиме «Option 1 — Explicit Vector Number » так и «Option 2 — Explicit Vector Offset»;
  • программно может быть настроена функция автоматического опускание флага прерывания при начале его обработки процессорным ядром;
  • при необходимости, на github доступна отдельная «песочница» с контроллером, в которой отлаживалась его работа [L6];
  • подключение контроллера к процессорному ядру выполнено на верхнем уровне MIPSfpga-plus (mfp_system.v), EIC_input — сигналы от источников прерываний, остальные вводы-выводы относятся к интерфейсу прерываний процессора [S21]:
`ifdef MFP_USE_IRQ_EIC
.EIC_input        (   EIC_input        ),
.EIC_Offset       (   SI_Offset        ),
.EIC_ShadowSet    (   SI_EISS          ),
.EIC_Interrupt    (   SI_Int           ),
.EIC_Vector       (   SI_EICVector     ),
.EIC_Present      (   SI_EICPresent    ),
.EIC_IAck         (   SI_IAck          ),
.EIC_IPL          (   SI_IPL           ),
.EIC_IVN          (   SI_IVN           ),
.EIC_ION          (   SI_ION           ),
`endif //MFP_USE_IRQ_EIC

В состав контроллера входят следующие файлы:

  • v ядро контроллера [S17];
  • vh основной конфигурационный файл (см. комментарии внутри) [S15];
  • v содержит логику по преобразованию номера прерывания в вектор/смещение и обратно (см. комментарии внутри) [S18];
  • v приоритетный шифратор с иерархической организацией [S19];
  • v модуль верхнего уровня, обеспечивающий интерфейс с шиной AHB-Lite [S20].

Для того, чтобы сформировать общее представление о настройке контроллера, перечислим его конфигурационные регистры, их полное описание в [D5]. Для всех регистров, кроме EICR, EISMSK_0 и EISMSK_1 предполагается, что номер бита соответствует номеру входа контроллера. Так, к примеру, EIFR_0[3]=1 — означает, что на входе 3 ожидает необработанное прерывание.

Пример

Порядок запуска

  • проверить, что в файле mfp_ahb_lite_matrix_config.vh установлен следующий режим [S1]:
`define MFP_USE_IRQ_EIC
`define MFP_USE_WORD_MEMORY
  • проверить, что в файле m14k_cpz_eicoffset_stub.v установлен параметр:
assign eic_offset = 1'b0;
  • перейти в каталог mipsfpga-plus/programs/07_eic/;
  • выполнить сборку программы и ее запуск в симуляторе:
02_compile_and_link.bat
05_generate_verilog_readmemh_file.bat
06_simulate_with_modelsim.bat
  • после завершения работы скрипта симуляции нажать «нет», чтобы предотвратить закрытие симулятора;

Описание программы и конфигурации системы

  • установленные в заголовочных файлах RTL настройки приводят к следующей конфигурации [S16]:
wire  [ `EIC_CHANNELS - 1 : 0 ] EIC_input;
assign EIC_input[`EIC_CHANNELS - 1:8] = {`EIC_CHANNELS - 6 {1'b0}};
assign EIC_input[7]   =  SI_TimerInt;
assign EIC_input[6]   =  1'b0;
assign EIC_input[5]   =  uart_interrupt;
assign EIC_input[4:2] =  3'b0;
assign EIC_input[1]   =  SI_SWInt[1];
assign EIC_input[0]   =  SI_SWInt[0];
assign SI_IPTI        =  3'h0;
  • сигналы шины Interrupt Interface обрабатываются модулем mfp_ahb_lite_eic, как это было показано выше;
  • файл exceptions.S аналогичен двум предыдущим примерам, за исключением именования прерываний и их количества. Так, самое последнее HW63 расположено на смещении 0x9E0 [S22]:
.org    0x9E0
.weak   __mips_isr_eic63
__isr_vec_eic63:
la      $k1, __mips_isr_eic63
beqz    $k1, __general_exception
nop
jr      $k1
nop
  • все макросы, необходимые для работы с регистрами контроллера, вынесены в файл eic.h [S23];
  • инициализация таймера в main.c выполняется точно также как и ранее;
  • инициализация прерываний [S24]:
//eic mode
//unmask interrupt
MFP_EIC_EIMSK_0     = (1 << IRQSW0) | (1 << IRQSW1) | (1 << IRQTIMER);
MFP_EIC_EIMSK_1     = (1 << IRQ63);

//enable auto clear
MFP_EIC_EIACM_0     = (1 << IRQSW0) | (1 << IRQSW1) | (1 << IRQTIMER);

//set interrupt on rising edge of the signal
MFP_EIC_EISMSK_0    = (SMSK_RIZE << SMSKSW0) | (SMSK_RIZE << SMSKSW1) | (SMSK_RIZE << SMSKTIMER);

// Status.BEV  0 - vector interrupt mode
mips32_bicsr (SR_BEV);

// Cause.IV,   1 - special int vector (0x200)
// where 0x200 - base when Status.BEV = 0;
mips32_biscr (CR_IV);

// get IntCtl reg value
uint32_t intCtl = mips32_getintctl();

// set interrupt table vector spacing (0x20 in our case)
// see exceptions.S for details
mips32_setintctl(intCtl | INTCTL_VS_32);

// enable external interrupt controller
// enable interrupts
MFP_EIC_EICR = 0x1;
mips32_bissr (SR_IE);
  • при наступлении прерывания по таймеру сигнал последовательно проходит через RTL модули: interrupt_sence — где настроена реакция только на фронт сигнала [S28], interrupt_channel — где производится регистрация прерывания и выставляется соответствующий флаг в EIFR [S29]. На основании одного или нескольких выставленных флагов приоритетный шифратор priority_encoder формирует номер прерывания с самым высоким приоритетом — чем больше номер входа EIC_input, тем выше приоритет прерывания [S19]. После этого номер прерывания обрабатывается модулем handler_params_encoder [S18], где на его основе формируется вектор/смещение;
  • для того, чтобы включить опцию непосредственного указания смещения (Option 2 — Explicit Vector Offset), необходимо помимо изменения конфигурации процессорного ядра (m14k_cpz_eicoffset_stub.v), раскомментировать в файле mfp_eic_core.vh [S15]параметр
`define EIC_USE_EXPLICIT_VECTOR_OFFSET
  • переключение между опциями работы по номеру вектора/по смещение никак не повлияет на работоспособность данного примера, т.к. инкремент смещения, определенный в mfp_eic_handler.v соответствует таковому при IntCtl.VS = 1 (32 байта) [S25];
  • сама обработка прерывания производится аналогично тому, как было показано выше [S26]:
ISR(IH_SW0)
{
   MFP_RED_LEDS = MFP_RED_LEDS | 0x1;
   mips32_biccr(CR_SINT0);         //clear software interrupt 0 flag
   MFP_RED_LEDS = MFP_RED_LEDS & ~0x1;
}
  • макросы ISR и EH_GENERAL определены в eic.h [S27];
  • результат работы при передаче номера вектора:

  • результат работы при передаче смещения:

Поддержка компилятором

В описании примеров выше не акцентировалось внимание на том, как осуществляется непосредственно вход в прерывание, т.к. необходимый для этого код создается для нас компилятором [L7]. Рассмотрим этот код для наиболее частых случаев.

В [D3] перечислен ряд опций компилятора, которые могут быть использованы разработчиком:

void __attribute__ ((interrupt)) v0 ();
void __attribute__ ((interrupt, use_shadow_register_set)) v1 ();
void __attribute__ ((interrupt, keep_interrupts_masked)) v2 ();
void __attribute__ ((interrupt, use_debug_exception_return)) v3 ();
void __attribute__ ((interrupt, use_shadow_register_set, keep_interrupts_masked)) v4 ();
void __attribute__ ((interrupt, use_shadow_register_set, use_debug_exception_return)) v5 ();
void __attribute__ ((interrupt, keep_interrupts_masked, use_debug_exception_return)) v6 ();
void __attribute__ ((interrupt, use_shadow_register_set, keep_interrupts_masked,
use_debug_exception_return)) v7 ();
void __attribute__ ((interrupt("eic"))) v8 ();
void __attribute__ ((interrupt("vector=hw3"))) v9 ();

Несколько комментариев по поводу их использования:

  • опция use_shadow_register_set не представляет для нас практического интереса, т.к. эта функциональность не доступна в ядре MIPSfpga;
  • указание имени вектора interrupt(«vector=hw3») ограничено векторами доступными для режима обратной совместимости;
  • код, который генерируется для (interrupt, keep_interrupts_masked), копирование регистров в стек и обратно опущено:
void __attribute__ ((interrupt, keep_interrupts_masked)) __mips_isr_sw0 ()
{
80001544:    401b7000     mfc0   k1,c0_epc
80001548:    27bdfff0     addiu  sp,sp,-16
8000154c:    afbb000c     sw     k1,12(sp)
80001550:    401b6000     mfc0   k1,c0_status
80001554:    afbb0008     sw     k1,8(sp)

//установить в k1 нули на смещение status.exl,.erl,.um, .ie;
80001558:    7c1b2004     ins    k1,zero,0x0,0x5

//записать status
8000155c:    409b6000     mtc0   k1,c0_status
80001560:    afbe0004     sw     s8,4(sp)
80001564:    03a0f025     move   s8,sp
...
}
80001568:    03c0e825     move   sp,s8
8000156c:    8fbe0004     lw     s8,4(sp)
80001570:    8fbb000c     lw     k1,12(sp)
80001574:    409b7000     mtc0   k1,c0_epc
80001578:    8fbb0008     lw     k1,8(sp)
8000157c:    27bd0010     addiu  sp,sp,16
80001580:    409b6000     mtc0   k1,c0_status
80001584:    42000018     eret
  • код, который генерируется для (interrupt), копирование регистров в стек и обратно опущено:
void __attribute__ ((interrupt)) __mips_isr_hw5 ()
{
80001588:    401a6800     mfc0   k0,c0_cause
8000158c:    401b7000     mfc0   k1,c0_epc
80001590:    27bdfff0     addiu  sp,sp,-16
80001594:    afbb000c     sw     k1,12(sp)
80001598:    401b6000     mfc0   k1,c0_status

//сдвинуть в начало k0 биты cause.ip7-ip2
8000159c:    001ad282     srl    k0,k0,0xa
800015a0:    afbb0008     sw     k1,8(sp)

//загрузить в k1 значения cause.ip6-ip2 на смещение status.im7-im2
800015a4:    7f5b7a84     ins    k1,k0,0xa,0x6

//установить в k1 нули на смещение status.exl,.erl,.um;
//  .ie при этом не меняется (остается 1)
800015a8:    7c1b2044     ins    k1,zero,0x1,0x4

//записать status
800015ac:    409b6000     mtc0   k1,c0_status
800015b0:    afbe0004     sw     s8,4(sp)
800015b4:    03a0f025     move   s8,sp
...
}
800015b8:    03c0e825     move   sp,s8
800015bc:    8fbe0004     lw     s8,4(sp)

//disable interrupts (status.ie = 0)
800015c0:    41606000     di
800015c4:    000000c0     ehb
800015c8:    8fbb000c     lw     k1,12(sp)

//восстановить epc
800015cc:    409b7000     mtc0   k1,c0_epc
800015d0:    8fbb0008     lw     k1,8(sp)
800015d4:    27bd0010     addiu  sp,sp,16

//восстановить status из стека
800015d8:    409b6000     mtc0   k1,c0_status
800015dc:    42000018     eret
  • таким образом, если мы не указываем keep_interrupts_masked, то для режима обратной совместимости и векторного будут разрешены только те прерывания, которые были уже запрошены на момент обработки текущего. Для режима контроллера внешних прерываний этот атрибут дает нам возможность не запрещать прерывания с более высоким приоритетом во время обработки текущего.

Благодарности

Автор выражает благодарность коллективу переводчиков учебника Дэвида Харриса и Сары Харрис «Цифровая схемотехника и архитектура компьютера», компании Imagination Technologies за академическую лицензию на современное процессорное ядро и персонально Юрию Панчулу YuriPanchul за его работу по популяризации MIPSfpga.

Ссылки

[L1] — Цифровая схемотехника и архитектура компьютера;

[L2] — Как начать работать с MIPSfpga;

[L3] — Проект MIPSfpga-plus на github;

[L4] — FPGA плата Terasic DE10-Lite;

[L5] — Embedded Linux  System Design and Development P. Raghavan, Amol Lad, Sriram Neelakandan (перевод);

[L6] — Проект ahb_lite_eic на github;

[L7] — Codescape MIPS SDK;

Документация

[D1] — MIPS32 microAptiv UP Processor Core Family Integrator’s Guide;

[D2] — MIPS32 microAptiv UP Processor Core Family Software User’s Manual;

[D3] — Codescape GNU Tools for MIPS Programmer’s Guide;

[D4] — MIPS32 Architecture For Programmers Volume III: The MIPS32 Privileged Resource Architecture;

[D5] — MIPSfpga+ External Interrupt Controller;

[D6] — MIPS32 microAptiv UP Processor Core Family Datasheet;

Изображения и таблицы

[P0] — MIPS 32 microAptiv UP Core Block Diagram (источник: D6);

[P1] — Режимы работы процессорного ядра с прерываниями;

[P2] — Работа системного таймера MIPS. Сигналы (скриншот);

[P3] — Подключение источников прерываний к процессорному ядру (источник: L5);

[P4] — Работа в режиме обратной совместимости. Сигналы (скриншот);

[P5] — Обработка прерываний в векторном режиме (источник: D2);

[P6] — Приоритет прерываний для векторного режима (источник: D2);

[P7] — Смещение адресов обработчиков для векторного режима (источник: D2);

[P8] — Работа в векторном режиме. Сигналы (скриншот);

[P9] — Обработка прерываний в режиме контроллера внешних прерываний (источник: D2);

[P10] — Сигналы контроллера внешних прерываний (источник: D1);

[P11] — Регистры контроллера внешних прерываний (источник: L6);

[P12] — Работа в режиме контроллера внешних прерываний, передача номера вектора (скриншот);

[P13] — Работа в режиме контроллера внешних прерываний, передача смещения (скриншот).

Ссылки на исходный код

[S0] — Инициализация и сброс таймера;

[S1] — Область настройки памяти и прерываний системы mipsfpga-plus;

[S2] — Настройка режима работы примера 06_timer_irq;

[S3] — Конфигурация mipsfpga-plus при отключенном контроллере внешних прерываний;

[S4] — Пример вектора прерывания;

[S5] — Пример вектора исключения;

[S6] — Настройка прерываний в режиме обратной совместимости;

[S7] — Обработка прерываний в режиме обратной совместимости;

[S8] — Прерывание по таймеру в режиме обратной совместимости;

[S9] — Программное прерывание в режиме обратной совместимости;

[S10] — Основной цикл функции main примера 06_timer_irq;

[S11] — Настройка прерываний в векторном режиме;

[S12] — Прерывание по таймеру в векторном режиме;

[S13] — Программное прерывание с отдельным обработчиком в векторном режиме;

[S14] — Работа в векторном режиме при отсутствии отдельного обработчика;

[S15] — Конфигурационный файл контроллера внешних прерываний mipsfpga-plus;

[S16] — Конфигурация mipsfpga-plus при использовании контроллера внешних прерываний;

[S17] — Ядро контроллера внешних прерываний;

[S18] — Обработка номера прерывания контроллером внешних прерываний;

[S19] — Приоритетный шифратор контроллера внешних прерываний;

[S20] — Интерфейс контроллера внешних прерываний и шины AHB-Lite;

[S21] — Сигналы интерфейса прерываний при обработке контроллером;

[S22] — Внешнее прерывание HW63;

[S23] — Заголовочный файл программного интерфейса работы с контроллером;

[S24] — Настройка прерываний в режиме контроллера внешних прерываний;

[S25] — Настройка инкремента смещения вектора в режиме контроллера внешних прерываний;

[S26] — Обработчик прерывания в режиме контроллера внешних прерываний;

[S27] — Макросы ISR и EH_GENERAL;

[S28] — Модуль interrupt_sence;

[S29] — Модуль interrupt_channel.