exponenta event banner

Преобразование алгоритма MATLAB с плавающей запятой в фиксированную точку вручную

В этом примере показано, как преобразовать алгоритм с плавающей запятой в фиксированную точку, а затем создать код C для алгоритма. В примере используются следующие передовые практики:

  • Отделите алгоритм от тестового файла.

  • Подготовьте свой алгоритм для создания контрольно-измерительных приборов и кода.

  • Управление типами данных и рост битов.

  • Отделите определения типов данных от алгоритмического кода, создав таблицу определений данных.

Полный список рекомендаций см. в разделе Рекомендации по преобразованию фиксированных точек вручную.

Отделите свой алгоритм от тестового файла

Напишите функцию MATLAB ® ,mysum, что суммирует элементы вектора.

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

Поскольку требуется только преобразовать алгоритмическую часть в фиксированную точку, более эффективно структурировать код так, чтобы алгоритм, в котором выполняется обработка ядра, был отделен от тестового файла.

Запись тестового сценария

В тестовом файле создайте входные данные, вызовите алгоритм и постройте график результатов.

  1. Напишите сценарий MATLAB, mysum_test, что проверяет поведение алгоритма с использованием двойных типов данных.

    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 помещает настройки генератора случайных чисел, используемого функцией rand, в значение по умолчанию, так что он создает те же случайные числа, что и при перезапуске MATLAB.

  2. Запустите тестовый сценарий.

    mysum_test
    err =
    
         0

    Результаты, полученные с использованием mysum соответствуют полученным с помощью MATLAB sum функция.

Дополнительные сведения см. в разделе Создание тестового файла.

Подготовка алгоритма для создания КИПиА и кода

В алгоритме после подписи функции добавьте %#codegen директива компиляции, указывающая на то, что вы намереваетесь внедрить алгоритм и сгенерировать для него код C. Добавление этой директивы предписывает анализатору кода MATLAB помочь в диагностике и устранении нарушений, которые могут привести к ошибкам при создании кода и контрольно-измерительных приборов.

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

Для этого алгоритма индикатор анализатора кода в правом верхнем углу окна редактора остается зеленым, указывая, что он не обнаружил никаких проблем.

Дополнительные сведения см. в разделе Подготовка алгоритма для ускорения кода или создания кода.

Создание кода C для исходного алгоритма

Создайте код C для исходного алгоритма, чтобы убедиться, что алгоритм подходит для генерации кода и увидеть код C с плавающей запятой. Используйте codegen Функция (MATLAB Coder) (требует MATLAB Coder™) для создания библиотеки C.

  1. Добавьте следующую строку в конец тестового сценария, чтобы создать код C для mysum.

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

  2. Запустите сценарий теста еще раз.

    Кодер MATLAB генерирует код C для mysum и предоставляет ссылку на отчет о создании кода.

  3. Щелкните ссылку, чтобы открыть отчет о создании кода и просмотреть созданный код C для 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;
     }

    Поскольку C не позволяет использовать индексы с плавающей запятой, счетчик цикла, n, автоматически объявляется как целочисленный тип. Преобразование не требуется n в фиксированную точку.

    Вход x и выходные данные y объявлены как двойные.

Управление типами данных и рост битов управления

Протестируйте свой алгоритм с помощью синглов для проверки несовпадений типов

  1. Измените тестовый файл таким образом, чтобы тип данных x одинарный.

    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. Запустите сценарий теста еще раз.

    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.

    Ошибка создания кода, сообщение о несоответствии типов данных в строке y = y + x(n);.

  3. Чтобы просмотреть ошибку, откройте отчет.

    В отчете, на строке y = y + x(n), в докладе подчеркивается y в левой части назначения красным цветом, что указывает на наличие ошибки. Проблема в том, что y объявлен как двойной, но назначается одному. y + x(n) - сумма двойника и одиночки, которая является одиночкой. При наведении курсора на переменные и выражения в отчете можно просмотреть информацию об их типах. Здесь видно, что выражение, y + x(n) является одиночкой.

  4. Чтобы исправить несоответствие типов, обновите алгоритм, чтобы использовать подстрочное назначение для суммы элементов. Изменение y = y + x(n) кому y(:) = y + x(n).

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

    Использование подстрочного назначения также предотвращает рост битов, который является поведением по умолчанию при добавлении чисел с фиксированной точкой. Дополнительные сведения см. в разделе Рост битов. Предотвращение роста битов важно, потому что вы хотите поддерживать фиксированные типы точек во всем коде. Дополнительные сведения см. в разделе Управление ростом битов.

  5. Повторно создайте код C и откройте отчет о создании кода. В коде C результат теперь сдвоен для устранения несоответствия типов.

Создать инструментальный Mex

Используйте buildInstrumentedMex функция для управления алгоритмом регистрации минимальных и максимальных значений всех именованных и промежуточных переменных. Используйте showInstrumentationResults функция для предложения типов данных с фиксированной точкой на основе этих записанных значений. Позже эти предлагаемые типы фиксированных точек будут использоваться для проверки алгоритма.

  1. Обновить сценарий теста:

    1. После объявления n, добавить buildInstrumentedMex mySum —args {zeros(n,1)} -histogram.

    2. Изменение x назад, чтобы удвоить. Заменить x = single(2*rand(n,1)-1); с x = 2*rand(n,1)-1;

    3. Вместо вызова исходного алгоритма вызовите сгенерированную функцию MEX. Изменение y = mysum(x) кому y=mysum_mex(x).

    4. После вызова функции MEX добавьте showInstrumentationResults mysum_mex -defaultDT numerictype(1,16) -proposeFL. -defaultDT numerictype(1,16) -proposeFL флаги указывают, что требуется предложить длины дробей для 16-битной длины слова.

      Вот обновленный сценарий теста.

      %% 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. Запустите сценарий теста еще раз.

    showInstrumentationResults функция предлагает типы данных и открывает отчет для просмотра результатов.

  3. В отчете перейдите на вкладку Переменные. showInstrumentationResults предлагает длину фракции 13 для y и 15 для x.

В отчете можно выполнить следующие действия:

  • Просмотр минимального и максимального значений моделирования для входных данных x и выходные данные y.

  • Просмотр предлагаемых типов данных для x и y.

  • Просмотр информации для всех переменных, промежуточных результатов и выражений в коде.

    Чтобы просмотреть эту информацию, наведите курсор на переменную или выражение в отчете.

  • Просмотр данных гистограммы для x и y помогает определить любые значения, находящиеся вне диапазона или ниже точности на основе текущего типа данных.

    Чтобы просмотреть гистограмму для определенной переменной, щелкните значок ее гистограммы.

Отделить определения типов данных от алгоритмического кода

Вместо того чтобы вручную изменять алгоритм для проверки поведения для каждого типа данных, отделите определения типов данных от алгоритма.

Изменить mysum так что он принимает входной параметр, T, которая представляет собой структуру, определяющую типы данных входных и выходных данных. Когда y сначала определяется, используйте cast функция, как синтаксис - cast(x,'like',y) - для литья x к требуемому типу данных.

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

Создание таблицы определений типов данных

Напишите функцию, mytypesопределяет различные типы данных, которые необходимо использовать для тестирования алгоритма. В таблице типов данных включите двойные, одиночные и масштабированные двойные типы данных, а также типы данных с фиксированной точкой, предложенные ранее. Перед преобразованием алгоритма в фиксированную точку рекомендуется:

  • Проверьте соединение между таблицей определения типа данных и алгоритмом с помощью двойных значений.

  • Протестируйте алгоритм с одиночными кодами, чтобы найти несовпадения типов данных и другие проблемы.

  • Запустите алгоритм с помощью масштабированных двойников для проверки переполнений.

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

Дополнительные сведения см. в разделе Отдельные определения типов данных из алгоритма.

Обновить тестовый сценарий для использования таблицы типов

Обновление тестового сценария, mysum_test, для использования таблицы типов.

  1. Для первого прогона проверьте соединение между таблицей и алгоритмом с помощью двойников. Перед объявлением n, добавить T = mytypes('double');

  2. Обновить вызов до buildInstrumentedMex для использования типа T.x в таблице типов данных: buildInstrumentedMex mysum -args {zeros(n,1,'like',T.x),T} -histogram

  3. Бросок x для использования типа T.x указано в таблице: x = cast(2*rand(n,1)-1,'like',T.x);

  4. Вызов функции MEX при входе T: y = mysum_mex(x,T);

  5. Звонить codegen прохождение T: codegen mysum -args {x,T} -config:lib -report

    Вот обновленный сценарий теста.

    %% 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. Запустите сценарий тестирования и щелкните ссылку, чтобы открыть отчет о создании кода.

    Сгенерированный код C совпадает с кодом, сгенерированным для исходного алгоритма. Потому что переменная T используется для указания типов, и эти типы являются постоянными во время генерации кода; T не используется во время выполнения и не отображается в созданном коде.

Создание кода с фиксированной точкой

Обновите сценарий тестирования для использования типов с фиксированной точкой, предложенных ранее, и просмотрите сгенерированный код C.

  1. Обновите сценарий тестирования для использования типов с фиксированной точкой. Заменить T = mytypes('double'); с T = mytypes('fixed'); а затем сохраните сценарий.

  2. Запустите сценарий тестирования и просмотрите сгенерированный код C.

    Эта версия кода C не очень эффективна; он содержит много обработки переполнения. Следующим шагом является оптимизация типов данных во избежание переполнения.

Оптимизация типов данных

Использование масштабированных двойников для обнаружения переполнения

Масштабированные двойники - это гибрид между числами с плавающей и фиксированной точками. Designer™ Fixed-Point сохраняет их как двойные с сохранением информации о масштабировании, знаке и длине слова. Поскольку вся арифметика выполняется с двойной точностью, можно увидеть любые возникающие переполнения.

  1. Обновите тестовый сценарий, чтобы использовать масштабированные двойники. Заменить T = mytypes('fixed'); с T = mytypes('scaled');

  2. Запустите сценарий теста еще раз.

    Тест запускается с использованием масштабированных двойных значений и отображает отчет. Переполнения не обнаружены.

    До сих пор вы запускаете сценарий теста с использованием случайных входных данных, что означает, что маловероятно, что тест выполнил полный рабочий диапазон алгоритма.

  3. Найдите полный диапазон входных данных.

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

  4. Обновите сценарий для проверки отрицательного варианта края. Управляемый mysum_mex с исходным случайным вводом и с вводом, который проверяет весь диапазон и агрегирует результаты.

    %% 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. Запустите сценарий теста еще раз.

    Тестовые запуски и y переполняет диапазон типа данных с фиксированной точкой. showInstrumentationResults предлагает новую длину фракции 11 для y.

  6. Обновление тестового сценария для использования масштабированных двойников с новым предлагаемым типом для y. В myTypes.m, для 'scaled' дело, T.y = fi([],true,16,11,'DataType','ScaledDouble')

  7. Повторно запустите тестовый сценарий.

    Переливов сейчас нет.

Создание кода для предлагаемого типа с фиксированной точкой

Обновите таблицу типов данных, чтобы использовать предлагаемый тип с фиксированной точкой и создать код.

  1. В myTypes.m, для 'fixed' дело, T.y = fi([],true,16,11)

  2. Обновление тестового сценария, mysum_test, для использования T = mytypes('fixed');

  3. Запустите сценарий тестирования и щелкните ссылку Просмотр отчета, чтобы просмотреть сгенерированный код C.

    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;
    }

    По умолчанию fi арифметика использует насыщение при переполнении и ближайшее округление, что приводит к неэффективному коду.

Изменить параметры fimath

Чтобы сделать созданный код более эффективным, используйте математику с фиксированной точкой (fimath), которые более подходят для генерации кода C: наложение при переполнении и округление пола.

  1. В myTypes.m, добавить 'fixed2' случай:

     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);
    

    Совет

    Вместо ввода вручную fimath можно использовать опцию «Вставить fimath» редактора MATLAB. Дополнительные сведения см. в разделе Построение конструкторов объектов fimath в графическом интерфейсе пользователя.

  2. Обновить тестовый сценарий для использования 'fixed2', запустите сценарий и просмотрите созданный код C.

    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;
    }

    Созданный код более эффективен, но y смещается для выравнивания с x и теряет 4 бита точности.

  3. Чтобы исправить эту потерю точности, обновите длину слова y до 32 бит и сохранить 15 бит точности для выравнивания с x.

    В myTypes.m, добавить 'fixed32' случай:

     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. Обновить тестовый сценарий для использования 'fixed32' и запустите сценарий, чтобы снова создать код.

    Теперь созданный код очень эффективен.

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

Дополнительные сведения см. в разделе Оптимизация алгоритма.