Design an asynchronous FIFO: an 8-entry, 8-bit FIFO where the write side and read side operate on two **independent, unrelated clocks** with no fixed phase or frequency relationship. This is widely considered one of the canonical "hard" RTL interview questions, because correctly crossing pointer information between clock domains without metastability or incorrect full/empty flags requires Gray-code pointer encoding and proper synchronization — a naive synchronous-FIFO-style design will not work here. Ports: - `wr_clk` — input, 1 bit, write-domain clock - `wr_rst` — input, 1 bit, synchronous reset for the write domain (active-high, synchronous to `wr_clk`) - `wr_en` — input, 1 bit, write enable (write domain) - `din` — input, 8 bits (`din[7:0]`), write data (write domain) - `full` — output, 1 bit, asserted when the FIFO cannot accept another write (write domain, synchronous to `wr_clk`) - `rd_clk` — input, 1 bit, read-domain clock - `rd_rst` — input, 1 bit, synchronous reset for the read domain (active-high, synchronous to `rd_clk`) - `rd_en` — input, 1 bit, read enable (read domain) - `dout` — output, 8 bits (`dout[7:0]`), registered read data (read domain, synchronous to `rd_clk`) - `empty` — output, 1 bit, asserted when the FIFO has nothing left to read (read domain, synchronous to `rd_clk`) Required behavior: 1. **Write side**: on each rising edge of `wr_clk`, if `wr_rst` is high, the write pointer resets to 0. Else, if `wr_en` is high and `full` is currently 0, `din` is stored and the write pointer advances (wrapping after 8 entries, circular buffer). Writes while `full` is 1 are silently ignored (no overflow corruption). 2. **Read side**: on each rising edge of `rd_clk`, if `rd_rst` is high, the read pointer resets to 0. Else, if `rd_en` is high and `empty` is currently 0, `dout` is updated with the data at the current read location (registered output, FIFO order preserved) and the read pointer advances (wrapping after 8 entries). Reads while `empty` is 1 are silently ignored (no underflow, `dout` does not change). 3. **Cross-domain pointer synchronization**: the write pointer must be synchronized into the read clock domain (to compute `empty`), and the read pointer must be synchronized into the write clock domain (to compute `full`), using Gray-code encoding plus at least a 2-stage synchronizer register chain in each direction, so that no single multi-bit pointer ever needs to cross domains in a way that could be sampled mid-transition. 4. `full` must correctly stay asserted until the read side has actually consumed at least one entry (as observed through the synchronized pointer), and `empty` must correctly stay asserted until the write side has actually produced at least one entry (as observed through the synchronized pointer) — i.e., the flags must be conservative and never falsely report space/data that the other domain hasn't actually confirmed yet, given synchronizer latency. 5. The two clocks (`wr_clk`, `rd_clk`) may run at different, unrelated frequencies and have no fixed phase relationship — the design must not assume any timing relationship between them beyond each clock's own rising edges.