Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
94da26c
misc: update readme, add icon
kcaisley Apr 2, 2026
bf1aa85
fix(spi): replace legacy RAMB16_S1_S9 with inferred BRAM
kcaisley Apr 20, 2026
2c1b413
fix: replace 8-bit array with flat 1-bit array for RAMB18E1 inference
kcaisley Apr 29, 2026
507b474
Update imports
kcaisley Apr 30, 2026
3b546c9
fix: add guarded includes for shared utility modules
kcaisley Apr 30, 2026
6bd888c
chore(firmware): Adding explicit imports, with guard statement, to su…
kcaisley May 4, 2026
43d5fe0
fix: Change alignment of INFO statements in logging
kcaisley May 5, 2026
1d92886
feat(utils): add address-map bus checker utility
kcaisley May 5, 2026
75fd93f
chore: Cleanup typos
kcaisley May 12, 2026
41c6498
feat: update primitive module names and add simulation models
kcaisley May 6, 2026
24166e2
feat(fast_spi_rx): Add variable DATA_SIZE support with convenience me…
kcaisley May 20, 2026
a14cb2b
docs(fast_spi_rx): Add comprehensive docstrings to all methods
kcaisley May 20, 2026
90fdc82
docs(gpio): Document set_output_en, get_output_en, set_data, get_data
kcaisley May 20, 2026
77629b5
docs(pulse_gen): Document is_ready, get_width, set_delay, reset, set_…
kcaisley May 20, 2026
ae1d045
docs(seq_gen): Add docstrings to all 22 methods and properties
kcaisley May 20, 2026
d70badf
docs(spi): Document get_mem_size, is_ready, is_done, improve set_data…
kcaisley May 20, 2026
007e63b
docs(fast_spi_rx): Rewrite README with consistent format and usage notes
kcaisley May 20, 2026
cc81ab3
docs(seq_gen): Add usage notes, fix typos, capitalize descriptions
kcaisley May 20, 2026
281377c
docs(spi): Add usage notes, fix register names, capitalize descriptions
kcaisley May 20, 2026
f736bcf
docs(gpio): Add StdRegister usage notes, capitalize descriptions
kcaisley May 20, 2026
2e9b603
docs(pulse_gen): Add usage notes, capitalize descriptions, fix REPEAT…
kcaisley May 20, 2026
a1f0675
fix(docs): Use double backticks for register names in READMEs
kcaisley May 20, 2026
53a6d75
docs(firmware): Merge shared-address registers in README tables
kcaisley May 20, 2026
cc3f30b
fix(fastrx): Bump fastrx driver version, since I changed API
kcaisley May 20, 2026
095739f
feat(fast_spi_rx): Expose DATA_SIZE as read-only register at addr 4
kcaisley May 20, 2026
9f24ddb
style(seq_gen): Fix all pydocstyle (D) ruff warnings
kcaisley May 20, 2026
f443463
style: Fix all pydocstyle D warnings across HL drivers
kcaisley May 20, 2026
3637142
fix(fast_spi_rx): Bump firmware version to 1 for DATA_SIZE register
kcaisley May 20, 2026
b53a283
feat(fast_spi_rx): Expose DATA_SIZE as read-only register at addr 4
kcaisley May 20, 2026
30c53db
fix: Remove authors accidentally reintroduced during rebase
kcaisley May 20, 2026
37b3e10
fix: Correct straggling mis-formatted doc strings
kcaisley May 20, 2026
be65509
fix: update Verilog simulation primitives
kcaisley May 22, 2026
356e844
feat: add KA3005P driver
kcaisley May 22, 2026
e988ca4
feat: add JTAG TAP include module
kcaisley May 22, 2026
6f7fef7
refactor: normalize Verilog include guards
kcaisley May 22, 2026
bcbeb0f
chore: update simulation test config
kcaisley May 22, 2026
0b6a992
fix: clean up Verilator simulation lint issues
kcaisley May 22, 2026
3d35ef5
refactor: share JTAG TAP simulation source
kcaisley May 22, 2026
573bb7d
fix: satisfy stricter Verilator SPI warnings
kcaisley May 22, 2026
b1e0a3e
style(spi): format spi core
kcaisley May 26, 2026
a9a4475
fix(firmwave): Simplify include guard to naming scheme
kcaisley May 27, 2026
d555e32
Merge remote-tracking branch 'refs/remotes/origin/cosimulation' into …
kcaisley May 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ host/tests/Makefile
docs/_build
*.vcd
tests/results.xml
sim_build
Makefile
*.out
examples/lx9/device/src/SiTCP

# PyDev files
Expand Down
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Basil

[![Build status](https://github.com/SiLab-Bonn/basil/workflows/Regression%20Tests/badge.svg)](https://github.com/silab-bonn/basil/actions?query=workflow%3A%22Regression+Tests%22)
[![Documentation](https://readthedocs.org/projects/basil/badge/?version=latest)](http://basil.rtfd.org)

Basil is a modular data acquisition (DAQ) framework developed by [SiLab](https://silab-bonn.github.io/) for the characterization of [monolithic](https://en.wikipedia.org/wiki/Monolithic_active_pixel_sensor) and [hybrid](https://en.wikipedia.org/wiki/Hybrid_pixel_detector) pixel detectors. It comprises a library of HDL modules (written in Verilog) for custom FPGA readout boards, paired with a collection of Python code that control the hardware over USB, Ethernet, or serial interfaces from a host PC. Basil also includes Python drivers for common lab instruments such as power supplies, oscilloscopes, and other bench equipment.

## Features

**Firmware:**
- Very simple single master bus definition
- Multiple basic modules (SPI, SEQ, GPIO, I2C, JTAG)
- Multiple interfaces (UART, USB2, USB3, Ethernet)

**Software:**
- Layer structure following hardware
- Generation based on YAML file
- Register abstract layer (RAL)
- Simulator interface allows software test against simulated RTL (thanks to [cocotb](https://github.com/cocotb/cocotb))

## Installation

Install via PyPI:

```bash
pip install basil-daq
```

> **Note:** The PyPI package may be outdated. Installing from source (below) is recommended to get the latest version.

Or install from source:

```bash
git clone https://github.com/SiLab-Bonn/basil.git
cd basil
pip install -e .
```

## Support

Please use GitHub's [issue tracker](https://github.com/SiLab-Bonn/basil/issues) for bug reports/feature requests/questions.

*For CERN users*: Feel free to subscribe to the [basil mailing list](https://e-groups.cern.ch/e-groups/EgroupsSubscription.do?egroupName=basil-devel).

## Documentation

Documentation can be found at: https://basil.rtfd.org

## Example Projects

- [TJ-Monopix2](https://github.com/SiLab-Bonn/tj-monopix2-daq) - DAQ for TJ-Monopix2 depleted monolithic pixel sensor
- [BDAQ53](https://gitlab.cern.ch/silab/bdaq53) - Readout system for ATLAS ITkPix (RD53) chips
- [LF-Monopix2](https://github.com/SiLab-Bonn/lf-monopix2-daq) - DAQ for LF-Monopix2 depleted monolithic pixel sensor
- [FRIDA](https://github.com/kcaisley/frida) - DAQ for FRIDA, an ADC test array for frame-based imaging detectors

## License

If not stated otherwise:

**Host Software:**
The host software is distributed under the BSD 3-Clause ("BSD New" or "BSD Simplified") License.

**FPGA Firmware:**
The FPGA code is distributed under the GNU Lesser General Public License, version 3.0 (LGPLv3).
78 changes: 0 additions & 78 deletions README.rst

This file was deleted.

73 changes: 70 additions & 3 deletions basil/HL/fast_spi_rx.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,100 @@
# SiLab, Institute of Physics, University of Bonn
# ------------------------------------------------------------
#
"""Fast SPI receive interface for reading variable-width serial data.

Provides a register-level hardware layer to arm/disarm capture, query
frame-size configuration, check for lost words, and parse 32-bit FIFO
words into (identifier, frame_counter, spi_data) tuples.
"""

from basil.HL.RegisterHardwareLayer import RegisterHardwareLayer


class fast_spi_rx(RegisterHardwareLayer):
"""Fast SPI interface"""
"""Fast SPI interface with variable data width support.

The module outputs 32-bit words containing:
- IDENTIFIER (4 bits)
- Frame counter (28 - DATA_SIZE bits)
- SPI data (DATA_SIZE bits)

The DATA_SIZE parameter must match the DATA_SIZE parameter used in the
FPGA firmware (fast_spi_rx_core.v).

Captured data is read via the dedicated FIFO output ports (not the register
bus). At the system level, these feed into a SiTCP FIFO stream accessed
through daq["fifo0"].get_data().
"""

_registers = {
"RESET": {"descr": {"addr": 0, "size": 8, "properties": ["writeonly"]}},
"VERSION": {"descr": {"addr": 0, "size": 8, "properties": ["ro"]}},
"EN": {"descr": {"addr": 2, "size": 1, "offset": 0}},
"LOST_COUNT": {"descr": {"addr": 3, "size": 8, "properties": ["ro"]}},
"DATA_SIZE": {"descr": {"addr": 4, "size": 8, "properties": ["ro"]}},
}
_require_version = "==0"
_require_version = "==1"

def __init__(self, intf, conf):
"""Initialize the fast_spi_rx hardware layer.

Args:
intf: The low-level interface to the hardware.
conf: Configuration dictionary passed to the base class.

"""
super(fast_spi_rx, self).__init__(intf, conf)

def get_size(self):
"""Return the DATA_SIZE (SPI data width in bits) used for parsing captured words.

Reads the value from the hardware DATA_SIZE register (addr 4).
"""
return self.DATA_SIZE

def reset(self):
"""Soft reset the module."""
"""Soft reset the module. Clears internal counters and shift registers on the next SEQ_CLK edge."""
self.RESET = 0

def set_en(self, value):
"""Arm/disarm capture.

When enabled, serial data on SDI is captured on each rising edge of
SEQ_CLK while SEN is high.
"""
self.EN = value

def get_en(self):
"""Return whether capture is armed (True) or disarmed (False)."""
return self.EN

def get_lost_count(self):
"""Return the count of lost data words due to CDC FIFO overflow.

Non-zero indicates the capture rate exceeded the readout rate.
"""
return self.LOST_COUNT

def parse_word(self, word):
"""Parse a 32-bit FIFO word into (identifier, frame_counter, spi_data).

The split between frame counter and captured data is determined
by get_size(). Useful for parsing words read via daq["fifo0"].get_data().

Args:
word: A 32-bit integer from the fast_spi_rx output FIFO.

Returns:
tuple: (identifier, frame_counter, spi_data)

"""
data_size = self.get_size()
identifier = (word >> 28) & 0xF
spi_data = word & ((1 << data_size) - 1)
frame_counter_bits = 28 - data_size
if frame_counter_bits > 0:
frame_counter = (word >> data_size) & ((1 << frame_counter_bits) - 1)
else:
frame_counter = 0
return identifier, frame_counter, spi_data
10 changes: 9 additions & 1 deletion basil/HL/gpio.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
# ------------------------------------------------------------
#

"""GPIO hardware layer for Basil."""

from basil.HL.RegisterHardwareLayer import RegisterHardwareLayer


class gpio(RegisterHardwareLayer):
"""GPIO interface"""
"""GPIO interface."""

def __init__(self, intf, conf):
"""Initialize GPIO interface."""
self._registers = {
"RESET": {"descr": {"addr": 0, "size": 8, "properties": ["writeonly"]}},
"VERSION": {"descr": {"addr": 0, "size": 8, "properties": ["ro"]}},
Expand All @@ -35,6 +38,7 @@ def __init__(self, intf, conf):
super(gpio, self).__init__(intf, conf)

def init(self):
"""Initialize the hardware."""
super(gpio, self).init()
if "output_en" in self._init:
self.OUTPUT_EN = self._init["output_en"]
Expand All @@ -44,13 +48,17 @@ def reset(self):
self.RESET = 0

def set_output_en(self, value):
"""Set the output enable mask. Each bit enables output mode for the corresponding pin (1=output, 0=high-impedance/input). Requires IO_TRI to be configured in the firmware parameter."""
self.OUTPUT_EN = value

def get_output_en(self):
"""Return the output enable mask. Each bit indicates whether the corresponding pin is in output mode (1) or input mode (0)."""
return self.OUTPUT_EN

def set_data(self, value):
"""Set the GPIO OUTPUT register. Writes the full IO_WIDTH byte to the FPGA, driving output pins to the specified logic levels. Typically used via StdRegister .write() for field-level access."""
self.OUTPUT = value

def get_data(self):
"""Read the GPIO INPUT register. Returns the current logic levels on all pins as a byte array. Reads the physical pin state regardless of direction configuration."""
return self.INPUT
89 changes: 89 additions & 0 deletions basil/HL/ka3005p.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#
# ------------------------------------------------------------
# Copyright (c) All rights reserved
# SiLab, Institute of Physics, University of Bonn
# ------------------------------------------------------------
#

"""Driver for the Korad KA3005P programmable DC power supply."""

import logging
from time import sleep

from basil.HL.HardwareLayer import HardwareLayer

logger = logging.getLogger(__name__)


class ka3005p(HardwareLayer):
"""Driver for the Korad KA3005P programmable DC power supply.

Communicates over a serial (RS-232) interface. Up to 30 V / 5 A.
"""

def __init__(self, intf, conf):
super(ka3005p, self).__init__(intf, conf)

def init(self):
"""Initialize the power supply.

Sets a safe current limit of 100 mA to protect the DUT.
"""
super(ka3005p, self).init()
self.set_current(0.1)

def set_voltage(self, voltage):
"""Set the output voltage.

Args:
voltage: Output voltage in volts. Clipped to 30 V max.
"""
if voltage > 30:
voltage = 30
cmd = "VSET1:%.2f" % round(voltage, 2)
self._intf.write(cmd)
sleep(0.05)

def set_current(self, current):
"""Set the output current limit.

Args:
current: Current limit in amps. Clipped to 5 A max.
"""
if current > 5:
current = 5
cmd = "ISET1:%.3f" % round(current, 3)
self._intf.write(cmd)
sleep(0.05)

def enable_output(self):
"""Enable the DC output (OUT1)."""
self._intf.write("OUT1")
sleep(0.5)

def disable_output(self):
"""Disable the DC output (OUT0)."""
self._intf.write("OUT0")
sleep(0.1)

def get_voltage(self):
"""Read back the actual output voltage.

Returns:
float: Output voltage in volts.
"""
self._intf.write("VOUT1?")
sleep(0.1)
response = self._intf.read()
return float(response)

def get_current(self):
"""Read back the actual output current.

Returns:
float: Output current in amps.
"""
self._intf.write("IOUT1?")
sleep(0.1)
response = self._intf.read()
return float(response)
Loading
Loading