From 9ae96ce8b1bc7ad1c02d6f15128ac336037203c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?=
 <fandabohacek@gmail.com>
Date: Sat, 1 Apr 2023 20:52:00 +0200
Subject: [PATCH] feat: add multipoint link support

---
 src/jesd204b_multipoint_link_rx.vhd       | 117 +++++++++++++
 src/jesd204b_pkg.vhd                      |   5 +
 src/transport/transport_pkg.vhd           |   1 +
 testbench/jesd204b_multipoint_data_tb.vhd | 191 ++++++++++++++++++++++
 4 files changed, 314 insertions(+)
 create mode 100644 src/jesd204b_multipoint_link_rx.vhd
 create mode 100644 testbench/jesd204b_multipoint_data_tb.vhd

diff --git a/src/jesd204b_multipoint_link_rx.vhd b/src/jesd204b_multipoint_link_rx.vhd
new file mode 100644
index 0000000..ae597c0
--- /dev/null
+++ b/src/jesd204b_multipoint_link_rx.vhd
@@ -0,0 +1,117 @@
+library ieee;
+use ieee.std_logic_1164.all;
+use work.jesd204b_pkg.all;
+use work.data_link_pkg.all;
+use work.transport_pkg.all;
+
+entity jesd204b_multipoint_link_rx is
+
+  generic (
+    K_character  : std_logic_vector(7 downto 0) := "10111100";  -- Sync character
+    R_character  : std_logic_vector(7 downto 0) := "00011100";  -- ILAS first
+                                        -- frame character
+    A_character  : std_logic_vector(7 downto 0) := "01111100";  -- Multiframe
+                                        -- alignment character
+    Q_character  : std_logic_vector(7 downto 0) := "10011100";  -- ILAS 2nd
+                                        -- frame 2nd character
+    LINKS        : integer;             -- Count of links
+    LANES        : integer;             -- Total nubmer of lanes
+    CONVERTERS   : integer;             -- Total number of converters
+    CONFIG       : link_config_array(0 to LINKS - 1);
+    ERROR_CONFIG : error_handling_config        := (2, 0, 5, 5, 5));
+
+  port (
+    ci_char_clk          : in  std_logic;
+    ci_frame_clk          : in  std_logic;
+    ci_reset            : in  std_logic;
+    ci_request_sync     : in  std_logic;
+    co_nsynced          : out std_logic;
+    co_error            : out std_logic;
+    di_transceiver_data : in  lane_input_array(0 to LANES - 1);
+    do_samples          : out simple_samples_array(0 to LINKS - 1)(0 to CONFIG(0).M - 1, 0 to CONFIG(0).CS - 1);
+    co_frame_state      : out frame_state_array(0 to LINKS - 1);
+    co_correct_data     : out std_logic);
+
+end entity jesd204b_multipoint_link_rx;
+
+architecture a1 of jesd204b_multipoint_link_rx is
+  constant all_ones : std_logic_vector(LINKS - 1 downto 0) := (others => '1');
+  constant all_zeros : std_logic_vector(LINKS - 1 downto 0) := (others => '0');
+
+  signal links_correct_data : std_logic_vector(LINKS - 1 downto 0);
+  signal links_error : std_logic_vector(LINKS - 1 downto 0);
+  signal links_nsynced : std_logic_vector(LINKS - 1 downto 0);
+
+  -- purpose: Count lanes before link with index link_index
+  function sumCummulativeLanes (
+    link_index : integer)
+    return integer is
+    variable lanes_count : integer := 0;
+  begin  -- function sumCummulativeLanes
+    if link_index /= 0 then
+        for i in 0 to link_index -1 loop
+          lanes_count := lanes_count + CONFIG(i).L;
+        end loop;  -- i
+    end if;
+
+    return lanes_count;
+  end function sumCummulativeLanes;
+
+  -- purpose: Count converters before link with index link_index
+  function sumCummulativeConverters (
+    link_index : integer)
+    return integer is
+    variable converters_count : integer := 0;
+  begin  -- function sumCummulativeConverters
+    if link_index /= 0 then
+        for i in 0 to link_index -1 loop
+          converters_count := converters_count + CONFIG(i).M;
+        end loop;  -- i
+    end if;
+
+    return converters_count;
+  end function sumCummulativeConverters;
+begin  -- architecture a1
+  co_nsynced <= '0' when links_nsynced = all_zeros else '1';
+  co_error <= '0' when links_error = all_zeros else '1';
+  co_correct_data <= '1' when links_correct_data = all_ones else '0';
+
+  links_rx: for i in 0 to LINKS - 1 generate
+    link: entity work.jesd204b_link_rx
+      generic map (
+        K_character  => K_character,
+        R_character  => R_character,
+        A_character  => A_character,
+        Q_character  => Q_character,
+        ERROR_CONFIG => ERROR_CONFIG,
+        ADJCNT       => CONFIG(i).ADJCNT,
+        BID          => CONFIG(i).BID,
+        DID          => CONFIG(i).DID,
+        HD           => CONFIG(i).HD,
+        JESDV        => CONFIG(i).JESDV,
+        PHADJ        => CONFIG(i).PHADJ,
+        SUBCLASSV    => CONFIG(i).SUBCLASSV,
+        K            => CONFIG(i).K,
+        CS           => CONFIG(i).CS,
+        M            => CONFIG(i).M,
+        S            => CONFIG(i).S,
+        L            => CONFIG(i).L,
+        F            => CONFIG(i).F,
+        CF           => CONFIG(i).CF,
+        N            => CONFIG(i).N,
+        Nn           => CONFIG(i).Nn,
+        ADJDIR       => CONFIG(i).ADJDIR)
+    port map (
+      ci_char_clk         => ci_char_clk,
+      ci_frame_clk        => ci_frame_clk,
+      ci_reset            => ci_reset,
+      ci_request_sync     => ci_request_sync,
+      co_nsynced          => links_nsynced(i),
+      co_error            => links_error(i),
+      di_transceiver_data => di_transceiver_data(sumCummulativeLanes(i) to sumCummulativeLanes(i) + CONFIG(i).L - 1),
+      do_samples          => do_samples(i), -- do_samples(sumCummulativeConverters(i) to sumCummulativeConverters(i) + CONFIG(i).M - 1),
+      co_frame_state      => co_frame_state(i),
+      co_correct_data     => links_correct_data(i));
+  end generate links_rx;
+
+end architecture a1;
diff --git a/src/jesd204b_pkg.vhd b/src/jesd204b_pkg.vhd
index 5ff7773..983ac89 100644
--- a/src/jesd204b_pkg.vhd
+++ b/src/jesd204b_pkg.vhd
@@ -1,5 +1,7 @@
 library ieee;
 use ieee.std_logic_1164.all;
+use work.transport_pkg.all;
+use work.data_link_pkg.all;
 
 -- Package for jesd204b types
 package jesd204b_pkg is
@@ -7,4 +9,7 @@ package jesd204b_pkg is
   -- array input data from lanes
   type lane_input_array is array (natural range <>) of std_logic_vector(9 downto 0);
 
+  -- array for link configs used in multipoint link
+  type link_config_array is array (natural range <>) of link_config;
+
 end package jesd204b_pkg;
diff --git a/src/transport/transport_pkg.vhd b/src/transport/transport_pkg.vhd
index eb4d345..4df3e3e 100644
--- a/src/transport/transport_pkg.vhd
+++ b/src/transport/transport_pkg.vhd
@@ -35,5 +35,6 @@ package transport_pkg is
 
   -- Array of samples in one frame by converter and by sample (used with oversampling)
   type samples_array is array (natural range <>, natural range <>) of sample;
+  type simple_samples_array is array (natural range <>) of samples_array;
 
 end package transport_pkg;
diff --git a/testbench/jesd204b_multipoint_data_tb.vhd b/testbench/jesd204b_multipoint_data_tb.vhd
new file mode 100644
index 0000000..fb7eea8
--- /dev/null
+++ b/testbench/jesd204b_multipoint_data_tb.vhd
@@ -0,0 +1,191 @@
+library ieee;
+use ieee.std_logic_1164.all;
+use work.testing_functions.all;
+use work.jesd204b_pkg.all;
+use work.data_link_pkg.all;
+use work.transport_pkg.all;
+
+entity jesd204b_multipoint_rx_data_tb is
+end entity jesd204b_multipoint_rx_data_tb;
+
+architecture a1 of jesd204b_multipoint_rx_data_tb is
+  constant LANES : integer := 2;
+  constant LINKS : integer := 2;
+  constant CONFIG : link_config_array(0 to LINKS - 1) :=
+  (
+    (0, '0', 0, 0, 2, 0, 2, '0', 1, 9, 1, 0, 1, 14, 16, '0', 1, '0', 0, "00000000", "00000000", "000000000", 0),
+    (0, '0', 0, 0, 2, 0, 2, '0', 1, 9, 1, 0, 1, 14, 16, '0', 1, '0', 0, "00000000", "00000000", "000000000", 0)
+  );
+
+  type octet_data is record
+    data : std_logic_vector(7 downto 0);
+    k : std_logic;
+  end record octet_data;
+
+  type lane_data_array is array (natural range <>) of octet_data;
+  type test_vector is record
+    data : lane_data_array(0 to LANES-1);
+  end record test_vector;
+
+  type test_vector_array is array (natural range <>) of test_vector;
+
+  constant char_offset : integer := 2;
+  constant char_prepend : std_logic_vector(char_offset-1 downto 0) := "00";
+  constant test_vectors : test_vector_array :=
+  (
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("10111100", '1'), ("10111100", '1'))),
+    (data => (("00011100", '1'), ("00011100", '1'))), -- 1st ILAS multiframe start
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("01111100", '1'), ("01111100", '1'))), -- 1st ILAS multiframe end
+    (data => (("00011100", '1'), ("00011100", '1'))), -- 2nd ILAS multiframe start
+    (data => (("10011100", '1'), ("10011100", '1'))), -- configuration start delimiter
+    (data => (("10101010", '0'), ("10101010", '0'))), -- DID
+    (data => (("01111110", '0'), ("01111110", '0'))), -- ADJCNT,BID
+    (data => (("01101010", '0'), ("01101010", '0'))), -- X,ADJDIR,PHADJ,LID
+    (data => (("11011110", '0'), ("11011110", '0'))), -- SCR,X,L
+    (data => (("11001100", '0'), ("11001100", '0'))), -- F
+    (data => (("01011111", '0'), ("01011111", '0'))), -- X, K
+    (data => (("00110011", '0'), ("00110011", '0'))), -- M
+    (data => (("10000011", '0'), ("10000011", '0'))), -- CS,X,N
+    (data => (("00111101", '0'), ("00111101", '0'))), -- SUBCLASSV,Nn
+    (data => (("00000000", '0'), ("00000000", '0'))), -- JESDV,S
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("01111100", '1'), ("01111100", '1'))), -- 2nd ILAS multiframe end
+    (data => (("00011100", '1'), ("00011100", '1'))), -- 3rd ILAS multiframe start
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("01111100", '1'), ("01111100", '1'))), -- 3rd ILAS multiframe end
+    (data => (("00011100", '1'), ("00011100", '1'))), -- 4th ILAS multiframe start
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("00000000", '0'), ("00000000", '0'))),
+    (data => (("01111100", '1'), ("01111100", '1'))), -- 4th ILAS multiframe end
+    (data => (("10101010", '0'), ("01010101", '0'))),  -- data
+    (data => (("10101001", '0'), ("01010110", '0'))),
+    (data => (("11111111", '0'), ("00000000", '0'))),  -- data
+    (data => (("11111110", '0'), ("00000001", '0'))),
+    (data => (("00000001", '0'), ("11111110", '0'))),  -- data
+    (data => (("11111111", '0'), ("00000000", '0')))
+  );
+
+  constant char_clk_period : time := 1 ns;    -- The clock period
+  constant frame_clk_period : time := 1 ns * CONFIG(0).F;    -- The clock period
+
+  signal di_transceiver_data : lane_input_array(0 to LANES-1);
+  signal di_lane_data : lane_data_array(0 to LANES-1);
+
+  signal char_clk : std_logic := '0';        -- The clock
+  signal frame_clk : std_logic := '0';        -- The clock
+  signal reset : std_logic := '0';      -- The reset
+
+  signal test_vec_index : integer := 0;
+
+  signal co_nsynced : std_logic;
+  signal co_error : std_logic;
+  signal do_samples : simple_samples_array (0 to 1) (0 to CONFIG(0).M - 1, 0 to CONFIG(0).S - 1)
+    (data(CONFIG(0).N - 1 downto 0), ctrl_bits(CONFIG(0).CS - 1 downto 0));
+  signal co_correct_data : std_logic;
+
+begin  -- architecture a1
+  uut : entity work.jesd204b_multipoint_link_rx
+    generic map (
+      LINKS      => LINKS,
+      LANES      => LANES,
+      CONVERTERS => 2,
+      CONFIG     => CONFIG)
+    port map (
+      ci_char_clk         => char_clk,
+      ci_frame_clk        => frame_clk,
+      ci_reset            => reset,
+      ci_request_sync     => '0',
+      di_transceiver_data => di_transceiver_data,
+      co_nsynced          => co_nsynced,
+      co_error            => co_error,
+      do_samples          => do_samples,
+      co_correct_data     => co_correct_data);
+
+  encoders: for i in 0 to LANES-1 generate
+    encoder: entity work.an8b10b_encoder
+      port map (
+        reset   => reset,
+        clk     => char_clk,
+        ena     => '1',
+        KI      => di_lane_data(i).k,
+        datain  => di_lane_data(i).data,
+        dataout => di_transceiver_data(i));
+  end generate encoders;
+
+  char_clk <= not char_clk after char_clk_period/2;
+  frame_clk <= not frame_clk after frame_clk_period/2;
+  reset <= '1' after char_clk_period*2;
+
+  test: process is
+  begin  -- process test
+    wait for char_clk_period*2;
+
+    for i in test_vectors'range loop
+      test_vec_index <= i;
+      di_lane_data <= test_vectors(i).data;
+      wait for char_clk_period;
+    end loop;  -- i
+
+    wait for 1000 ms;
+  end process test;
+end architecture a1;
-- 
2.48.1