【FPGA实战】基于Verilog的MCP2515 CAN控制器SPI驱动详解 | 附完整代码

【FPGA实战】基于Verilog的MCP2515 CAN控制器SPI驱动详解 | 附完整代码

📌 摘要:本文详细介绍一款基于Verilog HDL实现的MCP2515 CAN控制器SPI驱动模块。内容涵盖模块架构设计、接口协议说明、状态机工作流程、数据格式转换逻辑及关键代码解析,适合FPGA工程师、嵌入式开发者学习参考。文末附完整可综合代码及调试建议。


一、前言:为什么需要这个模块?

1.1 CAN总线简介

CAN(Controller Area Network)总线是一种广泛应用于汽车电子、工业控制领域的串行通信协议,具有多主仲裁、错误检测、高可靠性 等特点。在资源受限或需要隔离的场景中,常通过SPI接口的外置CAN控制器(如MCP2515)实现CAN通信功能。

1.2 MCP2515芯片特性

Microchip的MCP2515是一款经典的独立CAN控制器,主要特点:

  • ✅ 支持CAN 2.0B协议(标准帧/扩展帧)
  • ✅ SPI接口(最高10MHz)与MCU/FPGA通信
  • ✅ 3个发送缓冲区 + 2个接收缓冲区
  • ✅ 可编程波特率(5Kbps~1Mbps)
  • ✅ 中断输出、休眠模式等低功耗特性

1.3 本模块设计目标

verilog 复制代码
// 核心目标:
// 1. 封装MCP2515的底层SPI操作,提供简洁的RAM式接口
// 2. 自动完成初始化、波特率配置、中断轮询
// 3. 支持扩展帧格式转换,适配上层"以太网风格"数据帧
// 4. 全同步设计,可综合,适配20MHz系统时钟

二、模块整体架构

2.1 顶层接口概览

verilog 复制代码
module mcp2515_controller #(
    parameter CLK_FREQ_HZ = 20_000_000,  // FPGA系统时钟
    parameter CAN_BAUD_HZ = 250_000,     // CAN波特率
    parameter OSC_FREQ_HZ = 20_000_000   // MCP2515晶振频率
)(
    // 系统接口
    input  wire        clk, rst_n,
    
    // SPI接口(连接MCP2515)
    output wire        can_cs_n, can_sclk, can_mosi,
    input  wire        can_miso,
    
    // 控制信号
    input  wire        can_int_n,    // MCP2515中断(低有效)
    output wire        can_rst_n, can_clk,
    
    // TX RAM接口(外部写,内部读)
    input  wire        tx_ram_wr_clk, tx_ram_wr_en, tx_trigger,
    input  wire [3:0]  tx_ram_wr_addr,
    input  wire [7:0]  tx_ram_wr_data,
    output wire        tx_busy,
    
    // RX RAM接口(内部写,外部读)
    input  wire        rx_ram_rd_clk, rx_done,
    input  wire [3:0]  rx_ram_rd_addr,
    output wire [7:0]  rx_ram_rd_data,
    output reg         rx_valid,
    
    // 状态输出
    output reg         init_done,
    output reg  [7:0]  status_reg, error_reg
);

2.2 数据流图

2.3 关键参数配置

参数 默认值 说明
CLK_FREQ_HZ 20MHz FPGA系统时钟,用于时序计算
CAN_BAUD_HZ 250Kbps 目标CAN波特率
OSC_FREQ_HZ 20MHz MCP2515外接晶振频率

💡 波特率配置原理 :MCP2515通过CNF1/2/3寄存器设置位时序。本模块预设250Kbps配置:

verilog 复制代码
localparam VAL_CNF1 = 8'h03;  // SJW=1, BRP=3
localparam VAL_CNF2 = 8'h92;  // PHSEG1=3, PRSEG=3, BTLMODE=1
localparam VAL_CNF3 = 8'h02;  // PHSEG2=3
// 计算公式:Baud = Fosc / (2×BRP×(1+PRSEG+PHSEG1+PHSEG2))
// 250K = 20M / (2×4×(1+3+3+3)) ✓

三、关键接口协议详解

3.1 SPI通信时序

MCP2515采用SPI Mode 0(CPOL=0, CPHA=0):

  • 📡 SCLK空闲为低,上升沿采样数据,下降沿输出数据
  • 🔄 MSB先传,8位为一字节
  • ⚡ 本模块SPI时钟固定10MHz(can_sclk = clk/2
verilog 复制代码
// SPI状态机核心片段(简化)
case (spi_state)
    SPI_TX: begin
        if (!spi_phase) begin
            can_sclk <= 0;
            can_mosi <= spi_shift_reg[7];  // 下降沿输出
            spi_phase <= 1;
        end else begin
            can_sclk <= 1;
            spi_shift_reg <= {spi_shift_reg[6:0], can_miso};  // 上升沿采样
            spi_phase <= 0;
            // ...字节计数逻辑
        end
    end
endcase

3.2 TX/RX RAM数据格式(16字节/帧)

模块采用统一帧格式,屏蔽MCP2515寄存器细节:

地址 字段 扩展帧格式 标准帧格式
0 控制字 [IDE, RTR, 2'b00, DLC] 同左
1 ID28:21 EID28:21 SID10:3
2 ID20:13 {EID[20:18], SRR, EXIDE, EID[17:16]} {SID[2:0], RTR, 1'b0, 1'b0, 2'b00}
3-4 EID15:0 扩展ID低16位 保留
5-12 Data0:7 数据字节(最多8字节) 同左
13-15 保留 - -

🔁 格式转换逻辑 :模块内部通过tx_cov_ram/rx_cov_ram组合逻辑自动完成格式映射,用户只需按统一格式读写RAM。

3.3 控制信号时序

verilog 复制代码
// 发送流程:
1. 外部逻辑在tx_ram_wr_clk上升沿写入16字节帧数据
2. 拉高tx_trigger脉冲(≥1个clk周期)
3. 模块自动检测TXB0空闲→加载数据→发送→等待完成
4. tx_busy拉高表示发送中,拉低表示可发送新帧

// 接收流程:
1. MCP2515收到CAN帧后拉低can_int_n
2. 模块轮询CANINTF寄存器,识别RX0IF/RX1IF
3. 自动读取RXB0/RXB1缓冲区,写入rx_ram
4. 拉高rx_valid脉冲通知外部逻辑
5. 外部逻辑读取完成后拉高rx_done,模块清除中断标志

四、核心状态机深度解析

4.1 SPI事务控制器(底层驱动)

verilog 复制代码
// 6状态SPI控制器,支持可变长度读写
localparam SPI_IDLE=0, SPI_CS_LO=1, SPI_TX=2, SPI_RX=3, SPI_CS_HI=4, SPI_DONE=5;

// 关键特性:
// • 支持先TX后RX的复合事务(如READ命令)
// • 字节计数自动切换收发模式
// • 严格的CS/SCLK时序控制,满足MCP2515时序要求

4.2 主控制状态机(业务流程)

4.3 中断处理机制

verilog 复制代码
// 双缓冲接收设计:
// • RX0/RX1独立中断标志,避免帧丢失
// • 优先级:先处理挂起的接收,再响应发送请求
// • 自动清除中断标志,防止重复触发

if (spi_rx_buf[0] & 8'h01) rx0_pending <= 1;  // RX0IF
if (spi_rx_buf[0] & 8'h02) rx1_pending <= 1;  // RX1IF

// 清除中断使用BIT_MODIFY命令(原子操作,避免读-改-写竞争)
spi_tx_buf[0] <= CMD_BIT_MODIFY;
spi_tx_buf[1] <= ADDR_CANINTF;
spi_tx_buf[2] <= 8'h01;  // 掩码:清除RX0IF
spi_tx_buf[3] <= 8'h00;  // 值:写0

五、关键代码片段精讲

5.1 数据格式转换(TX方向)

verilog 复制代码
// 扩展帧ID拆分示例(32位→MCP2515寄存器格式)
assign tx_cov_ram[0] = (tx_ide) ? tx_id[28:21] : tx_id[10:3];  // SIDH
assign tx_cov_ram[1] = (tx_ide) ? 
    {tx_id[20:18], 1'b1, 1'b1, 1'b0, tx_id[17:16]} :  // SIDL: EXIDE=1, SRR=1
    {tx_id[2:0], tx_rtr, 1'b0, 1'b0, 2'b00};           // 标准帧SIDL

5.2 初始化序列(确保可靠启动)

verilog 复制代码
// 关键步骤:
// 1. 发送RESET命令(软复位所有寄存器)
// 2. 等待振荡器稳定(10us延时)
// 3. 配置波特率寄存器(必须在配置模式下)
// 4. 设置接收过滤为"接收所有"(开发阶段方便调试)
// 5. 使能接收中断
// 6. 切换到正常模式,轮询CANSTAT确认模式切换成功

// 错误处理:若10次轮询未进入正常模式,置ERR_INIT_FAIL并重启初始化

5.3 发送流程(零拷贝优化)

verilog 复制代码
// 使用LOAD_TX0命令一次性写入14字节(地址0x31~0x3E)
// 避免多次SPI事务,提升效率
spi_tx_buf[0]  <= CMD_LOAD_TX0_ID;  // 0x40: 从SIDH开始加载
spi_tx_buf[1]  <= tx_cov_ram[0];    // SIDH
spi_tx_buf[2]  <= tx_cov_ram[1];    // SIDL
// ... 中间11字节 ...
spi_tx_buf[13] <= tx_cov_ram[12];   // D7
spi_tx_cnt     <= 4'd14;            // 命令+13字节数据

// 发送RTS命令触发硬件发送
spi_tx_buf[0] <= CMD_RTS_TX0;  // 0x81: Request-to-Send for TXB0

六、使用示例与调试指南

6.1 模块实例化

verilog 复制代码
mcp2515_controller #(
    .CLK_FREQ_HZ(50_000_000),   // 根据实际时钟修改
    .CAN_BAUD_HZ(500_000),      // 500Kbps CAN
    .OSC_FREQ_HZ(16_000_000)    // MCP2515使用16MHz晶振
) u_can_ctrl (
    .clk(sys_clk),
    .rst_n(sys_rst_n),
    
    // SPI连接
    .can_cs_n(can_cs_n),
    .can_sclk(can_sclk),
    .can_mosi(can_mosi),
    .can_miso(can_miso),
    
    // 控制信号
    .can_int_n(can_int_n),
    .can_rst_n(can_rst_n),
    .can_clk(can_clk_out),  // 连接到MCP2515 OSC1
    
    // TX接口
    .tx_ram_wr_clk(user_clk),
    .tx_ram_wr_en(tx_wr_en),
    .tx_ram_wr_addr(tx_addr),
    .tx_ram_wr_data(tx_data),
    .tx_trigger(tx_start_pulse),
    .tx_busy(can_tx_busy),
    
    // RX接口
    .rx_ram_rd_clk(user_clk),
    .rx_ram_rd_addr(rx_addr),
    .rx_ram_rd_data(rx_data),
    .rx_valid(can_rx_valid),
    .rx_done(rx_read_done),
    
    // 状态
    .init_done(can_ready),
    .status_reg(can_status),
    .error_reg(can_error)
);

6.2 发送一帧扩展帧数据

verilog 复制代码
// 假设要发送:扩展ID=0x18FF0001, DLC=8, 数据=8'hAA~8'hFF
reg [7:0] tx_frame [0:15];
initial begin
    tx_frame[0] = 8'b1000_1000;  // IDE=1, RTR=0, DLC=8
    tx_frame[1] = 8'h18;         // EID[28:21]
    tx_frame[2] = 8'hFF;         // {EID[20:18], SRR=1, EXIDE=1, EID[17:16]}
    tx_frame[3] = 8'h00;         // EID[15:8]
    tx_frame[4] = 8'h01;         // EID[7:0]
    tx_frame[5] = 8'hAA;         // Data[0]
    // ... 填充Data[1:7] ...
    tx_frame[12] = 8'hFF;        // Data[7]
    
    // 写入TX RAM(16个时钟周期)
    for (integer i=0; i<16; i=i+1) begin
        #10 tx_ram_wr_addr = i;
        #10 tx_ram_wr_data = tx_frame[i];
        #10 tx_ram_wr_en = 1;
        #10 tx_ram_wr_en = 0;
    end
    
    // 触发发送
    #20 tx_trigger = 1;
    #10 tx_trigger = 0;
    
    // 等待发送完成
    wait (!can_tx_busy);
    $display("Frame sent!");
end

6.3 常见调试问题排查

现象 可能原因 解决方案
init_done 始终为0 SPI连接错误/晶振未起振 用ILA抓取SPI波形,检查MCP2515 OSC1/OSC2
发送后无CAN波形 TXEN未使能/波特率配置错误 检查ADDR_TXB0CTRL的TXEN位,验证CNF寄存器值
接收不到帧 过滤器配置过严 开发阶段设VAL_RXB_CTRL=8'h60(接收所有)
tx_busy 不释放 TXB0被占用/发送失败 检查ADDR_TEC/REC错误计数器,增加重试逻辑
SPI数据错乱 时钟域跨域问题 确保tx_ram_wr_clkclk异步时使用同步器

🔧 调试技巧 :在M_READY状态添加$display或ILA探针,监控main_statecan_intf_regspi_state等关键信号。


七、优化建议与扩展方向

7.1 性能优化

  • 多缓冲区支持:当前仅用TXB0,可扩展TXB1/TXB2实现发送队列
  • 零拷贝接收:RX RAM可直接映射到FIFO,减少RAM访问延迟
  • 中断驱动替代轮询 :将can_int_n连接到FPGA中断控制器,降低CPU占用

7.2 可靠性增强

verilog 复制代码
// 建议添加:
// 1. SPI事务超时检测(防止MCP2515挂死)
// 2. CAN错误计数器监控(TEC/REC > 127时报警)
// 3. 看门狗复位机制(主状态机卡死时自动重启)

// 示例:SPI超时检测
reg [15:0] spi_timeout;
always @(posedge clk) begin
    if (spi_busy) begin
        spi_timeout <= spi_timeout + 1;
        if (spi_timeout > 16'd10000) begin  // 500us超时
            error_reg <= ERR_SPI_ERR;
            main_state <= M_ERROR;
        end
    end else begin
        spi_timeout <= 0;
    end
end

7.3 低功耗设计

  • 🌙 利用MCP2515的Sleep Mode:空闲时发送CMD_WRITECANCTRL进入休眠
  • ⚡ 动态调整SPI时钟:通信时10MHz,空闲时降频降低功耗

八、总结

mcp2515_controller模块通过分层设计思想,将复杂的MCP2515 SPI操作封装为简洁的RAM接口,具有以下优势:

🔹 易用性 :用户只需按16字节帧格式读写RAM,无需关心底层寄存器

🔹 可靠性 :完整的初始化序列+错误处理机制,适应工业环境

🔹 可移植性 :参数化时钟/波特率,适配不同FPGA平台

🔹 可综合 :纯同步逻辑,无#delay,支持Xilinx/Intel主流工具链

九、完整代码

verilog 复制代码
//==============================================================================
// Module Name  : mcp2515_controller
// Description  : SPI Controller for MCP2515 CAN Controller
//==============================================================================

module mcp2515_controller #(
    parameter CLK_FREQ_HZ = 20_000_000, // FPGA system clock frequency
    parameter CAN_BAUD_HZ = 250_000,    // CAN bus baud rate
    parameter OSC_FREQ_HZ = 20_000_000  // MCP2515 oscillator frequency
)(
    //==========================================================================
    // System Interface
    //==========================================================================
    input  wire        clk,             // FPGA system clock (20MHz)
    input  wire        rst_n,           // Active-low asynchronous reset

    //==========================================================================
    // MCP2515 SPI Interface
    //==========================================================================
    output wire        can_cs_n,        // SPI Chip Select (active low)
    output wire        can_sclk,        // SPI Clock (10MHz)
    output wire        can_mosi,        // SPI Master Out Slave In
    input  wire        can_miso,        // SPI Master In Slave Out

    //==========================================================================
    // MCP2515 Control Signals
    //==========================================================================
    input  wire        can_int_n,       // MCP2515 INT pin (active low)
    output wire        can_rst_n,       // MCP2515 RESET pin (active low)
    output wire        can_clk,         // 20MHz clock output to MCP2515 OSC1

    //==========================================================================
    // TX RAM Interface (External Write, Internal Read)
    // Frame format (16 bytes per frame):
    //   [0]  SIDH = EID[28:21]
    //   [1]  SIDL = {EID[20:18], SRR, EXIDE(1'b1), EID[17:16]}
    //   [2]  EID8 = EID[15:8]
    //   [3]  EID0 = EID[7:0]
    //   [4]  DLC  = {1'b0, RTR, 2'b00, DLC[3:0]}
    //   [5]  D0   = Data[7:0]
    // ...
    //   [13-15]   Reserved
    //==========================================================================
    input  wire        tx_ram_wr_clk,
    input  wire        tx_ram_wr_en,    // TX RAM write enable
    input  wire [3:0]  tx_ram_wr_addr,  // TX RAM write address (0-15)
    input  wire [7:0]  tx_ram_wr_data,  // TX RAM write data
    input  wire        tx_trigger,      // Pulse to start transmitting current frame
    output wire        tx_busy,         // High when transmission in progress

    //==========================================================================
    // RX RAM Interface (Internal Write, External Read)
    //==========================================================================
    input  wire        rx_ram_rd_clk,
    input  wire [3:0]  rx_ram_rd_addr,  // RX RAM read address (0-15)
    output wire [7:0]  rx_ram_rd_data,  // RX RAM read data
    output reg         rx_valid,        // Pulse when new frame stored in RX RAM
    input  wire        rx_done,         // Pulse when frame read by external logic

    //==========================================================================
    // Status Interface
    //==========================================================================
    output reg         init_done,       // High when initialization complete
    output reg  [7:0]  status_reg,      // Current operation status
    output reg  [7:0]  error_reg        // Error flags register
);

    //==========================================================================
    // MCP2515 SPI Command Definitions (Table 12-1 in datasheet)
    //==========================================================================
    localparam CMD_RESET       = 8'hC0; // Reset internal registers
    localparam CMD_READ        = 8'h03; // Read from register
    localparam CMD_WRITE       = 8'h02; // Write to register
    localparam CMD_BIT_MODIFY  = 8'h05; // Bit modify register
    // LOAD TX BUFFER commands
    localparam CMD_LOAD_TX0_ID = 8'h40; // Load TXB0 starting at SIDH (addr 0x31)
    localparam CMD_LOAD_TX0_DT = 8'h41; // Load TXB0 starting at D0 (addr 0x36)
    // REQUEST-TO-SEND commands
    localparam CMD_RTS_TX0     = 8'h81; // RTS for TXB0
    localparam CMD_RTS_TX1     = 8'h82; // RTS for TXB1
    localparam CMD_RTS_TX2     = 8'h84; // RTS for TXB2
    // READ RX BUFFER commands
    localparam CMD_READ_RX0    = 8'h90; // Read RXB0 starting at SIDH (addr 0x61)
    localparam CMD_READ_RX1    = 8'h94; // Read RXB1 starting at SIDH (addr 0x71)
    // Status commands
    localparam CMD_READ_STAT   = 8'hA0; // Quick status read
    localparam CMD_RX_STATUS   = 8'hB0; // RX filter match status

    //==========================================================================
    // MCP2515 Register Addresses
    //==========================================================================
    localparam ADDR_RXF0SIDH   = 8'h00;
    localparam ADDR_RXF0SIDL   = 8'h01;
    localparam ADDR_BFPCTRL    = 8'h0C;
    localparam ADDR_TXRTSCTRL  = 8'h0D;
    localparam ADDR_CANSTAT    = 8'h0E;
    localparam ADDR_CANCTRL    = 8'h0F;
    localparam ADDR_TEC        = 8'h1C;
    localparam ADDR_REC        = 8'h1D;
    localparam ADDR_CNF3       = 8'h28;
    localparam ADDR_CNF2       = 8'h29;
    localparam ADDR_CNF1       = 8'h2A;
    localparam ADDR_CANINTE    = 8'h2B;
    localparam ADDR_CANINTF    = 8'h2C;
    localparam ADDR_EFLG       = 8'h2D;
    localparam ADDR_TXB0CTRL   = 8'h30;
    localparam ADDR_TXB0SIDH   = 8'h31;
    localparam ADDR_TXB0SIDL   = 8'h32;
    localparam ADDR_TXB0EID8   = 8'h33;
    localparam ADDR_TXB0EID0   = 8'h34;
    localparam ADDR_TXB0DLC    = 8'h35;
    localparam ADDR_TXB0D0     = 8'h36;
    localparam ADDR_RXB0CTRL   = 8'h60;
    localparam ADDR_RXB0SIDH   = 8'h61;
    localparam ADDR_RXB0D0     = 8'h66;
    localparam ADDR_RXB1CTRL   = 8'h70;

    //==========================================================================
    // Baud Rate Configuration
    //==========================================================================
    localparam VAL_CNF1        = 8'h03; // SJW=1, BRP=3
    localparam VAL_CNF2        = 8'h92; // BTLMODE=1, SAM=0, PHSEG1=3, PRSEG=3
    localparam VAL_CNF3        = 8'h02; // PHSEG2=3
    localparam VAL_RXB_CTRL    = 8'h60; // RXM[1:0]=11, receive all messages
    localparam VAL_CANINTE     = 8'h03; // RX0IE=1, RX1IE=1 (receive interrupts)
    localparam VAL_CANCTRL     = 8'h03; // Normal mode, CLKOUT=disabled

    //==========================================================================
    // Timing parameters
    //==========================================================================
    localparam OST_WAIT_CNT    = 8'd200;    // Wait 200*50ns = 10us
    localparam POLL_INTERVAL   = 16'd1000;  // Poll interval: 1000*50ns = 50us
    localparam TRANS_DELAY     = 16'd10000; // Frame Trans Delay: 10000*50ns = 500us

    //==========================================================================
    // Internal RAM (16 bytes per frame)
    //==========================================================================
    reg  [7:0] tx_ram     [0:15];
    reg  [7:0] rx_ram     [0:15];
    wire [7:0] tx_cov_ram [0:15];
    wire [7:0] rx_cov_ram [0:15];
    reg  [7:0] rx_ram_rdata;

    // RX RAM write control signals
    reg        rx_ram_wr_en;
    reg  [3:0] rx_ram_wr_addr;
    reg  [7:0] rx_ram_wr_data;

    // TX RAM write port (external)
    always @(posedge tx_ram_wr_clk) begin
        if (tx_ram_wr_en) begin
            tx_ram[tx_ram_wr_addr] <= tx_ram_wr_data;
        end
    end

    // RX RAM write port (internal)
    always @(posedge clk) begin
        if (rx_ram_wr_en) begin
            rx_ram[rx_ram_wr_addr] <= rx_ram_wr_data;
        end
    end

    // RX RAM read port (external)
    always @(posedge rx_ram_rd_clk) begin
        rx_ram_rdata <= rx_cov_ram[rx_ram_rd_addr];
    end

    assign rx_ram_rd_data = rx_ram_rdata;

    //==========================================================================
    // 1. RX Logic (MCP2515 Registers -> Ethernet Format)
    //==========================================================================
    wire       rx_ide = rx_ram[1][3];
    wire       rx_rtr = rx_ram[4][6];
    wire [3:0] rx_dlc = rx_ram[4][3:0];

    assign rx_cov_ram[0] = {rx_ide, rx_rtr, 2'b00, rx_dlc};
    assign rx_cov_ram[1] = (rx_ide) ? {3'b000, rx_ram[0][7:3]} : 8'h00;
    assign rx_cov_ram[2] = (rx_ide) ? {rx_ram[0][2:0], rx_ram[1][7:5], rx_ram[1][1:0]} : 8'h00;
    assign rx_cov_ram[3] = (rx_ide) ? rx_ram[2] : {5'b00000, rx_ram[0][7:5]};
    assign rx_cov_ram[4] = (rx_ide) ? rx_ram[3] : {rx_ram[0][4:0], rx_ram[1][7:5]};
    assign rx_cov_ram[5] = rx_ram[5];
    assign rx_cov_ram[6] = rx_ram[6];
    assign rx_cov_ram[7] = rx_ram[7];
    assign rx_cov_ram[8] = rx_ram[8];
    assign rx_cov_ram[9] = rx_ram[9];
    assign rx_cov_ram[10] = rx_ram[10];
    assign rx_cov_ram[11] = rx_ram[11];
    assign rx_cov_ram[12] = rx_ram[12];

    //==========================================================================
    // 2. TX Logic (Ethernet Format -> MCP2515 Registers)
    //==========================================================================
    wire        tx_ide  = tx_ram[0][7];
    wire        tx_rtr  = tx_ram[0][6];
    wire [3:0]  tx_dlc  = tx_ram[0][3:0];
    wire [31:0] tx_id   = {tx_ram[1], tx_ram[2], tx_ram[3], tx_ram[4]};

    assign tx_cov_ram[0] = (tx_ide) ? tx_id[28:21] : tx_id[10:3];
    assign tx_cov_ram[1] = (tx_ide) ? {tx_id[20:18], 1'b1, 1'b1, 1'b0, tx_id[17:16]} :
                                      {tx_id[2:0], tx_rtr, 1'b0, 1'b0, 2'b00};
    assign tx_cov_ram[2] = (tx_ide) ? tx_id[15:8] : 8'h00;
    assign tx_cov_ram[3] = (tx_ide) ? tx_id[7:0]  : 8'h00;
    assign tx_cov_ram[4] = {1'b0, tx_rtr, 2'b00, tx_dlc};
    assign tx_cov_ram[5] = tx_ram[5];
    assign tx_cov_ram[6] = tx_ram[6];
    assign tx_cov_ram[7] = tx_ram[7];
    assign tx_cov_ram[8] = tx_ram[8];
    assign tx_cov_ram[9] = tx_ram[9];
    assign tx_cov_ram[10] = tx_ram[10];
    assign tx_cov_ram[11] = tx_ram[11];
    assign tx_cov_ram[12] = tx_ram[12];

    //==========================================================================
    // Static Assignments
    //==========================================================================
    assign can_clk   = clk;
    assign can_rst_n = 1'b1;

    //==========================================================================
    // SPI Transaction Controller
    //==========================================================================
    localparam SPI_IDLE  = 4'd0;
    localparam SPI_CS_LO = 4'd1;
    localparam SPI_TX    = 4'd2;
    localparam SPI_RX    = 4'd3;
    localparam SPI_CS_HI = 4'd4;
    localparam SPI_DONE  = 4'd5;

    reg        spi_start;
    reg        spi_busy;
    reg  [7:0] spi_tx_buf [0:15];
    reg  [7:0] spi_rx_buf [0:15];
    reg  [3:0] spi_tx_cnt;
    reg  [3:0] spi_rx_cnt;

    reg  [3:0] spi_state;
    reg  [3:0] spi_byte_idx;
    reg  [2:0] spi_bit_cnt;
    reg        spi_phase;
    reg  [7:0] spi_shift_reg;
    reg        spi_cs_n_reg;
    reg        spi_sclk_reg;
    reg        spi_mosi_reg;
    reg        spi_done;

    assign can_cs_n = spi_cs_n_reg;
    assign can_sclk = spi_sclk_reg;
    assign can_mosi = spi_mosi_reg;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            spi_state     <= SPI_IDLE;
            spi_cs_n_reg  <= 1'b1;
            spi_sclk_reg  <= 1'b0;
            spi_mosi_reg  <= 1'b0;
            spi_busy      <= 1'b0;
            spi_done      <= 1'b0;
            spi_byte_idx  <= 4'd0;
            spi_bit_cnt   <= 3'd7;
            spi_phase     <= 1'b0;
            spi_shift_reg <= 8'h00;
        end else begin
            spi_done <= 1'b0;

            case (spi_state)
                SPI_IDLE: begin
                    spi_cs_n_reg <= 1'b1;
                    spi_sclk_reg <= 1'b0;
                    spi_busy     <= 1'b0;
                    if (spi_start) begin
                        spi_state     <= SPI_CS_LO;
                        spi_busy      <= 1'b1;
                        spi_byte_idx  <= 4'd0;
                        spi_bit_cnt   <= 3'd7;
                        spi_phase     <= 1'b0;
                        spi_shift_reg <= spi_tx_buf[0];
                    end
                end

                SPI_CS_LO: begin
                    spi_cs_n_reg <= 1'b0;
                    spi_state    <= SPI_TX;
                end

                SPI_TX: begin
                    if (spi_phase == 1'b0) begin
                        spi_sclk_reg <= 1'b0;
                        spi_mosi_reg <= spi_shift_reg[7];
                        spi_phase    <= 1'b1;
                    end else begin
                        spi_sclk_reg  <= 1'b1;
                        spi_shift_reg <= {spi_shift_reg[6:0], can_miso};
                        spi_phase     <= 1'b0;

                        if (spi_bit_cnt == 3'd0) begin
                            spi_bit_cnt  <= 3'd7;
                            spi_byte_idx <= spi_byte_idx + 1'b1;

                            if (spi_byte_idx + 1'b1 < spi_tx_cnt) begin
                                spi_shift_reg <= spi_tx_buf[spi_byte_idx + 1'b1];
                            end else if (spi_rx_cnt > 4'd0) begin
                                spi_state     <= SPI_RX;
                                spi_shift_reg <= 8'h00;
                                spi_byte_idx  <= 4'd0;
                            end else begin
                                spi_state <= SPI_CS_HI;
                            end
                        end else begin
                            spi_bit_cnt <= spi_bit_cnt - 1'b1;
                        end
                    end
                end

                SPI_RX: begin
                    if (spi_phase == 1'b0) begin
                        spi_sclk_reg <= 1'b0;
                        spi_mosi_reg <= spi_shift_reg[7];
                        spi_phase    <= 1'b1;
                    end else begin
                        spi_sclk_reg  <= 1'b1;
                        spi_shift_reg <= {spi_shift_reg[6:0], can_miso};
                        spi_phase     <= 1'b0;

                        if (spi_bit_cnt == 3'd0) begin
                            spi_rx_buf[spi_byte_idx] <= {spi_shift_reg[6:0], can_miso};
                            spi_bit_cnt  <= 3'd7;
                            spi_byte_idx <= spi_byte_idx + 1'b1;

                            if (spi_byte_idx + 1'b1 < spi_rx_cnt) begin
                                spi_shift_reg <= 8'h00;
                            end else begin
                                spi_state <= SPI_CS_HI;
                            end
                        end else begin
                            spi_bit_cnt <= spi_bit_cnt - 1'b1;
                        end
                    end
                end

                SPI_CS_HI: begin
                    spi_cs_n_reg <= 1'b1;
                    spi_sclk_reg <= 1'b0;
                    spi_state    <= SPI_DONE;
                end

                SPI_DONE: begin
                    spi_done  <= 1'b1;
                    spi_busy  <= 1'b0;
                    spi_state <= SPI_IDLE;
                end

                default: begin
                    spi_state <= SPI_IDLE;
                end
            endcase
        end
    end

    //==========================================================================
    // Main Control State Machine
    //==========================================================================
    localparam M_IDLE        = 8'd0;
    localparam M_RST_CMD     = 8'd1;
    localparam M_RST_WAIT    = 8'd2;
    localparam M_RST_DELAY   = 8'd3;
    localparam M_WR_CNF3     = 8'd4;
    localparam M_WR_CNF2     = 8'd5;
    localparam M_WR_CNF1     = 8'd6;
    localparam M_WR_RXB0     = 8'd7;
    localparam M_WR_RXB1     = 8'd8;
    localparam M_WR_INTE     = 8'd9;
    localparam M_WR_CTRL     = 8'd10;
    localparam M_RD_STAT     = 8'd11;
    localparam M_READY       = 8'd12;
    localparam M_POLL_INTF   = 8'd13;
    localparam M_CHK_RX0     = 8'd14;
    localparam M_READ_RX0    = 8'd15;
    localparam M_STORE_RX0   = 8'd16;
    localparam M_CLR_RX0IF   = 8'd17;
    localparam M_CHK_RX1     = 8'd18;
    localparam M_READ_RX1    = 8'd19;
    localparam M_STORE_RX1   = 8'd20;
    localparam M_CLR_RX1IF   = 8'd21;
    localparam M_TX_RD_CTRL  = 8'd22;
    localparam M_TX_CHK      = 8'd23;
    localparam M_TX_LOAD     = 8'd24;
    localparam M_TX_RTS      = 8'd25;
    localparam M_TX_WAIT     = 8'd26;
    localparam M_ERROR       = 8'd27;

    reg  [7:0]  main_state;
    reg  [7:0]  wait_cnt;
    reg  [15:0] poll_timer;
    reg         tx_busy_reg;
    reg  [3:0]  tx_byte_idx;
    reg  [3:0]  rx_byte_idx;
    reg         rx0_pending;
    reg         rx1_pending;
    reg  [7:0]  can_intf_reg;
    reg         tx_req_active;

    localparam STAT_INIT_DONE  = 8'h01;
    localparam STAT_TX_BUSY    = 8'h02;
    localparam STAT_RX_VALID   = 8'h04;
    localparam STAT_INT_ACTIVE = 8'h08;
    localparam STAT_MODE_ERR   = 8'h10;
    localparam STAT_SPI_ERR    = 8'h20;

    localparam ERR_INIT_FAIL   = 8'h01;
    localparam ERR_TX_FAIL     = 8'h02;
    localparam ERR_RX_OVF      = 8'h04;
    localparam ERR_BUS_OFF     = 8'h08;

    localparam FRAME_BYTE_CNT  = 4'd13;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            main_state     <= M_IDLE;
            wait_cnt       <= 8'd0;
            poll_timer     <= 16'd0;
            tx_busy_reg    <= 1'b0;
            tx_byte_idx    <= 4'd0;
            rx_byte_idx    <= 4'd0;
            init_done      <= 1'b0;
            rx_valid       <= 1'b0;
            rx0_pending    <= 1'b0;
            rx1_pending    <= 1'b0;
            can_intf_reg   <= 8'd0;
            status_reg     <= 8'd0;
            error_reg      <= 8'd0;
            tx_req_active  <= 1'b0;
            rx_ram_wr_en   <= 1'b0;
            rx_ram_wr_addr <= 4'd0;
            rx_ram_wr_data <= 8'd0;
            spi_start      <= 1'b0;
        end else begin
            // Default pulse clear
            rx_valid     <= 1'b0;
            rx_ram_wr_en <= 1'b0;

            case (main_state)
                M_IDLE: begin
                    main_state <= M_RST_CMD;
                    status_reg <= 8'd0;
                    error_reg  <= 8'd0;
                end

                M_RST_CMD: begin
                    if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_RESET;
                        spi_tx_cnt    <= 4'd1;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                        main_state    <= M_RST_WAIT;
                        wait_cnt      <= 8'd0;
                    end
                end

                M_RST_WAIT: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_RST_DELAY;
                    end
                end

                M_RST_DELAY: begin
                    if (wait_cnt < OST_WAIT_CNT) begin
                        wait_cnt <= wait_cnt + 1'b1;
                    end else begin
                        main_state <= M_WR_CNF3;
                    end
                end

                M_WR_CNF3: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_WR_CNF2;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_WRITE;
                        spi_tx_buf[1] <= ADDR_CNF3;
                        spi_tx_buf[2] <= VAL_CNF3;
                        spi_tx_cnt    <= 4'd3;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_WR_CNF2: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_WR_CNF2 + 1'b1;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_WRITE;
                        spi_tx_buf[1] <= ADDR_CNF2;
                        spi_tx_buf[2] <= VAL_CNF2;
                        spi_tx_cnt    <= 4'd3;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_WR_CNF1: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_WR_CNF1 + 1'b1;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_WRITE;
                        spi_tx_buf[1] <= ADDR_CNF1;
                        spi_tx_buf[2] <= VAL_CNF1;
                        spi_tx_cnt    <= 4'd3;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_WR_RXB0: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_WR_RXB0 + 1'b1;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_WRITE;
                        spi_tx_buf[1] <= ADDR_RXB0CTRL;
                        spi_tx_buf[2] <= VAL_RXB_CTRL;
                        spi_tx_cnt    <= 4'd3;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_WR_RXB1: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_WR_RXB1 + 1'b1;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_WRITE;
                        spi_tx_buf[1] <= ADDR_RXB1CTRL;
                        spi_tx_buf[2] <= VAL_RXB_CTRL;
                        spi_tx_cnt    <= 4'd3;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_WR_INTE: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_WR_INTE + 1'b1;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_WRITE;
                        spi_tx_buf[1] <= ADDR_CANINTE;
                        spi_tx_buf[2] <= VAL_CANINTE;
                        spi_tx_cnt    <= 4'd3;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_WR_CTRL: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_WR_CTRL + 1'b1;
                        wait_cnt   <= 8'd0;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_WRITE;
                        spi_tx_buf[1] <= ADDR_CANCTRL;
                        spi_tx_buf[2] <= VAL_CANCTRL;
                        spi_tx_cnt    <= 4'd3;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_RD_STAT: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        if ((spi_rx_buf[0] & 8'hE0) == 8'h00) begin
                            main_state <= M_RD_STAT + 1'b1;
                            poll_timer <= 16'd0;
                            init_done  <= 1'b1;
                            status_reg <= STAT_INIT_DONE;
                        end else if (wait_cnt < 8'd10) begin
                            wait_cnt <= wait_cnt + 1'b1;
                        end else begin
                            error_reg  <= ERR_INIT_FAIL;
                            main_state <= M_ERROR;
                        end
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_READ;
                        spi_tx_buf[1] <= ADDR_CANSTAT;
                        spi_tx_cnt    <= 4'd2;
                        spi_rx_cnt    <= 4'd1;
                        spi_start     <= 1'b1;
                    end
                end

                M_READY: begin
                    status_reg <= STAT_INIT_DONE;

                    if (tx_trigger && !tx_busy_reg) begin
                        tx_req_active <= 1'b1;
                        tx_busy_reg   <= 1'b1;
                        main_state    <= M_TX_RD_CTRL;
                    end else if (!can_int_n) begin
                        main_state <= M_POLL_INTF;
                    end else if (poll_timer >= POLL_INTERVAL - 1'b1) begin
                        main_state <= M_POLL_INTF;
                    end else begin
                        poll_timer <= poll_timer + 1'b1;
                    end
                end

                M_POLL_INTF: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        can_intf_reg <= spi_rx_buf[0];
                        if (spi_rx_buf[0] & 8'h01) begin
                            rx0_pending <= 1'b1;
                        end
                        if (spi_rx_buf[0] & 8'h02) begin
                            rx1_pending <= 1'b1;
                        end
                        main_state <= M_CHK_RX0;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_READ;
                        spi_tx_buf[1] <= ADDR_CANINTF;
                        spi_tx_cnt    <= 4'd2;
                        spi_rx_cnt    <= 4'd1;
                        spi_start     <= 1'b1;
                    end
                end

                M_CHK_RX0: begin
                    if (rx0_pending) begin
                        rx0_pending <= 1'b0;
                        main_state  <= M_READ_RX0;
                        rx_byte_idx <= 4'd0;
                    end else begin
                        main_state  <= M_CHK_RX1;
                    end
                end

                M_READ_RX0: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state  <= M_STORE_RX0;
                        rx_byte_idx <= 4'd0;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_READ_RX0;
                        spi_tx_cnt    <= 4'd1;
                        spi_rx_cnt    <= FRAME_BYTE_CNT;
                        spi_start     <= 1'b1;
                    end
                end

                M_STORE_RX0: begin
                    if (rx_byte_idx < FRAME_BYTE_CNT) begin
                        rx_ram_wr_en   <= 1'b1;
                        rx_ram_wr_addr <= rx_byte_idx;
                        rx_ram_wr_data <= spi_rx_buf[rx_byte_idx];
                        rx_byte_idx    <= rx_byte_idx + 1'b1;
                    end else begin
                        rx_valid   <= 1'b1;
                        status_reg <= STAT_INIT_DONE | STAT_RX_VALID;
                        main_state <= M_CLR_RX0IF;
                    end
                end

                M_CLR_RX0IF: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_CHK_RX1;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_BIT_MODIFY;
                        spi_tx_buf[1] <= ADDR_CANINTF;
                        spi_tx_buf[2] <= 8'h01;
                        spi_tx_buf[3] <= 8'h00;
                        spi_tx_cnt    <= 4'd4;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_CHK_RX1: begin
                    if (rx1_pending) begin
                        rx1_pending <= 1'b0;
                        main_state  <= M_READ_RX1;
                        rx_byte_idx <= 4'd0;
                    end else if (tx_req_active) begin
                        main_state <= M_TX_RD_CTRL;
                    end else begin
                        poll_timer <= 16'd0;
                        main_state <= M_READY;
                    end
                end

                M_READ_RX1: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state  <= M_STORE_RX1;
                        rx_byte_idx <= 4'd0;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_READ_RX1;
                        spi_tx_cnt    <= 4'd1;
                        spi_rx_cnt    <= FRAME_BYTE_CNT;
                        spi_start     <= 1'b1;
                    end
                end

                M_STORE_RX1: begin
                    if (rx_byte_idx < FRAME_BYTE_CNT) begin
                        rx_ram_wr_en   <= 1'b1;
                        rx_ram_wr_addr <= rx_byte_idx;
                        rx_ram_wr_data <= spi_rx_buf[rx_byte_idx];
                        rx_byte_idx    <= rx_byte_idx + 1'b1;
                    end else begin
                        rx_valid   <= 1'b1;
                        status_reg <= STAT_INIT_DONE | STAT_RX_VALID;
                        main_state <= M_CLR_RX1IF;
                    end
                end

                M_CLR_RX1IF: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        if (tx_req_active) begin
                            main_state <= M_TX_RD_CTRL;
                        end else begin
                            poll_timer <= 16'd0;
                            main_state <= M_READY;
                        end
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_BIT_MODIFY;
                        spi_tx_buf[1] <= ADDR_CANINTF;
                        spi_tx_buf[2] <= 8'h02;
                        spi_tx_buf[3] <= 8'h00;
                        spi_tx_cnt    <= 4'd4;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_TX_RD_CTRL: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_TX_CHK;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_READ;
                        spi_tx_buf[1] <= ADDR_TXB0CTRL;
                        spi_tx_cnt    <= 4'd2;
                        spi_rx_cnt    <= 4'd1;
                        spi_start     <= 1'b1;
                    end
                end

                M_TX_CHK: begin
                    if ((spi_rx_buf[0] & 8'h08) == 8'h00) begin
                        main_state  <= M_TX_LOAD;
                        tx_byte_idx <= 4'd0;
                    end else begin
                        poll_timer <= 16'd0;
                        main_state <= M_READY;
                    end
                end

                M_TX_LOAD: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_TX_RTS;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0]  <= CMD_LOAD_TX0_ID;
                        spi_tx_buf[1]  <= tx_cov_ram[0];
                        spi_tx_buf[2]  <= tx_cov_ram[1];
                        spi_tx_buf[3]  <= tx_cov_ram[2];
                        spi_tx_buf[4]  <= tx_cov_ram[3];
                        spi_tx_buf[5]  <= tx_cov_ram[4];
                        spi_tx_buf[6]  <= tx_cov_ram[5];
                        spi_tx_buf[7]  <= tx_cov_ram[6];
                        spi_tx_buf[8]  <= tx_cov_ram[7];
                        spi_tx_buf[9]  <= tx_cov_ram[8];
                        spi_tx_buf[10] <= tx_cov_ram[9];
                        spi_tx_buf[11] <= tx_cov_ram[10];
                        spi_tx_buf[12] <= tx_cov_ram[11];
                        spi_tx_buf[13] <= tx_cov_ram[12];
                        spi_tx_cnt     <= 4'd14;
                        spi_rx_cnt     <= 4'd0;
                        spi_start      <= 1'b1;
                    end
                end

                M_TX_RTS: begin
                    if (spi_busy) begin
                        spi_start <= 1'b0;
                    end
                    if (spi_done) begin
                        main_state <= M_TX_WAIT;
                        poll_timer <= 16'd0;
                    end else if (!spi_busy) begin
                        spi_tx_buf[0] <= CMD_RTS_TX0;
                        spi_tx_cnt    <= 4'd1;
                        spi_rx_cnt    <= 4'd0;
                        spi_start     <= 1'b1;
                    end
                end

                M_TX_WAIT: begin
                    if (poll_timer >= TRANS_DELAY - 1'b1) begin
                        tx_req_active <= 1'b0;
                        tx_busy_reg   <= 1'b0;
                        poll_timer    <= 16'd0;
                        status_reg    <= STAT_INIT_DONE;
                        main_state    <= M_READY;
                    end else begin
                        poll_timer <= poll_timer + 1'b1;
                    end
                end

                M_ERROR: begin
                    status_reg <= STAT_MODE_ERR;
                    if (wait_cnt < 8'hFF) begin
                        wait_cnt <= wait_cnt + 1'b1;
                    end else begin
                        main_state <= M_RST_CMD;
                    end
                end

                default: begin
                    main_state <= M_IDLE;
                end
            endcase

            if (rx_done) begin
                status_reg <= status_reg & ~STAT_RX_VALID;
            end
        end
    end

    // TX busy output
    assign tx_busy = tx_busy_reg;

endmodule

本文代码经平台实测通过。如有疑问欢迎留言讨论~ 🚀

相关推荐
szxinmai主板定制专家1 小时前
基于 ARM+FPGA 数据机床实时工业控制设计--以雕刻机为例
arm开发·人工智能·嵌入式硬件·fpga开发
XMAIPC_Robot2 小时前
基于RK3588 ARM+FPGA电火花数控机床控制系统设计,兼顾ethercat软硬件实时
linux·arm开发·人工智能·嵌入式硬件·fpga开发
XMAIPC_Robot2 小时前
基于 ARM+FPGA 数据机床控制系统设计--以雕刻机为例
arm开发·fpga开发
GateWorld2 小时前
LCD显示技术完全指南:原理·制造·驱动·FPGA实现之点屏一
fpga开发·lcd显示·fpga点亮屏幕·minilvds·fpga点屏
風清掦18 小时前
【STM32学习笔记-14】WDG看门狗 - 14.2 WWDG窗口看门狗
笔记·stm32·单片机·嵌入式硬件·学习·fpga开发
尤老师FPGA21 小时前
HDMI数据的接收发送实验(十二)
fpga开发
坏孩子的诺亚方舟1 天前
FPGA神经网络数学基础0
人工智能·神经网络·线性代数·fpga开发
熠速1 天前
PolarBox高性能实时仿真系统
arm开发·fpga开发·嵌入式实时数据库·硬件在环半实物仿真
南檐巷上学1 天前
基于Zynq-7020的带有正弦波发生器的8051软核设计
单片机·嵌入式硬件·fpga开发·fpga