Знакомство с SystemC
 

2. ОПИСАНИЕ СРЕДСТВ SYSTEMC ДЛЯ СОЗДАНИЯ МОДЕЛЕЙ

 

На главную | К содержанию | К предыдущей главе | К следующей главе

2.1. Структура языка SystemC


На «рисунке 2.1» представлена диаграмма, иллюстрирующая главные компоненты языка SystemC [3, c.25].

Ядро Моделирования.
Понятие времени

Процессы:
Потоки и Методы

Каналы и Интерфейсы

Типы данных

События

Модули и
Иерархия

Рисунок 2.1 – Главные компоненты языка SystemC


2.2. Ядро моделирования. Понятие времени


Имитатор SystemC имеет 3 главные фазы работы: разработка, выполнение и постобработка [1]. Выполнение всех операторов до конструкции sc_start() есть фаза разработки. На этом этапе происходит инициализация структур данных и подготовка к следующей фазе выполнения. Фаза выполнения передает управление ядру моделирования SystemC, которое управляет работой всех процессов и создает иллюзию параллельности их выполнения. Постобработка связана с удалением всех созданных структур данных, освобождением памяти и завершением этапа моделирования.


Принцип работы ядра моделирования SystemC схож с языками VHDL и Verilog. Если обратиться к Verilog и VHDL, то может показаться, что каждый процесс начинается сразу. В действительности проходит некоторое время между инициализацией кода и началом моделирования. В SystemC, также как и в C/C++, есть строго определенная точка входа в программу. В случае  SystemC – sc_main().


В библиотеке SystemC существует тип данных sc_time для того чтобы измерять время в процессе моделирования. У времени есть две составляющие: числовое значение и размерность. Языком поддерживается измерение времени в секундах, миллисекундах, микросекундах, наносекундах, пикосекундах, фемтосекундах.


Соответствующие спецификаторы времени следующие:

  • SC_SEC // секунды
  • SC_MS // миллисекунды
  • SC_US // микросекунды
  • SC_NS // наносекунды
  • SC_PS // пикосекунды
  • SC_FS // фемтосекунды

         Объявление переменных времени выглядит следующим образом:


sc_time имя_переменной (числовой значение, спецификатор);
sc_time t_Period(10, SC_NS);


Над переменными типа sc_time SystemC позволяет производить операции сложения, вычитания, масштабирования и др. В библиотеке определена константа SC_ZERO_TIME, которая соответствует времени 0.


2.2.1. Метод sc_start() и sc_stop()


Метод sc_start() запускает фазу моделирования, которая состоит из инициализации и выполнения. Метод может принимать параметр типа sc_time, который является ограничением максимального времени моделирования. Без параметра sc_start() запускает фазу моделирования, которая будет протекать бесконечно, пока в программе не встретится метод sc_stop(), который принудительно завершает моделирование.


2.2.2. Метод wait()


Метод wait() используется в SystemC для того чтобы моделировать задержки реальных действий (например: механических воздействий, химических реакций или распространения сигнала). С помощью метода можно приостановить выполнение процесса SC_THREAD на промежуток времени или до появления какого-либо события:


sc_time t_delay(25, SC_FS);
wait(t_delay);


2.2.3. Метод sc_time_stamp()


Данный метод позволяет определить в момент вызова, сколько времени прошло с начала моделирования, например:


sc_time t_delay1(10, SC_NS);
sc_time t_delay2(25, SC_NS);
cout << “Текущее время моделирования: ” << sc_time_stamp();
wait(t_dalay1);
cout << “Текущее время моделирования: ” << sc_time_stamp();
wait(t_delay2);
cout << “Текущее время моделирования: ” << sc_time_stamp();
В результате исполнения данного проекта, мы получем на экране следующие сообщения:
Текущее время моделирования: 0 s
Текущее время моделирования: 10 ns
Текущее время моделирования: 35 ns


2.3. Типы данных


В «таблице 2.1» представлены типы данных, поддерживаемые SystemC. В «таблице 2.2» представлены операции над данными, поддерживаемые SystemC [1].


Таблица 2.1 – Основные типы данных, поддерживаемые SystemC


Тип данных

Описание

sc_bit

Одиночный бит, принимающий значение true или false. Использовать данных тип не рекомендуется, более предпочтительно применение типа bool

sc_bv< n>

Вектор, содержащий n бит. Рекомендуется использовать sc_uint <n>, где это возможно

sc_logic

Одиночный бит, который принимает значения 0, 1, X, Z

sc_lv<n>

Вектор, содержащий n бит, типа sc_logic

sc_int<n>

Вектор, содержащий n целых чисел, размером 64 бит

sc_uint< n>

Беззнаковое sc_int <n>

sc_bigint<n>

Вектор, содержащий n целых чисел, размером более 64 бит

sc_biguint< n>

Беззнаковое sc_bigint <n>

         К поддерживаемым типам данных также относятся другие типы данных С++, такие как bool, int, unsigned int, long, unsigned long, char, unsigned char, short, unsigned short, struct, enum.


Таблица 2. Операции над данными, поддерживаемые SystemC


Операции

Описание

&(and), |(or), ^(xor), and ~(not)

Логические операции

<<(shift left) and >>(shift right)

Логические сдвиги

=, &=, |=, ^=, +=, -=, *=, /=, and %=

Префиксные операции

==, !=

Равенство

<, <=, >, >=

Сравнение

++ и --

Инкремент и декремент

 [x]

Индексирование

(x-y)

Беззнаковое sc_bigint <n>

(x,y)

Конкатенация

to_uint( ) и to_int( )

Преобразование типов

2.4. Модули и Иерархия


2.4.1. Модуль (SC_MODULE)


Модуль в SystemC – это базовый элемент, включающий в себя процессы (processes) и другие модули. SC_MODULE – макроc C++. На «рисунке 2.2» изображен модуль, который включает в себя несколько процессов.



Рисунок 2.1 – Модуль, содержащий несколько процессов


Описание каждого модуля на SystemC содержится в заголовочном файле module_name.h и содержит:

  • Объявление портов;
  • Объявление внутренних сигналов;
  • Объявление внутренних переменных;
  • Объявление экземпляров подмодулей;
  • Конструктор;
  • Деструктор;
  • Процессы;
  • Функции процессов;
  • Вспомогательные функции.

        Пример описания заголовочного файла модуля SystemC:


#include "systemc.h"
SC_MODULE (имя модуля)
{
SC_CTOR (module_name) // Конструктор
{
//Объявление процессов
//Объявление чувствительного списка
}
};


2.4.2. Порты (sc_in, sc_out, sc_inout)


Порты необходимы для взаимодействия модулей между собой. Каждый модуль может включать любое количество входных (inputs), выходных (outputs) и смешанных (inouts) портов.


Порты - это члены объекта SC_MODULE. Вы можете объявлять любое число портов типа sc_in, sc_out, sc_inout. Конструкция sc_inout позволяет читать значение из выходного порта модуля.
Пример описания портов на SystemC:


SC_MODULE (module_name)
{
sc_in< bool > port_name;
sc_out< char > port_name;
sc_inout< sc_logic > port_name;
SC_CTOR ( module_name)
{
// Объявление процессов
// Объявление списка чувствительных сигналов
}
};


2.4.3. Сигналы (sc_signal)


Порты используются для взаимодействия модуля с другими модулями. Внутри модуля необходимо использовать внутренние сигналы (Internal Signals) для передачи данных от одного процесса к другому.
Пример описания сигналов на SystemC:


SC_MODULE (module_name)
{
// Объявление портов
sc_in< port_type> port_name;
sc_out< port_type> port_name;
// Объявление сигналов
sc_signal< signal_type> signal_name;
sc_signal< signal_type> signal1, signal2;
SC_CTOR ( module_name)
{
// Объявление процессов
// Объявление списка чувствительных сигналов
}
};


2.4.4. Переменные (Variables)


Внутри модуля вам предоставляется возможность создавать переменные любого типа, поддерживаемого SystemC и C++. Эти переменные могут участвовать во всех взаимодействиях внутри создаваемого модуля.
Пример описания переменных на SystemC:


SC_MODULE (module_name)
{
// Объявление портов
sc_in< port_type> port_name;
sc_out< port_type> port_name;
// Объявление внутренних сигналов
sc_signal< signal_type> signal_name;
// Объявление внутренних переменных
int count_val; //Internal counter
sc_int<8> mem[1024]; // Массив sc_int
SC_CTOR ( module_name)
{
// Объявление процессов
// Объявление списка чувствительных сигналов
}
};


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


2.4.5. Вспомогательные функции


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


2.4.6. Чтение и запись портов и сигналов


С помощью методов чтения и записи можно считывать значения из портов или сигналов, а также записывать значения в порты или сигналы. Рекомендуется использовать функции read() и write() для чтения и записи значений:


address = into.read(); // чтение адреса
temp1 = address; // сохранение адреса
data_tmp = memory[address]; // чтение адреса из памяти
outof.write(data_tmp); // запись в выходной порт


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


sc_signal <sc_int<8>> a;
sc_int<8> b;
bool c;
b = a.read();
c = b[0];
// c = a[0]; //Данная запись не корректна в SystemC


Изменение значения при присваивании сигналу или выходному порту не происходит до завершения процесса. В случае с внутренними переменными присваивание происходит мгновенно.


2.4.7. Создание иерархического модуля (Hierarchical Module)


Чтобы создать иерархический модуль, необходимо:

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

2.5. Процессы: Потоки и Методы


Процессы в SystemC необходимы для того, чтобы в системе описать параллельное поведение физических элементов [3, c.77]. Это означает, что все процессы выполняются одновременно, а не последовательно как функции в C++.


Описание процесса схоже с описанием функции на C++. Процесс объявляется как функция класса SC_MODULE и регистрируется в его конструкторе. Процесс не принимает параметров и не возвращает значения.


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


Процесс способен читать и записывать значения входных и выходных портов, внутренних сигналов (Internal Signals) и внутренних переменных (Internal variables).


Самый простой тип процесса в SystemC это SC_THREAD. Процесс SC_THREAD в SystemC идентичен программному потоку. В C/C ++ программы, есть только один поток, работающий для всей программы. Ядро SystemC позволяет многим потокам выполняться параллельно. Процесс SC_THREAD запускается в процессе моделирования только один раз, однако его выполнение можно приостановить методом wait(). При завершении процесса, запустить его повторно нельзя, поэтому, данный тип процесса, как правило, содержит бесконечный цикл, имеющий по крайней мере один вызов функции wait().


В отличие от процесса SC_THREAD, процесс SC_METHOD никогда не приостанавливается (вызов функции wait() внутри процесса SC_METHOD приведет к ошибке), однако может быть вызван несколько раз в процессе моделирования, что делает его в некоторых случаях более предпочтительным. Данный тип процесса подобен процессам языков VHDL и Verilog. SC_METHOD поддерживает конструкцию next_trigger(), которая определяет динамическую чувствительность процесса (последующего вызова).


Процесс SC_CTHREAD является небольшой модификацией процесса SC_THREAD с добавлением возможности динамической чувствительности, речь о котором в данной работе не пойдет.


Процесс в SystemC объявляется в теле модуля и регистрируется как процесс внутри конструктора. Необходимо объявлять процесс как функцию void, не содержащую аргументов. При регистрации функции как SC_METHOD процесс, необходимо использовать конструкцию SC_METHOD, которая имеет один аргумент – имя процесса.


Пример описания процесса на SystemC:


SC_MODULE(my_module)
{
sc_in<int> a;
sc_in<bool> b;
sc_out<int> x;
sc_out<int> y;
sc_signal<bool>c;
sc_signal<int> d;
void my_method_proc();
SC_CTOR(my_module)
{
SC_METHOD(my_method_proc);
// Объявление списка чувствительных сигналов
}
};


Процессы реагируют на изменение сигналов, которые находятся в списке чувствительных входов. Возможно использование функций sensitive(), sensitive_pos() или sensitive_neg() или потоков sensitive, sensitive_pos, sensitive_neg при описании списка чувствительности (sensitivity list).


Для комбинаторной логики, спискок чувствительных входов приравнивает все входные порты (input и inout ports) и сигналы (signals) к входным сигналам процесса. Для реализации level-sensitive входов необходимо использовать метод sensitive так, как показано в примере:


SC_MODULE(my_module)
{
sc_in<int> a;
sc_in<bool> b;
sc_out<int> y;
sc_signal<bool>c;
void my_method_proc();
SC_CTOR(my_module)
{
SC_METHOD(my_method_proc);
// Объявление списка чувствительных сигналов                                             sensitive << a << c << d; // Потоковое описание
sensitive(b); //Функциональное описание
sensitive(e); //Функциональное описание
}
};


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


void comb_proc ()
{
out_x = in_a & in_b & in_c;
}
SC_CTOR( comb_logic_complete )
{
SC_METHOD( comb_proc);
sensitive << in_a << in_b; // пропущен in_c
}


Пример описания заголовочного файла модели элемента 3И на SystemC:
#include "systemc.h"


SC_MODULE(and3)
{
sc_in <bool> A, B, C;
sc_out <bool> F;
void do_and3()
{
F.write( A.read() && B.read() && C.read() );
}
SC_CTOR(and3)
{
SC_METHOD(do_and3);
sensitive << A << B << C;
}
};


Конструкция Edge-Sensitive используется для реализации последовательностной логики, при моделировании триггеров. Для этого необходимо использовать такие потоки как sensitive_neg (срез), sensitive_pos (фронт). Входные порты должны иметь тип sc_in<bool>, ниже приведен пример использования конструкции edge-sensitive:


SC_MODULE(my_module)
{
sc_in<int> a;
sc_in<bool> b;
sc_in<bool> clock;
sc_out<int> y;
sc_in<bool> reset;
sc_signal<bool>c;
void my_method_proc();
SC_CTOR(my_module)
{
SC_METHOD(my_method_proc);
sensitive_pos (clock); // Функциональное описание
sensitive_neg << b << reset; // Потоковое описание
}
};


Ограничения при использовании Sensitivity Lists:

  • Нельзя совмещать конструкции Level-Sensitive и Edge-Sensitive в одном процессе;
  • Нельзя применять тип sc_logic для реализации синхросигнала (clock) или других Edge-Sensitive’s входов. Допустимым является только тип sc_in<bool>.

         Пример описания заголовочного файла JK-триггера на SystemC:


#include "systemc.h"
SC_MODULE(jk)
{
sc_in <bool> J, K, Clock, Reset;
sc_out <bool> F, NF;
void do_jk()
{
if(!Clock.read())
{
if(J.read() == true && K.read() == true)
{
if(F.read())
{
F.write(false);
NF.write(true);
}
else
{
F.write(true);
NF.write(false);
}
}
if(J.read() == true && K.read() == false)
{
F.write(true);
NF.write(false);
}
if(J.read() == false && K.read() == true)
{
F.write(false);
NF.write(true);
}
}
if(!Reset.read())
{
F.write(false);
NF.write(true);
}
}
SC_CTOR(jk)
{
SC_METHOD(do_jk);
sensitive << Clock.neg();
sensitive << Reset.neg();
}
};


2.6. События


Событие – это нечто, происходящее в определенное время. У события нет никакого значения и продолжительности, оно либо произошло, либо еще нет. В SystemC событие определяется с помощью класса sc_event. Процессы могут реагировать на события, но для этого в списке чувствительности процесса должно быть явно указано событие, к которому он будет чувствителен, например,
sc_event delay;

SC_METHOD(do_delay);
sensitive << delay;


Событие можно вызвать с помощью метода notify(), в качестве параметра может быть указан промежуток времени в формате sc_time, через которое событие должно произойти (в таком случае, событие будет запланированным), например,


// Событие произойдет немедленно
delay.notify();
// Событие произойдет через промежуток времени 0
delay.notify(SC_ZERO_TIME);
// Событие произойдет через промежуток времени 20 нс (запланированное событие)
delay.notify(20, SC_NS);
Отменить вызов запланированного события можно с помощью метода cancel():
delay.cancel();


Ниже приведен пример модели логического элемента НЕ7404, задержка срабатывания которого реализовано с помощью события:


SC_MODULE(not)
{
sc_in <bool> A;
sc_out <bool> F;
sc_event delay;
SC_CTOR(not)
{
SC_METHOD(do_delay);
sensitive << A;
SC_METHOD(do_not);
sensitive << delay;
}
void do_delay(){delay.notify(22, SC_NS);}
void do_not(){F.write(!A.read());}
};


В данном примере временная задержка реализована с использованием события. Событие delay, объявленное в теле модуля происходит тогда, когда, на входе элемента НЕ меняется входной сигнал (sc_in <bool> A;), метод do_delay чувствителен к изменению сигнала A. Команда delay.notify(22, SC_NS) активизирует событие delay спустя 22 ns, в свою очередь метод do_not, чувствительный к событию delay, реагирует и меняет значение на выходе елемента НЕ.


2.7. Интерфейсы и Каналы


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


Интерфейс в SystemC – это абстрактный класс, наследуемый от базового класса sc_interface, в котором объявлены виртуальные методы, которые используют каналы и порты [3, c.130].


Канал в SystemC – это класс, который осуществляет один или более интерфейсов SystemC, наследуется от sc_channel или от sc_prim_channel [3, c.130].


В случае использования интерфейсов, мы можем создавать модули, не зависимые от реализации каналов коммуникации, примером интерфейса в SystemC может служить sc_mutex_if. Примитивный канал, реализующий данный интерфейс – это sc_mutex. Данный канал используется при моделировании разделяемого ресурса:


sc_mutex NAME;
NAME.lock(); // Чтобы занять ресурс NAME
NAME.trylock(); // Чтобы занять ресурс NAME (true – ресурс не занят, false – иначе).
NAME.unlock() // Освободить ресурс


В SystemC существуют также другие примитивные каналы, такие как sc_fifo и sc_semaphore. Sc_signal, речь о котором шла в предыдущих главах данной работы, тоже является каналом SystemC.


1. SC_MODULE и SC_CTOR - это макросы C++, описанные в библиотеке классов SystemC.

На главную | К содержанию | К предыдущей главе | К следующей главе