diff --git a/libs/libvqm/vqm_parser.l b/libs/libvqm/vqm_parser.l index f0e48c23c4a..9f713dfaa29 100644 --- a/libs/libvqm/vqm_parser.l +++ b/libs/libvqm/vqm_parser.l @@ -40,7 +40,7 @@ ^[ \t]*\/\/[^\n\r]*(\n|\r\n) /* skip one-line comments */ ^[ \t]*\(\*[^\n\r]*\*\) /* skip synthesis attributes and directives */ [ \t]+ /* skip white spaces */ -! /* skip the logical operator ! applied on the input ports of the lut - this results in lut mask not being valid anoymore */ +(!|~) /* skip the logical operator ! applied on the input ports of the lut - this results in lut mask not being valid anoymore */ (\n|\r\n) /* skip empty lines */ module return TOKEN_MODULE; endmodule return TOKEN_ENDMODULE; diff --git a/utils/vqm2blif/src/base/vqm2blif_util.cpp b/utils/vqm2blif/src/base/vqm2blif_util.cpp index d2bd322c5a2..fc5c6864beb 100644 --- a/utils/vqm2blif/src/base/vqm2blif_util.cpp +++ b/utils/vqm2blif/src/base/vqm2blif_util.cpp @@ -552,7 +552,7 @@ void generate_opname_ram (t_node* vqm_node, const LogicalModels& arch_models, st reg_outputs = true; } } else { - VTR_ASSERT(ram_info.mode == "dual_port" || ram_info.mode == "bidir_dual_port"); + VTR_ASSERT(ram_info.mode == "dual_port" || ram_info.mode == "bidir_dual_port" || ram_info.mode == "quad_port"); VTR_ASSERT_MSG(ram_info.port_b_input_clock, "RAM inputs always assumed sequential"); if (ram_info.mode == "dual_port" && ram_info.port_b_output_clock) { @@ -568,6 +568,16 @@ void generate_opname_ram (t_node* vqm_node, const LogicalModels& arch_models, st cout << " port A output sequential: " << bool(ram_info.port_a_output_clock) << "\n"; cout << " port B output sequential: " << bool(ram_info.port_b_output_clock) << "\n"; } + } else { + if (ram_info.port_a_output_clock && ram_info.port_b_output_clock) { + reg_outputs = true; //Sequential output + } else if (!ram_info.port_a_output_clock && !ram_info.port_b_output_clock) { + reg_outputs = false; //Comb output + } else { + cout << "Unable to resolve whether quad port RAM " << vqm_node->name << " outputs are sequential or combinational:\n"; + cout << " port A output sequential: " << bool(ram_info.port_a_output_clock) << "\n"; + cout << " port B output sequential: " << bool(ram_info.port_b_output_clock) << "\n"; + } } } @@ -602,6 +612,8 @@ void generate_opname_ram (t_node* vqm_node, const LogicalModels& arch_models, st //&& (port_b_data_width == NULL) && (port_b_addr_width == NULL)) { if(ram_info.mode == "single_port" || ram_info.mode == "rom") { VTR_ASSERT(ram_info.port_b_addr_width == 0); + VTR_ASSERT(ram_info.port_b_addr2_width == 0); + VTR_ASSERT(ram_info.port_a_addr2_width == 0); //Only print the address width, the data widths are handled by the VPR memory class mode_hash.append(".port_a_address_width{" + std::to_string(ram_info.port_a_addr_width) + "}"); @@ -613,8 +625,9 @@ void generate_opname_ram (t_node* vqm_node, const LogicalModels& arch_models, st } //A dual port memory, both port A and B params have been found - } else { - VTR_ASSERT(ram_info.mode == "dual_port" || ram_info.mode == "bidir_dual_port"); + } else if (ram_info.mode == "dual_port" || ram_info.mode == "bidir_dual_port"){ + VTR_ASSERT(ram_info.port_b_addr2_width == 0); + VTR_ASSERT(ram_info.port_a_addr2_width == 0); //2) Both ports are the same size, so only append the address widths, the data widths are handled by the VPR memory class if ( (ram_info.port_a_data_width == ram_info.port_b_data_width) @@ -645,6 +658,53 @@ void generate_opname_ram (t_node* vqm_node, const LogicalModels& arch_models, st tmp_mode_hash.append(".port_a_data_width{" + std::to_string(ram_info.port_a_data_width) + "}"); tmp_mode_hash.append(".port_b_data_width{" + std::to_string(ram_info.port_b_data_width) + "}"); + LogicalModelId arch_model_id = arch_models.get_model_by_name(tmp_mode_hash); + if (!arch_model_id.is_valid()) { + //3a) Not found, use the default name (no specific address/data widths) + ; // do nothing + } else { + //3b) Use the more detailed name, since it was found in the architecture + mode_hash = tmp_mode_hash; + } + } + } else { + VTR_ASSERT(ram_info.mode == "quad_port"); + + //2) Both ports are the same size, so only append the address widths, the data widths are handled by the VPR memory class + if ( (ram_info.port_a_data_width == ram_info.port_b_data_width) + && (ram_info.port_a_addr_width == ram_info.port_b_addr_width) + && (ram_info.port_a_addr_width == ram_info.port_b_addr2_width) + && (ram_info.port_a_addr2_width == ram_info.port_b_addr2_width)) { + + mode_hash.append(".port_a_address_width{" + std::to_string(ram_info.port_a_addr_width) + "}"); + mode_hash.append(".port_a_address2_width{" + std::to_string(ram_info.port_a_addr2_width) + "}"); + mode_hash.append(".port_b_address_width{" + std::to_string(ram_info.port_b_addr_width) + "}"); + mode_hash.append(".port_b_address2_width{" + std::to_string(ram_info.port_b_addr2_width) + "}"); + + LogicalModelId arch_model_id = arch_models.get_model_by_name(mode_hash); + if (!arch_model_id.is_valid()) { + cout << "Error: could not find dual port (non-mixed_width) memory primitive '" << mode_hash << "' in architecture file"; + exit(1); + } + //3) Mixed width dual port ram + } else { + //Make a temporary copy of the mode hash + string tmp_mode_hash = mode_hash; + + /* + * Try to see if the detailed version exists in the architecture, + * if it does, use it. Otherwise use the operation mode only. + */ + + tmp_mode_hash.append(".port_a_address_width{" + std::to_string(ram_info.port_a_addr_width) + "}"); + tmp_mode_hash.append(".port_a_address2_width{" + std::to_string(ram_info.port_a_addr2_width) + "}"); + tmp_mode_hash.append(".port_b_address_width{" + std::to_string(ram_info.port_b_addr_width) + "}"); + tmp_mode_hash.append(".port_b_address2_width{" + std::to_string(ram_info.port_b_addr2_width) + "}"); + + //Each port has a different size, so print both the address and data widths. Mixed widths are not handled by the VPR memory class + tmp_mode_hash.append(".port_a_data_width{" + std::to_string(ram_info.port_a_data_width) + "}"); + tmp_mode_hash.append(".port_b_data_width{" + std::to_string(ram_info.port_b_data_width) + "}"); + LogicalModelId arch_model_id = arch_models.get_model_by_name(tmp_mode_hash); if (!arch_model_id.is_valid()) { //3a) Not found, use the default name (no specific address/data widths) @@ -997,8 +1057,10 @@ RamInfo get_ram_info(const t_node* vqm_node, string device) { //We need to save the ram data and address widths, to identfy the RAM type (singel port, rom, simple dual port, true dual port) t_node_parameter* port_a_data_width = NULL; t_node_parameter* port_a_addr_width = NULL; + t_node_parameter* port_a_addr2_width = NULL; t_node_parameter* port_b_data_width = NULL; t_node_parameter* port_b_addr_width = NULL; + t_node_parameter* port_b_addr2_width = NULL; for (int i = 0; i < vqm_node->number_of_params; i++){ //Each parameter specifies a configuration of the node in the circuit. @@ -1020,6 +1082,11 @@ RamInfo get_ram_info(const t_node* vqm_node, string device) { port_a_addr_width = temp_param; continue; } + if (strcmp (temp_param->name, "port_a_address2_width") == 0){ + VTR_ASSERT( temp_param->type == NODE_PARAMETER_INTEGER ); + port_a_addr2_width = temp_param; + continue; + } if (strcmp (temp_param->name, "port_b_data_width") == 0){ VTR_ASSERT( temp_param->type == NODE_PARAMETER_INTEGER ); port_b_data_width = temp_param; @@ -1030,6 +1097,11 @@ RamInfo get_ram_info(const t_node* vqm_node, string device) { port_b_addr_width = temp_param; continue; } + if (strcmp (temp_param->name, "port_b_address2_width") == 0){ + VTR_ASSERT( temp_param->type == NODE_PARAMETER_INTEGER ); + port_b_addr2_width = temp_param; + continue; + } if (strcmp (temp_param->name, "port_a_address_clock") == 0){ // This parameter doesn't exist for Stratix 10 - clock0 is always used for port address_a VTR_ASSERT( temp_param->type == NODE_PARAMETER_STRING ); port_a_address_clock = temp_param; @@ -1096,6 +1168,12 @@ RamInfo get_ram_info(const t_node* vqm_node, string device) { VTR_ASSERT(port_a_data_width); ram_info.port_a_data_width = port_a_data_width->value.integer_value; + if (port_a_addr2_width) { + ram_info.port_a_addr2_width = port_a_addr2_width->value.integer_value; + } + if (port_b_addr2_width) { + ram_info.port_b_addr2_width = port_b_addr2_width->value.integer_value; + } if (port_b_addr_width) { ram_info.port_b_addr_width = port_b_addr_width->value.integer_value; } diff --git a/utils/vqm2blif/src/base/vqm2blif_util.h b/utils/vqm2blif/src/base/vqm2blif_util.h index 45cc5adedf4..193ab7a46fe 100644 --- a/utils/vqm2blif/src/base/vqm2blif_util.h +++ b/utils/vqm2blif/src/base/vqm2blif_util.h @@ -99,6 +99,7 @@ typedef pair tokpair; struct RamInfo { std::string mode = ""; int port_a_addr_width = 0; + int port_a_addr2_width = 0; int port_a_data_width = 0; t_node_port_association* port_a_input_clock = nullptr; t_node_port_association* port_a_input_ena = nullptr; @@ -108,6 +109,7 @@ struct RamInfo { t_node_port_association* port_a_dataout_sclr = nullptr; int port_b_addr_width = 0; + int port_b_addr2_width = 0; int port_b_data_width = 0; t_node_port_association* port_b_input_clock = nullptr; t_node_port_association* port_b_input_ena = nullptr; diff --git a/vtr_flow/arch/titan/stratix10_arch.timing.xml b/vtr_flow/arch/titan/stratix10_arch.timing.xml index 2d4c021f209..9a2c2e06651 100644 --- a/vtr_flow/arch/titan/stratix10_arch.timing.xml +++ b/vtr_flow/arch/titan/stratix10_arch.timing.xml @@ -4059,8 +4059,6 @@ - @@ -4889,6 +4887,919 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5743,7 +6654,7 @@ - + @@ -5773,8 +6684,8 @@ - - + + @@ -5871,8 +6782,8 @@ - - + + @@ -6000,8 +6911,8 @@ - - + + @@ -6128,8 +7039,8 @@ - - + + @@ -6276,8 +7187,8 @@ - - + + @@ -7985,15 +8896,15 @@ - + - + - + - - - + + + @@ -13516,8 +14427,10 @@ + + @@ -27828,6 +28741,5010 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -29169,6 +35086,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +