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

в 19:57, , рубрики: Altera, fpga, MIPSfpga, Quartus, virtual LEDs, Анализ и проектирование систем, Марсоход3, отладка, программирование микроконтроллеров

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

Перенос программного обеспечения с одной компьютерной архитектуры на другую в принципе, с некоторыми оговорками, дело относительно простое. Здесь на помощь приходят такие общеизвестные инструменты, как 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 тысяч логических элементов, но у первой гораздо больше входных/выходных сигналов. Понятно, что вместе с переназначением типа микросхемы ПЛИС «слетают» все назначения сигналов на конкретные выводы микросхемы.

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

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

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

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

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

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 сигналы — вот они присутствуют в моем проекте.

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

В среде 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.

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

Нужно указать правильную исходную частоту и в 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js