Manufaktur industri
Industri Internet of Things | bahan industri | Pemeliharaan dan Perbaikan Peralatan | Pemrograman industri |
home  MfgRobots >> Manufaktur industri >  >> Industrial programming >> VHDL

Cara membuat AXI FIFO di block RAM menggunakan handshake ready/valid

Saya sedikit terganggu dengan keanehan antarmuka AXI saat pertama kali harus membuat logika untuk antarmuka modul AXI. Alih-alih sinyal kontrol sibuk/valid, penuh/valid, atau kosong/valid biasa, antarmuka AXI menggunakan dua sinyal kontrol bernama "siap" dan "valid". Rasa frustrasi saya segera berubah menjadi kekaguman.

Antarmuka AXI memiliki kontrol aliran bawaan tanpa menggunakan sinyal kontrol tambahan. Aturannya cukup mudah dipahami, tetapi ada beberapa jebakan yang harus diperhitungkan saat mengimplementasikan antarmuka AXI pada FPGA. Artikel ini menunjukkan cara membuat FIFO AXI di VHDL.

AXI memecahkan masalah delay demi satu siklus

Mencegah over-read dan overwrite adalah masalah umum saat membuat antarmuka aliran data. Masalahnya adalah ketika dua modul logika clock berkomunikasi, setiap modul hanya akan dapat membaca output dari mitranya dengan penundaan satu siklus clock.

Gambar di atas menunjukkan diagram pengaturan waktu penulisan modul sekuensial ke FIFO yang menggunakan write enable/full skema sinyal. Modul antarmuka menulis data ke FIFO dengan menyatakan wr_en sinyal. FIFO akan menegaskan full memberi sinyal saat tidak ada ruang untuk elemen data lain, yang mendorong sumber data untuk berhenti menulis.

Sayangnya, modul antarmuka tidak memiliki cara untuk menghentikan waktu selama itu hanya menggunakan logika clock. FIFO menaikkan full bendera tepat di tepi naik jam. Secara bersamaan, modul antarmuka mencoba untuk menulis elemen data berikutnya. Itu tidak dapat mengambil sampel dan bereaksi terhadap full sinyal sebelum terlambat.

Salah satu solusinya adalah dengan menyertakan almost_empty tambahan sinyal, kami melakukan ini dalam tutorial Cara membuat buffer cincin FIFO di VHDL. Sinyal tambahan mendahului empty sinyal, memberikan waktu kepada modul antarmuka untuk bereaksi.

Jabat tangan siap/valid

Protokol AXI mengimplementasikan kontrol aliran hanya menggunakan dua sinyal kontrol di setiap arah, satu disebut ready dan valid lainnya . ready sinyal dikendalikan oleh penerima, logika '1' nilai pada sinyal ini berarti penerima siap menerima item data baru. valid sinyal, di sisi lain, dikendalikan oleh pengirim. Pengirim harus mengatur valid ke '1' ketika data yang disajikan pada bus data valid untuk pengambilan sampel.

Inilah bagian pentingnya: transfer data hanya terjadi jika keduanya ready dan valid adalah '1' pada siklus jam yang sama. Penerima menginformasikan kapan siap menerima data, dan pengirim hanya meletakkan data di luar sana ketika ada sesuatu untuk dikirim. Transfer terjadi ketika keduanya setuju, ketika pengirim siap mengirim dan penerima siap menerima.

Bentuk gelombang di atas menunjukkan contoh transaksi satu item data. Pengambilan sampel terjadi pada tepi jam yang naik, seperti biasanya dengan logika clock.

Implementasi

Ada banyak cara untuk mengimplementasikan FIFO AXI di VHDL. Itu bisa berupa register geser, tetapi kami akan menggunakan struktur penyangga cincin karena ini adalah cara paling mudah untuk membuat FIFO di RAM blok. Anda dapat membuat semuanya dalam satu proses raksasa menggunakan variabel dan sinyal, atau Anda dapat membagi fungsionalitas menjadi beberapa proses.

Implementasi ini menggunakan proses terpisah untuk sebagian besar sinyal yang harus diperbarui. Hanya proses yang perlu sinkron yang sensitif terhadap jam, yang lain menggunakan logika kombinasional.

Entitas

Deklarasi entitas mencakup port generik yang digunakan untuk mengatur lebar kata-kata input dan output, serta jumlah slot untuk cadangan ruang dalam RAM. Kapasitas FIFO sama dengan kedalaman RAM minus satu. Satu slot selalu dikosongkan untuk membedakan antara FIFO penuh dan kosong.

entity axi_fifo is
  generic (
    ram_width : natural;
    ram_depth : natural
  );
  port (
    clk : in std_logic;
    rst : in std_logic;

    -- AXI input interface
    in_ready : out std_logic;
    in_valid : in std_logic;
    in_data : in std_logic_vector(ram_width - 1 downto 0);

    -- AXI output interface
    out_ready : in std_logic;
    out_valid : out std_logic;
    out_data : out std_logic_vector(ram_width - 1 downto 0)
  );
end axi_fifo; 

Dua sinyal pertama dalam deklarasi port adalah input clock dan reset. Implementasi ini menggunakan penyetelan ulang sinkron dan sensitif terhadap tepi naik jam.

Ada antarmuka input gaya AXI menggunakan sinyal kontrol siap/valid dan sinyal data input dengan lebar umum. Akhirnya muncul antarmuka keluaran AXI dengan sinyal yang sama seperti yang dimiliki masukan, hanya dengan arah terbalik. Sinyal milik antarmuka input dan output diawali dengan in_ atau out_ .

Output dari satu AXI FIFO dapat dihubungkan langsung ke input yang lain, antarmuka sangat cocok satu sama lain. Meskipun demikian, solusi yang lebih baik daripada menumpuknya adalah dengan meningkatkan ram_depth generik jika Anda menginginkan FIFO yang lebih besar.

Deklarasi sinyal

Dua pernyataan pertama di wilayah deklaratif file VHDL menyatakan tipe RAM dan sinyalnya. RAM berukuran dinamis dari input umum.

-- The FIFO is full when the RAM contains ram_depth - 1 elements
type ram_type is array (0 to ram_depth - 1)
  of std_logic_vector(in_data'range);
signal ram : ram_type;

Blok kode kedua mendeklarasikan subtipe integer baru dan empat sinyal darinya. index_type berukuran untuk mewakili persis kedalaman RAM. head sinyal selalu menunjukkan slot RAM yang akan digunakan pada operasi tulis berikutnya. tail sinyal menunjuk ke slot yang akan diakses pada operasi baca berikutnya. Nilai count sinyal selalu sama dengan jumlah elemen yang saat ini disimpan dalam FIFO, dan count_p1 adalah salinan dari sinyal yang sama yang tertunda oleh satu siklus clock.

-- Newest element at head, oldest element at tail
subtype index_type is natural range ram_type'range;
signal head : index_type;
signal tail : index_type;
signal count : index_type;
signal count_p1 : index_type;

Kemudian muncul dua sinyal bernama in_ready_i dan out_valid_i . Ini hanyalah salinan dari keluaran entitas in_ready dan out_valid . _i postfix hanya berarti internal , itu bagian dari gaya pengkodean saya.

-- Internal versions of entity signals with mode "out"
signal in_ready_i : std_logic;
signal out_valid_i : std_logic;

Akhirnya, kami mendeklarasikan sinyal yang akan digunakan untuk menunjukkan pembacaan dan penulisan simultan. Saya akan menjelaskan tujuannya nanti di artikel ini.

-- True the clock cycle after a simultaneous read and write
signal read_while_write_p1 : std_logic;

Subprogram

Setelah sinyal, kami mendeklarasikan fungsi untuk menambah index_type khusus kami . next_index fungsi melihat read dan valid parameter untuk menentukan apakah ada transaksi baca atau baca/tulis yang sedang berlangsung. Jika itu masalahnya, indeks akan bertambah atau dibungkus. Jika tidak, nilai indeks yang tidak berubah akan dikembalikan.

function next_index(
  index : index_type;
  ready : std_logic;
  valid : std_logic) return index_type is
begin
  if ready = '1' and valid = '1' then
    if index = index_type'high then
      return index_type'low;
    else
      return index + 1;
    end if;
  end if;

  return index;
end function;

Untuk menyelamatkan kami dari pengetikan berulang, kami membuat logika untuk memperbarui head dan tail sinyal dalam suatu prosedur, bukan sebagai dua proses yang identik. update_index prosedur mengambil jam dan sinyal reset, sinyal index_type , sebuah ready sinyal, dan valid sinyal sebagai input.

procedure index_proc(
  signal clk : in std_logic;
  signal rst : in std_logic;
  signal index : inout index_type;
  signal ready : in std_logic;
  signal valid : in std_logic) is
begin
    if rising_edge(clk) then
      if rst = '1' then
        index <= index_type'low;
      else
        index <= next_index(index, ready, valid);
      end if;
    end if;
end procedure;

Proses yang sepenuhnya sinkron ini menggunakan next_index berfungsi untuk memperbarui index sinyal ketika modul keluar dari reset. Saat direset, index sinyal akan disetel ke nilai terendah yang dapat diwakilinya, yang selalu 0 karena cara index_type dan ram_type dideklarasikan. Kita bisa saja menggunakan 0 sebagai nilai reset, tapi saya mencoba sebisa mungkin untuk menghindari hard-coding.

Salin sinyal internal ke output

Kedua pernyataan bersamaan ini menyalin versi internal dari sinyal output ke output aktual. Kami perlu beroperasi pada salinan internal karena VHDL tidak mengizinkan kami untuk membaca sinyal entitas dengan mode out bagian dalam modul. Alternatifnya adalah mendeklarasikan in_ready dan out_valid dengan mode inout , tetapi sebagian besar standar pengkodean perusahaan membatasi penggunaan inout sinyal entitas.

in_ready <= in_ready_i;
out_valid <= out_valid_i;

Perbarui kepala dan ekor

Kita telah membahas index_proc prosedur yang digunakan untuk memperbarui head dan tail sinyal. Dengan memetakan sinyal yang sesuai ke parameter subprogram ini, kita mendapatkan persamaan dari dua proses yang identik, satu untuk mengontrol input FIFO dan satu untuk output.

-- Update head index on write
PROC_HEAD : index_proc(clk, rst, head, in_ready_i, in_valid);

-- Update tail index on read
PROC_TAIL : index_proc(clk, rst, tail, out_ready, out_valid_i);

Karena keduanya head dan tail diatur ke nilai yang sama dengan logika reset, FIFO awalnya akan kosong. Begitulah cara kerja ring buffer ini, ketika keduanya menunjuk ke indeks yang sama berarti FIFO-nya kosong.

Simpulkan RAM blok

Di sebagian besar arsitektur FPGA, primitif blok RAM adalah komponen yang sepenuhnya sinkron. Ini berarti bahwa jika kita ingin alat sintesis menyimpulkan RAM blok dari kode VHDL kita, kita perlu menempatkan port baca dan tulis di dalam proses clock. Selain itu, tidak ada nilai reset yang terkait dengan RAM blok.

PROC_RAM : process(clk)
begin
  if rising_edge(clk) then
    ram(head) <= in_data;
    out_data <= ram(next_index(tail, out_ready, out_valid_i));
  end if;
end process;

Tidak ada baca aktifkan atau aktifkan tulis di sini, itu akan terlalu lambat untuk AXI. Sebagai gantinya, kami terus menulis ke slot RAM yang ditunjukkan oleh head indeks. Kemudian, ketika kami menentukan bahwa transaksi tulis telah terjadi, kami cukup memajukan head untuk mengunci nilai tertulis.

Demikian juga, out_data diperbarui pada setiap siklus jam. tail pointer hanya bergerak ke slot berikutnya ketika membaca terjadi. Perhatikan bahwa next_index fungsi yang digunakan untuk menghitung alamat untuk port baca. Kita harus melakukan ini untuk memastikan bahwa RAM bereaksi cukup cepat setelah membaca dan mulai mengeluarkan nilai berikutnya.

Hitung jumlah elemen dalam FIFO

Menghitung jumlah elemen dalam RAM hanyalah masalah mengurangkan head dari tail . Jika head telah dibungkus, kita harus mengimbanginya dengan jumlah total slot di RAM. Kami memiliki akses ke informasi ini melalui ram_depth konstan dari input generik.

PROC_COUNT : process(head, tail)
begin
  if head < tail then
    count <= head - tail + ram_depth;
  else
    count <= head - tail;
  end if;
end process;

Kita juga perlu melacak nilai sebelumnya dari count sinyal. Proses di bawah ini membuat versi yang tertunda oleh satu siklus clock. _p1 postfix adalah konvensi penamaan untuk menunjukkan hal ini.

PROC_COUNT_P1 : process(clk)
begin
  if rising_edge(clk) then
    if rst = '1' then
      count_p1 <= 0;
    else
      count_p1 <= count;
    end if;
  end if;
end process;

Perbarui siap keluaran

in_ready sinyal harus '1' ketika modul ini siap menerima item data lain. Ini seharusnya terjadi selama FIFO tidak penuh, dan itulah yang dikatakan oleh logika proses ini.

PROC_IN_READY : process(count)
begin
  if count < ram_depth - 1 then
    in_ready_i <= '1';
  else
    in_ready_i <= '0';
  end if;
end process;

Mendeteksi membaca dan menulis secara bersamaan

Karena kasus sudut yang akan saya jelaskan di bagian selanjutnya, kita harus mampu mengidentifikasi operasi baca dan tulis secara simultan. Setiap kali ada transaksi baca dan tulis yang valid selama siklus clock yang sama, proses ini akan menyetel read_while_write_p1 sinyal ke '1' pada siklus jam berikut.

PROC_READ_WHILE_WRITE_P1: process(clk)
begin
  if rising_edge(clk) then
    if rst = '1' then
      read_while_write_p1 <= '0';

    else
      read_while_write_p1 <= '0';
      if in_ready_i = '1' and in_valid = '1' and
        out_ready = '1' and out_valid_i = '1' then
        read_while_write_p1 <= '1';
      end if;
    end if;
  end if;
end process;

Perbarui yang valid keluaran

out_valid sinyal menunjukkan ke modul hilir bahwa data disajikan pada out_data valid dan dapat diambil sampelnya kapan saja. out_data sinyal datang langsung dari output RAM. Menerapkan out_valid sinyal sedikit rumit karena penundaan siklus clock ekstra antara input dan output blok RAM.

Logika diimplementasikan dalam proses kombinasi sehingga dapat bereaksi tanpa penundaan terhadap sinyal input yang berubah. Baris pertama dari proses adalah nilai default yang menetapkan out_valid sinyal ke '1' . Ini akan menjadi nilai yang berlaku jika tidak satu pun dari dua pernyataan If berikutnya yang dipicu.

PROC_OUT_VALID : process(count, count_p1, read_while_write_p1)
begin
  out_valid_i <= '1';

  -- If the RAM is empty or was empty in the prev cycle
  if count = 0 or count_p1 = 0 then
    out_valid_i <= '0';
  end if;

  -- If simultaneous read and write when almost empty
  if count = 1 and read_while_write_p1 = '1' then
    out_valid_i <= '0';
  end if;

end process;

Pernyataan If pertama memeriksa apakah FIFO kosong atau kosong pada siklus clock sebelumnya. Jelas, FIFO kosong ketika ada 0 elemen di dalamnya, tetapi kita juga perlu memeriksa tingkat pengisian FIFO pada siklus jam sebelumnya.

Perhatikan bentuk gelombang di bawah ini. Awalnya, FIFO kosong, seperti yang dilambangkan dengan count sinyal menjadi 0 . Kemudian, penulisan terjadi pada siklus clock ketiga. Slot RAM 0 diperbarui pada siklus clock berikutnya, tetapi dibutuhkan siklus tambahan sebelum data muncul di out_data keluaran. Tujuan dari or count_p1 = 0 pernyataan adalah untuk memastikan bahwa out_valid tetap '0' (dilingkari merah) saat nilai menyebar melalui RAM.

Pernyataan If terakhir melindungi terhadap kasus sudut lainnya. Kami baru saja berbicara tentang bagaimana menangani kasus khusus dari write-on-empty dengan memeriksa tingkat pengisian FIFO saat ini dan sebelumnya. Tapi apa yang terjadi jika dan kita melakukan pembacaan dan penulisan simultan ketika count sudah 1 ?

Bentuk gelombang di bawah ini menunjukkan situasi seperti itu. Awalnya, ada satu item data D0 yang ada di FIFO. Sudah ada di sana untuk sementara waktu, jadi keduanya count dan count_p1 adalah 0 . Kemudian pembacaan dan penulisan simultan muncul dalam siklus jam ketiga. Satu item meninggalkan FIFO dan yang baru masuk, membuat penghitung tidak berubah.

Pada saat membaca dan menulis, tidak ada nilai berikutnya dalam RAM yang siap untuk dikeluarkan, seperti yang akan terjadi jika tingkat pengisian lebih tinggi dari satu. Kita harus menunggu dua siklus clock sebelum nilai input muncul pada output. Tanpa informasi tambahan, tidak mungkin untuk mendeteksi kasus sudut ini, dan nilai out_valid pada siklus jam berikut (ditandai sebagai merah solid) akan salah diatur ke '1' .

Itu sebabnya kami membutuhkan read_while_write_p1 sinyal. Ini mendeteksi bahwa telah terjadi pembacaan dan penulisan secara simultan, dan kita dapat memperhitungkannya dengan menyetel out_valid ke '0' dalam siklus jam itu.

Sintesis di Vivado

Untuk mengimplementasikan desain sebagai modul yang berdiri sendiri di Xilinx Vivado, pertama-tama kita harus memberikan nilai pada input generik. Ini dapat dicapai di Vivado dengan menggunakan PengaturanUmumGenerik/Parameter menu, seperti yang ditunjukkan pada gambar di bawah.

Nilai generik telah dipilih untuk mencocokkan primitif RAMB36E1 dalam arsitektur Xilinx Zynq yang merupakan perangkat target. Penggunaan sumber daya pasca-implementasi ditunjukkan pada gambar di bawah ini. FIFO AXI menggunakan satu blok RAM dan sejumlah kecil LUT dan flip-flop.

AXI lebih dari ready/valid

AXI adalah singkatan dari Advanced eXtensible Interface, ini adalah bagian dari standar Arsitektur Bus Mikrokontroler Lanjutan (AMBA) ARM. Standar AXI lebih dari sekadar jabat tangan baca/valid. Jika Anda ingin tahu lebih banyak tentang AXI, saya merekomendasikan referensi berikut untuk bacaan lebih lanjut:


VHDL

  1. Cloud dan Bagaimana Mengubah Dunia TI
  2. Cara memaksimalkan data Anda
  3. Cara menginisialisasi RAM dari file menggunakan TEXTIO
  4. Bagaimana Anda bersiap untuk AI menggunakan IoT
  5. Bagaimana Internet Industri Mengubah Manajemen Aset
  6. Praktik terbaik pelacakan aset:Cara memaksimalkan data aset yang diperoleh dengan susah payah
  7. Bagaimana kita mendapatkan gambaran yang lebih baik tentang IoT?
  8. Cara memaksimalkan IoT dalam bisnis restoran
  9. Bagaimana Data Mengaktifkan Rantai Pasokan Masa Depan
  10. Cara Membuat Data Rantai Pasokan Terpercaya