Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@
*.log
*.tmp
pytest_cache/
__pycache__/
__pycache__/
*.egg-info
tests/cocotb_tests/sim_build/
fft_ddc_performance_report.json
performance_plots/
tests/cocotb_tests/results.xml
183 changes: 0 additions & 183 deletions fft_ddc_performance_report.json

This file was deleted.

Binary file removed performance_plots/ddc_performance.png
Binary file not shown.
Binary file removed performance_plots/fft_performance.png
Binary file not shown.
17 changes: 17 additions & 0 deletions tests/cocotb_tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Makefile for cocotb tests

# Defaults
SIM ?= icarus
TOPLEVEL_LANG ?= verilog

# Point to the Verilog source files
VERILOG_SOURCES = $(shell find ../../verilog -name "*.v")

# TOPLEVEL is the name of the toplevel module in your Verilog design
TOPLEVEL ?= cic_decimator

# MODULE is the basename of the Python test file
MODULE ?= test_cic_decimator

# Include cocotb's make rules
include $(shell cocotb-config --makefiles)/Makefile.sim
1 change: 1 addition & 0 deletions tests/cocotb_tests/sim_build/cmds.f
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
+timescale+1ns/1ps
122 changes: 54 additions & 68 deletions tests/cocotb_tests/test_cic_decimator.py
Original file line number Diff line number Diff line change
@@ -1,85 +1,71 @@
# test_cic_decimator.py - cocotb testbench for CIC decimator validation
import cocotb
from cocotb.triggers import Timer, RisingEdge
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, Timer, FallingEdge
from cocotb.result import TestFailure
import numpy as np


@cocotb.test()
async def test_cic_decimator_basic(dut):
"""Basic CIC decimator functionality test"""
# Start 100 MHz system clock
clock = Clock(dut.clk, 10, units="ns")
cocotb.start_soon(clock.start())

# Reset sequence
async def reset_dut(dut):
dut.rst_n.value = 0
await Timer(100, units="ns")
await Timer(10, units='ns')
dut.rst_n.value = 1
await RisingEdge(dut.clk)

# Test impulse response (theoretical gain = decimation^stages = 8^3 = 512)
expected_gain = 512

# Send impulse
dut.data_in.value = 0x10000 # Impulse
dut.data_valid.value = 1
await RisingEdge(dut.clk)
dut.data_valid.value = 0

# Feed 7 zeros (total 8 samples for decimation by 8)
for i in range(7):
dut.data_in.value = 0
dut.data_valid.value = 1
await RisingEdge(dut.clk)
dut.data_valid.value = 0

# Wait for output (pipeline delay)
await Timer(1000, units="ns")

if dut.output_valid.value:
output_val = int(dut.data_out.value)
expected_output = (0x10000 << 21) >> 9 # Scaled accordingly per decimation
cocotb.log.info(f"CIC output: {output_val:08X}, expected range around {expected_output:08X}")
else:
raise TestFailure("CIC decimator did not produce valid output")

cocotb.log.info("CIC decimator basic test PASSED")

await Timer(10, units='ns')

@cocotb.test()
async def test_cic_decimator_saturation(dut):
"""Test CIC decimator saturation handling"""
async def test_cic_decimator(dut):
# Clock
clock = Clock(dut.clk, 10, units="ns")
cocotb.start_soon(clock.start())

# Reset
dut.rst_n.value = 0
await Timer(100, units="ns")
dut.rst_n.value = 1
await RisingEdge(dut.clk)

# Test with maximum input
dut.data_in.value = 0x7FFFFFFF # Maximum positive
dut.data_valid.value = 1
await RisingEdge(dut.clk)
dut.data_valid.value = 0

# Feed zeros
for i in range(7):
dut.data_in.value = 0
await reset_dut(dut)

# Test parameters
input_width = dut.INPUT_WIDTH.value
stages = dut.STAGES.value
decimation = dut.DECIMATION.value

# Generate input signal (a simple ramp)
test_data = np.arange(0, 256, 1, dtype=np.int32)

# Golden model
integrator = np.zeros(stages, dtype=np.int64)
comb = np.zeros(stages, dtype=np.int64)
comb_delay = np.zeros(stages, dtype=np.int64)
expected_output = []

for i in range(len(test_data)):
# Integrator stage
integrator[0] += test_data[i]
for j in range(1, stages):
integrator[j] += integrator[j-1]

# Decimator and Comb stage
if (i + 1) % decimation == 0:
temp_in = integrator[stages-1]
comb[0] = temp_in - comb_delay[0]
comb_delay[0] = temp_in
for j in range(1, stages):
temp_in = comb[j-1]
comb[j] = temp_in - comb_delay[j]
comb_delay[j] = temp_in

# Gain compensation
gain = decimation ** stages
output = comb[stages-1] // gain
expected_output.append(output)

# Drive DUT
output_from_dut = []
for data in test_data:
dut.data_in.value = int(data)
dut.data_valid.value = 1
await RisingEdge(dut.clk)
dut.data_valid.value = 0
if dut.output_valid.value == 1:
output_from_dut.append(dut.data_out.value.signed_integer)

# Wait for result
await Timer(1000, units="ns")
dut.data_valid.value = 0

if dut.output_valid.value:
output_val = int(dut.data_out.value)
assert abs(output_val) <= 0x7FFFFFFF, f"CIC output saturated incorrectly: {output_val:08X}"
else:
raise TestFailure("CIC decimator saturation test failed - no output")
# Compare results
assert len(output_from_dut) == len(expected_output), f"Output length mismatch: DUT={len(output_from_dut)}, Expected={len(expected_output)}"
for i in range(len(expected_output)):
assert output_from_dut[i] == expected_output[i], f"Mismatch at index {i}: DUT={output_from_dut[i]}, Expected={expected_output[i]}"

cocotb.log.info("CIC decimator saturation test PASSED")
35 changes: 35 additions & 0 deletions tests/test_edge_cases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest
import numpy as np
import sys
import os

sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'wideband-sdr-software'))
from digital_downconverter import DigitalDownconverter

def test_empty_input_signal():
ddc = DigitalDownconverter(1e6, 2e5, 1e5)
empty_signal = np.array([], dtype=np.complex64)
result = ddc.apply_ddc(empty_signal)
assert result.size == 0

def test_invalid_input_type():
ddc = DigitalDownconverter(1e6, 2e5, 1e5)
with pytest.raises(TypeError):
ddc.apply_ddc("not a numpy array")

def test_zero_bandwidth():
with pytest.raises(ValueError):
DigitalDownconverter(1e6, 2e5, 0)

def test_large_decimation_factor():
# This might not raise an error, but it's good to check behavior
ddc = DigitalDownconverter(1e6, 2e5, 100) # Very narrow bandwidth -> large decimation
signal = np.random.randn(10000) + 1j * np.random.randn(10000)
result = ddc.apply_ddc(signal)
assert result.size > 0

def test_mismatched_types():
ddc = DigitalDownconverter(1e6, 2e5, 1e5)
signal = np.random.randn(1024).astype(np.float32) # Real signal
with pytest.raises(TypeError):
ddc.apply_ddc(signal)
Loading