Main Content

Conversione manuale di un algoritmo MATLAB in virgola mobile in uno in virgola fissa

Questo esempio mostra come convertire un algoritmo in virgola mobile in uno in virgola fissa e come generare un codice C per l’algoritmo. L’esempio utilizza le seguenti best practice:

  • Separare l’algoritmo dal file di prova.

  • Preparare l’algoritmo per la strumentazione e la generazione di codice.

  • Gestire i tipi di dati e controllare la crescita di bit.

  • Separare le definizioni del tipo di dati dal codice dell’algoritmo creando una tabella di definizione dei dati.

Per un elenco completo delle best practice, vedere Manual Fixed-Point Conversion Best Practices.

Separazione dell’algoritmo dal file di prova

Scrivere una funzione MATLAB® mysum che sommi gli elementi di un vettore.

function y = mysum(x)
  y = 0;
  for n = 1:length(x)
    y = y + x(n);
  end
end

Poiché è necessario convertire solo la parte algoritmica in virgola fissa, risulta più efficiente strutturare il codice in modo che l’algoritmo, in cui si esegue l’elaborazione principale, sia separato dal file di prova.

Scrittura di uno script di prova

Creare gli input, chiamare l’algoritmo e tracciare i risultati nel file di prova.

  1. Scrivere uno script MATLAB, mysum_test che verifichi il comportamento dell’algoritmo utilizzando tipi di dati doppi.

    n = 10;
    rng default
    x = 2*rand(n,1)-1;
    
    % Algorithm
    y = mysum(x);
    
    % Verify results
    y_expected = sum(double(x));
    
    err = double(y) - y_expected

    rng default riporta le impostazioni del generatore di numeri casuali utilizzato dalla funzione rand al proprio valore predefinito, in modo che produca gli stessi numeri casuali come se si fosse riavviato MATLAB.

  2. Eseguire lo script di prova.

    mysum_test
    err =
    
         0

    I risultati ottenuti utilizzando mysum corrispondono a quelli ottenuti utilizzando la funzione MATLAB sum.

Per ulteriori informazioni, vedere Create a Test File.

Preparazione dell’algoritmo per la strumentazione e la generazione di codice

Aggiungere l’istruzione di compilazione %#codegen nell’algoritmo, dopo la firma della funzione, per indicare che si intende strumentare l’algoritmo e generare un codice C per l’algoritmo stesso. Aggiungendo questa istruzione, l’analizzatore di codice MATLAB fornisce un aiuto per diagnosticare e correggere le violazioni che potrebbero causare errori durante la strumentazione e la generazione di codice.

function y = mysum(x) %#codegen
  y = 0;  
  for n = 1:length(x)
    y = y + x(n);
  end
end

Per questo algoritmo, l’analizzatore di codice nell’angolo in alto a destra della finestra dell’editor rimane verde, ad indicare che non è stato rilevato nessun problema.

Screenshot of mysum function with code analyzer indicating there are no issues.

Per ulteriori informazioni, vedere Prepare Your Algorithm for Code Acceleration or Code Generation.

Generazione di un codice C per l’algoritmo originale

Generare un codice C per l’algoritmo originale per verificare che l’algoritmo sia adatto per la generazione di codice e per visualizzare il codice C in virgola mobile. Utilizzare la funzione codegen (MATLAB Coder) (richiede MATLAB Coder™) per generare una libreria C.

  1. Aggiungere la seguente riga alla fine dello script per generare il codice C per mysum.

    codegen mysum -args {x} -config:lib -report

  2. Eseguire nuovamente lo script di prova.

    MATLAB Coder genera un codice C per la funzione mysum e fornisce un collegamento al report di generazione di codice.

  3. Fare clic sul collegamento per aprire il report di generazione di codice e visualizzare il codice C generato per mysum.

    /* Function Definitions */
    double mysum(const double x[10])
    {
      double y;
      int n;
      y = 0.0;
      for (n = 0; n < 10; n++) {
        y += x[n];
     }
     
     return y;
     }

    Poiché C non consente indici in virgola mobile, il contatore di loop n, viene automaticamente dichiarato come un tipo intero. Non è necessario convertire n in virgola fissa.

    Gli input x e gli output y sono dichiarati come doppi.

Gestione dei tipi di dati e controllo della crescita di bit

Testare l’algoritmo con i singoli per verificare la presenza di disallineamenti di tipo

  1. Modificare il file di prova in modo che il tipo di dati x sia singolo.

    n = 10;
    rng default
    x = single(2*rand(n,1)-1);
    
    % Algorithm
    y = mysum(x);
    
    % Verify results
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    codegen mysum -args {x} -config:lib -report

  2. Eseguire nuovamente lo script di prova.

    mysum_test
    err =
    
      -4.4703e-08
    
    ??? This assignment writes a 'single' value into a 'double' type. Code generation
    does not support changing types through assignment. Check preceding assignments or
    input type specifications for type mismatches.

    La generazione di codice fallisce, riportando una mancata corrispondenza del tipo di dati sulla riga y = y + x(n);.

  3. Aprire il report per visualizzare l’errore.

    La riga y = y + x(n) del report evidenzia in rosso la y sul lato sinistro dell’assegnazione, ad indicare che è presente un errore. Il problema è che y è dichiarata come doppia ma viene assegnata a un singolo. y + x(n) è la somma di un doppio e di un singolo, che è un singolo. Posizionando il cursore sulle variabili e sulle espressioni del report è possibile visualizzare le informazioni relative ai loro tipi. Qui si può vedere che l’espressione y + x(n) è un singolo.

    Screenshot of report with cursor hovering over the expression y+x(n).

  4. Per correggere la mancata corrispondenza di tipo, aggiornare l'algoritmo per utilizzare l'assegnazione con pedice per la somma degli elementi. Modificare y = y + x(n) in y(:) = y + x(n).

    function y = mysum(x) %#codegen
      y = 0;
      for n = 1:length(x)
        y(:) = y + x(n);
      end
    end
    

    Utilizzando l'assegnazione con pedice, si impedisce anche la crescita di bit, che è il comportamento predefinito quando si aggiungono numeri in virgola fissa. Per ulteriori informazioni, vedere Crescita di bit. Prevenire la crescita di bit è importante perché si desidera mantenere i tipi in virgola fissa in tutto il codice. Per ulteriori informazioni, vedere Controllo della crescita di bit.

  5. Rigenerare il codice C e aprire il report di generazione di codice. Nel codice C, il risultato è ora convertito in doppio per risolvere la mancata corrispondenza di tipo.

Costruzione di un Mex strumentato

Utilizzare la funzione buildInstrumentedMex per strumentalizzare l’algoritmo e registrare i valori minimi e massimi di tutte le variabili con nome e intermedie. Utilizzare la funzione showInstrumentationResults per proporre tipi di dati in virgola fissa basati su questi valori registrati. Successivamente, utilizzare i tipi di dati in virgola fissa proposti per testare l’algoritmo.

  1. Aggiornare lo script di prova:

    1. Dopo aver dichiarato n, aggiungere buildInstrumentedMex mySum —args {zeros(n,1)} -histogram.

    2. Modificare nuovamente x in doppio. Sostituire x = single(2*rand(n,1)-1); con x = 2*rand(n,1)-1;

    3. Anziché chiamare l’algoritmo originale, chiamare la funzione MEX generata. Modificare y = mysum(x) in y=mysum_mex(x).

    4. Dopo aver chiamato la funzione MEX, aggiungere showInstrumentationResults mysum_mex -defaultDT numerictype(1,16) -proposeFL. I flag -defaultDT numerictype(1,16) -proposeFL indicano che si desidera proporre lunghezze frazionarie per una lunghezza della parola di 16 bit.

      Nel seguito, uno script di prova aggiornato.

      %% Build instrumented mex
      n = 10;
      
      buildInstrumentedMex mysum -args {zeros(n,1)} -histogram
      
      %% Test inputs
      rng default
      x = 2*rand(n,1)-1;
      
      % Algorithm
      y = mysum_mex(x);
      
      % Verify results
      
      showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
      y_expected = sum(double(x));
      
      err = double(y) - y_expected
      
      %% Generate C code
      
      codegen mysum -args {x} -config:lib -report
      

  2. Eseguire nuovamente lo script di prova.

    La funzione showInstrumentationResults propone tipi di dati e apre un report per visualizzare i risultati.

  3. Nel report, fare clic sulla scheda Variables. showInstrumentationResults propone una lunghezza della frazione di 13 per y e di 15 per x.

    Screenshot of Variables tab containing column for Proposed FL.

Nel report è possibile:

  • Visualizzare i valori minimi e massimi della simulazione di input x e di output y.

  • Visualizzare i tipi di dati di x e y.

  • Visualizzare le informazioni relative a tutte le variabili, i risulti intermedi e le espressioni del codice.

    Per visualizzare queste informazioni, posizionare il cursore sopra la variabile o l’espressione all’interno del report.

  • Visualizzare i dati dell’istogramma di x e y per identificare qualsiasi valore al di fuori dell’intervallo o al di sotto della precisione, in base al tipo di dati attuale.

    Per visualizzare l’istogramma relativo a una specifica variabile, fare clic sull’icona dell’istogramma .

Separazione delle definizioni del tipo di dati dal codice dell’algoritmo

Anziché modificare manualmente l'algoritmo per esaminare il comportamento di ciascun tipo di dati, separare le definizioni del tipo di dati dall'algoritmo.

Modificare mysum in modo che prenda un parametro di input T, che è una struttura che definisce i tipi di dati di input e di output. Quando y è definita per la prima volta, utilizza la funzione cast come sintassi — cast(x,'like',y) — per convertire x nel tipo di dati desiderato.

function y = mysum(x,T) %#codegen
  y = cast(0,'like',T.y);
  for n = 1:length(x)
    y(:) = y + x(n);
  end
end

Creazione di una tabella di definizione del tipo di dati

Scrivere una funzione mytypes che definisca i diversi tipi di dati che si desiderano utilizzare per testare l’algoritmo. Nella tabella dei tipi di dati, includere i tipi di dati doppi, singoli e doppi ridimensionati, nonché i tipi di dati in virgola fissa proposti in precedenza. Prima di convertire l’algoritmo in virgola fissa, è buona prassi:

  • Testare la connessione tra la tabella di definizione del tipo di dati e l’algoritmo utilizzando i doppi.

  • Testare l’algoritmo con i singoli per rilevare tipi di dati non corrispondenti o altre problematiche.

  • Eseguire l’algoritmo utilizzando i doppi ridimensionati per verificare la presenza di overflow.

function T = mytypes(dt)
  switch dt
    case 'double'
      T.x = double([]);
      T.y = double([]);
    case 'single'
      T.x = single([]);
      T.y = single([]);
    case 'fixed'
      T.x = fi([],true,16,15);
      T.y = fi([],true,16,13);
    case 'scaled'
      T.x = fi([],true,16,15,...
           'DataType','ScaledDouble');
      T.y = fi([],true,16,13,...
           'DataType','ScaledDouble');
  end
end

Per ulteriori informazioni, vedere Separate Data Type Definitions from Algorithm.

Aggiornamento dello script di prova per utilizzare la tabella dei tipi

Aggiornare lo script di prova mysum_test per utilizzare la tabella dei tipi.

  1. Per la prima esecuzione, verificare che la connessione tra la tabella e l’algoritmo utilizzi i doppi. Prima di dichiarare n, aggiungere T = mytypes('double');

  2. Aggiornare la chiamata di buildInstrumentedMex per utilizzare il tipo di T.x specificato nella tabella dei tipi di dati: buildInstrumentedMex mysum -args {zeros(n,1,'like',T.x),T} -histogram

  3. Aggiornare la chiamata di x per utilizzare il tipo di T.x specificato nella tabella dei tipi di dati: x = cast(2*rand(n,1)-1,'like',T.x);

  4. Chiamare la funzione MEX passando da T: y = mysum_mex(x,T);

  5. Chiamare codegen passando da T: codegen mysum -args {x,T} -config:lib -report

    Nel seguito, lo script di prova aggiornato.

    %% Build instrumented mex
    T = mytypes('double');
    
    n = 10;
    
    buildInstrumentedMex mysum ...
        -args {zeros(n,1,'like',T.x),T} -histogram
    
    %% Test inputs
    rng default
    x = cast(2*rand(n,1)-1,'like',T.x);
    
    % Algorithm
    y = mysum_mex(x,T);
    
    % Verify results
    
    showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
    
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    
    %% Generate C code
    
    codegen mysum -args {x,T} -config:lib -report
    

  6. Eseguire lo script di prova e fare clic sul collegamento per aprire il report di generazione di codice.

    Il codice C generato è lo sesso del codice generato per l’algoritmo originale. Poiché la variabile T è utilizzata per specificare i tipi e, questi tipi, sono costanti al momento della generazione di codice, T non viene utilizzata in fase di esecuzione e non appare nel codice generato.

Generazione di codice in virgola fissa

Aggiornare lo script di prova per utilizzare i tipi in virgola fissa proposti in precedenza e visualizzare il codice C generato.

  1. Aggiornare lo script di prova per utilizzare i tipi in virgola fissa. Sostituire T = mytypes('double'); con T = mytypes('fixed');, quindi salvare lo script.

  2. Eseguire lo script di prova e visualizzare il codice C generato.

    Questa versione del codice C non è molto efficiente poiché comporta molta gestione dell'overflow. Il passaggio successivo consiste nell’ottimizzare i tipi di dati per evitare overflow.

Ottimizzazione dei tipi di dati

Utilizzare i doppi ridimensionati per rilevare l’overflow

I doppi ridimensionati sono un ibrido tra i numeri in virgola mobile e i numeri in virgola fissa. Fixed-Point Designer™ li archivia come doppi conservando le informazioni relative al ridimensionamento, la firma e la lunghezza della parola. Poiché tutta l'aritmetica viene eseguita in doppia precisione, è possibile vedere tutti gli overflow che si verificano.

  1. Aggiornare lo script di prova per utilizzare i doppi ridimensionati. Sostituire T = mytypes('fixed'); con T = mytypes('scaled');

  2. Eseguire nuovamente lo script di prova.

    La prova viene eseguita utilizzando doppi ridimensionati e viene visualizzato il report. Non vengono rilevati overflow.

    Finora, sono stati eseguiti script di prova utilizzando degli input casuali; è quindi improbabile che la prova abbia esplorato l’intero intervallo operativo dell’algoritmo.

  3. Trovare l’intero intervallo di input.

    range(T.x)
    -1.000000000000000   0.999969482421875
    
              DataTypeMode: Fixed-point: binary point scaling
                Signedness: Signed
                WordLength: 16
            FractionLength: 15

  4. Aggiornare lo script per testare il caso del limite negativo. Eseguire mysum_mex con l’input casuale originale e con un input che verifichi l’intero intervallo e aggreghi i risultati.

    %% Build instrumented mex
    T = mytypes('scaled');
    n = 10;
    
    buildInstrumentedMex mysum ...
        -args {zeros(n,1,'like',T.x),T} -histogram
    
    %% Test inputs
    rng default
    x = cast(2*rand(n,1)-1,'like',T.x);
    y = mysum_mex(x,T);
     % Run once with this set of inputs
    y_expected = sum(double(x));
    err = double(y) - y_expected
    
    % Run again with this set of inputs. The logs will aggregate.
    x = -ones(n,1,'like',T.x);
    y = mysum_mex(x,T); 
    y_expected = sum(double(x));
    err = double(y) - y_expected 
    
    % Verify results
    
    showInstrumentationResults mysum_mex ...
        -defaultDT numerictype(1,16) -proposeFL
    
    y_expected = sum(double(x));
    
    err = double(y) - y_expected
    
    %% Generate C code
    
    codegen mysum -args {x,T} -config:lib -report
    

  5. Eseguire nuovamente lo script di prova.

    Il test viene eseguito e y supera l’intervallo del tipo di dati in virgola fissa. showInstrumentationResults propone una nuova lunghezza della frazione di 11 per y.

    Screenshot of instrumentation results showing a Proposed FL of 11 for y.

  6. Aggiornare lo script di prova per utilizzare i doppi ridimensionati con il nuovo tipo proposto per y. In myTypes.m, per il caso 'scaled', T.y = fi([],true,16,11,'DataType','ScaledDouble')

  7. Eseguire nuovamente lo script di prova.

    Non sono presenti overflow.

Generazione di codice per il tipo in virgola fissa proposto

Aggiornare la tabella dei tipi di dati per utilizzare il tipo in virgola fissa proposto e generare il codice.

  1. In myTypes.m, per il caso 'fixed', T.y = fi([],true,16,11)

  2. Aggiornare lo script di prova mysum_test, per utilizzare T = mytypes('fixed');

  3. Eseguire lo script di prova, quindi fare clic sul collegamento Visualizza Report per visualizzare il codice C generato.

    short mysum(const short x[10])
    {
      short y;
      int n;
      int i;
      int i1;
      int i2;
      int i3;
      y = 0;
      for (n = 0; n < 10; n++) {
        i = y << 4;
        i1 = x[n];
        if ((i & 1048576) != 0) {
          i2 = i | -1048576;
        } else {
          i2 = i & 1048575;
       }
       
        if ((i1 & 1048576) != 0) {
         i3 = i1 | -1048576;
        } else {
          i3 = i1 & 1048575;
        }
    
      i = i2 + i3;
      if ((i & 1048576) != 0) {
        i |= -1048576;
      } else {
        i &= 1048575;
      }
    
      i = (i + 8) >> 4;
      if (i > 32767) {
        i = 32767;
      } else {
          if (i < -32768) {
            i = -32768;
          }
        }
    
       y = (short)i;
      }
      return y;
    }

    Per impostazione predefinita, l’aritmetica fi utilizza la saturazione in overflow e l’arrotondamento al più vicino che risulta in un codice inefficiente.

Modifica delle impostazioni fimath

Per rendere più efficiente il codice generato, utilizzare le impostazioni matematiche (fimath) in virgola fissa, che sono più appropriate per la generazione di codice C: avvolgimento sull’overflow e arrotondamento per difetto.

  1. Aggiungere un caso 'fixed2' in myTypes.m:

     case 'fixed2'
          F = fimath('RoundingMethod', 'Floor', ...
               'OverflowAction', 'Wrap', ...
               'ProductMode', 'FullPrecision', ...
               'SumMode', 'KeepLSB', ...
               'SumWordLength', 32, ...
               'CastBeforeSum', true);
          T.x = fi([],true,16,15,F);
          T.y = fi([],true,16,11,F);
    

    Conseil

    Anziché inserire le proprietà fimath manualmente, è possibile utilizzare l’opzione Insert fimath del MATLAB Editor. Per ulteriori informazioni, vedere Building fimath Object Constructors in a GUI.

  2. Aggiornare lo script di prova per utilizzare 'fixed2', eseguire lo script, quindi visualizzare il codice C generato.

    short mysum(const short x[10])
    {
     short y;
     int n;
     y = 0;
     for (n = 0; n < 10; n++) {
       y = (short)(((y << 4) + x[n]) >> 4);
     }
    
      return y;
    }

    Il codice generato è più efficiente ma y viene spostata per allinearsi con x, e si perdono 4 bit di precisione.

  3. Per correggere questa perdita di precisione, aggiornare la lunghezza della parola di y a 32 bit e mantenere 15 bit di precisione per allinearsi con x.

    Aggiungere un caso 'fixed32' in myTypes.m:

     case 'fixed32'
          F = fimath('RoundingMethod', 'Floor', ...
               'OverflowAction', 'Wrap', ...
               'ProductMode', 'FullPrecision', ...
               'SumMode', 'KeepLSB', ...
               'SumWordLength', 32, ...
               'CastBeforeSum', true);
          T.x = fi([],true,16,15,F);
          T.y = fi([],true,32,15,F);
    

  4. Aggiornare lo script di prova per utilizzare 'fixed32' e eseguire lo script per generare nuovamente il codice.

    Ora, il codice generato è estremamente efficiente.

    int mysum(const short x[10])
    {
      int y;
      int n;
      y = 0;
      for (n = 0; n < 10; n++) {
        y += x[n];
      }
     
      return y;
    }

Per ulteriori informazioni, vedere Optimize Your Algorithm.