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

Основна ідея — реалізувати постійну повторну передачу даних до приходу підтвердження про прийняте пакеті. У цьому полягає головна особливість протоколу і саме це дозволяє прискорити відновлення даних.

Розглянемо реалізацію однієї лінії:



На передавачі реалізовані чотири буфера. Джерело даних виявляє вільний буфер і записує в нього дані. Запис іде в строгому порядку 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 блок.



Основним компонентом є prq_transceiver_gtx_m1
prq_transceiver_gtx_m1
component is prq_transceiver_gtx_m1
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 is prq_connect_m1
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 з восьми ліній.
Структурна схема:


До його складу входять два 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';
сигнал 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 entity tested
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 entity tested
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, "УП=%3d %10r ns %10r ns\n", 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 ns\n", fo(now) );
fprint( output, L, " Прийнято пакунків: %d\n", fo( pkg_cnt ) );
fprint( output, L, " Правильних: %d\n", fo( pkg_ok ) );
fprint( output, L, " Помилкових: %d\n", fo( pkg_error ) );
fprint( output, L, " Загальне число помилок: %d\n", 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, "\n\пТест успішно завершено\n\n" );
fprint( output, L, "\n\nTEST finished successfully\n\n" );
else
--fprint( output, L, "\n\пТест завершено з помилками\n\n" );
fprint( output, L, "\n\nTEST out with ERR\n\n" );
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 ns 1746
# 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 ns 1515
# 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 ns 1746
# 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 ns 1746
# 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 ns 1746
# 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 ns 1746
# 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. Посилання на сайт — в моєму профілі.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.