Booksim2源码分析1:核心函数调用逻辑 NoC片上网络模拟 含视频教程

哈哈哈,我们就从main函数开始,看看BookSim2都调用了哪些核心函数。

视频教程1.2 BookSim2源码解读1:核心函数调用逻辑_哔哩哔哩_bilibili

一、main() 

我们从main()函数开始,前面都是获取,监控输出啥的,重点关注

         bool result = Simulate( config );

Simulate这个函数,配置并执行仿真器。

源码速递:

源码位置:booksim2-master\src\main.cpp

// 主函数,接受命令行参数  
int main( int argc, char **argv )  

{  

    // 创建一个BookSimConfig类型的对象config,用于存储和配置模拟器的参数  
    BookSimConfig config;  

    // 调用ParseArgs函数,尝试解析命令行参数并填充到config对象中  
    // 如果解析失败(可能是参数格式不正确或缺少必要的参数),则输出使用说明并返回0  
    if ( !ParseArgs( &config, argc, argv ) ) {  
        cerr << "Usage: " << argv[0] << " configfile... [param=value...]" << endl;  
        return 0;  
    }   

    // 初始化路由、流量和注入函数(这些函数的具体实现没有在这段代码中给出)  
    /*initialize routing, traffic, injection functions*/  
    InitializeRoutingMap( config );  

    // 根据config中的"print_activity"参数值来设置gPrintActivity变量的值  
    // 如果"print_activity"大于0,则gPrintActivity为true,否则为false  
    gPrintActivity = (config.GetInt("print_activity") > 0);  

    // 根据config中的"viewer_trace"参数值来设置gTrace变量的值  
    // 如果"viewer_trace"大于0,则gTrace为true,否则为false  
    gTrace = (config.GetInt("viewer_trace") > 0);  

    // 获取config中的"watch_out"参数值,这是一个字符串,可能用于指定一个输出文件的名称  
    string watch_out_file = config.GetStr( "watch_out" );  

    // 根据watch_out_file的值来设置gWatchOut变量的值  
    // 如果watch_out_file为空字符串,则gWatchOut为NULL  
    // 如果watch_out_file为"-",则gWatchOut指向cout(即标准输出)  
    // 否则,创建一个新的ofstream对象,并将gWatchOut指向它,用于将输出写入到指定的文件中  
    if(watch_out_file == "") {  
        gWatchOut = NULL;  
    } else if(watch_out_file == "-") {  
        gWatchOut = &cout;  
    } else {  
        gWatchOut = new ofstream(watch_out_file.c_str());  
    }  


    // 配置并运行模拟器,返回一个bool值表示模拟是否成功  
    /*configure and run the simulator*/  
    bool result = Simulate( config );  

 
    // 如果模拟成功(result为true),则返回-1,否则返回0  
    // 返回值的具体意义可能根据程序的上下文而定,但通常非零值表示某种错误或异常  
    return result ? -1 : 0;  
}

二、Simulate()

核心逻辑:

1、获取配置,初始化Network和TrafficManager

2、仿真 trafficManager->Run() ;

哈哈哈,就这两步,是不是很简单,其它获取程序运行时间,销毁资源先不管。

源码速递:

源码位置:booksim2-master\src\main.cpp

// Simulate函数,用于配置并运行模拟器  
bool Simulate( BookSimConfig const & config )  

{  
  // 创建一个vector,用于存储指向Network对象的指针  
  vector<Network *> net;    

  // 从配置中获取子网的数量  
  int subnets = config.GetInt("subnets");   

  // 注释说明:要添加新的网络,需要在这里注册网络  
  // 如果要添加新的网络类型,可以在下面添加else if语句,并使用相应的网络名称    
  // 根据子网数量调整net vector的大小  
  net.resize(subnets);    

  // 遍历每个子网,并创建对应的Network对象  
  for (int i = 0; i < subnets; ++i) {  
    ostringstream name; // 创建一个输出字符串流,用于构建网络名称  
    name << "network_" << i; // 将"network_"和子网索引添加到输出字符串流中  
    net[i] = Network::New( config, name.str() ); // 调用Network类的静态方法New来创建新的Network对象,并将配置和名称作为参数  
  }  

  // 注释说明:tcc和characterize是遗留的(legacy)代码,不确定如何使用它们    
  // 断言确保trafficManager当前为NULL,以避免内存泄漏或意外覆盖  
  assert(trafficManager == NULL);  

  // 创建TrafficManager对象,并传入配置和net vector作为参数  
  trafficManager = TrafficManager::New( config, net ) ;  

  // 注释说明:开始模拟运行  
  // 定义总运行时间变量  
  double total_time; /* 运行的总时间 */  
  // 定义两个timeval结构体,用于获取开始和结束时间  
  struct timeval start_time, end_time; /* 时间在用户代码执行前/后 */  
  total_time = 0.0; // 初始化总运行时间为0    

  // 获取当前时间作为模拟开始时间  
  gettimeofday(&start_time, NULL);  
  
  // 运行TrafficManager的Run方法,开始模拟  
  bool result = trafficManager->Run() ;  
 
  // 获取模拟结束时间  
  gettimeofday(&end_time, NULL);  

  // 计算总运行时间(秒)  
  total_time = ((double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0)  
            - ((double)(start_time.tv_sec) + (double)(start_time.tv_usec)/1000000.0);  

  
  // 输出总运行时间  
  cout<<"Total run time "<<total_time<<endl;  

  // 遍历每个子网  
  for (int i=0; i<subnets; ++i) {  

    // 如果配置中指定了进行功耗分析(sim_power大于0)  
    /// 功耗分析  
    if(config.GetInt("sim_power") > 0){  
      // 创建Power_Module对象,并传入net[i]和config作为参数  
      Power_Module pnet(net[i], config);  
      // 运行Power_Module的run方法,进行功耗分析  
      pnet.run();  
    }  

    // 删除当前子网的Network对象,释放内存  
    delete net[i];  
  }  

  // 删除TrafficManager对象,释放内存  
  delete trafficManager;  
  trafficManager = NULL; // 将trafficManager设置为NULL,以避免悬挂指针  

  // 返回模拟运行的结果(成功为true,失败为false)  
  return result;  
}

三、TrafficManager::Run

 核心逻辑:

1、清除上次仿真的内容,重置参数

2、_SingleSim()进行本次仿真

以上过程循环多次,仿真多次。

源码速递:

booksim2-master\src\trafficmanager.cpp

// 定义TrafficManager类的Run方法,该方法返回一个布尔值  
bool TrafficManager::Run( )  
{  
    // 开始一个循环,执行_total_sims次模拟  
    for ( int sim = 0; sim < _total_sims; ++sim ) {  
  
        // 重置模拟时间为0  
        _time = 0;  
  
        // 清除任何前一次模拟中待处理的请求  
        // 使用assign方法将_requestsOutstanding数组中的所有元素设置为0  
        _requestsOutstanding.assign(_nodes, 0);  
  
        // 遍历所有节点  
        for (int i=0;i<_nodes;i++) {  
            // 当_repliesPending[i](某个节点的待处理回复队列)不为空时  
            while(!_repliesPending[i].empty()) {  
                // 释放队列中第一个回复所占用的资源(假设Free方法执行此操作)  
                _repliesPending[i].front()->Free();  
                // 从队列中移除已释放的回复  
                _repliesPending[i].pop_front();  
            }  
        }  
  
        // 重置所有源的队列时间  
        for ( int s = 0; s < _nodes; ++s ) {  
            // 使用assign方法将_qtime[s](某个源的队列时间数组)中的所有元素设置为0  
            _qtime[s].assign(_classes, 0);  
            // 将_qdrained[s](某个源是否已排空队列的标记数组)中的所有元素设置为false  
            _qdrained[s].assign(_classes, false);  
        }  
  
        // 模拟的预热阶段开始...  
        // 重置统计信息,所有在warmup_time之后标记的数据包将被视为收敛的  
        // (这里可能还有关于排空的逻辑,但代码没有直接显示)  
        _sim_state    = warming_up;  // 设置模拟状态为预热中  
  
        // 清除统计信息  
        _ClearStats( );  
  
        // 重置所有类别的流量模式和注入过程  
        for(int c = 0; c < _classes; ++c) {  
            _traffic_pattern[c]->reset();  
            _injection_process[c]->reset();  
        }  
  
        // 执行单次模拟  
        if ( !_SingleSim( ) ) {  
            // 如果单次模拟不稳定,输出错误消息并结束整个Run方法  
            cout << "Simulation unstable, ending ..." << endl;  
            return false;  
        }  
  
        // 清空网络中的剩余数据包  
        cout << "Draining remaining packets ..." << endl;  
        _empty_network = true;  // 设置标志为正在清空网络  
        int empty_steps = 0;   // 记录清空步骤的计数器  
  
        bool packets_left = false; // 标记是否还有剩余数据包  
        // 检查每个类别的_total_in_flight_flits队列是否为空  
        for(int c = 0; c < _classes; ++c) {  
            // 如果某个类别的队列不为空,则将packets_left设置为true  
            packets_left |= !_total_in_flight_flits[c].empty();  
        }  
  
        // 当还有剩余数据包时,持续执行  
        while( packets_left ) {   
            // 执行模拟的一步  
            _Step( );   
  
            // 增加清空步骤的计数器  
            ++empty_steps;  
  
            // 每1000步显示一次剩余数据包的信息  
            if ( empty_steps % 1000 == 0 ) {  
                _DisplayRemaining( );   
            }  
  
            // 再次检查是否还有剩余数据包  
            packets_left = false;  
            for(int c = 0; c < _classes; ++c) {  
                // 如果某个类别的队列不为空,则将packets_left设置为true  
                packets_left |= !_total_in_flight_flits[c].empty();  
            }  
        }  
  
        // 等待直到所有的信用都被消耗掉  
        while(Credit::OutStanding()!=0){  
            _Step(); // 继续执行模拟步骤直到没有信用剩余  
        }  
  
        // 清空网络模式结束  
        _empty_network = false;  
  
        // 设置标志为不再清空网络  
        _empty_network = false;  

  
        // 这是一个注释,警告不要在其他地方说"Time taken",可能是因为某个脚本或工具依赖于  
        // 这个特定的输出格式或位置,以确保正确的解析或处理  
        // "for the love of god don't ever say "Time taken" anywhere else"  
        // "为了上帝的爱,千万不要在其他地方说'Time taken'"  
        //the power script depend on it // 这里的脚本依赖于这个输出  

 
        // 输出模拟所花费的时间(以周期为单位)  
        cout << "Time taken is " << _time << " cycles" << endl;   

        // 如果_stats_out是一个有效的输出流(可能是文件流),则写入统计信息  
        if(_stats_out) {  

            WriteStats(*_stats_out);  
        }  

        // 更新全局或总体的统计信息  
        _UpdateOverallStats();  

        // 显示总体统计信息  
        DisplayOverallStats();  
    }

  
    // 如果设置了_print_csv_results标志,则以CSV格式显示总体统计信息  
    if(_print_csv_results) {  
        DisplayOverallStatsCSV();  
    }  

    // 如果所有模拟都成功执行,则返回true  
    return true;  

}

四、TrafficManager::_SingleSim

我们先大概理解三个阶段

warming_up:预热阶段,正式开始信息统计之前,先模拟一段时间,进行模拟预热。

running:运行阶段,模拟并且按周期输出数据统计报告。

draining:排放阶段,运行阶段完成后处理未处理的数据包。

核心逻辑

1、先预热

2、运行并统计信息

3、排放阶段,处理未处理的数据包。

哈哈哈,是不是很简单。其实这三个过程,核心都是在执行_Step()这个函数,只是目的不一样。一个为了预热、一个为了运行并统计信息、一个是处理未处理的数据包。

未什么这个代码看起来很复杂呢,因为它有很多情况要处理。

1、预热阶段

        1a.如果满足运行条件,进入运行阶段。

        1b.如果出现重大异常,结束循环。

        1c.如果模拟的总阶段数大于_max_samples,退出主循环。

2、运行阶段

        2a.如果三次收敛运行(未出现任何异常),进入排放阶段,否则继续运行。

        2b.如果出现重大异常,结束循环。

        2c.如果模拟的总阶段数大于_max_samples,进入排放阶段。

3、排放阶段

源码速递

源码位置:booksim2-master\src\trafficmanager.cpp

// TrafficManager类的_SingleSim方法,用于模拟交通管理系统的单次运行  

bool TrafficManager::_SingleSim() {  

    int converged = 0; // 初始化收敛计数器为0  

    // 创建一个存储上一次延迟的向量,大小为_classes(交通类别的数量)  
    // 并将所有元素初始化为0.0  
    vector<double> prev_latency(_classes, 0.0);  

    // 创建一个存储上一次接受率的向量,大小为_classes,并将所有元素初始化为0.0  
    vector<double> prev_accepted(_classes, 0.0);  

    bool clear_last = false; // 标记是否需要清除最后一次统计数据的布尔值,初始化为false  
    int total_phases = 0; // 初始化模拟的总阶段数为0  

    // 当模拟的总阶段数小于_max_samples且模拟状态不是running或未达到连续3次收敛时,继续循环  
    while( ( total_phases < _max_samples ) &&   
           ( ( _sim_state != running ) ||   
             ( converged < 3 ) ) ) {  

        // 如果需要清除最后一次的统计数据或模拟状态为warming_up且总阶段数为偶数,则清除统计数据  
        if ( clear_last || (( ( _sim_state == warming_up ) && ( ( total_phases % 2 ) == 0 ) )) ) {  
            clear_last = false; // 清除后标记为不再需要清除  
            _ClearStats(); // 调用清除统计数据的方法  
        }  

        // 模拟执行_sample_period次迭代(可能是时间步或模拟事件)  
        for ( int iter = 0; iter < _sample_period; ++iter )  
            _Step(); // 调用模拟的单个步骤方法  
 
        // 更新统计信息  
        UpdateStats();  

        // 显示统计信息  
        DisplayStats();  

        // 初始化一些用于记录变化的变量  
        int lat_exc_class = -1; // 延迟异常的类别,初始化为-1(表示无异常)  
        int lat_chg_exc_class = -1; // 延迟变化异常的类别,初始化为-1  
        int acc_chg_exc_class = -1; // 接受率变化异常的类别,初始化为-1  

        // 遍历每个交通类别  
        for(int c = 0; c < _classes; ++c) {  
            // 如果该类别的度量统计为0,则跳过  
            if(_measure_stats[c] == 0) {  
                continue;  
            }  

            // 计算当前延迟  
            double cur_latency = _plat_stats[c]->Average();  
 
            // 计算总接受率(需要_ComputeStats方法提供total_accepted_count)  
            int total_accepted_count;  
            _ComputeStats( _accepted_flits[c], &total_accepted_count );  
            double total_accepted_rate = (double)total_accepted_count / (double)(_time - _reset_time);  
            double cur_accepted = total_accepted_rate / (double)_nodes;   

            // 计算延迟变化  
            double latency_change = fabs((cur_latency - prev_latency[c]) / cur_latency);  
            prev_latency[c] = cur_latency; // 更新上一次延迟  

              // 计算当前类别的接受率变化  
            double accepted_change = fabs((cur_accepted - prev_accepted[c]) / cur_accepted);  
            // 更新上一次接受率  
            prev_accepted[c] = cur_accepted;  
            
            // 从_plat_stats中获取当前类别的累积延迟  
            double latency = (double)_plat_stats[c]->Sum();  
            // 从_plat_stats中获取当前类别的样本数量  
            double count = (double)_plat_stats[c]->NumSamples();  
            
            // 遍历当前类别中所有在飞行中的Flit  
            map<int, Flit *>::const_iterator iter;  
            for(iter = _total_in_flight_flits[c].begin();   
                iter != _total_in_flight_flits[c].end();   
                iter++) {  
                // 对于每个在飞行中的Flit,将其从创建时间到当前时间的延迟加到latency中  
                latency += (double)(_time - iter->second->ctime);  
                // 增加计数,因为又考虑了一个Flit  
                count++;  
            }  
            
            // 检查当前类别的延迟是否超过阈值  
            if((lat_exc_class < 0) &&  
            (_latency_thres[c] >= 0.0) &&  
            ((latency / count) > _latency_thres[c])) {  
                // 如果超过阈值,则记录该类别为延迟异常的类别  
                lat_exc_class = c;  
            }  
            
            // 输出当前类别的延迟变化  
            cout << "latency change    = " << latency_change << endl;  
            
            // 检查当前类别的延迟变化是否超过阈值  
            if(lat_chg_exc_class < 0) {  
                // 如果模拟状态是warming_up,并且warmup_threshold阈值有效且超过阈值  
                if((_sim_state == warming_up) &&  
                (_warmup_threshold[c] >= 0.0) &&  
                (latency_change > _warmup_threshold[c])) {  
                    // 记录该类别为延迟变化异常的类别  
                    lat_chg_exc_class = c;  
                }  
                // 如果模拟状态是running,并且stopping_threshold阈值有效且超过阈值  
                else if((_sim_state == running) &&  
                        (_stopping_threshold[c] >= 0.0) &&  
                        (latency_change > _stopping_threshold[c])) {  
                    // 记录该类别为延迟变化异常的类别  
                    lat_chg_exc_class = c;  
                }  
            }  
            
            // 输出当前类别的接受率变化  
            cout << "throughput change = " << accepted_change << endl;  
            
            // 检查当前类别的接受率变化是否超过阈值  
            if(acc_chg_exc_class < 0) {  
                // 如果模拟状态是warming_up,并且acc_warmup_threshold阈值有效且超过阈值  
                if((_sim_state == warming_up) &&  
                (_acc_warmup_threshold[c] >= 0.0) &&  
                (accepted_change > _acc_warmup_threshold[c])) {  
                    // 记录该类别为接受率变化异常的类别  
                    acc_chg_exc_class = c;  
                }  
                // 如果模拟状态是running,并且acc_stopping_threshold阈值有效且超过阈值  
                else if((_sim_state == running) &&  
                        (_acc_stopping_threshold[c] >= 0.0) &&  
                        (accepted_change > _acc_stopping_threshold[c])) {  
                    // 记录该类别为接受率变化异常的类别  
                    acc_chg_exc_class = c;  
                }  
            }
       
            // 如果正在测量延迟,并且延迟异常类别(lat_exc_class)是非负的(表示有延迟异常)  
            if ( _measure_latency && ( lat_exc_class >= 0 ) ) {  
                // 打印出哪个类别的平均延迟超过了设定的阈值,并宣布终止模拟  

                cout << "Average latency for class " << lat_exc_class << " exceeded " << _latency_thres[lat_exc_class] << " cycles. Aborting simulation." << endl;  

                // 将收敛标志设置为0,表示模拟没有收敛  
                converged = 0;   

                // 将模拟状态设置为draining,意味着模拟开始排空阶段  
                _sim_state = draining;  

                // 设置_drain_time为当前时间_time,可能是为了记录模拟排空开始的时间  
                _drain_time = _time;  

                // 如果启用了统计输出(_stats_out非空),则写入统计信息到_stats_out  
                if(_stats_out) {  

                    WriteStats(*_stats_out);  

                }  

                // 跳出当前循环(可能是_SingleSim函数的主体循环)  
                break;  
            }  
            
            // 如果模拟状态是warming_up(预热阶段)  
            if ( _sim_state == warming_up ) {  
                // 如果设置了预热周期(_warmup_periods大于0),则根据总阶段数(total_phases)判断是否达到预热周期  
                // 否则,如果不测量延迟或延迟变化没有异常,并且接受率变化也没有异常,则也判断为预热完成  
                if ( ( _warmup_periods > 0 ) ?   
                    ( total_phases + 1 >= _warmup_periods ) :  
                    ( ( !_measure_latency || ( lat_chg_exc_class < 0 ) ) &&  
                    ( acc_chg_exc_class < 0 ) ) ) {  
                    // 打印出预热完成的消息,并显示使用了多少周期  
                    cout << "Warmed up ..." << "Time used is " << _time << " cycles" << endl;  
                    // 可能是为了清空某些上一阶段的状态或数据  
                    clear_last = true;  
                    // 将模拟状态设置为running,表示预热阶段结束,进入运行阶段  
                    _sim_state = running;  
                }  
            }   

            // 如果模拟状态是running(运行阶段)  
            else if(_sim_state == running) {  
                // 如果不测量延迟或延迟变化没有异常,并且接受率变化也没有异常,则增加收敛计数器  
                if ( ( !_measure_latency || ( lat_chg_exc_class < 0 ) ) &&  
                    ( acc_chg_exc_class < 0 ) ) {  
                    ++converged;  
                }   
                // 如果有任何异常(延迟或接受率),则重置收敛计数器  
                else {  
                    converged = 0;  
                }  
            }  
            // 无论模拟处于哪个阶段,都增加总阶段数  
            ++total_phases;
    }


    // 检查当前模拟状态是否为running  
    if ( _sim_state == running ) {  

        // 如果模拟是running状态,则converged计数器加1  
        ++converged;  

        // 将模拟状态更改为draining  
        _sim_state  = draining;  
        // 记录draining状态开始的时间为当前时间_time  
        _drain_time = _time;  

        // 如果_measure_latency为真(即需要测量延迟)  
        if ( _measure_latency ) {  
            // 在控制台上打印消息,表明正在清空所有已记录的数据包  
            cout << "Draining all recorded packets ..." << endl;  

            // 初始化一个计数器empty_steps,用于跟踪清空数据包的步骤数  
            int empty_steps = 0;  

            // 当还有未处理的数据包时,继续循环  
            while( _PacketsOutstanding( ) ) {   
                // 执行模拟的一个步骤  
                _Step( );   
                // 每执行一个步骤,empty_steps计数器加1  
                ++empty_steps;  

                // 如果empty_steps是1000的倍数(即每1000个步骤检查一次)  
                if ( empty_steps % 1000 == 0 ) {  

                    // 初始化一个变量lat_exc_class,用于存储超过延迟阈值的类索引  
                    int lat_exc_class = -1;  

                    // 遍历所有类  
                    for(int c = 0; c < _classes; c++) {  

                        // 获取当前类的延迟阈值  
                        double threshold = _latency_thres[c];  

                        // 如果阈值为负,跳过当前类  
                        if(threshold < 0.0) {  
                            continue;  
                        }  

                        // 计算当前类的累积延迟  
                        double acc_latency = _plat_stats[c]->Sum();  

                        // 计算当前类的样本数量  
                        double acc_count = (double)_plat_stats[c]->NumSamples();  

                        // 遍历当前类所有在飞行中的Flit(可能是数据包片段)  
                        map<int, Flit *>::const_iterator iter;  
                        for(iter = _total_in_flight_flits[c].begin();   
                            iter != _total_in_flight_flits[c].end();   
                            iter++) {  
                            // 将每个Flit的延迟加到累积延迟中  
                            acc_latency += (double)(_time - iter->second->ctime);  
                            // 样本数量加1  
                            acc_count++;  
                        }  

                        // 如果当前类的平均延迟超过了阈值  
                        if((acc_latency / acc_count) > threshold) {  
                            // 将lat_exc_class设置为当前类的索引  
                            lat_exc_class = c;  
                            // 跳出循环,不再检查其他类  
                            break;  
                        }  
                    }  

    
                    // 如果lat_exc_class非负,说明有类的延迟超过了阈值  
                    if(lat_exc_class >= 0) {  
                        // 在控制台上输出消息,表明指定类的平均延迟超过了阈值,并决定中止模拟  
                        cout << "Average latency for class " << lat_exc_class << " exceeded " << _latency_thres[lat_exc_class] << " cycles. Aborting simulation." << endl;  

                        // 重置converged为0  
                        converged = 0;   
                        // 将模拟状态重置为warming_up  
                        _sim_state = warming_up;  
                        // 如果_stats_out指向一个有效的输出流,则写入统计信息  
                        if(_stats_out) {  
                            WriteStats(*_stats_out);  
                        }  
                        // 跳出循环,不再继续清空数据包  
                        break;  
                    }  
    
                    // 注意:这里原本的代码被截断了,但似乎应该有一个_DisplayRemaining()的调用,用于显示剩余的某些内容或数据  
                    // _DisplayRemaining( );  
                }  
            }  
        }  
    }

 下期预告:

_Step()仿真一步(可能有)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值