Pull to refresh

Моделирование смешанных схем на System Verilog

Reading time 7 min
Views 11K

Жили были, не, не так… Однажды рано утром, придя в очередной раз на работу, я узнал, что у нас в серверной всего один ввод электропитания и он может отгорать. Целый день было нечего делать, и я решил написать статью на Хабр. Статья ориентирована на начинающих и праздно интересующихся.


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


Так как мы живем в аналоговом мире, то даже цифровая микросхема должна уметь с этим миром общаться. Цифровые микросхемы содержат на кристалле десятки больших аналоговых блоков, таких как АЦП, ЦАП, ФАПЧ, блоки вторичного питания и т.д. Исключением из этого правила, вероятно, являются только большие процессоры, типа Core i и т.п., где все это хозяйство вынесено в чипсет.


Традиционно для моделирования аналоговых блоков используются spice симуляторы, такие как pi-spice, mmsim, hspice и т.д. В таких симуляторах схема описывается системой дифференциальных уравнений огромной размерности (или матрицей, ее представляющей). Spice симуляторы на каждом шаге вычислений находят решение этой системы уравнений численными методам. Конечно, используются методы ускорения этих вычислений, такие как: разбиение матрицы на подматрицы, распараллеливание на некоторое количество потоков и вычислительных ядер, переменный шаг вычислений и т.д.


К сожалению, числовые методы являются фундаментально итеративными и плохо распараллеливаются, поэтому этот вид моделирования, все равно, остается достаточно медленным для моделирования системы в целом. Тем не менее, он широко применяется при разработке самих аналоговых блоков и аналоговых микросхем. Мы же, поведем рассказ о цифровых (в целом) микросхемах, содержащих аналоговые блоки и аналого-цифровые системы, где нам хотелось бы описать наши блоки в виде формул и уравнений, и решать эти уравнения Навье-Стокса(шутка) аналитически. Использование данной техники не отменяет гораздо более точный расчет на spice симуляторе, а лишь дополняет его позволяя ускорить разработку и моделирование.


Представление аналоговых сигналов


Для представления аналоговых сигналов хорошо подходит тип с плавающей точкой. В System Verilog это типы shortreal (эквивалентен float в С) и real. Необходимо отметить, что это типы данных с памятью. Значение в них обновляется только в момент присваивания, т.е. это тип аналогичный reg, но в котором запомненное значение представляется не 0 или 1, а напряжением или током, представленном, в свою очередь, в виде числа с плавающей точкой.
Теперь, нам очень нужен тип аналогичный wire, который обновляется непрерывно, а не только в момент записи. Надо сказать, что в System Verilog такого типа нет. Ходят слухи, что при обсуждении стандарта, было некое движение, дабы вставить в него этот функционал, но оно не реализовалось ни во что конкретное. Тем не менее, если вы используете симулятор ncsim, то в нем есть модификатор var, который делает из типа real и других типов аналог wire. Пример:


real a;
var real b;

assign a = in1+in2; //тут будет ошибка
assign b = in1+in2; // это будет работать, b – всегда будет равно in1+in2

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

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


Если ваш симулятор не поддерживает var, то можно сделать так:


real b;

always @( * ) // симулятор входит в этот always при любом изменении in1 или in2
          b <= in1+in2;

Запись менее удобная, тем не менее вполне рабочая.


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


В verilog встроены следующие функции для преобразования данных


$itor() // integer to real
$rtoi() // real to integer
$bitstoreal() //reg [  : ] to real
$realtobits() // real  to reg [  : ] 

Если код, который вы хотите преобразовать в real — знаковый и представлен в дополнительном коде, нужно быть аккуратным при использовании этих функций, возможно понадобиться еще конвертация или расширение знака. Если вы, по каким-то причинам, не хотите использовать эти функции, то можно воспользоваться следующей техникой.


reg [7:0] code;
int       a;
real      voltage;

always @( * ) 
begin
        a       = {{24{code[7]}}, code[7:0]}; //расширяем знак до размера int 
        voltage = a;
end

Упрощенные модели аналоговых блоков на Verilog


Усилитель с аддитивным белым шумом


module amp(input var real in, output real out);
   parameter k = 10; //коэффициент усиления 
   parameter seed = 60;
   parameter noise_power = -20; //мощность шума в dB
   real noise;

   always @(*)
   begin
          noise = $sqrt(10**(noise_power/10))* $itor($dist_normal(seed, 0 , 100_000))/100_000;
          out   = in * k + noise;
   end
endmodule

ЦАП c ФНЧ


`timescale 1ns / 1ps
module DAC(input signed [7:0] DAC_code, output real out);

   parameter fs     = 10e-9;
   parameter ffilt  = fs/64;    //частота расчета фильтра
   parameter CUTOFF = 100e6;    //частота среза фильтра
   parameter a      = ffilt/(ffilt+(1/(2*3.141592* CUTOFF)));

   real DAC_out;

   //ЦАП
   always @( * ) 
           DAC_out <= $bitstoint(DAC_code[7:0]);

   //ФНЧ 1го порядка
    always #(0.5*ffilt)
           out  <= a* DAC_out + (1-a)*out;

endmodule

АЦП c учетом нелинейности


module ADC (input real in, input clk, output reg [7:0] ADC_code)
  real adc_tf[0:255];
  real min_dist;
  int i,j;
  int dnl_file;

  initial
  begin   
      dnl_file=$fopen("DNL_file","r"); 
      if(dnl_file==0)
        $stop;     
      for(i=0;i<256;i=i+1)
        $fscanf(dnl_file, "%f;", adc_tf[i]);//считываем из файла характеристику АЦП
  end

  always @(posedge clk) 
  begin
    min_dist = 10;
    for(j=0;j<256; j=j+1) //находим ближайший к входному сигналу код
           if($abs(in- adc_tf[j]) < min_dist)
           begin
                min_dist = delta_abs;
                ADC_code[7:0]=j;
           end
  end
endmodule

Многофазный источник тактовой частоты (ФАПЧ)


module MPLL (input en, input [5:0]phase, output clk_out);
  parameter REFERENCE_CLOCK_PERIOD=10e-6;
  parameter PHASES_NUMBER=64;

  reg [PHASES_NUMBER-1:0]PLL_phase=64'h00000000_FFFFFFFF; //ГУН на кольцевом генераторе

  always #(REFERENCE_CLOCK_PERIOD/PHASES_NUMBER)  
     if(en===1) 
           PLL_phase[PHASES_NUMBER-1:0] <= {PLL_phase[PHASES_NUMBER-2:0], PLL_phase[PHASES_NUMBER-1]}; //сдвигаем кольцевой генератор по кругу

  assign clk_out = PLL_phase[phase]; //мультиплексор клока 
endmodule

Использование таких и подобных, но более сложных аналитических моделей, на порядки ускоряет расчёты по сравнению со spice моделированием и позволяет реально моделировать и верифицировать систему в сборе на System Verilog.


Еще ускоряемся


К сожалению, современные системы уже настолько сложны, что данного ускорения бывает недостаточно, в этом случае приходится прибегать к распараллеливанию. Многопоточных Verilog симуляторов, насколько я знаю, пока не изобрели, поэтому придется в рукопашную.
В SystemVerilog введен новый механизм для обращения к внешним программным модулям – Direct Programming Interface (DPI). Т.к. этот механизм проще, по сравнению с другими двумя, будем использовать его.


В начале модуля, где мы хотим вызвать внешнюю функцию нужно вставить строчку import.
import "DPI-C" function int some_funct(input string file_name, input int in, output real out);
Далее можно в Verilog ее использовать обычным образом, например, так:


 always @(posedge clk)           
     res1 <= some_funct (“file.name”, in1, out1);

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


Пример
#include <pthread.h>
typedef struct 
{
 //work specific
   double in; // данные для расчета
   double out;   //результат расчета
   …
 //thread specific
   char processing;               //флаг разрешения расчета
   pthread_mutex_t mutex;
   pthread_cond_t  cond_start;
   pthread_cond_t  cond_finish;       
   void *next_th_params;
   pthread_t tid;
}th_params;

static th_params th_pool[POOL_SIZE];

Расчётная функция:


void* worker_thread(void *x_void_ptr)
{
  th_params *x_ptr = (th_params *)x_void_ptr;
  while(1)  //бесконечный цикл
  {
       // ждем поступления новых данных
      pthread_mutex_lock (&x_ptr->mutex);         //блокируем        
      x_ptr->processing = 0;                                //Окэй, рэди фор ворк
      pthread_cond_signal(&x_ptr->cond_finish);  //даем гудок, что закончили
      while(x_ptr->processing == 0)
          pthread_cond_wait(&x_ptr->cond_start, &x_ptr->mutex);  //ждем ответного гудка
      x_ptr->processing = 1;                               //ставим флаг - занят
      pthread_mutex_unlock(&x_ptr->mutex);     //разблокируем
      // здесь что-то считаем, вероятно ассемблерная вставка SSE2
      …
  }
}

Функция запуска расчётных функций


void init(th_params *tp)
{ 
    int i=0;
    for(;i<12;i++)
    {
        pthread_attr_t attr;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        pthread_create(th_pool->tid,  &attr, &worker_thread, tp);
    }
}

Функция, раздающая работу расчётным функциям (ее будем вызывать из Verilog постоянно)


int ch(double in, double *out)
{   
   int i;               
   for(i=0;i<12;i+=1)
   {
         //ждем если рабочие функции еще не досчитали
         pthread_mutex_lock(&th_pool[i].mutex);                                //блокируем
         while(th_pool[i].processing == 1)      
                pthread_cond_wait(&th_pool[i].cond_finish, &th_pool[i].mutex); //ждем гудок
         pthread_mutex_unlock(&th_pool[i].mutex);                            //разблокируем
   }  

   //присваиваем результаты в массив на выходе для передачи в Verilog
   for(i=0;i<12;i+=1) 
        out[i] = th_pool[i].out;

   for(i=0;i<12;i+=1)
   {
        pthread_mutex_lock   (&th_pool[i].mutex);       //блокируем     
        th_pool[i].in          = in;                                   //передаем расчетной функции данные
        th_pool[i].processing  = 1;                               //ставим флажок разрешения расчета
        pthread_cond_signal  (&th_pool[i].cond_start);  //даем ответный гудок, что бы проснулась
        pthread_mutex_unlock (&th_pool[i].mutex);      //разблокируем   
   }
}

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


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

Tags:
Hubs:
+14
Comments 3
Comments Comments 3

Articles