Перенос программного обеспечения с одной компьютерной архитектуры на другую в принципе, с некоторыми оговорками, дело относительно простое. Здесь на помощь приходят такие общеизвестные инструменты, как 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, который демонстрирует отображение двоичного счетчика на виртуальных светодиодах: github.com/marsohod4you/hdmi_leds_seg7
А вот его видеодемонстрация:
Мне кажется, что получилось довольно симпатично.
Таким образом, с помощью виртуальных светодиодов и семисегментных индикаторов мне удалось сохранить значительную часть проекта 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. Без этих статей не было бы и этой моей работы.
Автор: nckma