RISC-V/V003/common/fifo.h
2024-05-07 11:46:49 +02:00

73 lines
3.7 KiB
C++

#ifndef FIFO_H
#define FIFO_H
/** Typ dbus_w_t je podobně definován jako sig_atomic_t v hlavičce signal.h.
* Je to prostě největší typ, ke kterému je "atomický" přístup. V GCC je definováno
* __SIG_ATOMIC_TYPE__, šlo by použít, ale je znaménkový.
* */
#ifdef __SIG_ATOMIC_TYPE__
typedef unsigned __SIG_ATOMIC_TYPE__ dbus_w_t;
#else
typedef unsigned int dbus_w_t; // pro AVR by to měl být uint8_t (šířka datové sběrnice)
#endif //__SIG_ATOMIC_TYPE__
/// Tahle podivná rekurzívní formule je použita pro validaci délky bufferu.
static constexpr bool isValidM (const int N, const dbus_w_t M) {
// constexpr má raději rekurzi než cyklus (c++11)
return (N > 12) ? false : (((1u << N) == M) ? true : isValidM (N+1, M));
}
/** @class FIFO
* @brief Jednoduchá fronta (kruhový buffer).
*
* V tomto přikladu je vidět, že synchronizace mezi přerušením a hlavní smyčkou programu
* může být tak jednoduchá, že je v podstatě neviditelná. Využívá se toho, že pokud
* do kruhového buferu zapisujeme jen z jednoho bodu a čteme také jen z jednoho bodu
* (vlákna), zápis probíhá nezávisle pomocí indexu m_head a čtení pomocí m_tail.
* Délka dat je dána rozdílem tt. indexů, pokud v průběhu výpočtu délky dojde k přerušení,
* v zásadě se nic špatného neděje, maximálně je délka určena špatně a to tak,
* že zápis nebo čtení je nutné opakovat. Důležité je, že po výpočtu se nová délka zapíše
* do paměti "atomicky". Takže např. pro 8-bit procesor musí být indexy jen 8-bitové.
* To není moc velké omezení, protože tyto procesory obvykle mají dost malou RAM, takže
* velikost bufferu stejně nebývá být větší než nějakých 64 položek.
* Opět nijak nevadí že přijde přerušení při zápisu nebo čtení položky - to se provádí
* dříve než změna indexu, zápis a čtení je vždy na jiném místě RAM. Celé je to uděláno
* jako šablona, takže je možné řadit do fronty i složitější věci než je pouhý byte.
* Druhým parametrem šablony je délka bufferu (aby to šlo konstruovat jako statický objekt),
* musí to být mocnina dvou v rozsahu 8 až 4096, default je 64. Mocnina 2 je zvolena proto,
* aby se místo zbytku po dělení mohl použít jen bitový and, což je rychlejší.
* */
template<typename T, const dbus_w_t M = 64> class FIFO {
T m_data [M];
volatile dbus_w_t m_head; //!< index pro zápis (hlava)
volatile dbus_w_t m_tail; //!< index pro čtení (ocas)
/// vrací skutečnou délku dostupných dat
constexpr dbus_w_t lenght () const { return (M + m_head - m_tail) & (M - 1); };
/// zvětší a saturuje index, takže se tento motá v kruhu @param n index
void sat_inc (volatile dbus_w_t & n) const { n = (n + 1) & (M - 1); };
public:
/// Konstruktor
explicit constexpr FIFO<T,M> () noexcept {
// pro 8-bit architekturu může být byte jako index poměrně malý
static_assert (1ul << (8 * sizeof(dbus_w_t) - 1) >= M, "atomic type too small");
// a omezíme pro jistotu i delku buferu na nějakou rozumnou delku
static_assert (isValidM (3, M), "M must be power of two in range <8,4096> or <8,128> for 8-bit data bus (AVR)");
m_head = 0;
m_tail = 0;
}
/// Čtení položky
/// @return true, pokud se úspěšně provede
const bool Read (T & c) {
if (lenght() == 0) return false;
c = m_data [m_tail];
sat_inc (m_tail);
return true;
}
/// Zápis položky
/// @return true, pokud se úspěšně provede
const bool Write (const T & c) {
if (lenght() >= (M - 1)) return false;
m_data [m_head] = c;
sat_inc (m_head);
return true;
}
};
#endif // FIFO_H