Первоначально, вариант обвязки 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.

Вопрос насущный так-как nios2/e показался медленнее даже какой-то аврки.