• FPGA——SPI总线控制flash(3)含代码


    前面几篇详细 介绍了SPI的原理,并且实现了对flash芯片的写使能,读状态,擦除,页读,页写

    ​​​​​​FPGA——SPI总线详解(概念)_居安士的博客-CSDN博客_fpga芯片

    FPGA——SPI总线控制flash(1)(含代码)_居安士的博客-CSDN博客

    FPGA——SPI总线控制flash(2)(含代码)_居安士的博客-CSDN博客

    接下来我们需要将各个模块汇合到一起:

    目录

    总线应答模块

    SPI控制模块

    SPI_FLASH总模块 


    总线应答模块

    之前的总线应答都是写在TB文件里面,现在所以模块写齐了之后,需要将多个模块的总线应答汇总在一个模块里,根据请求总线的信号,回复ack。当接收到总线同意通信的信号后,将spi_cs ,spi_clk ,spi_dout ,spi_din传递给总线,传递结束后,请求总线关闭,回复ack。

    总线控制模块需要完成的任务:

    1. 控制5个模块(写使能,读状态,擦除,页写,页读)的总线应答
    2. 根据模块给spi_bus信号(spi_cs ,spi_clk ,spi_dout ,spi_din)
    3. 流程图如下:

     0:给5个模块分配标号

    1:在请求总线开放信号到来时,根据标号给总线应答开放信号

    2:在请求总线关闭信号到来时,采请求总线下降沿,给总线应答关闭信号

    总线一次只能通信一个模块,根据分配的标号,将模块输出的spi_cs ,spi_clk ,spi_dout给总线,总线输出的spi_din给模块

    1. module mux_top(
    2. input clk,
    3. input reset,
    4. //写使能
    5. input mux_wren ,
    6. output reg mux_wren_done,
    7. input spi_cs1 ,
    8. input spi_clk1 ,
    9. input spi_dout1 ,
    10. output reg spi_din1 ,
    11. //读状态
    12. input mux_status ,
    13. output reg mux_status_done,
    14. input spi_cs2 ,
    15. input spi_clk2 ,
    16. input spi_dout2 ,
    17. output reg spi_din2 ,
    18. //擦除
    19. input mux_earse ,
    20. output reg mux_earse_done ,
    21. input spi_cs3 ,
    22. input spi_clk3 ,
    23. input spi_dout3 ,
    24. output reg spi_din3 ,
    25. //页写
    26. input mux_write ,
    27. output reg mux_write_done ,
    28. input spi_cs4 ,
    29. input spi_clk4 ,
    30. input spi_dout4 ,
    31. output reg spi_din4 ,
    32. //页读
    33. input mux_read ,
    34. output reg mux_read_done ,
    35. input spi_cs5 ,
    36. input spi_clk5 ,
    37. input spi_dout5 ,
    38. output reg spi_din5 ,
    39. //总线bus
    40. input spi_din ,
    41. output spi_cs ,
    42. output spi_clk ,
    43. output spi_dout
    44. );
    45. //输入打一拍
    46. reg mux_wren_dly ;
    47. reg mux_status_dly;
    48. reg mux_earse_dly ;
    49. reg mux_write_dly ;
    50. reg mux_read_dly ;
    51. always@(posedge clk)begin
    52. if(reset)begin
    53. mux_wren_dly <=1'd0;
    54. mux_status_dly <=1'd0;
    55. mux_earse_dly <=1'd0;
    56. mux_write_dly <=1'd0;
    57. mux_read_dly <=1'd0;
    58. end
    59. else begin
    60. mux_wren_dly <=mux_wren ;
    61. mux_status_dly <=mux_status;
    62. mux_earse_dly <=mux_earse ;
    63. mux_write_dly <=mux_write ;
    64. mux_read_dly <=mux_read ;
    65. end
    66. end
    67. reg [2:0] mux_mode;//选择写、读、擦除等模式
    68. reg [1:0] state;//状态机
    69. always@(posedge clk)begin
    70. if(reset)begin
    71. mux_mode<=1'd0;
    72. state<=2'd0;
    73. end
    74. else begin
    75. mux_wren_done <=1'd0;
    76. mux_status_done<=1'd0;
    77. mux_earse_done <=1'd0;
    78. mux_write_done <=1'd0;
    79. mux_read_done <=1'd0;
    80. case(state)
    81. 2'd0:begin //根据请求总线的信号选择当前模式
    82. if(mux_wren_dly)begin
    83. mux_mode<=3'd1;
    84. state<=2'd1;
    85. end
    86. else if(mux_status_dly)begin
    87. mux_mode<=3'd2;
    88. state<=2'd1;
    89. end
    90. else if(mux_earse_dly)begin
    91. mux_mode<=3'd3;
    92. state<=2'd1;
    93. end
    94. else if(mux_write_dly)begin
    95. mux_mode<=3'd4;
    96. state<=2'd1;
    97. end
    98. else if(mux_read_dly)begin
    99. mux_mode<=3'd5;
    100. state<=2'd1;
    101. end
    102. else begin
    103. mux_mode<=3'd0;
    104. state<=2'd0;
    105. end
    106. end
    107. 2'd1:begin//回复同意开启总线
    108. case(mux_mode)
    109. 3'd1:mux_wren_done<=1'd1;
    110. 3'd2:mux_status_done<=1'd1;
    111. 3'd3:mux_earse_done<=1'd1;
    112. 3'd4:mux_write_done<=1'd1;
    113. 3'd5:mux_read_done<=1'd1;
    114. endcase
    115. state<=2'd2;
    116. end
    117. 2'd2:begin//回复关闭总线
    118. if(~mux_wren&&mux_wren_dly)begin//在mux_wren下降沿
    119. mux_wren_done<=1'd1;
    120. state<=2'd0;
    121. end
    122. else if(~mux_status&&mux_status_dly)begin
    123. mux_status_done<=1'd1;
    124. state<=2'd0;
    125. end
    126. else if(~mux_earse&&mux_earse_dly)begin
    127. mux_earse_done<=1'd1;
    128. state<=2'd0;
    129. end
    130. else if(~mux_write&&mux_write_dly)begin
    131. mux_write_done<=1'd1;
    132. state<=2'd0;
    133. end
    134. else if(~mux_read&&mux_read_dly)begin
    135. mux_read_done<=1'd1;
    136. state<=2'd0;
    137. end
    138. end
    139. endcase
    140. end
    141. end
    142. //总线经过选择之后,发送cs
    143. assign spi_cs=(mux_mode==3'd1)?spi_cs1
    144. (mux_mode==3'd2)?spi_cs2
    145. (mux_mode==3'd3)?spi_cs3
    146. (mux_mode==3'd4)?spi_cs4
    147. (mux_mode==3'd5)?spi_cs5
    148. 1'd1;
    149. //总线经过选择之后,发送clk
    150. assign spi_clk =(mux_mode==3'd1)? spi_clk1 :
    151. (mux_mode==3'd2)? spi_clk2 :
    152. (mux_mode==3'd3)? spi_clk3 :
    153. (mux_mode==3'd4)? spi_clk4 :
    154. (mux_mode==3'd5)? spi_clk5 :
    155. 1'd0;
    156. //总线经过选择之后,发送dout
    157. assign spi_dout =(mux_mode==3'd1)? spi_dout1 :
    158. (mux_mode==3'd2)? spi_dout2 :
    159. (mux_mode==3'd3)? spi_dout3 :
    160. (mux_mode==3'd4)? spi_dout4 :
    161. (mux_mode==3'd5)? spi_dout5 :
    162. 1'd1;
    163. //把输出的din给模块
    164. always@(*)begin
    165. if(mux_mode==3'd2)begin
    166. spi_din2=spi_din;
    167. end
    168. else if (mux_mode==3'd5)begin
    169. spi_din5=spi_din;
    170. end
    171. else begin
    172. spi_din2=1'd1;
    173. spi_din5=1'd1;
    174. end
    175. end
    176. endmodule

    SPI控制模块

    spi控制模块需要完成的任务是:

    1 定义操作启动信号,利用操作启动信号来控制每种模块的启动信号

    定义操作信号(0~9),根据操作指令来控制spi里面5个模块(写使能,读状态,擦除,页写,页读)的每种指令

    2 输入想要控制spi里面模块的地址和突发长度

    3 输出写类完成信号(写使能,擦除,页写)和读类完成信号(读状态,页读),

    以及总线忙信号和总线不忙信号

    4 控制写使能模块:输出启动写使能信号,输出写使能指令

    5 控制读状态模块:输出启动读状态信号,输出读状态指令,输入读状态数据

    6 控制擦除模块:输出启动擦除信号,输出擦除指令,输出擦除的地址

    7 控制页写模块:输出页写擦除信号,输出页写指令,输出页写的地址,输出页写的突发长度

    8 控制页读模块:输出页读擦除信号,输出页读指令,输出页读的地址,输出页读的突发长度,输入页读的数据

     编写代码如下:

    1. module spi_control(
    2. input clk,
    3. input reset,
    4. //操作信号
    5. input op_en ,//启动操作
    6. input [3:0] op_cmd ,//0:写使能04h 1:写使能06h 2:读状态05h 3:读状态35h 4:擦除20h 5:擦除52h 6:擦除D8h 7:擦除整片 8:页写 9:页读
    7. input [23:0]op_addr ,
    8. input [8:0] op_brust_length,
    9. output reg wr_cmd_done , //返回写类done:写使能,页写,擦除
    10. output reg rd_cmd_done ,//返回读类done:读状态寄存器,页读
    11. output reg rd_cmd_fail ,//读状态寄存器为忙
    12. output reg status_ok ,
    13. //写使能
    14. output reg start_wr ,
    15. output reg wren_cmd ,
    16. input wren_done ,
    17. //读状态
    18. output reg start_status ,
    19. output reg status_cmd ,
    20. input[7:0] status_data ,
    21. input status_done ,
    22. //擦除
    23. output reg start_erase ,
    24. output reg [1:0] erase_cmd ,
    25. output reg [23:0]erase_addr ,
    26. input erase_done ,
    27. //页写
    28. output reg start_write ,
    29. output reg [8:0] wr_brust_length ,
    30. output reg [23:0]write_addr ,
    31. input write_done ,
    32. //页读
    33. output reg start_read ,
    34. output reg [8:0] rd_brust_length ,
    35. output reg [23:0]read_addr ,
    36. input read_done
    37. );
    38. //输出start信号和对应的命令
    39. always@(posedge clk)begin
    40. if(reset)begin
    41. wr_cmd_done <=1'd0;
    42. rd_cmd_done <=1'd0;
    43. rd_cmd_fail <=1'd0;
    44. status_ok <=1'd0;
    45. start_wr <=1'd0;
    46. wren_cmd <=1'd0;
    47. start_status <=1'd0;
    48. status_cmd <=1'd0;
    49. start_erase <=1'd0;
    50. erase_cmd <=2'd0;
    51. erase_addr <=24'd0;
    52. start_write <=1'd0;
    53. wr_brust_length<=8'd0;
    54. write_addr <=24'd0;
    55. start_read <=1'd0;
    56. rd_brust_length<=8'd0;
    57. read_addr <=24'd0;
    58. end
    59. else begin
    60. if(op_en)begin
    61. case(op_cmd)
    62. 4'd0:begin//写使能04h
    63. start_wr<=1'd1;
    64. wren_cmd<=1'd0;
    65. end
    66. 4'd1:begin//写使能06h
    67. start_wr<=1'd1;
    68. wren_cmd<=1'd1;
    69. end
    70. 4'd2:begin//读状态05h
    71. start_status<=1'd1;
    72. status_cmd<=1'd0;
    73. end
    74. 4'd3:begin//读状态35h
    75. start_status<=1'd1;
    76. status_cmd<=1'd1;
    77. end
    78. 4'd4:begin//擦除20h
    79. start_erase<=1'd1;
    80. erase_cmd<=2'd0;
    81. erase_addr <=op_addr;
    82. end
    83. 4'd5:begin//擦除52h
    84. start_erase<=1'd1;
    85. erase_cmd<=2'd1;
    86. erase_addr <=op_addr;
    87. end
    88. 4'd6:begin//擦除D8h
    89. start_erase<=1'd1;
    90. erase_cmd<=2'd2;
    91. erase_addr <=op_addr;
    92. end
    93. 4'd7:begin//擦除整片
    94. start_erase<=1'd1;
    95. erase_cmd<=2'd3;
    96. erase_addr <=op_addr;
    97. end
    98. 4'd8:begin//页写
    99. start_write<=1'd1;
    100. wr_brust_length<=op_brust_length;
    101. write_addr <=op_addr;
    102. end
    103. 4'd9:begin//页读
    104. start_read<=1'd1;
    105. rd_brust_length<=op_brust_length;
    106. read_addr <=op_addr;
    107. end
    108. endcase
    109. end
    110. else begin
    111. start_wr<=1'd0;
    112. start_status<=1'd0;
    113. start_erase<=1'd0;
    114. start_write<=1'd0;
    115. start_read<=1'd0;
    116. end
    117. end
    118. end
    119. always@(posedge clk)begin//输出写类完成信号
    120. if(wren_done||erase_done||write_done)begin
    121. wr_cmd_done<=1'b1;
    122. end
    123. else begin
    124. wr_cmd_done<=1'b0;
    125. end
    126. end
    127. always@(posedge clk)begin//输出读类完成信号
    128. if(status_done||read_done)begin
    129. rd_cmd_done<=1'b1;
    130. end
    131. else begin
    132. rd_cmd_done<=1'b0;
    133. end
    134. end
    135. always@(posedge clk)begin
    136. if((status_data[0]==1'b0)&&status_done)begin//读状态总线不忙
    137. status_ok<=1'b1;
    138. end
    139. else begin
    140. status_ok<=1'b0;
    141. end
    142. end
    143. always@(posedge clk)begin
    144. if((status_data[0]==1) &&status_done)begin//status_data_dly=1表示状态忙
    145. rd_cmd_fail<=1'b1;
    146. end
    147. else begin
    148. rd_cmd_fail<=1'b0;
    149. end
    150. end
    151. endmodule

    SPI_FLASH总模块 

    接下来把所有的模块例化到一起;

    1. module spi_flash(
    2. input clk,
    3. input reset ,
    4. input clk_wr ,
    5. input clk_rd ,
    6. //控制信号
    7. input op_en ,
    8. input [3:0] op_cmd ,
    9. input [23:0] op_addr ,
    10. input [8:0] op_brust_length,
    11. //done信号
    12. output wr_cmd_done,
    13. output rd_cmd_done,
    14. output rd_cmd_fail,
    15. //bus
    16. output spi_cs ,
    17. output spi_clk ,
    18. output spi_dout,
    19. input spi_din
    20. );
    21. // 写使能
    22. wire start_wr ;
    23. // 读状态
    24. wire start_status ;
    25. //擦
    26. wire start_erase ;
    27. wire erase_cmd_out ;
    28. //写
    29. wire start_write ;
    30. wire din ;
    31. //计时器
    32. wire time_done ;
    33. wire write_en ;
    34. wire erase_en ;
    35. wire erase_cmd ;
    36. //spi控制
    37. wire wren_cmd ;
    38. wire wren_done ;
    39. wire status_cmd ;
    40. wire status_data ;
    41. wire status_done ;
    42. wire erase_addr ;
    43. wire erase_done ;
    44. wire write_addr ;
    45. wire write_done ;
    46. wire read_addr ;
    47. wire read_done ;
    48. //总线模式选择
    49. wire mux_wren ;
    50. wire mux_wren_done ;
    51. wire spi_cs1 ;
    52. wire spi_clk1 ;
    53. wire spi_dout1 ;
    54. wire spi_din1 ;
    55. wire mux_status ;
    56. wire mux_status_done ;
    57. wire spi_cs2 ;
    58. wire spi_clk2 ;
    59. wire spi_dout2 ;
    60. wire spi_din2 ;
    61. wire mux_earse ;
    62. wire mux_earse_done ;
    63. wire spi_cs3 ;
    64. wire spi_clk3 ;
    65. wire spi_dout3 ;
    66. wire spi_din3 ;
    67. wire mux_write ;
    68. wire mux_write_done ;
    69. wire spi_cs4 ;
    70. wire spi_clk4 ;
    71. wire spi_dout4 ;
    72. wire spi_din4 ;
    73. wire mux_read ;
    74. wire mux_read_done ;
    75. wire spi_cs5 ;
    76. wire spi_clk5 ;
    77. wire spi_dout5 ;
    78. wire spi_din5 ;
    79. //-------------------------------------------写使能
    80. wr_enable inst_wr_enable(
    81. .clk (clk),
    82. .reset (reset) ,
    83. .start_wr (start_wr) ,//开始写使能
    84. .wren_cmd (wren_cmd) ,//指令(0/1)
    85. .mux_wren_done (mux_wren_done) ,//总线返回
    86. .wren_done (wren_done) ,//写使能完成
    87. .mux_wren (mux_wren) ,//请求总线
    88. .spi_cs (spi_cs1) ,//总线发送的片选线
    89. .spi_clk (spi_clk1) ,//总线发送的clk线
    90. .spi_dout (spi_dout1) //总线发送的DI线
    91. );
    92. //----------------------------------------读状态
    93. read_status inst_read_status(
    94. .clk (clk),
    95. .reset (reset) ,
    96. .start_status (start_status) ,//开始读状态
    97. .status_cmd (status_cmd) ,//读状态指令
    98. .mux_status_done(mux_status_done) ,//总线返回
    99. .spi_din (spi_din2) ,//由总线输入
    100. .mux_status (mux_status) ,//请求总线
    101. .status_done (status_done) ,//读状态完成
    102. .status_data (status_data) ,//读状态返回8位数据
    103. .spi_cs (spi_cs2) ,//片选信号
    104. .spi_clk (spi_clk2) ,//时钟信号
    105. .spi_dout (spi_dout2) //总线输入
    106. );
    107. //--------------------------------------------------擦除
    108. erase_top erase_top(
    109. .clk (clk) ,
    110. .reset (reset) ,
    111. .start_erase (start_erase) ,
    112. .erase_cmd (erase_cmd) ,//擦除指令
    113. .erase_addr (erase_addr) ,//擦除地址
    114. .mux_erase_done (mux_erase_done) ,
    115. .time_done (time_done) ,//计时完成
    116. .mux_erase (mux_erase) ,
    117. .spi_cs (spi_cs3) ,
    118. .spi_clk (spi_clk3) ,
    119. .spi_dout (spi_dout3) ,
    120. .erase_done (erase_done) ,
    121. .erase_en (erase_en)
    122. );
    123. //-----------------------------------------------------页写
    124. write_top inst_write_top(
    125. .clk (clk),
    126. .reset (reset) ,
    127. .start_write (start_write) ,//启动页写
    128. //fifo部分驱动接口
    129. .brust_length (brust_length) ,//突发长度
    130. .write_addr (write_addr) ,//初始地址
    131. .clk_wr (clk_wr) ,//fifo时钟
    132. .wr_fifo_en (wr_fifo_en) ,//fifo写使能
    133. .din (wr_fifo_data) ,//写数据
    134. .wr_fifo_empty (wr_fifo_empty) ,//fifo空
    135. .wr_fifo_full (wr_fifo_full) ,//fifo满
    136. //总线接口
    137. .spi_cs (spi_cs4) ,//spi片选
    138. .spi_clk (spi_clk4) ,//spi时钟
    139. .spi_dout (spi_dout4) ,//spi输出
    140. .mux_write (mux_write) ,//请求总线
    141. .mux_write_done (mux_write_done) ,//总线应答
    142. .write_done (write_done) ,//页写完成
    143. //计时器
    144. .time_done (time_done) ,//计时完成
    145. .write_en (write_en) //启动计时
    146. );
    147. //-------------------------------------------页读
    148. read_top inst_read_top(
    149. .clk (clk),
    150. .reset (reset) ,
    151. .rd_clk (rd_clk) ,
    152. .start_read (start_read) ,//开始页读
    153. .read_addr (read_addr) , //页读地址
    154. .brust_length (brust_length) ,//突发长度
    155. .rd_fifo_en (rd_fifo_en) ,//fifo读使能
    156. .rd_fifo_data (rd_fifo_data) ,//从fifo读出的数据
    157. .rd_fifo_empty (rd_fifo_empty) ,
    158. .rd_fifo_full (rd_fifo_full) ,
    159. .spi_cs (spi_cs5) ,
    160. .spi_clk (spi_clk5) ,
    161. .spi_dout (spi_dout5) ,
    162. .spi_din (spi_din5) ,
    163. .mux_read (mux_read) ,
    164. .mux_read_done (mux_read_done) ,
    165. .read_done (read_done)
    166. );
    167. //-------------------------------------------time_top模块
    168. erase_time inst_erase_time(
    169. .clk (clk),
    170. .reset (reset),
    171. .write_en (write_en),//页写计时
    172. .erase_en (erase_en),//擦除计时
    173. .erase_cmd (erase_cmd),//擦除模式选择
    174. .time_done (time_done)
    175. );
    176. //------------------------------------------spi_control模块
    177. spi_control inst_spi_control(
    178. .clk (clk),
    179. .reset (reset),
    180. //操作信号
    181. .op_en (op_en),
    182. .op_cmd (op_cmd),//0:写使能04h 1:写使能06h 2:读状态05h 3:读状态35h 4:擦除20h 5:擦除52h 6:擦除D8h 7:擦除整片 8:页写 9:页读
    183. .op_addr (op_addr),
    184. .op_brust_length(op_brust_length),
    185. .wr_cmd_done (wr_cmd_done), //返回写类done:写使能,页写,擦除
    186. .rd_cmd_done (rd_cmd_done),//返回读类done:读状态寄存器,页读
    187. .rd_cmd_fail (rd_cmd_fail),//读状态寄存器为忙
    188. //写使能
    189. .start_wr (start_wr) ,
    190. .wren_cmd (wren_cmd) ,
    191. .wren_done (wren_done) ,
    192. //读状态
    193. .start_status (start_status) ,
    194. .status_cmd (status_cmd) ,
    195. .status_data (status_data) ,
    196. .status_done (status_done) ,
    197. //擦除
    198. .start_erase (start_erase) ,
    199. .erase_cmd (erase_cmd) ,
    200. .erase_addr (erase_addr) ,
    201. .erase_done (erase_done) ,
    202. //页写
    203. .start_write (start_write) ,
    204. .wr_brust_length (wr_brust_length) ,
    205. .write_addr (write_addr) ,
    206. .write_done (write_done) ,
    207. //页读
    208. .start_read (start_read) ,
    209. .rd_brust_length(rd_brust_length) ,
    210. .read_addr (read_addr) ,
    211. .read_done (read_done)
    212. );
    213. //-------------------------------------------总线交互模块
    214. mux_top mux_top(
    215. .clk (clk) ,
    216. .reset (reset) ,
    217. //写使能
    218. .mux_wren (mux_wren) ,
    219. .mux_wren_done (mux_wren_done) ,
    220. .spi_cs1 (spi_cs1) ,
    221. .spi_clk1 (spi_clk1) ,
    222. .spi_dout1 (spi_dout1) ,
    223. .spi_din1 (spi_din1 ) ,
    224. //读状态
    225. .mux_status (mux_status) ,
    226. .mux_status_done (mux_status_done) ,
    227. .spi_cs2 (spi_cs2) ,
    228. .spi_clk2 (spi_clk2) ,
    229. .spi_dout2 (spi_dout2) ,
    230. .spi_din2 (spi_din2) ,
    231. //擦除
    232. .mux_earse (mux_earse) ,
    233. .mux_earse_done (mux_earse_done) ,
    234. .spi_cs3 (spi_cs3) ,
    235. .spi_clk3 (spi_clk3) ,
    236. .spi_dout3 (spi_dout3) ,
    237. .spi_din3 (spi_din3) ,
    238. //页写
    239. .mux_write (mux_write) ,
    240. .mux_write_done (mux_write_done) ,
    241. .spi_cs4 (spi_cs4) ,
    242. .spi_clk4 (spi_clk4) ,
    243. .spi_dout4 (spi_dout4) ,
    244. .spi_din4 (spi_din4) ,
    245. //页读
    246. .mux_read (mux_read) ,
    247. .mux_read_done (mux_read_done) ,
    248. .spi_cs5 (spi_cs5) ,
    249. .spi_clk5 (spi_clk5) ,
    250. .spi_dout5 (spi_dout5) ,
    251. .spi_din5 (spi_din5) ,
    252. //总线bus
    253. .spi_din (spi_din) ,
    254. .spi_cs (spi_cs) ,
    255. .spi_clk (spi_clk) ,
    256. .spi_dout (spi_dout)
    257. );
    258. endmodule

  • 相关阅读:
    SVN+Gitee配置版本控制库
    ElasticSearch复合查寻
    Ai-WB2系列模组linux开发环境搭建
    RabbitMQ的常见工作模式
    【Python学习篇】Python基础入门学习——Python基础语法(二)
    多态原理之虚函数表VBTL
    论文基础常识摘录
    前端文件流相关
    访问一次网站的全过程
    作为一面面试官,如何考察候选人
  • 原文地址:https://blog.csdn.net/weixin_46188211/article/details/125947775