«…лишь недалекие люди боятся конкуренции, а люди подлинного творчества ценят общение с каждым талантом…» А. Бек, Талант.

Open Source VHDL Verification Methodology/Описание пакета RandomPkg

Материал из Wiki
Перейти к: навигация, поиск
Проект OS-VVM

Исходные коды

Описание примеров

Презентации

Coverage

* VHDL * PSL *

Содержание

Псевдослучайное тестирование (Randomization) и пакет RandomPkg

RandomPkg provides types subprograms and protected type methods to simplify randomization. As such, it is at the heart of SynthWorks' constrained and coverage driven random methodology.

Пакет RandomPkg предоставляет типы подпрограмм и метода защищённого (protected) типа для упрощения псевдослучайной генерации чисел. Как таковой,

This documentation provides details on the STANDARD revision 2.0 of RandomPkg. As such this documentation supercedes the slides presented in the webinar in March of 2009. These packages are updated from time to time and are freely available at http://www.synthworks.com/downloads.

1. Randomization Using IEEE.math_real.uniform = Yuck!

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

RandomGenProc : process
  variable RandomVal : real ;                     -- Random value
  variable DataSent : integer ;
  variable seed1 : positive := 7 ;                -- initialize seeds
  variable seed2 : positive := 1 ;
begin
  for i in 0 to 255*6 loop
    uniform(seed1, seed2, RandomVal) ;            -- randomize 0.0 to 1.0
    DataSent := integer(trunc(RandomVal*256.0)) ; -- scale to 0 to 255
    do_transaction(…, DataSent, …) ;
    . . .

Оптимально можно было бы вызвать функцию, которая делала бы это, можно сделать всё за один шаг. Но к сожалению нельзя записать нормальную VHDL функцию, поскольку необходимо прочитать и обновить начальное значение генератора псевдослучайных чисел as well as return a randomized value.

Упрощённая генерация случайных чисел

Пакет RandomPkg использует защищённый тип (protected), именуемый RandomPType, для инкапсуляции начального значения (seed) псевдослучайного генератора. Используя защищённый тип, impure функции могут читать и обновлять seed так же как возвращать псевдослучайное значение. Для генерации псевдослучайных чисел используя защищённый тип, необходимо объявить переменную типа RandomPType, инициализировать начальное значение генератора (seed) и получить псевдослучайное число.

RandomGenProc : process
  variable RV : RandomPType ;                   -- protected type from RandomPkg
begin
  RV.InitSeed (RV'instance_name) ;              -- Generate initial seeds
  for i in 0 to 255*6 loop
    do_transaction(…, RV.RandInt(0, 255), …) ;  -- random value between 0 and 255

Следует обратить внимание, что вызовы методов защищённого типа (подпрограммы) включают переменную защищённого типа (RV) с вызовом функции (например, RV.RandInt(0, 255)).

Управление начальными значениями генератора (seed)

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

Метод InitSeed преобразует значение аргумента к типу RandomSeedType (внутреннее представление seed) и сохраняет значение внутри защищённого типа. InitSeed перегружена, чтобы принимать значения типа string или integer. Приемлемый путь дать каждому значению seed уникальное значение, это назначить строковое значение RV'instance_name.

RV.InitSeed (RV'instance_name) ;

Методы GetSeed и SetSeed используются для чтения и восстановления значения seed. Объявление для этоко показаны ниже.

impure function GetSeed return RandomSeedType ;
procedure SetSeed (RandomSeedIn : RandomSeedType ) ;

Функция to_string и процедуры write и read используются для записи и чтения значения типа RandomSeedType. Декларации для этих подпрограмм показаны ниже. Следует обратить внимание, что это объявлено в файле RandomBasePkg.vhd и отделено от защищённого типа.

function to_string(A : RandomSeedType) return string ;
procedure write(L: inout line ; A : RandomSeedType ) ;
procedure read (L: inout line ; A : out RandomSeedType ; good : out boolean ) ;
procedure read (L: inout line ; A : out RandomSeedType ) ;

Aqua pencil.pngДля длинного тест, может быть преимуществом чтение seed периодически и печатать (выводить) его. Если сбой или другой интересное состояние генерируется, seed может быть восстановлено значение, которое было записано перед сбоем с целью генерации ошибки быстро, чтобы помочь с отладкой.


Задание параметров

    -- Setting Randomization Parameters
    -- Allows RandInt to have distributions other than uniform
    procedure SetRandomParm (RandomParmIn : RandomParmType) ;
    procedure SetRandomParm (
      Distribution : RandomDistType ;
      Mean         : Real := 0.0 ;
      Deviation    : Real := 0.0
    ) ;
    impure function GetRandomParm return RandomParmType ;
    impure function GetRandomParm return RandomDistType ;
 
    -- For compatibility with previous version - replace with alias
    procedure SetRandomMode (RandomDistIn : RandomDistType) ;
    -- alias SetRandomMode is SetRandomParm [RandomDistType, Real, Real] ;

Основы генерации случайных чисел

Базовый генератор псевдослучайных чисел генерирует значение типа integer, которое либо входит в какой-то диапазон или в набор значений. Набор значений и исключенных значений являются все типа integer_vector (определённый в стандарте VHDL-2008). Примеры базовых функций генерации псевдослучайных чисел показаны ниже. Когда задаётся значение типа integer_vector, то дополнительные круглые скобки означают, что это множество (сборное) значений.

RandomGenProc : process
  variable RV : RandomPType ;           -- protected type from RandomPkg
  variable DataInt : integer ;
begin
  RV.InitSeed (RV'instance_name) ;      -- Generate initial seeds
  -- Generate a value in range 0 to 255
  DataInt := RV.RandInt(0, 255) ;
  . . .
  -- Generate a value in range 1 to 9 except exclude values 2,4,6,8
  DataInt := RV.RandInt(1, 9, (2,4,6,8)) ;
  . . .
  -- Generate a value in set 1,3,5,7,9
  DataInt := RV.RandInt( (1,3,7,9) ) ;  -- note two sets of parens required
  . . .
  -- Generate a value in set 1,3,5,7,9 except exclude values 3,7
  DataInt := RV.RandInt((1,3,7,9), (3,7) ) ;

Такие же функции доступны для типов std_logic_vector (RandSlv), unsigned (RandUnsigned) и signed (RandSigned). Следует обратить внимание, что значение этого параметра по прежнему задаётся как целое и есть дополнительное значение, которое используется для задания размера (разрядности) генерируемого значения. Например, следующий вызов метода RandSlv задаёт массив размером 8 бит.

. . .
variable DataSlv : std_logic_vector(7 downto 0) ;
begin
  . . .
  -- Generate a value in range 0 to 255
  DataSlv := RV.RandSlv(0, 255, 8) ;

Для генерации псевдослучайных чисел из диапазона, существует также функция RandReal. Так же как процедура Uniform, она никогда не генерирует свои конечные значения.

Перегруженные функции RandInt представлены ниже.

impure function RandInt (Min, Max : integer) return integer ;
impure function RandInt (Min, Max: integer; Exclude: integer_vector) return integer;
impure function RandInt ( A : integer_vector ) return integer ;
impure function RandInt ( A : integer_vector; Exclude: integer_vector) return integer;




Специальные типы данных

Для управления параметрами, представляющими собой набор чисел, в пакете определены два типа:

  • DistRecType – запись с двумя полями типа integer Value и Weight;
  • DistType – массив (неопределённой длинны) элементов типа DistRecType.

Чтобы обеспечить задание, хранение и восстановление типа случайного распределения, использующийся функциями псевдослучайного генератора, был введён специальный перечислимый тип RandomDistType:

type RandomDistType is (NONE, UNIFORM, FAVOR_SMALL, FAVOR_BIG, NORMAL, POISSON);

На практике значение NONE действует также как и UNIFORM, но позволяет проверить было ли текущее распределение значением по умолчанию (т.е. NONE) или было изменено в коде.

Aqua pencil.png В то время как UNIFORM, NORMAL и POISSON распределения ведут себя как описано в литературе, FAVOR_BIG и FAVOR_SMALL используют внутреннюю функцию квадратного корня, чтобы получить ожидаемые нелинейные (non-uniform) результаты.

Чтобы задать не только тип распределения, но и его числовые параметры, следующая запись RandomParmType объявлена:

type RandomParmType is record
  Distribution : RandomDistType ;
  Mean         : Real ;
  StdDeviation : Real ;
end record ;

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

Пожалуйста, обратите внимание, что во избежание снижения качества генерируемых случайных чисел пользователи должны объявлять переменные типа RandomPType как локальные с узкой возможной областью действия. Такие переменные должны быть использованы для создания потока значений для одного конкретного приложения/объекта проекта и не должна быть общими (shared).


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

Взвешенное распределение псевдослучайно генерирует каждый набор значений опоределённый процент раз. RandomPType обеспечивает взвешанное распределение двумя функциями, в одной из которых задаются значение и его вес (DistValInt), а в другой задаётся только вес (DistInt).

DistValInt вызывается с массивом пар значений. Первый элемент в паре это значение, а второй — вес. Частота с которой каждое значение будет возникать равно вес/(сумму всех весов) [weight/(sum of weights)]. В результате в следующем вызове метода DistValInt вероятность возникновения 1 будет 7/10 раз или 70%. Вероятность 3 будет 20%, а 5 — 10%.

variable RV : RandomPType ;
. . .
DataInt := RV.DistValInt( ((1, 7), (3, 2), (5, 1)) ) ;

DistInt является упрощённой версией DistValInt, в которой задаются только веса. Значения генерируются от 0 до N-1, где N это число заданных весов. Результат следующего вызова DistInt вероятность 0 будет 70%, 1 — 20%, а 2 — 10%.

variable RV : RandomPType ;
. . .
DataInt := RV.DistValInt((7, 2, 1)) ;



Использование (Применение)

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

wait for RV.RandInt(3, 10) * tperiod_Clk - tpd ;
wait until Clk = '1' ;

Значения могут также использоватся непосредственно внутри оператора выбора CASE. Следующий пример использует функцию DistInt для генерации выбора первого блока с вероятностью 70%, второго 20 %, и третьего 10%.

variable RV : RandomPType ;
. . .
StimGen : while TestActive loop            -- Repeat until done
  case RV.DistInt( (7, 2, 1) ) is
    when 0 =>                              -- Normal Handling  -- 70%
    . . .
    when 1 =>                              -- Error Case 1     -- 20%
    . . .
    when 2 =>                              -- Error Case 2     -- 10%
    . . .
    when others =>
      report "DistInt" severity failure ;  -- Signal bug in DistInt
  end case ;
end loop ;

Следующий фрагмент кода генерирует транзакции для записи DMA_WORD_COUNT, DMA_ADDR_HI, и DMA_ADDR_LO в случайном порядке, которые запускается каждый раз в различный момент. Последовательность заканчивает с записью в DMA_CTRL. Когда вызывается DistInt с весом 0, соответствующее значение не будет сгенерировано. Следовательно инициализируя все веса в 1 и затем устанавливая их в 0, когда выбираются, каждый блок оператора выбора происходит единожды. Цикл "for loop" проходит три раза чтобы разрешить каждой тракзакции сработаь.

variable RV : RandomPType ;
. . .
Wt0 := 1; Wt1 := 1; Wt2 := 1;            -- Initial Weights
for i in 1 to 3 loop                     -- Loop 1x per transaction
  case RV.DistInt( (Wt0, Wt1, Wt2) ) is  -- Select transaction
    when 0 =>                            -- Transaction 0
      CpuWrite(CpuRec, DMA_WORD_COUNT, DmaWcIn);
      Wt0 := 0 ;                         -- remove from randomization
    when 1 =>                            -- Transaction 1
      CpuWrite(CpuRec, DMA_ADDR_HI, DmaAddrHiIn);
      Wt1 := 0 ;                         -- remove from randomization
    when 2 =>                            -- Transaction 2
      CpuWrite(CpuRec, DMA_ADDR_LO, DmaAddrLoIn);
      Wt2 := 0 ;                         -- remove from randomization
    when others => report "DistInt" severity failure ;
  end case ;
end loop ;
CpuWrite(CpuRec, DMA_CTRL, START_DMA or DmaCycle);

Следующий пример использует список исключений, чтобы не повторить последнего значения. Aqua pencil.png Следует обратить внимание, что когда проходит целое значение integer в параметре integer_vector, множество успользующее поимённое назначени е используется для обозначения одного элемента массива. Обратите внимание, что в течении первого выполнения этого процесса LastDataInt имеет значение integer'left (очень малое значение) которое лежит вне диапазона от 0 до 255, и в результате не имеет влияния на генерацию псевдослучайных чисел.

RandomGenProc : process
  variable RV : RandomPType ;
  variable DataInt, LastDataInt : integer ;
begin
  . . .
  DataInt := RV.RandInt(0, 255, (0 => LastDataInt)) ;
  LastDataInt := DataInt;
  . . .

Следующий фрагмент кода использует лист исключений, чтобы избежать четырёх предыдущих значений.

RandProc : process
  variable RV : RandomPtype ;
  variable DataInt : integer ;
  variable Prev4DataInt : integer_vector(3 downto 0) := (others => integer'low) ;
begin
  . . .
  DataInt := RV.RandInt(0, 100, Prev4DataInt) ;
  Prev4DataInt := Prev4DataInt(2 downto 0) & DataInt ;
  . . .

Создание теста

Aqua pencil.png Создание тестов является всё о методологии. SynthWorks методология совмещает подпрограммы псевдослучайной генерации чисел (из пакета RandomPkg) и подпрограммы для функционального покрытия (из CoveragePkg — также свободно доступного по ссылке http://www.synthworks.com/downloads) с конструкциями языка VHDL. Каждая тестовая последовательность получается путём псевдослучайного выбора или ветви кода или значений для операций. Управляемая рандомизация создаётся используя обычную технику последовательное кодирование (например, case, if, циклы, и операторы присваения). Этот подход прост и до сих пор эффективен. Поскольку весть код последовательный, псевдослучайные последовательности легко смешиваются с прямыми и алгоритмическими последовательностями.

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

variable RV : RandomPType ;
. . .
TxStimGen : while TestActive loop
  -- Burst between 1 and 10 values
  BurstLen := RV.RandInt(Min => 1, Max => 10);
  for i in 1 to BurstLen loop
    DataSent := DataSent + 1 ;
    WriteToFifo(DataSent) ;
  end loop ;
  -- Delay between bursts: (BurstLen <=3: 1-6, >3: 3-10)
  if BurstLen <= 3 then
    BurstDelay := RV.RandInt(1, 6) ;  -- small burst, small delay
  else
    BurstDelay := RV.RandInt(3, 10) ; -- bigger burst, bugger delay
  end if ;
  wait for BurstDelay * tperiod_Clk - tpd ;
  wait until Clk = '1' ;
end loop TxStimGen ;

Функциональное покрытие считает, какие тестовые состояния сгенерированы и даёт инженерам индикатор того, когда тестирование закончено. Это важно, когда используя псевдослучайное тестирования для создания теста, так как в противном случае нет способа узнать, что тест действительно прошел. Функциональное покрытие может быть выполнено, используя вызовы подпрограмм (собственных или из пакета CoveragePkg) или VHDL-код. Функциональное покрытие сохраняется в сигналах и может использоваться для изменения псевдослучайных тестов (или непосредственно как ограничения или косвенно, как что-либо влияющее на изменение ограничений) для генерации пропущенных элементов (точек) покрытия.

Aqua pencil.png В примере FIFO необходимо увидеть много попыток записи, пока полное и попытки читать, пока пустое. Одно можно сделать для улучшения предыдущего теста то, чтобы увеличить или уменьшить длину пакета и задержку, основанную на числе попыток записи, пока полон, или попыток чтения, пока пусто, мы видели. Чтобы исследовать как генерируется покрытие, необходимо обратиться к документации пакета CoveragePkg.

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

Solutions for the two previous coverage driven randomization problems are provided in SynthWorks' VHDL Testbenches and Verification class.

8. Random Stability

A protected type is always used with a variable object. If the object is declared in a process, it is a regular variable. If the object is declared in an architecture, then it is declared as a shared variable.

All of the examples in this document show RandomPType being defined in a process as a regular variable. This is done to ensure random stability. Random stability is the ability to re-run a test and get exactly the same sequence. Random stability is required for verification since if we find a failure and then fix it, if the same sequence is not generated, we will not know the fix actually worked.

Random stability is lost when a randomization variable is declared as a shared variable in an architecture and shared among multiple processes. When a randomization variable is shared, the seed is shared. Each randomization reads and updates the seed. If the processes accessing the shared variable run during the same delta cycle, then the randomization of the test depends on the order of which RandomPType is accessed. This order can change anytime the design is optimized - which will happen after fixing bugs. As a result, the test is unstable.

To ensure stability, create a separate variable for randomization in each process.


9. Other Distributions

По умолчанию все генераторы случайных чисел используют распределение uniform. В дополнение к этому распределению, в RandomPType есть также другие распределения favor_small, favor_big, normal, и poisson. Далее приведён список этих функций.

-- Generate values, each with an equal probability
impure function Uniform (Min, Max : in real) return real ;
impure function Uniform (Min, Max : integer) return integer ;
impure function Uniform (Min, Max : integer ; Exclude: integer_vector) return integer ;
-- Generate more small numbers than big
impure function Favor_small (Min, Max : real) return real ;
impure function Favor_small (Min, Max : integer) return integer ;
impure function Favor_small (Min, Max : integer; Exclude: integer_vector) return integer ;
-- Generate more big numbers than small
impure function Favor_big (Min, Max : real) return real ;
impure function Favor_big (Min, Max : integer) return integer ;
impure function Favor_big (Min, Max : integer ; Exclude: integer_vector) return integer ;
-- Generate normal = gaussian distribution
impure function Normal (Mean, StdDeviation : real) return real ;
impure function Normal (Mean, StdDeviation, Min, Max : real) return real ;
impure function Normal (Mean : real ;
                        StdDeviation : real ;
                        Min : integer ;
                        Max : integer ;
                        Exclude : integer_vector := NULL_INTV
                       ) return integer ;
-- Generate poisson distribution
impure function Poisson (Mean : real) return real ;
impure function Poisson (Mean, Min, Max : real) return real ;
impure function Poisson (
                         Mean : real ;
                         Min : integer ;
                         Max : integer ;
                         Exclude : integer_vector := NULL_INTV
                        ) return integer ;

Пакет также предоставляет экспериментальные механизмы для изменения распределения использующиеся с функциями RandInt, RandSlv, RandUnsigned, и RandSigned.

10. Compiling RandomPkg and Friends

The core package is RandomPkg. It is supported by the packages RandomBasePkg and SortListPkg_int. We compile all of our packages into a library named SynthWorks. Directions for compiling these packages using either VHDL-2008 or VHDL-2002 are provided in the separate document, README_RandomPkg.pdf. Your programs will need to reference RandomPkg. If your programs need to use IO for the seed (to_string, write, read), then you will also need to include RandomBasePkg.

library SynthWorks ;
use SynthWorks.RandomPkg.all ;


11. Future Work

RandomPkg.vhd is a work in progress and will be updated from time to time. In addition to the RandomPkg, we also are freely distributing our coverage package, CoveragePkg. See http://www.SynthWorks.com/downloads. Over time we will also be releasing other packages that we currently distribute with our classes (such as scoreboards and memory modeling) and hope to convince simulation vendors to distribute our libraries with their tools.


12. About SynthWorks

SynthWorks' VHDL training can help you jumpstart your VHDL design and verification tasks. Whether it be introductory, verification or synthesis training, the knowledge you gain will help you finish your next FPGA or ASIC project in a more timely and efficient manner.

We provide training in leading edge VHDL verification techniques, including transactionbased testing, bus functional modeling, self-checking, data structures (linked-lists, scoreboards, memories), directed, algorithmic, constrained random, and coverage driven random testing, and functional coverage. In addition to RandomPkg, our verification class comes with packages for functional coverage, memories, scoreboards, and interfaces.

Help support our open source packages by buying your VHDL training from SynthWorks.

13. About the Author

Jim Lewis, the founder of SynthWorks, has twenty-six years of design, teaching, and problem solving experience. In addition to working as a Principal Trainer for SynthWorks, Mr Lewis has done ASIC and FPGA design, custom model development, and consulting. Mr Lewis is an active member of the VHDL standards effort and is the current IEEE VHDL Study Group chair. I am passionate about the use of VHDL for verification. If you find any innovative usage for the package, let us know. If you find bugs with any of SynthWorks' packages or would like to request enhancements, you can reach me at jim@synthworks.com.