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

Cara membuat penyangga cincin FIFO di VHDL

Buffer melingkar adalah konstruksi populer untuk membuat antrian dalam bahasa pemrograman sekuensial, tetapi mereka juga dapat diimplementasikan dalam perangkat keras. Pada artikel ini, kita akan membuat ring buffer di VHDL untuk mengimplementasikan FIFO di block RAM.

Ada banyak keputusan desain yang harus Anda buat saat menerapkan FIFO. Jenis antarmuka apa yang Anda butuhkan? Apakah Anda dibatasi oleh sumber daya? Haruskah itu tahan terhadap over-read dan overwrite? Apakah latensi dapat diterima? Itulah beberapa pertanyaan yang muncul di benak saya ketika diminta untuk membuat FIFO.

Ada banyak implementasi FIFO online gratis, serta generator FIFO seperti Xilinx LogiCORE. Tapi tetap saja, banyak insinyur lebih suka menerapkan FIFO mereka sendiri. Karena meskipun mereka semua melakukan tugas antrian dan dequeue dasar yang sama, mereka bisa sangat berbeda saat mempertimbangkan detailnya.

Cara kerja penyangga cincin

Buffer cincin adalah implementasi FIFO yang menggunakan memori yang berdekatan untuk menyimpan data buffer dengan pengocokan data minimum. Elemen baru tetap berada di lokasi memori yang sama sejak saat penulisan hingga dibaca dan dihapus dari FIFO.

Dua penghitung digunakan untuk melacak lokasi dan jumlah elemen dalam FIFO. Penghitung ini mengacu pada offset dari awal ruang memori tempat data disimpan. Dalam VHDL, ini akan menjadi indeks ke sel array. Untuk sisa artikel ini, kita akan merujuk ke counter ini pointer .

Kedua pointer ini adalah head dan ekor petunjuk. Kepala selalu menunjuk ke slot memori yang akan berisi data tertulis berikutnya, sedangkan ekor mengacu pada elemen berikutnya yang akan dibaca dari FIFO. Ada varian lain, tapi ini yang akan kita gunakan.

Status kosong

Jika kepala dan ekor menunjuk ke elemen yang sama, itu berarti FIFO kosong. Gambar di atas menunjukkan contoh FIFO dengan delapan slot. Baik penunjuk kepala dan penunjuk ekor menunjuk ke elemen 0, menunjukkan bahwa FIFO kosong. Ini adalah keadaan awal buffer cincin.

Perhatikan bahwa FIFO akan tetap kosong jika kedua penunjuk berada pada indeks lain, misalnya, 3. Untuk setiap penulisan, penunjuk kepala bergerak satu tempat ke depan. Penunjuk ekor bertambah setiap kali pengguna FIFO membaca sebuah elemen.

Ketika salah satu pointer berada pada indeks tertinggi, penulisan atau pembacaan berikutnya akan menyebabkan pointer kembali ke indeks terendah. Inilah keindahan ring buffer, data tidak bergerak, hanya pointer yang bergerak.

Kepala memimpin ekor

Gambar di atas menunjukkan buffer cincin yang sama setelah lima kali menulis. Penunjuk ekor masih di slot nomor 0, tetapi penunjuk kepala telah pindah ke slot nomor 5. Slot yang berisi data berwarna biru muda pada ilustrasi. Penunjuk ekor akan berada di elemen tertua, sedangkan kepala menunjuk ke slot kosong berikutnya.

Ketika kepala memiliki indeks lebih tinggi daripada ekor, kita dapat menghitung jumlah elemen dalam penyangga cincin dengan mengurangkan ekor dari kepala. Pada gambar di atas, itu menghasilkan hitungan lima elemen.

Kepala ujung ekor

Mengurangi kepala dari ekor hanya berhasil jika kepala memimpin ekor. Pada gambar di atas, kepala berada pada indeks 2 sedangkan ekor berada pada indeks 5. Jadi, jika kita melakukan perhitungan sederhana ini, kita mendapatkan 2 – 5 =-3, yang tidak masuk akal.

Solusinya adalah untuk mengimbangi kepala dengan jumlah total slot di FIFO, 8 dalam kasus ini. Perhitungan sekarang menghasilkan (2 + 8) – 5 =5, yang merupakan jawaban yang benar.

Ekor akan selamanya mengejar kepala, begitulah cara kerja penyangga cincin. Setengah dari waktu ekor akan memiliki indeks lebih tinggi dari kepala. Data disimpan di antara keduanya, seperti yang ditunjukkan oleh warna biru muda pada gambar di atas.

Kondisi penuh

Buffer cincin penuh akan memiliki ekor yang menunjuk ke indeks langsung setelah kepala. Konsekuensi dari skema ini adalah kita tidak akan pernah bisa menggunakan semua slot untuk menyimpan data, setidaknya harus ada satu slot kosong. Gambar di atas menunjukkan situasi dimana ring buffer penuh. Slot yang terbuka, tetapi tidak dapat digunakan, berwarna kuning.

Sinyal kosong/penuh khusus juga dapat digunakan untuk menunjukkan bahwa buffer cincin sudah penuh. Ini akan memungkinkan semua slot memori untuk menyimpan data, tetapi memerlukan logika tambahan dalam bentuk register dan tabel pencarian (LUT). Oleh karena itu, kita akan menggunakan tetap buka skema implementasi ring buffer FIFO kami, karena ini hanya menghabiskan RAM blok yang lebih murah.

Implementasi FIFO ring buffer

Bagaimana Anda menentukan sinyal antarmuka ke dan dari FIFO Anda akan membatasi jumlah kemungkinan implementasi buffer cincin Anda. Dalam contoh kita, kita akan menggunakan variasi dari antarmuka baca/tulis klasik dan antarmuka kosong/penuh/valid.

Akan ada tulis data bus di sisi input yang membawa data untuk didorong ke FIFO. Juga akan ada aktifkan tulis sinyal, yang bila ditegaskan, akan menyebabkan FIFO mengambil sampel data input.

Sisi keluaran akan memiliki baca data dan membaca valid sinyal dikendalikan oleh FIFO. Ini juga akan memiliki mengaktifkan baca sinyal dikendalikan oleh pengguna hilir FIFO.

kosong dan penuh sinyal kontrol adalah bagian dari antarmuka FIFO klasik, kami juga akan menggunakannya. Mereka dikendalikan oleh FIFO, dan tujuannya adalah untuk mengkomunikasikan keadaan FIFO kepada pembaca dan penulis.

Tekanan balik

Masalah dengan menunggu sampai FIFO kosong atau penuh sebelum mengambil tindakan adalah bahwa logika antarmuka tidak akan punya waktu untuk bereaksi. Logika sekuensial bekerja berdasarkan siklus jam ke siklus jam, tepi naik jam secara efektif memisahkan peristiwa dalam desain Anda menjadi langkah waktu.

Salah satu solusinya adalah memasukkan hampir kosong dan hampir penuh sinyal yang mendahului sinyal asli dengan satu siklus clock. Ini memberikan waktu bagi logika eksternal untuk bereaksi, bahkan ketika membaca atau menulis terus menerus.

Dalam implementasi kami, sinyal sebelumnya akan diberi nama empty_next dan full_next , hanya karena saya lebih suka menggunakan nama postfix daripada nama awalan.

Entitas

Gambar di bawah ini menunjukkan entitas FIFO ring buffer kami. Selain sinyal input dan output di port, ia memiliki dua konstanta generik. RAM_WIDTH generik mendefinisikan jumlah bit dalam kata-kata input dan output, jumlah bit setiap slot memori akan berisi.

RAM_DEPTH generik mendefinisikan jumlah slot yang akan disediakan untuk buffer cincin. Karena satu slot dicadangkan untuk menunjukkan bahwa buffer cincin sudah penuh, kapasitas FIFO akan menjadi RAM_DEPTH – 1. Kode RAM_DEPTH konstanta harus dicocokkan dengan kedalaman RAM pada FPGA target. RAM yang tidak digunakan dalam satu blok RAM primitif akan terbuang sia-sia, tidak dapat dibagi dengan logika lain di FPGA.

entity ring_buffer is
  generic (
    RAM_WIDTH : natural;
    RAM_DEPTH : natural
  );
  port (
    clk : in std_logic;
    rst : in std_logic;

    -- Write port
    wr_en : in std_logic;
    wr_data : in std_logic_vector(RAM_WIDTH - 1 downto 0);

    -- Read port
    rd_en : in std_logic;
    rd_valid : out std_logic;
    rd_data : out std_logic_vector(RAM_WIDTH - 1 downto 0);

    -- Flags
    empty : out std_logic;
    empty_next : out std_logic;
    full : out std_logic;
    full_next : out std_logic;

    -- The number of elements in the FIFO
    fill_count : out integer range RAM_DEPTH - 1 downto 0
  );
end ring_buffer;

Selain jam dan reset, deklarasi port akan menyertakan data klasik/mengaktifkan port baca dan tulis. Ini digunakan oleh modul upstream dan downstream untuk mendorong data baru ke FIFO, dan untuk mengeluarkan elemen tertua darinya.

rd_valid sinyal ditegaskan oleh FIFO ketika rd_data port berisi data yang valid. Peristiwa ini tertunda satu siklus clock setelah pulsa pada rd_en sinyal. Kami akan berbicara lebih banyak tentang mengapa harus seperti ini di akhir artikel ini.

Kemudian muncul bendera kosong/penuh yang ditetapkan oleh FIFO. empty_next sinyal akan ditegaskan ketika ada 1 atau 0 elemen yang tersisa, sementara empty hanya aktif ketika ada 0 elemen di FIFO. Demikian pula, full_next sinyal akan menunjukkan bahwa ada ruang untuk 1 atau 0 elemen lebih, sementara full hanya menegaskan ketika FIFO tidak dapat mengakomodasi elemen data lain.

Terakhir, ada fill_count keluaran. Ini adalah bilangan bulat yang akan mencerminkan jumlah elemen yang saat ini disimpan di FIFO. Saya telah menyertakan sinyal keluaran ini hanya karena kita akan menggunakannya secara internal di dalam modul. Memecahnya melalui entitas pada dasarnya gratis, dan pengguna dapat memilih untuk membiarkan sinyal ini tidak terhubung saat membuat instance modul ini.

Wilayah deklaratif

Di wilayah deklaratif file VHDL, kami akan mendeklarasikan tipe kustom, subtipe, sejumlah sinyal, dan prosedur untuk penggunaan internal dalam modul buffer cincin.

  type ram_type is array (0 to RAM_DEPTH - 1) of
    std_logic_vector(wr_data'range);
  signal ram : ram_type;

  subtype index_type is integer range ram_type'range;
  signal head : index_type;
  signal tail : index_type;

  signal empty_i : std_logic;
  signal full_i : std_logic;
  signal fill_count_i : integer range RAM_DEPTH - 1 downto 0;

  -- Increment and wrap
  procedure incr(signal index : inout index_type) is
  begin
    if index = index_type'high then
      index <= index_type'low;
    else
      index <= index + 1;
    end if;
  end procedure;

Pertama, kami mendeklarasikan tipe baru untuk memodelkan RAM kami. ram_type type adalah array vektor, berukuran dengan input generik. Tipe baru digunakan pada baris berikutnya untuk mendeklarasikan ram sinyal yang akan menyimpan data dalam buffer cincin.

Di blok kode berikutnya, kami mendeklarasikan index_type , subtipe bilangan bulat. Jangkauannya akan secara tidak langsung diatur oleh RAM_DEPTH umum. Di bawah deklarasi subtipe, kami menggunakan tipe indeks untuk mendeklarasikan dua sinyal baru, penunjuk kepala dan ekor.

Kemudian mengikuti blok deklarasi sinyal yang merupakan salinan internal dari sinyal entitas. Mereka memiliki nama dasar yang sama dengan sinyal entitas tetapi diberi postfix dengan _i untuk menunjukkan bahwa mereka adalah untuk penggunaan internal. Kami menggunakan pendekatan ini karena dianggap gaya yang buruk untuk menggunakan inout mode pada sinyal entitas, meskipun ini akan memiliki efek yang sama.

Akhirnya, kami mendeklarasikan prosedur bernama incr yang mengambil index_type sinyal sebagai parameter. Subprogram ini akan digunakan untuk menambah pointer kepala dan ekor, dan membungkusnya kembali ke 0 ketika mereka berada pada nilai tertinggi. Kepala dan ekor adalah subtipe bilangan bulat, yang biasanya tidak mendukung perilaku pembungkusan. Kami akan menggunakan prosedur untuk menghindari masalah ini.

Pernyataan bersamaan

Di bagian atas arsitektur, kami mendeklarasikan pernyataan bersamaan kami. Saya lebih suka mengumpulkan tugas sinyal satu baris ini sebelum proses normal karena mudah diabaikan. Pernyataan konkuren sebenarnya adalah suatu bentuk proses, Anda dapat membaca lebih lanjut tentang pernyataan konkuren di sini:

Cara membuat Pernyataan Bersamaan di VHDL

  -- Copy internal signals to output
  empty <= empty_i;
  full <= full_i;
  fill_count <= fill_count_i;

  -- Set the flags
  empty_i <= '1' when fill_count_i = 0 else '0';
  empty_next <= '1' when fill_count_i <= 1 else '0';
  full_i <= '1' when fill_count_i >= RAM_DEPTH - 1 else '0';
  full_next <= '1' when fill_count_i >= RAM_DEPTH - 2 else '0';

Di blok pertama penugasan bersamaan, kami menyalin versi internal dari sinyal entitas ke output. Baris-baris ini akan memastikan bahwa sinyal entitas mengikuti versi internal pada waktu yang sama persis, tetapi dengan satu penundaan siklus delta dalam simulasi.

Blok kedua dan terakhir dari pernyataan bersamaan adalah tempat kami menetapkan flag output, menandakan status buffer cincin penuh/kosong. Kami mendasarkan perhitungan pada RAM_DEPTH generik dan pada fill_count sinyal. Kedalaman RAM adalah konstanta yang tidak akan berubah. Oleh karena itu, tanda hanya akan berubah sebagai akibat dari jumlah pengisian yang diperbarui.

Memperbarui penunjuk kepala

Fungsi dasar dari penunjuk kepala adalah untuk menambah setiap kali sinyal pengaktifan tulis ditegaskan dari luar modul ini. Kami melakukan ini dengan meneruskan head sinyal ke incr yang disebutkan sebelumnya prosedur.

  PROC_HEAD : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        head <= 0;
      else

        if wr_en = '1' and full_i = '0' then
          incr(head);
        end if;

      end if;
    end if;
  end process;

Kode kami berisi and full_i = '0' tambahan pernyataan untuk melindungi dari penimpaan. Logika ini dapat dihilangkan jika Anda yakin bahwa sumber data tidak akan pernah mencoba menulis ke FIFO saat penuh. Tanpa perlindungan ini, penimpaan akan menyebabkan buffer cincin menjadi kosong kembali.

Jika penunjuk kepala bertambah saat penyangga cincin penuh, kepala akan menunjuk ke elemen yang sama dengan ekor. Dengan demikian, modul akan “melupakan” data yang ada, dan isian FIFO tampak kosong.

Dengan mengevaluasi full_i sinyal sebelum menambah penunjuk kepala, itu hanya akan melupakan nilai yang ditimpa. Saya pikir solusi ini lebih baik. Namun bagaimanapun juga, jika penimpaan pernah terjadi, ini menunjukkan kegagalan fungsi di luar modul ini.

Memperbarui penunjuk ekor

Penunjuk ekor dinaikkan dengan cara yang sama seperti penunjuk kepala, tetapi read_en input digunakan sebagai pemicu. Sama seperti penimpaan, kami melindungi dari pembacaan berlebihan dengan menyertakan and empty_i = '0' dalam ekspresi Boolean.

  PROC_TAIL : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        tail <= 0;
        rd_valid <= '0';
      else
        rd_valid <= '0';

        if rd_en = '1' and empty_i = '0' then
          incr(tail);
          rd_valid <= '1';
        end if;

      end if;
    end if;
  end process;

Selain itu, kami menampilkan rd_valid sinyal pada setiap pembacaan yang valid. Data yang dibaca selalu valid pada siklus jam setelah rd_en ditegaskan, jika FIFO tidak kosong. Dengan pengetahuan ini, sebenarnya tidak ada kebutuhan untuk sinyal ini, tetapi kami akan memasukkannya untuk kenyamanan. rd_valid sinyal akan dioptimalkan dalam sintesis jika dibiarkan tidak terhubung saat modul dipakai.

Menyimpulkan blok RAM

Untuk membuat alat sintesis menyimpulkan blok RAM, kita harus mendeklarasikan port baca dan tulis dalam proses sinkron tanpa reset. Kami akan membaca dan menulis ke RAM pada setiap siklus clock, dan membiarkan sinyal kontrol menangani penggunaan data ini.

  PROC_RAM : process(clk)
  begin
    if rising_edge(clk) then
      ram(head) <= wr_data;
      rd_data <= ram(tail);
    end if;
  end process;

Proses ini tidak tahu kapan penulisan berikutnya akan terjadi, tetapi tidak perlu diketahui. Sebaliknya, kami hanya menulis terus menerus. Ketika head sinyal bertambah sebagai hasil dari penulisan, kami mulai menulis ke slot berikutnya. Ini akan secara efektif mengunci nilai yang ditulis.

Memperbarui jumlah pengisian

fill_count sinyal digunakan untuk menghasilkan sinyal penuh dan kosong, yang pada gilirannya digunakan untuk mencegah penimpaan dan pembacaan berlebihan FIFO. Penghitung pengisian diperbarui dengan proses kombinasional yang sensitif terhadap penunjuk kepala dan ekor, tetapi sinyal tersebut hanya diperbarui pada tepi naik jam. Oleh karena itu, jumlah pengisian juga akan berubah segera setelah tepi jam.

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

Hitungan pengisian dihitung hanya dengan mengurangkan ekor dari kepala. Jika indeks ekor lebih besar dari kepala, kita harus menambahkan nilai RAM_DEPTH konstan untuk mendapatkan jumlah elemen yang benar yang saat ini ada di buffer ring.

Kode VHDL lengkap untuk ring buffer FIFO

library ieee;
use ieee.std_logic_1164.all;

entity ring_buffer is
  generic (
    RAM_WIDTH : natural;
    RAM_DEPTH : natural
  );
  port (
    clk : in std_logic;
    rst : in std_logic;

    -- Write port
    wr_en : in std_logic;
    wr_data : in std_logic_vector(RAM_WIDTH - 1 downto 0);

    -- Read port
    rd_en : in std_logic;
    rd_valid : out std_logic;
    rd_data : out std_logic_vector(RAM_WIDTH - 1 downto 0);

    -- Flags
    empty : out std_logic;
    empty_next : out std_logic;
    full : out std_logic;
    full_next : out std_logic;

    -- The number of elements in the FIFO
    fill_count : out integer range RAM_DEPTH - 1 downto 0
  );
end ring_buffer;

architecture rtl of ring_buffer is

  type ram_type is array (0 to RAM_DEPTH - 1) of
    std_logic_vector(wr_data'range);
  signal ram : ram_type;

  subtype index_type is integer range ram_type'range;
  signal head : index_type;
  signal tail : index_type;

  signal empty_i : std_logic;
  signal full_i : std_logic;
  signal fill_count_i : integer range RAM_DEPTH - 1 downto 0;

  -- Increment and wrap
  procedure incr(signal index : inout index_type) is
  begin
    if index = index_type'high then
      index <= index_type'low;
    else
      index <= index + 1;
    end if;
  end procedure;

begin

  -- Copy internal signals to output
  empty <= empty_i;
  full <= full_i;
  fill_count <= fill_count_i;

  -- Set the flags
  empty_i <= '1' when fill_count_i = 0 else '0';
  empty_next <= '1' when fill_count_i <= 1 else '0';
  full_i <= '1' when fill_count_i >= RAM_DEPTH - 1 else '0';
  full_next <= '1' when fill_count_i >= RAM_DEPTH - 2 else '0';

  -- Update the head pointer in write
  PROC_HEAD : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        head <= 0;
      else

        if wr_en = '1' and full_i = '0' then
          incr(head);
        end if;

      end if;
    end if;
  end process;

  -- Update the tail pointer on read and pulse valid
  PROC_TAIL : process(clk)
  begin
    if rising_edge(clk) then
      if rst = '1' then
        tail <= 0;
        rd_valid <= '0';
      else
        rd_valid <= '0';

        if rd_en = '1' and empty_i = '0' then
          incr(tail);
          rd_valid <= '1';
        end if;

      end if;
    end if;
  end process;

  -- Write to and read from the RAM
  PROC_RAM : process(clk)
  begin
    if rising_edge(clk) then
      ram(head) <= wr_data;
      rd_data <= ram(tail);
    end if;
  end process;

  -- Update the fill count
  PROC_COUNT : process(head, tail)
  begin
    if head < tail then
      fill_count_i <= head - tail + RAM_DEPTH;
    else
      fill_count_i <= head - tail;
    end if;
  end process;

end architecture;

Kode di atas menunjukkan kode lengkap untuk ring buffer FIFO. Anda dapat mengisi formulir di bawah ini untuk mendapatkan file proyek ModelSim serta testbench yang dikirimkan kepada Anda secara instan.

Meja ujian

FIFO dipakai dalam testbench sederhana untuk menunjukkan cara kerjanya. Anda dapat mengunduh kode sumber untuk testbench bersama dengan proyek ModelSim dengan menggunakan formulir di bawah ini.

Input generik telah disetel ke nilai berikut:

Testbench pertama-tama mengatur ulang FIFO. Saat reset dilepaskan, testbench menulis nilai berurutan (1-255) ke FIFO hingga penuh. Akhirnya, FIFO dikosongkan sebelum pengujian selesai.

Kita dapat melihat bentuk gelombang untuk menjalankan testbench secara lengkap pada gambar di bawah ini. fill_count sinyal ditampilkan sebagai nilai analog dalam bentuk gelombang untuk menggambarkan tingkat pengisian FIFO dengan lebih baik.

Head, tail, dan fill count adalah 0 pada awal simulasi. Pada titik di mana full sinyal dinyatakan, kepala memiliki nilai 255, dan begitu juga fill_count sinyal. Jumlah pengisian hanya mencapai 255 meskipun kami memiliki kedalaman RAM 256. Itu karena kami menggunakan tetap buka metode untuk membedakan antara penuh dan kosong, seperti yang telah kita bahas sebelumnya di artikel ini.

Pada titik balik di mana kita berhenti menulis ke FIFO dan mulai membaca darinya, nilai head membeku sementara jumlah tail dan fill mulai berkurang. Terakhir, pada akhir simulasi saat FIFO kosong, baik head maupun tail memiliki nilai 255 sedangkan fill count adalah 0.

Testbench ini tidak boleh dianggap memadai untuk apa pun selain tujuan demonstrasi. Itu tidak memiliki perilaku atau logika pemeriksaan sendiri untuk memverifikasi bahwa output dari FIFO benar sama sekali.

Kami akan menggunakan modul ini di artikel minggu depan ketika kami mempelajari subjek verifikasi acak terbatas . Ini adalah strategi pengujian yang berbeda dari pengujian terarah yang lebih umum digunakan. Singkatnya, testbench akan melakukan interaksi acak dengan DUT (perangkat yang sedang diuji), dan perilaku DUT harus diverifikasi oleh proses testbench terpisah. Akhirnya, ketika sejumlah peristiwa yang telah ditentukan telah terjadi, pengujian selesai.

Klik di sini untuk membaca entri blog tindak lanjut:
Verifikasi acak terbatas

Sintesis di Vivado

Saya mensintesis buffer cincin di Xilinx Vivado karena ini adalah alat implementasi FPGA paling populer. Namun, ini harus bekerja pada semua arsitektur FPGA yang memiliki RAM blok dua port.

Kita harus menetapkan beberapa nilai ke input generik untuk dapat mengimplementasikan buffer ring sebagai modul yang berdiri sendiri. Ini dilakukan di Vivado dengan menggunakan PengaturanUmumGenerik/Parameter menu, seperti yang ditunjukkan pada gambar di bawah.

Nilai untuk RAM_WIDTH diatur ke 16, yang sama seperti pada simulasi. Tapi saya telah mengatur RAM_DEPTH hingga 2048 karena ini adalah kedalaman maksimal primitif RAMB36E1 dalam arsitektur Xilinx Zynq yang telah saya pilih. Kita bisa saja memilih nilai yang lebih rendah, tetapi tetap menggunakan jumlah blok RAM yang sama. Nilai yang lebih tinggi akan menghasilkan lebih dari satu blok RAM yang digunakan.

Gambar di bawah ini menunjukkan penggunaan sumber daya pasca implementasi, seperti dilansir Vivado. Buffer cincin kami memang menghabiskan satu blok RAM dan beberapa LUT dan flip-flop.

Menghilangkan sinyal yang valid

Anda mungkin bertanya pada diri sendiri apakah penundaan satu siklus jam antara rd_en dan rd_valid sinyal sebenarnya diperlukan. Lagi pula, datanya sudah ada di rd_data ketika kita menegaskan rd_en sinyal. Tidak bisakah kita menggunakan nilai ini dan membiarkan ring buffer melompat ke elemen berikutnya pada siklus clock berikutnya seperti yang kita baca dari FIFO?

Sebenarnya, kita tidak memerlukan valid sinyal. Saya menyertakan sinyal ini hanya untuk kenyamanan. Bagian penting adalah bahwa kita harus menunggu sampai siklus jam setelah kita menegaskan rd_en sinyal, jika tidak, RAM tidak akan punya waktu untuk bereaksi.

Blok RAM di FPGA adalah komponen yang sepenuhnya sinkron, mereka membutuhkan tepi jam untuk membaca dan menulis data. Jam baca dan tulis tidak harus berasal dari sumber jam yang sama, tetapi harus ada tepi jam. Selanjutnya, tidak ada logika antara output RAM dan register berikutnya (flip-flop). Ini karena register yang digunakan untuk mencatat output RAM berada di dalam blok primitif RAM.

Gambar di atas menunjukkan pada diagram waktu bagaimana nilai menyebar dari wr_data masukan di buffer ring kita, melalui RAM, dan akhirnya muncul di rd_data keluaran. Karena setiap sinyal diambil sampelnya pada tepi clock yang meningkat, dibutuhkan tiga siklus clock dari kita mulai menggerakkan port tulis sebelum muncul di port baca. Dan siklus jam tambahan berlalu sebelum modul penerima dapat menggunakan data ini.

Mengurangi latensi

Ada beberapa cara untuk mengurangi masalah ini, tetapi itu harus mengorbankan sumber daya tambahan yang digunakan dalam FPGA. Mari kita coba eksperimen untuk memotong satu penundaan siklus clock dari port baca buffer cincin kita. Pada cuplikan kode di bawah ini kami telah mengubah rd_data output dari proses sinkron ke proses kombinasional yang sensitif terhadap ram dan tail sinyal.

  PROC_READ : process(ram, tail)
   begin
     rd_data <= ram(tail);
   end process;

Sayangnya, kode ini tidak dapat dipetakan untuk memblokir RAM karena mungkin ada logika kombinasional antara output RAM dan register hilir pertama pada rd_data sinyal.

Gambar di bawah ini menunjukkan penggunaan sumber daya seperti dilansir Vivado. Blok RAM telah digantikan oleh LUTRAM; suatu bentuk RAM terdistribusi yang diimplementasikan dalam LUT. Penggunaan LUT telah meroket dari 37 LUT menjadi 947. Tabel pencarian dan flip-flop lebih mahal daripada RAM blok, itulah alasan utama kami memiliki RAM blok.

Ada banyak cara untuk mengimplementasikan ring buffer FIFO di block RAM. Anda mungkin dapat menyimpan siklus jam ekstra dengan menggunakan desain lain, tetapi akan dikenakan biaya dalam bentuk logika pendukung tambahan. Untuk sebagian besar aplikasi, buffer cincin yang disajikan dalam artikel ini sudah cukup.

Pembaruan:
Cara membuat ring buffer FIFO di blok RAM menggunakan handshake AXI ready/valid

Dalam posting blog berikutnya, kami akan membuat testbench yang lebih baik untuk modul buffer ring dengan menggunakan verifikasi acak terbatas .

Klik di sini untuk membaca entri blog tindak lanjut:
Verifikasi acak terbatas


VHDL

  1. Cara membuat daftar string di VHDL
  2. Cara membuat testbench berbasis Tcl untuk modul kunci kode VHDL
  3. Bagaimana menghentikan simulasi di testbench VHDL
  4. Cara membuat pengontrol PWM di VHDL
  5. Cara menghasilkan angka acak di VHDL
  6. Cara membuat testbench pemeriksaan mandiri
  7. Cara membuat Daftar Tertaut di VHDL
  8. Cara menggunakan Prosedur dalam Proses di VHDL
  9. Cara menggunakan Fungsi Tidak Murni di VHDL
  10. Cara menggunakan Fungsi di VHDL