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
Link to post
Share on other sites
(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
Link to post
Share on other sites
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
Link to post
Share on other sites
(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
Link to post
Share on other sites
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

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
Link to post
Share on other sites

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?

Link to post
Share on other sites
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

Link to post
Share on other sites
(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
Link to post
Share on other sites

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
Link to post
Share on other sites
(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
Link to post
Share on other sites

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.

Link to post
Share on other sites
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

Link to post
Share on other sites

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

  • Pomogłeś! 1
Link to post
Share on other sites
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

Link to post
Share on other sites

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.

Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.