第三章 SA8775P GPU深度解析:为什么汽车需要1.3TFLOPS GPU? ——从3D智能座舱、多屏显示、AR HUD,到未来汽车图形计算全面拆解
2026/8/13 12:26:25
数据位宽,数据帧数,时钟极性,SPI速率可参数化修改。多线模式实际工作未涉及,没有调过,感兴趣可以自己修改。
SPI速率最大为系统时钟的1/2。
//============================================================================== // Module: spi_tx // Description: // Author: wmm // Email: wmm246@qq.com // Date: 2026 //============================================================================== `timescale 1ns / 1ps module spi_tx #( parameter DATA_WIDTH = 256, // 单帧数据位宽 parameter FRAME_NUM = 4, // 最大帧数 parameter CPOL = 0, // 时钟极性:0-空闲低,1-空闲高 parameter CPHA = 0, // 时钟相位:0-第一个边沿发数,1-第二个边沿发数 parameter N = 1, // MOSI并行输出位宽(1, 2, 4, 8...) parameter MODE = 1, // 发送顺序:0-先发低位(LSB first),1-先发高位(MSB first) parameter CLK_DIV_FACTOR = 10, // 时钟分频系数。最大速率为系统时钟1/2。 parameter WAIT_TIME = 20 // CS拉低后等待数据时间,单位为系统时钟周期数 )( // 基础控制与时钟 input rst_n, input clk, // 系统主时钟(如 100 MHz) input start_in, // 数据输入(发送) input [DATA_WIDTH-1:0] spi_data_in, // 帧数量输入 // input [7:0] FRAME_NUM,// 需要传输的帧数(1~FRAME_NUM),可接受外部动态输入 // SPI 物理接口 output reg cs_n, output reg sclk, output reg [N-1:0] mosi, // 状态与标志信号 output reg done, // 所有帧传输完成 output wire frame_done // 单帧传输完成脉冲 ); // ==================== 状态机定义 ==================== localparam [1:0] IDLE = 2'b00, // 空闲状态 WAIT = 2'b01, // 等待状态 TRANSMIT = 2'b11, // 传输状态 FINISH = 2'b10; // 完成状态 reg [1:0] current_state, next_state; // ==================== 内部信号定义 ==================== reg [DATA_WIDTH-1:0] tx_shift_reg; reg [N-1:0] tx_buffer; reg sclk_r0,sclk_r1; // 时钟控制 reg [$clog2(CLK_DIV_FACTOR)-1:0] clk_div_cnt; // 动态位宽的分频计数器 wire clk_div_edge; // 分频时钟边沿(用于翻转sclk) wire sclk_posedge; wire sclk_negedge; wire data_change_edge; wire data_frame_edge; reg [$clog2(DATA_WIDTH):0] bit_tx_cnt; reg [$clog2(WAIT_TIME):0] time_cnt; // 帧计数 reg [$clog2(FRAME_NUM+1)-1:0] frame_cnt; // 当前帧计数 reg start_in1,start_in2,start_in3,start; // ==================== 时钟分频与边沿检测 ==================== // 生成分频边沿信号:当计数器达到最大值时产生一个脉冲 assign clk_div_edge = (clk_div_cnt == ((CLK_DIV_FACTOR>>1) - 1)); // SPI时钟生成(在negedge clk下工作,实现半周期延迟) always @(negedge clk) begin if (!rst_n) begin sclk_r0 <= CPOL; clk_div_cnt <= 0; end else if ((current_state == TRANSMIT) & (frame_cnt < FRAME_NUM)) begin if (clk_div_edge) begin clk_div_cnt <= 0; sclk_r0 <= ~sclk_r0; // 在分频边沿翻转SPI时钟 end else begin clk_div_cnt <= clk_div_cnt + 1'b1; end end else begin sclk_r0 <= CPOL; // 空闲时保持CPOL定义的电平 clk_div_cnt <= 0; end if (current_state == WAIT) begin if (time_cnt < WAIT_TIME) begin time_cnt <= time_cnt + 1; end else begin time_cnt <= 0; end end else begin time_cnt <= 0; end sclk_r1 <= sclk_r0; sclk <= sclk_r1; start_in1 <= start_in; start_in2 <= start_in1; start_in3 <= start_in2; start <= start_in3; end // 检测SPI时钟的上升沿和下降沿(相对于CPOL) assign sclk_posedge = ~sclk_r1 && (sclk_r0); assign sclk_negedge = sclk_r1 && (~sclk_r0); // 确定数据发送边沿 assign data_change_edge = (CPHA == CPOL) ? sclk_posedge : sclk_negedge; // 帧数据更新边沿 assign data_frame_edge = (CPHA != CPOL) ? sclk_posedge : sclk_negedge; // 单帧传输完成信号 assign frame_done = (bit_tx_cnt >= DATA_WIDTH); // ==================== 状态机:状态寄存器 ==================== always @(posedge clk or negedge rst_n) begin if (!rst_n) begin current_state <= IDLE; end else begin current_state <= next_state; end end // ==================== 状态机:状态转移逻辑 ==================== always @(*) begin case (current_state) IDLE: begin if (start) begin next_state = WAIT; end else begin next_state = IDLE; end end WAIT: begin if (time_cnt < WAIT_TIME) begin next_state = WAIT; end else begin next_state = TRANSMIT; end end TRANSMIT: begin //帧传输完成,否则保持在TRANSMIT状态,继续传输下一帧 if (frame_cnt == FRAME_NUM) begin next_state = FINISH; // 所有帧传输完成 end else begin next_state = TRANSMIT; end end FINISH: begin next_state = IDLE; end default: begin next_state = IDLE; end endcase end // ==================== 状态机:输出逻辑 ==================== always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cs_n <= 1'b1; mosi <= {N{1'b0}}; bit_tx_cnt <= 0; tx_shift_reg <= 0; tx_buffer <= 0; done <= 1'b0; frame_cnt <= 0; end else begin case (current_state) IDLE: begin cs_n <= 1'b1; mosi <= {N{1'b0}}; done <= 1'b0; bit_tx_cnt <= 0; frame_cnt <= 0; // 只在start信号时捕获输入数据 if (start) begin tx_shift_reg <= spi_data_in; tx_buffer <= (MODE == 1) ? spi_data_in[DATA_WIDTH-1] : spi_data_in[0]; end end WAIT: begin cs_n <= 1'b0; end TRANSMIT: begin cs_n <= 1'b0; done <= 1'b0; // 单帧传输完成处理 if (frame_done && data_frame_edge) begin bit_tx_cnt <= 0; // 重置位计数器 frame_cnt <= frame_cnt + 1'b1; // 帧计数加1 // 捕获新的输入数据 tx_shift_reg <= spi_data_in; tx_buffer <= tx_buffer;//(MODE == 1) ? spi_data_in[DATA_WIDTH-1] : spi_data_in[0]; end else begin // 在"数据更新边沿"移位发送数据 if (data_change_edge) begin if (MODE == 1) begin // MSB first tx_buffer <= tx_shift_reg[DATA_WIDTH-1]; tx_shift_reg <= {tx_shift_reg[DATA_WIDTH-2:0], 1'b0}; end else begin // LSB first tx_buffer <= tx_shift_reg[0]; tx_shift_reg <= {1'b0, tx_shift_reg[DATA_WIDTH-1:1]}; end bit_tx_cnt <= bit_tx_cnt + 1'b1; end end mosi <= tx_buffer; end FINISH: begin cs_n <= 1'b1; done <= 1'b1; // 产生完成脉冲 mosi <= {N{1'b0}}; end default: begin cs_n <= 1'b1; mosi <= {N{1'b0}}; done <= 1'b0; end endcase end end endmodule//============================================================================== // Module: spi_rx // Description: // Author: wmm // Email: wmm246@qq.com // Date: 2026 //============================================================================== module spi_rx( input sys_clk ,// 系统时钟 (100MHz) input rst_n ,// 异步复位,低有效 input cs_n , input sclk , input miso , output reg [15:00] spi_data_out , output reg spi_data_vld ); reg cs_n_r0,cs_n_r1 ; reg sclk_r0,sclk_r1 ; reg miso_r0,miso_r1 ; reg neg_sclk_r0 ; wire neg_sclk ; wire pos_sclk ; reg [03:00] bit_rx_cnt ; reg [15:00] rx_shift_reg ; reg [15:00] byte_cnt ; assign neg_sclk = ~sclk_r0 & sclk_r1 ; assign pos_sclk = ~sclk_r1 & sclk_r0 ; always @(posedge sys_clk)begin cs_n_r0 <= cs_n; cs_n_r1 <= cs_n_r0; sclk_r0 <= sclk; sclk_r1 <= sclk_r0; miso_r0 <= miso; miso_r1 <= miso_r0; neg_sclk_r0 <= neg_sclk; if(cs_n_r1)begin byte_cnt<= 'd0; end else begin if(spi_data_vld)begin byte_cnt<= byte_cnt+ 1; end end end always @(posedge sys_clk )begin if(~cs_n_r1)begin if (neg_sclk) begin bit_rx_cnt <= bit_rx_cnt + 1'b1; rx_shift_reg <= {rx_shift_reg[14:0], miso}; end if ((bit_rx_cnt == 15) & neg_sclk_r0)begin spi_data_out <= rx_shift_reg; spi_data_vld <= 1'b1; end else begin spi_data_out <= spi_data_out; spi_data_vld <= 1'b0; end end else begin bit_rx_cnt <= 'hf; end end endmodule`timescale 1ns / 1ps module tb; parameter DATA_WIDTH = 16; parameter N = 1; parameter CLK_PERIOD = 10; reg rst_n ; reg clk ; reg clk_spi ; reg start ; reg [DATA_WIDTH-1: 0] spi_data_in ; wire cs_n ; wire sclk ; wire [ N-1: 0] mosi ; wire done,frame_done ; wire [ 15:00] spi_data_out ; wire spi_data_vld ; spi_tx #( .DATA_WIDTH (16), .FRAME_NUM (4 ), .CPOL (0 ), .CPHA (1 ), .N (1 ), .MODE (1 ), .CLK_DIV_FACTOR (10) ) spi_tx( .rst_n (rst_n ), .clk (clk ), .start_in (start ), .spi_data_in (spi_data_in ), .cs_n (cs_n ), .sclk (sclk ), .mosi (mosi ), .done (done ), .frame_done (frame_done ) ); spi_rx spi_rx( .sys_clk (clk ), .rst_n (rst_n ), .cs_n (cs_n ), .sclk (sclk ), .miso (mosi ), .spi_data_out (spi_data_out ), .spi_data_vld (spi_data_vld ) ); initial begin clk = 1'b0; end always #(CLK_PERIOD/2) clk = ~clk; initial begin rst_n = 1'b0; start = 1'b0; spi_data_in = 'h00; #100; rst_n = 1'b1; #100; spi_data_in = 'h55; start = 1'b1; #(CLK_PERIOD); start = 1'b0; wait(frame_done); spi_data_in = 'hAA; wait(frame_done); spi_data_in = 'hBB; wait(frame_done); spi_data_in = 'hCC; wait(done); #400; spi_data_in = 8'hF0; start = 1'b1; #(CLK_PERIOD); start = 1'b0; wait(done); #5000; $stop; end endmodule收发数据相同,验证成功。