- PVSM.RU - https://www.pvsm.ru -
Перенос программного обеспечения с одной компьютерной архитектуры на другую в принципе, с некоторыми оговорками, дело относительно простое. Здесь на помощь приходят такие общеизвестные инструменты, как autoconf / automake/ libtool / gnulib. Собрать программу из исходников на каком нибудь Raspberry/ARM бывает так же просто, как и на ПК с Ubuntu/x86-64.
А вот как заставить проект ПЛИС разработанный для одной платы работать на другой плате? Там и сама ПЛИС может быть другой и на плате совершенно другие компоненты могут стоять. Простой перекомпиляцией проекта не обойтись.
Расскажу о своем опыте портирования проекта MIPSfpga для платы Марсоход3 с ПЛИС MAX10 Intel. Статьи о проекте MIPSfpga неоднократно появлялись на хабре. Они были так интересны, что мне захотелось и самому попробовать этот проект в имеющейся у меня плате. В своей работе я опирался на хабровские статьи
И многие другие…
Итак, что нужно сделать, чтобы портировать проект ПЛИС на другую плату?
Повезет, если исходный проект ориентирован на точно такую же микросхему ПЛИС, как и на вашей плате. Проект 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.
Если плата не очень знакома, ее схему, например, мы досконально пока не изучили, лучше неиспользуеые выводы ПЛИС отключить от цепей платы.
Поскольку тип микросхемы был переназначен, то теперь нужно указать на какие входы/выходы идут те или иные сигналы проекта ПЛИС. В каждом проекте ПЛИС есть модуль самого верхнего уровня. Сигналы этого модуля соединены с выводами микросхемы ПЛИС, с теми, куда мы укажем в настройках проекта.
Вот, например, в моем проекте:
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, если необходимо.
Вероятно, что исходный проект сделан для платы у которой номинал кварцевого генератора не такой, как на нашей плате. Так и оказалось в моем случае. Плата 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 потом нужно вставить в проект.
В случае портирования проектов ПЛИС на другие платы часто сталкиваешься с тем, что повторить проект один-в-один не удастся. Самая простая причина — например, исходный проект сделан для платы у которой есть 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 [4]
А вот его видеодемонстрация:
Мне кажется, что получилось довольно симпатично.
Таким образом, с помощью виртуальных светодиодов и семисегментных индикаторов мне удалось сохранить значительную часть проекта 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 можно прочитать вот здесь [5].
В заключении хотел бы высказать благодарность SparF [6], YuriPanchul [7], Frantony [8] за их замечательный цикл статей о MIPSfpga. Без этих статей не было бы и этой моей работы.
Автор: nckma
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/fpga/275792
Ссылки в тексте:
[1] Как начать работать с MIPSfpga: https://habrahabr.ru/post/275215/
[2] MIPSfpga – практический опыт: https://habrahabr.ru/post/335848/
[3] MIPSfpga и внутрисхемная отладка: https://habrahabr.ru/post/322442/
[4] github.com/marsohod4you/hdmi_leds_seg7: https://github.com/marsohod4you/hdmi_leds_seg7
[5] прочитать вот здесь: https://marsohod.org/projects/proekty-dlya-platy-marsokhod3/mipsfpga
[6] SparF: https://habrahabr.ru/users/sparf/
[7] YuriPanchul: https://habrahabr.ru/users/yuripanchul/
[8] Frantony: https://habrahabr.ru/users/frantony/
[9] Источник: https://habrahabr.ru/post/351672/?utm_campaign=351672
Нажмите здесь для печати.