Skocz do zawartości

Sipeed Tang Nano 4k z ADC - Gowin FPGA Designer


Pomocna odpowiedź

(edytowany)

Cześć,

dzisiaj usiadłem do projektu (dla FPGA Spartan7) i utworzyłem w "Vivado" "Block design", a następnie umieściłem na nim kilka potrzebnych IP Cores:

1) Clocking Wizard

2) Processor System Reset

3) Block Memory Generator

4) AXI4-Stream Data Width Converter

5) Fast Fourier Transform

i połączyłem te bloki odpowiednimi magistralami/sygnałami, oraz wyprowadziłem porty wejścia/wyjścia (dla Block Design). Patrz zrzuty ekranu:

SP7_BlockDesign01.thumb.png.44602a65f117163ac3eee678b298db6a.png

design_fft.pdf

Następnie wygenerowałem "HDL Wrapper"  dla tego "Block Design" (design_fft_wrapper) - patrz kod poniżej:

//Copyright 1986-2020 Xilinx, Inc. All Rights Reserved.
//--------------------------------------------------------------------------------
//Tool Version: Vivado v.2020.1 (win64) Build 2902540 Wed May 27 19:54:49 MDT 2020
//Date        : Sat Feb 26 12:29:52 2022
//Host        : DESKTOP-4Q2NB93 running 64-bit major release  (build 9200)
//Command     : generate_target design_fft_wrapper.bd
//Design      : design_fft_wrapper
//Purpose     : IP block netlist
//--------------------------------------------------------------------------------
`timescale 1 ps / 1 ps

module design_fft_wrapper
   (Address12b,
    clk_50MHz,
    event_data_in_channel_halt,
    event_data_out_channel_halt,
    event_fft_overflow,
    event_frame_started,
    event_status_channel_halt,
    event_tlast_missing,
    event_tlast_unexpected,
    m_axis_data_tdata,
    m_axis_data_tlast,
    m_axis_data_tready,
    m_axis_data_tuser,
    m_axis_data_tvalid,
    reset_rtl_0,
    s_axis_config_tdata,
    s_axis_config_tready,
    s_axis_config_tvalid,
    s_axis_data_tlast,
    s_axis_tready,
    s_axis_tvalid);
  input [12:0]Address12b;
  input clk_50MHz;
  output event_data_in_channel_halt;
  output event_data_out_channel_halt;
  output event_fft_overflow;
  output event_frame_started;
  output event_status_channel_halt;
  output event_tlast_missing;
  output event_tlast_unexpected;
  output [31:0]m_axis_data_tdata;
  output m_axis_data_tlast;
  input m_axis_data_tready;
  output [23:0]m_axis_data_tuser;
  output m_axis_data_tvalid;
  input reset_rtl_0;
  input [31:0]s_axis_config_tdata;
  output s_axis_config_tready;
  input s_axis_config_tvalid;
  input s_axis_data_tlast;
  output s_axis_tready;
  input s_axis_tvalid;

  wire [12:0]Address12b;
  wire clk_50MHz;
  wire event_data_in_channel_halt;
  wire event_data_out_channel_halt;
  wire event_fft_overflow;
  wire event_frame_started;
  wire event_status_channel_halt;
  wire event_tlast_missing;
  wire event_tlast_unexpected;
  wire [31:0]m_axis_data_tdata;
  wire m_axis_data_tlast;
  wire m_axis_data_tready;
  wire [23:0]m_axis_data_tuser;
  wire m_axis_data_tvalid;
  wire reset_rtl_0;
  wire [31:0]s_axis_config_tdata;
  wire s_axis_config_tready;
  wire s_axis_config_tvalid;
  wire s_axis_data_tlast;
  wire s_axis_tready;
  wire s_axis_tvalid;

  design_fft design_fft_i
       (.Address12b(Address12b),
        .clk_50MHz(clk_50MHz),
        .event_data_in_channel_halt(event_data_in_channel_halt),
        .event_data_out_channel_halt(event_data_out_channel_halt),
        .event_fft_overflow(event_fft_overflow),
        .event_frame_started(event_frame_started),
        .event_status_channel_halt(event_status_channel_halt),
        .event_tlast_missing(event_tlast_missing),
        .event_tlast_unexpected(event_tlast_unexpected),
        .m_axis_data_tdata(m_axis_data_tdata),
        .m_axis_data_tlast(m_axis_data_tlast),
        .m_axis_data_tready(m_axis_data_tready),
        .m_axis_data_tuser(m_axis_data_tuser),
        .m_axis_data_tvalid(m_axis_data_tvalid),
        .reset_rtl_0(reset_rtl_0),
        .s_axis_config_tdata(s_axis_config_tdata),
        .s_axis_config_tready(s_axis_config_tready),
        .s_axis_config_tvalid(s_axis_config_tvalid),
        .s_axis_data_tlast(s_axis_data_tlast),
        .s_axis_tready(s_axis_tready),
        .s_axis_tvalid(s_axis_tvalid));
endmodule

Teraz spróbuję napisać test-bench dla tego projektu i zobaczyć jak wygląda wyliczona Transformata Fouriera w symulacji. Być może w celu napisania testu będę musiał obudować jeszcze wygenerowany "HDL Wrapper" dodatkowym modułem i kodem w Verilogu.

Update: kurczę jakieś złe fatum - uszkodził mi się dysk na którym miałem zainstalowane Vivado (jeden podkatalog). Na szczęście miałem backup tego dysku i udało mi się odtworzyć katalog z zainstalowanym Vivado, oraz uruchomić syntezę i implementację projektu (wygląda, że synteza jest OK). Teraz zaczynam pisać test-bench, ale będzie on mocno skomplikowany. No i stało się, jak myślałem w fazie implementacji zabrakło pinów I/O i będę musiał obudować "HDL wrapper" za pomocą dodatkowego modułu.

Pozdrawiam

Edytowano przez FlyingDutch
update
(edytowany)

Cześć! @FlyingDutch

Dnia 26.02.2022 o 14:48, FlyingDutch napisał:

Update: kurczę jakieś złe fatum

Ja za to osiągnąłem jeden z istotnych punktów na krzywej Dunninga-Krugera...

image.thumb.png.15ce347a51ad5b981db20f18768f8e9a.png

Po zapoznaniu się ze składnią uznałem, że już wszystko wiem, aż rozbiłem się o ścianę symulatora, który uświadomił mnie że nic nie wiem. Zadanie, które uznałem za stosunkowo łatwe (synteza sterownika diod ES2812B) okazało się i tak wystarczająco trudne, że siedzę nad tym od kilku dni. Pojawiło się wiele problemów o których nie miałem pojęcia, np. nie sądziłem ile dylematów ciągnie za sobą wybór krawędzi zbocza sygnału zegarowego.

Najpierw napisałem licznik powiązany z układem kombinacyjnym, który ten licznik miał resetować. Wszystko odbywało się przy narastającym zboczu sygnału i nie wiedziałem co robię źle. Aż naszła mnie refleksja, że przecież jeżeli sprawdzam stan licznika w momencie jego jednoczesnej inkrementacji to przecież to jest błąd, bo nie wiem jaką odpowiedź uzyskam. Wystarczyło przesunąć licznik i jego odczyty o pół okresu i wiele problemów zniknęło.

Druga sprawa to coś czego nie mogę sobie wbić do głowy - always @ - jeden sygnał sterowany ale wiele sterujących. Najwidoczniej intuicja z języków programowania powoduje, że unikam powtarzania tego samego bloku warunkowego i nie podoba mi się jak w każdym bloku always @ muszę dodać ten sam if(rst) itp.

Cóż... mogę poczuć się jak kursanci zadający na forum względnie naiwne pytania.

image.thumb.png.84e7881a104dc396bb0459728d919968.png

Także zapoznałem się z test benchem. Na przebiegach widać jak po załadowaniu zawartości do rejestru data_stored reprezentującego kolor pojedynczej diody jest on sukcesywnie opróżniany, a kolejne bity current_bit są zamieniane na sygnał PWM na wyjściu sterownika diod drive. Niby proste, ale jednak kilka godzin nad tym siedziałem.

module ws2812b_modulator(
    input clk, //20MHz
    input en,
    input res, //global reset
    input [23:0] data, //bit to modulate or reseti
    output reg drive //direct drive signal to LED
);

localparam N = 10; // for counter

reg [N-1:0] counter;
wire current_bit;
reg shift;
reg [23:0] data_stored;

assign current_bit = data_stored[0];

always @(posedge clk)
begin
    begin
        case(current_bit)
        1'b0: begin 
            drive <= (counter < 2) ? 1'b1 : 1'b0; //8
            shift <= (counter >= 6-1) ? 1'b1 : 1'b0; //25
        end
        1'b1 : begin 
            drive <= (counter < 4) ? 1'b1 : 1'b0; //16
            shift <= (counter >= 6-1) ? 1'b1 : 1'b0; //25
        end
        default : drive <= 1'b1;
        endcase
    end
end

always @(negedge clk)
begin
    if(res || shift)
    begin
        counter <= {N-1{1'b0}};
    end
    else if (counter == 1023)
        counter <= 0;
    else
        counter <= counter + 1;
end

always @(posedge clk)
begin
    if(res)
        data_stored <= data;
    else if(shift)
        data_stored <= (data_stored >> 1);
end
endmodule



module test_ws2812b_modulator;

	reg reset;
	reg enable;
    wire busy_feedback;
    wire led;

    reg [23:0] buff;

	initial
	begin
		$display("Hello world");
		$dumpfile("test_dump.vcd");
		$dumpvars(0, test_ws2812b_modulator);
		# 0 reset = 1; enable = 1; buff = {20'b1, 4'b0101};
		# 1 reset = 1;
		# 1 reset = 0;
		# 513 $finish;
	end
	
	reg clk = 0;
	always #1 clk = ~clk;

    ws2812b_modulator INST_MODULATOR(
        .clk(clk), //20MHz
        .en(enable),
        .res(reset), //global reset
        .data(buff), //bit to modulate or reseti
        .drive(led) //direct drive signal to LED
    );
	
	initial
		$monitor("At time %t, led = %h (%0d)", $time, led, led);
endmodule

Zostało jeszcze napisanie generatora kolorów i układu do tworzenia długiej przerwy do aktualizacji koloru. Mam nadzieję, że po tym będę mądrzejszy i w końcu uda mi się wesprzeć Cię merytorycznie we właściwym projekcie 🙂 

Już się nie dziwię, dlaczego nie jest to popularny temat. To jest naprawdę trudne...

Edytowano przez Gieneq
  • Lubię! 2
2 godziny temu, Gieneq napisał:

Cześć! @FlyingDutch

Ja za to osiągnąłem jeden z istotnych punktów na krzywej Dunninga-Krugera...

Także zapoznałem się z test benchem. Na przebiegach widać jak po załadowaniu zawartości do rejestru data_stored reprezentującego kolor pojedynczej diody jest on sukcesywnie opróżniany, a kolejne bity current_bit są zamieniane na sygnał PWM na wyjściu sterownika diod drive. Niby proste, ale jednak kilka godzin nad tym siedziałem.




module ws2812b_modulator(
    input clk, //20MHz
    input en,
    input res, //global reset
    input [23:0] data, //bit to modulate or reseti
    output reg drive //direct drive signal to LED
);

localparam N = 10; // for counter

reg [N-1:0] counter;
wire current_bit;
reg shift;
reg [23:0] data_stored;

assign current_bit = data_stored[0];

always @(posedge clk)
begin
    begin
        case(current_bit)
        1'b0: begin 
            drive <= (counter < 2) ? 1'b1 : 1'b0; //8
            shift <= (counter >= 6-1) ? 1'b1 : 1'b0; //25
        end
        1'b1 : begin 
            drive <= (counter < 4) ? 1'b1 : 1'b0; //16
            shift <= (counter >= 6-1) ? 1'b1 : 1'b0; //25
        end
        default : drive <= 1'b1;
        endcase
    end
end

always @(negedge clk)
begin
    if(res || shift)
    begin
        counter <= {N-1{1'b0}};
    end
    else if (counter == 1023)
        counter <= 0;
    else
        counter <= counter + 1;
end

always @(posedge clk)
begin
    if(res)
        data_stored <= data;
    else if(shift)
        data_stored <= (data_stored >> 1);
end
endmodule



module test_ws2812b_modulator;

	reg reset;
	reg enable;
    wire busy_feedback;
    wire led;

    reg [23:0] buff;

	initial
	begin
		$display("Hello world");
		$dumpfile("test_dump.vcd");
		$dumpvars(0, test_ws2812b_modulator);
		# 0 reset = 1; enable = 1; buff = {20'b1, 4'b0101};
		# 1 reset = 1;
		# 1 reset = 0;
		# 513 $finish;
	end
	
	reg clk = 0;
	always #1 clk = ~clk;

    ws2812b_modulator INST_MODULATOR(
        .clk(clk), //20MHz
        .en(enable),
        .res(reset), //global reset
        .data(buff), //bit to modulate or reseti
        .drive(led) //direct drive signal to LED
    );
	
	initial
		$monitor("At time %t, led = %h (%0d)", $time, led, led);
endmodule

Zostało jeszcze napisanie generatora kolorów i układu do tworzenia długiej przerwy do aktualizacji koloru. Mam nadzieję, że po tym będę mądrzejszy i w końcu uda mi się wesprzeć Cię merytorycznie we właściwym projekcie 🙂 

Już się nie dziwię, dlaczego nie jest to popularny temat. To jest naprawdę trudne...

Cześć @Gieneq,

nie znałem tej krzywej, ale mi się podoba 😄, ja jestem w podobnym miejscu tej krzywej od czasu jak zainteresowałem się układami FPGA (czyli od kilku lat). Ja walczę z tym ostatnim projektem i staram się obudować "Block Design" (HDL Wrapper) za pomocą dodatkowego modułu i oprogramować całość, ale napotkałem problemy z generowaniem sygnałów sterujących magistrali AXI( a raczej ich synchronizacją pomiędzy kilkoma blokami IP) i też utknąłem (powoli eliminuję błędy, ale pojawiają się następne i cały cykl się powtarza). Dzisiaj odebrałem płytkę przetwornika ADC (ADS1256) i będę też mógł rozpocząć próby z tą płytką.

BTW: często się tak robi - to znaczy wykorzystuje oba zbocza sygnału do wykonania różnych operacji (przynajmniej widziałem to często w cudzym kodzie).

A wiesz co jest najgorsze: to, że na moim kompie (4rdzenie) wszystko dzieje się tragicznie wolno - czekasz mnóstwo czasu, aby dowiedzieć się , że znów masz błędy np. w fazie implementacji. Ja musiałem kostkę w projekcie zmienić na dużo większą bo brakowało mi bloków DSP i wyprowadzeń. Chcę tylko uruchomić symulację, ale brakuje mi z 70  pinów I/O, to i tak muszę zmieniać układ FPGA na większy (potem upgradować IP Cores i generować wszystko na nowo), tylko po to aby uruchomić symulację (test-bench). To frustruje 🤨

Pozdrawiam

  • Lubię! 1
(edytowany)
5 godzin temu, FlyingDutch napisał:

od czasu jak zainteresowałem się układami FPGA (czyli od kilku lat).

O masakra... to jakieś szaleństwo. 

Widzę, że to naprawdę trochę zajmie. Dziś zmieniłem edukacyjny przykład, bo sterownik diod jest za trudny i napisałem sterownik serwa - to jest akurat banał. Po czym wymyśliłem, że dołożę cegiełkę i dodam SPI slave. Jak zobaczyłem SPI master w IP core to od razu zrezygnowałem i jak na razie doszedłem że w sumie nic nie wiem o SPI.

Cóż, gdyby robić tylko to w czym jest się dobrym, nigdy nie przeskoczy się samego siebie 😄 

5 godzin temu, FlyingDutch napisał:

A wiesz co jest najgorsze: to, że na moim kompie (4rdzenie) wszystko dzieje się tragicznie wolno

Ciekawe czy wszystkie rdzenie są wykorzystane. U mnie jest 6 rdzeni i7 9 gen ale jeszcze nie miałem okazji wykorzystać potencjału.

Pamiętam jak robiłem coś w ISE Xilinxa to synteza czegokolwiek zajmowała długo. FPGA Designer przeprowadza synteze błyskawicznie. Z jednej strony fajnie, z drugiej to trochę podejrzanie.

Znalazłem ciekawy filmik o SPI Master, postaram się go przerobić. Jest też inny poradnik na tym kanale z kodem w VHDL.

 

Edytowano przez Gieneq
  • Lubię! 1

Cześć,

dzisiaj utworzyłem projekt w Vivado 2020.1 dla FPGA Artix7 (XC7A100T-2FGG676I), bo Spartany7 są po prostu za małe dla tego IP Core. Do projektu dodałem sam IP Core "FFT", wtedy udało mi się wygenerować test-bench (oryginalny kod VHDL Xilinx'a). Gdy IP Core "FFT" był dodawany do "Block Design" nie wiem z jakiego powodu test-bench nie był generowany. Już wiem dlaczego test-bench pisany przeze mnie nie działał prawidłowo. Po prostu nie uwzględniłem wszystkich  potrzebnych rzeczy (opisane były w dokumentacji IP Core FFT bardzo pobieżnie). Kod test-bench'a jest bardzo długi - zamieszczam go tutaj, bo może się komuś przydać (także mnie za jakiś czas):

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;

entity tb_xfft_0 is
end tb_xfft_0;

architecture tb of tb_xfft_0 is

  -----------------------------------------------------------------------
  -- Timing constants
  -----------------------------------------------------------------------
  constant CLOCK_PERIOD : time := 100 ns;
  constant T_HOLD       : time := 10 ns;
  constant T_STROBE     : time := CLOCK_PERIOD - (1 ns);

  -----------------------------------------------------------------------
  -- DUT signals
  -----------------------------------------------------------------------

  -- General signals
  signal aclk                        : std_logic := '0';  -- the master clock

  -- Config slave channel signals
  signal s_axis_config_tvalid        : std_logic := '0';  -- payload is valid
  signal s_axis_config_tready        : std_logic := '1';  -- slave is ready
  signal s_axis_config_tdata         : std_logic_vector(15 downto 0) := (others => '0');  -- data payload

  -- Data slave channel signals
  signal s_axis_data_tvalid          : std_logic := '0';  -- payload is valid
  signal s_axis_data_tready          : std_logic := '1';  -- slave is ready
  signal s_axis_data_tdata           : std_logic_vector(31 downto 0) := (others => '0');  -- data payload
  signal s_axis_data_tlast           : std_logic := '0';  -- indicates end of packet

  -- Data master channel signals
  signal m_axis_data_tvalid          : std_logic := '0';  -- payload is valid
  signal m_axis_data_tready          : std_logic := '1';  -- slave is ready
  signal m_axis_data_tdata           : std_logic_vector(31 downto 0) := (others => '0');  -- data payload
  signal m_axis_data_tuser           : std_logic_vector(23 downto 0) := (others => '0');  -- user-defined payload
  signal m_axis_data_tlast           : std_logic := '0';  -- indicates end of packet

  -- Status master channel signals
  signal m_axis_status_tvalid        : std_logic := '0';  -- payload is valid
  signal m_axis_status_tready        : std_logic := '1';  -- slave is ready
  signal m_axis_status_tdata         : std_logic_vector(7 downto 0) := (others => '0');  -- data payload

  -- Event signals
  signal event_frame_started         : std_logic := '0';
  signal event_tlast_unexpected      : std_logic := '0';
  signal event_tlast_missing         : std_logic := '0';
  signal event_fft_overflow          : std_logic := '0';
  signal event_status_channel_halt   : std_logic := '0';
  signal event_data_in_channel_halt  : std_logic := '0';
  signal event_data_out_channel_halt : std_logic := '0';

  -----------------------------------------------------------------------
  -- Aliases for AXI channel TDATA and TUSER fields
  -- These are a convenience for viewing data in a simulator waveform viewer.
  -- If using ModelSim or Questa, add "-voptargs=+acc=n" to the vsim command
  -- to prevent the simulator optimizing away these signals.
  -----------------------------------------------------------------------

  -- Config slave channel alias signals
  signal s_axis_config_tdata_fwd_inv      : std_logic                    := '0';              -- forward or inverse
  signal s_axis_config_tdata_scale_sch    : std_logic_vector(9 downto 0) := (others => '0');  -- scaling schedule

  -- Data slave channel alias signals
  signal s_axis_data_tdata_re             : std_logic_vector(15 downto 0) := (others => '0');  -- real data
  signal s_axis_data_tdata_im             : std_logic_vector(15 downto 0) := (others => '0');  -- imaginary data

  -- Data master channel alias signals
  signal m_axis_data_tdata_re             : std_logic_vector(15 downto 0) := (others => '0');  -- real data
  signal m_axis_data_tdata_im             : std_logic_vector(15 downto 0) := (others => '0');  -- imaginary data
  signal m_axis_data_tuser_xk_index       : std_logic_vector(9 downto 0) := (others => '0');  -- sample index
  signal m_axis_data_tuser_ovflo          : std_logic := '0';  -- overflow

  -- Status master channel alias signals
  signal m_axis_status_tdata_ovflo        : std_logic := '0';  -- overflow

  -----------------------------------------------------------------------
  -- Constants, types and functions to create input data
  -----------------------------------------------------------------------

  constant IP_WIDTH    : integer := 16;
  constant MAX_SAMPLES : integer := 2**10;  -- maximum number of samples in a frame
  type T_IP_SAMPLE is record
    re : std_logic_vector(IP_WIDTH-1 downto 0);
    im : std_logic_vector(IP_WIDTH-1 downto 0);
  end record;
  type T_IP_TABLE is array (0 to MAX_SAMPLES-1) of T_IP_SAMPLE;

  -- Zeroed input data table, for reset and initialization
  constant IP_TABLE_CLEAR : T_IP_TABLE := (others => (re => (others => '0'),
                                                      im => (others => '0')));

  -- Function to generate input data table
  -- Data is a complex sinusoid exp(-jwt) with a frequency 2.6 times the frame size
  -- added to another with a lower magnitude and a higher frequency
  function create_ip_table return T_IP_TABLE is
    variable result : T_IP_TABLE;
    variable theta  : real;
    variable theta2 : real;
    variable re_real : real;
    variable im_real : real;
    variable re_int : integer;
    variable im_int : integer;
    constant DATA_WIDTH : integer := 14;
  begin
    for i in 0 to MAX_SAMPLES-1 loop
      theta   := real(i) / real(MAX_SAMPLES) * 2.6 * 2.0 * MATH_PI;
      re_real := cos(-theta);
      im_real := sin(-theta);
      theta2  := real(i) / real(MAX_SAMPLES) * 23.2 * 2.0 * MATH_PI;
      re_real := re_real + (cos(-theta2) / 4.0);
      im_real := im_real + (sin(-theta2) / 4.0);
      re_int  := integer(round(re_real * real(2**(DATA_WIDTH))));
      im_int  := integer(round(im_real * real(2**(DATA_WIDTH))));
      result(i).re := std_logic_vector(to_signed(re_int, IP_WIDTH));
      result(i).im := std_logic_vector(to_signed(im_int, IP_WIDTH));
    end loop;
    return result;
  end function create_ip_table;

  -- Call the function to create the input data
  constant IP_DATA : T_IP_TABLE := create_ip_table;

  -----------------------------------------------------------------------
  -- Testbench signals
  -----------------------------------------------------------------------

  -- Communication between processes regarding DUT configuration
  type T_DO_CONFIG is (NONE, IMMEDIATE, AFTER_START, DONE);
  shared variable do_config : T_DO_CONFIG := NONE;  -- instruction for driving config slave channel
  type T_CFG_FWD_INV is (FWD, INV);
  signal cfg_fwd_inv : T_CFG_FWD_INV := FWD;
  type T_CFG_SCALE_SCH is (ZERO, DEFAULT);
  signal cfg_scale_sch : T_CFG_SCALE_SCH := DEFAULT;

  -- Recording output data, for reuse as input data
  signal ip_frame        : integer    := 0;    -- input / configuration frame number
  signal op_data         : T_IP_TABLE := IP_TABLE_CLEAR;  -- recorded output data
  signal op_frame        : integer    := 0;    -- output frame number (incremented at end of frame output)


begin

  -----------------------------------------------------------------------
  -- Instantiate the DUT
  -----------------------------------------------------------------------

  dut : entity work.xfft_0
    port map (
      aclk                        => aclk,
      s_axis_config_tvalid        => s_axis_config_tvalid,
      s_axis_config_tready        => s_axis_config_tready,
      s_axis_config_tdata         => s_axis_config_tdata,
      s_axis_data_tvalid          => s_axis_data_tvalid,
      s_axis_data_tready          => s_axis_data_tready,
      s_axis_data_tdata           => s_axis_data_tdata,
      s_axis_data_tlast           => s_axis_data_tlast,
      m_axis_data_tvalid          => m_axis_data_tvalid,
      m_axis_data_tready          => m_axis_data_tready,
      m_axis_data_tdata           => m_axis_data_tdata,
      m_axis_data_tuser           => m_axis_data_tuser,
      m_axis_data_tlast           => m_axis_data_tlast,
      m_axis_status_tvalid        => m_axis_status_tvalid,
      m_axis_status_tready        => m_axis_status_tready,
      m_axis_status_tdata         => m_axis_status_tdata,
      event_frame_started         => event_frame_started,
      event_tlast_unexpected      => event_tlast_unexpected,
      event_tlast_missing         => event_tlast_missing,
      event_fft_overflow          => event_fft_overflow,
      event_status_channel_halt   => event_status_channel_halt,
      event_data_in_channel_halt  => event_data_in_channel_halt,
      event_data_out_channel_halt => event_data_out_channel_halt
      );

  -----------------------------------------------------------------------
  -- Generate clock
  -----------------------------------------------------------------------

  clock_gen : process
  begin
    aclk <= '0';
    wait for CLOCK_PERIOD;
    loop
      aclk <= '0';
      wait for CLOCK_PERIOD/2;
      aclk <= '1';
      wait for CLOCK_PERIOD/2;
    end loop;
  end process clock_gen;

  -----------------------------------------------------------------------
  -- Generate data slave channel inputs
  -----------------------------------------------------------------------

  data_stimuli : process

    -- Variables for random number generation
    variable seed1, seed2 : positive;
    variable rand         : real;

    -- Procedure to drive an input sample with specific data
    -- data is the data value to drive on the tdata signal
    -- last is the bit value to drive on the tlast signal
    -- valid_mode defines how to drive TVALID: 0 = TVALID always high, 1 = TVALID low occasionally
    procedure drive_sample ( data       : std_logic_vector(31 downto 0);
                             last       : std_logic;
                             valid_mode : integer := 0 ) is
    begin
      s_axis_data_tdata  <= data;
      s_axis_data_tlast  <= last;

      if valid_mode = 1 then
        uniform(seed1, seed2, rand);  -- generate random number
        if rand < 0.25 then
          s_axis_data_tvalid <= '0';
          uniform(seed1, seed2, rand);  -- generate another random number
          wait for CLOCK_PERIOD * integer(round(rand * 4.0));  -- hold TVALID low for up to 4 cycles
          s_axis_data_tvalid <= '1';  -- now assert TVALID
        else
          s_axis_data_tvalid <= '1';
        end if;
      else
        s_axis_data_tvalid <= '1';
      end if;
      loop
        wait until rising_edge(aclk);
        exit when s_axis_data_tready = '1';
      end loop;
      wait for T_HOLD;
      s_axis_data_tvalid <= '0';
    end procedure drive_sample;

    -- Procedure to drive an input frame with a table of data
    -- data is the data table containing input data
    -- valid_mode defines how to drive TVALID: 0 = TVALID always high, 1 = TVALID low occasionally
    procedure drive_frame ( data         : T_IP_TABLE;
                            valid_mode   : integer := 0 ) is
      variable samples : integer;
      variable index   : integer;
      variable sample_data : std_logic_vector(31 downto 0);
      variable sample_last : std_logic;
    begin
      samples := data'length;
      index  := 0;
      while index < data'length loop
        -- Look up sample data in data table, construct TDATA value
        sample_data(15 downto 0)  := data(index).re;                  -- real data
        sample_data(31 downto 16) := data(index).im;                  -- imaginary data
        -- Construct TLAST's value
        index := index + 1;
        if index >= data'length then
          sample_last := '1';
        else
          sample_last := '0';
        end if;
        -- Drive the sample
        drive_sample(sample_data, sample_last, valid_mode);
      end loop;
    end procedure drive_frame;

    variable op_data_saved : T_IP_TABLE;  -- to save a copy of recorded output data


  begin

    -- Drive inputs T_HOLD time after rising edge of clock
    wait until rising_edge(aclk);
    wait for T_HOLD;

    -- Drive a frame of input data
    ip_frame <= 1;
    drive_frame(IP_DATA);

    -- Allow the result to emerge
    wait until m_axis_data_tlast = '1';
    wait until rising_edge(aclk);
    wait for T_HOLD;

    -- Take a copy of the result, to use later as input
    op_data_saved := op_data;

    -- Now perform an inverse transform on the result to get back to the original input
    -- Set up the configuration (config_stimuli process handles the config slave channel)
    ip_frame <= 2;
    cfg_fwd_inv <= INV;
    do_config := IMMEDIATE;
    while do_config /= DONE loop
      wait until rising_edge(aclk);
    end loop;
    wait for T_HOLD;

    -- Configuration is done.  Set up another configuration to return to forward transforms,
    -- and make the configuration occur as soon as the next frame has begun
    ip_frame <= 3;
    cfg_fwd_inv <= FWD;
    do_config := AFTER_START;

    -- Now drive the input data, using the output data of the last frame
    drive_frame(op_data);
    wait until m_axis_data_tlast = '1';
    wait until rising_edge(aclk);
    wait for T_HOLD;

    -- The frame is complete, and the configuration to forward transforms has already been done,
    -- so drive the input data, using the output data of the last frame,
    -- which is the same as the original input (excepting scaling and finite precision effects).
    -- This time, deassert the data slave channel TVALID occasionally to illustrate AXI handshaking effects:
    -- as the core is configured to use Non Real Time throttle scheme, it will pause when TVALID is low.
    drive_frame(op_data, 1);

    -- During the output of this frame, deassert the data master channel TREADY occasionally:
    -- as the core is configured to use Non Real Time throttle scheme, it will pause when TREADY is low.
    wait until m_axis_data_tvalid = '1';
    wait until rising_edge(aclk);
    while m_axis_data_tlast /= '1' loop
      wait for T_HOLD;
      uniform(seed1, seed2, rand);  -- generate random number
      if rand < 0.25 then
        m_axis_data_tready <= '0';
      else
        m_axis_data_tready <= '1';
      end if;
      wait until rising_edge(aclk);
    end loop;
    wait for T_HOLD;
    m_axis_data_tready <= '1';
    wait for CLOCK_PERIOD;

    -- Now run 4 back-to-back transforms, as quickly as possible.
    -- First queue up 2 configurations: these will be applied successively over the next 2 transforms.
    -- 1st configuration
    ip_frame <= 4;
    cfg_fwd_inv <= FWD;  -- forward transform
    cfg_scale_sch <= DEFAULT;  -- default scaling schedule
    do_config := IMMEDIATE;
    while do_config /= DONE loop
      wait until rising_edge(aclk);
    end loop;
    wait for T_HOLD;

    -- 2nd configuration: same as 1st, except:
    ip_frame <= 5;
    cfg_fwd_inv <= INV;  -- inverse transform
    cfg_scale_sch <= ZERO;  -- no scaling
    do_config := IMMEDIATE;
    while do_config /= DONE loop
      wait until rising_edge(aclk);
    end loop;
    wait for T_HOLD;

    -- Drive the 1st data frame
    drive_frame(IP_DATA);

    -- Request a 3rd configuration, to be sent after 2nd data frame starts
    ip_frame <= 6;
    cfg_fwd_inv <= FWD;  -- forward transform
    cfg_scale_sch <= ZERO;  -- no scaling
    do_config := AFTER_START;

    -- Drive the 2nd data frame
    drive_frame(op_data_saved);

    -- Request a 4th configuration, to be sent after 3rd data frame starts: same as 3rd, except:
    ip_frame <= 7;
    cfg_fwd_inv <= INV;  -- inverse transform
    cfg_scale_sch <= DEFAULT;  -- default scaling schedule
    do_config := AFTER_START;

    -- Drive the 3rd data frame
    drive_frame(IP_DATA);

    -- Drive the 4th data frame
    drive_frame(op_data_saved);

    -- Wait until all the output data from all frames has been produced
    wait until op_frame = 7;
    wait for CLOCK_PERIOD * 10;

    -- End of test
    report "Not a real failure. Simulation finished successfully. Test completed successfully" severity failure;
    wait;

  end process data_stimuli;

  -----------------------------------------------------------------------
  -- Generate config slave channel inputs
  -----------------------------------------------------------------------

  config_stimuli : process
    variable scale_sch : std_logic_vector(9 downto 0);
  begin

    -- Drive a configuration when requested by data_stimuli process
    wait until rising_edge(aclk);
    while do_config = NONE or do_config = DONE loop
      wait until rising_edge(aclk);
    end loop;

    -- If the configuration is requested to occur after the next frame starts, wait for that event
    if do_config = AFTER_START then
      wait until event_frame_started = '1';
      wait until rising_edge(aclk);
    end if;

    -- Drive inputs T_HOLD time after rising edge of clock
    wait for T_HOLD;

    -- Construct the config slave channel TDATA signal
    s_axis_config_tdata <= (others => '0');  -- clear unused bits
    -- Format the transform direction
    if cfg_fwd_inv = FWD then
      s_axis_config_tdata(0) <= '1';  -- forward
    elsif cfg_fwd_inv = INV then
      s_axis_config_tdata(0) <= '0';  -- inverse
    end if;
    -- Format the scaling schedule
    if cfg_scale_sch = ZERO then  -- no scaling
      scale_sch := (others => '0');
    elsif cfg_scale_sch = DEFAULT then  -- default scaling, for largest magnitude output with no overflow guaranteed
      scale_sch(1 downto 0) := "11";  -- largest scaling at first stage
      for s in 2 to 5 loop
        scale_sch(s*2-1 downto s*2-2) := "10";  -- less scaling at later stages
      end loop;
    end if;
    s_axis_config_tdata(10 downto 1) <= scale_sch;

    -- Drive the transaction on the config slave channel
    s_axis_config_tvalid <= '1';
    loop
      wait until rising_edge(aclk);
      exit when s_axis_config_tready = '1';
    end loop;
    wait for T_HOLD;
    s_axis_config_tvalid <= '0';

    -- Tell the data_stimuli process that the configuration has been done
    do_config := DONE;

  end process config_stimuli;

  -----------------------------------------------------------------------
  -- Record outputs, to use later as inputs for another frame
  -----------------------------------------------------------------------

  record_outputs : process (aclk)
    variable index : integer := 0;

  begin
    if rising_edge(aclk) then
      if m_axis_data_tvalid = '1' and m_axis_data_tready = '1' then
        -- Record output data such that it can be used as input data
        -- Output sample index is given by xk_index field of m_axis_data_tuser
        index := to_integer(unsigned(m_axis_data_tuser(9 downto 0)));
        op_data(index).re <= m_axis_data_tdata(15 downto 0);
        op_data(index).im <= m_axis_data_tdata(31 downto 16);
        -- Track the number of output frames
        if m_axis_data_tlast = '1' then  -- end of output frame: increment frame counter
          op_frame <= op_frame + 1;
        end if;
      end if;
    end if;
  end process record_outputs;

  -----------------------------------------------------------------------
  -- Check outputs
  -----------------------------------------------------------------------

  check_outputs : process
    variable check_ok : boolean := true;
    -- Previous values of data master channel signals
    variable m_data_tvalid_prev : std_logic := '0';
    variable m_data_tready_prev : std_logic := '0';
    variable m_data_tdata_prev  : std_logic_vector(31 downto 0) := (others => '0');
    variable m_data_tuser_prev  : std_logic_vector(23 downto 0) := (others => '0');
    -- Previous values of status master channel signals
    variable m_status_tvalid_prev : std_logic := '0';
    variable m_status_tready_prev : std_logic := '0';
    variable m_status_tdata_prev  : std_logic_vector(7 downto 0) := (others => '0');
  begin

    -- Check outputs T_STROBE time after rising edge of clock
    wait until rising_edge(aclk);
    wait for T_STROBE;

    -- Do not check the output payload values, as this requires a numerical model
    -- which would make this demonstration testbench unwieldy.
    -- Instead, check the protocol of the data and status master channels:
    -- check that the payload is valid (not X) when TVALID is high
    -- and check that the payload does not change while TVALID is high until TREADY goes high

    if m_axis_data_tvalid = '1' then
      if is_x(m_axis_data_tdata) then
        report "ERROR: m_axis_data_tdata is invalid when m_axis_data_tvalid is high" severity error;
        check_ok := false;
      end if;
      if is_x(m_axis_data_tuser) then
        report "ERROR: m_axis_data_tuser is invalid when m_axis_data_tvalid is high" severity error;
        check_ok := false;
      end if;

      if m_data_tvalid_prev = '1' and m_data_tready_prev = '0' then  -- payload must be the same as last cycle
        if m_axis_data_tdata /= m_data_tdata_prev then
          report "ERROR: m_axis_data_tdata changed while m_axis_data_tvalid was high and m_axis_data_tready was low" severity error;
          check_ok := false;
        end if;
        if m_axis_data_tuser /= m_data_tuser_prev then
          report "ERROR: m_axis_data_tuser changed while m_axis_data_tvalid was high and m_axis_data_tready was low" severity error;
          check_ok := false;
        end if;
      end if;

    end if;

    if m_axis_status_tvalid = '1' then
      if is_x(m_axis_status_tdata) then
        report "ERROR: m_axis_status_tdata is invalid when m_axis_status_tvalid is high" severity error;
        check_ok := false;
      end if;

      if m_status_tvalid_prev = '1' and m_status_tready_prev = '0' then  -- payload must be the same as last cycle
        if m_axis_status_tdata /= m_status_tdata_prev then
          report "ERROR: m_axis_status_tdata changed while m_axis_status_tvalid was high and m_axis_status_tready was low" severity error;
          check_ok := false;
        end if;
      end if;

    end if;

    assert check_ok
      report "ERROR: terminating test with failures." severity failure;

    -- Record payload values for checking next clock cycle
    if check_ok then
      m_data_tvalid_prev  := m_axis_data_tvalid;
      m_data_tready_prev  := m_axis_data_tready;
      m_data_tdata_prev   := m_axis_data_tdata;
      m_data_tuser_prev   := m_axis_data_tuser;
      m_status_tvalid_prev := m_axis_status_tvalid;
      m_status_tready_prev := m_axis_status_tready;
      m_status_tdata_prev  := m_axis_status_tdata;
    end if;

  end process check_outputs;

  -----------------------------------------------------------------------
  -- Assign TDATA / TUSER fields to aliases, for easy simulator waveform viewing
  -----------------------------------------------------------------------

  -- Config slave channel alias signals
  s_axis_config_tdata_fwd_inv    <= s_axis_config_tdata(0);
  s_axis_config_tdata_scale_sch  <= s_axis_config_tdata(10 downto 1);

  -- Data slave channel alias signals
  s_axis_data_tdata_re           <= s_axis_data_tdata(15 downto 0);
  s_axis_data_tdata_im           <= s_axis_data_tdata(31 downto 16);

  -- Data master channel alias signals
  m_axis_data_tdata_re           <= m_axis_data_tdata(15 downto 0);
  m_axis_data_tdata_im           <= m_axis_data_tdata(31 downto 16);
  m_axis_data_tuser_xk_index     <= m_axis_data_tuser(9 downto 0);
  m_axis_data_tuser_ovflo        <= m_axis_data_tuser(16);

  -- Status master channel alias signals
  m_axis_status_tdata_ovflo      <= m_axis_status_tdata(0);

end tb;

A tutaj screenshot z symulacji w Vivado:

Artix7_FFT_1K_TB_.thumb.png.ce2b86a3e7c548420f64b9a7a3744bca.png

Przeanalizowanie go zajęło mi sporo czasu. Nie wiem dlaczego każda próba zrobienia czegoś praktycznego i ciekawszego w FPGA zaczyna zawsze absurdalnie rosnąć jeśli chodzi o złożoność i liczbę linii kodu. Jutro podłączę płytkę z przetwornikiem ADC do zestawu Nucleo z STM32 (bo jest do tego kod źródłowy) i zobaczę czy działa.

Pozdrawiam

  • Pomogłeś! 1

Cześć @FlyingDutch

Masakra... nie dziwi mnie czemu ten temat jest niezabardzo popularny. Podziwiam za zapał i wiedzę. W ogóle zawodowo jakie projekty robisz w FPGA? Wszystko co ma jakieś praktyczne walory jest tak trudne w tym temacie?

10 godzin temu, Gieneq napisał:

Cześć @FlyingDutch

Masakra... nie dziwi mnie czemu ten temat jest niezabardzo popularny. Podziwiam za zapał i wiedzę. W ogóle zawodowo jakie projekty robisz w FPGA? Wszystko co ma jakieś praktyczne walory jest tak trudne w tym temacie?

Cześć @Gieneq,

nie, zawodowo zajmuję się programowaniem mikro-kontrolerów (głównie różne wersje STM32). FPGA to tylko hobby.

Pozdrawiam

(edytowany)

Cześć,

tak jak już pisałem przyszła do mnie płytka z przetwornikiem ADS1256 - tutaj link do sklepu:

https://www.aliexpress.com/item/1005001593721645.html?gatewayAdapt=glo2pol&spm=a2g0o.9042311.0.0.52de5c0fFZ7nE5

A tutaj kod w języku C (HAL) dla kilku wersji STM32 (wygląda w miarę prosto):

https://github.com/eta-systems/ADS1255

Tutaj wszystkie potrzebne podzespoły: płytka z ADS1256, NUCLEO-F411RE, i ośmiokanałowy konwerter poziomów logicznych

IMG_20220302_130932.thumb.jpg.72e782f9c5f5db99825c061b15616fd1.jpg

Zobaczę co z tego wyjdzie - to w ramach małego odpoczynku od FPGA 😉

Update: podłączyłem płytkę z przetwornikiem ADC ADS1256 do zestawu NUCLEO-F411RE i wgrałem projekt, który utworzyłem dla "System Workbench 4 STM32" (z obsługą przetwornika ADC) - patrz zrzut ekranu:

SW4STM32_01.thumb.png.2bfbe02bf257511debc17c23726388b2.png

IMG_20220302_165728.thumb.jpg.a5399e0f36218f5c5db16645c3617b1a.jpg

Wygląda na to, że przetwornik jest poprawnie inicjalizowany i sczytuje w miarę poprawne wartości (bardzo małe liczby float), dla kanałów które wiszą w powietrzu. Teraz muszę podłączyć do płytki przetwornika jakieś źródło sygnału audio i zobaczyć jak będą wyglądały sample.

W zipie spakowany projekt dla Nucleo-F411RE (SW4STM32).

Nucleo-F411RE_ADC.zip

Pozdrawiam

Edytowano przez FlyingDutch
update
  • Pomogłeś! 1

Cześć @FlyingDutch ta część wygląda dużo prościej. Do mnie też dotarł przetwornik:

IMG_8757.thumb.jpg.d59f3290687e14ff0239316e5be394ab.jpg

Tak się zastanawiałem nad tym projektem i doszedłem do wniosku, że jeżeli będzie to absurdalnie trudne zadanie to najpierw zrobię to co umiem:

  • ESP32 do połączenia z Home Assistant,
  • STM32 do liczenia FFT i sterowania wyświetlaczem + ADS1256.

I tak i tak dojdzie osobny układ, na ten moment najważniejsze jest, żeby udało się użyć przetwornik i rozszerzyć pasmo.

Na chwilę odłożyłem projekt, bo chcę dokończyć jeden temat o który jestem dość często dopytywany, żeby wrzucić na YT nagranie z budowy wyświetlacza. Także ten tydzień spędzam w programie do montażu, a później postaram się podłączyć ten przetwornik.

Jeszcze raz dziękuję za ogromne zaangażowanie, pozdrawiam! 🙂 

  • Lubię! 1
(edytowany)

Cześć,

sprawdziłem dzisiaj działanie płytki z przetwornikiem ADS1256 ostatecznie dla prędkości zbierania sampli 30000sps (dla jednego kanału) i wygląda, że działa prawidłowo. Potwierdzam, że będzie to dużo łatwiej zrobić dla STM32 (powiedzmy seria F4 bo od tej zaczynają się MCU za sprzętowym modułem FPU - floating point) i liczyć FFT na STM32. Przetwornik ADC o prędkości próbkowania 30Ksps śmiało można obsłużyć przez CPU. Gorzej by było z ADC o prędkości 10--20 Msps, przy takiej prędkości to pozostaje FPGA lub bardzo zaawansowany procesor DSP. Swoją drogą nie porzucam jeszcze pomysłu, żeby cały tor zbudować w oparciu na FPGA ze względów edukacyjnych.

BTW: w projekcie na STM32 musiałem trochę zmienić parametry inicjalizacji SPI (zwiekszyć preskaler do 16).

Pozdrawiam

Edytowano przez FlyingDutch

Cześć @FlyingDutch

NIe mam zamiaru porzucić tego projektu. Ten wyświetlacz jest takim moim dziełem życia, który mam zamiar rozwijać. Stąd też kanał na YT i różne inne pomysły, żeby jakoś to dokumentować i stopniowo rozwijać. Myślę, że przejście przez STMy będzie dobrym krokiem pośrednim do FPGA.

17 minut temu, Gieneq napisał:

Cześć @FlyingDutch

NIe mam zamiaru porzucić tego projektu. Ten wyświetlacz jest takim moim dziełem życia, który mam zamiar rozwijać. Stąd też kanał na YT i różne inne pomysły, żeby jakoś to dokumentować i stopniowo rozwijać. Myślę, że przejście przez STMy będzie dobrym krokiem pośrednim do FPGA.

Cześć @Gieneq,

dzisiaj próbowałem podłączyć płytkę z przetwornikiem ADC do płytki z MCU ESP32, program do obsługi ADS1256 (Arduino IDE) skompilował się poprawnie i nie rzuca żadnych błędów wykonania, ale sczytywane sample mają wartość 0. Wiem, że płytka ADSC działa poprawnie bo podłączona do STM32F411 poprawnie sczytywała sample. Muszę ustalić co jest nie tak - podejrzewam komunikację po SPI.

Pozdrawiam

3 godziny temu, ethanak napisał:

ESP ma jeden ból - komunikacja po I2C czy SPI może paść, jeśli nie jest realizowana w głównej pętli Arduino.

@ethanak,

dzięki za podpowiedź.

Pozdrawiam

SPI może paść? Dobre sobie, ciekawe czy jest to uwzględnione w dokumentacji 😄 STMów mam pod dostatkiem, trzeba w końcu zrobić z nich dobry użytek. 

Tu się chyba objawia jak dla mnie jedna z większych zalet układów sprzętowych - determinizm, a w przypadku układów mikrokontrolerowych jego brak.

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...