FPGA HLS 卷积单元 数据类型hls优化约束设置
创始人
2024-01-20 05:39:56

数据类型

自定义精度整形:

ap_int<4> in1, in2;
ap_int<8> concat;
concat = (in1, in2);	// in1和in2拼起来(按照补码拼起来)
/*
例子:
in1 = 1, in2 = -1
补码:
in1 =  0001
in2 =  1001 ==> 1110+1 ==> 1111
concat = 00011111 = 31
*/
concat.xor_reduce();	// 按位异或

image-20221104195907274

自定义定点数

为了替换float,double类型的数,加快运算,节约资源

ap_fixed<11, 6> Var1 = 22.96875; // 一共11个bit,其中6个bit表示整数,5个bit表示小数;剩一个bit表示正负数
ap_ufixed<12, 11> Var2 = 512.5;	// 一共12个bit,11位表示整数,最后一位表示小数

image-20221104200355155

卷积的量化或定点化

根据输入的数据,找到卷积层的数据范围

A= aaaaaaaaaaaaaaaa, fix_point=12
B= bbbbbbbbbbbbbbbb, fix_point=13
C= ????????????????, fix_point=13????????????????*(2^13)=A*B/2^(12+13)求C的编码:????????????????
= A*B/2^(12+13-13)
= A*B/2^(fix_pointA + fix_pointB – fix_pointC)例子:
A:0010.1100 = 44/16 = 2+0.5+0.25 = 2.75 fix_point = 4
B:00101.100 = 44/8 = 5+0.5 = 5.5 fix_point = 3
C:????.???? Fix_point = 4
A*B =44*44 = 011110010000 == 右移(3+4) = 01111.0010000 = 15.125
一共有(3+4)位小数,但是C的精度是4,所以需要把多余的小数移出去
移出去的位数就是(3+4-4) = 3
所以C = 1111.0010 fix_point = 4

自定义卷积

特征的内存排布方式

image-20221104213524620

权重的内存排布方式

image-20221104213622677

卷积的大小不固定,需要根据在内存中的排布方式算出地址

新建conv_core项目

conv_core.h

#ifndef __CONV_CORE_H__
#define __CONV_CORE_H__#include 
#include 
using namespace std;#define K 8typedef ap_int<16> data_type;	// 单个数据的大小
typedef ap_int<16*K> tile_type;	// 分块数据的大小typedef ap_int<32> mul_type;	// 两个数据相乘的数据大小:16*16==>32
typedef ap_int<32*K> mul_tile_type; // 分块数据相乘的大小typedef ap_int<40> acc_type;  // 一次卷积内的数据相加后的大小,按照经验推断// 卷积的定义
void conv_core(ap_uint<16> in_channel, // 输入特征的通道数ap_uint<16> in_height, // 输入特征高度ap_uint<16> in_width, // 输入特征宽度ap_uint<16> out_channel, // 输出特征通道数// 卷积核参数ap_uint<8> kernel_width, // 卷积核宽度ap_uint<8> kernel_height, // 卷积核高度ap_uint<8> stride_x, // 宽度方向步长ap_uint<8> stride_y, // 高度方向步长ap_uint<1> padding_mode, // 卷积的模式; 0: valid(没有padding填充), 1:same(输入输出的图大小不变)ap_uint<1> relu_en, // 激活函数tile_type input_feature[], ap_uint<4> input_feature_precision,	// 输入特征图地址和精度(小数点位置)tile_type weight[], ap_uint<4> weight_precision,// 权重地址和精度(小数点位置)tile_type output_feature[], ap_uint<4> out_feature_precision// 输出特征图地址和精度(小数点位置)
);#endif

conv_core.cpp

#include "conv_core.h"void conv_core(// 特征图参数ap_uint<16> in_channel, // 输入特征的通道数ap_uint<16> in_height, // 输入特征高度ap_uint<16> in_width, // 输入特征宽度ap_uint<16> out_channel, // 输出特征通道数// 卷积核参数ap_uint<8> kernel_width, // 卷积核宽度ap_uint<8> kernel_height, // 卷积核高度ap_uint<8> stride_x, // 宽度方向步长ap_uint<8> stride_y, // 高度方向步长ap_uint<1> padding_mode, // 卷积的模式; 0: valid(没有padding填充), 1:same(输入输出的图大小不变)ap_uint<1> relu_en, // 激活函数tile_type input_feature[], ap_uint<4> input_feature_precision,	// 输入特征图地址和精度(小数点位置)tile_type weight[], ap_uint<4> weight_precision,// 权重地址和精度(小数点位置)tile_type output_feature[], ap_uint<4> out_feature_precision// 输出特征图地址和精度(小数点位置)
)
{// Feature: [CHin/K][H][W][K]// Weight: [CHout][CHin/K][KH][KW][K]// 根据卷积模式,计算paddingap_uint<8> padding_x, padding_y;if(padding_mode == 0){padding_x = padding_y = 0;}else{padding_x = (kernel_width-1)/2;padding_y = (kernel_height-1)/2;}// 计算分块个数ap_uint<16> div_tile_num = (in_channel + K-1) / K;// 计算输出截断精度ap_uint<5> out_truncate = input_feature_precision + weight_precision - out_feature_precision;/** [x x x] x x x* x x [x x x] x*/// 计算输出宽度和高度ap_uint<16> out_width = (in_width + padding_x*2) / stride_x + 1;ap_uint<16> out_height = (in_height + padding_y*2) / stride_y + 1;// 选择输出特征的第y行,第x列,第c_out个输出通道的数据// 选择第c_out个权重的第y行,第x列,第tile_index个分块LOOP_out_channel:for(int out_index = 0; out_index < out_channel; ++ out_index){LOOP_out_height:for(int i = 0; i < out_height; ++ i){LOOP_out_width:for(int j = 0; j < out_width; ++ j){// 相乘结果累加acc_type sum=0;// 计算输出特征中一个tile的数据ap_int<16> out_tile = 0;LOOP_kernel_height:for(int kh = 0; kh < kernel_height; ++ kh){LOOP_kernel_width:for(int kw = 0; kw < kernel_width; ++ kw){LOOP_div_tile_num:for(int tile_index = 0; tile_index < div_tile_num; ++ tile_index){// 获取计算点ap_uint<16> in_h = i*stride_y-padding_y + kh;ap_uint<16> in_w = j*stride_x-padding_x + kw;// 获取输入特征和权重的一个块数据tile_type data_tile, weight_tile;// 有padding会越界if(in_h >= 0 && in_h < in_height && in_w >= 0 && in_w < in_width){data_tile = input_feature[in_width*in_height*tile_index + in_width*in_h + in_w];weight_tile = weight[kernel_width*kernel_height*div_tile_num*out_index+ kernel_width*kernel_height*tile_index+ kernel_width*kh+kw];}else{data_tile = 0; weight_tile = 0;}// 块数据相乘mul_tile_type mul_tile_data;for(int k = 0; k < K; ++ k)mul_tile_data.range(k*32+31, k*32) =(data_type)data_tile.range(k*16+15, k*16)*(data_type)weight_tile.range(k*16+15, k*16);// 相乘结果累加for(int k = 0; k < K; ++ k)sum += (mul_tile_type)mul_tile_data.range(k*32+31, k*32);}}}// 激活函数if(relu_en & sum < 0) sum = 0;// 截断多余精度acc_type res = sum >> out_truncate;if (res > 32767)res = 32767;else if (res < -32768)res = -32768;out_tile.range((out_index % K) * 16 + 15, (out_index % K) * 16) = res;// 存tile里的一个数据// 一个tile都存完了或者存到最后一个通道凑不够一个tileif((out_index%K) == K - 1 || out_index == (out_channel - 1))output_feature[(out_index/K)*out_width*out_height + out_width*i+j] = out_tile;}}}
}

main.cpp

#include "stdio.h"
#include "conv_core.h"#define IN_WIDTH 10
#define IN_HEIGHT 10
#define IN_CHANNEL 1
#define IN_DIV_TILE_NUM	((IN_CHANNEL+K-1)/K)#define KERNEL_WIDTH 5
#define KERNEL_HEIGHT 5
#define STRIDE_X 1
#define STRIDE_Y 1#define RELU_EN 0
#define PADDING_MODE 0 // 0:valid 1:same
#define PADDING_X (PADDING_MODE?(KERNEL_WIDTH-1)/2:0)
#define PADDING_Y (PADDING_MODE?(KERNEL_HEIGHT-1)/2:0)#define OUT_CHANNEL 1
#define OUT_WIDTH ((IN_WIDTH+PADDING_X*2-KERNEL_WIDTH)/STRIDE_X+1)
#define OUT_HEIGHT ((IN_HEIGHT+PADDING_Y*2-KERNEL_HEIGHT)/STRIDE_Y+1)
#define OUT_DIV_TILE_NUM ((OUT_CHANNEL+K-1)/K)int main(void)
{tile_type input_feature[IN_DIV_TILE_NUM][IN_HEIGHT][IN_WIDTH];tile_type weight[OUT_CHANNEL][IN_DIV_TILE_NUM][KERNEL_HEIGHT][KERNEL_WIDTH];tile_type output_feature[OUT_DIV_TILE_NUM][OUT_HEIGHT][OUT_WIDTH];for(int tile_index = 0; tile_index < IN_DIV_TILE_NUM; ++ tile_index){for(int i = 0; i < IN_HEIGHT; ++ i){for(int j = 0; j < IN_WIDTH; ++ j){for(int k = 0; k < K; ++ k){// 可能除不尽if(tile_index*K+k < IN_CHANNEL)input_feature[tile_index][i][j].range(k*16+15, k*16) = (1<<14);elseinput_feature[tile_index][i][j].range(k*16+15, k*16) = 0;}}}}for(int out_index = 0; out_index < OUT_CHANNEL; ++ out_index){for(int tile_index = 0; tile_index < OUT_DIV_TILE_NUM; ++ tile_index){for(int i = 0; i < OUT_HEIGHT; ++ i){for(int j = 0; j < OUT_WIDTH; ++j){for(int k = 0; k < K; ++ k){// 输入特征赋值为0,特征值就不用考虑除不尽的问题weight[out_index][tile_index][i][j].range(16*k+15, 16*k) = (1<<14);}}}}}for(int tile_index = 0; tile_index < OUT_DIV_TILE_NUM; ++ tile_index){for(int i = 0; i < OUT_HEIGHT; ++ i){for(int j = 0; j < OUT_WIDTH; ++ j){output_feature[tile_index][i][j] = 0;}}}printf("initial down\n");conv_core(IN_CHANNEL, IN_HEIGHT, IN_WIDTH, OUT_CHANNEL,KERNEL_WIDTH, KERNEL_HEIGHT,STRIDE_X, STRIDE_Y,PADDING_MODE, RELU_EN,&input_feature[0][0][0], 14,&weight[0][0][0][0], 14,&output_feature[0][0][0], 10);for(int tile_index = 0; tile_index < OUT_DIV_TILE_NUM; ++ tile_index){for(int i = 0; i < OUT_HEIGHT; ++ i){for(int j = 0; j < OUT_WIDTH; ++ j){cout << "out[" << tile_index <<"]["<

运行

C仿真结果:

image-20221105183939657

C综合:

image-20221105184415141

出错了,需要加约束。报错信息:输入特征feature_in是没有固定长度的

但是我们只是把input_feature当作基地址,而不是数组,所以需要告诉工具,数据来自外部存储器

数据接口约束

image-20221105185249772

image-20221105185314764

#pragma HLS INTERFACE m_axi depth=999999999 port=weight offset=slave
#pragma HLS INTERFACE m_axi depth=999999999 port=output_feature offset=slave
#pragma HLS INTERFACE m_axi depth=999999999 port=input_feature offset=slave

再次C综合:

image-20221105185415418

???是因为,循环次数是一个变量,工具无法估计性能

循环次数约束

那就根据test bench里的例子进行测试约束

image-20221105190053192

#include "conv_core.h"void conv_core(// 特征图参数ap_uint<16> in_channel, // 输入特征的通道数ap_uint<16> in_height, // 输入特征高度ap_uint<16> in_width, // 输入特征宽度ap_uint<16> out_channel, // 输出特征通道数// 卷积核参数ap_uint<8> kernel_width, // 卷积核宽度ap_uint<8> kernel_height, // 卷积核高度ap_uint<8> stride_x, // 宽度方向步长ap_uint<8> stride_y, // 高度方向步长ap_uint<1> padding_mode, // 卷积的模式; 0: valid(没有padding填充), 1:same(输入输出的图大小不变)ap_uint<1> relu_en, // 激活函数tile_type input_feature[], ap_uint<4> input_feature_precision,	// 输入特征图地址和精度(小数点位置)tile_type weight[], ap_uint<4> weight_precision,// 权重地址和精度(小数点位置)tile_type output_feature[], ap_uint<4> out_feature_precision// 输出特征图地址和精度(小数点位置)
)
{#pragma HLS INTERFACE m_axi depth=999999999 port=weight offset=slave#pragma HLS INTERFACE m_axi depth=999999999 port=output_feature offset=slave#pragma HLS INTERFACE m_axi depth=999999999 port=input_feature offset=slave// Feature: [CHin/K][H][W][K]// Weight: [CHout][CHin/K][KH][KW][K]// 根据卷积模式,计算paddingap_uint<8> padding_x, padding_y;if(padding_mode == 0){padding_x = padding_y = 0;}else{padding_x = (kernel_width-1)/2;padding_y = (kernel_height-1)/2;}// 计算分块个数ap_uint<16> div_tile_num = (in_channel + K-1) / K;// 计算输出截断精度ap_uint<5> out_truncate = input_feature_precision + weight_precision - out_feature_precision;/** [x x x] x x x* x x [x x x] x*/// 计算输出宽度和高度ap_uint<16> out_width = (in_width + padding_x*2) / stride_x + 1;ap_uint<16> out_height = (in_height + padding_y*2) / stride_y + 1;// 选择输出特征的第y行,第x列,第c_out个输出通道的数据// 选择第c_out个权重的第y行,第x列,第tile_index个分块LOOP_out_channel:for(int out_index = 0; out_index < out_channel; ++ out_index){
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1LOOP_out_height:for(int i = 0; i < out_height; ++ i){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10LOOP_out_width:for(int j = 0; j < out_width; ++ j){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10// 相乘结果累加acc_type sum=0;// 计算输出特征中一个tile的数据ap_int<16> out_tile = 0;LOOP_kernel_height:for(int kh = 0; kh < kernel_height; ++ kh){
#pragma HLS LOOP_TRIPCOUNT min=5 max=5 avg=5LOOP_kernel_width:for(int kw = 0; kw < kernel_width; ++ kw){
#pragma HLS LOOP_TRIPCOUNT min=5 max=5 avg=5LOOP_div_tile_num:for(int tile_index = 0; tile_index < div_tile_num; ++ tile_index){
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1// 获取计算点ap_uint<16> in_h = i*stride_y-padding_y + kh;ap_uint<16> in_w = j*stride_x-padding_x + kw;// 获取输入特征和权重的一个块数据tile_type data_tile, weight_tile;// 有padding会越界if(in_h >= 0 && in_h < in_height && in_w >= 0 && in_w < in_width){data_tile = input_feature[in_width*in_height*tile_index + in_width*in_h + in_w];weight_tile = weight[kernel_width*kernel_height*div_tile_num*out_index+ kernel_width*kernel_height*tile_index+ kernel_width*kh+kw];}else{data_tile = 0; weight_tile = 0;}// 块数据相乘mul_tile_type mul_tile_data;for(int k = 0; k < K; ++ k)mul_tile_data.range(k*32+31, k*32) =(data_type)data_tile.range(k*16+15, k*16)*(data_type)weight_tile.range(k*16+15, k*16);// 相乘结果累加for(int k = 0; k < K; ++ k)sum += (mul_tile_type)mul_tile_data.range(k*32+31, k*32);}}}// 激活函数if(relu_en & sum < 0) sum = 0;// 截断多余精度acc_type res = sum >> out_truncate;if (res > 32767)res = 32767;else if (res < -32768)res = -32768;out_tile.range((out_index % K) * 16 + 15, (out_index % K) * 16) = res;// 存tile里的一个数据// 一个tile都存完了或者存到最后一个通道凑不够一个tileif((out_index%K) == K - 1 || out_index == (out_channel - 1))output_feature[(out_index/K)*out_width*out_height + out_width*i+j] = out_tile;}}}
}

综合结果:

image-20221105190949870

优化

最内层的块相乘只用两个周期完成

一个周期用来去input_tile,另一个取weight_tile,输出计算结果的同时,也在取数据

image-20221105191912130

添加 pipeline 优化

迭代间隔 II = 2

image-20221105192124549

如果效果理想的话应该是 1*10*10*5*5*2=5000

但是综合结果并不是:

image-20221105193101366

卷积一次:2*25 = 50

这里循环的latency是59,多了9个周期是迭代latency

但是顶层的迭代latency达到了70,其中多的11周期的计算在这里:

image-20221105194344619

完美循环优化与计算顺序的调换

  • 完美循环

使用if语句使循环之间没有代码,使得循环可以合并

从存储器取块数据相乘累加的运算,其实可以和计算结果的存储并行

image-20221105201529553

  • 计算顺序调换

将out_channel挪到kernel_width上面,这样每计算完一次卷积tile,就判断一次对输出特征写计算结果

#include "conv_core.h"void conv_core(// 特征图参数ap_uint<16> in_channel, // 输入特征的通道数ap_uint<16> in_height, // 输入特征高度ap_uint<16> in_width, // 输入特征宽度ap_uint<16> out_channel, // 输出特征通道数// 卷积核参数ap_uint<8> kernel_width, // 卷积核宽度ap_uint<8> kernel_height, // 卷积核高度ap_uint<8> stride_x, // 宽度方向步长ap_uint<8> stride_y, // 高度方向步长ap_uint<1> padding_mode, // 卷积的模式; 0: valid(没有padding填充), 1:same(输入输出的图大小不变)ap_uint<1> relu_en, // 激活函数tile_type input_feature[], ap_uint<4> input_feature_precision,	// 输入特征图地址和精度(小数点位置)tile_type weight[], ap_uint<4> weight_precision,// 权重地址和精度(小数点位置)tile_type output_feature[], ap_uint<4> out_feature_precision// 输出特征图地址和精度(小数点位置)
)
{#pragma HLS INTERFACE m_axi depth=999999999 port=weight offset=slave#pragma HLS INTERFACE m_axi depth=999999999 port=output_feature offset=slave#pragma HLS INTERFACE m_axi depth=999999999 port=input_feature offset=slave// Feature: [CHin/K][H][W][K]// Weight: [CHout][CHin/K][KH][KW][K]// 根据卷积模式,计算paddingap_uint<8> padding_x, padding_y;if(padding_mode == 0){padding_x = padding_y = 0;}else{padding_x = (kernel_width-1)/2;padding_y = (kernel_height-1)/2;}// 计算分块个数ap_uint<16> div_tile_num = (in_channel + K-1) / K;// 计算输出截断精度ap_uint<5> out_truncate = input_feature_precision + weight_precision - out_feature_precision;/** [x x x] x x x* x x [x x x] x*/// 计算输出宽度和高度ap_uint<16> out_width = (in_width + padding_x*2) / stride_x + 1;ap_uint<16> out_height = (in_height + padding_y*2) / stride_y + 1;// 计算输出特征中一个tile的数据ap_int<16> out_tile = 0;// 相乘结果累加acc_type sum=0;// 选择输出特征的第y行,第x列,第c_out个输出通道的数据// 选择第c_out个权重的第y行,第x列,第tile_index个分块LOOP_out_height:for(int i = 0; i < out_height; ++ i){
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1LOOP_out_width:for(int j = 0; j < out_width; ++ j){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10LOOP_out_channel:for(int out_index = 0; out_index < out_channel; ++ out_index){
#pragma HLS LOOP_TRIPCOUNT min=10 max=10 avg=10LOOP_kernel_height:for(int kh = 0; kh < kernel_height; ++ kh){
#pragma HLS LOOP_TRIPCOUNT min=5 max=5 avg=5LOOP_kernel_width:for(int kw = 0; kw < kernel_width; ++ kw){
#pragma HLS LOOP_TRIPCOUNT min=5 max=5 avg=5LOOP_div_tile_num:for(int tile_index = 0; tile_index < div_tile_num; ++ tile_index){
#pragma HLS PIPELINE II=2
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1// 获取计算点ap_uint<16> in_h = i*stride_y-padding_y + kh;ap_uint<16> in_w = j*stride_x-padding_x + kw;// 获取输入特征和权重的一个块数据tile_type data_tile, weight_tile;// 有padding会越界if(in_h >= 0 && in_h < in_height && in_w >= 0 && in_w < in_width){data_tile = input_feature[in_width*in_height*tile_index + in_width*in_h + in_w];weight_tile = weight[kernel_width*kernel_height*div_tile_num*out_index+ kernel_width*kernel_height*tile_index+ kernel_width*kh+kw];}else{data_tile = 0; weight_tile = 0;}// 块数据相乘mul_tile_type mul_tile_data;for(int k = 0; k < K; ++ k)mul_tile_data.range(k*32+31, k*32) =(data_type)data_tile.range(k*16+15, k*16)*(data_type)weight_tile.range(k*16+15, k*16);// 相乘结果累加for(int k = 0; k < K; ++ k)sum += (mul_tile_type)mul_tile_data.range(k*32+31, k*32);if(tile_index == div_tile_num-1 && kh == kernel_height-1 && kw == kernel_width-1){// 激活函数if(relu_en & sum < 0) sum = 0;// 截断多余精度acc_type res = sum >> out_truncate;if (res > 32767)res = 32767;else if (res < -32768)res = -32768;// 先缓存下来,下面一次写入out_tile.range((out_index % K) * 16 + 15, (out_index % K) * 16) = res;sum = 0;// 存tile里的一个数据// 一个tile都存完了或者存到最后一个通道凑不够一个tileif((out_index%K) == K - 1 || out_index == (out_channel - 1)){output_feature[(out_index/K)*out_width*out_height + out_width*i+j] = out_tile;out_tile = 0;}}}}}}}}
}

C综合已经达到我们的预期了

image-20221105201042737

访问方式的优化-通道方向的并行

给特征图和权重分别分配一条总线,那么就可以同时取出两个数据,只需要一个周期就可以完成一次tile的计算

bundle:一捆,不选的话默认是同一个总线

所以这里我们给input_feature和weight取两个不同的名字,就给它们分配了不同的总线:

image-20221105202209564

image-20221105202520163

AXI是全双工的,可以同时写和读,但是同时读两个就不太行,读一个的同时写一个可以。所以不需要给output_feature分配一个总线

然后我们就可以在一个周期内运行一次tile计算,设置pipeline的II = 1:

image-20221105203023536

也就是K个数据的乘法和累加,需要K个乘法器同时进行

image-20221105203224253

可以看出来是2500个周期:1*10*10*5*5*(1 clock)=2500

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
苁蓉的食用方法 鲜苁蓉的吃法有... 肉苁蓉号称“温而不热,补而不峻,暖而不燥,滑而不泄,故有从容之名。”历来也是传统药食同源的滋补佳品。...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
阳澄湖在哪里哪个省的 阳澄湖是... 点击题目下方苏州生活指南有用 有趣 有态度近期的阳澄湖度很高,3月24日,央视二套《第一时间》直播连...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...