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.IP1—IP0) и др. — более подробно отражено в [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.