TRCM - симулируем цифровое железо на C++

Использование и разработка софта (преимущественно на ПЦ)

Moderator: Shaos

User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Вот более вменяемый тест - три полусумматора подключенные как инкремент трёх-битного числа:

Code: Select all

#include "TRCMath.hpp"

using namespace std;

using namespace TRC;

class HalfAdder : public Entity
{
 protected:

// indecies:
  int iA,iB,iS,iC;

// inputs:
  Signal A,B;

// outputs:
  Signal S,C;

 public:

  HalfAdder(const char* s) : Entity(s)
  {

// empty constructor for generic unit

  }

  void step()
  {
    A = io(iA).read();
    B = io(iB).read();

    if(A==TRUE && B==TRUE)
       S = FALSE;
    else if(A==FALSE && B==TRUE)
       S = TRUE;
    else if(A==TRUE && B==FALSE)
       S = TRUE;
    else // if(A==FALSE && B==FALSE)
       S = FALSE;

    if(A==TRUE && B==TRUE)
       C = TRUE;
    else
       C = FALSE;

//    cout << name() << ":" << A << B << "->" << C << S << endl;

    io(iS) << S;
    io(iC) << C;
  }
};

class World : public Entity
{

// indecies:
  int i_increment,i_input,i_output,i_carry;

// internal counter:
  int counter;

 public:

  World() : Entity("World")
  {
    i_increment = at("INC");
    i_input     = at("I",3);
    i_output    = at("O",3);
    i_carry     = at("C2");

    counter = 0;
  }

  void step() // test cases
  {
    Wire<4> vec; // temporary vector
    vec[0] = io(i_output+0).read();
    vec[1] = io(i_output+1).read();
    vec[2] = io(i_output+2).read();
    vec[3] = io(i_carry).read();
    cout << "Case " << counter << " output=" << vec << endl;

    switch(counter++)
    {
       case 0:
       case 1:
       case 2:
       case 3:
          io(i_increment) << FALSE;
          io(i_input+0)   << TRUE;
          io(i_input+1)   << TRUE;
          io(i_input+2)   << TRUE;
          break;

       case 4:
       case 5:
       case 6:
       case 7:
          io(i_increment) << TRUE;
          io(i_input+0)   << TRUE;
          io(i_input+1)   << TRUE;
          io(i_input+2)   << TRUE;
          break;

       case 8:
       case 9:
       case 10:
       case 11:
          io(i_increment) << TRUE;
          io(i_input+0)   << FALSE;
          io(i_input+1)   << FALSE;
          io(i_input+2)   << FALSE;
          break;


    }
  }

};

int main()
{
  System *sys = System::getInstance();

  World world;

  class HalfAdder0 : public HalfAdder
  {
   public:
    HalfAdder0() : HalfAdder("HalfAdder0")
    {
      iA = at("INC");
      iB = at("I[0]");
      iS = at("O[0]");
      iC = at("C0");
    }
  } ha0;

  class HalfAdder1 : public HalfAdder
  {
   public:
    HalfAdder1() : HalfAdder("HalfAdder1")
    {
      iA = at("C0");
      iB = at("I[1]");
      iS = at("O[1]");
      iC = at("C1");
    }
  } ha1;

  class HalfAdder2 : public HalfAdder
  {
   public:
    HalfAdder2() : HalfAdder("HalfAdder2")
    {
      iA = at("C1");
      iB = at("I[2]");
      iS = at("O[2]");
      iC = at("C2");
    }
  } ha2;

  for(int i=0;i<=12;i++)
  {
    sys->prepare();
    ha0.step();
    ha1.step();
    ha2.step();
    world.step();
  }
}
Вывод программы:

Code: Select all

INC <- World (idx=0)
I[0] <- World (idx=1)
I[1] <- World (idx=2)
I[2] <- World (idx=3)
O[0] <- World (idx=4)
O[1] <- World (idx=5)
O[2] <- World (idx=6)
C2 <- World (idx=7)
INC <- HalfAdder0 (idx=0)
I[0] <- HalfAdder0 (idx=1)
O[0] <- HalfAdder0 (idx=4)
C0 <- HalfAdder0 (idx=8)
C0 <- HalfAdder1 (idx=8)
I[1] <- HalfAdder1 (idx=2)
O[1] <- HalfAdder1 (idx=5)
C1 <- HalfAdder1 (idx=9)
C1 <- HalfAdder2 (idx=9)
I[2] <- HalfAdder2 (idx=3)
O[2] <- HalfAdder2 (idx=6)
C2 <- HalfAdder2 (idx=7)
Case 0 output=ZZZZ
Case 1 output=NNNN
Case 2 output=PPPN
Case 3 output=PPPN
Case 4 output=PPPN
Case 5 output=PPPN
Case 6 output=NPPN
Case 7 output=NNPN
Case 8 output=NNNP
Case 9 output=NNNP
Case 10 output=PPPN
Case 11 output=PNNN
Case 12 output=PNNN
Обратите внимание как считается 111+1:
Case 5 output=PPPN (0111 от старого результата)
Case 6 output=NPPN (0110)
Case 7 output=NNPN (0100)
Case 8 output=NNNP (1000)
Только на четвёртом такте бит переполнения прошёл через все три полусумматора :)
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Всё таки решил добавить сишные макросы, чтобы спрятать кишки классов:

Code: Select all

#define STRING(s) #s
#define INSTANCE(x,y) class x##y : public x { public: x##y() : x(STRING(x##y)) {
#define SUBINSTANCE(x,y) class x##y : public x { public: x##y(std::string s) : x(s.c_str()) {
#define NAMED(z) }}z
И вот тест на проверку 8-битного инкрементера, составленного из 8 двоичных полусумматоров:

Code: Select all

#include "../TRCMath.hpp"
#undef DEBUG

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

using namespace std;

using namespace TRC;

class HalfAdder : public Entity
{
 protected:

// indecies:
  int iA,iB,iS,iC;

// inputs:
  Signal A,B;

// outputs:
  Signal S,C;

 public:

  HalfAdder(const char* s) : Entity(s)
  {

// empty constructor for generic unit

  }

  void step()
  {
    A = io(iA).read();
    B = io(iB).read();

    if(A==TRUE && B==TRUE)
       S = FALSE;
    else if(A==FALSE && B==TRUE)
       S = TRUE;
    else if(A==TRUE && B==FALSE)
       S = TRUE;
    else // if(A==FALSE && B==FALSE)
       S = FALSE;

    if(A==TRUE && B==TRUE)
       C = TRUE;
    else
       C = FALSE;

//    cout << name() << ":" << A << B << "->" << C << S << endl;

    io(iS) << S;
    io(iC) << C;
  }
};

class World : public Entity
{

// indecies:
  int i_increment,i_input,i_output,i_carry;

 public:

// internal counter:
  long long counter;

  World() : Entity("World")
  {
    i_increment = at("INC");
    i_input     = at("I",8);
    i_output    = at("O",8);
    i_carry     = at("COUT");

    counter = 0;
  }

  void step() // test cases
  {
    Wire<9> vec; // temporary vector
    vec[8] = io(i_output+0).read();
    vec[7] = io(i_output+1).read();
    vec[6] = io(i_output+2).read();
    vec[5] = io(i_output+3).read();
    vec[4] = io(i_output+4).read();
    vec[3] = io(i_output+5).read();
    vec[2] = io(i_output+6).read();
    vec[1] = io(i_output+7).read();
    vec[0] = io(i_carry).read();
#ifdef DEBUG
    cout << "Case " << counter << " output=" << vec.binarize() << endl;
#endif

    io(i_increment) << TRUE;
    io(i_input+0)   << ((counter&(1<<4))?TRUE:FALSE);
    io(i_input+1)   << ((counter&(1<<5))?TRUE:FALSE);
    io(i_input+2)   << ((counter&(1<<6))?TRUE:FALSE);
    io(i_input+3)   << ((counter&(1<<7))?TRUE:FALSE);
    io(i_input+4)   << ((counter&(1<<8))?TRUE:FALSE);
    io(i_input+5)   << ((counter&(1<<9))?TRUE:FALSE);
    io(i_input+6)   << ((counter&(1<<10))?TRUE:FALSE);
    io(i_input+7)   << ((counter&(1<<11))?TRUE:FALSE);

    counter++;
  }

};

unsigned char BYTE = 0;

int main()
{
 unsigned long t1,t2;
 int i,n = 100000000;
 t1 = clock();
 for(i=0;i<n;i++)
 {
    BYTE++;
    BYTE++;
    BYTE++;
    BYTE++;
    BYTE++;
    BYTE++;
    BYTE++;
    BYTE++;
    BYTE++;
    BYTE++;
 }
 t2 = clock();

  System *sys = System::getInstance();

  World world;

  INSTANCE(HalfAdder,0);
      iA = at("INC");
      iB = at("I[0]");
      iS = at("O[0]");
      iC = at("C0");
  NAMED(ha0);

  INSTANCE(HalfAdder,1);
      iA = at("C0");
      iB = at("I[1]");
      iS = at("O[1]");
      iC = at("C1");
  NAMED(ha1);

  INSTANCE(HalfAdder,2);
      iA = at("C1");
      iB = at("I[2]");
      iS = at("O[2]");
      iC = at("C2");
  NAMED(ha2);

  INSTANCE(HalfAdder,3);
      iA = at("C2");
      iB = at("I[3]");
      iS = at("O[3]");
      iC = at("C3");
  NAMED(ha3);

  INSTANCE(HalfAdder,4);
      iA = at("C3");
      iB = at("I[4]");
      iS = at("O[4]");
      iC = at("C4");
  NAMED(ha4);

  INSTANCE(HalfAdder,5);
      iA = at("C4");
      iB = at("I[5]");
      iS = at("O[5]");
      iC = at("C5");
  NAMED(ha5);

  INSTANCE(HalfAdder,6);
      iA = at("C5");
      iB = at("I[6]");
      iS = at("O[6]");
      iC = at("C6");
  NAMED(ha6);

  INSTANCE(HalfAdder,7);
      iA = at("C6");
      iB = at("I[7]");
      iS = at("O[7]");
      iC = at("COUT");
  NAMED(ha7);

  printf("BYTE=0x%2.2X (%6.6fs or %2.2fns per increment)\n",BYTE,
        (double)(t2-t1)/CLOCKS_PER_SEC,
        (double)(t2-t1)/(n/1e8)/CLOCKS_PER_SEC
        );

  n = 0x100000;
  t1 = clock();
  while(world.counter!=n)
  {
    sys->prepare();
    ha0.step();
    ha1.step();
    ha2.step();
    ha3.step();
    ha4.step();
    ha5.step();
    ha6.step();
    ha7.step();
    world.step();
  }
  t2 = clock();

  printf("%4.4fs or %2.2fns per step\n",
         (double)(t2-t1)/CLOCKS_PER_SEC,
         (double)(t2-t1)/(n/1e9)/CLOCKS_PER_SEC
        );
}
Тут заодно проверка скорости идёт и сравнение с тупым инкрементом одного байта (unsigned char):

Code: Select all

INC <- World (idx=0)
I[0] <- World (idx=1)
I[1] <- World (idx=2)
I[2] <- World (idx=3)
I[3] <- World (idx=4)
I[4] <- World (idx=5)
I[5] <- World (idx=6)
I[6] <- World (idx=7)
I[7] <- World (idx=8)
O[0] <- World (idx=9)
O[1] <- World (idx=10)
O[2] <- World (idx=11)
O[3] <- World (idx=12)
O[4] <- World (idx=13)
O[5] <- World (idx=14)
O[6] <- World (idx=15)
O[7] <- World (idx=16)
COUT <- World (idx=17)
INC <- HalfAdder0 (idx=0)
I[0] <- HalfAdder0 (idx=1)
O[0] <- HalfAdder0 (idx=9)
C0 <- HalfAdder0 (idx=18)
C0 <- HalfAdder1 (idx=18)
I[1] <- HalfAdder1 (idx=2)
O[1] <- HalfAdder1 (idx=10)
C1 <- HalfAdder1 (idx=19)
C1 <- HalfAdder2 (idx=19)
I[2] <- HalfAdder2 (idx=3)
O[2] <- HalfAdder2 (idx=11)
C2 <- HalfAdder2 (idx=20)
C2 <- HalfAdder3 (idx=20)
I[3] <- HalfAdder3 (idx=4)
O[3] <- HalfAdder3 (idx=12)
C3 <- HalfAdder3 (idx=21)
C3 <- HalfAdder4 (idx=21)
I[4] <- HalfAdder4 (idx=5)
O[4] <- HalfAdder4 (idx=13)
C4 <- HalfAdder4 (idx=22)
C4 <- HalfAdder5 (idx=22)
I[5] <- HalfAdder5 (idx=6)
O[5] <- HalfAdder5 (idx=14)
C5 <- HalfAdder5 (idx=23)
C5 <- HalfAdder6 (idx=23)
I[6] <- HalfAdder6 (idx=7)
O[6] <- HalfAdder6 (idx=15)
C6 <- HalfAdder6 (idx=24)
C6 <- HalfAdder7 (idx=24)
I[7] <- HalfAdder7 (idx=8)
O[7] <- HalfAdder7 (idx=16)
COUT <- HalfAdder7 (idx=17)
BYTE=0x00 (2.308623s or 2.31ns per increment)
1.7257s or 1645.74ns per step
С учётом того, что для 8-битного инкрементера распространение правильного значения проходит до 8 тактов мы считаем 1646ns*8=13168ns против 2.31ns у скомпилированной версии - в 5700 раз медленнее!!!

P.S. А если с ключами оптимизации собирать, то получается так:

Code: Select all

-O1:
0.2054s or 195.87ns per step
-O2:
0.2087s or 199.00ns per step
-O3:
0.1822s or 173.73ns per step
почти в 10 раз быстрее чем было (и в 600 раз медленнее нативного байтового инкремента)

P.P.S. это получается около 700 тысяч байтовых инкрементов в секунду на моей машине - Intel 8080 в реальном времени не сэмулишь, но полюбому побыстрее будет чем в логисиме ;)

P.P.P.S. с другой стороны если эмулировать схему быстрого переноса, то икремент раза в 4 побыстрее должно быть, а так получилось почти 6 миллионов шагов симуляции в секунду ( для 18 точек коннекта, на одной коре что равносильно порядка 100 милионнов точек коннекта в секунду, но в теории можно и распараллелить на все доступные коры ; )
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Вот чего существут из похожего кроме SystemC:
https://www.veripool.org/wiki/verilator - Verilator - транслятор из Verilog в C++ и SystemC
http://verilog2cpp.sourceforge.net/ - Verilog2C++ (по ссылке выше написано, что проект заброшен)
Надо попробовать их тоже...

P.S. вот тут достаточно простой тест скорости с цифрами:
https://www.veripool.org/boards/2/topics/1896-Verilator-Verilator-Speed
но чуваку отвечают, что его тест слишком простой, чтобы увидеть все чудеса ускорения Верилятора...

P.P.S. существует очень компактная реализация 8080 на Верилоге (с микрокодом):
https://opencores.org/project/light8080
Xilinx XST on Spartan 3 (-5 grade):

204 LUTs plus 1 BRAM @ 80 MHz (optimized for area)
228 LUTs plus 1 BRAM @ 100 MHz (optimized for speed)
618 LUTs @ 53 MHz (optimized for area, no block ram)
надо её чтоли попробовать сконвертить да погонять...

P.P.P.S. есть более старая реализация 8080 потяжелее (без микрокода):
https://opencores.org/project/cpu8080

P.P.P.P.S. ещё есть софт-процы OPC (One Page Computing) - одна страница на верилоге для каждого процыка от OPC-1 до OPC-7:
https://hackaday.io/project/25357-opc-1-cpu-for-cpld
https://github.com/revaldinho/opc
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Shaos wrote:Короче - до этого момента я всегда все свои симуляшные движки писал отталкиваясь от идеи, что у нас есть входы и есть выходы, но в реальности всё не так - источники напряжений могут подключаться и отключаться от проводников (двунаправленные сигналы) - тот же FPGA например невозможно сэмулировать не имея возможности переподключаться то так, то эдак. Вобщем я решил идти от уже проверенных промышленных подходов (добавив немного своего троичного) - на проводник могут накладываться вот такие значения:

Code: Select all

 const char TRUE     = 'P'; // прямое подключение к питанию
 const char MAYBE    = 'O'; // третье промежуточное состояние (для симуляции троичных схем)
 const char FALSE    = 'N'; // прямое подключение к земле
 const char NC       = 'Z'; // не подключено (высокий импенданс)
 const char PULLUP   = '1'; // слабая подтяжка к питанию
 const char PULLDOWN = '0'; // слабая подтяжка к земле
 const char INVALID  = '?'; // ошибка (конфликт)
Например на NC может наложиться PULLUP, на который может наложится скажем FALSE, а вот если на проводнике был FALSE, то при попытке применить к нему TRUE результирующее состояние получится INVALID

P.S. Возможно 'X' надо заменить скажем на знак вопроса, чтобы народ случайно не спутал с "любым значением", которое имеет смысл только в картах карно...
Наверное пока не поздно надо добавлять слабую подтяжку к промежуточному значению - PULLMID которая скажем символ '-' (минус)? Ну или '~' (тильда)

P.S. Любое значение 'X' кстати наверное таки надо добавить - для сравнений (и возможно следует разделить понятия "любое двоичное значение" и "любое троичное значение")
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Клапауций
Banned
Posts: 412
Joined: 29 Jun 2018 08:48

Re: TRCM - симулируем цифровое железо на C++

Post by Клапауций »

Shaos wrote:следует разделить понятия "любое двоичное значение" и "любое троичное значение")
так или иначе - результатом всего этого будет: или "любое двоичное" или "любое".
любое значение вообще.

потому, как: почему троичная система, а не n-ричная, где n - бесконечность?
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Клапауций wrote:
Shaos wrote:следует разделить понятия "любое двоичное значение" и "любое троичное значение")
так или иначе - результатом всего этого будет: или "любое двоичное" или "любое".
любое значение вообще.

потому, как: почему троичная система, а не n-ричная, где n - бесконечность?
N-ричные системы где N>3 не имеют никаких преимуществ - только лишние сложности...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Клапауций
Banned
Posts: 412
Joined: 29 Jun 2018 08:48

Re: TRCM - симулируем цифровое железо на C++

Post by Клапауций »

Shaos wrote:N-ричные системы где N>3 не имеют никаких преимуществ - только лишние сложности...
ок. если теоретически N>3 не имеет смысла, то вопрос закрыт.
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Клапауций wrote:
Shaos wrote:N-ричные системы где N>3 не имеют никаких преимуществ - только лишние сложности...
ок. если теоретически N>3 не имеет смысла, то вопрос закрыт.
Я в том же 2010 когда нарисовал свой классический сдвоенный TRIMUX из двух DG403 также нарисовал четверичный мультиплексор на тех же DG403 - на каждую границу уровней надо 2 половинки DG403, т.е. чем больше "ричность" тем больше DG403-х...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Всё - перевалил за 1000 строк C++ кода :)

Теперь у меня базовым классом является нетемплейтный класс Wires, а уже все остальные темплейтные классы происходят от него - так проще потом будет с разными типами цепей работать...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Попробую вручную переписать процык OPC1 с Верилога на C++

Code: Select all

module opccpu( inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b);
   parameter FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4 ;
   parameter AND=5'bx0000,  LDA=5'bx0001, NOT=5'bx0010, ADD=5'bx0011;
   parameter LDAP=5'b01001, STA=5'b11000, STAP=5'b01000;
   parameter JPC=5'b11001,  JPZ=5'b11010, JP=5'b11011,  JSR=5'b11100;
   parameter RTS=5'b11101,  LXA=5'b11110;
   reg [10:0] OR_q, PC_q;
   reg [7:0]  ACC_q;
   reg [2:0]  FSM_q;
   reg [4:0]  IR_q;
   reg [2:0]  LINK_q; // bottom bit doubles up as carry flag
`define CARRY LINK_q[0]
   wire       writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ;
   assign rnw = ~writeback_w ;
   assign data = (writeback_w)?ACC_q:8'bz ;
   assign address = ( writeback_w || FSM_q == RDMEM || FSM_q==RDMEM2)? OR_q:PC_q;

   always @ (posedge clk or negedge reset_b )
     if (!reset_b)
       FSM_q <= FETCH0;
     else
       case(FSM_q)
         FETCH0 : FSM_q <= FETCH1;
         FETCH1 : FSM_q <= (IR_q[4])?EXEC:RDMEM ;
         RDMEM  : FSM_q <= (IR_q==LDAP)?RDMEM2:EXEC;
         RDMEM2 : FSM_q <= EXEC;
         EXEC   : FSM_q <= FETCH0;
       endcase

   always @ (posedge clk)
     begin
        IR_q <= (FSM_q == FETCH0)? data[7:3] : IR_q;
        // OR_q[10:8] is upper part nybble for address - needs to be zeroed for both pointer READ and WRITE operations once ptr val is read
        OR_q[10:8] <= (FSM_q == FETCH0)? data[2:0]: (FSM_q==RDMEM)?3'b0:OR_q[10:8];
        OR_q[7:0] <= data; //Lowest byte of OR is dont care in FETCH0 and at end of EXEC
        if ( FSM_q == EXEC )
          casex (IR_q)
            JSR    : {LINK_q,ACC_q} <= PC_q ;
            LXA    : {LINK_q,ACC_q} <= {ACC_q[2:0], 5'b0, LINK_q};
            AND    : {`CARRY, ACC_q}  <= {1'b0, ACC_q & OR_q[7:0]};
            NOT    : ACC_q <= ~OR_q[7:0];
            LDA    : ACC_q <= OR_q[7:0];
            LDAP   : ACC_q <= OR_q[7:0];
            ADD    : {`CARRY,ACC_q} <= ACC_q + `CARRY + OR_q[7:0];
            default: {`CARRY,ACC_q} <= {`CARRY,ACC_q};
          endcase
     end

   always @ (posedge clk or negedge reset_b )
     if (!reset_b) // On reset start execution at 0x100 to leave page zero clear for variables
       PC_q <= 11'h100;
     else
       if ( FSM_q == FETCH0 || FSM_q == FETCH1 )
         PC_q <= PC_q + 1;
       else
         case (IR_q)
           JP    : PC_q <= OR_q;
           JPC   : PC_q <= (`CARRY)?OR_q:PC_q;
           JPZ   : PC_q <= ~(|ACC_q)?OR_q:PC_q;
           JSR   : PC_q <= OR_q;
           RTS   : PC_q <= {LINK_q, ACC_q};
           default: PC_q <= PC_q;
         endcase
endmodule
У всех OPC есть тесты - так что можно легко проверить удачно сконвертилось или нет :roll:
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Вобщем внутри каждого класса, описывающего пучёк проводов (Wire<N>, Uint<N>, Sint<N> и Tint<N>), каждый провод является символом в строке - т.е. их всегда можно заслать на печать т.к. это обычная сишная zero-terminated строка). Отсюда проблема - обращения к строке по индексу сначала (слева), а при представлении числа битами (тритами) как бы подразумевается, что слева самый старший, а справа - самый младший разряд. Получается строку надо переворачивать - вот я думаю переворачивать её только при печати в консоль и при создании объектов из строк, а внтури оно всё также будет идти от младшего к старшему. Пример:

Code: Select all

Wire<5> w; // внутри "ZZZZZ"
w[0] = TRUE; // внутри "PZZZZ"
cout << w << endl; // тут должно будет напечатать "ZZZZP" (нулевой разряд - самый правый)
Wire<5> ww("PNNNN"); // внутри оно станет "NNNNP"
Uint<5> u = ww; // внутри оно всё также "NNNNP" (что будет означать двоичное число 1000)
Sint<5> s = ww; // внутри оно всё также "NNNNP" (что будет означать двоичное число 1000)
// и потом можно будет делать так:
cout << u << endl; // напечатает 16
cout << s << endl; // напечатает -16 (потому что старший разряд установлен в логическую единицу)
У кого какие соображения?

P.S. Всё сделал :mrgreen:
https://gitlab.com/ternary/trcm

P.P.S. Uint и Sint отличаются при конверсии из числовых типов си и в числовые типы (например для печати), а также тем в какой момент будет происходить переполнение при сложении/вычитании (Uint<16> например переполнится если сложить 65535 и 1 или вычесть 1 из 0, а Sint<16> если сложить 32767 и 1 или вычесть 1 из -32768)
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Shaos wrote:... ещё есть софт-процы OPC (One Page Computing) - одна страница на верилоге для каждого процыка от OPC-1 до OPC-7:
https://hackaday.io/project/25357-opc-1-cpu-for-cpld
https://github.com/revaldinho/opc
Пока вот как получается (комментарии с начала строки - это оригинальный верилоговский код OPC1):

Code: Select all

// OPC1 test - based on https://github.com/revaldinho/opc/tree/master/opc1

#include <TRCMath.hpp>

using namespace std;

using namespace TRC;

// module opccpu( inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b); // SEE BELOW

class OPC1 : public Entity
{
  public:

//   parameter FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4 ;
   const unsigned FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4;

//   parameter AND=5'bx0000,  LDA=5'bx0001, NOT=5'bx0010, ADD=5'bx0011;
   const Wire<5> AND, LDA, NOT, ADD; // SEE BELOW

//   parameter LDAP=5'b01001, STA=5'b11000, STAP=5'b01000;
   const Wire<5> LDAP, STA, STAP; // SEE BELOW

//   parameter JPC=5'b11001,  JPZ=5'b11010, JP=5'b11011,  JSR=5'b11100;
   const Wire<5> JPC, JPZ, JP, JSR; // SEE BELOW

//   parameter RTS=5'b11101,  LXA=5'b11110;
   const Wire<5> RTS, LXA; // SEE BELOW

//   reg [10:0] OR_q, PC_q;
   Wire<11> OR_q;
   Uint<11> PC_q;

//   reg [7:0]  ACC_q;
   Uint<8> ACC_q;

//   reg [2:0]  FSM_q;
   Uint<3> FSM_q;

//   reg [4:0]  IR_q;
   Wire<5> IR_q;

//   reg [2:0]  LINK_q; // bottom bit doubles up as carry flag
   Wire<3> LINK_q;

//`define CARRY LINK_q[0]
#define CARRY LINK_q[0]

// IMPLEMENTATION OF INTERFACE (inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b);

   int i_data, i_address, i_rnw, i_clk, i_reset_b;

   OPC1() : Entity("OPC1"),
            // initialization for constant Wire<5>s:
            AND("X0000"), LDA("X0001"), NOT("X0010"), ADD("X0011"),
            LDAP("01001"), STA("11000"), STAP("01000"),
            JPC("11001"), JPZ("11010"), JP("11011"), JSR("11100"),
            RTS("11101"), LXA("11110")
   {
      // global attachements (we have the only instance of the class so it will work)
      i_data = at("DATA",8,PULLUP);
      i_address = at("ADDRESS",11);
      i_rnw = at("RNW");
      i_clk = at("CLK");
      i_reset_b = at("RESET_B");
   }

   void step()
   {

     Signal reset_b = io(i_reset_b).read();

//   wire       writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ;
     Signal writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ;

//   assign rnw = ~writeback_w ;
     Signal rnw = ~writeback_w;

//   assign data = (writeback_w)?ACC_q:8'bz ;
     Wire<8> data; // it's all Z by default
     if(writeback_w) data = ACC_q;
     else // below required for later logic
     {
        for(int i=0;i<8;i++) data[i] = io(i_data+i).read();
     }

//   assign address = ( writeback_w || FSM_q == RDMEM || FSM_q==RDMEM2)? OR_q:PC_q;
     Wire<11> address = ( (bool)writeback_w || FSM_q == RDMEM || FSM_q==RDMEM2 )? OR_q:PC_q;

//   always @ (posedge clk or negedge reset_b )
     if(posedge(i_clk) || negedge(i_reset_b))
     {

//     if (!reset_b)
       if (!reset_b)

//       FSM_q <= FETCH0;
         FSM_q = FETCH0;

//     else
       else
       {
//       case(FSM_q)
//         FETCH0 : FSM_q <= FETCH1;
         if(FSM_q == FETCH0) FSM_q = FETCH1;

//         FETCH1 : FSM_q <= (IR_q[4])?EXEC:RDMEM ;
         else if(FSM_q == FETCH1) FSM_q = (IR_q[4]==TRUE)?EXEC:RDMEM;

//         RDMEM  : FSM_q <= (IR_q==LDAP)?RDMEM2:EXEC;
         else if(FSM_q == RDMEM) FSM_q = (IR_q==LDAP)?RDMEM2:EXEC;

//         RDMEM2 : FSM_q <= EXEC;
         else if(FSM_q == RDMEM2) FSM_q = EXEC;

//         EXEC   : FSM_q <= FETCH0;
         else if(FSM_q == EXEC) FSM_q = FETCH0;

//       endcase
       }
     }

//   always @ (posedge clk)
     if(posedge(i_clk))

//     begin
     {

//        IR_q <= (FSM_q == FETCH0)? data[7:3] : IR_q;
          if(FSM_q == FETCH0)
          {
            IR_q[0] = data[3];
            IR_q[1] = data[4];
            IR_q[2] = data[5];
            IR_q[3] = data[6];
            IR_q[4] = data[7];
          }

//        // OR_q[10:8] is upper part nybble for address - needs to be zeroed
//        // for both pointer READ and WRITE operations once ptr val is read
//        OR_q[10:8] <= (FSM_q == FETCH0)? data[2:0]: (FSM_q==RDMEM)?3'b0:OR_q[10:8];
          if(FSM_q == FETCH0)
          {
             OR_q[8] = data[0];
             OR_q[9] = data[1];
             OR_q[10] = data[2];
          }
          else if(FSM_q == RDMEM)
          {
             OR_q[8] = FALSE;
             OR_q[9] = FALSE;
             OR_q[10] = FALSE;
          }

//        OR_q[7:0] <= data; //Lowest byte of OR is dont care in FETCH0 and at end of EXEC
          for(int i=0;i<8;i++) OR_q[i] = data[i];

//        if ( FSM_q == EXEC )
          if(FSM_q == EXEC)
          {

//          casex (IR_q)
//            JSR    : {LINK_q,ACC_q} <= PC_q ;
              if(IR_q==JSR)
              {
                 LINK_q = PC_q.part(8,10);
                 ACC_q  = PC_q.part(0,7);
              }

//            LXA    : {LINK_q,ACC_q} <= {ACC_q[2:0], 5'b0, LINK_q};
              else if(IR_q==LXA)
              {
                 LINK_q = ACC_q.part(0,2);
                 for(int i=7;i>=0;i--)
                 {
                     if(i>=3) ACC_q[i] = FALSE;
                     else ACC_q[i] = LINK_q[i];
                 }
              }

//            AND    : {`CARRY, ACC_q}  <= {1'b0, ACC_q & OR_q[7:0]};
              else if(IR_q==AND)
              {
                 CARRY = FALSE;
                 ACC_q = ACC_q & OR_q.part(0,7);
              }

//            NOT    : ACC_q <= ~OR_q[7:0];
              else if(IR_q==NOT)
              {
                 ACC_q = ~OR_q.part(0,7);
              }

//            LDA    : ACC_q <= OR_q[7:0];
              else if(IR_q==LDA)
              {
                 ACC_q = OR_q.part(0,7);
              }

//            LDAP   : ACC_q <= OR_q[7:0];
              else if(IR_q==LDAP)
              {
                 ACC_q = OR_q.part(0,7);
              }

//            ADD    : {`CARRY,ACC_q} <= ACC_q + `CARRY + OR_q[7:0];
              else if(IR_q==ADD)
              {
                 ACC_q += OR_q.part(0,7);
                 if(CARRY==TRUE) ACC_q++;
                 if(ACC_q.overflow())
                      CARRY = TRUE;
                 else CARRY = FALSE;
              }

//            default: {`CARRY,ACC_q} <= {`CARRY,ACC_q};
//          endcase
          }

//     end
     }

//   always @ (posedge clk or negedge reset_b )
     if(posedge(i_clk) || negedge(i_reset_b))
     {

//     if (!reset_b) // On reset start execution at 0x100 to leave page zero clear for variables
       if(!reset_b)

//       PC_q <= 11'h100;
         PC_q = 0x100;

//     else
       else

//       if ( FSM_q == FETCH0 || FSM_q == FETCH1 )
         if(FSM_q==FETCH0 || FSM_q==FETCH1)

//         PC_q <= PC_q + 1;
           PC_q++;

//       else
         else
         {

//         case (IR_q)
//           JP    : PC_q <= OR_q;
             if(IR_q==JP)
             {
                PC_q = OR_q;
             }

//           JPC   : PC_q <= (`CARRY)?OR_q:PC_q;
             else if(IR_q==JPC)
             {
                PC_q = (CARRY==TRUE)?OR_q:PC_q;
             }

//           JPZ   : PC_q <= ~(|ACC_q)?OR_q:PC_q;
             else if(IR_q==JPZ)
             {
                PC_q = (ACC_q==0)?OR_q:PC_q;
             }

//           JSR   : PC_q <= OR_q;
             else if(IR_q==JSR)
             {
                PC_q = OR_q;
             }

//           RTS   : PC_q <= {LINK_q, ACC_q};
             else if(IR_q==RTS)
             {
                for(int i=10;i>=0;i--)
                {
                   if(i>=8) PC_q[i]=LINK_q[i-8];
                   else PC_q[i]=ACC_q[i];
                }
             }

//           default: PC_q <= PC_q;
//         endcase
         }

         if(writeback_w)
         {
             for(int i=0;i<8;i++) io(i_data) << data[i];
         }
         for(int i=0;i<11;i++) io(i_address) << address[i];
         io(i_rnw) << rnw;
      }

//endmodule
   }
};
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

В коде OPC1 есть проблема - там аж ТРИ блока always @ (posedge clk ... которые как бы параллельно исполняются - там читаются и пишутся по сути один и тот же набор регистров. Это значит что выполнять эти блоки последовательно нельзя т.к. более поздняя логика будет использовать уже измененные в ранней логике значения, а оно всё должно меняться одномоментно - по изменению CLK из нуля в "1". Значит придётся хитрить и мухлевать, например вот так:

Code: Select all

// OPC1 test - based on https://github.com/revaldinho/opc/tree/master/opc1

#include <TRCMath.hpp>

using namespace std;

using namespace TRC;

// module opccpu( inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b); // SEE BELOW

class OPC1 : public Entity
{
  public:

//   parameter FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4 ;
   const unsigned FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4;

//   parameter AND=5'bx0000,  LDA=5'bx0001, NOT=5'bx0010, ADD=5'bx0011;
   const Wire<5> AND, LDA, NOT, ADD; // SEE BELOW

//   parameter LDAP=5'b01001, STA=5'b11000, STAP=5'b01000;
   const Wire<5> LDAP, STA, STAP; // SEE BELOW

//   parameter JPC=5'b11001,  JPZ=5'b11010, JP=5'b11011,  JSR=5'b11100;
   const Wire<5> JPC, JPZ, JP, JSR; // SEE BELOW

//   parameter RTS=5'b11101,  LXA=5'b11110;
   const Wire<5> RTS, LXA; // SEE BELOW

//   reg [10:0] OR_q, PC_q;
   Wire<11> OR_q,OR_q_REG;
   Uint<11> PC_q,PC_q_REG;

//   reg [7:0]  ACC_q;
   Uint<8> ACC_q,ACC_q_REG;

//   reg [2:0]  FSM_q;
   Uint<3> FSM_q,FSM_q_REG;

//   reg [4:0]  IR_q;
   Wire<5> IR_q,IR_q_REG;

//   reg [2:0]  LINK_q; // bottom bit doubles up as carry flag
   Wire<3> LINK_q,LINK_q_REG;
// SHOUD WE HAVE A MACROS REG(Wire<3>,LINK_q)?

//`define CARRY LINK_q[0]
#define CARRY LINK_q[0]
// BUT FOR THE LEFT SIDE WE NEED TO USE TEMPORARY COPY!!!
#define CARRY_REG LINK_q_REG[0]

// TEMPORARY SIGNALS:
   Signal reset_b, writeback_w, rnw;
   Wire<11> address;
   Wire<8> data; // it's all Z by default

// IMPLEMENTATION OF INTERFACE (inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b);

   int i_data, i_address, i_rnw, i_clk, i_reset_b;

   OPC1() : Entity("OPC1"),
            // initialization for constant Wire<5>s:
            AND("X0000"), LDA("X0001"), NOT("X0010"), ADD("X0011"),
            LDAP("01001"), STA("11000"), STAP("01000"),
            JPC("11001"), JPZ("11010"), JP("11011"), JSR("11100"),
            RTS("11101"), LXA("11110")
   {
      // global attachements (we have the only instance of the class so it will work)
      i_data = at("DATA",8,PULLUP);
      i_address = at("ADDRESS",11);
      i_rnw = at("RNW");
      i_clk = at("CLK");
      i_reset_b = at("RESET_B");
   }

   void step()
   {

     reset_b = io(i_reset_b).read();

//   wire       writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ;
     writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ;

//   assign rnw = ~writeback_w ;
     rnw = ~writeback_w;

//   assign data = (writeback_w)?ACC_q:8'bz ;
     if(writeback_w) data = ACC_q;
     else // below required for later logic
     {
        for(int i=0;i<8;i++) data[i] = io(i_data+i).read();
     }

//   assign address = ( writeback_w || FSM_q == RDMEM || FSM_q==RDMEM2)? OR_q:PC_q;
     address = ( (bool)writeback_w || FSM_q == RDMEM || FSM_q == RDMEM2 )? OR_q:PC_q;

//   always @ (posedge clk or negedge reset_b )
     if(posedge(i_clk) || negedge(i_reset_b)) // WRITE FSM_q
     {

//     if (!reset_b)
       if (!reset_b)

//       FSM_q <= FETCH0;
         FSM_q_REG = FETCH0;

//     else
       else
       {
//       case(FSM_q)
//         FETCH0 : FSM_q <= FETCH1;
         if(FSM_q_REG == FETCH0) FSM_q = FETCH1;

//         FETCH1 : FSM_q <= (IR_q[4])?EXEC:RDMEM ;
         else if(FSM_q == FETCH1) FSM_q_REG = (IR_q[4]==TRUE)?EXEC:RDMEM;

//         RDMEM  : FSM_q <= (IR_q==LDAP)?RDMEM2:EXEC;
         else if(FSM_q == RDMEM) FSM_q_REG = (IR_q==LDAP)?RDMEM2:EXEC;

//         RDMEM2 : FSM_q <= EXEC;
         else if(FSM_q == RDMEM2) FSM_q_REG = EXEC;

//         EXEC   : FSM_q <= FETCH0;
         else if(FSM_q == EXEC) FSM_q_REG = FETCH0;

//       endcase
       }
     }

//   always @ (posedge clk)
     if(posedge(i_clk)) // WRITE IR_q, OR_q, LINK_q, ACC_q and CARRY

//     begin
     {

//        IR_q <= (FSM_q == FETCH0)? data[7:3] : IR_q;
          if(FSM_q == FETCH0)
          {
            IR_q_REG[0] = data[3];
            IR_q_REG[1] = data[4];
            IR_q_REG[2] = data[5];
            IR_q_REG[3] = data[6];
            IR_q_REG[4] = data[7];
          }

//        // OR_q[10:8] is upper part nybble for address - needs to be zeroed
//        // for both pointer READ and WRITE operations once ptr val is read
//        OR_q[10:8] <= (FSM_q == FETCH0)? data[2:0]: (FSM_q==RDMEM)?3'b0:OR_q[10:8];
          if(FSM_q == FETCH0)
          {
             OR_q_REG[8] = data[0];
             OR_q_REG[9] = data[1];
             OR_q_REG[10] = data[2];
          }
          else if(FSM_q == RDMEM)
          {
             OR_q_REG[8] = FALSE;
             OR_q_REG[9] = FALSE;
             OR_q_REG[10] = FALSE;
          }
          else
          {
             OR_q_REG[8] = OR_q[8];
             OR_q_REG[9] = OR_q[9];
             OR_q_REG[10] = OR_q[10];
          }

//        OR_q[7:0] <= data; //Lowest byte of OR is dont care in FETCH0 and at end of EXEC
          for(int i=0;i<8;i++) OR_q[i] = data[i];

//        if ( FSM_q == EXEC )
          if(FSM_q == EXEC)
          {

//          casex (IR_q)
//            JSR    : {LINK_q,ACC_q} <= PC_q ;
              if(IR_q==JSR)
              {
                 LINK_q_REG = PC_q.part(8,10);
                 ACC_q_REG  = PC_q.part(0,7);
              }

//            LXA    : {LINK_q,ACC_q} <= {ACC_q[2:0], 5'b0, LINK_q};
              else if(IR_q==LXA)
              {
                 LINK_q_REG = ACC_q.part(0,2);
                 for(int i=7;i>=0;i--)
                 {
                     if(i>=3) ACC_q_REG[i] = FALSE;
                     else ACC_q_REG[i] = LINK_q[i];
                 }
              }

//            AND    : {`CARRY, ACC_q}  <= {1'b0, ACC_q & OR_q[7:0]};
              else if(IR_q==AND)
              {
                 CARRY_REG = FALSE;
                 ACC_q_REG = ACC_q & OR_q.part(0,7);
              }

//            NOT    : ACC_q <= ~OR_q[7:0];
              else if(IR_q==NOT)
              {
                 ACC_q_REG = ~OR_q.part(0,7);
              }

//            LDA    : ACC_q <= OR_q[7:0];
              else if(IR_q==LDA)
              {
                 ACC_q_REG = OR_q.part(0,7);
              }

//            LDAP   : ACC_q <= OR_q[7:0];
              else if(IR_q==LDAP)
              {
                 ACC_q_REG = OR_q.part(0,7);
              }

//            ADD    : {`CARRY,ACC_q} <= ACC_q + `CARRY + OR_q[7:0];
              else if(IR_q==ADD)
              {
                 ACC_q_REG = ACC_q + (CARRY==TRUE)?1:0 + OR_q.part(0,7);
                 if(ACC_q.overflow())
                      CARRY_REG = TRUE;
                 else CARRY_REG = FALSE;
              }

//            default: {`CARRY,ACC_q} <= {`CARRY,ACC_q};
              else
              {
                 CARRY_REG = CARRY;
                 ACC_q_REG = ACC_q;
              }

//          endcase
          }

//     end
     }

//   always @ (posedge clk or negedge reset_b )
     if(posedge(i_clk) || negedge(i_reset_b)) // WRITE PC_q
     {

//     if (!reset_b) // On reset start execution at 0x100 to leave page zero clear for variables
       if(!reset_b)

//       PC_q <= 11'h100;
         PC_q_REG = 0x100;

//     else
       else

//       if ( FSM_q == FETCH0 || FSM_q == FETCH1 )
         if(FSM_q==FETCH0 || FSM_q==FETCH1)

//         PC_q <= PC_q + 1;
           PC_q_REG = PC_q + 1;

//       else
         else
         {

//         case (IR_q)
//           JP    : PC_q <= OR_q;
             if(IR_q==JP)
             {
                PC_q_REG = OR_q;
             }

//           JPC   : PC_q <= (`CARRY)?OR_q:PC_q;
             else if(IR_q==JPC)
             {
                PC_q_REG = (CARRY==TRUE)?OR_q:PC_q;
             }

//           JPZ   : PC_q <= ~(|ACC_q)?OR_q:PC_q;
             else if(IR_q==JPZ)
             {
                PC_q_REG = ((unsigned)ACC_q==0)?OR_q:PC_q;
             }

//           JSR   : PC_q <= OR_q;
             else if(IR_q==JSR)
             {
                PC_q_REG = OR_q;
             }

//           RTS   : PC_q <= {LINK_q, ACC_q};
             else if(IR_q==RTS)
             {
                for(int i=10;i>=0;i--)
                {
                   if(i>=8) PC_q_REG[i]=LINK_q[i-8];
                   else PC_q_REG[i]=ACC_q[i];
                }
             }

//           default: PC_q <= PC_q;
             else PC_q_REG = PC_q;

//         endcase
         }

         OR_q = OR_q_REG;
         PC_q = PC_q_REG;
         ACC_q = ACC_q_REG;
         FSM_q = FSM_q_REG;
         IR_q = IR_q_REG;
         LINK_q = LINK_q_REG;

         if(writeback_w)
         {
             for(int i=0;i<8;i++) io(i_data) << data[i];
         }
         for(int i=0;i<11;i++) io(i_address) << address[i];
         io(i_rnw) << rnw;
      }

//endmodule
   }
};
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Короче суть мухлежа состоит в том, что каждый регистр, сохраняющий N сигналов, представляется парой пучков проводов:

Code: Select all

Wire<11> OR_q,OR_q_REG;
и в обработчике шага эмуляции все изменения пишутся в *_REG и только в самом конце, когда все вычисления выполнены, *_REG копируют свои значения в основные переменные...
Я тут за главного - если что шлите мыло на me собака shaos точка net
User avatar
Shaos
Admin
Posts: 24080
Joined: 08 Jan 2003 23:22
Location: Silicon Valley

Re: TRCM - симулируем цифровое железо на C++

Post by Shaos »

Чото я запустил вопрос - надо возвращаться в тему...
Я тут за главного - если что шлите мыло на me собака shaos точка net