Хабрахабр

О портировании проекта MIPSfpga

Перенос программного обеспечения с одной компьютерной архитектуры на другую в принципе, с некоторыми оговорками, дело относительно простое. Здесь на помощь приходят такие общеизвестные инструменты, как autoconf / automake/ libtool / gnulib. Собрать программу из исходников на каком нибудь Raspberry/ARM бывает так же просто, как и на ПК с Ubuntu/x86-64.

А вот как заставить проект ПЛИС разработанный для одной платы работать на другой плате? Там и сама ПЛИС может быть другой и на плате совершенно другие компоненты могут стоять. Простой перекомпиляцией проекта не обойтись.

Расскажу о своем опыте портирования проекта MIPSfpga для платы Марсоход3 с ПЛИС MAX10 Intel. Статьи о проекте MIPSfpga неоднократно появлялись на хабре. Они были так интересны, что мне захотелось и самому попробовать этот проект в имеющейся у меня плате. В своей работе я опирался на хабровские статьи

И многие другие…

Итак, что нужно сделать, чтобы портировать проект ПЛИС на другую плату?

1) Нужно переназначить тип используемой ПЛИС в проекте

Повезет, если исходный проект ориентирован на точно такую же микросхему ПЛИС, как и на вашей плате. Проект MIPSfpga уже был портирован на платы разных производителей, в том числе, там есть платы и с FPGA Xilinx и платы с FPGA Intel/Altera. Тем не менее, скажем так, мне «не повезло». Да, есть проект для платы DE10-lite и там используется ПЛИС MAX10, как и на моей плате. Но это совершенно другая MAX10 — на DE10-lite стоит 10M50DAF484C7G, а на плате Марсоход3 стоит ПЛИС MAX1050SAE144C8. Обе микросхемы имеют 50 тысяч логических элементов, но у первой гораздо больше входных/выходных сигналов. Понятно, что вместе с переназначением типа микросхемы ПЛИС «слетают» все назначения сигналов на конкретные выводы микросхемы.

Назначение типа используемой микросхемы в среде САПР Intel Quartus производится через меню Assignments => Device в диалоговом окне Device.

Здесь же есть кнопка Device And Pin Options — зайдите туда в следующее диалоговое окно и я очень рекомендую сразу указать, что неиспользуемые в проекте пины должны быть As input tri-state.

Если плата не очень знакома, ее схему, например, мы досконально пока не изучили, лучше неиспользуеые выводы ПЛИС отключить от цепей платы.

2) Сделать новые назначения входам и выходам

Поскольку тип микросхемы был переназначен, то теперь нужно указать на какие входы/выходы идут те или иные сигналы проекта ПЛИС. В каждом проекте ПЛИС есть модуль самого верхнего уровня. Сигналы этого модуля соединены с выводами микросхемы ПЛИС, с теми, куда мы укажем в настройках проекта.

Вот, например, в моем проекте:

module m3
( input MAX10_CLK_100, input [ 1:0] KEY, output [ 7:0] LEDR, input wire FTDI_RX, output wire FTDI_TX, inout wire [7:0]FTD, `ifdef MFP_USE_SDRAM_MEMORY output [11:0] DRAM_ADDR, output [ 1:0] DRAM_BA, output DRAM_CAS_N, //output DRAM_CKE, output DRAM_CLK, //output DRAM_CS_N, inout [15:0] DRAM_DQ, output DRAM_LDQM, output DRAM_RAS_N, output DRAM_UDQM, output DRAM_WE_N, `endif `ifdef UNUSED input ADC_CLK_10, input MAX10_CLK1_50, input MAX10_CLK2_50, input [ 9:0] SW, output [ 7:0] HEX0, output [ 7:0] HEX1, output [ 7:0] HEX2, output [ 7:0] HEX3, output [ 7:0] HEX4, output [ 7:0] HEX5, output [ 3:0] VGA_B, output [ 3:0] VGA_G, output VGA_HS, output [ 3:0] VGA_R, output VGA_VS, output GSENSOR_CS_N, input [ 2:1] GSENSOR_INT, output GSENSOR_SCLK, inout GSENSOR_SDI, inout GSENSOR_SDO, inout [15:0] ARDUINO_IO, inout ARDUINO_RESET_N, inout [35:0] GPIO, `endif inout [15:0] GPIO, //HDMI output output wire [7:0]TMDS
);

На плате Марсоход3 нет ни переключателей SW, ни семисегментных индикаторов HEX0..HEX5, ни VGA разъема. Эти сигналы я не стал прямо удалять из проекта, но удалил их условной компиляцией с помощью конструкции `if UNUSED… `endif. На плате Марсоход3 есть светодиоды, кнопочки, сигналы с FTDI и даже HDMI сигналы — вот они присутствуют в моем проекте.

В среде Quartus через меню Assigments => Assignment Editor нужно задать соответствие сигнала номеру контакта микросхемы. Конечно, это нужно делать согласно схемы платы электрической принципиальной. Кроме указания номера вывода микросхемы возможно понадобится сделать другие назначения вроде i/O Standard или Pull-Up Resistor, если необходимо.

3) Внимание на тактовую частоту

Вероятно, что исходный проект сделан для платы у которой номинал кварцевого генератора не такой, как на нашей плате. Так и оказалось в моем случае. Плата DE10-lite имеет генератор 50МГц, а моя плата — 100МГц. Что нужно сделать — пересоздать экземпляр модуля PLL для данного проекта. В меню квартуса выбираем Tools => IP Catalog, там среди Installed IPs => Libraries находим basic Functions => Clocks; PLLs and Reset => PLLs => ALTPLL и запускаем Wizard.

Нужно указать правильную исходную частоту и в PLL задать нужные выходные частоту. Тут придется учитывать много факторов. Я решил добавить в проект вывод на HDMI дисплей, которого не было в исходном проекте, а значит мне понадобились дополнительные частоты требующиеся непосредственно для формирования видеосигналов TMDS (74МГц и 370МГц).

Созданную вариацию компонента PLL потом нужно вставить в проект.

4) Урезать или расширить функционал?

В случае портирования проектов ПЛИС на другие платы часто сталкиваешься с тем, что повторить проект один-в-один не удастся. Самая простая причина — например, исходный проект сделан для платы у которой есть 16 светодиодов и/или семисегментные индикаторы, а на нашей плате например такого вообще нет, или светодионы есть, но их меньше, не шестнадцать, а только восемь. Или на плате стоит другой объем ОЗУ или флэш… Тут приходится что-то придумывать, как-то обходить ограничения.

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

Для этой цели я написал на Verilog модуль виртуальных светодиодов и семисегментных индикаторов.

Приведу текст модуля здесь целиком:

Модуль виртуальных светодиодов и индикаторов

//marsohod3 board has only 8 yellow LEDs
//but sample MIPS needs red/green LEDs and 7-segment indicators
//so try display LEDs and 7-segment info on HDMI output of Marsohod3 board module display ( input wire reset, input wire clk_video, //74MHz input wire clk_hdmi, //370MHz input wire [NUM_RED_LEDS-1:0]red_leds, input wire [NUM_GREEN_LEDS-1:0]green_leds, input wire [NUM_SEGMENTS*4-1:0]segments, //HDMI output output wire [7:0]TMDS ); parameter NUM_RED_LEDS = 16;
parameter NUM_GREEN_LEDS = 16;
parameter NUM_SEGMENTS = 6; wire w_hsync;
wire w_vsync;
wire w_active;
wire [11:0]w_pixel_count;
wire [11:0]w_line_count; wire w_video_clk5; assign w_video_clk5 = clk_hdmi;
wire w_video_clk; assign w_video_clk = clk_video; hvsync u_hvsync( .reset( reset ), .pixel_clock( w_video_clk ), .hsync(w_hsync), .vsync(w_vsync), .active(w_active), .pixel_count(w_pixel_count), .line_count(w_line_count), .dbg( ) ); reg d_active;
reg r_hsync;
reg r_vsync;
reg r_vsync_;
reg [NUM_RED_LEDS-1:0]red_leds_fixed;
reg [NUM_GREEN_LEDS-1:0]green_leds_fixed;
reg [NUM_SEGMENTS*4-1:0]segments_fixed; always @(posedge w_video_clk)
begin r_hsync <= w_hsync; r_vsync <= w_vsync; r_vsync_ <= r_vsync; d_active <= w_active; if( r_vsync_==1'b0 && r_vsync==1'b1 ) begin red_leds_fixed <= red_leds; green_leds_fixed <= green_leds; segments_fixed <= segments; end
end reg [9:0]red_word;
reg [9:0]green_word;
reg [9:0]blue_word; wire led_visible; assign led_visible = w_pixel_count<512 && w_pixel_count[4]==1'b1;
wire [3:0]led_idx; assign led_idx = NUM_RED_LEDS-1-w_pixel_count[8:5];
wire current_green_led = (green_leds_fixed >> led_idx)&1;
wire current_red_led = (red_leds_fixed >> led_idx)&1; wire h_seg_line5; assign h_seg_line5 = w_pixel_count<(64*1-8) && w_pixel_count>(64*0+32);
wire h_seg_line4; assign h_seg_line4 = w_pixel_count<(64*2-8) && w_pixel_count>(64*1+32);
wire h_seg_line3; assign h_seg_line3 = w_pixel_count<(64*3-8) && w_pixel_count>(64*2+32);
wire h_seg_line2; assign h_seg_line2 = w_pixel_count<(64*4-8) && w_pixel_count>(64*3+32);
wire h_seg_line1; assign h_seg_line1 = w_pixel_count<(64*5-8) && w_pixel_count>(64*4+32);
wire h_seg_line0; assign h_seg_line0 = w_pixel_count<(64*6-8) && w_pixel_count>(64*5+32); wire vl_seg_line5; assign vl_seg_line5 = w_pixel_count<(64*1-32) && w_pixel_count>(64*0+24);
wire vl_seg_line4; assign vl_seg_line4 = w_pixel_count<(64*2-32) && w_pixel_count>(64*1+24);
wire vl_seg_line3; assign vl_seg_line3 = w_pixel_count<(64*3-32) && w_pixel_count>(64*2+24);
wire vl_seg_line2; assign vl_seg_line2 = w_pixel_count<(64*4-32) && w_pixel_count>(64*3+24);
wire vl_seg_line1; assign vl_seg_line1 = w_pixel_count<(64*5-32) && w_pixel_count>(64*4+24);
wire vl_seg_line0; assign vl_seg_line0 = w_pixel_count<(64*6-32) && w_pixel_count>(64*5+24); wire vr_seg_line5; assign vr_seg_line5 = w_pixel_count<(64*1) && w_pixel_count>(64*0+56);
wire vr_seg_line4; assign vr_seg_line4 = w_pixel_count<(64*2) && w_pixel_count>(64*1+56);
wire vr_seg_line3; assign vr_seg_line3 = w_pixel_count<(64*3) && w_pixel_count>(64*2+56);
wire vr_seg_line2; assign vr_seg_line2 = w_pixel_count<(64*4) && w_pixel_count>(64*3+56);
wire vr_seg_line1; assign vr_seg_line1 = w_pixel_count<(64*5) && w_pixel_count>(64*4+56);
wire vr_seg_line0; assign vr_seg_line0 = w_pixel_count<(64*6) && w_pixel_count>(64*5+56); function [7:0]seg7;
input [3:0]a;
begin case(a) 0: seg7 = 63; 1: seg7 = 6; 2: seg7 = 91; 3: seg7 = 79; 4: seg7 = 102; 5: seg7 = 109; 6: seg7 = 125; 7: seg7 = 7; 8: seg7 = 127; 9: seg7 = 111; 10: seg7 = 119; 11: seg7 = 124; 12: seg7 = 57; 13: seg7 = 94; 14: seg7 = 121; 15: seg7 = 113; endcase
end
endfunction reg [6:0]seg7_0;
reg [6:0]seg7_1;
reg [6:0]seg7_2;
reg [6:0]seg7_3;
reg [6:0]seg7_4;
reg [6:0]seg7_5; always @(posedge clk_video)
begin seg7_0 <= seg7( segments_fixed[ 3: 0] ); seg7_1 <= seg7( segments_fixed[ 7: 4] ); seg7_2 <= seg7( segments_fixed[11: 8] ); seg7_3 <= seg7( segments_fixed[15:12] ); seg7_4 <= seg7( segments_fixed[19:16] ); seg7_5 <= seg7( segments_fixed[23:20] );
end reg [2:0]color; always @*
begin if( w_line_count>64 && w_line_count<80 ) begin //green led line if(led_visible) color = current_green_led ? 1 : 2; else color=0; end else if( w_line_count>96 && w_line_count<112 ) begin //red led line if(led_visible) color = current_red_led ? 3 : 4; else color=0; end else if( w_line_count>118+32 && w_line_count<126+32 ) begin // 7seg top line if(h_seg_line0) color = seg7_0[0] ? 5 : 6; else if(h_seg_line1) color = seg7_1[0] ? 5 : 6; else if(h_seg_line2) color = seg7_2[0] ? 5 : 6; else if(h_seg_line3) color = seg7_3[0] ? 5 : 6; else if(h_seg_line4) color = seg7_4[0] ? 5 : 6; else if(h_seg_line5) color = seg7_5[0] ? 5 : 6; else color=0; end else if( w_line_count>126+32 && w_line_count<148+32 ) begin if(vr_seg_line0) color = seg7_0[1] ? 5 : 6; else if(vr_seg_line1) color = seg7_1[1] ? 5 : 6; else if(vr_seg_line2) color = seg7_2[1] ? 5 : 6; else if(vr_seg_line3) color = seg7_3[1] ? 5 : 6; else if(vr_seg_line4) color = seg7_4[1] ? 5 : 6; else if(vr_seg_line5) color = seg7_5[1] ? 5 : 6; else if(vl_seg_line0) color = seg7_0[5] ? 5 : 6; else if(vl_seg_line1) color = seg7_1[5] ? 5 : 6; else if(vl_seg_line2) color = seg7_2[5] ? 5 : 6; else if(vl_seg_line3) color = seg7_3[5] ? 5 : 6; else if(vl_seg_line4) color = seg7_4[5] ? 5 : 6; else if(vl_seg_line5) color = seg7_5[5] ? 5 : 6; else color=0; end else if( w_line_count>148+32 && w_line_count<156+32 ) begin // 7seg middle line if(h_seg_line0) color = seg7_0[6] ? 5 : 6; else if(h_seg_line1) color = seg7_1[6] ? 5 : 6; else if(h_seg_line2) color = seg7_2[6] ? 5 : 6; else if(h_seg_line3) color = seg7_3[6] ? 5 : 6; else if(h_seg_line4) color = seg7_4[6] ? 5 : 6; else if(h_seg_line5) color = seg7_5[6] ? 5 : 6; else color=0; end else if( w_line_count>156+32 && w_line_count<180+32 ) begin if(vr_seg_line0) color = seg7_0[2] ? 5 : 6; else if(vr_seg_line1) color = seg7_1[2] ? 5 : 6; else if(vr_seg_line2) color = seg7_2[2] ? 5 : 6; else if(vr_seg_line3) color = seg7_3[2] ? 5 : 6; else if(vr_seg_line4) color = seg7_4[2] ? 5 : 6; else if(vr_seg_line5) color = seg7_5[2] ? 5 : 6; else if(vl_seg_line0) color = seg7_0[4] ? 5 : 6; else if(vl_seg_line1) color = seg7_1[4] ? 5 : 6; else if(vl_seg_line2) color = seg7_2[4] ? 5 : 6; else if(vl_seg_line3) color = seg7_3[4] ? 5 : 6; else if(vl_seg_line4) color = seg7_4[4] ? 5 : 6; else if(vl_seg_line5) color = seg7_5[4] ? 5 : 6; else color=0; end else if( w_line_count>180+32 && w_line_count<188+32 ) begin // 7seg bottom line if(h_seg_line0) color = seg7_0[3] ? 5 : 6; else if(h_seg_line1) color = seg7_1[3] ? 5 : 6; else if(h_seg_line2) color = seg7_2[3] ? 5 : 6; else if(h_seg_line3) color = seg7_3[3] ? 5 : 6; else if(h_seg_line4) color = seg7_4[3] ? 5 : 6; else if(h_seg_line5) color = seg7_5[3] ? 5 : 6; else color=0; end else color=0;
end reg [2:0]color_fixed;
always @(posedge clk_video) color_fixed<=color; always @*
begin case(color_fixed) 0: begin //gray red_word = 10'b0000011111; green_word = 10'b0000011111; blue_word = 10'b0000111111; end 1: begin //bright green red_word = 10'b0000011111; green_word = 10'b1111111111; blue_word = 10'b0000011111; end 2: begin //dark green red_word = 10'b0000011111; green_word = 10'b0000111111; blue_word = 10'b0000011111; end 3: begin //bright red red_word = 10'b1111111111; green_word = 10'b0000011111; blue_word = 10'b0000011111; end 4: begin //dark red red_word = 10'b0000111111; green_word = 10'b0000011111; blue_word = 10'b0000011111; end 5: begin //bright yellow red_word = 10'b1111111111; green_word = 10'b1111111111; blue_word = 10'b0000011111; end 6: begin //dark yellow red_word = 10'b0000111111; green_word = 10'b0000111111; blue_word = 10'b0000011111; end 7: begin //gray red_word = 10'b0000011111; green_word = 10'b0000011111; blue_word = 10'b0000111111; end endcase
end wire w_tmds_bh;
wire w_tmds_bl;
wire w_tmds_gh;
wire w_tmds_gl;
wire w_tmds_rh;
wire w_tmds_rl; hdmi u_hdmi( .pixclk( w_video_clk ), .clk_TMDS2( w_video_clk5 ), .hsync( r_hsync ), .vsync( r_vsync ), .active( d_active ), .red( red_word ), .green( green_word ), .blue( blue_word ), .TMDS_bh( w_tmds_bh ), .TMDS_bl( w_tmds_bl ), .TMDS_gh( w_tmds_gh ), .TMDS_gl( w_tmds_gl ), .TMDS_rh( w_tmds_rh ), .TMDS_rl( w_tmds_rl )
); altddio_out1 u_ddio1( .datain_h( w_video_clk), .datain_l( w_video_clk), .outclock(w_video_clk5), .dataout( TMDS[1] ) );
altddio_out1 u_ddio0( .datain_h(~w_video_clk), .datain_l(~w_video_clk), .outclock(w_video_clk5), .dataout( TMDS[0] ) );
altddio_out1 u_ddio3( .datain_h( w_tmds_bh), .datain_l( w_tmds_bl), .outclock(w_video_clk5), .dataout( TMDS[3] ) );
altddio_out1 u_ddio2( .datain_h(~w_tmds_bh), .datain_l(~w_tmds_bl), .outclock(w_video_clk5), .dataout( TMDS[2] ) );
altddio_out1 u_ddio5( .datain_h( w_tmds_gh), .datain_l( w_tmds_gl), .outclock(w_video_clk5), .dataout( TMDS[5] ) );
altddio_out1 u_ddio4( .datain_h(~w_tmds_gh), .datain_l(~w_tmds_gl), .outclock(w_video_clk5), .dataout( TMDS[4] ) );
altddio_out1 u_ddio7( .datain_h( w_tmds_rh), .datain_l( w_tmds_rl), .outclock(w_video_clk5), .dataout( TMDS[7] ) );
altddio_out1 u_ddio6( .datain_h(~w_tmds_rh), .datain_l(~w_tmds_rl), .outclock(w_video_clk5), .dataout( TMDS[6] ) ); endmodule

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

А вот его видеодемонстрация:

Мне кажется, что получилось довольно симпатично.

Таким образом, с помощью виртуальных светодиодов и семисегментных индикаторов мне удалось сохранить значительную часть проекта MIPSfpga при портировании на плату у которой физически нет 32х светодиодов и нет 7-ми сегментных индикаторов.

Далее я покажу, как проект MIPSfpga загружается в плату:

На этой видеодемонстрации показано, как загружается ПЛИС платы с ноутбука из среды САПР Quartus Prime и сразу после загрузки ПЛИС подключенный HDMI монитор получает синхронизацию. Затем на ноутбуке компилируется тестовая программа counter и загружается в плату и запускается на процессоре MIPS в FPGA.

Здесь уже больше примеров программ для MIPS:

  1. Запускается тест памяти программа 04_memtest, потом
  2. исследуется программа связи 05_uart для SoC MIPSfpga и для последовательного порта с ПК,
  3. работает программа 06_timer_irq использующая аппаратные прерывания
  4. 09_adc_0 — это программа тестирующая встроенный в ПЛИС MAX10 АЦП

Ну и еще мне хотелось бы показать, как можно вести внутрисхемную отладку программ MIPS с помощью внешнего программатора MBFTDI и программ OpenOCD и GDB:

Подробнее о проекте MIPSfpga в плате Марсоход3 можно прочитать вот здесь.

В заключении хотел бы высказать благодарность SparF, YuriPanchul, Frantony за их замечательный цикл статей о MIPSfpga. Без этих статей не было бы и этой моей работы.

Показать больше

Похожие публикации

Кнопка «Наверх»