229 lines
8.6 KiB
Verilog
229 lines
8.6 KiB
Verilog
`timescale 1 ns / 1 ps
|
|
|
|
//------------------------------------------------------------------------------
|
|
// 简单的 CSI040 SPI master。
|
|
//
|
|
// 命令侧是一个 request/ready 接口:
|
|
// cmd_valid 和 cmd_ready 同时为 1 时启动一帧 SPI。
|
|
// cmd_write 选择写 opcode 或读 opcode。
|
|
// cmd_addr 在 opcode 后发送,高字节先发。
|
|
// cmd_wdata 的 payload byte0 来自 cmd_wdata[7:0]。
|
|
// cmd_len 选择 payload 字节数,并被限制到 1..MAX_BURST_BYTES。
|
|
//
|
|
// 当前 SPI 模式:
|
|
// csn 空闲为高,低有效
|
|
// sck 空闲为低
|
|
// si 在下降沿后更新
|
|
// 读数据阶段在上升沿采样 so
|
|
//------------------------------------------------------------------------------
|
|
module csi040_spi_master#
|
|
(
|
|
parameter CLK_DIV = 50, // SCK 半周期对应的 clk 周期数
|
|
parameter REG_ADDR_WIDTH = 16,
|
|
parameter ADDR_BYTES = 2,
|
|
parameter MAX_BURST_BYTES = 4,
|
|
parameter [7:0] OPCODE_WRITE = 8'h00,
|
|
parameter [7:0] OPCODE_READ = 8'h80
|
|
)
|
|
(
|
|
input clk,
|
|
input rst_n,
|
|
|
|
// 来自 csi040_ctrl 的命令请求。
|
|
output cmd_ready,
|
|
input cmd_valid,
|
|
input cmd_write,
|
|
input [REG_ADDR_WIDTH-1:0]cmd_addr,
|
|
input [8*MAX_BURST_BYTES-1:0] cmd_wdata,
|
|
input [7:0] cmd_len,
|
|
|
|
output reg done,
|
|
output reg [8*MAX_BURST_BYTES-1:0] cmd_rdata,
|
|
output reg busy,
|
|
|
|
// ASIC SPI 引脚。
|
|
output csn,
|
|
output sck,
|
|
output si,
|
|
input so
|
|
);
|
|
|
|
// 内部最大帧宽度包含 opcode + 地址 + 最大 payload。
|
|
// 实际只会移出 HEADER_BITS + 8*safe_cmd_len 个 bit。
|
|
localparam FRAME_MAX_BITS = 8 * (1 + ADDR_BYTES + MAX_BURST_BYTES);
|
|
localparam HEADER_BITS = 8 * (1 + ADDR_BYTES);
|
|
localparam [15:0] MAX_BURST_BYTES_W = MAX_BURST_BYTES;
|
|
localparam [15:0] HEADER_BITS_W = HEADER_BITS;
|
|
|
|
localparam IDLE_ST = 2'd0;
|
|
localparam TRANS_ST = 2'd1;
|
|
localparam DONE_ST = 2'd2;
|
|
|
|
// 传输状态、移位寄存器和计数器。
|
|
reg [1:0] cstate;
|
|
reg [FRAME_MAX_BITS-1:0] tx_shift;
|
|
reg [8*MAX_BURST_BYTES-1:0] rx_shift;
|
|
reg [15:0] half_cnt;
|
|
reg [15:0] total_bits;
|
|
reg [15:0] sampled_bits;
|
|
reg csn_r;
|
|
reg sck_r;
|
|
reg si_r;
|
|
reg cmd_write_r;
|
|
reg [7:0] cmd_len_r;
|
|
reg last_sampled;
|
|
|
|
// 组合逻辑组帧寄存器。
|
|
reg [FRAME_MAX_BITS-1:0] frame;
|
|
reg [REG_ADDR_WIDTH-1:0] addr_pad;
|
|
integer n;
|
|
|
|
wire half_tick;
|
|
wire data_bit_phase;
|
|
wire sample_data_bit;
|
|
wire [15:0] safe_cmd_len;
|
|
wire [15:0] cmd_total_bits;
|
|
|
|
// 把 cmd_len 限制在合法范围。因为 CSI040 寄存器按 byte 访问,
|
|
// 长度为 0 时按 1 字节处理。
|
|
assign safe_cmd_len = (cmd_len == 0) ? 16'd1 :
|
|
(cmd_len > MAX_BURST_BYTES) ? MAX_BURST_BYTES_W :
|
|
{8'd0, cmd_len};
|
|
|
|
// 总移位 bit 数 = opcode + 地址字节 + payload 字节。
|
|
assign cmd_total_bits = HEADER_BITS_W + (safe_cmd_len << 3);
|
|
|
|
// 一个 SCK 半周期已经到达。
|
|
assign half_tick = (half_cnt == (CLK_DIV - 1));
|
|
|
|
// 只有发完 opcode/address 头部之后,读数据阶段才有效。
|
|
assign data_bit_phase = (sampled_bits >= HEADER_BITS);
|
|
assign sample_data_bit = (!cmd_write_r) && data_bit_phase;
|
|
|
|
assign cmd_ready = (cstate == IDLE_ST);
|
|
assign csn = csn_r;
|
|
assign sck = sck_r;
|
|
assign si = si_r;
|
|
|
|
// 构造最大长度 SPI 帧:
|
|
// [opcode][addr MSB..LSB][payload byte0][payload byte1]...
|
|
// payload byte0 来自 cmd_wdata[7:0]。
|
|
always @(*) begin
|
|
frame = {FRAME_MAX_BITS{1'b0}};
|
|
addr_pad = cmd_addr;
|
|
|
|
frame[FRAME_MAX_BITS-1 -: 8] = cmd_write ? OPCODE_WRITE : OPCODE_READ;
|
|
|
|
for (n = 0; n < ADDR_BYTES; n = n + 1) begin
|
|
frame[FRAME_MAX_BITS-1-8*(n+1) -: 8] =
|
|
addr_pad[8*(ADDR_BYTES-n)-1 -: 8];
|
|
end
|
|
|
|
for (n = 0; n < MAX_BURST_BYTES; n = n + 1) begin
|
|
frame[FRAME_MAX_BITS-1-8*(1+ADDR_BYTES+n) -: 8] =
|
|
cmd_wdata[8*n +: 8];
|
|
end
|
|
end
|
|
|
|
// SPI 主状态机。每 CLK_DIV 个 clk 周期翻转一次 SCK。
|
|
// SCK 上升沿采样数据,SCK 下降沿准备下一位 SI。
|
|
always @(posedge clk or negedge rst_n) begin
|
|
if (!rst_n) begin
|
|
cstate <= IDLE_ST;
|
|
tx_shift <= 0;
|
|
rx_shift <= 0;
|
|
half_cnt <= 0;
|
|
total_bits <= 0;
|
|
sampled_bits <= 0;
|
|
csn_r <= 1'b1;
|
|
sck_r <= 1'b0;
|
|
si_r <= 1'b0;
|
|
cmd_write_r <= 1'b0;
|
|
cmd_len_r <= 0;
|
|
last_sampled <= 1'b0;
|
|
done <= 1'b0;
|
|
cmd_rdata <= 0;
|
|
busy <= 1'b0;
|
|
end else begin
|
|
done <= 1'b0;
|
|
|
|
case (cstate)
|
|
IDLE_ST: begin
|
|
csn_r <= 1'b1;
|
|
sck_r <= 1'b0;
|
|
si_r <= 1'b0;
|
|
half_cnt <= 0;
|
|
sampled_bits <= 0;
|
|
last_sampled <= 1'b0;
|
|
busy <= 1'b0;
|
|
|
|
if (cmd_valid) begin
|
|
// 帧开始时锁存命令,这样上层后续改变命令总线也不影响本帧。
|
|
tx_shift <= frame;
|
|
rx_shift <= 0;
|
|
total_bits <= cmd_total_bits;
|
|
cmd_write_r <= cmd_write;
|
|
cmd_len_r <= safe_cmd_len[7:0];
|
|
csn_r <= 1'b0;
|
|
si_r <= frame[FRAME_MAX_BITS-1];
|
|
busy <= 1'b1;
|
|
cstate <= TRANS_ST;
|
|
end
|
|
end
|
|
|
|
TRANS_ST: begin
|
|
busy <= 1'b1;
|
|
|
|
if (half_tick) begin
|
|
half_cnt <= 0;
|
|
|
|
if (!sck_r) begin
|
|
// 上升沿:ASIC 采样 SI;读 payload 阶段 master 采样 SO。
|
|
sck_r <= 1'b1;
|
|
|
|
if (sample_data_bit)
|
|
rx_shift <= {rx_shift[8*MAX_BURST_BYTES-2:0], so};
|
|
|
|
sampled_bits <= sampled_bits + 1'b1;
|
|
|
|
if (sampled_bits == (total_bits - 1))
|
|
last_sampled <= 1'b1;
|
|
end else begin
|
|
// 下降沿:准备下一位输出;如果最后一位已经采完则结束。
|
|
sck_r <= 1'b0;
|
|
|
|
if (last_sampled) begin
|
|
cstate <= DONE_ST;
|
|
cmd_rdata <= rx_shift;
|
|
end else begin
|
|
tx_shift <= {tx_shift[FRAME_MAX_BITS-2:0], 1'b0};
|
|
si_r <= tx_shift[FRAME_MAX_BITS-2];
|
|
end
|
|
end
|
|
end else begin
|
|
half_cnt <= half_cnt + 1'b1;
|
|
end
|
|
end
|
|
|
|
DONE_ST: begin
|
|
// 拉高 CSN,并产生 1 个 clk 周期的 done 脉冲。
|
|
csn_r <= 1'b1;
|
|
sck_r <= 1'b0;
|
|
si_r <= 1'b0;
|
|
busy <= 1'b0;
|
|
done <= 1'b1;
|
|
last_sampled <= 1'b0;
|
|
cstate <= IDLE_ST;
|
|
end
|
|
|
|
default: begin
|
|
cstate <= IDLE_ST;
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
|
|
|
|
|
|
endmodule
|