Первоначально, вариант обвязки 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.
Давайте теперь попробуем запустить некоторые другие программы.
Общий принцип действий простой:
- заходим в папку с программой, желательно в консоли cmd.exe, тогда, если что-то пойдет не так, мы хотя бы увидим сообщение об ошибке или другие полезные сообщения;
- возможно придется исправлять текст программы main.c, там есть некоторые объявления как #define RUNTIME HARDWARE или #define RUNTIME SIMULATION. Мы пробуем в плате, значит нужно выбирать HARDWARE;
- компилируем программу с помощью скрипта 02_compile_and_link.bat;
- создаем s-record файл, запуская скрипт 08_generate_motorola_s_record_file.bat;
- запускаем скомпилированную программу в процессоре, загружая ее в плату через последовательный порт с помощью скрипта 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.
Подробнее...