a grande vantagem do software eh a flexibilidade, mas eu cheguei a conclusao de que essa flexibilidade apenas atrasa a vida, pois desperdica um grande potencial computacional presente nos processadores.
no exemplo abaixo vc pode ver como flexibilidade e performance sao coisa bem opostas! voce pode comecar pensando em uma unidade computacional bem eficiente:
- Código: Selecionar todos
module mac(CLK,WEX,WEY,WEZ,VEC,DI,DO);
input CLK;
input WEX;
input WEY;
input WEZ;
input VEC;
input [15:0] DI;
output [15:0] DO;
reg [15:0] DX;
reg [15:0] DY;
reg [15:0] DZ;
assign DO = DZ;
always@(posedge CLK)
begin
if(VEC)
begin
if(WEX)
DX <= DI;
if(WEY)
DY <= DI;
if(WEZ)
DZ <= DZ+DX*DY;
end
end
endmodule
em essencia, cada unidade computacional destas eh capaz de fazer 125 MMAC/s em uma spartan XC3S250E. a ideia eh carregar previamente em DY um coeficiente constante e entao continuamente carregar em DX um valor, multiplicar e acumular em DZ.
como uma XC3S250E possui 12 multiplicadores deste, eh possivel facilmente projetar uma unidade vetorial:
- Código: Selecionar todos
module vec(CLK,WEX,WEY,WEZ,VEC,ADDR,DI,DO);
input CLK;
input WEX;
input WEY;
input WEZ;
input [0:11] VEC;
input [3:0] ADDR;
input [15:0] DI;
output [15:0] DO;
wire [15:0] DZ [0:11];
assign DO = DZ[ADDR];
mac mac00(CLK,WEX,WEY,WEZ,VEC[00],DI,DZ[00]);
mac mac01(CLK,WEX,WEY,WEZ,VEC[01],DI,DZ[01]);
mac mac02(CLK,WEX,WEY,WEZ,VEC[02],DI,DZ[02]);
mac mac03(CLK,WEX,WEY,WEZ,VEC[03],DI,DZ[03]);
mac mac04(CLK,WEX,WEY,WEZ,VEC[04],DI,DZ[04]);
mac mac05(CLK,WEX,WEY,WEZ,VEC[05],DI,DZ[05]);
mac mac06(CLK,WEX,WEY,WEZ,VEC[06],DI,DZ[06]);
mac mac07(CLK,WEX,WEY,WEZ,VEC[07],DI,DZ[07]);
mac mac08(CLK,WEX,WEY,WEZ,VEC[08],DI,DZ[08]);
mac mac09(CLK,WEX,WEY,WEZ,VEC[09],DI,DZ[09]);
mac mac10(CLK,WEX,WEY,WEZ,VEC[10],DI,DZ[10]);
mac mac11(CLK,WEX,WEY,WEZ,VEC[11],DI,DZ[11]);
endmodule
neste caso a performance de pico seria de 1500 MMAC/s. mas como estamos falando em software, a coisa nao eh tao eficiente. se for pensar em memoria externa, tem o compromisso de largura de bus vs bandwidth. e com apenas um barramento de 16 bits, o bandwidth efetivo eh de apenas 250MB/s.
eh possivel carregar multiplos registros na mesma instrucao e fazer os MAC ao mesmo tempo em paralelo, porem se os coeficientes ou dados forem todos diferentes, seria necessario uma instrucao por registro. daih na pratica seria necessario primeiro fazer 12 instrucoes load, uma instrucao de mac vetorial e entao 12 instrucoes store.
o resultado eh que seriam necessarios 25 clocks para fazer 12 operacoes MAC, o que resultaria em meros 60MMAC/s. o gargalo estaria concentrado justamente nos load/store.
mas claro, isso depende do tipo de filtro. uma outra possibilidade seria, ao inves de pensar em 12 filtros separados, pensar em um filtro de 12 termos. neste caso seria necessario apenas uma instrucao load e uma store em meio a uma mac vetorial, o que totalizaria 3 clocks para 12 operacoes e resultaria em 500MMAC/s.
a diferenca eh que cada resultado DZ seria o proximo DX, evitando mover dados pelo barramento externo. ainda assim, teria apenas 1/3 da maxima performance possivel, sem falar que seria uma solucao bem dedicada, ou seja, estaria perdendo muito a flexibilidade de software.
a melhor performance mesmo seria fazer tudo fixo: um DMA puxando valores de DX, com os DZ entrando no proximo DX e o resultado final no ultimo DZ sendo gravado em outra memoria. a flexibilidade seria zero, mas atingiria entao os 1500MMAC/s.
a conclusao eh simples: quando mais flexivel em software, pior a performance!
