Сокеты модели уровня передачи SystemC: двусторонняя связь

В документации Doulos SystemC Transfer Level Model написано

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

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

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

adder.cc

#define SC_INCLUDE_DYNAMIC_PROCESS

#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"
#include <iostream>

using namespace sc_core;
using namespace std;

#include "adder.h"

adder::adder(sc_module_name name)
    : sc_module(name), socket("socket2")
{
    socket.register_b_transport(this, &adder::b_transport);
    socket.register_transport_dbg(this, &adder::transport_dbg);
}

void adder::b_transport(tlm::tlm_generic_payload& trans, sc_time& delay)
{
    tlm::tlm_command cmd = trans.get_command();
    sc_dt::uint64   addr = trans.get_address();
    uint32_t    *ptr = (uint32_t*)trans.get_data_ptr();
    unsigned int    len = trans.get_data_length();
    unsigned char   *byt = trans.get_byte_enable_ptr();
    unsigned int    wid = trans.get_streaming_width();

    addend1 = *ptr;
    addend2 = *(++ptr);
    add();
    cout << "addend1: " << addend1 << endl;
        cout << "addend2: " << addend2 << endl;     
    cout << "sum: " << sum << endl;

    uint32_t *return_sum_loc = ptr;

    for(int i = 0; i< 2; i++) {
        return_sum_loc++;
    }

    memcpy(return_sum_loc, (char*) &sum, sizeof(uint32_t));
    cout << "New sum for return: " << *(return_sum_loc) << endl;
}

unsigned int adder::transport_dbg(tlm::tlm_generic_payload& trans)
{
    return 0;
}

void adder::add()
{
    sum = addend1 + addend2;
}

Затем у меня есть модуль test_bench, который будет инициатором.

test_bench.cc

#define SC_INCLUDE_DYNAMIC_PROCESS

#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"

using namespace sc_core;
using namespace std;

#include "test_bench.h"
#include <fstream>
#include <iostream>

test_bench::test_bench(sc_module_name name):
    sc_module(name), socket("socket")
{
    SC_THREAD(run_tests);
}

void test_bench::run_tests()
{
    ifstream infile("./adder.golden.dat");
    ofstream ofs;
    ofs.open("./adder.dat");
    
    uint32_t theoretical_sum = 0;

    while(infile >> data[0] >> data[1] >> theoretical_sum)
    {   
        tlm::tlm_generic_payload *trans = new tlm::tlm_generic_payload;
        sc_time delay = sc_time(10, SC_NS);
    
        cout << "Sending" << endl;
        cout << "Data[0]: " << data[0] << endl;
        cout << "Data[1]: " << data[1] << endl; 

        trans->set_data_ptr((unsigned char*)data);

        socket->b_transport(*trans, delay);

        cout << "data[2]" << data[2] << endl;

        ofs << data[0] << "\t" << data[1] << "\t" << data[2] << "\n";       

        delete trans;
    }
    infile.close();
    ofs.close();

    printf ("Comparing against output data \n");
    if (system("diff -w adder.dat adder.golden.dat")) 
    {

        cout << "*******************************************" << endl;
        cout << "FAIL: Output DOES NOT match the golden output" << endl;
        cout << "*******************************************" << endl;
    } 
    else 
    {
        cout << "*******************************************" << endl;
        cout << "PASS: The output matches the golden output!" << endl;
        cout << "*******************************************" << endl;
    }

}

Вот родительский модуль, который создает и соединяет их.

main.cc

#include "systemc.h"
#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"
#include "tlm_utils/tlm_quantumkeeper.h"

using namespace sc_core;
using namespace sc_dt;
using namespace std;

#include "test_bench.h"
#include "adder.h"

SC_MODULE(Top)
{
    test_bench  *tb;
    adder       *ad;

    sc_signal<bool> rst;

    sc_signal<bool> tb_irq;
    sc_signal<bool> ad_irq;

    Top(sc_module_name name) :
        rst("rst")
    {
        tb = new test_bench("test_bench");
        ad = new adder("adder");

        tb->socket.bind(ad->socket);
        tb->irq(tb_irq);
        ad->irq(ad_irq);
    }

};

int sc_main(int argc, char *argv[])
{
    Top *top = new Top("Top");
    sc_start();

    
}

Когда я запускаю исполняемый файл, это результат, который я получаю.

< 1 0 0

< 1 1 0

< 2 1 0

< 2 2 0

< 2 3 0

< 3 3 0

< 4 3 0

< 4 4 0

< 5 4 0

< 5 5 0


1 0 1

1 1 2

2 1 3

2 2 4

2 3 5

3 3 6

4 3 7

4 4 8

5 4 9

5 5 10


FAIL: Выход НЕ соответствует золотому выходу

Итак, моя первоначальная мысль заключалась в том, что вы передаете по значению эту полезную нагрузку в функцию b_transport инициатора, которая привязана к цели. Цель получит и расшифрует эту полезную нагрузку. Эта часть происходит. Я могу проанализировать uint32_t, переданный по значению, в data[]. Что я в конце концов понял, основываясь на своих 0 возвращаемых значениях, которые были записаны в переданную память, так это то, что это на самом деле не передается по значению. По какой-то причине он создается как тип указателя, затем он разыменовывается при передаче. По сути, это лишает цель возможности манипулировать памятью, которая была передана для передачи ответа инициатору.

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


person John Frye    schedule 22.12.2017    source источник
comment
adder.h отсутствует   -  person random    schedule 23.12.2017


Ответы (1)


Это подпись вызова b_transport:

void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay )

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

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

Блокирующий транспортный протокол, реализуемый вызовом b_transport, является однонаправленным. Модуль-инициатор активен, целевой модуль пассивен. Транзакция завершается одним вызовом. Target может вызывать wait() внутри реализации b_transport.

Но TLM2.0 также поддерживает неблокирующий протокол, состоящий из двух вызовов:

  • nb_transport_fw от инициатора к цели
  • nb_transport_bw от цели к инициатору

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

Однако на практике почти все используют b_transport. Большинство моделей, которые я видел, даже не поддерживают неблокирующий интерфейс.

person random    schedule 22.12.2017
comment
Я нахожу стиль с приблизительной синхронизацией очень громоздким (поскольку он навязывает стиль использования обратных вызовов, а не простой стиль программирования с использованием SC_THREADS). По вашему опыту, использует ли кто-нибудь интерфейс nb_transport, и если нет, то как точно смоделировать конкуренцию за ресурсы с помощью b_transport, если вы хотите оценить производительность? - person sheridp; 04.06.2018
comment
@sheridp По моему опыту, nb_transport на практике не используется. Большинство моделей SystemC рассчитаны на свободное время и используются для ранней разработки программного обеспечения. - person random; 09.06.2018