Выполнение транзакций на шине PCI. Реализация на VHDL

от автора

Не так давно я спрашивал о механизме опроса PCI-устройств. После я устроился на работу, доделал тестовое задание, а спрашивал я именно о нем, и благополучно забыл о нем. Но недавно выдали новый проект и пришлось все вспомнить, заодно и решил написать сюда.

Транзакций на шине PCI достаточно много, в данном топике будет описаны только следующие:

  • Конфигурационные транзакции
  • Транзакции ввода/вывода
  • Транзакции обращения к памяти


При осуществлении транзакций возможно 2 варианта:

  • Когда ведущим устройством является южный мост
  • Когда ведущим устройством является устройство, подключенное к шине PCI

Как уже сложилось, при рассмотрении шины PCI, ведущее устройство я буду называть мастером(Master), ведомое — таргетом (Target).
В данной статье рассматриваются только транзакции, когда мастером является южный мост, так как транзакции, когда мастером является устройство, подключенное к шине PCI заслуживает отдельной статьи.

И так, для работы с шиной, нам понадобятся следующие сигналы:

entity main is Port (	 	clk	    : in	std_logic; 	AD	: inout	std_logic_vector(31 downto 0); 	IDSEL	: in	std_logic; 	CBE	: in	std_logic_vector(3 downto 0); 	FRAME	: in	std_logic;			   	IRDY	: in	std_logic; 	TRDY	: inout	std_logic; 	STOP	: inout	std_logic; 	PAR	: inout	std_logic; 	RST	: in	std_logic; 	DEVSEL	: inout	std_logic  ); end main; 

clk (Clock) — обеспечивает синхронизацию всех транзакций на PCI, а также является входным для каждого PCI — устройства.
AD (Address and Data) — мультиплексирования шина адреса и данных.
IDSEL (Initialization Device Select) — выбор устройства инициализации, используется для выбора кристалла при транзакциях чтения конфигурации и записи.
CBE (Bus Command and Byte Enables) — команды шины и разрешение байта.
FRAME (Frame) — сигнал выдаётся мастером в начале транзакции и определяет её длительность.
IRDY (Initiator Ready) — сигнал готовности мастера. Он свидетельствует о готовности мастера завершить текущую фазу данных.
TRDY (Target Ready) — сигнал готовности таргета, свидетельствующий о готовности таргета завершить текущую фазу данных.
STOP (Stop) — этот сигнал выдаётся таргетом, если он хочет остановить текущую транзакцию.
PAR (Parity) — контроль четности по линиям AD и CBE.
RST(Reset) — cигнал сброса. Является асинхронным.
DEVSEL (Device Select) — сигнал выбора устройства.

Перед началом работы с любым устройством его нужно инициализировать. Поэтому рассмотрим особенности выполнения конфигурационных транзакций.

Конфигурационные транзакции. Общие сведения.

Вид на конфигурационные транзакции со стороны ОС

Для генерации конфигурационных транзакций PCI на ПК используются обращения к двум портам ввода-вывода, носящим имена CONFIG_ADDRESS и CONFIG_DATA, имеющим адреса 0CF8h и 0CFCh соответственно и входящим в состав моста Host–PCI, через который шина PCI прямо или косвенно соединяется с процессором.
Порт CONFIG_ADDRESS имеет размер двойное слово и доступен только как единое целое. Обращения меньшего размера по принадлежащим ему адресам передаются на шину PCI как обычные транзакции ввода-вывода. Этот порт доступен для чтения и записи и имеет следующий формат:

Когда необходимо выполнить конфигурационную транзакцию, в этот порт записывается адрес регистра конфигурационного пространства PCI, состоящий из номеров шины (разряды 23–16), устройства (15–11), функции (10–8) и собственно регистра (7–2). Биты 1 и 0 должны всегда содержать нули, а старший бит должен содержать единицу, разрешая тем самым выполнение конфигурационной транзакции. Разряды 30–24 зарезервированы и должны содержать нули.

Собственно генерация конфигурационной транзакции происходит при чтении или записи порта CONFIG_DATA, когда в CONFIG_ADDRESS был записан адрес с установленным старшим битом и номером шины, соответствующим шине, подключенной к мосту Host–PCI, или любой шине PCI, лежащей ниже этой шины и соединённой с ней через один или несколько мостов PCI–PCI (допустимый диапазон номеров шин задаётся мосту Host–PCI в процессе его настройки). Доступ к порту CONFIG_DATA должен иметь размер, равный размеру считываемого или записываемого конфигурационного регистра, адрес которого находится в CONFIG_ADDRESS.
Если номер шины, заданный в CONFIG_ADDRESS, совпадает с номером шины, подключённой непосредственно к мосту Host–PCI, генерируется конфигурационная транзакция с адресом типа 0, причём номер устройства, находящийхся в разрядах 15–11 порта CONFIG_ADDRESS, используется для выдачи одного из сигналов IDSEL, которые и служат для выбора конкретного устройства. Кроме того, декодированный номер устройства (один единичный и остальные нулевые биты) в фазе адреса конфигурационной транзакции передаётся в разрядах 31–11 адреса.
Если адрес в CONFIG_ADDRESS указывает не ту шину, которая непосредственно подключена к мосту Host–PCI, последний генерирует конфигурационную транзакцию с адресом типа 1. Она будет обработана мостом PCI–PCI, который опознает содержащийся в адресе номер шины. Этот мост либо выполнит конфигурационную транзакцию с адресом типа 0 (если адресуемое устройство подключено к шине, прямо подсоединённой к этому мосту), либо сгенерирует транзакцию с адресом типа 1, обеспечив тем самым её прохождение через следующий мост. Длина этой цепочки теоретически ограничена только разрядностью поля, отведённого под номер шины (8 бит).

Если при выполнении транзакции выяснится, что адресуемого конфигурационного регистра не существует (указан номер несуществующей шины, устройства, функции или регистра), то операция записи не возымеет никаких действий, а операция чтения вернёт процессору значение, содержащее единицы в каждом разряде

Формат адреса для транзакции типа 1.

Формат адреса для транзакции типа 0.

Формат регистра конфигурации:

Минимальный набор регистров:

  • Vendor ID — поле идентифицирует изготовителя устройства. Запрещено использовать значение 0xFFFF.
  • Device ID — поле идентифицирует конкретный вид устройства.
  • Revision ID — дополнение к идентификатору устройства. Может быть равно нулю.
  • Header Type — Для многофункциональных устройств. Если 7ой бит равен 0, то устройство является однофункцальным, иначе — многофункциональное.
  • Class Code — доступен только для чтения. Используется для идентификации общего функционального назначения устройства. Старший байт (адрес 0Bh) определяет базовый класс, средний — подкласс, младший — программный интерфейс (если он стандартизован).
  • Subsystem ID, Subsystem Vendor ID — задаются производителем. Только для чтения. Хранят идентификаторы, позволяющие точно идентифицировать карты и устройства (в системе могут быть установлены
    несколько карт с совпадающими идентификаторами устройства и производителя (Device ID и Vendor ID).
  • BAR0 — BAR5 — описывают области памяти и портов ввода-вывода.

Для областей памяти и портов описания различаются:

  • Бит 0 = 0 — признак памяти. Размером не более 2 Гбайт
  • Бит 0 = 1 — признак области портов. Размером до 256 байт.

Размер областей вычисляется следующим образом. В BAR записывается 0xFFFFFFFF. Далее, из BAR считывается значение, и вычитается из 0xFFFFFFFF. Результат и есть размер области. Единица в младшем бите не учитывается.

Общий алгоритм выполнения транзакций

Мастер выставляет на шине AD адрес устройства, на шине CBE выполняемую команду, устанавливает сигнал FRAME в 0 и сигнал IRDY в 0. Далее, мастер ждет от таргета — выставления им сигналов TRDY и DEVSEL. Так же, таргет выставляет на шину AD запрашиваемые данные. Данные считаются валидными, когда FRAME, IRDY и DEVSEL равны уровню логического нуля.

Реализация

Для обращения к выводам ПЛИС потребуются специальные компоненты: буферы ввода/вывода.
Так, для шины AD подключение будет выглядеть следующим образом:

signal AD_I:			std_logic_vector (AD'range); signal AD_O:			std_logic_vector (AD'range); signal AD_T:			std_logic;  AD_BUF: for iCount in AD'low to AD'high generate       begin          	IOBUF_AD : IOBUF 	generic map  	( 	DRIVE => 12, 	IOSTANDARD => "PCI33_3", 	SLEW => "SLOW") 	port map ( 		O => AD_I(iCount), 		IO => AD(iCount), 		I => AD_O(iCount), 		T => AD_T 	); end generate; 

Где,

  • O — выход буфера.
  • IO — вход/выход буфера, непосредственно подключается к выводу ПЛИС.
  • I — выход буфера.
  • T — управление входом, уровень единицы — вход, уровень нуля — выход.

Для остальных сигналов аналогично, не буду приводить, что бы не загромождать статью.

Как я уже писал выше, при начале транзации, когда на шине AD выставлен адрес, всегда сигнал FRAME равен нулю. Ниже приведен код, который формирует сигнал AdrPhASE, во время действия которого нужно защелкнуть шину адреса и шину команд для последующей работы.

signal AdrPhASE:		std_logic; signal FRAME_D:	std_logic; signal Addres:			std_logic_vector(AD_I'range); signal Command:	std_logic_vector(CBE'range); signal bCfgTr:			boolean;  process (clk_I, RST_I) begin 	if (RST_I = '1') then 		if (rising_edge(clk_I)) then 			FRAME_D <= FRAME_I after cTCQ; 		end if; 	else FRAME_D <= '1' after cTCQ; 	end if; end process;  AdrPhASE <= not FRAME_I and FRAME_D;  process (clk_I, RST_I) begin 	if (RST_I = '0') then 		Address	<= (others => '0') after cTCQ; 		Command	<= (others => '0') after cTCQ; 		bCfgTr <= false after cTCQ; 	elsif (rising_edge(clk_I)) then 		if (AdrPhASE = '1') then 			Address	<= AD_I	 after cTCQ; 			Command	<= CBE_I after cTCQ; 			bCfgTr	<= (IDSEL_I = '1') after cTCQ; 		end if; 	end if; end process; 

Далее, работу всего устройства можно описать с помощью автомата.

Код автомата

type TSM_PCI_T is		(sIDLE, sDECODE, sCFG_READ, sCFG_WRITE, sIO_READ, sIO_WRITE, sMEM_READ, sMEM_WRITE); signal smPCI_T: 		TSM_PCI_T;  process(clk_I, RST_I) begin 	if (RST_I = '0') then 		smPCI_T <= sIDLE after cTCQ; 	elsif (rising_edge(clk_I)) then 		case (smPCI_T) is 			when sIDLE	=>	if (AdrPhASE = '1') then smPCI_T <= sDECODE after cTCQ;	end if; 			when sDECODE	=>	if (bCfgTr and Address(10 downto 8) = b"000" and Command(3 downto 1) = b"101")  then 							if (Command(0) = '0') then smPCI_T <= sCFG_READ	after cTCQ; 								else smPCI_T <= sCFG_WRITE after cTCQ; end if; 						elsif (Command(3 downto 1)= b"001") and (Addres(31 downto 8) = BAR0(31 downto 8))then	 							if (Command(0) = '0') then smPCI_T <= sIO_READ after cTCQ; 							else smPCI_T <= sIO_WRITE after cTCQ;	end if; 						elsif (Command(3 downto 1) = b"011") and (Addres(31 downto 16) = BAR1(31 downto 16)) then 							if (Command(0) = '0') then smPCI_T <= sMEM_READ	after cTCQ; 								else smPCI_T <= sMEM_WRITE after cTCQ; end if; 						else smPCI_T <= sIDLE after cTCQ;  						end if; 			when sCFG_READ	=>	if (IRDY_I = '0') then	smPCI_T <= sIDLE after cTCQ;  end if;  	 			when sCFG_WRITE	=>	if (IRDY_I = '0') then	smPCI_T <= sIDLE after cTCQ;  end if;  	 			when sIO_WRITE	=>	if (IRDY_I = '0') then	smPCI_T <= sIDLE after cTCQ;  end if;  	 			when sIO_READ	=>	if (IRDY_I = '0') then	smPCI_T <= sIDLE after cTCQ;  end if;  	 			when sMEM_READ	=>	if (IRDY_I = '0') then	smPCI_T <= sIDLE after cTCQ;  end if; 			when sMEM_WRITE	=>	if (IRDY_I = '0') then	smPCI_T	<= sIDLE after cTCQ;  end if; 			when others		=>	smPCI_T <= sIDLE		after cTCQ; 		end case; 	end if; end process; 

Для понимания выше написанного, приведу возможные команды, передаваемые по шине CBE.

  • 0010 I/O Read
  • 0011 I/O Write
  • 0110 Memory Read
  • 0111 Memory Write
  • 1010 Configuration Read
  • 1011 Configuration Write

Каждой команде соответствует свое состояние автомата. Переход в него зависит от текущего состояния шины CBE и шины AD для транзакций обращения к памяти и портам ввода-вывода. Выход в начальное состояние осуществляется по приходу сигнала IRDY от мастера.

Чтение конфигурации

Как было описано выше, для обработки устройством используются транзакции типа 0. Так как устройство однофункциональное, то номер функции — 000, который проверятся в управляющем автомате. В зависимости от номера регистра (биты 7..0 шины AD) на шину AD выдается нужный регистра, согласно рисунку выше.

Код чтения конфигурации

signal CfgRData:			std_logic_vector(31 downto 0):=x"00000000"; signal CommandReg:			std_logic_vector(15 downto 0) := x"0000"; signal StatusReg:			std_logic_vector(15 downto 0) := x"0200"; signal LatencyTimer:			std_logic_vector(7 downto 0) := x"00"; signal CacheLineSize:			std_logic_vector(7 downto 0) := x"00"; signal BAR0:					std_logic_vector(31 downto 0) := x"00000001"; signal BAR1:					std_logic_vector(31 downto 0) := x"00000000"; signal InterruptLine:			std_logic_vector(7 downto 0); process (clk_I) begin 	if (rising_edge(clk_I)) then 		case (Address(7 downto 0)) is  			when x"00"	=> CfgRData <= x"00017788" ; --Device ID and Vendor ID 			when x"04"	=> CfgRData <= StatusReg & CommandReg; --Status Register, Command Register 			when x"08"	=> CfgRData <= x"10000001"; -- Class Code and Revision ID 			when x"0C"	=> CfgRData <= x"0000" & LatencyTimer & CacheLineSize; -- BIST, Header Type(bit 7 = 0, single, bits 6-0 = 0, type0), Latency Timer(for masters), Cache Line Size (bit 2 in 1) 			when x"10"	=> CfgRData <= BAR0; -- Base Adress 0 (Register IO address decoder) 			when x"14"	=> CfgRData <= BAR1; -- Base Adress 1 			when x"28"	=> CfgRData <= x"00000000"; -- CarfdBus CIS Pointer 			when x"2C"	=> CfgRData <= x"00017788"; -- Subsystem ID, Subsystem Vendor ID 			when x"30"	=> CfgRData <= x"00000000"; -- Expanxion Rom Base Address 			when x"34"	=> CfgRData <= x"00000000"; -- Reserved, Capabilitis Pointer 			when x"38"	=> CfgRData <= x"00000000";	-- Reserved 			when x"3C"	=> CfgRData <= x"004001" & InterruptLine;	-- Max_Lat(only bus master), Min_Gnt, Interrupt Pin, Interrupt Line 			when others	=> CfgRData <= (others => '0'); 		end case;	 	end if; end process; 

Так выгядит чтение конфигурации в симуляторе:

Полное изображение

Запись конфигурации

На шине AD выставляет адрес регистра для записи, а в следующем такте выставляются данные, которые нужно записать. В BAR0 биты 7..0 являются read-only, в BAR1 биты 15..0 являются read-only. Поэтому адресов ввода/вывода 256, адресов памяти 4 294 967 296.

Код записи конфигурации

process(clk_I, RST_I) begin 	if(RST_I = '0')then 		CommandReg <= x"0000" after cTCQ; 		StatusReg <= x"0200" after cTCQ; 		LatencyTimer <= x"00" after cTCQ; 		CacheLineSize <= x"00" after cTCQ; 		BAR0 <= x"00000001" after cTCQ; 		BAR1 <= x"00000000" after cTCQ; 	elsif(rising_edge(clk_I)) then 		if (smPCI_T = sCFG_WRITE) then 			case(Address(7 downto 0)) is  				when x"04"	=>	if (CBE_I(1) = '0') then	CommandReg(15 downto 8) <= AD_I(15 downto 8)	after cTCQ; end if; 								if (CBE_I(0) = '0') then	CommandReg(7 downto 0) <= AD_I(7 downto 0)		after cTCQ; end if; 				when x"0C"	=>	if (CBE_I(1) = '0') then	LatencyTimer <= AD_I(15 downto 8)				after cTCQ; end if; 								if (CBE_I(0) = '0') then	CacheLineSize <= AD_I(7 downto 0)				after cTCQ; end if; 				when x"10"	=>	if (CBE_I(3) = '0')	then 	BAR0(31 downto 24) <= AD_I(31 downto 24)		after cTCQ; end if; 								if (CBE_I(2) = '0')	then 	BAR0(23 downto 16) <= AD_I(23 downto 16)		after cTCQ; end if; 								if (CBE_I(1) = '0')	then 	BAR0(15 downto 8) <= AD_I(15 downto 8)			after cTCQ; end if; 				when x"14"	=>	if (CBE_I(3) = '0')	then 	BAR1(31 downto 24) <= AD_I(31 downto 24)		after cTCQ; end if; 								if (CBE_I(2) = '0')	then 	BAR1(23 downto 16) <= AD_I(23 downto 16)		after cTCQ; end if; 				when x"3C"	=>	if (CBE_I(0) = '0') then	InterruptLine <= AD_I(7 downto 0)				after cTCQ; end if; 				when others	=>	null; 			end case; 		end if; 	end if; end process; 

Запись в порт

На шине AD выставляется номер регистра для записи, в следующем такте выставляются данные, которые нужно записать.
Приведем пример только для записи одного регистра, остальные записываются аналогично.

signal IOReg0:			std_logic_vector (31 downto 0);  process(clk_I, RST_I) begin 	if(RST_I = '0') then 		IOReg0 <= x"00000000" after cTCQ; 	elsif (rising_edge(clk_I)) then 		if (smPCI_T = sIO_WRITE and Address(7 downto 0) = x"00") then 			if (CBE_I(0) = '0') then	IOReg0( 7 downto  0) <= AD_I( 7 downto  0) after cTCQ; end if; 			if (CBE_I(1) = '0') then IOReg0(15 downto  8) <= AD_I(15 downto  8) after cTCQ; end if; 			if (CBE_I(2) = '0') then IOReg0(23 downto 16) <= AD_I(23 downto 16) after cTCQ; end if; 			if (CBE_I(3) = '0') then IOReg0(31 downto 24) <= AD_I(31 downto 24) after cTCQ; end if; 		end if; 	end if; end process; 

Чтение порта

На шине AD выставляется номер регистра, который нужно прочитать. Затем устройство выдает на шину AD запрашиваемые данные.

Код чтения порта

signal IORDate:			std_logic_vector (31 downto 0);  process (clk_I, RST_I) begin 	if (RST_I = '0') then 		IORDate <= x"00000000"; 	elsif (rising_edge(clk_I)) then 			case (Address(7 downto 0)) is 				when x"00" =>	IORDate <= IOReg0 after cTCQ; 				when x"04" =>	IORDate <= IOReg1 after cTCQ; 				when x"08" =>	IORDate <= IOReg2 after cTCQ; 				when x"0C" =>	IORDate <= IOReg3 after cTCQ; 				when x"10" =>	IORDate <= IOReg4 after cTCQ; 				when x"14" =>	IORDate <= IOReg5 after cTCQ; 				when x"18" =>	IORDate <= IOReg6 after cTCQ; 				when x"1C" =>	IORDate <= IOReg7 after cTCQ; 				when x"20" =>	IORDate <= IOReg8 after cTCQ; 				when x"24" =>	IORDate <= IOReg9 after cTCQ; 				when others =>	IORDate <= (others => '0'); 			end case; 	end if; end process; 

Так выглядит запись и чтение порта ввода-вывода:

Полное изображение

Запись и чтение памяти

На шине AD выставляется адрес, по которому нужно записать данные, а в следующем такте сами данные. При чтении на шину AD выставляет адрес для чтения, затем на шину AD таргет выставляет сами данные.
Данные пишуется в RAM в порт А, читаются из порта B.

Код обращения к памяти

signal RamWrEn:		std_logic; signal RamOutputDate:			std_logic_vector (31 downto 0); signal RamInputDate:			std_logic_vector (31 downto 0); signal RamRst:				std_logic := '0';  RAMB16_S36_S36_inst : RAMB16_S36_S36 port map ( 	DOA	=> open,						-- Port A 32-bit Data Output 	DOB	=> RamOutputDate,			-- Port B 32-bit Data Output 	DOPA	=> open,					-- Port A 4-bit Parity Output 	DOPB	=> open,					-- Port B 4-bit Parity Output 	ADDRA	=> Address(8 downto 0),	-- Port A 9-bit Address Input 	ADDRB	=> Address(8 downto 0),	-- Port B 9-bit Address Input 	CLKA	=> clk_I,					-- Port A Clock 	CLKB	=> clk_I,					-- Port B Clock 	DIA	=> RamInputDate,			-- Port A 32-bit Data Input 	DIB	=> x"00000000",			-- Port B 32-bit Data Input 	DIPA	=> x"0",					-- Port A 4-bit parity Input 	DIPB	=> x"0",					-- Port-B 4-bit parity Input 	ENA	=> '1',					-- Port A RAM Enable Input 	ENB	=> '1',					-- PortB RAM Enable Input 	SSRA	=> '0',					-- Port A Synchronous Set/Reset Input 	SSRB	=> '0',					-- Port B Synchronous Set/Reset Input 	WEA	=> RamWrEn,		-- Port A Write Enable Input 	WEB	=> '0'					-- Port B Write Enable Input );  process(clk_I) begin 	if (rising_edge(clk_I)) then 		if (RST_I = '1') then 			RamRst <= '0'; 		else 			RamRst <= '1'; 		end if; 	end if; end process;  process(clk_I, RST_I) begin 	if(RST_I = '0') then 		RamInputDate <= (others => '0') after cTCQ; 		RamWrEn <= '0' after cTCQ; 	elsif (rising_edge(clk_I)) then 		if (smPCI_T = sMEM_WRITE) then 			if (CBE_I(0) = '0') then RamInputDate(7  downto  0) <= AD_I( 7 downto  0) after cTCQ; end if; 			if (CBE_I(1) = '0') then RamInputDate(15 downto  8) <= AD_I(15 downto  8) after cTCQ; end if; 			if (CBE_I(2) = '0') then RamInputDate(23 downto 16) <= AD_I(23 downto 16) after cTCQ; end if; 			if (CBE_I(3) = '0') then RamInputDate(31 downto 24) <= AD_I(31 downto 24) after cTCQ; end if; 			RamWrEn <= '1' after cTCQ; 		else  			RamWrEn <= '0' after cTCQ; 		end if; 	end if; end process; 

Так выглядит запись и чтение памяти в симуляторе:

Полное изображение

Данные на шину AD выводятся следующим образом. В зависимости от состояния автомата, к выходному буферу подключается соответствующий регистр.

process (clk_I, RST_I) begin 	if (RST_I = '0') then 		AD_O <= (others => '0') after cTCQ; 	elsif (rising_edge(clk_I)) then 		if (smPCI_T = sCFG_READ) then 			AD_O <= CfgRData after cTCQ; 		elsif (smPCI_T = sIO_READ) then 			AD_O <= IORDate after cTCQ; 		elsif (smPCI_T = sMEM_READ) then 			AD_O <= RamOutputDate after cTCQ; 		end if; 	end if; end process; 

Сигнал разрешения выдачи данных на шину AD формируется следующим образом:

process (clk_I, RST_I) begin 	if (RST_I = '0') then 		AD_T <= '1' after cTCQ; 	elsif (rising_edge(clk_I)) then 		AD_T <= not b2l(smPCI_T = sCFG_READ or smPCI_T = sIO_READ or smPCI_T = sMEM_READ) after cTCQ; 	end if; end process; 

Заключение

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


Работает! 🙂

Сам проект rghost.ru/46686218. Открывать xilinx ise 14.2.

ссылка на оригинал статьи http://habrahabr.ru/post/182152/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *