Прерывания в Nios II Altera

Прерывания в Nios II AlteraПрерывания в Nios II Altera, в частности внешние, совсем не похожи на прерывания современного микропроцессора или микроконтрлера. Не похожи в первую очередь потому, что в простейшем варианте любое прерывание - это переход по одному и тому же вектору. Далее программно вычисляется флаг перехода и осуществляется вход в обработчик. Все это занимает огромное количество времени доходящее до 1000 тактов.

Выход из ситуации - применение ядра аппаратного контроллера прерываний которое входит в библиотеку Nios II. Но это уже тема другого поста. В этом посте о том как создать простейший модуль с внешним прерыванием и простейший софт с его обработчиком. Все построено на базе все той же DE0 Board.

Итак для того, чтобы создать модуль с внешним прерыванием необходимо в SOPC создать новый компонент Avalon-MM Slave With Interupt, сгенерровать HDL файл а затем в него добавить пользовательские сигналы. В результате получится компонент который в SOPC подключается с одной стороны к шине Avalon-MM Slave, а с другой к внутренним и внешним пользовательским сигналам FPGA. Последовательно как это делается.

В SOPC File->New Component затем Templates->Add Typical Avalon-MM Slave With Interupt

Новый компонент в Sopc Builder

Далее небходимо создать HDL шаблон компонента и сохранить файл под любым нужным именем. Компонент пока можно не сохранять поскольку HDL файл компонента необходимо еще пополнить пользовательскими сигналами со стороны FPGA.

HDL шаблон

Теперь файл шаблона можно откорректировать добавив в него пользовательские сигналы типа Exports, примерно так:


--------------------------------------------------------------------
-- Project : ramtest 
--------------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use IEEE.numeric_std.all;

entity ramtest is
	port (
--		Avalon Slave
		clk              	: in  std_logic                     := '0';             -- clock.clk
		reset_n          	: in  std_logic                     := '0';             --      .reset_n
		avs_s0_address   	: in  std_logic_vector(1 downto 0)  := (others => '0'); --    s0.address
		avs_s0_read_n    	: in  std_logic                     := '0';             --      .read_n
		avs_s0_readdata  	: out std_logic_vector(15 downto 0);                    --      .readdata
		avs_s0_write_n   	: in  std_logic                     := '0';             --      .write_n
		avs_s0_writedata 	: in  std_logic_vector(15 downto 0) := (others => '0'); --      .writedata
		avs_s0_byteenable	: in  std_logic_vector(1 downto 0) 	:= (others => '0');
		ins_irq0_irq 			: out std_logic;	
		
--		RAM Exports
--   	Вспомогательные сигналы (тест загрузки и чтения RAM)
		avs_s0_read_ext		: out  std_logic;             													--				
		avs_s0_irq_ext		: out  std_logic;	
		ram_irq		 		 		: in  std_logic                     := '0';	           	--  внешний старт прерывания
		ram_load				 	: out  std_logic;                                 			--  старт загрузки RAM (для теста) 	
		ram_wr				 	 	: out  std_logic;                                 			--	строб записи RAM     	
		cnt_out				 	 	: out  std_logic;                                 			--   	
		en_irq_out		 	 	: out  std_logic;                                 			--   	
		ram_data_in			 	: out  std_logic_vector(15 downto 0) 										--  входные данные RAM	(для теста)
	);

end entity ramtest;

architecture rtl of ramtest is

--------------------------------------------------------------------
	-- компонент двухпортовая RAM 

	component ramresult
	generic 
	(
		DATA_WIDTH : integer := 16;
		ADDR_WIDTH : integer := 2
	);

	port 
	(
		rclk	: in std_logic;
		wclk	: in std_logic;
		raddr	: in std_logic_vector (0 to ADDR_WIDTH - 1);
		waddr	: in std_logic_vector (0 to ADDR_WIDTH - 1);
		data	: in std_logic_vector((DATA_WIDTH-1) downto 0);
		we		: in std_logic := '1';
		q			: out std_logic_vector((DATA_WIDTH -1) downto 0)
	);
	end component;

	-- сигналы загрузки и теста чтения RAM 	
	signal cnt 							: std_logic_vector(3 downto 0) 		:= X"0";			-- делитель
	signal s_data_in				: std_logic_vector(15 downto 0) 	:= X"0000";		-- данные RAM (вход)
	signal s_data_out				: std_logic_vector(15 downto 0);	-- данные RAM (выход)
	signal s_rd 						: std_logic;											-- клок чтения из RAM
	signal s_wr 						: std_logic; 											-- клок записи в RAM
	signal s_addr_wr_ram 		: std_logic_vector(1 downto 0);		-- адрес записи
	signal en_loadcnt 			: std_logic	:= '0';								-- разрешение загрузки в RAM
	signal s_lrcnt 					: std_logic_vector(5 downto 0) 		:= "000000";	-- счетчик 
	signal s_ramload 				: std_logic	:= '1';								-- 
	signal s_ramread 				: std_logic	:= '1';								--
	signal en_wrrd 					: std_logic	:= '1';								-- 

	-- сигналы для шины Avalon
	signal avs_s0_address_reg 		: std_logic_vector(1 downto 0);	
	signal avs_s0_we 							: std_logic;	
	signal avs_s0_read_n_ext_reg	: std_logic;	

	-- сигналы для IRQ
	signal irq_cnt					: std_logic_vector(31 downto 0);	-- 
	signal s_irq_t 					: std_logic;											-- 
	signal en_irq						: std_logic;											-- 
	signal s_irq_d					: std_logic;											-- 
	signal s_irq_n					: std_logic;											-- 
	signal s_irq_h					: std_logic;											-- 

begin

	uramresult : ramresult
		generic map (16, 2)
		port map (rclk => s_rd, wclk => s_wr, raddr => avs_s0_address_reg, 
			waddr => s_addr_wr_ram, data => s_data_in, we => avs_s0_we, q => s_data_out);


--------------------------------------------------------------------
--			Вспомогательные сигналы
--------------------------------------------------------------------
-- 
	process(clk)
	begin
	if (rising_edge(clk)) then 
		cnt <= cnt + 1;
	end if;	
	end process;

	cnt_out <= cnt(0);

	-- сигналы для загрузи RAM
	process(cnt(0))
	begin
	if (rising_edge(cnt(0))) then 
		if(en_wrrd = '1') then
			s_lrcnt <= s_lrcnt + 1;
		end if;	
		if (s_lrcnt = "000011") then 
				s_ramload <= '0';
		elsif	(s_lrcnt = "000101") then 		
				s_ramload <= '1';
		elsif	(s_lrcnt = "001011") then 		
				s_ramread <= '0';
		elsif	(s_lrcnt = "001101") then 		
				s_ramread <= '1';
		elsif	(s_lrcnt = "101101") then 
			en_wrrd <= '0';
		end if;	
	end if;	
	end process;
	
--------------------------------------------------------------------
--			Заполнение RAM 
--------------------------------------------------------------------

-- старт стоп перебора адресов RAM	
	process(cnt(0))
	begin
	if (falling_edge(cnt(0))) then 
		if(s_ramload = '0') then
			en_loadcnt <= '1';
		end if;	
    if(en_loadcnt = '1') then
			s_addr_wr_ram <= s_addr_wr_ram + 1;
		else	
			s_addr_wr_ram <= (others => '0');
		end if;
		if(s_addr_wr_ram = "11") then
			en_loadcnt <= '0';
		end if;
  end if;
	end process;

	-- на RAM запись
	s_wr <= not((cnt(0) and en_loadcnt) and (not clk)); 
	ram_wr <= s_wr;  
	ram_load <= en_loadcnt;

-- загрузка RAM 
	process(cnt(0))
	begin
	if (rising_edge(cnt(0))) then 
    if(en_loadcnt = '1') then
			if(s_addr_wr_ram = "00") then
				s_data_in <= X"3132";
			elsif(s_addr_wr_ram = "01") then
				s_data_in <= X"3334";
			elsif(s_addr_wr_ram = "10") then
				s_data_in <= X"3536";
			elsif(s_addr_wr_ram = "11") then
				s_data_in <= X"3738";
			end if;
		end if;
  end if;
	end process;

	ram_data_in <= s_data_in;

--------------------------------------------------------------------
--			Avalon slave register read logic 
--------------------------------------------------------------------

	-- из Avalon MM Slave 
	avs_s0_read_n_ext_reg <= avs_s0_read_n;
	avs_s0_read_ext <= avs_s0_read_n_ext_reg;

	-- на RAM адрес из Avalon MM Slave 
	avs_s0_address_reg <= avs_s0_address;

	-- данные на Avalon MM Slave из RAM
	avs_s0_readdata <= s_data_out;

	-- сигнал чтения из Avalon MM Slave на RAM 
	s_rd <= (not clk and not avs_s0_read_n); 

	-- сигнал выбор кристалла из Avalon MM Slave на RAM 
	avs_s0_we <= avs_s0_read_n; 

	s_irq_t <= not ram_irq and en_irq; 
	
	-- прерывания
	process(clk)
	begin
	if (reset_n = '0') then
		en_irq <= '1'; 
		irq_cnt <= (others => '0');
		s_irq_h <= '0';
	else if (rising_edge(clk)) then 
    if(s_irq_t = '1') then
				s_irq_d <= '1';
		else		
				s_irq_d <= '0';
		end if;
    if(s_irq_d = '1') then
			en_irq <= '0';
			s_irq_h <= '1';
		end if;
		if (en_irq = '0') then
			irq_cnt <= irq_cnt + 1;
			s_irq_n <= '1';	
		end if;
		if(avs_s0_address_reg = "11" and avs_s0_read_n_ext_reg = '0') then 
				s_irq_h <= '0';
		elsif (irq_cnt = "00000001111111111111111111111111") then
			en_irq <= '1';
			irq_cnt <= (others => '0');
		end if;	
  end if;
  end if;
	end process;

	ins_irq0_irq <= s_irq_h;
	en_irq_out <= en_irq;
	
	-- прерывание длина (внешний тест)	
	avs_s0_irq_ext <= s_irq_h;

end architecture rtl; -- of ramtest

Avalon-MM Slave With Interupt и пользовательские сигналы

Далее из файла можно создать компонент и сохранить его под любым нужным имененем. В данном проекте создан компонент ramtest имеющий в своем составе шину Avalon-MM Slave With Interupt со стороны процессора Nios II и двухпортовую пользовательскую память со стороны FPGA. На базе данного компонента и построена логика работы внешнего прерывания. Сигнал ram_irq соединен с внешней кнопкой на DE0. При ее нажатии модуль ramtest формирует уровень прерывания на ins_irq0_irq который сбрасывается программно после входа в подпрограмму обработчик. Конфигурация системы двольно проста:

Конфигурация системы с прерываениями

Как все работает? После загрузки системы в FPGA, в двухпортовую RAM записываются 4 слова данных 0x3132 0x3334 0x3536 0x3738, которые программно из Nios II через шину Avalon-MM Slave в цикле читаются из памяти и передаются по rs232 плюс текст "Test Success". В сою очередь если нажать на кнопку KN2 DE0 Board то будет сгенерировано внешнее прерывание, выполнится подпрограмма обратчик.

Внешние прерывания - визуализация на UART

Обработчик сбросит тригер формирующий прерывание путем чтения данных из памяти по одному из адресов, считает все оставшиеся данные и передаст их по rs232 плюс дополнительно текст "Irq Success". Таким бразом будет осуществлен визуальный контроль работы прерываний в Nios II. А для пущей убедительности можно воспользоваться готовым проектом NiosIrq_Quartus90 в котором реализовано все вышесказанное.

Похожий проект выполнен и на базе DE0_nano Board от Terasic в Quartus11.0. Скриншет работы теста на консоли Nios II ниже по тексту.

Внешние прерывания Nios II - визуализация на консоли

Top.Mail.Ru