Files

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