Первоначально, вариант обвязки MIPSfpga+ процессора MIPS microAptiv UP для FPGA написал Yuri Panchul на основе обвязки MIPSfpga 1.0 написанной Sarah Harris. Затем MIPSfpga+ существенно развил Stanislav Zhelnio, который и ведет проект сейчас. В проект также вносили добавления и создавали на его основе другие проекты разработчики из России, Украины, США, Великобритании, Италии и других стран.. В mipsfpga-plus репозитории на github есть примеры программ, которые можно запустить в процессоре.

Здесь я дам некоторые пояснения к некоторым программам и расскажу, как я запускаю их в плате Марcоход3.

Итак, в проекте mipsfpga_plus есть папка programs и в ней еще несколько подпапок:

00_counter
01_light_sensor
02_cache_misses
03_pipeline_bypasses
04_memtest
05_uart
06_timer_irq
07_eic
08_uart_irq
09_adc
10_linux

Это примеры программ, которые можно откомпилировать и запустить в процессоре (или правильнее сказать в SoC) MIPSfpga. В предыдущей статье я уже компилировал сам проект FPGA, загружал ПЛИС платы Марсоход3 и запускал программу 00_counter.

Давайте теперь попробуем запустить некоторые другие программы.

Общий принцип действий простой:

  1. заходим в папку с программой, желательно в консоли cmd.exe, тогда, если что-то пойдет не так, мы хотя бы увидим сообщение об ошибке или другие полезные сообщения;
  2. возможно придется исправлять текст программы main.c, там есть некоторые объявления как #define RUNTIME HARDWARE или #define RUNTIME SIMULATION. Мы пробуем в плате, значит нужно выбирать HARDWARE;
  3. компилируем программу с помощью скрипта 02_compile_and_link.bat;
  4. создаем s-record файл, запуская скрипт 08_generate_motorola_s_record_file.bat;
  5. запускаем скомпилированную программу в процессоре, загружая ее в плату через последовательный порт с помощью скрипта 10_upload_to_the_board_using_uart.bat.

В этом скрипте 10_upload_to_the_board_using_uart.bat в первой строке "set a=17" задается номер последовательного порта и возможно у вас в системе он будет другой. Обычно при подключении платы Марсоход3 появляется в системе 2 последовательных порта. У меня COM16 и COM17. Вот второй, который нечетный - это скорее всего нужный порт. Первый порт, который в микросхеме FTDI является каналом A, используется как JTAG программатор для платы Марсоход3.

Программа тестирования памяти 04_memtest.

Заходим в папку 04_memtest и открываем в любом текстовом редакторе программу main.c

#include "mfp_memory_mapped_registers.h"
#include <stdint.h>

#define SIMULATION  0
#define SDRAM_64M   64
#define SDRAM_8M    8

// -------- config start ------------

//count of HEX segments on board
#define HEX_SEGMENT_COUNT   6
#define MEMTYPE SDRAM_8M /*SIMULATION*/

// --------  config end  ------------

#if     MEMTYPE == SIMULATION
    #define TEST_ARRSIZE    200   
    #define TEST_DELAY      10
    #define TEST_COUNT      2
#elif   MEMTYPE == SDRAM_64M
    #define TEST_ARRSIZE    (10*1024*1024)   /* Size = sizeof(uint32_t)*10M = 40M */
    #define TEST_DELAY      1000000
    #define TEST_COUNT      0xff
#elif   MEMTYPE == SDRAM_8M
    #define TEST_ARRSIZE    (1*1024*1024)    /* Size = sizeof(uint32_t)*1M = 4M */
    #define TEST_DELAY      1000000
    #define TEST_COUNT      0xff
#endif

void _delay(uint32_t val)
{
    for (uint32_t i = 0; i < val; i++)
        __asm__ volatile("nop");
}

//optput statistic to 7segment 
void statOut(uint8_t iterationNum, uint16_t errCount)
{
    #if     HEX_SEGMENT_COUNT == 6
        //HEX = CCEEEE, where CC - check num, EEEE - found errors count
        uint32_t out = (((uint32_t)iterationNum) << 16) + errCount;

    #elif   HEX_SEGMENT_COUNT == 4
        //HEX = CCEE, where CC - check num, EE - found errors count
        uint32_t out = (((uint32_t)iterationNum) << 8) +  (uint8_t)errCount;
    #endif

    MFP_7_SEGMENT_HEX = out;
}

//current step out
void stepOut(uint8_t stepNum)
{
    uint16_t out = (1 << stepNum);
    MFP_RED_LEDS   = out;
    MFP_GREEN_LEDS = out;
}

void cacheFlush(uint32_t *addr)
{
    __asm__ volatile(
        "cache 0x15, 0(%[ADDR])" "\n\t"
        : : [ADDR] "r" (addr)
    );
}

int main ()
{
    const uint32_t  arrSize  = TEST_ARRSIZE;
    const uint32_t  delayCnt = TEST_DELAY;
    const uint8_t   checkCnt = TEST_COUNT;
    
    uint16_t errCount = 0;
    uint32_t arr[arrSize];

    //write to mem
    stepOut(0);
    for (uint32_t i = 0; i < arrSize; i++)
        arr[i] = i;

    //check
    for (uint8_t j = 0; j < checkCnt; j++)
    {
        //flush cache
        stepOut(1);
        for (uint32_t i = 0; i < arrSize; i++)
            cacheFlush(&arr[i]);

        //delay
        stepOut(2);
        _delay(delayCnt);

        //read
        stepOut(3);
        for (uint32_t i = 0; i < arrSize; i++) {
            if(arr[i] != i){
                errCount++;
                statOut(j, errCount);
            }
        }
        statOut(j, errCount);
    }
        
    //end
    //4 - no errors, 5 - some errors
    stepOut(!errCount ? 4 : 5);

    for(;;);
}

"Программа тестирования памяти" на самом деле тестирует ячейки массива программы, они описаны в функции main() как uint32_t arr[arrSize]; Тестирование памяти не очень интенсивное, но, тем не менее, оно работает.

arrSize задается в коде программы в самом начале: для платы Марсоход3 нужно определить #define MEMTYPE SDRAM_8M и тогда размер массива для тестирования получится #define TEST_ARRSIZE (1*1024*1024), то есть будут тестироваться 4 мегабайта или миллион 4-х байтных слов.

Программа состоит из нескольких шагов. На нулевом шаге массив заполняется числами в цикле. На первом шаге сбрасывается кэш, на втором, делается пауза (программная задержка), на третьем, читается содержимое массива и сравнивается с ожидаемым значением.
Все это повторяется многократно.

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

На плате Марсоход3 вообще-то нет физических семисегментных индикаторов, но есть виртуальные, которые отображаются на экране подключенного по HDMI монитора.

Я запускаю тест памяти и он успешно проходит без ошибок до конца.

Программа связи через последовательный порт 05_uart для MIPSfpga.

#include "mfp_memory_mapped_registers.h"
#include "uart16550.h"
#include <stdint.h>

#define SIMULATION  0
#define HARDWARE    1

// config start

#define RUNTYPE     HARDWARE

// config end

// The devisor value set should be equal to 
// (system clock speed) / (16 x desired baud rate).
#define DIVISOR_50M     (50*1000*1000 / (16*115200))
#define DIVISOR_SIM     1

#if     RUNTYPE == SIMULATION
    #define UART_DIVISOR    DIVISOR_SIM
#elif   RUNTYPE == HARDWARE
    #define UART_DIVISOR    DIVISOR_50M
#endif

void uartInit(uint16_t divisor)
{
    MFP_UART_LCR = MFP_UART_LCR_8N1;      // 8n1
    MFP_UART_LCR |= MFP_UART_LCR_LATCH;   // Divisor Latches access enable
    MFP_UART_DLL = divisor & 0xFF;        // Divisor LSB
    MFP_UART_DLH = (divisor >> 8) & 0xff; // Divisor MSB
    MFP_UART_LCR &= ~MFP_UART_LCR_LATCH;  // Divisor Latches access disable
}

void uartTransmit(uint8_t data)
{
    // waiting for transmitter fifo empty
    while (!(MFP_UART_LSR & MFP_UART_LSR_TFE));

    // transmitted data
    MFP_UART_TXR = data;
}

void receivedDataOutput(uint8_t data)
{
    MFP_RED_LEDS        = data;
    MFP_GREEN_LEDS      = data;
    MFP_7_SEGMENT_HEX   = data;
}

uint8_t uartReceive(void)
{
    //waiting for RX data
    while (!(MFP_UART_LSR & MFP_UART_LSR_DR));
    //returning received data
    return MFP_UART_RXR;
}

void uartWrite(const char str[])
{
    while(*str)
        uartTransmit(*str++);
}

int main ()
{
    // init
    const uint16_t uartDivisor = UART_DIVISOR;
    uartInit(uartDivisor);

    // say Hello after reset
    uartWrite("Hello!");

    // received data output and loopback
    for(;;)
    {
        uint8_t data = uartReceive();
        receivedDataOutput(data);

        #if   RUNTYPE == HARDWARE
        uartTransmit(data);
        #endif
    }
}

В этой программе, как и в других примерах, нужно в самом начале программы определить #define RUNTYPE HARDWARE для испытания в плате. Другой вариант #define RUNTYPE SIMULATION используется для функциональной симуляции с помощью Icarus Verilog или ModelSim.

Программа принимает байт из последовательного порта, отображает его на светодиодах и семисегментном индикаторе и отправляет его назад в последовательный порт.

Откомпилированная программа, как обычно, загружается в SoC MIPSfpga через последовательный порт с помощью скрипта 10_upload_to_the_board_using_uart.bat После этого, можно на компьютере запустить программу терминала, например, putty. В программе терминала откройте тот же последовательный порт, какой указан в скрипте 10_upload_to_the_board_using_uart.bat

Все, что набираете в терминале в нем же и отображается, так как отправляемые символы отправляются из платы назад.
На виртуальных светодиодах, то есть на мониторе, подключенном к плате Марсоход3 виден код принятого символа.

Аппаратные прерывания и таймер. Программа 06_timer_irq.

Вот часть исходного кода:

#include <stdint.h>
#include <mips/cpu.h>

#include "mfp_memory_mapped_registers.h"

// run types
#define COMPATIBILITY   0
#define VECTOR          1

// config start

#define RUNTYPE             VECTOR
#define MIPS_TIMER_PERIOD   0x200

// config end

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

void mipsTimerReset(void)
{
    mips32_setcount(0);                     //reset counter as it reached the TOP value
    mips32_setcompare(MIPS_TIMER_PERIOD);   //clear timer interrupt flag
}

void mipsInterruptInit(void)
{
    #if     RUNTYPE == COMPATIBILITY
        //compatibility mode, one common handler
        mips32_bicsr (SR_BEV);              // Status.BEV  0 - place handlers in kseg0 (0x80000000)
        mips32_biccr (CR_IV);               // Cause.IV,   0 - general exception handler (offset 0x180)
        mips32_bissr (SR_IE | SR_HINT5 | SR_SINT1);    // interrupt enable, HW5,SW1 - unmasked

    #elif   RUNTYPE == VECTOR
        //vector mode, multiple handlers
        mips32_bicsr (SR_BEV);              // Status.BEV  0 - place handlers in kseg0 (0x80000000)
        mips32_biscr (CR_IV);               // Cause.IV,   1 - special int vector (offset 0x200), 
                                            //                 where 0x200 - base for other vectors

        uint32_t intCtl = mips32_getintctl();       // get IntCtl reg value
        mips32_setintctl(intCtl | INTCTL_VS_32);    // set interrupt table vector spacing (0x20 in our case)
                                                    // see exceptions.S for details

        mips32_bissr (SR_IE | SR_HINT5 | SR_SINT0 | SR_SINT1); // interrupt enable, HW5 and SW0,SW1 - unmasked
    #endif
}

volatile uint32_t n;

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;
}

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;
}

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;
}

int main ()
{
    n = 0;
    mipsTimerInit();
    mipsInterruptInit();

    for (;;)
        MFP_7_SEGMENT_HEX = n;

    return 0;
}

Сказать по правде, эту программу я мало понимаю. Просто потому, что нужно вникать в аппаратные особенности процессора и его регистров контроллера прерывания. Тем не менее программа работает и в режиме VECTOR и в режиме COMPATIBILITY - режимы работы задаются в программе. Программируется таймер, обработчик прерываний увеличивает переменную "n", которая которая отображается на семисегментном индикаторе. На светодиодах отображаются различные флаги вроде того, что "зашел в прерывание" - "вышел из прерывания".

 

Программа опроса АЦП канала FPGA MAX10, 09_adc.

На самом деле эта программа, как она есть, мало подходит для запуска в плате Марсоход3. На нашей плате реально можно использовать только нулевой канал АЦП, а он в программе как раз и не опрашивается.

Я сделал свою программу, 09_adc_0 (форк проекта https://github.com/marsohod4you/mipsfpga-plus). Вот ее и буду пробовать.

#include <stdint.h>
#include <mips/cpu.h>

#include "mfp_memory_mapped_registers.h"
#include "adc_m10.h"

#define SIMULATION  0
#define HARDWARE    1

// config start

#define RUNTYPE     HARDWARE

// config end

#define PERIOD_200  200
#define PERIOD_4M   (4*1000*1000)

#if     RUNTYPE == SIMULATION
    #define MIPS_TIMER_PERIOD    PERIOD_200
#elif   RUNTYPE == HARDWARE
    #define MIPS_TIMER_PERIOD    PERIOD_4M
#endif

// interrupt functions
void mipsTimerInit(void)
{
    mips32_setcompare(MIPS_TIMER_PERIOD);   //set compare (TOP) value to turn timer on
    mips32_setcount(0);                     //reset counter
}

void mipsTimerReset(void)
{
    mips32_setcount(0);                     //reset counter as it reached the TOP value
    mips32_setcompare(MIPS_TIMER_PERIOD);   //clear timer interrupt flag
}

void mipsInterruptInit(void)
{
    //vector mode, multiple handlers
    mips32_bicsr (SR_BEV);              // Status.BEV  0 - place handlers in kseg0 (0x80000000)
    mips32_biscr (CR_IV);               // Cause.IV,   1 - special int vector (offset 0x200), 
                                        //                 where 0x200 - base for other vectors

    uint32_t intCtl = mips32_getintctl();       // get IntCtl reg value
    mips32_setintctl(intCtl | INTCTL_VS_32);    // set interrupt table vector spacing (0x20 in our case)
                                                // see exceptions.S for details

    // interrupt enable, HW4, HW5 - unmasked
    mips32_bissr (SR_IE | SR_HINT4 | SR_HINT5 ); 
}

// ADC functions
void adcInit(void)
{
    // unmask ADC channels 1-4
    MFP_ADCM10_ADMSK = ( ADMSK0 /*| ADMSK1 | ADMSK2 | ADMSK3 | ADMSK4*/ );
    // enable ADC module and ADC interrupt
    MFP_ADCM10_ADCS  = ( ADCS_EN | ADCS_IE );
}

void adcStart(void)
{
    //start ADC conversion
    MFP_ADCM10_ADCS |= ( ADCS_SC );
}

void adcOutput(void)
{
    uint16_t val;

    //get switch value (channel selector)
	/*
    switch(MFP_SWITCHES)
    {
        //get ADC value
        default: val = MFP_ADCM10_ADC1; break;
        case  1: val = MFP_ADCM10_ADC2; break;
        case  2: val = MFP_ADCM10_ADC3; break;
        case  3: val = MFP_ADCM10_ADC4; break;
    }
	*/
	val = MFP_ADCM10_ADC0;

    //value output
    MFP_7_SEGMENT_HEX = val /* >> 4 */;

    //reset the ADC interrupt flag
    MFP_ADCM10_ADCS |= ( ADCS_IF );
}

int num_ints = 0;

//interrupt handlers
ISR(IH_TIMER)
{
	MFP_GREEN_LEDS = num_ints;
	num_ints++;
    MFP_RED_LEDS = MFP_RED_LEDS | 0x1;

    adcStart();
    mipsTimerReset();

    MFP_RED_LEDS = MFP_RED_LEDS & ~0x1;
}

ISR(IH_ADC)
{
    MFP_RED_LEDS = MFP_RED_LEDS | 0x2;

    adcOutput();

    MFP_RED_LEDS = MFP_RED_LEDS & ~0x2;
}

int main ()
{
    adcInit();
    mipsTimerInit();
    mipsInterruptInit();

    for (;;);

    return 0;
}

Тут программируется таймер, который запускает оцифровку входного сигнала и по прерыванию от АЦП из него считывается значение.

Значение сигнала отображается на семисегментном индикаторе и на светодиодах. Проще всего для экспериментов с АЦП подключить переменный резистор и считывать значение напряжения на резистивном делителе. И это работает.

На видеодемонстрации выше показаны все эти примеры.
Вот так работают тестовые программы MIPSfpga в плате Марсоход3.

 


Добавить комментарий