PROTEQ — протокол обмена по мультигигабитным линиям для ПЛИС Xilinx

в 14:00, , рубрики: fpga, open source, PROTEQ, xilinx

Современные ПЛИС содержат мультигигабитные линия связи и существует большое количество протоколов для обмена. Однако при ближайшем рассмотрении применять стандартные протоколы в ПЛИС не всегда удобно. Например для ПЛИС Xilinx доступны реализации PCI Express, RapidIO, Aurora; У каждого из них есть недостатки. PCI Express и RapidIO работают с кодировкой 8/10 что сразу ограничивает пропускную способность. Aurora может работать с кодировкой 64/66 но не обеспечивает восстановление данных после сбоя. С учётом недостатков стандартных протоколов и особенностей применения я решил реализовать свой протокол обмена.

Название PROTEQ досталось по наследству от предыдущего проекта для которого была высказана похожая идея восстановления данных после сбоя.

Итак исходные данные:
• аппаратура — модуль FMC106P, две ПЛИС Virtex 6 LX130T-2 и LX240T-1
• скорость передачи — 5 Гбит/с
• число линий — 8
• источник данных — АЦП, нет возможности приостановить передачу
• двунаправленный обмен данными
• требуется реализовать быстрое восстановление данных после сбоя
• кодировка — 64/67

Основная идея — реализовать постоянную повторную передачу данных до прихода подтверждения о принятом пакете. В этом состоит главная особенность протокола и именно это позволяет ускорить восстановление данных.

Рассмотрим реализацию одной линии:

PROTEQ — протокол обмена по мультигигабитным линиям для ПЛИС Xilinx - 1

На передатчике реализованы четыре буфера. Источник данных обнаруживает свободный буфер и записывает в него данные. Запись идёт в строгом порядке 0,1,2,3; Узел передачи также по кругу опрашивает флаг заполнения буфера и начинает передавать данные из заполненных буферов. Когда от приёмника приходит подтверждение приёма, то буфер помечается свободным. При большом потоке данных подтверждение успевает прийти до передачи всех четырёх буферов и передача идёт на максимальной скорости. При маленьком потоке узел передачи успеет отправить повторный пакет, но он будет отброшен на приёмной стороне.

Это решение сильно ускоряет восстановление данных. В традиционной схеме приёмник должен принять пакет, провести его анализ и сформировать запрос на повторную передачу. Это долго.

На приёмнике также реализованы четыре буфера. В заголовке пакета находится номер буфера и пакет сразу отправляется в буфер назначения. В таком решении есть очень большая опасность. Если будет испорчен номер буфера, то возможно будут испорчены два пакета — и текущий пакет и уже принятый пакет. Чтобы этого избежать в пакете используются две контрольные суммы — одна идёт сразу за заголовком, вторая — в конце пакета.

Для скорости 5 Гбит/с на узле GTX используется шина 32 разряда с частотой 156.25 МГц. Обмен между FIFO и внутренними буферами идёт на частоте 250 МГц. Это обеспечивает запас скорости для восстановления. Например если произошла ошибка при передачи в буфер 1, а передача в буфер 2 и 3 произошла без ошибки, то запись в выходное FIFO будет задержана до повторного прихода пакета в буфер 1. Но после в FIFO будут сразу записаны пакеты из буферов 2 и 3.

Протокол использует фиксированную длину пакета — 256 слов по 32 бита.
Существуют два типа пакета:

  • Пакет данных
  • Служебный пакет

Формат пакета данных:

  • CMD1
  • CRC1
  • DATA — 256 слов
  • CMD2
  • CRC2

Формат служебного пакета:

  • CMD1
  • CRC1
  • CMD2
  • CRC2

Накладные расходы достаточно низкие — только четыре 32-х разрядных слова. Полная длина пакета с данными — 260 слов.

Служебные пакеты передаются в случае отсутствия данных.

Для увеличения скорости реализована передача по нескольким линиям. Есть узел который работает с числом линий от 1 до 8. Для каждой линии ширина шины данных 32 бита. При использовании 8 линий общая ширина шины данных — 256 бит.

Расчётная скорость обмена для восьми линий на частоте 5 ГГц:
5000000000 * 64/67 * 256/260 * 8 / 8 /1024 / 1024 = 4484,7 Мбайт/с

Именно эта скорость достигнута в результате экспериментов. Ошибки периодически возникают, но они исправляются протоколом. Быстрое восстановление позволяет использовать небольшой размер FIFO для подключения АЦП.

Интересно сравнить эффективность PROTEQ с PCI Express v2.0 реализованном на этой же ПЛИС. Оба протокола используют восемь линков на скорости 5 Гбит/с. Максимальная скорость обмена составляет:
5000000000/8/1024/1024*8 = 4768 Мбайт/с
Эффективность PROTEQ:
4484/4768 = 0.94; т.е. 94% от максимальной скорости линии.

PCI Express обеспечивает скорость 3200 Мбайт/с
3200/4768 = 0.67; т.е. 67% от максимальной скорости линии.

Конечно главная причина низкой эффективности PCI Express v2.0 это применение кодировки 8/10;

Интересно также сравнить занимаемые ресурсы ПЛИС для реализации протоколов. На рисунке представлены области которые занимает PCI Express и PROTEQ со сравнимой функциональностью. При этом следует учитывать, что PCI Express использует ещё HARD блок.

PROTEQ — протокол обмена по мультигигабитным линиям для ПЛИС Xilinx - 2

Основным компонентом является prq_transceiver_gtx_m1

prq_transceiver_gtx_m1

component prq_transceiver_gtx_m1 is
	generic(
		is_simulation	: in integer:=0;	-- 1 - режим моделирования 
		LINES			: in integer;		-- число MGT линий 
		RECLK_EN		: in integer:=0;	-- 1 - приёмник работает на частоте recclk
											-- 0 - приёмник работает на частоте txoutclk
		USE_REFCLK_IN	: boolean:=FALSE;	-- FALSE - используются вход MGTCLK
											-- TRUE - используется вход REFCLK_IN
		is_tx_dpram_use	: in integer:=1; 	-- 1 - использование памяти для передачи
		is_rx_dpram_use	: in integer:=1		-- 1 - использование памяти для приёма
	);	
	port(
	
		clk				: in std_logic;	-- тактовая частота приложения  - 266 МГц
		clk_tx_out		: out std_logic;	-- тактовая частота передатчика - 156.25 МГц
		clk_rx_out		: out std_logic;	-- тактовая частота приёмника   - 156.25 МГц 
		
		--- SYNC ---
		reset			: in  std_logic;	-- 0 - сброс
		sync_done		: out std_logic;	-- 1 - завершена инициализация
		
		tx_enable		: in std_logic;		-- 1 - разрешение передачи данных 
		rx_enable		: in std_logic;		-- 1 - разрешение приёма данных
		rst_buf			: in std_logic:='0';	-- 1 - сброс буферов данных 
		transmitter_dis	: in std_logic:='0';	-- 1 - запрет работы передатчика
		
		---- DATA ----
		tx_ready		: out std_logic;		-- 1 - готовность к передаче одного буфера
		rx_ready		: out std_logic;		-- 1 - один буфер готов к чтению 
		
		tx_data			: in std_logic_vector( 31+(LINES-1)*32 downto 0 );	-- данные для передачи
		tx_data_we		: in std_logic;			-- 1 - запись данных
		tx_data_title	: in std_logic_vector( 3 downto 0 );	-- заголовок пакета
		tx_data_eof		: in std_logic;			-- 1 - конец фрейма 
		tx_user_flag	: in std_logic_vector( 7+(LINES-1)*8 downto 0 );	-- локальные флаги
		
		tx_inject_error	: in std_logic_vector( LINES-1 downto 0 ):=(others=>'0');	-- 1 - добавить ошибку в передаваемый буфер 
		
		rx_data			: out std_logic_vector( 31+(LINES-1)*32 downto 0 );	-- принятые данные
		rx_data_rd		: in std_logic;			-- 1 - чтение данных	 
		rx_data_title	: out std_logic_vector( 3 downto 0 );	-- заголовок принятого пакета
		rx_data_eof		: in std_logic;			-- 1 - конец фрейма 
		rx_user_flag	: out std_logic_vector( 7+(LINES-1)*8 downto 0 ); 	-- удалённые флаги
		rx_user_flag_we	: out std_logic_vector( (LINES-1) downto 0 );		-- 1 - обновление удалённых флагов
	   	
		rx_crc_error_adr: in std_logic_vector( 2 downto 0 ):="000";			-- адрес счётчика ошибок
		rx_crc_error	: out std_logic_vector( 7 downto 0 );				-- счётчик ошибок для выбранного канала
		
		--- MGT ---
		rxp				: in std_logic_vector( LINES-1 downto 0 );
		rxn				: in std_logic_vector( LINES-1 downto 0 );
		
		txp				: out std_logic_vector( LINES-1 downto 0 );
		txn				: out std_logic_vector( LINES-1 downto 0 );
		
		refclk_in		: in std_logic:='0';
		mgtclk_p		: in std_logic;
		mgtclk_n		: in std_logic
		 
	);
	
end component;

Группа сигналов tx_* организуют канал передачи.
Алгоритм передачи пакта:

  • Ждать появления tx_ready=1
  • Записать пакет по шине tx_data с помощью строба tx_data_we=1
  • Сформировать tx_data_eof=1 на один такт — пакет будет отправлен

Записать можно от 1 до 256 слов, но в любом случае будет отправлен пакет длиной 256 слов.

Аналогично, группа сигналов rx_* организует канал приёма.
Алгоритм приёма пакета:

  • Ждать появления rx_ready=1
  • Прочитать пакет по шине rx_data с помощью строба rx_data_rd
  • Сформировать rx_data_eof=1

Также как и при записи допускается прочитать не весь пакет.
Вместе с пакетом передаётся четырёхбитный заголовок. При передаче вместе с пакетом будет передано значение на входе tx_data_title. При приёме вместе с готовностью tx_ready=1 появится значение на rx_data_title. Это позволяет иметь несколько источников и приёмников данных при одном канале передаче.

Дополнительно в канале происходит передача флагов. Данные на входе передатчика tx_user_flag передаются на выход приёмника rx_user_flag. Это может использоваться для передачи флагов состояния FIFO.

Компонент prq_transceiver_gtx_m1 может передавать данные по нескольким линиям. Количество линий настраивается через параметр LINES; От количества линий зависит ширина шин tx_data, rx_data.

Вход tx_inject_error позволяет добавить ошибку в процессе передачи. Это позволяет проверить механизм восстановления данных.

Следующим уровнем является компонент prq_connect_m1

prq_connect_m1

component prq_connect_m1 is
	generic(
		is_simulation	: in integer:=0;	-- 1 - режим моделирования 
		RECLK_EN		: in integer:=0;	-- 1 - приёмник работает на частоте recclk
											-- 0 - приёмник работает на частоте txoutclk
		is_tx_dpram_use	: in integer:=1; 	-- 1 - использование памяти для передачи
		is_rx_dpram_use	: in integer:=1;	-- 1 - использование памяти для приёма
		
		FIFO0_WITH		: in integer:=1;	-- ширина FIFO0: 1 - нет, 32,64,128,256 бит
		FIFO0_PAF		: in integer:=16;	-- уровень срабатывания флага PAF 
		FIFO0_DEPTH		: in integer:=1024;	-- глубина FIFO0
		
		FIFO1_WITH		: in integer:=1;	-- ширина FIFO1: 1 - нет, 32,64,128,256 бит
		FIFO1_PAF		: in integer:=16;	-- уровень срабатывания флага PAF 
		FIFO1_DEPTH		: in integer:=1024	-- глубина FIFO0
		
	);	
	port(	 							 
	
		--- MGT ---
		rxp				: in std_logic_vector( 7 downto 0 );
		rxn				: in std_logic_vector( 7 downto 0 );
		
		txp				: out std_logic_vector( 7 downto 0 );
		txn				: out std_logic_vector( 7 downto 0 );
		
		mgtclk_p		: in std_logic;
		mgtclk_n		: in std_logic;
	
		---- Tranceiver ----
		clk				: in std_logic;	-- тактовая частота приложения  - 266 МГц
		clk_tx_out		: out std_logic;	-- тактовая частота передатчика - 156.25 МГц
		clk_rx_out		: out std_logic;	-- тактовая частота приёмника   - 156.25 МГц 
		
		--- SYNC ---
		reset			: in  std_logic;	-- 0 - сброс
		sync_done		: out std_logic;	-- 1 - завершена инициализация
		
		tx_enable		: in std_logic;		-- 1 - разрешение передачи данных 
		rx_enable		: in std_logic;		-- 1 - разрешение приёма данных
		
		---- FIFO0 ----
		fi0_clk			: in std_logic:='0';		-- тактовая частота записи в FIFO
		fi0_data		: in std_logic_vector( FIFO0_WITH-1 downto 0 ):=(others=>'0');	-- шина данных FIFO 
		fi0_data_en		: in std_logic:='0';		-- 1 - запись в FIFO 
		fi0_paf			: out std_logic;			-- 1 - FIFO почти полное
		fi0_id			: in std_logic_vector( 3 downto 0 ):=(others=>'0');	-- идентификатор FIFO
		fi0_rstp		: in std_logic:='0';		-- 1 - сброс FIFO
		fi0_enable		: in std_logic:='0';		-- 1 - разрешение передачи
		fi0_prs_en		: in std_logic:='0';		-- 1 - включение генератора псевдослучайной последовательности
		fi0_ovr			: out std_logic;			-- 1 - переполнение FIFO 
		fi0_rd_full_speed: in std_logic:='0';		-- 1 - формирование выходного потока на полной скорости 

		---- FIFO1 ----
		fi1_clk			: in std_logic:='0';	-- тактовая частота записи в FIFO
		fi1_data		: in std_logic_vector( FIFO1_WITH-1 downto 0 ):=(others=>'0');	-- шина данных FIFO 
		fi1_data_en		: in std_logic:='0';		-- 1 - запись в FIFO 
		fi1_paf			: out std_logic;			-- 1 - FIFO почти полное
		fi1_id			: in std_logic_vector( 3 downto 0 ):=(others=>'0');	-- идентификатор FIFO
		fi1_rstp		: in std_logic:='0';		-- 1 - сброс FIFO
		fi1_enable		: in std_logic:='0';		-- 1 - разрешение передачи
		fi1_prs_en		: in std_logic:='0';		-- 1 - включение генератора псевдослучайной последовательности
		fi1_ovr			: out std_logic;			-- 1 - переполнение FIFO 
		fi1_rd_full_speed: in std_logic:='0';		-- 1 - формирование выходного потока на полной скорости 
		
		tx_inject_error	: in std_logic_vector( 7 downto 0 ):=(others=>'0');	-- 1 - добавить ошибку в передаваемый буфер 
		
		tx_user_flag	: in std_logic_vector( 63 downto 0 ):=(others=>'0');	-- локальные флаги 
		
		---- Приём данных ----
		fifo_data		: out std_logic_vector( 255 downto 0 ); -- выход FIFO 
		fifo_we			: out std_logic;		-- 1 - запись данных 
		fifo_id			: out std_logic_vector( 3 downto 0 );	-- идентификатор FIFO 
		fifo_rdy		: in std_logic_vector( 15 downto 0 );	-- готовность к приёму данных 
		
		rx_crc_error_adr: in std_logic_vector( 2 downto 0 ):="000";			-- адрес счётчика ошибок
		rx_crc_error	: out std_logic_vector( 7 downto 0 );				-- счётчик ошибок для выбранного канала
		
		rx_user_flag	: out std_logic_vector( 63 downto 0 );			-- удалённые флаги 
		rx_user_flag_we	: out std_logic_vector( 7 downto 0 )	-- 1 - обновление удалённых флагов

	);
end component;

Он уже реализует механизм передачи потока данных через FIFO по восьми линиям.
Структурная схема:
PROTEQ — протокол обмена по мультигигабитным линиям для ПЛИС Xilinx - 3

В его состав входят два FIFO, prq_transceiver, автоматы приёма и передачи.
Ширина входной шины каждого FIFO настраивается через параметры FIFOx_WITH, также настраивается количество слов и уровень срабатывания флага почти полного FIFO. Запись в каждое FIFO производится на своей тактовой частоте. Каждое FIFO сопровождается своим идентификатором fi0_id, fi1_id; Это позволяет разделить поток данных при приёме. После FIFO установлен генератор псевдослучайной последовательности. На схеме он обозначен как PSD. Генератор реализует три режима:

  1. Пропустить поток данных без изменений
  2. Подставить тестовую последовательность при сохранении скорости потока данных
  3. Подставить тестовую последовательность и передавать данные на полной скорости

На этом генераторе основано тестирование в составе реальных проектов. Этот генератор не занимает много места в ПЛИС, он есть во всех проектах и позволяет в любой момент провести проверку канала обмена.

Компоненты prq_connect_m1 и prq_transceiver_gtx_m1 являются базовыми. Они были разработаны для ПЛИС Virtex 6; Впоследствии разработаны компоненты prq_transceiver_gtx_m4 и prq_transceiver_gtx_m6;

  • prq_transceiver_gtx_m4 — буфер 0 выделен для передачи командн
  • prq_transceiver_gtx_m6 — для ПЛИС Kintex 7

Проект полностью моделируется, реализован последовательный запуск тестов через tcl файл.
И здесь я хочу выразить благодарность Игорю Казинову. Он внёс большой вклад в организацию моделирования в этом проекте.

Общий результат моделирования выглядит так:

Файл global_tc_summary.log

Global PROTEQ TC log:
tc_00_0 PASSED
tc_00_1 PASSED
tc_00_2 PASSED
tc_00_3 PASSED
tc_02_0 PASSED
tc_02_1 PASSED
tc_02_2 PASSED
tc_02_3 PASSED
tc_02_4 PASSED
tc_02_5 PASSED
tc_03_0 PASSED
tc_05_0 PASSED
tc_05_1 PASSED

Каждая строчка в файле это результат выполнения одного теста. tc — это Test Case — тестовый случай. Для примера приведу компонент tc_00_1 — проверка передачи пакета и внесение одиночной ошибки в процесс передачи.

tc_00_1

-------------------------------------------------------------------------------
--
-- Title       : tc_00_1
-- Author      : Dmitry Smekhov
-- Company     : Instrumental Systems
-- E-mail      : dsmv@insys.ru
--
-- Version     : 1.0
--
-------------------------------------------------------------------------------
--
-- Description :  Проверка на тесте prq_transceiver_tb
--
--				  Приём 32-х пакетов.
--				  Одиночная ошибка
--
-------------------------------------------------------------------------------
-- 
-- Rev0.1 - debug test #1
--
-------------------------------------------------------------------------------


library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

library std;
use std.textio.all;

library work;
use work.prq_transceiver_tb_pkg.all;    -- to bind testbench and its procedures
use work.utils_pkg.all; 			-- for "stop_simulation"
use work.pck_fio.all;	  

entity tc_00_1 is
end tc_00_1;

architecture bhv of tc_00_1 is

signal	tx_err			: std_logic;
signal	rx_err			: std_logic;

begin
----------------------------------------------------------------------------------
-- Instantiate TB
TB : prq_transceiver_tb 
	generic map(
		max_pkg			=> 32,			-- число пакетов, которое нужно принять
		max_time		=> 100 us,		-- максимальное время теста 
		tx_pause		=> 1 ns,		-- минимальное время между пакетами при передаче
		rx_pause		=> 1 ns			-- минимальное время между пакетами при приёме 
	)
	port map(
		tx_err			=> tx_err,		-- сигнал ошибки m1->m2
		rx_err			=> rx_err		-- сигнал ошибки m2->m1
	
	);
----------------------------------------------------------------------------------
--
-- Define ERR at TIME#
--
tx_err <= '0', '1' after 27 us, '0' after 27.001 us;
rx_err <= '0';

----------------------------------------------------------------------------------
end bhv;

Компонент очень простой. Он вызывает prq_transceiver_tb (а вот он как раз сложный), настраивает параметры и формирует сигнал tx_err который вносит ошибку на линию передачи.
Остальные компоненты от tc_00_1 до tc_00_5 примерно такие же, они отличаются настроенными параметрами, что позволяет проверить передачу данных при разных условиях.
Компонент prq_transceiver_tb гораздо сложнее. Собственно он формирует тестовую последовательность, передаёт поток между двумя prq_transceiver_gtx_m1 и проверяет принятый поток данных. При необходимости — вносит ошибки в процесс передачи.
Сам компонент здесь:

prq_transceiver_tb

library ieee;
use ieee.std_logic_1164.all;	 
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;


use work.prq_transceiver_gtx_m1_pkg.all;

library std;
use std.textio.all;
use work.pck_fio.all;	  
use work.utils_pkg.all;

entity prq_transceiver_tb is
	generic(
		max_pkg			: integer:=0;			-- число пакетов, которое нужно принять
		max_time		: time:=100 us;			-- максимальное время теста 
		tx_pause		: time:=100 ns;			-- минимальное время между пакетами при передаче
		rx_pause		: time:=100 ns			-- минимальное время между пакетами при приёме 
	);
	port(
		tx_err			: in std_logic:='0';	-- сигнал ошибки m1->m2
		rx_err			: in std_logic:='0'		-- сигнал ошибки m2->m1
	
	);
end prq_transceiver_tb;

architecture TB_ARCHITECTURE of prq_transceiver_tb is

signal clk : std_logic:='0';
signal reset : STD_LOGIC;
signal tx_data : STD_LOGIC_VECTOR(31 downto 0);
signal tx_data_we : STD_LOGIC;
signal tx_data_title : STD_LOGIC_VECTOR(3 downto 0);
signal rx_data_rd : STD_LOGIC;
signal rxp : STD_LOGIC_VECTOR(0 downto 0);
signal rxn : STD_LOGIC_VECTOR(0 downto 0);
signal mgtclk_p : STD_LOGIC;
signal mgtclk_n : STD_LOGIC;
signal	mgtclk		: std_logic:='0';
-- Observed signals - signals mapped to the output ports of tested entity
signal clk_tx_out : STD_LOGIC;
signal clk_rx_out : STD_LOGIC;
signal sync_done : STD_LOGIC;
signal tx_enable : STD_LOGIC;
signal rx_enable : STD_LOGIC;
signal tx_ready : STD_LOGIC;
signal rx_ready : STD_LOGIC;
signal rx_data : STD_LOGIC_VECTOR(31 downto 0);
signal rx_data_title : STD_LOGIC_VECTOR(3 downto 0);
signal txp : STD_LOGIC_VECTOR(0 downto 0);
signal txn : STD_LOGIC_VECTOR(0 downto 0);

signal m2_txp : STD_LOGIC_VECTOR(0 downto 0);
signal m2_txn : STD_LOGIC_VECTOR(0 downto 0);
signal m2_rxp : STD_LOGIC_VECTOR(0 downto 0);
signal m2_rxn : STD_LOGIC_VECTOR(0 downto 0);

signal	tx_err_i	: std_logic_vector( 0 downto 0 );
signal	rx_err_i	: std_logic_vector( 0 downto 0 );

signal	tx_data_eof	: std_logic;
signal	rx_data_eof	: std_logic;

signal m2_clk : std_logic:='0';
signal m2_tx_data : STD_LOGIC_VECTOR(31 downto 0);
signal m2_tx_data_we : STD_LOGIC;
signal m2_tx_data_title : STD_LOGIC_VECTOR(3 downto 0);
signal m2_rx_data_rd : STD_LOGIC;
signal	m2_tx_data_eof	: std_logic;
signal	m2_rx_data_eof	: std_logic;

-- Observed signals - signals mapped to the output ports of tested entity
signal m2_clk_tx_out : STD_LOGIC;
signal m2_clk_rx_out : STD_LOGIC;
signal m2_sync_done : STD_LOGIC;
signal m2_tx_enable : STD_LOGIC;
signal m2_rx_enable : STD_LOGIC;
signal m2_tx_ready : STD_LOGIC;
signal m2_rx_ready : STD_LOGIC;
signal m2_rx_data : STD_LOGIC_VECTOR(31 downto 0);
signal m2_rx_data_title : STD_LOGIC_VECTOR(3 downto 0);		

signal	tx_user_flag	: std_logic_vector( 7 downto 0 ):=x"A0";
signal	rx_user_flag	: std_logic_vector( 7 downto 0 );
signal	rx_user_flag_we : std_logic_vector( 0 downto 0 );

signal	m2_tx_user_flag	: std_logic_vector( 7 downto 0 ):=x"C0";
signal	m2_rx_user_flag	: std_logic_vector( 7 downto 0 );
signal	m2_rx_user_flag_we : std_logic_vector( 0 downto 0 );

signal	m2_reset			: std_logic;

begin
	
clk <= not clk after 1.9 ns;
mgtclk <= not mgtclk after 3.2 ns;

m2_clk <= not m2_clk after 1.8 ns;

mgtclk_p <= mgtclk;
mgtclk_n <= not mgtclk;
	
	-- Unit Under Test port map
UUT : prq_transceiver_gtx_m1
		generic map (
			is_simulation		=> 1, 	-- 1 - режим моделирования 
			LINES 				=> 1,
			is_tx_dpram_use		=> 1, 	-- 1 - использование памяти для передачи
			is_rx_dpram_use		=> 0	-- 1 - использование памяти для приёма
			
		)

		port map (

		clk				=> clk,				         
		clk_tx_out		=> clk_tx_out,		         
		clk_rx_out		=> clk_rx_out,		         
						                         
		--- SYNC ---	--- SYNC ---             
		reset			=> reset,			         
		sync_done		=> sync_done,		         
						                         
		tx_enable		=> tx_enable,		         
		rx_enable		=> rx_enable,		         
						                         
		---- DATA ----	---- DATA ----           
		tx_ready		=> tx_ready,		         
		rx_ready		=> rx_ready,	         
						                         
		tx_data			=> tx_data,			         
		tx_data_we		=> tx_data_we,		         
		tx_data_title	=> tx_data_title,	
		tx_data_eof		=> tx_data_eof,	 
		tx_user_flag	=> tx_user_flag,
						                         
		rx_data			=> rx_data,			         
		rx_data_rd		=> rx_data_rd,		         
		rx_data_title	=> rx_data_title,	         
		rx_data_eof		=> rx_data_eof,	
		rx_user_flag	=> rx_user_flag,
		rx_user_flag_we	=> rx_user_flag_we,
						                         
		--- MGT ---		--- MGT ---              
		rxp				=> rxp,				         
		rxn				=> rxn,				         
						                         
		txp				=> txp,				         
		txn				=> txn,				         
						                         
		mgtclk_p		=> mgtclk_p,		         
		mgtclk_n		=> mgtclk_n		         
		
		); 
		
UUT2 : prq_transceiver_gtx_m1
		generic map (
			is_simulation		=> 1, 	-- 1 - режим моделирования 
			LINES 				=> 1,
			is_tx_dpram_use		=> 0, 	-- 1 - использование памяти для передачи
			is_rx_dpram_use		=> 1	-- 1 - использование памяти для приёма
		)

		port map (

		clk				=> m2_clk,				         
		clk_tx_out		=> m2_clk_tx_out,		         
		clk_rx_out		=> m2_clk_rx_out,		         
						                         
		--- SYNC ---	--- SYNC ---             
		reset			=> m2_reset,			         
		sync_done		=> m2_sync_done,		         
						                         
		tx_enable		=> m2_tx_enable,		         
		rx_enable		=> m2_rx_enable,		         
						                         
		---- DATA ----	---- DATA ----           
		tx_ready		=> m2_tx_ready,		         
		rx_ready		=> m2_rx_ready,	         
						                         
		tx_data			=> m2_tx_data,			         
		tx_data_we		=> m2_tx_data_we,		         
		tx_data_title	=> m2_tx_data_title,	         
		tx_data_eof		=> m2_tx_data_eof,
		tx_user_flag	=> m2_tx_user_flag,
		
						                         
		rx_data			=> m2_rx_data,			         
		rx_data_rd		=> m2_rx_data_rd,		         
		rx_data_title	=> m2_rx_data_title,	         
		rx_data_eof		=> m2_rx_data_eof,	       
		rx_user_flag	=> m2_rx_user_flag,
		rx_user_flag_we	=> m2_rx_user_flag_we,
		
						                         
		--- MGT ---		--- MGT ---              
		rxp				=> m2_rxp,				         
		rxn				=> m2_rxn,				         
						                         
		txp				=> m2_txp,				         
		txn				=> m2_txn,				         
						                         
		mgtclk_p		=> mgtclk_p,		         
		mgtclk_n		=> mgtclk_n		         
		
		);						

rx_err_i <= (others=>rx_err);		
tx_err_i <= (others=>tx_err);

rxp <= m2_txp or rx_err_i;
rxn <= m2_txn or rx_err_i;

m2_rxp <= txp or tx_err_i;
m2_rxn <= txn or tx_err_i;
		
reset <= '0', '1' after 1 us;		
m2_reset <= '0', '1' after 2 us;
		

tx_enable <= '0', '1' after 22 us;
rx_enable <= '0', '1' after 22 us;

m2_tx_enable <= '0', '1' after 24 us;
m2_rx_enable <= '0', '1' after 24 us;

m2_tx_data_we <= '0';		  
m2_tx_data <= (others=>'0');
m2_tx_data_title <= "0000";
m2_tx_data_eof <= '0';

pr_tx_data: process begin	  
	
	tx_data_we <= '0';
	tx_data_eof <= '0';
	tx_data <= x"AB000000";
	
	tx_data_title <= "0010";
	
	loop
		
		wait until rising_edge( clk ) and tx_ready='1';
		
	   tx_data_we <= '1' after 1 ns;
		for ii in 0 to 255 loop
		   wait until rising_edge( clk );
		   tx_data <= tx_data + 1 after 1 ns;
		end loop;
		tx_data_we <= '0' after 1 ns;	 
		wait until rising_edge( clk );
		tx_data_eof <= '1' after 1 ns;
		
		wait until rising_edge( clk );
		tx_data_eof <= '0' after 1 ns;
		
		wait until rising_edge( clk );
		
		wait for tx_pause;
		
	end loop;
	
end process;	
		   

pr_rx_data: process 

variable	expect_data 		: std_logic_vector( 31 downto 0 ):= x"AB000000";
variable	error_cnt			: integer:=0;
variable	pkg_cnt				: integer:=0;
variable	pkg_ok				: integer:=0;
variable	pkg_error			: integer:=0;
variable	index				: integer;
variable	flag_error			: integer;
variable	tm_start			: time;
variable	tm_stop				: time;
variable	byte_send			: real;
variable	tm					: real;
variable	velocity			: real;
variable	tm_pkg				: time:=0 ns;
variable	tm_pkg_delta		: time:=0 ns;
	

variable L 	: line;

begin
	m2_rx_data_rd <= '0';  
	m2_rx_data_eof <= '0'; 

	--fprint( output, L, "Чтение данныхn" );
	
	loop
		loop
			wait until rising_edge( m2_clk );
			if( m2_rx_ready='1'  or now>max_time ) then
				exit;
			end if;
			
		end loop;

		if( now>max_time ) then
			 exit;
		end if;
		
		
		if( pkg_cnt=0 ) then
			tm_start:=now;	 
			tm_pkg:=now;
		end if;
		tm_pkg_delta := now - tm_pkg;
		fprint( output, L, "PKG=%3d  %10r ns %10r nsn", fo(pkg_cnt), fo(now), fo(tm_pkg_delta) );
		tm_pkg:=now;
		
		index:=0;	 
		flag_error:=0;
		m2_rx_data_rd <= '1' after 1 ns;
		wait until rising_edge( m2_clk );
		loop
			wait until rising_edge( m2_clk );
			if( expect_data /= m2_rx_data ) then
				if( error_cnt<32 ) then
				
				fprint( output, L, "ERROR: pkg=%d  index=%d expect=%r read=%r n",
					fo(pkg_cnt), fo(index), fo(expect_data), fo(m2_rx_data) );
			
				end if;
				error_cnt:=error_cnt+1;
				flag_error:=1;
			end if;
			index:=index+1;
			expect_data:=expect_data+1;
			
			if( index=255 ) then
				m2_rx_data_rd <= '0' after 1 ns;			  	
			end if;
			
			if( index=256 ) then
				exit;
			end if;
		
		end loop;			 
		
		if( flag_error=0 ) then
			pkg_ok:=pkg_ok+1;
		else
			pkg_error:=pkg_error+1;
		end if;
		
		
		wait until rising_edge( m2_clk ); 
		m2_rx_data_eof <= '1' after 1 ns; 
		
		wait until rising_edge( m2_clk );
		m2_rx_data_eof <= '0' after 1 ns; 
		
		wait until rising_edge( m2_clk );
		--wait until rising_edge( m2_clk );
		
		wait for rx_pause;
		
		pkg_cnt:=pkg_cnt+1;
		
		if( pkg_cnt=max_pkg ) then
			exit;
		end if;
		
	end loop;	  
	
	tm_stop:=now;
	
	fprint( output, L, "Завершён приём данных: %r nsn", fo(now) );
	fprint( output, L, " Принято пакетов:    %dn", fo( pkg_cnt ) );
	fprint( output, L, " Правильных:         %dn", fo( pkg_ok ) );
	fprint( output, L, " Ошибочных:          %dn", fo( pkg_error ) );
	fprint( output, L, " Общее число ошибок: %dn", fo( error_cnt ) );
	
	byte_send:=real(pkg_cnt)*256.0*4.0;
	tm := real(tm_stop/ 1 ns )-real(tm_start/ 1 ns);
	velocity := byte_send*1000000000.0/(tm*1024.0*1024.0);
	
	fprint( output, L, " Скорость передачи: %r МБайт/сn", fo( integer(velocity) ) );
	
	flag_error:=0;
	if( max_pkg>0 and pkg_cnt/=max_pkg ) then
		flag_error:=1;
	end if;
	
	if( flag_error=0 and pkg_cnt>0 and error_cnt=0 ) then
		--fprint( output, L, "nnТест завершён успешноnn" );
		fprint( output, L, "nnTEST finished successfullynn" );
	else
		--fprint( output, L, "nnТест завершён с ошибкамиnn" );
		fprint( output, L, "nnTEST finished with ERRnn" );
	end if;
	
	utils_stop_simulation;
	
	wait;
	
end process;	
	
	
pr_tx_user_flag: process begin
	tx_user_flag <= tx_user_flag + 1;
	wait for 1 us;
end process;	

pr_m2_tx_user_flag: process begin
	m2_tx_user_flag <= m2_tx_user_flag + 1;
	wait for 1.5 us;
end process;	

end TB_ARCHITECTURE;

Результат работы теста:

tc_00_1.log

# KERNEL: PKG=  0       25068 ns          0 ns
# KERNEL: PKG=  1       26814 ns       1746 ns
# KERNEL: PKG=  2       35526 ns       8712 ns
# KERNEL: PKG=  3       36480 ns        954 ns
# KERNEL: PKG=  4       37434 ns        954 ns
# KERNEL: PKG=  5       38388 ns        954 ns
# KERNEL: PKG=  6       39342 ns        954 ns
# KERNEL: PKG=  7       40858 ns       1515 ns
# KERNEL: PKG=  8       42597 ns       1738 ns
# KERNEL: PKG=  9       44339 ns       1742 ns
# KERNEL: PKG= 10       46081 ns       1742 ns
# KERNEL: PKG= 11       47827 ns       1746 ns
# KERNEL: PKG= 12       49566 ns       1738 ns
# KERNEL: PKG= 13       51309 ns       1742 ns
# KERNEL: PKG= 14       53051 ns       1742 ns
# KERNEL: PKG= 15       54790 ns       1738 ns
# KERNEL: PKG= 16       56536 ns       1746 ns
# KERNEL: PKG= 17       58278 ns       1742 ns
# KERNEL: PKG= 18       60021 ns       1742 ns
# KERNEL: PKG= 19       61759 ns       1738 ns
# KERNEL: PKG= 20       63502 ns       1742 ns
# KERNEL: PKG= 21       65248 ns       1746 ns
# KERNEL: PKG= 22       66990 ns       1742 ns
# KERNEL: PKG= 23       68729 ns       1738 ns
# KERNEL: PKG= 24       70471 ns       1742 ns
# KERNEL: PKG= 25       72210 ns       1738 ns
# KERNEL: PKG= 26       73953 ns       1742 ns
# KERNEL: PKG= 27       75699 ns       1746 ns
# KERNEL: PKG= 28       77441 ns       1742 ns
# KERNEL: PKG= 29       79180 ns       1738 ns
# KERNEL: PKG= 30       80922 ns       1742 ns
# KERNEL: PKG= 31       82661 ns       1738 ns
# KERNEL: Завершён приём данных:       83598 ns
# KERNEL:  Принято пакетов:    32
# KERNEL:  Правильных:         32
# KERNEL:  Ошибочных:          0
# KERNEL:  Общее число ошибок: 0
# KERNEL:  Скорость передачи:         534 МБайт/с
# KERNEL: 
# KERNEL: 
# KERNEL: TEST finished successfully

Обратите внимание на правую колонку. Это время между принятыми пакетами. Обычное время ~1740 ns, однако пакет 2 принят с задержкой в 8712 ns. Это как раз действовал сигнал ошибки. И ещё обратите внимание что следующие пакеты приняты с задержкой 954 ns. Это из-за того, что неправильно был принят только один пакет, а остальные дожидались своей очереди в буферной памяти.

Хочу отметить, что автоматизированный запуск тестов мне очень помог при отладке протокола. Это позволило держать все изменения под контролем. И небольшие изменения в исходном коде не смогли привести к обрушению проекта.

Проект PROTEQ доступен как OpenSource. Ссылка на сайт — в моём профиле.

Автор: dsmv2014

Источник


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


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