Транзакций на шине 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) — сигнал выбора устройства.
Перед началом работы с любым устройством его нужно инициализировать. Поэтому рассмотрим особенности выполнения конфигурационных транзакций.
Конфигурационные транзакции. Общие сведения.
Порт 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/
Добавить комментарий