OVM/OVM методология/Основы объектно-ориентированного программирования
Материал из Wiki
Содержание |
Разработка программного обеспечения, не связанная с физикой электричества и магнетизма, уже давно пытается создать многократно используемые, взаимозаменяемые, надежные компоненты. Важной моделью программирования, которая решает проблему, называется объектно-ориентированным программированием (ООП). Основная идея ООП заключается в том, что программы организуются как набор взаимодействующих объектов, каждый со своим собственным областью данных и функций. Объекты могут быть многократно использованы, потому что они инкапсулируют все, что им нужно для работы, могут быть построены с минимальным числом или без внешних зависимостей, и может быть высоко параметризованы. В этой главе вводятся основные понятия объектно-ориентированного программирования, в том числе понятия инкапсуляции и интерфейса. Глава завершается обсуждением того, почему ООП является важным для построения testbenches.
Процедурные против ООП
Чтобы понять ООП и его роль в проверке, необходимо сначала понять традиционное процедурное программирование и его ограничения. Это даст основу для понимания того, как ООП может преодолеть эти ограничения. В первые дни языка ассемблера, программисты и компьютерные архитекторы быстро обнаружили, что программы часто содержат последовательности инструкций, которые повторяются во всей программе. Повторение кода (в частности, с перфокарт) является трудоемким и порождает ошибки. Чтобы избежать трудоемкости и ошибок, вызванных повторением последовательностей, была изобретена подпрограмма. Подпрограмма является единицей повторно используемого кода. Вместо кодирования той же последовательности встроенных инструкций, вы вызываете подпрограмму. Параметры, передаваемые в подпрограмму, позволяют динамически изменять код. То есть, каждый вызов подпрограммы с различными значениями параметров заставляет подпрограмму вести себя по-разному в зависимости от конкретных значений параметров.
Каждый язык программирования любой значимости имеет конструкции для создания подпрограмм, процедур и функций, а также синтаксис для передачи в них параметров и возвращаемых значений. Эти функции могут использоваться для создания операций, которые часто используются. Тем не менее, некоторые операции очень распространены (таких как I / O, преобразования данных, численные методы и т. д.). И во избежание переписывания этих операций, программисты посчитали необходимым создать библиотеки часто используемых функций. В результате, большинство языков программирования включает такие библиотеки как часть пакета компилятора. Одним из наиболее известных примеров является библиотека C, которая поступает с каждым компилятором C. Она содержит полезные функции, такие как printf() (), COS (), atof (), и QSort (). Эти функции практически каждый программист будет использовать в тот или иной момент. Представьте себе, что писать собственные подпрограммы ввода / вывода или ваши собственные вычисления для преобразования чисел в строки и строк в числа. Был момент, когда программисты так и делали. Библиотеки универсальных функций все изменили и увеличили общую производительность программирования.
Как показывает практика программного обеспечения и передовых технологий, программисты начали думать на более высоком уровне абстракции, чем инструкции и процедуры. Вместо написания отдельных инструкций, программисты пишут код на языках, которые обеспечивают более высокую модель абстракции компьютера, и компиляторы или интерпретаторы переводят эти модели в конкретные инструкции. Библиотеки, такие как C библиотеки или STL в C + +, являются одной из форм абстракции. Они представляет собой набор функций, которые программисты могут использовать для создания все более сложных программы или абстракции. В своей основополагающей книге Алгоритмы + Структуры данных = Программы, Никлаус Вирт объясняет, что для решения любой задачи программирования, вы должны разработать абстракцию реальности, которая имеет характеристики и свойства задачи, и игнорировать остальные детали. Он утверждает, что набор данных, которые вам нужны для решения проблемы, формирует абстракцию. Поэтому, прежде чем вы сможете решить проблема, в первую очередь необходимо определить, какие данные нужно иметь, чтобы создать решение. Чтобы продолжить создание многократно используемой абстракции, мы должны создавать библиотеки данных объектов, которые могут быть многократно использованы для решения конкретных видов проблем. Поиск способа сделать это приводит к развитию объектно-ориентированных технологий.
Объектно-ориентированный анализ и проектирование программ сосредоточен вокруг данных объектов, функциональности, связанной с каждым объектом, и отношения между объектами.
Объектно-ориентированные языки предоставляют средства для отделения проблем программы, и выделения каждой из них независимо друг от друга, и для инкапсуляции данных абстракций и представление их через строго определенные интерфейсы. Полностью объектно-ориентированная программа строится путем разделения функциональности программы в различные классы, определения интерфейса для каждого класса, а затем установления связей и взаимодействий между компонентами через их интерфейсы.
Классы и объекты
Основным модулем программирования на объектно-ориентированных языках, таких как SystemVerilog, является класс. Класс содержит элементы данных, называемые членами, и задач и функций, называемых методами. Для выполнения объектно-ориентированных программ, вы должны создать один или более классов в основной программе, а затем вызвать методы на различных объектах. Хотя термины класс и объект иногда используются как синонимы, как правило, термин класс относится к классу декларации объекта, а термин объект относится к экземпляру класса. Чтобы проиллюстрировать эти понятия, ниже приведен пример простой класс register.
class register; local bit[31:0] contents; function void write(bit[31:0] d) contents = d; endfunction function bit[31:0] read(); return contents; endfunction endclass
Это очень простой класс с одной переменной, и двумя методами, read() и write(). Чтобы использовать этот класс, вы создаете объекты путем создания экземпляра класса, а затем вызвать методы объекта, как показано ниже
module top; register r; bit[31:0] d; initial begin r = new(); r.write(32’h00ff72a8); d = r.read(); end endmodule
Локальный атрибут contents класса указывает компилятору строго соблюдать границы класса. Если вы пытаетесь получить доступ к contents напрямую, компилятор выдаст ошибку. Вы можете получить доступ к содержимому через доступные функции чтения и записи. Этот вид контроля доступа важен для гарантирования независимости от внутренностей класса и тем самым делает класс многократно используемым.
Вы можете использовать классы для создания новых типов данных, таких, как наш простой register. Использование классов для создания новых типов данных является важной частью ООП. Вы можете также использовать их для инкапсуляции математических вычислений или создания динамических структур данных, таких как стеки, списки, очереди, и так далее. Инкапсуляция организации структур данных или сведений о вычислениях в классе делает структуру данных или вычислений многократно используемыми.
В качестве более полного примера, давайте посмотрим на полезный тип данных, стек. Стек - это LIFO ("последним пришёл - первым обслужен") структура. Элементы помещаются в стек командой push(), а извлекаются из стека pop(). Pop () возвращает последний элемент, помещенный в стек, и удаляет его из структуры данных. Внутренние элемент stkptr следит за вершиной стека. Элемент, на который он указывает, это вершина стека, а все, что ниже его (то есть, с меньшим индексом) ниже в стеке. Ниже приведена базовая реализация стека в SystemVerilog
43 class stack; 44 45 typedef bit[31:0] data_t; 46 local data_t stk[20]; 47 local int stkptr; 48 49 function new(); 50 clear(); 51 endfunction 52 53 function bit pop(output data_t data); 54 55 if(is_empty()) 56 return 0; 57 58 data = stk[stkptr]; 59 stkptr = stkptr - 1; 60 return 1; 61 62 endfunction 63 64 function bit push(data_t data); 65 66 if(is_full()) 67 return 0; 68 69 stkptr = stkptr + 1; 70 stk[stkptr] = data; 71 return 1; 72 73 endfunction 74 75 function bit is_full(); 76 return stkptr >= 19; 77 endfunction 78 79 function bit is_empty(); 80 return stkptr < 0; 81 endfunction 82 83 function void clear(); 84 stkptr = -1; 85 endfunction 86 87 function void dump(); 88 89 $write(“stack:”); 90 if(is_empty()) begin 91 $display(“<empty>”); 92 return; 93 end 94 95 for(int i = 0; i <= stkptr; i = i + 1) begin 96 $write(“ %0d”, stk[i]); 97 end 98 99 if(is_full()) 100 $write(“ <full>”); 101 $display(““); 102 103 endfunction 104 endclass file: 02_intro_to_OOP/01_stack/stack.sv
Класс stack инкапсулирует все, что нужно знать о стеке данных структуры. Он содержит интерфейс и реализацию интерфейса. Интерфейс – это набор методов, которые используются для взаимодействия с классом. Реализация - это код, который заставляет класс работать. Интерфейс нашего стека содержит следующие методы:
function new(); function bit pop(output DATA data); function bit push(DATA data); function bit is_full(); function bit is_empty(); function void clear(); function void dump();
Не существует другого способа взаимодействия со стеком, как с помощью этих методов. Есть также два элемента данных класса, stk и stkptr, которые представляют фактическую структуру стека. Тем не менее, эти два элемента локальные, что означает, что компилятор запретит любые попытки доступа к ним за пределами класса. Для предотвращения доступа к внутренней структуре данных извне, мы можем сделать некоторые гарантии о состоянии данных. Например, Push () и Pop () могут рассчитывать на то, что stkptr корректно и указывает на вершину стека. Если бы можно было изменить значение stkptr иными средствами, чем с помощью интерфейса функций, тогда Push () и Pop () пришлось бы прибегнуть к дополнительным затратам и возможностям для ненадежной проверки истинности stkptr. Реализация интерфейса встроенная. Объявление класса содержит не только определение интерфейса, а также реализацию каждой функции интерфейса. Оба C + + и SystemVerilog позволяют отделить реализацию от интерфейса. Разделение интерфейса и реализации является важной концепцией. Программисты, пишущие на C + +, могут использовать файлы заголовков для ввода интерфейс и. куб (или. срр или любой другой компилятор использует) для реализации.
Есть некоторые важные явления доступа через класс интерфейсов. Одним из них является возможность многократного использования. Мы можем удобно использовать классы, интерфейсы которых четко определены и хорошо объяснены, чем те, чьи интерфейсы нечеткие. Другим важным явлением обеспечения доступа через класс интерфейсов является надежность. Авторы класса может гарантировать определенную инвариантность (например, stkptr меньше, чем размер имеющегося массива STK) когда они знают, что пользователи не будут изменять данные иначе, чем предоставляемыми средствами. Кроме того, пользователи могут ожидать, что состояние объекта будет предсказуемым, когда они придерживаются интерфейса. Ясность другое явление. Интерфейс может описать всю семантику класса. Объект не будет делать ничего другого, чем выполнять операции, доступные через интерфейс. Это делает проще понять для тех, кто использует класс, что именно он будет делать.
Отношения между объектами
Истинная сила ООП становится очевидной, когда объекты взаимосвязаны различными отношениями. Есть много видов отношений, которые возможны. Мы рассмотрим два из наиболее фундаментальных отношений HAS-A и IS-A.
HAS-A
HAS-A относится к концепции, когда один объект содержится или принадлежит другим. В нашем классе стека, например, HAS-A указатель стека (stkptr) и массив стека. Это примитивные типы данных, а не классы, но применяется тоже понятие HAS-A. В SystemVerilog вы можете создать HAS-отношения между классами с ссылками или указателями. На рисунке ниже показаны основные памяти модель для HAS-А отношений. Объект А содержит ссылку или указатель на Объект B.
Unified Modeling Language (UML) является графическим языком для представления систем, в частности, отношения между объектами в этих системах. UML для HAS-связи выражается линией между объектами и закрашенным ромбиком(??????), как показано на рисунке ниже.
Объект А принадлежит экземпляру объекта B. кодирование HAS-А отношений в
SystemVerilog заключается в включении одного класса в другой или других способов предоставления доступа одного класса к одному классу, который хранится внутри.
class B; endclass class A; local B b; function new(); b = new(); endfunction endclass
Класс А содержит ссылку на класс B. конструктор для класса A, метод new(), вызывает new() в классе B, чтобы создать его экземпляр. Член b содержит ссылку на вновь созданный экземпляр B.
IS-A
IS-А отношения чаще всего называют наследованием. Новый класс является производным от ранее существующего класса и наследует его характеристики. Объекты, созданные с наследованием наследования, созданы с использованием отношения IS-A. Производный класс является подклассом или более специализированная версия родительского объекта. Для иллюстрации понятия наследования, рисунок 2-3 использует часть систематики млекопитающих.
Животные, которые являются членами китообразных, хищных, приматов - млекопитающие. Эти очень разные виды существ имеют общие черты млекопитающих. Тем не менее, у китообразных (киты, дельфины), плотоядных (собак, медведей, енотов), и приматов (обезьян, людей), у каждого есть свои четкие и безошибочные характеристики. Для использования ОО терминологии, медведь - хищник и плотоядный IS- А млекопитающего. Иными словами, медведь состоит из атрибутов и плотоядные млекопитающие и плюс, дополнительные атрибуты, отличающие его от других плотоядных животных.
Для представления IS-A на языке UML, мы рисуем линию между объектами с открытыми стрелками, указывающие на базовый класс. Традиционно, мы изображаем базовый класс выше производных классов, а стрелки указывают вверх, образуя дерево наследования (или ориентированный ациклический граф, который может быть реализован в языках, таких как C + +, которые поддерживают множественное наследование).
При представлении двух объектов в компьютерной программе с использованием наследования, новый производный объект содержит характеристики родителей и обычно включает в себя дополнительные характеристики. На рисунке ниже показана базовая модель памяти для IS-А композиции. В этом примере класс B является производным от А.
SystemVerilog использует ключевое слово extends на определения наследования между классами:
class A; int i; float f; endclass class B extends A; string s; endclass
Класс B получена из A, поэтому он содержит все атрибуты A. Любой экземпляр B не только содержит строку S, но и объект с плавающей запятой F и целое i.
Виртуальные функции и полиморфизм
Одной из причин для создания объектов, используя наследование, является определение различного поведения для той же операции. Другими словами, поведение, определенное в производном классе переопределяет поведение, определенное в базовом классе. Это можно сделать, используя виртуальные функции. Виртуальная функция – функция, которую можно переопределить в производном классе. Рассмотрим следующий универсальный пакет класса.
class generic_packet; addr_t src_addr; addr_t dest_addr; bit m_header []; bit m_trailer []’ bit m_body []; virtual function void set_header(); virtual function void set_trailer(); virtual function void set_body(); endclass
Она имеет три виртуальных функций, чтобы установить содержимое пакета. Различные виды пакетов, требуют различного содержания. Мы используем generic_packet как базовый класс и получают различные виды пакетов из него.
class packet_A extends generic packet; virtual function void set_header(); endfunction virtual function void set_trailer(); endfunction virtual function void set_body(); endfunction endclass class packet_B extends generic_packet; virtual function void set_header(); endfunction virtual function void set_trailer(); endfunction virtual function void set_body(); endfunction endclass
Оба packet_A и packet_B могут иметь различные заголовки и трейлеры и различные форматы полезной нагрузки. Информация о том, как части пакета отформатированы, хранится локально внутри производных классов пакета. Виртуальные функции set_header (), set_trailer (), и set_body () реализованы по-разному в каждом подклассе основываясь на типе пакета. Базовый класс generic_packet устанавливает организацию классов и типы операций, которые возможны, и производные классы могут изменить поведение этих операций.
Виртуальные функции используются для поддержки полиморфизма: несколько классов, которые могут быть использованы как взаимозаменяемые, каждый с разным поведением. Например, некоторая обработка пакетов не нуждается в информации о том, какой пакет обрабатывается. Единственной необходимой информацией является то, что объект действительно пакет, то есть, он является производным от базового класса. Еще один способ показать, что пакет является предком базового класса - это через IS-A отношения. Виртуальные функции являются механизмом, с помощью которого мы можем переопределять поведение для различных вариантов пакетов.
Чтобы посмотреть немного глубже, как работают виртуальные функции, давайте рассмотрим три класса, связанных друг с другом IS-А отношением.
figure является базовым классом; polygon является производным от figure; square происходит от polygon. Каждый класс имеет две функции, draw(), которая является виртуальной, и compute_area (), которая не является виртуальной. Следующий пример показывает, SystemVerilog код:
38 39 class figure; 40 41 virtual function void draw(); 42 $display(“figure::draw”); 43 endfunction 44 45 function void compute_area(); 46 $display(“figure::compute_area”); 47 endfunction 48 49 endclass 50 51 class polygon extends figure; 52 53 virtual function void draw(); 54 $display(“polygon::draw”); 55 endfunction 56 57 function void compute_area(); 58 $display(“polygon::compute_area”); 59 endfunction 60 61 endclass 62 63 class square extends polygon; 64 65 virtual function void draw(); 66 $display(“square::draw”); 67 endfunction 68 69 function void compute_area(); 70 $display(“square::compute_area”); 71 endfunction 72 73 endclass file: 02_intro_to_OOP/03_virtual/virtual.sv
75 program top; 76 figure f; 77 polygon p; 78 square s; 79 80 initial begin 81 s = new(); 82 f = s; 83 p = s; 84 85 p.draw(); 86 p.compute_area(); 87 f.draw(); 88 f.compute_area(); 89 s.draw(); 90 s.compute_area(); 91 end 92 endprogram file: 02_intro_to_OOP/03_virtual/virtual.sv
Ниже показано, что происходит, когда мы запускаем эту программу:
Сначала мы создаем s, квадрат, а затем назначаем его на f и р. базовым классом для square является polygon и базовым классом polygon - figure. Из напечатанного вывода, мы можем заключить, что функции связаны в соответствии со следующей таблице:
Во всех случаях, compute_area () была связана с конкретным compute_area () функцией с типом ссылки, которая ее определяет -р ссылки на polygon, таким образом связаны polygon :: compute_area (). Так как compute_area () не является виртуальной функцией. Компилятор может легко определить, какие версии функций вызывать, основываясь на типе объекта.
Так как draw() является виртуальной, не всегда возможно для компилятора определить, какую функцию вызывать. Решение принимается на этапе выполнения с помощью виртуальной таблицы, таблицы привязки функций. Виртуальная таблица используется для привязки функций, привязка не может быть полностью определена во время компиляции.
Обратите внимание, что хотя р представляет собой polygon, вызов p.draw () приводит к square::draw() вызывается не polygon::draw(), как вы могли бы ожидать. То же самое происходит и с F-f.draw () связан с кsquare::draw(). объект, который мы первоначально создан представляет собой square, он поддерживает различные типы операций но, то, что это square не забывается. Это работает только потому, что square является производным от polygon, который в свою очередь является производным figure, и потому draw() объявлен как виртуальный. Ошибка компиляции О несовместимости типа возникает при попытке присвоить ей к s и p так как с не получено от р.
Универсальное программирование
Напомним, что объектно-ориентированные языки обладают средствами для разделения программ на задачи и рассмотрения их отдельно. Импликация отдельных задач заключается в том, что каждая задача представлена один раз. Дублирование кода нарушает принцип. На практике, многие задачи очень схожи, и имеют похожее решение, но не идентичное. Интуитивно, мы хотим написать код, который может быть использован во многих ситуациях. Это интуиция подводит нас к написанию универсального кода, кода, который высоко параметризованный, так что он может быть легко использован в самых разнообразных ситуациях.
Подробная информация об универсальном коде поставляется во время компиляции или во время выполнения вместо жесткого кодирования. Любой код, который имеет параметры, такие как вызов функций, может, считается универсальным, но этот термин обычно используется для кода с использованием шаблонов (в C + +) или параметризованные классы (в SystemVerilog). Написание универсальных программ согласуется с целью ООП о разделении задач. Таким образом, ООП языки обеспечивают условия для создания универсального кода.
Параметризованный класс – это класс, который (очевидно) имеет параметры. В синтаксисе SystemVerilog для определения параметров используется знак # в заголовке класса, после следует список параметров в скобках. В качестве примера, рассмотрим следующий параметризованный класс:
class param #(type T=int, int R=16); endclass
Этот класс имеет два параметра, T, который является фиктивным параметром и R, который является целочисленным параметром. Экземпляры параметризованных классов с определенными значениями параметров создают специализации, то есть версии кода с примененными параметрами.
param #(real, 29) z; param #(int unsigned, 12) q;
Приведенные выше объявления создают специализации параметризованного класса param. Имя класса и параметры определяют специализацию. Таким образом, специализации, по сути, уникальные типы. Компилятор не позволит вам присвоить q к z, или наоборот, потому что они являются объектами различных типов.
Параметр type позволяет писать типа-независимый код, код, данные и алгоритмы которого могут работать с широким спектром типов данных. Например:
class maximizer #(type T=int); function T max(T a, T b); if( a > b ) return a; else return b; endfunction endclass
Параметризованный класс maximizer имеет функцию max(), которая возвращает максимальное из двух значений. Алгоритм max() такой же, независимо от типа сравнения объектов. В этом случае, единственным ограничением является то, что объекты должны быть сравнимы оператором больше чем (>). Классы не могут сравниваться с использованием оператора большего чем, поэтому другая версия maximizer должна работать с классами. Для создания версии maximizer, которая будет возвращать больший из двух классов, мы должны определить метод в каждом классе, который будет сравнивать объекты.
class maximizer #(type T=int); function T max( T a, T b); if( a.comp(b) > 0 ) return a; else return b; endfunction endclass
Это предполагает, что параметр T типа type действительно класс, а не встроенного тип, такие как int или real. Кроме того, он предполагает, что T имеет функцию под названием Comp (), которая используется для сравнения себя с другим экземпляром. Библиотека OVM содержит параметризованный компонент, называемый ovm_in_order_comparator # (T), который используется для сравнения потоков операций. Она имеет два варианта, один для сравнения потоков встроенных типов, и один для сравнения потоков классов. Причина необходимости двух компараторов в том, что SystemVerilog не поддерживает операторы, которые могут работать и с классами и со встроенными типами.
Универсальный стек
Наш стек не особенно универсальный. Она имеет фиксированный размер 20, а тип данных элементов хранящихся в стеке Int. Ниже приводится более общий вид стека, который изменяет эти фиксированные характеристики параметризованными.
53 class stack #(type T = int); 54 55 local T stk[]; 56 local int stkptr; 57 local int size; 58 local int tp; 59 60 function new(int s = 20); 61 size = s; 62 stk = new [size]; 63 clear(); 64 endfunction 65 66 function bit pop(output T data); 67 68 if(is_empty()) 69 return 0; 70 71 data = stk[stkptr]; 72 stkptr = stkptr - 1; 73 return 1; 74 75 endfunction 76 77 function bit push(T data); 78 79 if(is_full()) 80 return 0; 81 82 stkptr = stkptr + 1; 83 stk[stkptr] = data; 84 return 1; 85 86 endfunction 87 88 function bit is_full(); 89 return stkptr >= (size - 1); 90 endfunction 91 92 function bit is_empty(); 93 return stkptr < 0; 94 endfunction 95 96 function void clear(); 97 stkptr = -1; 98 tp = stkptr; 99 endfunction 100 101 function void traverse_init(); 102 tp = stkptr; 103 endfunction 104 105 function int traverse_next(output T t); 106 if(tp < 0) 107 return 0; // failure 108 109 t = stk[tp]; 110 tp = tp - 1; 111 return 1; 112 113 endfunction 114 115 virtual function void print(input T t); 116 $display(“print is unimplemented”); 117 endfunction 118 119 function void dump(); 120 121 T t; 122 123 $write(“stack:”); 124 if(is_empty()) begin 125 $display(“<empty>”); 126 return; 127 end 128 129 traverse_init(); 130 131 while(traverse_next(t)) begin 132 print(t); 133 end 134 $display(); 135 136 endfunction 137 138 endclass file: 02_intro_to_OOP/02_generic_stack/stack.sv
Универсальный класс stack параметризован типом объектов стека. Параметр T содержит тип. В этом случае T может быть либо классом или встроенным типом. В любом месте класса, где мы ранее использовали Int как тип стека, теперь мы используем Т. Например, push() теперь принимает аргумент типа T. Параметры класса, такие как T, устанавливаются во время компиляции. Для специализации stack#(T), мы инициализируем его с определенным значением для данного типа. Например:
stack #(real) real_stack;
Этот оператор создает специализацию stack, который использует real, как тип объектов стека. Размер стека больше не зафиксирована на уровне 20. Мы используем динамический массив для хранения стека, размер которого указан в качестве параметра конструктора. В отличие от T, аргумент size задается при выполнении программы. Это позволяет нам создать несколько стеков, каждый с разным размером.
stack #(real) big_stack; stack #(real) little_stack; ... big_stack = new(2048); little_stack = new(6);
big_stack и little_stack одного типа. Они используют одну и ту же специализацию stack#(T). Тем не менее, каждый из них создается с различными параметрами размера.
В процессе создания универсального стека, мы сделали еще одно изменение. Мы заменили dump() на traverse_init () и traverse_next (). dump() зависит от типа элементов стека, которые не известны до компиляции. Мы должны иметь возможность пройти по стеку и отформатировать каждый элемент, не зависимо от типа элемента. Это может быть INT, или это может быть сложный класс с несколькими членами. Чтобы сохранить stack#(T) универсальным, мы должны исключить любую зависимость от типа элементов стека.
В то время как dump() пробежит по всем элементам стека и напечатает их в определенном порядке, traverse_init () устанавливает внутренний указатель(tp) на вершину стека, а traverse_next () возвращает текущий элемент и уменьшает tp. Стек поддерживает некоторую статическую информацию обхода. Статическая информация сбрасывается, когда вызывается traverse_init ().
Классы и Модули
Интересно, что HDLs, такие как Verilog и VHDL, хотя они не считаются объектно-ориентированными языками, строятся на концепции очень схожей классам и объектам. Модули в Verilog, например, являются объектами, каждый из которых имеет свое собственное пространство данных и набор задач и функций. Так же, как объекты в ОО программах, каждый экземпляр модуля является независимой копией. Все экземпляры имеют один и тот же набор задач и функций и те же интерфейсы, но данные содержащихся внутри каждого является независимым от всех остальных экземпляров. Модули контролируются их интерфейсами. Verilog модули не поддерживают наследование или параметризации типов, и они являются статическими, что делает их непригодными для истинного ООП.
Сходство между классами и модулями открывает возможность для нас использовать объекты класса в аппаратном контексте. Мы можем создать компоненты верификации как экземпляры классов, что дает нам гибкость при подключении к аппаратным элементам. Дизайнеры SystemVerilog выигрывают при расширении Verilog классами, предоставляя возможность для классов работать как модули.
В приведенной ниже таблице сравниваются особенности классов в Verilog, SystemVerilog, и C + +.
В SystemVerilog есть особенность, которая делает это возможным это виртуальный интерфейс. Виртуальный интерфейс является ссылкой на интерфейс (здесь мы имеем в виду SystemVerilog интерфейс). Мы можем написать класс, содержащий ссылки к элементам внутри интерфейса, которых еще не существует (то есть, они не создаются). Когда создается экземпляр класса, виртуальный интерфейс подключается к реальному интерфейсу. Это делает возможным для объекта класса реагировать и отвечать на входные изменения. Модули SystemC реализованы в виде классов и позволяют входам быть в списке портов, обеспечивая такую же структуру.
В HDLs, таких как Verilog и VHDL, не хватает многих возможностей ООП, и, следовательно, не хорошо подходят для построения testbenches. Основной единицей программирования в большинстве HDLs является модуль, и это статический объект. Модули создаются в самом начале программы и сохраняются не измененными до завершения программы. Они синтаксически статические и это означает, что
средства для изменения модулей, создания другого варианта, ограничены. Verilog позволяет параметризовать скалярные значения, но не типы. Часто все сводятся к резке и вставке кода, а затем внесению локальных изменений. Если у вас есть десять различных вариаций нужных в конкретном проекте, необходимо вставить десять копий в соответствующие места, а затем локально изменить каждую из них.
ООП и Верификация
Создание объектно-ориентированных программ и создания testbench мало чем отличается. testbench представляет собой сеть взаимодействующих компонентов. ООП работает с определением и анализом сетей взаимодействующих объектов. Объекты могут быть связаны IS-A или HAS-A отношениями, и они общаются через интерфейсы. ООП просто естественно вписывается проблему создания testbenches.
Языки, таких как SystemC / C + + и SystemVerilog, которые действительно поддерживают возможности ООП , лучше подходят для создания testbench, чем HDLs, такие как Verilog и VHDL. Используя динамические классы, параметризованные классы, наследование и параметризованные конструкторы, вы можете создать компоненты, которые являются гибкими, повторно используемыми и надежные. Потратив немного больше времени, чтобы построить универсальную компоненту, приведет вас приросту производительности, когда эта компонент повторно используется по-разному в разных местах.