Lumees Lab — FPGA-Verified, Production-Ready Silicon IP
The Lumees Lab CAN IP Core is a complete Controller Area Network (CAN 2.0A/B) controller implementing the full ISO 11898-1 protocol in synthesizable SystemVerilog. It includes the Bit Timing Logic (BTL), Bit Stream Processor (BSP), TX priority queue, RX FIFO with acceptance filtering, and full error management with automatic state transitions (Error Active → Error Passive → Bus Off).
Unlike soft-core CAN implementations that rely on CPU-assisted bit timing, this core handles all protocol processing in hardware — from bit-level synchronization and stuffing to frame-level CRC, ACK, and error confinement. The result is a deterministic, interrupt-driven CAN controller suitable for real-time automotive and industrial applications.
Verified in simulation (10/10 cocotb tests) and on Xilinx FPGA hardware (Arty A7-100T, 42/42 UART regression tests at 500 kbps), the core is production-ready for SoC integration.
| Feature | Detail |
|---|---|
| Standard | CAN 2.0A (11-bit ID) and CAN 2.0B (29-bit extended ID), ISO 11898-1 |
| Bit Timing | Programmable BRP, TSEG1 (PROP_SEG + PHASE_SEG1), TSEG2, SJW |
| Bit Rates | 10 kbps – 1 Mbps (crystal-dependent; 500 kbps silicon-verified) |
| TX Buffers | 3-entry priority queue (buffer 0 = highest priority) |
| RX FIFO | 8-entry with overrun detection (depth configurable) |
| Acceptance Filter | 29-bit code + mask, extended-only mode |
| Error Management | TEC/REC counters, ERROR_ACTIVE → ERROR_PASSIVE → BUS_OFF per ISO 11898-1 |
| CRC | CRC-15 (polynomial 0x4599) computed inline during TX/RX |
| Bit Stuffing | Automatic stuff/destuff per ISO 11898-1 |
| Frame Types | Data, Remote (RTR), Error, Overload |
| Loopback Mode | Internal TX→RX for self-test without external transceiver |
| Interrupts | TX done, RX data available, bus error (combined IRQ) |
| Bus Interfaces | AXI4-Lite, Wishbone B4, AXI4-Stream, bare port |
| Technology | FPGA / ASIC, pure synchronous RTL, no vendor primitives |
| Resource | Full SoC | Core (est.) | Available | SoC % |
|---|---|---|---|---|
| LUT | 1,602 | ~850 | 63,400 | 2.53% |
| FF | 2,162 | ~1,200 | 126,800 | 1.71% |
| DSP48 | 0 | 0 | 240 | 0% |
| Block RAM | 0 | 0 | 135 | 0% |
Timing: WNS = +8.645 ns @ 100 MHz (post-route). Zero DSP/BRAM. 0 failing endpoints.
┌────────────────────────────────────────────────────┐
│ can_top │
│ │
tx_frame ──►│ TX Priority ┌──────────┐ ┌──────────┐ │──► can_tx_o
tx_req ──►│ Queue (×3) ──► │ can_bsp │◄──│ can_btl │◄────── │◄── can_rx_i
│ │ (TX/RX │ │ (Bit │ │
│ RX FIFO (×8)◄─ │ FSM, │ │ Timing, │ │
rx_frame ◄──│ + Acceptance │ CRC-15, │ │ BRP, │ │
rx_valid ◄──│ Filter │ Stuff/ │ │ Sync) │ │
│ │ Destuff)│ │ │ │
│ error_state ◄──│ TEC/REC │ │ │ │
└─────────────────┴──────────┴───┴──────────┘────────┘
can_top u_can (
.clk (clk),
.rst_n (rst_n),
// Bit timing
.brp_i (brp), // [5:0] Baud Rate Prescaler
.tseg1_i (tseg1), // [3:0] PROP_SEG + PHASE_SEG1
.tseg2_i (tseg2), // [2:0] PHASE_SEG2
.sjw_i (sjw), // [1:0] Sync Jump Width
// Control
.loopback_i (loopback), // Internal TX→RX loopback
.sw_reset_i (sw_reset), // Synchronous reset
// TX
.tx_frame_i (tx_frame), // can_frame_t: {id, data, dlc, ide, rtr}
.tx_req_i (tx_req), // Pulse: load frame
.tx_buf_sel_i (tx_buf_sel), // [1:0] Buffer select (0..2)
.tx_ready_o (tx_ready), // Buffer free
.tx_done_o (tx_done), // Frame sent
// RX
.rx_frame_o (rx_frame), // Oldest received frame
.rx_valid_o (rx_valid), // FIFO has data
.rx_ready_i (rx_read), // Dequeue
.rx_overrun_o (rx_overrun), // FIFO overrun
// Filter
.filter_code_i (code), // [28:0] Acceptance code
.filter_mask_i (mask), // [28:0] Mask (1=don't care)
.filter_ext_i (ext_only), // Extended frames only
.filter_en_i (filter_en), // Enable
// CAN bus
.can_tx_o (can_tx), // To transceiver
.can_rx_i (can_rx), // From transceiver
// Status
.error_state_o (err_state), // ERROR_ACTIVE/PASSIVE/BUS_OFF
.tec_o (tec), // [8:0] TX Error Counter (bit[8]=bus-off)
.rec_o (rec), // [7:0] RX Error Counter
.bus_error_o (bus_error) // Protocol error pulse
);| Offset | Register | Access | Description |
|---|---|---|---|
| 0x00 | CTRL | R/W | [0]=TX_REQ [2:1]=TX_BUF [3]=LOOPBACK [4]=SW_RESET [5]=FILTER_EN |
| 0x04 | STATUS | R/W1C | [0]=TX_READY [1]=TX_DONE [2]=RX_VALID [3]=RX_OVERRUN [5:4]=ERR_STATE |
| 0x08 | BIT_TIMING | R/W | [5:0]=BRP [9:6]=TSEG1 [12:10]=TSEG2 [14:13]=SJW |
| 0x0C | VERSION | RO | 0x00010000 |
| 0x10 | TX_ID | R/W | [28:0]=ID [29]=IDE [30]=RTR |
| 0x14 | TX_DLC | R/W | [3:0]=DLC |
| 0x18 | TX_DATA_LO | R/W | Data bytes [3:0] |
| 0x1C | TX_DATA_HI | R/W | Data bytes [7:4] |
| 0x20 | RX_ID | RO | Received ID |
| 0x24 | RX_DLC | RO | Received DLC |
| 0x28 | RX_DATA_LO | RO | RX data [3:0] (read dequeues) |
| 0x2C | RX_DATA_HI | RO | RX data [7:4] |
| 0x30 | FILTER_CODE | R/W | Acceptance code |
| 0x34 | FILTER_MASK | R/W | Acceptance mask |
| 0x38 | TEC_REC | RO | [7:0]=TEC [15:8]=REC [16]=BUS_OFF |
| 0x3C | IRQ_STATUS | R/W1C | [0]=TX_DONE [1]=RX_AVAIL [2]=BUS_ERR |
| Test | Description |
|---|---|
| T01 | Standard frame loopback (11-bit ID) |
| T02 | Extended frame loopback (29-bit ID) |
| T03 | RTR frame (remote request) |
| T04 | DLC sweep 0–8 bytes |
| T05 | CRC-15 verification vs golden model |
| T06 | Bit stuffing correctness |
| T07 | Error counter increment/decrement |
| T08 | RX FIFO depth and overrun |
| T09 | Acceptance filter (disabled mode) |
| T10 | Bit timing configuration |
| T11 | Standard-frame acceptance filter (11-bit ID match/reject) |
| T12 | Large TSEG1=15 (verifies full bit_cnt in resync) |
| T13 | BUS_OFF / TEC width (9-bit tec_o, error_state check) |
| T14 | TX queue stress (3 buffers back-to-back, priority order) |
| T15 | Multi-config resync (4 BRP/TSEG configs in one test) |
Arty A7-100T @ 500 kbps via LiteX SoC + UARTBone. Standard loopback, extended loopback, DLC sweep, RTR, error counters, random frames.
can/
├── rtl/ # 8 files, 2,468 lines
│ ├── can_pkg.sv # Types, CRC polynomial, constants
│ ├── can_btl.sv # Bit Timing Logic
│ ├── can_bsp.sv # Bit Stream Processor (853 lines)
│ ├── can_crc.sv # CRC-15 module
│ ├── can_top.sv # TX queue, RX FIFO, filter, IRQ
│ ├── can_axil.sv # AXI4-Lite wrapper
│ ├── can_wb.sv # Wishbone B4 wrapper
│ └── can_axis.sv # AXI4-Stream wrapper
├── model/ # Python golden model
├── tb/directed/ # cocotb tests (10/10 PASS)
├── tb/uvm/ # UVM environment (11 files)
├── sim/ # Makefile.cocotb
├── litex/ # LiteX SoC integration
├── README.md
├── LICENSE
└── .gitignore
- 1 Mbps FPGA silicon verification
- Dedicated
CTRL[5]=FILTER_ENregister bit (replace code!=0 heuristic) - Hardware timestamping for RX frames
- Automatic retransmission on arbitration loss
- Multi-node clock-drift simulation (two can_top instances with offset BRP)
- Error injection test suite (bad CRC, stuff errors, form errors)
- CAN-FD (up to 8 Mbps data phase, 64-byte payload)
- TX event FIFO
- Power-down / sleep mode with wake-on-CAN
- UVM constrained-random regression (wire up existing tb/uvm/ environment)
- SkyWater 130nm silicon-proven version
- ISO 11898-2 integrated PHY interface (ASIC)
Dual license: Free for non-commercial use (Apache 2.0). Commercial use requires a Lumees Lab license.
See LICENSE for full terms.
Lumees Lab · Hasan Kurşun · lumeeslab.com · info@lumeeslab.com
Copyright © 2026 Lumees Lab. All rights reserved.