| #include <cstring> | |
| #include <vector> | |
| using namespace std; | |
| #include "vtr_assert.h" | |
| #include "vtr_util.h" | |
| #include "vtr_random.h" | |
| #include "vtr_log.h" | |
| #include "vtr_memory.h" | |
| #include "vpr_types.h" | |
| #include "vpr_error.h" | |
| #include "vpr_utils.h" | |
| #include "globals.h" | |
| #include "read_xml_arch_file.h" | |
| #include "SetupVPR.h" | |
| #include "pb_type_graph.h" | |
| #include "pack_types.h" | |
| #include "lb_type_rr_graph.h" | |
| #include "rr_graph_area.h" | |
| #include "echo_arch.h" | |
| #include "read_options.h" | |
| #include "echo_files.h" | |
| static void SetupNetlistOpts(const t_options& Options, t_netlist_opts& NetlistOpts); | |
| static void SetupPackerOpts(const t_options& Options, | |
| t_packer_opts *PackerOpts); | |
| static void SetupPlacerOpts(const t_options& Options, | |
| t_placer_opts *PlacerOpts); | |
| static void SetupAnnealSched(const t_options& Options, | |
| t_annealing_sched *AnnealSched); | |
| static void SetupRouterOpts(const t_options& Options, t_router_opts *RouterOpts); | |
| static void SetupRoutingArch(const t_arch& Arch, t_det_routing_arch *RoutingArch); | |
| static void SetupTiming(const t_options& Options, const t_arch& Arch, | |
| const bool TimingEnabled, | |
| t_timing_inf * Timing); | |
| static void SetupSwitches(const t_arch& Arch, | |
| t_det_routing_arch *RoutingArch, | |
| const t_arch_switch_inf *ArchSwitches, int NumArchSwitches); | |
| static void SetupAnalysisOpts(const t_options& Options, t_analysis_opts& analysis_opts); | |
| static void SetupPowerOpts(const t_options& Options, t_power_opts *power_opts, | |
| t_arch * Arch); | |
| static int find_ipin_cblock_switch_index(const t_arch& Arch); | |
| /* Sets VPR parameters and defaults. Does not do any error checking | |
| * as this should have been done by the various input checkers */ | |
| void SetupVPR(t_options *Options, | |
| const bool TimingEnabled, | |
| const bool readArchFile, | |
| t_file_name_opts *FileNameOpts, | |
| t_arch * Arch, | |
| t_model ** user_models, | |
| t_model ** library_models, | |
| t_netlist_opts* NetlistOpts, | |
| t_packer_opts *PackerOpts, | |
| t_placer_opts *PlacerOpts, | |
| t_annealing_sched *AnnealSched, | |
| t_router_opts *RouterOpts, | |
| t_analysis_opts* AnalysisOpts, | |
| t_det_routing_arch *RoutingArch, | |
| vector <t_lb_type_rr_node> **PackerRRGraphs, | |
| t_segment_inf ** Segments, t_timing_inf * Timing, | |
| bool * ShowGraphics, int *GraphPause, | |
| t_power_opts * PowerOpts) { | |
| int i; | |
| using argparse::Provenance; | |
| auto& device_ctx = g_vpr_ctx.mutable_device(); | |
| if (Options->CircuitName.value() == "") { | |
| vpr_throw(VPR_ERROR_BLIF_F,__FILE__, __LINE__, | |
| "No blif file found in arguments (did you specify an architecture file?)\n"); | |
| } | |
| alloc_and_load_output_file_names(Options->CircuitName); | |
| //TODO: Move FileNameOpts setup into separate function | |
| FileNameOpts->CircuitName = Options->CircuitName; | |
| FileNameOpts->ArchFile = Options->ArchFile; | |
| FileNameOpts->BlifFile = Options->BlifFile; | |
| FileNameOpts->NetFile = Options->NetFile; | |
| FileNameOpts->PlaceFile = Options->PlaceFile; | |
| FileNameOpts->RouteFile = Options->RouteFile; | |
| FileNameOpts->ActFile = Options->ActFile; | |
| FileNameOpts->PowerFile = Options->PowerFile; | |
| FileNameOpts->CmosTechFile = Options->CmosTechFile; | |
| FileNameOpts->out_file_prefix = Options->out_file_prefix; | |
| FileNameOpts->verify_file_digests = Options->verify_file_digests; | |
| SetupNetlistOpts(*Options, *NetlistOpts); | |
| SetupPlacerOpts(*Options, PlacerOpts); | |
| SetupAnnealSched(*Options, AnnealSched); | |
| SetupRouterOpts(*Options, RouterOpts); | |
| SetupAnalysisOpts(*Options, *AnalysisOpts); | |
| SetupPowerOpts(*Options, PowerOpts, Arch); | |
| if (readArchFile == true) { | |
| XmlReadArch(Options->ArchFile.value().c_str(), TimingEnabled, Arch, &device_ctx.block_types, | |
| &device_ctx.num_block_types); | |
| } | |
| *user_models = Arch->models; | |
| *library_models = Arch->model_library; | |
| /* TODO: this is inelegant, I should be populating this information in XmlReadArch */ | |
| device_ctx.EMPTY_TYPE = nullptr; | |
| for (i = 0; i < device_ctx.num_block_types; i++) { | |
| t_type_ptr type = &device_ctx.block_types[i]; | |
| if (strcmp(device_ctx.block_types[i].name, EMPTY_BLOCK_NAME) == 0) { | |
| VTR_ASSERT(device_ctx.EMPTY_TYPE == nullptr); | |
| device_ctx.EMPTY_TYPE = type; | |
| } else { | |
| if (block_type_contains_blif_model(type, MODEL_INPUT)) { | |
| device_ctx.input_types.insert(type); | |
| } | |
| if (block_type_contains_blif_model(type, MODEL_OUTPUT)) { | |
| device_ctx.output_types.insert(type); | |
| } | |
| } | |
| } | |
| VTR_ASSERT(device_ctx.EMPTY_TYPE != nullptr); | |
| if (device_ctx.input_types.empty()) { | |
| VPR_THROW(VPR_ERROR_ARCH, | |
| "Architecture contains no top-level block type containing '.input' models"); | |
| } | |
| if (device_ctx.output_types.empty()) { | |
| VPR_THROW(VPR_ERROR_ARCH, | |
| "Architecture contains no top-level block type containing '.output' models"); | |
| } | |
| *Segments = Arch->Segments; | |
| RoutingArch->num_segment = Arch->num_segments; | |
| SetupSwitches(*Arch, RoutingArch, Arch->Switches, Arch->num_switches); | |
| SetupRoutingArch(*Arch, RoutingArch); | |
| SetupTiming(*Options, *Arch, TimingEnabled, Timing); | |
| SetupPackerOpts(*Options, PackerOpts); | |
| RoutingArch->write_rr_graph_filename = Options->write_rr_graph_file; | |
| RoutingArch->read_rr_graph_filename = Options->read_rr_graph_file; | |
| //Setup the default flow, if no specific stages specified | |
| //do all | |
| if ( !Options->do_packing | |
| && !Options->do_placement | |
| && !Options->do_routing | |
| && !Options->do_analysis) { | |
| //run all stages if none specified | |
| PackerOpts->doPacking = STAGE_DO; | |
| PlacerOpts->doPlacement = STAGE_DO; | |
| RouterOpts->doRouting = STAGE_DO; | |
| AnalysisOpts->doAnalysis = STAGE_DO; | |
| } else { | |
| //We run all stages up to the specified stage | |
| //Note that by checking in reverse order (i.e. analysis to packing) | |
| //we ensure that earlier stages override the default 'LOAD' action | |
| //set by later stages | |
| if(Options->do_analysis) { | |
| PackerOpts->doPacking = STAGE_LOAD; | |
| PlacerOpts->doPlacement = STAGE_LOAD; | |
| RouterOpts->doRouting = STAGE_LOAD; | |
| AnalysisOpts->doAnalysis = STAGE_DO; | |
| } | |
| if(Options->do_routing) { | |
| PackerOpts->doPacking = STAGE_LOAD; | |
| PlacerOpts->doPlacement = STAGE_LOAD; | |
| RouterOpts->doRouting = STAGE_DO; | |
| AnalysisOpts->doAnalysis = STAGE_DO; //Always run analysis after routing | |
| } | |
| if(Options->do_placement) { | |
| PackerOpts->doPacking = STAGE_LOAD; | |
| PlacerOpts->doPlacement = STAGE_DO; | |
| } | |
| if(Options->do_packing) { | |
| PackerOpts->doPacking = STAGE_DO; | |
| } | |
| } | |
| /* init global variables */ | |
| vtr::out_file_prefix = Options->out_file_prefix; | |
| /* Set seed for pseudo-random placement, default seed to 1 */ | |
| PlacerOpts->seed = Options->Seed; | |
| vtr::srandom(PlacerOpts->seed); | |
| vtr::printf_info("Building complex block graph.\n"); | |
| alloc_and_load_all_pb_graphs(PowerOpts->do_power); | |
| *PackerRRGraphs = alloc_and_load_all_lb_type_rr_graph(); | |
| if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_LB_TYPE_RR_GRAPH)) { | |
| echo_lb_type_rr_graphs(getEchoFileName(E_ECHO_LB_TYPE_RR_GRAPH),*PackerRRGraphs); | |
| } | |
| if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_PB_GRAPH)) { | |
| echo_pb_graph(getEchoFileName(E_ECHO_PB_GRAPH)); | |
| } | |
| *GraphPause = Options->GraphPause; | |
| *ShowGraphics = Options->show_graphics; | |
| if (getEchoEnabled() && isEchoFileEnabled(E_ECHO_ARCH)) { | |
| EchoArch(getEchoFileName(E_ECHO_ARCH), device_ctx.block_types, device_ctx.num_block_types, | |
| Arch); | |
| } | |
| } | |
| static void SetupTiming(const t_options& Options, const t_arch& Arch, | |
| const bool TimingEnabled, | |
| t_timing_inf * Timing) { | |
| /* Don't do anything if they don't want timing */ | |
| if (false == TimingEnabled) { | |
| Timing->timing_analysis_enabled = false; | |
| return; | |
| } | |
| int ipin_cblock_switch_index = find_ipin_cblock_switch_index(Arch); | |
| Timing->C_ipin_cblock = Arch.Switches[ipin_cblock_switch_index].Cin; | |
| Timing->T_ipin_cblock = Arch.Switches[ipin_cblock_switch_index].Tdel(); | |
| Timing->timing_analysis_enabled = TimingEnabled; | |
| Timing->SDCFile = Options.SDCFile; | |
| Timing->slack_definition = Options.SlackDefinition; | |
| #ifdef ENABLE_CLASSIC_VPR_STA | |
| VTR_ASSERT(Timing->slack_definition == std::string("R") || Timing->slack_definition == std::string("I") || | |
| Timing->slack_definition == std::string("S") || Timing->slack_definition == std::string("G") || | |
| Timing->slack_definition == std::string("C") || Timing->slack_definition == std::string("N")); | |
| #endif | |
| } | |
| /* This loads up VPR's arch_switch_inf data by combining the switches from | |
| * the arch file with the special switches that VPR needs. */ | |
| static void SetupSwitches(const t_arch& Arch, | |
| t_det_routing_arch *RoutingArch, | |
| const t_arch_switch_inf *ArchSwitches, int NumArchSwitches) { | |
| auto& device_ctx = g_vpr_ctx.mutable_device(); | |
| int switches_to_copy = NumArchSwitches; | |
| device_ctx.num_arch_switches = NumArchSwitches; | |
| RoutingArch->wire_to_arch_ipin_switch = find_ipin_cblock_switch_index(Arch); | |
| /* Depends on device_ctx.num_arch_switches */ | |
| RoutingArch->delayless_switch = device_ctx.num_arch_switches++; | |
| /* Alloc the list now that we know the final num_arch_switches value */ | |
| device_ctx.arch_switch_inf = new t_arch_switch_inf[device_ctx.num_arch_switches]; | |
| for (int iswitch = 0; iswitch < switches_to_copy; iswitch++){ | |
| device_ctx.arch_switch_inf[iswitch] = ArchSwitches[iswitch]; | |
| } | |
| /* Delayless switch for connecting sinks and sources with their pins. */ | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].set_type(SwitchType::MUX); | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].name = vtr::strdup("__vpr_delayless_switch__"); | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].R = 0.; | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].Cin = 0.; | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].Cout = 0.; | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].set_Tdel(t_arch_switch_inf::UNDEFINED_FANIN, 0.); | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].power_buffer_type = POWER_BUFFER_TYPE_NONE; | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].mux_trans_size = 0.; | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].buf_size_type = BufferSize::ABSOLUTE; | |
| device_ctx.arch_switch_inf[RoutingArch->delayless_switch].buf_size = 0.; | |
| VTR_ASSERT_MSG(device_ctx.arch_switch_inf[RoutingArch->delayless_switch].buffered(), "Delayless switch expected to be buffered (isolating)"); | |
| VTR_ASSERT_MSG(device_ctx.arch_switch_inf[RoutingArch->delayless_switch].configurable(), "Delayless switch expected to be configurable"); | |
| RoutingArch->global_route_switch = RoutingArch->delayless_switch; | |
| //Warn about non-zero Cout values for the ipin switch, since these values have no effect. | |
| //VPR do not model the R/C's of block internal routing connectsion. | |
| // | |
| //Note that we don't warn about the R value as it may be used to size the buffer (if buf_size_type is AUTO) | |
| if (device_ctx.arch_switch_inf[RoutingArch->wire_to_arch_ipin_switch].Cout != 0.) { | |
| vtr::printf_warning(__FILE__, __LINE__, "Non-zero switch output capacitance (%g) has no effect when switch '%s' is used for connection block inputs\n", | |
| device_ctx.arch_switch_inf[RoutingArch->wire_to_arch_ipin_switch].Cout, Arch.ipin_cblock_switch_name.c_str()); | |
| } | |
| } | |
| /* Sets up routing structures. Since checks are already done, this | |
| * just copies values across */ | |
| static void SetupRoutingArch(const t_arch& Arch, | |
| t_det_routing_arch *RoutingArch) { | |
| RoutingArch->switch_block_type = Arch.SBType; | |
| RoutingArch->R_minW_nmos = Arch.R_minW_nmos; | |
| RoutingArch->R_minW_pmos = Arch.R_minW_pmos; | |
| RoutingArch->Fs = Arch.Fs; | |
| RoutingArch->directionality = BI_DIRECTIONAL; | |
| if (Arch.Segments){ | |
| RoutingArch->directionality = Arch.Segments[0].directionality; | |
| } | |
| /* copy over the switch block information */ | |
| RoutingArch->switchblocks = Arch.switchblocks; | |
| } | |
| static void SetupRouterOpts(const t_options& Options, t_router_opts *RouterOpts) { | |
| RouterOpts->astar_fac = Options.astar_fac; | |
| RouterOpts->bb_factor = Options.bb_factor; | |
| RouterOpts->criticality_exp = Options.criticality_exp; | |
| RouterOpts->max_criticality = Options.max_criticality; | |
| RouterOpts->max_router_iterations = Options.max_router_iterations; | |
| RouterOpts->min_incremental_reroute_fanout = Options.min_incremental_reroute_fanout; | |
| RouterOpts->pres_fac_mult = Options.pres_fac_mult; | |
| RouterOpts->route_type = Options.RouteType; | |
| RouterOpts->full_stats = Options.full_stats; | |
| //TODO document these? | |
| RouterOpts->congestion_analysis = Options.full_stats; | |
| RouterOpts->fanout_analysis = Options.full_stats; | |
| RouterOpts->switch_usage_analysis = Options.full_stats; | |
| RouterOpts->verify_binary_search = Options.verify_binary_search; | |
| RouterOpts->router_algorithm = Options.RouterAlgorithm; | |
| RouterOpts->fixed_channel_width = Options.RouteChanWidth; | |
| RouterOpts->min_channel_width_hint = Options.min_route_chan_width_hint; | |
| //TODO document these? | |
| RouterOpts->trim_empty_channels = false; /* DEFAULT */ | |
| RouterOpts->trim_obs_channels = false; /* DEFAULT */ | |
| RouterOpts->initial_pres_fac = Options.initial_pres_fac; | |
| RouterOpts->base_cost_type = Options.base_cost_type; | |
| RouterOpts->first_iter_pres_fac = Options.first_iter_pres_fac; | |
| RouterOpts->acc_fac = Options.acc_fac; | |
| RouterOpts->bend_cost = Options.bend_cost; | |
| if (Options.do_routing) { | |
| RouterOpts->doRouting = STAGE_DO; | |
| } | |
| RouterOpts->routing_failure_predictor = Options.routing_failure_predictor; | |
| RouterOpts->routing_budgets_algorithm = Options.routing_budgets_algorithm; | |
| } | |
| static void SetupAnnealSched(const t_options& Options, | |
| t_annealing_sched *AnnealSched) { | |
| AnnealSched->alpha_t = Options.PlaceAlphaT; | |
| if (AnnealSched->alpha_t >= 1 || AnnealSched->alpha_t <= 0) { | |
| vpr_throw(VPR_ERROR_OTHER,__FILE__, __LINE__, "alpha_t must be between 0 and 1 exclusive.\n"); | |
| } | |
| AnnealSched->exit_t = Options.PlaceExitT; | |
| if (AnnealSched->exit_t <= 0) { | |
| vpr_throw(VPR_ERROR_OTHER,__FILE__, __LINE__, "exit_t must be greater than 0.\n"); | |
| } | |
| AnnealSched->init_t = Options.PlaceInitT; | |
| if (AnnealSched->init_t <= 0) { | |
| vpr_throw(VPR_ERROR_OTHER,__FILE__, __LINE__, "init_t must be greater than 0.\n"); | |
| } | |
| if (AnnealSched->init_t < AnnealSched->exit_t) { | |
| vpr_throw(VPR_ERROR_OTHER,__FILE__, __LINE__, "init_t must be greater or equal to than exit_t.\n"); | |
| } | |
| AnnealSched->inner_num = Options.PlaceInnerNum; | |
| if (AnnealSched->inner_num <= 0) { | |
| vpr_throw(VPR_ERROR_OTHER,__FILE__, __LINE__, "inner_num must be greater than 0.\n"); | |
| } | |
| AnnealSched->type = Options.anneal_sched_type; | |
| } | |
| /* Sets up the s_packer_opts structure baesd on users inputs and on the architecture specified. | |
| * Error checking, such as checking for conflicting params is assumed to be done beforehand | |
| */ | |
| void SetupPackerOpts(const t_options& Options, | |
| t_packer_opts *PackerOpts) { | |
| PackerOpts->output_file = Options.NetFile; | |
| PackerOpts->blif_file_name = Options.BlifFile; | |
| if (Options.do_packing) { | |
| PackerOpts->doPacking = STAGE_DO; | |
| } | |
| //TODO: document? | |
| PackerOpts->global_clocks = true; /* DEFAULT */ | |
| PackerOpts->hill_climbing_flag = false; /* DEFAULT */ | |
| PackerOpts->allow_unrelated_clustering = Options.allow_unrelated_clustering; | |
| PackerOpts->connection_driven = Options.connection_driven_clustering; | |
| PackerOpts->timing_driven = Options.timing_driven_clustering; | |
| PackerOpts->cluster_seed_type = Options.cluster_seed_type; | |
| PackerOpts->alpha = Options.alpha_clustering; | |
| PackerOpts->beta = Options.beta_clustering; | |
| PackerOpts->debug_clustering = Options.debug_clustering; | |
| PackerOpts->enable_pin_feasibility_filter = Options.enable_clustering_pin_feasibility_filter; | |
| PackerOpts->target_device_utilization = Options.target_device_utilization; | |
| //TODO: document? | |
| PackerOpts->inter_cluster_net_delay = 1.0; /* DEFAULT */ | |
| PackerOpts->auto_compute_inter_cluster_net_delay = true; | |
| PackerOpts->packer_algorithm = PACK_GREEDY; /* DEFAULT */ | |
| PackerOpts->device_layout = Options.device_layout; | |
| PackerOpts->hmetis_input_file = Options.hmetis_input_file; | |
| } | |
| static void SetupNetlistOpts(const t_options& Options, t_netlist_opts& NetlistOpts) { | |
| NetlistOpts.absorb_buffer_luts = Options.absorb_buffer_luts; | |
| NetlistOpts.sweep_dangling_primary_ios = Options.sweep_dangling_primary_ios; | |
| NetlistOpts.sweep_dangling_nets = Options.sweep_dangling_nets; | |
| NetlistOpts.sweep_dangling_blocks = Options.sweep_dangling_blocks; | |
| NetlistOpts.sweep_constant_primary_outputs = Options.sweep_constant_primary_outputs; | |
| } | |
| /* Sets up the s_placer_opts structure based on users input. Error checking, | |
| * such as checking for conflicting params is assumed to be done beforehand */ | |
| static void SetupPlacerOpts(const t_options& Options, t_placer_opts *PlacerOpts) { | |
| if (Options.do_placement) { | |
| PlacerOpts->doPlacement = STAGE_DO; | |
| } | |
| PlacerOpts->inner_loop_recompute_divider = Options.inner_loop_recompute_divider; | |
| //TODO: document? | |
| PlacerOpts->place_cost_exp = 1; | |
| PlacerOpts->td_place_exp_first = Options.place_exp_first; | |
| PlacerOpts->td_place_exp_last = Options.place_exp_last; | |
| PlacerOpts->place_algorithm = Options.PlaceAlgorithm; | |
| PlacerOpts->pad_loc_file = Options.pad_loc_file; | |
| PlacerOpts->pad_loc_type = Options.pad_loc_type; | |
| PlacerOpts->place_chan_width = Options.PlaceChanWidth; | |
| PlacerOpts->recompute_crit_iter = Options.RecomputeCritIter; | |
| PlacerOpts->timing_tradeoff = Options.PlaceTimingTradeoff; | |
| /* Depends on PlacerOpts->place_algorithm */ | |
| PlacerOpts->enable_timing_computations = Options.ShowPlaceTiming; | |
| //TODO: document? | |
| PlacerOpts->place_freq = PLACE_ONCE; /* DEFAULT */ | |
| } | |
| static void SetupAnalysisOpts(const t_options& Options, t_analysis_opts& analysis_opts) { | |
| if (Options.do_analysis) { | |
| analysis_opts.doAnalysis = STAGE_DO; | |
| } | |
| analysis_opts.gen_post_synthesis_netlist = Options.Generate_Post_Synthesis_Netlist; | |
| } | |
| static void SetupPowerOpts(const t_options& Options, t_power_opts *power_opts, | |
| t_arch * Arch) { | |
| auto& device_ctx = g_vpr_ctx.mutable_device(); | |
| power_opts->do_power = Options.do_power; | |
| if (power_opts->do_power) { | |
| if (!Arch->power) | |
| Arch->power = (t_power_arch*) vtr::malloc(sizeof(t_power_arch)); | |
| if (!Arch->clocks) | |
| Arch->clocks = (t_clock_arch*) vtr::malloc(sizeof(t_clock_arch)); | |
| device_ctx.clock_arch = Arch->clocks; | |
| } else { | |
| Arch->power = nullptr; | |
| Arch->clocks = nullptr; | |
| device_ctx.clock_arch = nullptr; | |
| } | |
| } | |
| static int find_ipin_cblock_switch_index(const t_arch& Arch) { | |
| int ipin_cblock_switch_index = UNDEFINED; | |
| for (int i = 0; i < Arch.num_switches; ++i) { | |
| if (Arch.Switches[i].name == Arch.ipin_cblock_switch_name) { | |
| if (ipin_cblock_switch_index != UNDEFINED) { | |
| VPR_THROW(VPR_ERROR_ARCH, "Found duplicate switches named '%s'\n", Arch.ipin_cblock_switch_name.c_str()); | |
| } else { | |
| ipin_cblock_switch_index = i; | |
| } | |
| } | |
| } | |
| if (ipin_cblock_switch_index == UNDEFINED) { | |
| VPR_THROW(VPR_ERROR_ARCH, "Failed to find connection block input pin switch named '%s'\n", Arch.ipin_cblock_switch_name.c_str()); | |
| } | |
| return ipin_cblock_switch_index; | |
| } | |