Unidad 1 · Filtrado digital · la línea de retardos, su costo, la forma transpuesta y el plegado
Un filtro FIR es una suma con pesos fijos de la entrada actual y de las anteriores. El FIR transversal es esa suma dibujada en hardware: una fila de registros por donde la entrada avanza una muestra por tic, una derivación a la salida de cada registro con su multiplicador, y un sumador que junta todo. Es la estructura del slide 17 de la filmina de convolución y la «Transversal Filter» de la filmina de estructuras. No tiene nada que diseñar: la ecuación ya es el circuito.
Un FIR causal de coeficientes calcula, en cada instante,
Es la suma de convolución con finita y causal: la segunda forma de la filmina, la que se lee «la salida actual es una combinación ponderada de la entrada actual y las anteriores, con pesos ». La estructura que la implementa tiene tres piezas:
Cada término de la suma es una columna del diagrama. Se ve en tres pasos, siguiendo una muestra.
Paso 1: cada registro atrasa una muestra. Un que recibe entrega . Después de registros la línea tiene . Así que en todo instante la línea guarda, en orden, las últimas entradas: . La más vieja se cae por el final.
Paso 2: cada derivación pesa su muestra. El multiplicador de la columna recibe y entrega : el término de la sumatoria.
Paso 3: el sumador cierra la sumatoria. Suma los productos y da . En el tic siguiente la línea avanza, cada muestra pasa a la columna de al lado, y la misma cuenta da . Los coeficientes no se mueven: se mueve la señal por debajo de ellos. Es el «dar vuelta y deslizar» de la convolución a mano, con la diferencia de que acá lo que se desliza es .
El mismo dibujo se lee en . Un bloque multiplica por , así que la columna aporta y el diagrama entero es el polinomio
No hay ningún camino que vuelva hacia atrás, y por eso no hay denominador: sus raíces son los ceros del filtro, que es tema de polos y ceros.
Con un impulso, la estructura escupe su propio . Si entra , en cada instante hay un solo 1 en la línea. En el instante está en la columna , y la salida es el coeficiente de esa columna: . A las muestras el 1 se cae por el final y la salida queda en cero para siempre. Es la respuesta finita, leída en el circuito.
El dibujo cuenta sus recursos. Por cada muestra de salida:
El costo crece lineal con y no depende de nada más. Un FIR de 101 taps son 101 multiplicaciones por muestra; el IIR de un polo de Ejemplo_filtros.m hace dos. Esa es la cuenta que pesa en la comparación con los IIR.
Los registros arrancan en cero. Antes de la primera muestra la línea está vacía, y eso es suponer para : la condición inicial de filter. Cuando la entrada se termina, la línea todavía tiene muestras adentro. Para vaciarla hay que meter ceros, y mientras tanto sigue saliendo algo: son las muestras de conv, contra las de filter, que corta en cuanto se acaba la entrada. Es la diferencia de largos que muestra waystofilt.m.
La simulación usa el FIR de 7 taps de la filmina de fase, , y la entrada de waystofilt.m, . Son 8 muestras y 7 taps, así que la salida completa tiene .
En la forma directa los registros guardan entradas enteras, y en cada pulsación se corren un lugar a la derecha. La columna tiene siempre la muestra que entró hace tics: a los siete tics el 1 del principio ya se cayó. La salida azul cae una por una sobre los círculos de la convolución, y sigue saliendo después de la última muestra de , mientras la línea se vacía con ceros. En la salida es cero exacto por casualidad de los números:
Con el impulso, la salida dibuja el propio y en se apaga.
La misma se puede calcular con otro circuito. Sale de reagrupar el polinomio.
Paso 1: sacar de factor común, una y otra vez. Es la regla de Horner aplicada a :
Paso 2: cada paréntesis es un registro. Se lee de adentro hacia afuera. El paréntesis más interno es , que se guarda en un registro. El siguiente es más lo que sale de ese registro, y se guarda en otro. Y así hasta afuera. Con el contenido del registro , en el tiempo:
Paso 3: dibujarlo. La entrada va a los multiplicadores a la vez. Abajo, la cadena corre al revés, de derecha a izquierda: cada eslabón es un sumador seguido de un registro, y la salida sale por la izquierda. Es el diagrama directo con las flechas invertidas, los nodos de derivación cambiados por sumadores y la entrada cambiada por la salida: el teorema de transposición de grafos, que garantiza la misma transferencia. Contado, cuesta exactamente lo mismo: multiplicaciones, sumas y registros.
Los registros ya no guardan entradas. Guardan sumas parciales de salidas futuras: es todo menos el término de la muestra que todavía no llegó. En la simulación, elegí «Transpuesta» y llevala a : los seis registros valen 2,80 · 3,20 · 3,00 · 2,00 · 1,10 · 0,40, y la salida es , la misma de la forma directa. Son los que da en MATLAB el lazo fir_transpuesta de la sección 08, cortado en ese instante. Cambiá de estructura en cualquier instante: la salida no se mueve, los registros sí.
Por qué conviene en hardware: el camino crítico. En un circuito sincrónico, el período de reloj tiene que alcanzar para que una señal atraviese el tramo combinacional más largo entre dos registros. En la forma directa, con el sumador armado como la cadena del dibujo, ese tramo va de la entrada, pasa por un multiplicador y atraviesa todos los sumadores:
En la transpuesta, entre la entrada y cualquier registro hay un multiplicador y un sumador, y nada más, tenga el filtro 7 taps o 101. La frecuencia de reloj máxima deja de depender de . Con un árbol de sumadores la directa baja a , que para 101 taps son 7 sumadores en serie, pero sigue creciendo con . La directa también se puede segmentar metiendo registros entre sumadores; eso agrega latencia y registros que la transpuesta no necesita, porque ya viene segmentada.
Tiene dos precios. La entrada alimenta a multiplicadores a la vez, un cable con mucho abanico de salida. Y los registros guardan sumas, que ocupan más bits que una muestra de entrada, así que en punto fijo la memoria crece. En MATLAB, filter dice implementar la forma directa II transpuesta, que para un FIR es esta.
Un FIR de fase lineal tiene coeficientes simétricos, . En la forma directa eso es desperdicio: el mismo número se multiplica dos veces por muestra, contra dos entradas distintas. La nota verde de la filmina de fase lo dice así: este tipo de filtros reduce el almacenamiento de coeficientes y la cantidad de operaciones.
Paso 1: juntar los términos que comparten coeficiente. El término y el término llevan el mismo :
Es factor común. Una multiplicación menos por cada par, a cambio de una suma previa.
Paso 2: doblar la línea. Para tener las dos muestras de cada par una enfrente de la otra, la línea se dobla por la mitad como una hoja: la primera mitad va hacia la derecha, da la vuelta y vuelve por abajo. La columna tiene arriba y abajo , un presumador las junta y recién ahí se multiplica. Con impar sobra la muestra del centro, , que no tiene pareja y se multiplica sola:
Paso 3: contar. Hay pares, más el centro si es impar: multiplicaciones. Las sumas no bajan: las previas más las que juntan los productos dan otra vez. Los registros tampoco: la línea es la misma, sólo que doblada. Contado con un contador dentro de los lazos de MATLAB:
| N | directa y transpuesta | plegada |
|---|---|---|
| 7 | 7 × · 6 + | 4 × · 6 + |
| 8 | 8 × · 7 + | 4 × · 7 + |
| 100 | 100 × · 99 + | 50 × · 99 + |
| 101 | 101 × · 100 + | 51 × · 100 + |
Multiplicaciones (×) y sumas (+) por muestra de salida. Los 51 de 101 son la respuesta del autochequeo del apunte de fase. En software lo que se ahorra depende del procesador. En una FPGA, donde los multiplicadores son el recurso escaso, es la mitad del filtro. Y la simetría paga dos veces: la misma condición que da la fase lineal deja guardar la mitad de los coeficientes.
En la simulación, «Plegada» muestra el FIR de 7 taps con 4 multiplicadores. Un detalle: la línea doblada tiene los registros de abajo corriendo hacia la izquierda, así que la muestra que entra arriba a la izquierda sale, seis tics después, abajo a la izquierda, al lado de donde entró. Por eso las dos mitades de cada par quedan en la misma columna.
El nombre viene de cuando la línea era analógica: un cable o un dispositivo por donde la señal viaja y tarda. La señal recorre la línea a lo largo, y las derivaciones la cortan de través, en ángulo recto con el recorrido. Lo que se suma en cada instante es un corte transversal de la historia de la señal: las últimas muestras, tomadas todas a la vez. La filmina de estructuras usa los dos nombres juntos, Transversal Filter y Tapped Delay Line. Son la misma cosa vista de dos lados: una por cómo se leen las muestras, la otra por lo que es el hardware.
El filtro de media móvil es un transversal con todos los coeficientes iguales a . Justamente por eso admite otra estructura, recursiva, que el transversal general no tiene.
La línea de retardos con derivaciones vuelve al final del curso, en la ecualización adaptiva de la Unidad 6. El ecualizador lineal es un transversal cuyos coeficientes no son fijos: un algoritmo los corrige muestra a muestra. La línea, los multiplicadores y el sumador son los de esta nota. Lo único nuevo es quién elige los .
Las tres estructuras escritas a mano, con un vector de estado y un lazo muestra a muestra, comparadas contra filter y conv. Lo que figura como comentario es la salida real de correrlo en R2026a.
%% 1. El FIR de la filmina y una entrada corta h = [0.1 0.2 0.3 0.4 0.3 0.2 0.1]; % N = 7, simétrico x = [1 2 3 4 5 6 -5 -4]; % la entrada de waystofilt.m N = numel(h); xz = [x, zeros(1, N-1)]; % ceros al final: que salga la cola y_conv = conv(h, x); y_dir = fir_directa(h, xz); y_tr = fir_transpuesta(h, xz); y_pl = fir_plegada(h, xz); fprintf('y = '); fprintf('%.2f ', y_dir); fprintf('\n') % y = 0.10 0.40 1.00 2.00 3.30 4.80 5.20 4.40 2.70 -0.00 -1.40 -1.60 -1.30 -0.40 %% 2. Las tres estructuras contra conv y filter, con un FIR largo rng(1) hl = fir1(100, 0.3); % N = 101, simétrico xl = randn(1, 5000); yf = filter(hl, 1, xl); yc = conv(hl, xl); yc = yc(1:numel(xl)); yd = fir_directa(hl, xl); yt = fir_transpuesta(hl, xl); yp = fir_plegada(hl, xl); fprintf('max|directa - filter| = %.1e\n', max(abs(yd - yf))) fprintf('max|transpuesta- filter| = %.1e\n', max(abs(yt - yf))) fprintf('max|plegada - filter| = %.1e\n', max(abs(yp - yf))) fprintf('max|directa - conv | = %.1e\n', max(abs(yd - yc))) fprintf('max|filter - conv | = %.1e\n', max(abs(yf - yc))) fprintf('max|conv(h,x) chica - directa| = %.1e\n', max(abs(y_conv - y_dir))) fprintf('max|transpuesta - directa| chica = %.1e\n', max(abs(y_tr - y_dir))) fprintf('max|plegada - directa| chica = %.1e\n', max(abs(y_pl - y_dir))) % max|directa - filter| = 2.2e-15 % max|transpuesta- filter| = 8.9e-16 % max|plegada - filter| = 1.3e-15 % max|directa - conv | = 2.2e-15 % max|filter - conv | = 1.1e-16 % max|conv(h,x) chica - directa| = 0.0e+00 % max|transpuesta - directa| chica = 8.9e-16 % max|plegada - directa| chica = 8.9e-16 %% 3. Contar operaciones por muestra for Nn = [7 8 100 101] [~, c1] = fir_directa(ones(1, Nn), 1); [~, c2] = fir_transpuesta(ones(1, Nn), 1); [~, c3] = fir_plegada(ones(1, Nn), 1); fprintf('N = %3d directa %3d mult, %3d sumas transpuesta %3d mult, %3d sumas plegada %3d mult, %3d sumas\n', ... Nn, c1(1), c1(2), c2(1), c2(2), c3(1), c3(2)) end % N = 7 directa 7 mult, 6 sumas transpuesta 7 mult, 6 sumas plegada 4 mult, 6 sumas % N = 8 directa 8 mult, 7 sumas transpuesta 8 mult, 7 sumas plegada 4 mult, 7 sumas % N = 100 directa 100 mult, 99 sumas transpuesta 100 mult, 99 sumas plegada 50 mult, 99 sumas % N = 101 directa 101 mult, 100 sumas transpuesta 101 mult, 100 sumas plegada 51 mult, 100 sumas %% Las tres estructuras, muestra a muestra function [y, ops] = fir_directa(h, x) % Registros: r(k) = x[n-k], k = 1..N-1. Guardan entradas viejas. N = numel(h); r = zeros(1, N-1); y = zeros(size(x)); ops = [0 0]; for n = 1:numel(x) acc = h(1) * x(n); ops(1) = ops(1) + 1; for k = 1:N-1 acc = acc + h(k+1) * r(k); ops = ops + 1; end y(n) = acc; r = [x(n), r(1:end-1)]; % la línea avanza un lugar end ops = ops / numel(x); end function [y, ops] = fir_transpuesta(h, x) % Registros: s(k) guardan sumas parciales, no entradas. N = numel(h); s = zeros(1, N-1); y = zeros(size(x)); ops = [0 0]; for n = 1:numel(x) y(n) = h(1) * x(n) + s(1); ops = ops + 1; for k = 1:N-2 s(k) = h(k+1) * x(n) + s(k+1); ops = ops + 1; end s(N-1) = h(N) * x(n); ops(1) = ops(1) + 1; end ops = ops / numel(x); end function [y, ops] = fir_plegada(h, x) % Supone h simétrico: h(k) = h(N+1-k). Suma los pares antes de multiplicar. N = numel(h); M = floor(N/2); r = zeros(1, N-1); y = zeros(size(x)); ops = [0 0]; for n = 1:numel(x) v = [x(n), r]; % v(k+1) = x[n-k] acc = 0; for k = 1:M par = v(k) + v(N+1-k); ops(2) = ops(2) + 1; % suma previa if k == 1, acc = h(k) * par; ops(1) = ops(1) + 1; else, acc = acc + h(k) * par; ops = ops + 1; end end if mod(N, 2) == 1 % tap central, sin pareja acc = acc + h(M+1) * v(M+1); ops = ops + 1; end y(n) = acc; r = [x(n), r(1:end-1)]; end ops = ops / numel(x); end
Las catorce salidas del bloque 1 son las de la simulación. En el FIR largo, las tres estructuras dan lo mismo que filter y que conv hasta unos pocos eps: la peor diferencia es 2,2×10⁻¹⁵ sobre 5000 muestras. No es cero porque cada estructura suma en otro orden, y la suma en punto flotante no es asociativa. Ni siquiera la transpuesta coincide al bit con filter, aunque sea la estructura que filter dice usar: difieren en 8,9×10⁻¹⁶. Las dos cuentas no son la misma aritmética, y justamente por eso la comparación dice algo.
Los detalles que muerden:
h(k+1), y el registro que guarda es r(k). En la plegada, el par de h(k) es v(k) + v(N+1-k).conv hay que meterle los ceros a mano, como hace xz. Sin eso da las de filter, y hay que recortar conv para comparar, como en el bloque 2.-0.00: es un residuo de redondeo del orden de eps, con signo.fir1 lo da simétrico; conviene mirar max(abs(h - fliplr(h))) antes de plegar.filter está compilado y el lazo no. El ahorro de la plegada y el camino corto de la transpuesta son de hardware; en MATLAB el lazo es para ver la estructura, no para ganarle a filter.filter. Con un impulso, escupe .eps porque suman en otro orden.