Текущий раздел:

Описания RDS

Руководство программиста

Глава 2. Создание моделей блоков

§2.5. Статические переменные блоков

§2.5.7. Использование входов со связанными сигналами

# Руководство программиста

## Глава 2. Создание моделей блоков

### §2.5. Статические переменные блоков

#### §2.5.7. Использование входов со связанными сигналами

Описывается работа с входами блока, для которых заданы связанные сигналы – по этим сигналам можно понять, какие из входов блока сработали в данном такте расчета. Приводится пример модели блока, вычисляющей произведение минимального элемента первой входной матрицы и максимального элемента второй входной матрицы. Анализируя связанные сигналы входов, эта модель вычисляет максимальный или минимальный элемент только для изменившейся матрицы, экономя тем самым процессорное время.

В режиме расчета, когда модели блоков схемы постоянно вызываются в цикле и с заданной периодичностью перерисовываются все открытые окна, нагрузка на систему увеличивается. Если модель производит какие-либо сложные вычисления (например, итеративный расчет или обработку матриц), задержка, вызванная этими вычислениями, повторяемыми в цикле, может снизить быстродействие системы. Поэтому обычно стараются сделать так, чтобы модели блоков вызывались только тогда, когда это действительно необходимо. Обычно для этого в параметрах блока отключают флаг «запуск каждый такт» и устанавливают флаги «пуск» для всех входов блока, в результате чего модель будет вызываться только при срабатывании связи, подключенной к любому из входов блока. Если ни одна из связей не сработала, значит, значения входов блока не изменились, и, в большинстве случаев, новые значения выходов вычислять не обязательно (если блок подписан на динамические переменные, ему необходимо также отслеживать и их изменения, но для этого существуют другие методы, которые описаны в §2.6).

Иногда информации о том, что значение какого-то входа блока изменилось, бывает недостаточно. Часто модели необходимо знать, какой именно вход получил новое значение – это позволит ей выполнить только те вычисления, которые связаны с изменившимся входом. Для того, чтобы модель могла узнать об изменении конкретного входа, необходимо добавить в блок дополнительную переменную-сигнал (неважно, будет она входом, выходом или внутренней) и связать с ней нужный вход блока, задав для этого входа тип «вход/сигнал» вместо «вход» и указав имя сигнальной переменной (см. рис. 9). При срабатывании связи, подключенной к данному входу, связанной сигнальной переменной автоматически будет присвоено значение 1. Появление единицы в одной или нескольких связанных переменных укажет модели блока на входы, значения которых изменились в предыдущем такте расчета. Выполнив необходимые вычисления, модель должна присвоить всем связанным переменным значение 0, подготовив их тем самым к следующему срабатыванию. Связанные переменные, как и обычные сигналы, не могут получить нулевое значение по подключенной связи – при срабатывании связи сигнальная переменная принимает значение 1 и сохраняет его до тех пор, пока модель блока самостоятельно не обнулит переменную. Допускается связывание нескольких входов с одним сигналом, если модель блока должна выполнять одинаковые действия при изменении любого из этих входов.

Рассмотрим в качестве примера блок, вычисляющий произведение минимального элемента входной матрицы вещественных чисел «M1» и максимального элемента такой же входной матрицы «M2». Для того, чтобы определить максимальное и минимальное значения, необходимо перебрать все элементы матриц. Для матриц больших размеров этот перебор может занять значительное время, поэтому модель блока будет запоминать значения максимального и минимального элементов матриц «M1» и «M2» во вспомогательных переменных «M1min» и «M2max» соответственно. Значение «M1min» будет вычисляться только при изменении входа «M1», значение «M2max» – только при изменении «M2». Для того, чтобы модель могла узнать о поступлении новых значений на входы, вход «M1» будет связан с внутренней сигнальной переменной «s1», вход «M2» – с переменной «s2». Таким образом, блок будет иметь следующую структуру переменных:

Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | --- | --- | ---
0 | Start | Сигнал | 1 | Вход | ✓ | 1
1 | Ready | Сигнал | 1 | Выход |  | 0
2 | s1 | Сигнал | 1 | Внутренняя |  | 1
3 | s2 | Сигнал | 1 | Внутренняя |  | 1
4 | M1 | Матрица double | 16 | Вход/сигнал s1 | ✓ |
20 | M2 | Матрица double | 16 | Вход/сигнал s2 | ✓ |
36 | M1min | double | 8 | Внутренняя |  | ?
44 | M2max | double | 8 | Внутренняя |  | ?
52 | y | double | 8 | Выход |  | ?

Следует обратить внимание на то, что в качестве начальных значений переменных «M1min» и «M2max» указан вопросительный знак. Вопросительным знаком в RDS обозначается специальное значение, используемое в качестве индикатора ошибки вычисления. Действительно, пока максимальный элемент «M2» и минимальный элемент «M1» не вычислены, мы ничего не можем присвоить переменным «M1min» и «M2max».

Поскольку необходимость запуска модели будет определяться срабатыванием входных связей, необходимо включить флаг «запуск по сигналу» (вместо «запуск каждый такт») на вкладке «общие» окна параметров этого блока и установить флаг «пуск» для входов «M1» и «M2» в окне редактирования переменных. Также необходимо задать переменным «Start», «s1» и «s2» начальное значение 1. При первом запуске расчета, до того, как сработает какая-либо из входных связей, модель должна вычислить значения «M1min» и «M2max» по начальным значениям «M1» и «M2». Единичное начальное значение переменной «Start» заставит модель запуститься в самом первом такте расчета, единичные начальные значения «s1» и «s2» заставят ее обработать обе входных матрицы. Начальные значения всех остальных переменных могут быть любыми.

Модель блока будет выглядеть следующим образом:

      extern "C" __declspec(dllexport)
        int RDSCALL TestMatMinMax(int CallMode,
                                   RDS_PBLOCKDATA BlockData,
                                   LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define s1 (*((char *)(pStart+2*RDS_VSZ_S)))
      #define s2 (*((char *)(pStart+3*RDS_VSZ_S)))
      #define pM1 ((void **)(pStart+4*RDS_VSZ_S))
      #define pM2 ((void **)(pStart+4*RDS_VSZ_S+RDS_VSZ_M))
      #define M1min (*((double *)(pStart+4*RDS_VSZ_S+2*RDS_VSZ_M)))
      #define M2max (*((double *)(pStart+4*RDS_VSZ_S+2*RDS_VSZ_M+ \
                    RDS_VSZ_D)))
      #define y (*((double *)(pStart+4*RDS_VSZ_S+2*RDS_VSZ_M+ \
                2*RDS_VSZ_D)))

        switch(CallMode)
          { // Проверка типа переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSSSMDMDDDD}")==0)
                return RDS_BFR_DONE;
              return RDS_BFR_BADVARSMSG;

            // Выполнение такта моделирования
            case RDS_BFM_MODEL:
              if(s1) // Изменилось значение входа M1
                { // Необходимо найти новый минимальный элемент
                  s1=0; // Сброс сигнала
                  if(RDS_ARRAYEXISTS(pM1)) // Матрица M1 не пуста
                    { // Вспомогательные переменные
                      int m1count;
                      double *m1data;
                      // Число элементов в матрице
                      m1count=RDS_ARRAYROWS(pM1)*RDS_ARRAYCOLS(pM1);
                      // Получить указатель на первый элемент
                      m1data=(double*)RDS_ARRAYDATA(pM1);
                      // Поиск минимального элемента
                      M1min=DoubleErrorValue;
                      for(int i=0;i<m1count;i++)
                        if(m1data[i]!=DoubleErrorValue &&
                           (M1min==DoubleErrorValue || M1min>m1data[i]))
                          M1min=m1data[i];
                    }
                  else // Матрица M1 пуста
                    M1min=DoubleErrorValue;
                } // if(s1)
              if(s2) // Изменилось значение входа M2
                { // Необходимо найти новый максимальный элемент
                  s2=0; // Сброс сигнала
                  if(RDS_ARRAYEXISTS(pM2)) // Матрица M2 не пуста
                    { // Вспомогательные переменные
                      int m2count;
                      double *m2data;
                      // Число элементов в матрице
                      m2count=RDS_ARRAYROWS(pM2)*RDS_ARRAYCOLS(pM2);
                      // Получить указатель на первый элемент
                      m2data=(double*)RDS_ARRAYDATA(pM2);
                      // Поиск максимального элемента
                      M2max=DoubleErrorValue;
                      for(int i=0;i<m2count;i++)
                        if(m2data[i]!=DoubleErrorValue &&
                           (M2max==DoubleErrorValue || M2max<m2data[i]))
                          M2max=m2data[i];
                    }
                  else // Матрица M2 пуста
                    M2max=DoubleErrorValue;
                } // if(s2)
              // Значения M1min и M2max определены – вычисление произведения
              if(M1min!=DoubleErrorValue && M2max!=DoubleErrorValue)
                y=M1min*M2max;
              else
                y=DoubleErrorValue;
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef y
      #undef M2max
      #undef M1min
      #undef pM2
      #undef pM1
      #undef s2
      #undef s1
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

При вызове модели с параметром RDS_BFM_MODEL прежде всего проверяется сигнал s1. Если значение s1 ненулевое, значит, в конце предыдущего такта расчета сработала связь, соединенная со входом M1, в результате чего вход получил новое значение. В этом случае переменной s1 присваивается 0 (сигнал подготавливается к следующему срабатыванию) и, если матрица M1 не пуста, производится поиск минимального элемента, который записывается во внутреннюю переменную блока M1min. Если матрица пуста, переменной M1min присваивается значение-индикатор ошибки из глобальной переменной DoubleErrorValue (подразумевается, что эта переменная инициализирована в главной функции DLL, как указано в §2.5.1).

Далее проверяется сигнал s2. Его ненулевое значение сигнализирует об изменении значения матрицы M2 и необходимости вычисления нового значения ее максимального элемента. В этом случае сигнал s2 сбрасывается, и найденный максимальный элемент матрицы записывается во внутреннюю переменную M2max. Затем произведение M1min и M2max присваивается выходу блока y, если ни одна из этих двух внутренних переменных не равна значению DoubleErrorValue, указывающему на ошибку вычисления (максимальный или минимальный элемент матрицы найти не удалось – одна из матриц пуста или во всех элементах одной из матриц находится значение-индикатор ошибки).

В результате единственная операция, выполняющаяся в модели при любом запуске – это вычисление произведения M1min и M2max. Перебор элементов матрицы M1 выполняется только при срабатывании подключенной к ней связи, так же как и перебор элементов M2. Если значения на одном из входов блока меняются редко, такая модель позволит получить выигрыш в быстродействии, поскольку вычисления для этого входа будут выполняться только при его изменениях. Если бы у блока не было связанных сигнальных переменных и модель запускалась бы при срабатывании любой связи, ей пришлось бы каждый раз перебирать элементы обеих матриц, независимо от того, какая из них изменилась.

Проверить работу созданного блока можно при помощи схемы, изображенной на рис. 34. В процессе расчета при любом изменении значений в обеих входных матрицах на числовом индикаторе должно отображаться произведение минимального элемента первой матрицы и максимального элемента второй. Если это произведение не может быть вычислено (например, если одна из матриц пуста), на индикаторе отобразится вопросительный знак.

Рис. 34. Блок, вычисляющий произведение минимального элемента одной матрицы и максимального элемента другой