...

суббота, 11 февраля 2017 г.

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 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 по восьми линиям.
Структурная схема:

В его состав входят два 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 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\n" );
                fprint( output, L, "\n\nTEST finished successfully\n\n" );
        else
                --fprint( output, L, "\n\nТест завершён с ошибками\n\n" );
                fprint( output, L, "\n\nTEST finished 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       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. Ссылка на сайт — в моём профиле.

Комментарии (0)

    Let's block ads! (Why?)

    Комментариев нет:

    Отправить комментарий