2024年寒假练 - 用STEP BaseBoard V4.0制作计算器
该项目使用了思得普的WebIDE、硬件描述语言,实现了两位十进制加、减、乘、除计算器的设计,它的主要功能为:两位十进制加、减、乘、除计算器。
标签
FPGA
STEP BaseBoard V4.0
2024寒假练
菜菜鱼
更新2024-04-02
河南大学
241

 

 

特色:启用8个数码管中最右侧的4个显示。除法能计算出两位小数,而不一定非得是整除;考虑到小数的一般书写习惯,如果小数点后最末尾存在0可以自动删除并数据整体向右侧补位(例如18/3显示为6而非6.00)。输入的数可以是1位也可以是2位,输入灵活度增加。可以按下清零键后从头开始进行运算,无需再按复位键。

1.项目需求

实现一个两位十进制数加、减、乘、除运算的计算器,运算数和运算符(加、减、乘、除)由按键来控制,复位按键和4×4键盘按键分配如下图所示。


image.png

  • /*  4×4键盘按键分配
  •     7 8 9 +
  •     4 5 6 -
  •     1 2 3 *
  •     0 C = /
  • */

运算数和计算结果通过8个八段数码管显示。每个运算数使用两个数码管显示,左侧显示十位数,右侧显示个位数。输入两位十进制数时,最高位先在右侧显示,然后其跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。

 FheM1anVmLBxLX0YIiIxNkkAIWf6

 

 

2.需求分析

根据项目需求和给定的硬件资源,我们需要实现一个基于矩阵键盘和两个74HC595D芯片的两位十进制数加减乘除运算的计算器。下面是对需求的分析:

 

硬件需求:

4×4矩阵键盘:用于输入运算数和运算符。

8个八段数码管:用于显示运算数和计算结果。每个运算数使用两个数码管显示,分别表示十位数和个位数。


功能需求:

实现加、减、乘、除四种基本运算。

通过矩阵键盘输入两位十进制数和运算符。

显示输入的运算数和计算结果。

在输入两位十进制数时,最高位先在右侧显示,然后跳变到左侧的数码管上,低位在刚才高位占据的数码管上显示。

软件实现:

使用两个74HC595D芯片作为数码管的驱动器,通过串行方式控制8个数码管的显示。

实现输入的解析和运算逻辑,根据输入的运算符执行相应的运算。

控制数码管显示输入的运算数和计算结果,按照需求将输入的两位十进制数在数码管上显示。74HC595D是一种8位串行移位寄存器,通常用于驱动数码管或其他类似的数字显示设备。使用两个74HC595D可以实现8个数码管显示。

 

难点:系统应具有实时性,即时响应用户的按钮输入。同时一次按键不能导致多次输出,乘法器和除法器的设计也是难点。


3.实现的方式

按键扫描和识别:

使用Verilog编写按键扫描和识别的模块,该模块可以扫描矩阵键盘的按键状态,并将按下的键位进行识别。

将识别出的键位存储到适当的寄存器中,以备后续处理。


数值处理和转换为BCD码:

根据按键输入的顺序和规则,将识别出的按键值转换为两位十进制数。

实现加减乘除运算的逻辑,根据输入的运算符和运算数进行相应的运算。

将运算结果转换为BCD码(二进制码十进制),以便后续显示。


输出的BCD码转换为74HC595D的时序:

编写Verilog代码实现BCD码转换为74HC595D的时序控制逻辑。

使用串行方式将BCD码发送到74HC595D芯片,并控制时钟、数据和存储器时钟等引脚,将数据正确地加载到74HC595D中。

需要设计一个状态机来处理不同的状态:输入运算数状态、输入运算符状态、计算状态和显示结果状态。

在输入运算数状态和输入运算符状态,根据按键输入构建运算表达式,并处理格式问题。这里使用了一些的标志位来处理。小数点、0的显示都是避不开的问题。

在计算状态,对输入的运算表达式进行加减乘除运算,并将结果转换为BCD码。

在显示结果状态,将BCD码发送到74HC595D芯片进行数码管的显示。

4.功能框图

image.png



image.png


image.png


5.代码(内嵌到报告中)及说明

顶层文件:

module type_system(

        input              clk,

        input              rst_n,

        input       [3:0]   col,

        output  [3:0]   row,

        output             rclk_out,       //74HC595的RCK管脚

        output             sclk_out,       //74HC595的SCK管脚

        output             sdio_out        //74HC595的SER管脚


    );

 

        wire  [3:0] seg_data;

        wire         seg_data_flag;

 

wire            [15:0]  key_out;

wire            [15:0]  key_pulse;

//wire          [3:0]       seg_data;

//Array_KeyBoard

array_keyboard u1(

            .clk(clk),

            .rst_n(rst_n),

            .col(col),

            .row(row),

            .key_out(key_out),

            .key_pulse(key_pulse)

    );

 

 

//key_decode

key_decode u2(

            .clk                   (clk            ),

            .rst_n                 (rst_n          ),

            .key_pulse             (key_pulse      ),

            .seg_data              (seg_data       ),  //高4位代表十位,低4位代表个位

            .seg_data_flag         (seg_data_flag  )

    );

 

//wire [7:0] result;      // 定义结果

calculator calculator_inst (

    .clk(clk),

    .input_data(seg_data),

    .input_valid(seg_data_flag),

    .rst_n_in(rst_n),

    .rclk_out(rclk_out),       //74HC595的RCK管脚

    .sclk_out(sclk_out),       //74HC595的SCK管脚

    .sdio_out(sdio_out)     //74HC595的SER管脚

);
endmodule


image.png


  1. 输入输出信号:
    • 输入信号:
      • clk:时钟信号
      • rst_n:复位信号(低电平有效)
      • col:列线输入,4位信号
    • 输出信号:
      • row:行线输出,4位信号
      • rclk_out:74HC595芯片的RCK(存储时钟)管脚输出
      • sclk_out:74HC595芯片的SCK(移位时钟)管脚输出
      • sdio_out:74HC595芯片的SER(串行数据输入)管脚输出
  2. 模块实例化:
    • array_keyboard u1:实例化了一个名为u1array_keyboard模块,用于处理矩阵键盘输入。
    • key_decode u2:实例化了一个名为u2key_decode模块,用于解码按键并生成对应的数码管显示数据。
    • calculator calculator_inst:实例化了一个名为calculator_instcalculator模块,用于将解码后的数码管显示数据控制输出到74HC595芯片。
  3. 信号连接:
    • seg_dataseg_data_flag:用于传递解码后的数码管显示数据和有效标志,用来保证数据有效性。
    • key_outkey_pulse:用于处理从矩阵键盘输入得到的按键数据和按键脉冲信号。
  4. 代码功能:
    • array_keyboard模块处理矩阵键盘输入,并生成按键数据和按键脉冲信号。
    • key_decode模块解码按键数据,并生成对应的数码管显示数据和有效标志。
    • calculator模块控制74HC595芯片,将数码管显示数据输出到对应的管脚上,实现数码管显示。

 

 

 

STEP BaseBoard V4.0底板上的4×4矩阵键盘电路图如下: 

image.png

该电路图包括4根行线(ROW1、ROW2、ROW3、ROW4)和4根列线(COL1、COL2、COL3、COL4)。列线通过上拉电阻连接到VCC电压(3.3V)。矩阵按键的工作原理如下:

  • 4根行线由FPGA控制,可以拉高或拉低。
  • 4根列线则是输出线,它们的状态由4根行线的输入和按键状态共同决定,然后输出给FPGA。

在某一时刻,如果FPGA控制4根行线分别为ROW1=0、ROW2=1、ROW3=1、ROW4=1时,

  • 对于K1、K2、K3、K4按键:当按下时,对应的4根列线输出COL1=0、COL2=0、COL3=0、COL4=0;未按下时,对应的4根列线输出COL1=1、COL2=1、COL3=1、COL4=1。
  • 对于K5到K16之间的按键:无论按下与否,对应的4根列线输出COL1=1、COL2=1、COL3=1、COL4=1。

根据上述描述,只有在K1、K2、K3、K4按键被按下时,才会导致4根列线输出COL1=0、COL2=0、COL3=0、COL4=0;否则,输出为COL1=1、COL2=1、COL3=1、COL4=1。反过来,当FPGA检测到列线(COL1、COL2、COL3、COL4)中有低电平信号时,对应的K1、K2、K3、K4按键应该被按下了。

扫描的方式将这个过程分为4个时刻,对应4根行线中的一根被拉低。这样的循环扫描方式完成了对矩阵按键的全部扫描检测。程序中可以将这4个时刻映射到状态机的4个状态。

当然,这只是一行的情况,而我们有四行。将矩阵键盘的扫描周期分为4个时刻,对应4个状态,使得状态机在4个状态上循环跳转,最终通过扫描的方式获取矩阵键盘的操作状态。


image.png

扫描显示是一种节约I/O口资源的方法,适用于控制多个数码管的情况。在小脚丫底板上使用的是共阴极数码管中,扫描显示的原理如下:

在每个时刻,FPGA控制8根公共的段选接口输出对应数字1的数码管字库数据(例如8'h06,表示数字1的显示模式),同时只有1位数码管处于使能状态,其余数码管不显示。这通过控制数码管的位选信号来实现,例如第1位数码管为使能状态,其他位数码管不使能。

按照扫描的方式,总共分为8个时刻,每个时刻段选端口对应输出数码管需要显示的字库数据,位选端口则保持只有1位数码管处于使能状态。这8个时刻依次循环,当扫描频率足够高,则人眼看到的数码管显示是连续的,因为切换速度很快,我们会看到8个不同的数字在数码管上显示。

这种扫描显示方法有效地节约了I/O口资源,因为只需要8根段选信号和8根位选信号,共计16根信号,就可以控制多个数码管的显示,而不是每个数码管都需要独立的控制信号线。

74HC595是较为常用的串行转并行的芯片,内部集成了一个8位移位寄存器、一个存储器和8个三态缓冲输出。在最简单的情况下我们只需要控引脚输入得到8根引脚并行输出信号,我们使用3个I/O口控制两个级联的74HC595芯片,产生16路并行输出,连接到扫描显示的数码管上,完成数码管驱动任务。

参照给出的旧版本3.0的6个数码管电路和代码,我们需要增加两个数码管的显示。只需要增加两个状态,再在扫描中计算并加入对应的段选和位选即可。


 数码管驱动程序框图74hc595时序图.jpg74hc595逻辑图74hc595引脚功能.jpg

旧版本3.0的状态转换和时序

 

整体运算和处理的逻辑需要大量标志位,下面是部分变量。

reg [2:0] state;       // 定义状态寄存器,表示7个不同的状态

reg [7:0] num1, num2;  // 定义两个输入数字

reg [3:0] operator;     // 定义运算符

reg [14:0] result;      // 定义结果

reg [7:0] seg_dot_en;   //小数点标志位

reg seg_dot_en_flag;    //小数点显示标志位

reg negative_flag;         //负号标志位

reg div_error_flag;        //错误标志位

 

这是我在处理数据的一些变量,就不过多介绍了,主要是显示和计算逻辑处理。虽然这里的单个逻辑都不会很难,但是这里的逻辑很多,一定要有耐心。

    if (!rst_n_in || input_data == 4'd14) begin
state <= STATE_NUM1_INPUT1;
num1 <= 4'b0;
num2 <= 4'b0;
operator <= 4'b0;
result <= 8'b0;
negative_flag <=0 ;
div_error_flag <= 0;
// seg_dot_en <= 0 ;
seg_dot_en_flag <= 0;
end


数值显示逻辑我写的比较复杂,用了很多变量,主要是理清逻辑,考虑每一种情况:

always @(posedge clk) begin
seg_dot_en = 8'd0 ;
seg_data_en[3:0] = 4'b0000;//不用
seg_data_en[7] = 1'b1;

if(seg_dot_en_flag == 1)//小数点显示
begin
if(tens == 4'd0 && units == 4'd0) begin display_units_ago = hundreds; display_tens_ago = thousands; display_hundreds_ago = 4'd0; display_thousands_ago = 4'd0; end
else if(units == 4'd0) begin display_units_ago = tens; display_tens_ago = hundreds; display_hundreds_ago = thousands; display_thousands_ago = 4'd0; seg_dot_en[6]=1; end
else begin display_units_ago = units; display_tens_ago = tens; display_hundreds_ago = hundreds; display_thousands_ago = thousands; seg_dot_en[5]=1; end
// 十位显示
if (display_tens_ago == 4'd0 && display_hundreds_ago == 4'd0 && display_thousands_ago == 4'd0 && seg_dot_en[6]==0 && seg_dot_en[5]==0)
seg_data_en[6] = 1'b0;
else
seg_data_en[6] = 1'b1;

// 百位显示
if (display_hundreds_ago == 4'd0 && display_thousands_ago == 4'd0 && seg_dot_en[5]==0)
seg_data_en[5] = 1'b0;
else
seg_data_en[5] = 1'b1;

// 千位显示
if (display_thousands_ago == 4'd0)
seg_data_en[4] = 1'b0;
else
seg_data_en[4] = 1'b1;
end
else//一般情况显示
begin
// 十位显示
if (tens == 4'd0 && hundreds == 4'd0 && thousands == 4'd0)
seg_data_en [6] = 1'b0;
else
seg_data_en[6] = 1'b1;
// 百位显示
if (hundreds == 4'd0 && thousands == 4'd0)
seg_data_en [5] = 1'b0;
else
seg_data_en [5] = 1'b1;
// 千位显示
if (thousands == 4'd0)
seg_data_en[4] = 1'b0;
else
seg_data_en[4] = 1'b1;
end

if(negative_flag==1)//最后阶段 && input_data == 4'd15 && state==STATE_NUM1_INPUT1)(state==STATE_OPERATOR_FINAL || )
begin
display_units = units;
if(seg_data_en[6:4] == 3'b000) begin display_tens = 4'd10; seg_data_en[6] = 1'b1; display_hundreds = hundreds; display_thousands = thousands; end
else if(seg_data_en[5:4] == 2'b00) begin display_tens = tens; display_hundreds = 4'd10; seg_data_en [5] = 1'b1; display_thousands = thousands; end
else if(seg_data_en[4:4] == 1'b0) begin display_tens = tens; display_hundreds = hundreds; display_thousands = 4'd10; seg_data_en[4] = 1'b1; end
end
else if(seg_dot_en_flag)//触发小数情况
begin
display_units = display_units_ago;
display_tens = display_tens_ago;
display_hundreds = display_hundreds_ago;
display_thousands = display_thousands_ago;
end
else if(div_error_flag)//(display_units_ago==4'b1000 && display_tens_ago==4'b1000 && display_hundreds_ago==4'b1000 && display_units_ago==4'b1000) //触发小数非法情况
begin
display_units = 4'd10;
display_tens = 4'd10;
display_hundreds = 4'd10;
display_thousands = 4'd10;
end
else
begin
display_units = units;
display_tens = tens;
display_hundreds = hundreds;
display_thousands = thousands;
end
seg_data_en_final = seg_data_en;
seg_dot_en_final = seg_dot_en;
end


BCD码的计算使用的是加3移位的方式。

module bcd_d(
input wire [13:0] binary,
output wire [3:0] g,
output wire [3:0] s,
output wire [3:0] b,
output wire [3:0] q
);

//***********************//
/*
* z 作为存储 BCD 码和 二进制码的寄存器
* 如果输入为 8 位,那么 z 需要的长度为
* 0xFF = 255 ---> 10-0101-0101 +++ ????-????
* 总共 18 位
*/
reg [29:0] z;
//***********************//

always @ (*)
begin
z = 29'b0; //置 0
z[13:0] = binary; //读入低 8 位
repeat (14) //重复 8 次
begin
if(z[17:14]>4) //大于 4 就加 3
z[17:14] = z[17:14] + 2'b11;
if(z[21:18]>4)
z[21:18] = z[21:18] + 2'b11;
if(z[25:22]>4)
z[25:22] = z[25:22] + 2'b11;
if(z[29:26]>4)
z[29:26] = z[29:26] + 2'b11;
z[29:1] = z[28:0]; //左移一位
end
end
assign q = z[29:26];
assign b = z[25:22]; //输出 BCD 码
assign s = z[21:18];
assign g = z[17:14];

endmodule


需要格外注意的是,中间的还许多情况需要考虑,例如除数为0输出错误,对数据显示的处理,但是这些主要是细节和逻辑,将它们理清还是不简单的。我的方法是先对它们单独处理,再统一逻辑,将相同的问题归类。例如,去除先导0的问题是4种运算的共同问题,但是小数点后的0只有除法出现,要单独处理。小数点只出现在除法中,乘法最大99*99。


6.仿真波形图

image.png image.png

7.FPGA的资源利用说明

 

Design Summary

   Number of registers:    258 out of  4635 (6%)

      PFU registers:          258 out of  4320 (6%)

      PIO registers:            0 out of   315 (0%)

   Number of SLICEs:      1564 out of  2160 (72%)

      SLICEs as Logic/ROM:   1564 out of  2160 (72%)

      SLICEs as RAM:            0 out of  1620 (0%)

      SLICEs as Carry:        626 out of  2160 (29%)

   Number of LUT4s:        3111 out of  4320 (72%)

      Number used as logic LUTs:        1859

      Number used as distributed RAM:     0

      Number used as ripple logic:      1252

      Number used as shift registers:     0

   Number of PIO sites used: 13 + 4(JTAG) out of 105 (16%)

   Number of block RAMs:  0 out of 10 (0%)

   Number of GSRs:        1 out of 1 (100%)

   EFB used :        No

   JTAG used :       No

   Readback used :   No

   Oscillator used : No

   Startup used :    No

   POR :             On

   Bandgap :         On

   Number of Power Controller:  0 out of 1 (0%)

   Number of Dynamic Bank Controller (BCINRD):  0 out of 6 (0%)

   Number of Dynamic Bank Controller (BCLVDSO):  0 out of 1 (0%)

   Number of DCCA:  0 out of 8 (0%)

   Number of DCMA:  0 out of 2 (0%)

   Number of PLLs:  0 out of 2 (0%)

   Number of DQSDLLs:  0 out of 2 (0%)

   Number of CLKDIVC:  0 out of 4 (0%)

   Number of ECLKSYNCA:  0 out of 4 (0%)

   Number of ECLKBRIDGECS:  0 out of 2 (0%)

   Notes:-

      1. Total number of LUT4s = (Number of logic LUT4s) + 2*(Number of

     distributed RAMs) + 2*(Number of ripple logic)

      2. Number of logic LUT4s does not include count of distributed RAM and

     ripple logic.

   Number of clocks:  2

     Net clk_c: 141 loads, 141 rising, 0 falling (Driver: PIO clk )

 

     Net clk_200hz: 24 loads, 0 rising, 24 falling (Driver: u1/clk_200hz_38 )

   Number of Clock Enables:  28

     Net clk_c_enable_142: 5 loads, 5 LSLICEs

     Net u1/clk_200hz_N_24_enable_42: 6 loads, 6 LSLICEs

     Net u1/clk_200hz_N_24_enable_48: 6 loads, 6 LSLICEs

     Net u1/clk_200hz_N_24_enable_39: 6 loads, 6 LSLICEs

     Net u1/clk_200hz_N_24_enable_46: 6 loads, 6 LSLICEs

     Net calculator_inst/clk_c_enable_124: 5 loads, 5 LSLICEs

     Net calculator_inst/clk_c_enable_117: 4 loads, 4 LSLICEs

     Net calculator_inst/clk_c_enable_110: 13 loads, 13 LSLICEs

     Net calculator_inst/clk_c_enable_97: 3 loads, 3 LSLICEs

     Net calculator_inst/clk_c_enable_68: 10 loads, 10 LSLICEs

     Net calculator_inst/clk_c_enable_18: 1 loads, 1 LSLICEs

     Net calculator_inst/clk_c_enable_24: 1 loads, 1 LSLICEs

     Net calculator_inst/clk_c_enable_27: 1 loads, 1 LSLICEs

     Net calculator_inst/clk_c_enable_77: 1 loads, 1 LSLICEs

     Net seg_data_flag: 3 loads, 3 LSLICEs

     Net calculator_inst/unsigned_mul_inst/clk_c_enable_93: 14 loads, 14 LSLICEs

    

     Net calculator_inst/unsigned_mul_inst/clk_c_enable_41: 7 loads, 7 LSLICEs

     Net calculator_inst/unsigned_mul_inst/state_1_N_483_1: 1 loads, 1 LSLICEs

     Net calculator_inst/unsigned_mul_inst/state_1: 14 loads, 14 LSLICEs

     Net calculator_inst/Segment_scan_inst/clk_c_enable_137: 10 loads, 10

     LSLICEs

     Net calculator_inst/Segment_scan_inst/clk_c_enable_89: 4 loads, 4 LSLICEs

     Net calculator_inst/Segment_scan_inst/clk_c_enable_83: 1 loads, 1 LSLICEs

     Net calculator_inst/Segment_scan_inst/state_1: 2 loads, 2 LSLICEs

     Net calculator_inst/Segment_scan_inst/clk_c_enable_86: 1 loads, 1 LSLICEs

     Net calculator_inst/Segment_scan_inst/clk_c_enable_87: 1 loads, 1 LSLICEs

     Net calculator_inst/Segment_scan_inst/clk_c_enable_88: 1 loads, 1 LSLICEs

     Net calculator_inst/Segment_scan_inst/clk_c_enable_90: 1 loads, 1 LSLICEs

     Net u2/seg_data_3__N_149: 2 loads, 2 LSLICEs

   Number of LSRs:  16

     Net clk_200hz_N_26: 9 loads, 9 LSLICEs

     Net u1/n12763: 1 loads, 1 LSLICEs

     Net calculator_inst/n24509: 1 loads, 1 LSLICEs

     Net calculator_inst/n24674: 33 loads, 33 LSLICEs

     Net calculator_inst/n24496: 1 loads, 1 LSLICEs

     Net calculator_inst/n11439: 1 loads, 1 LSLICEs

     Net calculator_inst/seg_data_en_final_7_N_237_4: 1 loads, 1 LSLICEs

     Net calculator_inst/n11936: 1 loads, 1 LSLICEs

     Net calculator_inst/unsigned_mul_inst/n12718: 18 loads, 18 LSLICEs

     Net calculator_inst/unsigned_mul_inst/n12720: 2 loads, 2 LSLICEs

     Net calculator_inst/unsigned_mul_inst/state_0: 1 loads, 1 LSLICEs

     Net calculator_inst/Segment_scan_inst/n12724: 6 loads, 6 LSLICEs

     Net calculator_inst/Segment_scan_inst/n12729: 2 loads, 2 LSLICEs

     Net calculator_inst/Segment_scan_inst/n12775: 1 loads, 1 LSLICEs

     Net calculator_inst/Segment_scan_inst/n12767: 8 loads, 8 LSLICEs

     Net calculator_inst/Segment_scan_inst/cnt_9__N_1118: 6 loads, 6 LSLICEs

   Number of nets driven by tri-state buffers:  0

   Top 10 highest fanout non-clock nets:

     Net num2_6: 116 loads

     Net num2_7: 99 loads

     Net num2_3: 92 loads

     Net num2_4: 92 loads

     Net num2_0: 80 loads

 

     Net n4231: 77 loads

     Net n4243: 77 loads

     Net n4252: 77 loads

     Net n4253: 77 loads

     Net n4255: 77 loads

 

 

 

 

   Number of warnings:  0

   Number of errors:    0

寄存器:共使用了258个寄存器,占4635个寄存器的6%。其中,PFU寄存器占用了258个,PIO寄存器未使用。

 

SLICEs:共使用了1564个SLICEs,占2160个SLICEs的72%。其中,SLICEs主要用作逻辑和ROM,占用了1564个;作为RAM和Carry的SLICEs均未使用。

 

LUT4s:共使用了3111个LUT4s,占4320个LUT4s的72%。其中,作为逻辑LUTs的个数为1859个,作为Ripple Logic的个数为1252个,没有作为分布式RAM和移位寄存器的LUT4s。

 

PIO sites:共使用了13个PIO sites,占105个PIO sites的16%。4个JTAG PIO sites也被使用。

 

其他资源(如RAM、PLLs等)均未使用或仅使用了一部分,占比很低。

 

综合来看,该设计对SLICEs和LUT4s的利用率较高,而对寄存器和PIO sites的利用率较低。没有出现资源占用上的警告或错误,整体资源利用比较合理。

8.演示视频

见开头

9.代码附件

附件下载
lab1_type_system.zip
Diamond 工程文件
团队介绍
河南大学2021级电子信息科学与技术朱子豪
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号