
tt_um_tinyopt4 is a compact sequential parameter update engine designed specifically for adaptive FIR identification. The core executes an 11-state, time-multiplexed schedule with a 10-cycle processing path around a shared arithmetic unit:
S_IDLE: Polls control bits; captures incoming data sample $x[n]$ into the 4-element shift register when start = 1 and data_sel = 0.S_MAC0 to S_MAC3: Feeds pairs $(w_i[n], x[n-i])$ sequentially into the single signed $8 \times 8$ multiplier. Products are added into the 18-bit accumulator.S_ERR: Clamps the normalized accumulator to the 8-bit range $[-128, +127]$, subtracts this estimate from reference $d[n]$, and registers the bounded difference $e[n]$.S_UPD0 to S_UPD3: Re-routes the multiplier to compute $e[n] \cdot x[n-i]$. The 16-bit intermediate product passes through an arithmetic barrel shifter configured by $s$, saturates, and updates register $w_i[n+1]$.S_DONE: Pulses done high for one cycle, deasserts busy, and returns the FSM to S_IDLE.Verification relies on the Cocotb test framework running under Icarus Verilog (iverilog):
| Interface | Signal | Role |
|---|---|---|
ui_in[7:0] |
Data / Address | Input values ($x[n]$, $d[n]$, or shift exponent $s$). When idle, bits [2:0] act as register read address. |
uo_out[7:0] |
Data Out | Monitored byte selected by ui_in[2:0]. |
uio_in[0] |
start |
Clock-synchronous execution strobe. |
uio_in[1] |
data_sel |
0 = Shift sample $x[n]$; 1 = Latch reference $d[n]$ and trigger 10-cycle update. |
uio_in[2] |
cfg_sel |
1 = Write learning rate exponent $s$ from ui_in[2:0]. |
uio_out[4] |
busy |
Held high by FSM during the 10 processing cycles. |
uio_out[5] |
done |
Single-cycle pulse marking coefficient update completion. |
uio_out[6] |
overflow |
Latched high if prediction or error calculation exceeds $Q1.7$ limits. |
uio_out[7] |
w_sat |
Latched high if any tap update hits $\pm 127$ saturation bounds. |
rst_n = 0 for 3 clock cycles, then release (rst_n = 1).ui_in[2:0] = s (e.g., 3 for $\mu = 2^{-3}$), pulse uio_in = 3'b101 for one cycle, then return uio_in to 0.ui_in[7:0], strobe uio_in = 3'b001 for one cycle, and wait one cycle.ui_in[7:0], strobe uio_in = 3'b011 for one cycle, then clear uio_in = 0.uio_out[4] (busy) returns low and uio_out[5] (done) pulses high.uio_in = 0, apply the target address to ui_in[2:0]:3'b000: Predicted response $\hat{y}[n]$3'b001: Error residual $e[n]$3'b010 to 3'b101: Filter taps $w_0$ to $w_3$None required. The module operates self-contained on any standard Tiny Tapeout carrier board:
| # | Input | Output | Bidirectional |
|---|---|---|---|
| 0 | Data input bit 0 (x / d / cfg_mu) | Multiplexed output bit 0 (y_hat / e / w0-w3) | In: start |
| 1 | Data input bit 1 | Multiplexed output bit 1 | In: data_sel (0=x, 1=d) |
| 2 | Data input bit 2 | Multiplexed output bit 2 | In: cfg_sel (1=config mu) |
| 3 | Data input bit 3 | Multiplexed output bit 3 | In: unused / tied low |
| 4 | Data input bit 4 | Multiplexed output bit 4 | Out: busy |
| 5 | Data input bit 5 | Multiplexed output bit 5 | Out: done |
| 6 | Data input bit 6 | Multiplexed output bit 6 | Out: overflow flag |
| 7 | Data input bit 7 | Multiplexed output bit 7 | Out: weight saturation flag |