当前位置:   article > 正文

【Verilog我思我用】-generate

verilog generate for嵌套

【Verilog我思我用】-generate

bc38969d38e860a74361a8d993080bef.png

在使用xilinx官方例程《XAPP585》实现CameraLink接口发送或者接收数据时,有个程序还是值得学习的,下面把这段程序截出来:

  1. genvar i ;
  2. genvar j ;
  3. generate
  4. for (i = 0 ; i <= (N-1) ; i = i+1)
  5. begin : loop0
  6. serdes_7_to_1_diff_sdr #(
  7.        .D   (D),
  8.        .DATA_FORMAT  (DATA_FORMAT))
  9. dataout (
  10.  .dataout_p    (dataout_p[D*(i+1)-1:D*i]),
  11.  .dataout_n    (dataout_n[D*(i+1)-1:D*i]),
  12.  .clkout_p    (clkout_p[i]),
  13.  .clkout_n    (clkout_n[i]),
  14.  .txclk      (txclk),
  15.  .pixel_clk      (pixel_clk),
  16.  .reset     (reset),
  17.  .clk_pattern    (clk_pattern),
  18.  .datain    (datain[(D*(i+1)*7)-1:D*i*7]));  
  19. end
  20. endgenerate

主要是generate的用法,整个文件的功能是实现可选多通道数据发送,我们知道Cameralink中对于多通道传输时有一部分功能代码时相同的,只不过需要多通道复用,我们知道generate有一个功能就是重复操作多个模块的实例引用,当然就适合本例程。

下面我们先讲一讲generate的用法再结合代码简单讲解一下,对于generate其实很好理解,只不过写出来比较难。

generate用法

关键字generate和endgenerate(和begin / end类似)作为使用语法的起点,有三种衍生结构,分别为:

  • generate - for 语句结构

  • generate - if 语句结构

  • generate - case 语句结构

使用generate的情况主要如下:

  • 使用 for 循环对模块进行多次相似实例化

  • 使用参数更改模块的结构或设计

  • 使用带有断言语句进行功能和形式验证

b7f0d59c61bbe85bc73f2daa8ab8ea55.png

在这里我们思考一下,generate是在运行中构造重复模块吗??

答案是否定的,generate语句不是运行时构造。如果你想一想,这个generate结构实际上是在创建一个重复电路,我们不能即时添加或删除硬件电路,所以generate在综合过程中其实是重复构造相似电路,而不是在运行时构造。

下面先按照generate结构分别举例,然后举例几个常用案例。

generate - for语句结构

在使用generate - for语句之前,我们需要先声明一个变量genvar,用于for循环语句进行判断。

下面举两个不同应用的例子:

d56aac19b6fa742220b67159dd2c82a4.png

0a54c37f3f0aeade23fa008dc89bb3fc.png

上面两个模块功能一样,第一个是对always 块进行了循环;第二个则是对实例化时的模块进行了循环。xorLoop 是 generate 语句模块名,目的是通过它对循环语句进行层次化引用,所以在上面栗子中的 xorLoop 模块相对层次名为 xorLoop[0].u_xor(后面会举例说明)

这里在对比两个常见的例子:

1f7fd5615a740f817fe39c25c18fe941.png

上面的例子功能也一样,一个使用generate...for语句一个使用for语句,关于这两者区别我会在文章最后总结里说明,大家可以自己先思考。

generate - if语句结构

generate -if 语句结构比较宽松,即不需要对不需要对generate语句进行命名(generate...for主要是对循环语句进行层次化引用) ,也不需要变量genvar。由于 generate - if 语句结构是通过判断语句执行代码块,这就决定了每次最多执行一个代码块,这种情况下,可以对各个代码块使用相同命名是合法的,且有助于保持对代码的层次化引用。

需要注意的一点是,在 generate 块中的判断条件必须是常量!

4363d8c7ffb94bc84813d96753e1c155.png

generate - case

generate - case 语句和 generate - if 语句核心思想都是进行条件判断,用法基本一致。

和 generate - if 语句一样,case 判断条件必须是常量。

1a1a588ff73d2177b05a43db3ee3c9f5.png

下面按照应用场景举例:

循环生成构造

循环生成构造提供了一种简单而简洁的方法来创建模块项的多个实例,例如模块实例、分配语句、断言、接口实例等。你可以把它想象成一台“克隆机”。

本质上,它是一种特殊类型的for循环,其循环索引变量为 datatype genvar。这是一个有趣的事实- genvar它是一个整数数据类型,仅在综合时存在并在运行时消失。

我们看到的《XAPP585》的例程就是这种运行结构,下面再举例看下该语句的特点:

  1. /** Example 1 */
  2. /**
  3.  * 16 input mux
  4.  *
  5.  * Example of how to use Loop Generate Construct
  6.  */
  7. module mux_16(
  8.     input  logic [0:15] [127:0] mux_in,
  9.     input  logic [3:0select,
  10.     output logic [127:0] mux_out
  11. );
  12.     logic [0:15] [127:0] temp;
  13.     // The for-loop creates 16 assign statements
  14.     genvar i;
  15.     generate
  16.         for (i=0; i < 16; i++) begin
  17.             assign temp[i] = (select == i) ? mux_in[i] : 0;
  18.         end
  19.     endgenerate
  20.     assign mux_out = temp[0] | temp[1] | temp[2] | temp[3] |
  21.                      temp[4] | temp[5] | temp[6] | temp[7] |
  22.                      temp[8] | temp[9] | temp[10] | temp[11] |
  23.                      temp[12] | temp[13] | temp[14] | temp[15];
  24. endmodule: mux_16

仿真文件如下:

  1. `timescale 1ns/1ps
  2. /**
  3.  * Testbench to exercise the mux_16 module.
  4.  * Here we instantiate the mux 4 times. Each instance is
  5.  * fed a different input with different input `select` and
  6.  * the output is observed.
  7.  */
  8. module tb_mux_16;
  9. logic               clk;
  10. logic [0:15][127:0] test_in[4];
  11. logic [3:0]         test_select[4];
  12. logic [127:0]       test_out[4];
  13. int i, j, k;
  14. initial begin
  15.     clk = 0;
  16.     forever #1ns clk = ~clk;
  17. end
  18. initial begin
  19.     // Set inputs
  20.     for (i=0; i < 4; i++) begin
  21.         for (j=0; j < 16; j++) begin
  22.             test_in[i][j] = 127'habcd_0000 + (i << 8) + j;
  23.         end
  24.         test_select[i] = i;
  25.     end
  26.     #2ns;
  27.     // Print outputs
  28.     for(k=0; k < 4; k++) begin
  29.         $display("test_out[%0d] = 0x%x", k, test_out[k]);
  30.     end
  31.     #2ns;
  32.     // Change input select 
  33.     for (i=0; i < 4; i++) begin
  34.         test_select[i] = 10 + i;
  35.     end
  36.     #2ns;
  37.     // Print outputs again
  38.     for(k=0; k < 4; k++) begin
  39.         $display("test_out[%0d] = 0x%x", k, test_out[k]);
  40.     end
  41.     #10ns;
  42.     $finish;
  43. end
  44. genvar m;
  45. generate
  46.     for (m=0; m < 4; m++) begin: MUX
  47.        mux_16 imux_16 (
  48.            .mux_in(test_in[m]),
  49.            .select(test_select[m]),
  50.            .mux_out(test_out[m])
  51.        ); 
  52.     end
  53. endgenerate
  54. endmodule: tb_mux_16

我们还可以嵌套generate...for 循环。只需确保genvars将外部循环和内部循环分开使用,并在嵌套的 for 循环中引用这些变量时要小心,这是一个经常犯错误的地方。

条件生成构造

条件生成构造允许根据在模块实例化期间传递的参数值更改设计结构。这在为设计创建参数化通用 RTL 模块时非常有用。

一个简单的例子:

  1. /** Example 2.1 */
  2. /**
  3.  * A simple generate example. This paramerter OPERATION_TYPE,
  4.  * passed when this module is instantiated, is used to select
  5.  * the operation between inputs `a` and `b`.
  6.  */
  7. module conditional_generate
  8.     #(parameter OPERATION_TYPE = 0)
  9.     (
  10.         input  logic [31:0] a,
  11.         input  logic [31:0] b,
  12.         output logic [63:0] z
  13.     );
  14.     // The generate-endgenerate keywords are optional.
  15.     // It is the act of doing a conditional operation
  16.     // on a parameter that makes this a generate block.
  17.     generate
  18.         if (OPERATION_TYPE == 0) begin
  19.             assign z = a + b;
  20.         end
  21.         else if (OPERATION_TYPE == 1) begin
  22.             assign z = a - b;
  23.         end
  24.         else if (OPERATION_TYPE == 2) begin
  25.             assign z = (a << 1) + b; // 2a+b
  26.         end
  27.         else begin
  28.             assign z = b - a;
  29.         end
  30.     endgenerate
  31. endmodule: conditional_generate

另一个例子 - 我们需要创建一个通用 CRC 生成器的任务。团队中的其他设计人员应该能够在 3 个多项式中选择 1 个进行 CRC 计算。

这是一种方法 - 提供一个名为 CRC_SEL 的参数,该参数在此模块实例化时使用,此CRC_SEL参数用来选择在模块中生成哪个 CRC 函数。通过使用generate而不是简单的多路复用器,可以节省一堆门电路和触发器,因为不需要的 CRC 函数不会被实例化。

fc8e42703e7fb0b3e73c5d8fd8121d86.png

完整代码如下:

  1. /**
  2.  * CRC generator module. Select the desired polynomial
  3.  * using the CRC_SEL parameter.
  4.  * 
  5.  * Default polynomial : x^16 + x^15 + x^2 + 1 
  6.  * CRC_SEL = 0        : x^16 + x^1 + 1
  7.  * CRC_SEL = 1        : x^16 + x^12 + x^5 + 1
  8.  *
  9.  * USAGE:
  10.  * + Strobe `start` when driving the first valid byte
  11.  * + Strobe `done` one clk after driving the last valid byte
  12.  * + The final CRC is available 1 clk after the last valid byte
  13.  *   is driven. This is the same cycle you'll drive `done`.
  14.  *
  15.  */
  16. module crc_gen
  17.     #(parameter CRC_SEL = 0)
  18.     (
  19.         input  logic clk,
  20.         input  logic rst,
  21.         input  logic start,
  22.         input  logic done,
  23.         input  logic [7:0] data_in,
  24.         input  logic [15:0] crc_in,
  25.         output logic [15:0] crc_out
  26.     );
  27.     logic [7:0]  data_in_d;
  28.     logic [15:0] crc_in_d;
  29.     assign crc_in_d = (start | done) ? 16'd0 : crc_in;
  30.     assign data_in_d = (done) ? 8'd0 : data_in;
  31.     always_ff @(posedge clk) begin
  32.         if (rst) begin
  33.             crc_out <= 'd0;
  34.         end
  35.         else begin
  36.             // Generate blocks are always assigned a name. If
  37.             // you don't name the generate block, it will be
  38.             // given a default auto generated name.
  39.             //
  40.             // To invoke a function within a generate block,
  41.             // hierarchically call it 
  42.             // <generate_blk_name>.<function_name>
  43.             crc_out <= crc_poly.nextCRC16_D8(data_in_d, crc_in_d);
  44.         end
  45.     end
  46.     // Once again the generate-endgenerate keywords are optional
  47.     // It is the act of using a parameter, CRC_SEL, in the case
  48.     // statement that makes it a generate block
  49.     //
  50.     // Also notice how all the generate blocks are given the same
  51.     // name `crc_poly` and all the function names are the same
  52.     // `nextCRC16_D8`. This is correct because only one of the
  53.     // function declarations is compiled in during elaboration
  54.     // phase.
  55.     generate
  56.     case (CRC_SEL)
  57.         0
  58.         begin: crc_poly
  59.             // polynomial: x^16 + x^1 + 1
  60.             // data width: 8
  61.             // convention: the first serial bit is D[7]
  62.             function automatic [15:0] nextCRC16_D8;
  63.             
  64.                 input [7:0] Data;
  65.                 input [15:0] crc;
  66.                 reg [7:0] d;
  67.                 reg [15:0] c;
  68.                 reg [15:0] newcrc;
  69.                 d = Data;
  70.                 c = crc;
  71.                 
  72.                 newcrc[0] = d[0] ^ c[8];
  73.                 newcrc[1] = d[1] ^ d[0] ^ c[8] ^ c[9];
  74.                 newcrc[2] = d[2] ^ d[1] ^ c[9] ^ c[10];
  75.                 newcrc[3] = d[3] ^ d[2] ^ c[10] ^ c[11];
  76.                 newcrc[4] = d[4] ^ d[3] ^ c[11] ^ c[12];
  77.                 newcrc[5] = d[5] ^ d[4] ^ c[12] ^ c[13];
  78.                 newcrc[6] = d[6] ^ d[5] ^ c[13] ^ c[14];
  79.                 newcrc[7] = d[7] ^ d[6] ^ c[14] ^ c[15];
  80.                 newcrc[8] = d[7] ^ c[0] ^ c[15];
  81.                 newcrc[9] = c[1];
  82.                 newcrc[10] = c[2];
  83.                 newcrc[11] = c[3];
  84.                 newcrc[12] = c[4];
  85.                 newcrc[13] = c[5];
  86.                 newcrc[14] = c[6];
  87.                 newcrc[15] = c[7];
  88.                 nextCRC16_D8 = newcrc;
  89.             endfunction
  90.         end
  91.         1:
  92.         begin: crc_poly
  93.             // polynomial: x^16 + x^12 + x^5 + 1
  94.             // data width: 8
  95.             // convention: the first serial bit is D[7]
  96.             function automatic [15:0] nextCRC16_D8;
  97.             
  98.                 input [7:0] Data;
  99.                 input [15:0] crc;
  100.                 reg [7:0] d;
  101.                 reg [15:0] c;
  102.                 reg [15:0] newcrc;
  103.                 d = Data;
  104.                 c = crc;
  105.                 
  106.                 newcrc[0] = d[4] ^ d[0] ^ c[8] ^ c[12];
  107.                 newcrc[1] = d[5] ^ d[1] ^ c[9] ^ c[13];
  108.                 newcrc[2] = d[6] ^ d[2] ^ c[10] ^ c[14];
  109.                 newcrc[3] = d[7] ^ d[3] ^ c[11] ^ c[15];
  110.                 newcrc[4] = d[4] ^ c[12];
  111.                 newcrc[5] = d[5] ^ d[4] ^ d[0] ^ c[8] ^ c[12] ^ c[13];
  112.                 newcrc[6] = d[6] ^ d[5] ^ d[1] ^ c[9] ^ c[13] ^ c[14];
  113.                 newcrc[7] = d[7] ^ d[6] ^ d[2] ^ c[10] ^ c[14] ^ c[15];
  114.                 newcrc[8] = d[7] ^ d[3] ^ c[0] ^ c[11] ^ c[15];
  115.                 newcrc[9] = d[4] ^ c[1] ^ c[12];
  116.                 newcrc[10] = d[5] ^ c[2] ^ c[13];
  117.                 newcrc[11] = d[6] ^ c[3] ^ c[14];
  118.                 newcrc[12] = d[7] ^ d[4] ^ d[0] ^ c[4] ^ c[8] ^ c[12] ^ c[15];
  119.                 newcrc[13] = d[5] ^ d[1] ^ c[5] ^ c[9] ^ c[13];
  120.                 newcrc[14] = d[6] ^ d[2] ^ c[6] ^ c[10] ^ c[14];
  121.                 newcrc[15] = d[7] ^ d[3] ^ c[7] ^ c[11] ^ c[15];
  122.                 nextCRC16_D8 = newcrc;
  123.             endfunction
  124.         end
  125.         default
  126.             begin: crc_poly
  127.             // polynomial: x^16 + x^15 + x^2 + 1
  128.             // data width: 8
  129.             // convention: the first serial bit is D[7]
  130.             function automatic [15:0] nextCRC16_D8;
  131.             
  132.                 input [7:0] Data;
  133.                 input [15:0] crc;
  134.                 reg [7:0] d;
  135.                 reg [15:0] c;
  136.                 reg [15:0] newcrc;
  137.                 d = Data;
  138.                 c = crc;
  139.                 
  140.                 newcrc[0] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ d[0] ^ c[8] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[13] ^ c[14] ^ c[15];
  141.                 newcrc[1] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[13] ^ c[14] ^ c[15];
  142.                 newcrc[2] = d[1] ^ d[0] ^ c[8] ^ c[9];
  143.                 newcrc[3] = d[2] ^ d[1] ^ c[9] ^ c[10];
  144.                 newcrc[4] = d[3] ^ d[2] ^ c[10] ^ c[11];
  145.                 newcrc[5] = d[4] ^ d[3] ^ c[11] ^ c[12];
  146.                 newcrc[6] = d[5] ^ d[4] ^ c[12] ^ c[13];
  147.                 newcrc[7] = d[6] ^ d[5] ^ c[13] ^ c[14];
  148.                 newcrc[8] = d[7] ^ d[6] ^ c[0] ^ c[14] ^ c[15];
  149.                 newcrc[9] = d[7] ^ c[1] ^ c[15];
  150.                 newcrc[10] = c[2];
  151.                 newcrc[11] = c[3];
  152.                 newcrc[12] = c[4];
  153.                 newcrc[13] = c[5];
  154.                 newcrc[14] = c[6];
  155.                 newcrc[15] = d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ d[0] ^ c[7] ^ c[8] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[13] ^ c[14] ^ c[15];
  156.                 nextCRC16_D8 = newcrc;
  157.             endfunction
  158.         end
  159.     endcase
  160.     endgenerate
  161. endmodule: crc_gen

下面是仿真文件及结果:

  1. `timescale 1ns/1ps
  2. /**
  3.  * Testbench to exercise the mux_16 module.
  4.  * Here we instantiate the mux 4 times. Each instance is
  5.  * fed a different input with different input `select` and
  6.  * the output is observed.
  7.  */
  8. module tb_mux_16;
  9. logic               clk;
  10. logic [0:15][127:0] test_in[4];
  11. logic [3:0]         test_select[4];
  12. logic [127:0]       test_out[4];
  13. int i, j, k;
  14. initial begin
  15.     clk = 0;
  16.     forever #1ns clk = ~clk;
  17. end
  18. initial begin
  19.     // Set inputs
  20.     for (i=0; i < 4; i++) begin
  21.         for (j=0; j < 16; j++) begin
  22.             test_in[i][j] = 127'habcd_0000 + (i << 8) + j;
  23.         end
  24.         test_select[i] = i;
  25.     end
  26.     #2ns;
  27.     // Print outputs
  28.     for(k=0; k < 4; k++) begin
  29.         $display("test_out[%0d] = 0x%x", k, test_out[k]);
  30.     end
  31.     #2ns;
  32.     // Change input select 
  33.     for (i=0; i < 4; i++) begin
  34.         test_select[i] = 10 + i;
  35.     end
  36.     #2ns;
  37.     // Print outputs again
  38.     for(k=0; k < 4; k++) begin
  39.         $display("test_out[%0d] = 0x%x", k, test_out[k]);
  40.     end
  41.     #10ns;
  42.     $finish;
  43. end
  44. genvar m;
  45. generate
  46.     for (m=0; m < 4; m++) begin: MUX
  47.        mux_16 imux_16 (
  48.            .mux_in(test_in[m]),
  49.            .select(test_select[m]),
  50.            .mux_out(test_out[m])
  51.        ); 
  52.     end
  53. endgenerate
  54. endmodule: tb_mux_16
  55. Footer
  56. © 2022 GitHub, Inc.
  57. Footer navigation
  58. Terms
  59. Privacy
  60. Security
  61. Status
  62. Docs
  63. Cont
5c10dfbe7bd21b7ec43ed7e7c4025c34.png

断言和形式验证

generate - case 语句结构在编写断言时也非常有用,这反过来有助于形式验证。

如果对形式验证有任何经验,那么就会知道形式工具在尝试证明属性时很快就会遇到计算界限。因此,重要的是要保持属性简短而简单。

例如,如果有一个具有 8 个 REQquest 输入和 8 个 ACK 输出的仲裁器块,那么与其编写单个断言来覆盖所有 8 个 REQ/ACK 对,不如将其分解为具有 1 个 REQ/ACK 的 8 个单独的断言对。

  1. /** Example 3.1 */
  2. genvar k;
  3. generate
  4.     for (k=0; k < 8; k++) begin
  5.         req_a: assert property (req[k] |=> ack[k]);
  6.     end
  7. endgenerate

分层访问生成块

绊倒人们的一件事是如何访问位于生成块内的模块项。

生成块有一个名字。如果不为其命名,编译器将自动分配一个通用名称,例如genblk01,genblk02通常必须转储 wave 并查看Visualizer工具以查看分配了哪些名称。

要访问生成块中的模块项,必须分层访问它<generate_blk_name>.<module_item_name>。

这是来自 SystemVerilog LRM 1800-2012 的一个很好的示例(示例 4 第 27.5 节)。查看如何访问块中定义的任务和模块实例。

分层实例名称为:

  1. memory.word16[3].p, memory.word16[2].p,
  2. memory.word16[1].p, memory.word16[0].p,
  1. /** Example 4 */
  2. module dimm(addr, ba, rasx, casx, csx, wex, cke, clk, dqm, data, dev_id);
  3.     parameter [31:0] MEM_WIDTH = 16, MEM_SIZE = 8;
  4.     ... 
  5.     genvar i;
  6.     case ({MEM_SIZE, MEM_WIDTH})
  7.         {32'd8, 32'd16}: // 8Meg x 16 bits wide
  8.         begin: memory
  9.             for (i=0; i<4; i=i+1) begin:word16
  10.                 sms_08b216t0 p(.clk(clk), .csb(csx), .cke(cke),.ba(ba),
  11.                     .addr(addr), .rasb(rasx), .casb(casx),
  12.                     .web(wex), .udqm(dqm[2*i+1]), .ldqm(dqm[2*i]),
  13.                     .dqi(data[15+16*i:16*i]), .dev_id(dev_id));
  14.                 // The hierarchical instance names are:
  15.                 // memory.word16[3].p, memory.word16[2].p,
  16.                 // memory.word16[1].p, memory.word16[0].p,
  17.                 // and the task memory.read_mem
  18.             end
  19.             task read_mem;
  20.                 input [31:0] address;
  21.                 output [63:0] data;
  22.                 begin // call read_mem in sms module
  23.                     word[3].p.read_mem(address, data[63:48]);
  24.                     word[2].p.read_mem(address, data[47:32]);
  25.                     word[1].p.read_mem(address, data[31:16]);
  26.                     word[0].p.read_mem(address, data[150]);
  27.                 end
  28.             endtask 
  29.         end
  30.     ...
  31.     endcase
  32. endmodule

总结

这篇文章是在阅读《XAPP585》代码时候看着generate语法极其方便,所以引出了该篇文章,下面说下generate...for和for的区别:

  • 首先第二个代码时错误的!

只有当 for 循环在 generate 中时,才能将 always 放在 for 循环中!

  • generate for 循环和常规 for 循环之间的主要区别在于 generate for 循环是为每次迭代生成一个实例。这意味着在示例中将有 3 个 always 块(与常规循环情况下的 1 个块相反)。

一个更好的例子是:

  1. module A();
  2. ..
  3. endmodule;
  4. module B();
  5. parameter NUM_OF_A_MODULES = 2// should be overriden from higher hierarchy
  6. genvar i;
  7. for (i=0 i<NUM_OF_A_MODULES; i=i+1) {
  8.   A A_inst();
  9. }
  10. endmodule;

在此示例中,常规 for 无法完成创建 NUM_OF_A_MODULES 实例的工作。

参考

https://blog.csdn.net/weixin_42150654/article/details/123132249

夏宇闻. Verilog数字系统设计教程(第三版)[M]. 北京: 北京航空航天大学出版社, 2013: 68.

数字IC小站, Verilog中generate的使用[EB/OL], https://zhuanlan.zhihu.com/p/107047600, 2020-02-15.

Formal Verification - Erik Seligman, et al.

题目练习

HDLbits上有两道使用Generate的题目,如下:

https://hdlbits.01xz.net/wiki/Adder100i

https://hdlbits.01xz.net/wiki/Bcdadd100

无需注册,可以练习Generate的语法使用,同时还有仿真。后续我会在《HDLBits: 在线学习 SystemVerilog》系列解析这两个题目。 

0b5bab174ec0651155eaa472305e37dc.gif

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/873008
推荐阅读
相关标签
  

闽ICP备14008679号