diff --git a/Makefile b/Makefile index 6538175..f190fef 100644 --- a/Makefile +++ b/Makefile @@ -113,6 +113,7 @@ endef MODULES += axis_replay_buffer MODULES += descramble +MODULES += led_blinker MODULES += mdio MODULES += mdio_io MODULES += mdio_regs diff --git a/rtl/led_blinker.v b/rtl/led_blinker.v new file mode 100644 index 0000000..59e07e5 --- /dev/null +++ b/rtl/led_blinker.v @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: AGPL-3.0-Only +/* + * Copyright (C) 2023 Sean Anderson + * + * This is an LED blinker designed to make it easier to monitor internal + * signals with LEDs. The blinker is active for ~16 and inactive for ~16 ms. + * When triggered, the corresponding output will go high the next time + * the blinker becomes active. This results in blinking at 30 Hz if + * continuously triggered. All outputs blink at the same time. + */ + +`include "common.vh" + +module led_blinker ( + input clk, + input [LEDS - 1:0] triggers, + output reg [LEDS - 1:0] out, + + input test_mode +); + + parameter LEDS = 2; + + localparam TIMER_RESET = 21'h1ffffe; + /* 16 cycles before the end */ + localparam TEST_TIMER_RESET = 21'h0ccccf; + + reg active, active_next; + reg [LEDS - 1:0] out_next, triggered, triggered_next; + reg [20:0] lfsr, lfsr_next; + + initial begin + active = 0; + triggered = {LEDS{1'b0}}; + out = {LEDS{1'b0}}; + lfsr = TEST_TIMER_RESET; + end + + always @(*) begin + active_next = active; + triggered_next = triggered | triggers; + out_next = out; + lfsr_next = { lfsr[19:0], lfsr[20] ^ lfsr[18] }; + if (&lfsr) begin + if (active) begin + active_next = 0; + triggered_next = triggered_next & ~out; + out_next = {LEDS{1'b0}}; + end else begin + active_next = 1; + out_next = triggered; + end + lfsr_next = test_mode ? 21'hCCCCF : 21'h1FFFFE; + end + end + + always @(posedge clk) begin + active <= active_next; + triggered <= triggered_next; + out <= out_next; + lfsr <= lfsr_next; + end + +endmodule diff --git a/tb/led_blinker.py b/tb/led_blinker.py new file mode 100644 index 0000000..60e21bd --- /dev/null +++ b/tb/led_blinker.py @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: AGPL-3.0-Only +# Copyright (C) 2023 Sean Anderson + +import cocotb +from cocotb.binary import BinaryValue +from cocotb.clock import Clock +from cocotb.triggers import ClockCycles, FallingEdge, Timer + +from .util import BIT + +@cocotb.test(timeout_time=1, timeout_unit='us') +async def test_elastic(led): + led.clk.value = BinaryValue('Z') + led.triggers.value = 0 + led.test_mode.value = 1 + + await Timer(1) + await cocotb.start(Clock(led.clk, 2, units='ns').start()) + + await FallingEdge(led.clk) + assert not led.out.value + + led.triggers.value = 1 + await FallingEdge(led.clk) + assert not led.out.value + + led.triggers.value = 0 + while not led.out.value: + await FallingEdge(led.clk) + assert led.out.value == 1 + + led.triggers.value = 1 + await FallingEdge(led.clk) + + led.triggers.value = 0 + await ClockCycles(led.clk, 16, False) + led.triggers.value = 2 + + await FallingEdge(led.clk) + assert not led.out.value + while not led.out.value: + await FallingEdge(led.clk) + assert led.out.value == 2