Implementasi AXI FIFO di Block RAM dengan Handshake Ready/Valid
Saat pertama kali saya menemui antarmuka AXI saat merancang modul, saya terkejut dengan pendekatan kontrol aliran yang efisien. Berbeda dengan protokol tradisional yang bergantung pada pasangan busy/valid, full/valid, atau empty/valid, AXI hanya menggunakan dua sinyal: ready dan valid. Apa yang dimulai sebagai kegelisahan dengan cepat menjadi kesukaran.
AXI menyematkan kontrol aliran langsung dalam jabatannya, menghilangkan kebutuhan akan baris status tambahan. Sementara aturan dasarnya sederhana, perhatian hati-hati diperlukan saat memetakan protokol ke perangkat keras FPGA. Dalam tutorial ini, saya memandu Anda melalui implementasi VHDL dari AXI FIFO yang berjalan di block RAM.
AXI memecahkan masalah tertunda satu siklus
Mencegah over-read dan overwrite adalah tantangan umum ketika dua modul yang disinkronkan clock bertukar aliran data. Inti masalahnya adalah setiap modul hanya dapat mengamati output modul lain satu siklus clock setelah terjadi.
Diagram di atas menggambarkan modul sekuensial yang menulis ke FIFO yang menggunakan skema sinyal klasik write enable/full. Penulis mengajukan wr_en untuk mendorong data ke FIFO, sementara FIFO mengajukan full ketika tidak ada ruang lagi yang tersedia. Masalahnya adalah full diajukan tepat pada tepi naik clock, jadi penulis tidak lagi dapat bereaksi padanya dalam siklus yang sama. Solusinya adalah memberikan sinyal look-ahead seperti almost_empty, memberikan logika upstream awal.
Jatuh tangan ready/valid
Antarmuka aliran AXI memecahkan dilema waktu ini dengan jabat dua sinyal: ready dan valid. Penerima mengajukan ready; nilai tinggi menunjukkan bahwa ia siap menerima datum baru. Pengirim mengajukan valid; nilai tinggi berarti data pada bus stabil dan dapat diambil. Transfer hanya terjadi ketika kedua sinyal tinggi pada tepi naik yang sama.
Gelombang menunjukkan transfer satu item data. Data diambil pada tepi naik clock, sama seperti dalam desain sinkron konvensional.
Implementasi
Ada beberapa cara untuk membangun AXI FIFO dalam VHDL. Sementara shift register akan bekerja, ring buffer yang didukung block RAM adalah pilihan paling efisien dan scalable. Di bawah ini, saya menyajikan desain modular yang memisahkan logika ke dalam proses terpadu untuk kejelasan dan ramah sintesis.
Entitas
Entitas menentukan dua parameter generik – ram_width dan ram_depth – yang menetapkan lebar kata dan kedalaman FIFO. Kapasitas efektif adalah ram_depth - 1, karena satu slot dicadangkan untuk membedakan FIFO penuh dari FIFO 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;
Antarmuka mengikuti konvensi AXI: sisi input menggunakan in_ready dan in_valid, sementara sisi output mencerminkan sinyal dengan arah yang terbalik. Simetri ini memungkinkan beberapa FIFO dirantai dengan lancar.
Deklarasi Sinyal
Pertama, kita mendefinisikan tipe RAM yang cocok dengan lebar dan kedalaman generik, lalu kita membuat instance array:
-- 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;
Selanjutnya, kita mendeklarasikan subtype indeks yang dapat mewakili alamat apa pun dalam RAM, dan empat sinyal inti: head, tail, count, dan salinan tertunda count_p1:
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;
Kami juga menyimpan versi internal siaran ready dan valid untuk menghindari membaca sinyal out di dalam arsitektur:
signal in_ready_i : std_logic; signal out_valid_i : std_logic;
Sinyal pembantu melacak saat pembacaan dan penulisan bersamaan terjadi dalam siklus sebelumnya, yang penting untuk menyetel out_valid dengan benar ketika FIFO hampir kosong:
signal read_while_write_p1 : std_logic;
Subprogram
Fungsi next_index menentukan alamat berikutnya berdasarkan apakah jabat baca atau tulis baru saja selesai. Ia membungkus ketika akhir array tercapai.
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;
Prosedur index_proc mengemas logika sinkron yang memperbarui pointer head atau tail. Ini menghindari duplikasi kode dan menjaga arsitektur tetap rapi.
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;
Salin Sinyal Internal ke Output
in_ready <= in_ready_i; out_valid <= out_valid_i;
Perbarui Head dan Tail
Kami membuat instance prosedur pembantu dua kali – sekali untuk pointer tulis (head) dan sekali untuk pointer baca (tail):
Block RAM di sebagian besar FPGA adalah sinkron. Untuk memungkinkan sintesis menyimpulkan primitif RAM, kita menjaga operasi baca dan tulis di dalam proses terkoordinasi dan menghindari sinyal enable eksplisit:
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;
Tulis terjadi tanpa syarat pada siklus ketika in_valid tinggi, dan data baca diperbarui setiap siklus sehingga konsumen dapat mengamati kata yang valid setiap kali FIFO berisi data.
Hitung Jumlah Elemen dalam FIFO
Sinyal count mencerminkan kepaduan saat ini. Ketika head di depan tail, jumlahnya adalah selisihnya; jika head telah membungkus, kita tambahkan kedalaman total:
PROC_COUNT : process(head, tail) begin if head < tail then count <= head - tail + ram_depth; else count <= head - tail; end if; end process;
Salinan tertunda count_p1 diperlukan untuk mendeteksi ketika FIFO kosong dalam siklus sebelumnya, yang mempengaruhi out_valid:
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 Output Ready
Penulis harus diberitahu kapan FIFO dapat menerima lebih banyak data. Sinyal ready tinggi setiap kali jumlahnya kurang dari ram_depth - 1:
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;
Deteksi Baca dan Tulis Bersamaan
Ketika baca dan tulis terjadi dalam siklus yang sama, FIFO tidak mengubah kepaduan tetapi keadaan internal harus mencerminkan bahwa nilai akan tersedia hanya setelah dua siklus ketika FIFO hampir kosong. Oleh karena itu kita menangkap peristiwa ini di read_while_write_p1:
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 Output Valid
Sinyal out_valid memberitahu logika downstream kapan out_data menyimpan kata yang dapat digunakan. Proses kombinatorial di bawah ini menyetelnya tinggi secara default tetapi menghapusnya dalam dua kasus sudut:
PROC_OUT_VALID : process(count, count_p1, read_while_write_p1) begin out_valid_i <= '1'; -- Jika FIFO kosong atau kosong dalam siklus sebelumnya if count = 0 or count_p1 = 0 then out_valid_i <= '0'; end if; -- Jika baca dan tulis bersamaan terjadi ketika FIFO hanya memiliki satu item if count = 1 and read_while_write_p1 = '1' then out_valid_i <= '0'; end if; end process;
Kondisi pertama menjamin bahwa out_valid tetap rendah selama penundaan propagasi setelah tulis pertama. Kedua melindungi dari situasi di mana FIFO tampaknya tetap penuh untuk satu siklus tetapi sebenarnya tidak menyimpan data untuk dioutput.
Mensintesis di Vivado
Untuk mensintesis desain ini di Xilinx Vivado, berikan nilai konkret untuk generik. Dalam dialog Settings (General → Generics/Parameters), pilih kedalaman yang cocok dengan primitif RAMB36E1 untuk perangkat Zynq target. Setelah implementasi, penggunaan sumber daya cukup sederhana – satu block RAM plus beberapa LUT dan FF.
AXI lebih dari sekedar ready/valid
AXI (Advanced eXtensible Interface) adalah komponen dari arsitektur bus ARM AMBA dan menawarkan serangkaian fitur yang kaya di luar jabat sederhana ready/valid. Untuk pemahaman yang lebih dalam, pertimbangkan sumber daya berikut: