From cfdec1aec796f4fdb1cb536cefbf8a8528306852 Mon Sep 17 00:00:00 2001 From: Joppe Blondel Date: Thu, 5 Mar 2026 16:13:26 +0100 Subject: [PATCH] Added trigger to scope --- .../signal_scope/rtl/signal_scope_q15.v | 65 ++++++++++++++++--- .../signal/signal_scope/tool/capture_plot.py | 49 +++++++++++--- 2 files changed, 96 insertions(+), 18 deletions(-) diff --git a/cores/signal/signal_scope/rtl/signal_scope_q15.v b/cores/signal/signal_scope/rtl/signal_scope_q15.v index 3caf4ff..8425655 100644 --- a/cores/signal/signal_scope/rtl/signal_scope_q15.v +++ b/cores/signal/signal_scope/rtl/signal_scope_q15.v @@ -20,11 +20,19 @@ module signal_scope_q15 #( localparam [31:0] reg_base_addr = 32'h8000_0000; localparam [3:0] reg_control = 4'h0; localparam [3:0] reg_status = 4'h1; + localparam [3:0] reg_trig_val = 4'h2; (* ram_style = "block" *) reg [16*4-1:0] mem[depth-1:0]; reg [aw-1:0] counter; reg count_enable; reg arm_req; + reg trigger_enable; + reg scope_armed; + reg scope_triggered; + reg capture_done; + reg [15:0] trig_val; + reg [15:0] signal_a_prev; + reg signal_a_prev_valid; reg [15:0] signal_a; reg [15:0] signal_b; @@ -69,8 +77,15 @@ module signal_scope_q15 #( counter <= {aw{1'b0}}; count_enable <= 1'b0; arm_req <= 1'b0; + trigger_enable <= 1'b0; + scope_armed <= 1'b0; + scope_triggered <= 1'b0; + capture_done <= 1'b0; wb_ack <= 1'b0; wb_rdt <= 32'b0; + trig_val <= 16'h0000; + signal_a_prev <= 16'h0000; + signal_a_prev_valid <= 1'b0; signal_a <= 0; signal_b <= 0; signal_c <= 0; @@ -85,6 +100,18 @@ module signal_scope_q15 #( if(i_signal_valid_a) begin signal_a <= i_signal_a; signal_a_pending <= 1'b1; + + // Trigger on signal_a rising across trig_val. + if(scope_armed && trigger_enable && !count_enable) begin + if(signal_a_prev_valid && + ($signed(signal_a_prev) < $signed(trig_val)) && + ($signed(i_signal_a) >= $signed(trig_val))) begin + count_enable <= 1'b1; + scope_triggered <= 1'b1; + end + signal_a_prev <= i_signal_a; + signal_a_prev_valid <= 1'b1; + end end if(i_signal_valid_b) begin signal_b <= i_signal_b; @@ -99,10 +126,14 @@ module signal_scope_q15 #( signal_d_pending <= 1'b1; end - // Arm/rearm capture on control-register command pulse. + // Arm/rearm capture. If trigger is disabled, start capture immediately. if(arm_req) begin counter <= {aw{1'b0}}; - count_enable <= 1'b1; + count_enable <= !trigger_enable; + scope_armed <= 1'b1; + scope_triggered <= !trigger_enable; + capture_done <= 1'b0; + signal_a_prev_valid <= 1'b0; signal_a_pending <= 1'b0; signal_b_pending <= 1'b0; signal_c_pending <= 1'b0; @@ -114,10 +145,15 @@ module signal_scope_q15 #( if(counter <= depth_last) begin mem[counter] <= {signal_a, signal_b, signal_c, signal_d}; counter <= counter + {{(aw-1){1'b0}}, 1'b1}; - if(counter == depth_last) + if(counter == depth_last) begin count_enable <= 1'b0; + scope_armed <= 1'b0; + capture_done <= 1'b1; + end end else begin count_enable <= 1'b0; + scope_armed <= 1'b0; + capture_done <= 1'b1; end signal_a_pending <= 1'b0; signal_b_pending <= 1'b0; @@ -136,9 +172,17 @@ module signal_scope_q15 #( // can be added without touching memory-path logic. case(wb_reg_idx) reg_control: begin - // Bit 0: write-1 to arm/rearm scope (pulse). - if(wb_sel[0] && wb_dat[0]) - arm_req <= 1'b1; + if(wb_sel[0]) begin + // Bit 0: write-1 pulse to arm/rearm scope. + if(wb_dat[0]) + arm_req <= 1'b1; + // Bit 1: trigger enable. + trigger_enable <= wb_dat[1]; + end + end + reg_trig_val: begin + if(wb_sel[0]) trig_val[7:0] <= wb_dat[7:0]; + if(wb_sel[1]) trig_val[15:8] <= wb_dat[15:8]; end default: begin end @@ -147,10 +191,11 @@ module signal_scope_q15 #( end else begin if(wb_is_reg) begin case(wb_reg_idx) - // Write-pulse register: reads as zero. - reg_control: wb_rdt <= 32'b0; - // Basic status for polling/debug. - reg_status: wb_rdt <= {30'b0, count_enable, (counter == depth_last)}; + // [1]=trigger_enable, [0]=arm bit is write-pulse only. + reg_control: wb_rdt <= {30'b0, trigger_enable, 1'b0}; + // [0]=triggered, [1]=capturing, [2]=armed, [3]=done + reg_status: wb_rdt <= {28'b0, capture_done, scope_armed, count_enable, scope_triggered}; + reg_trig_val: wb_rdt <= {16'b0, trig_val}; default: wb_rdt <= 32'b0; endcase end else if(wb_mem_idx <= depth_last) begin diff --git a/cores/signal/signal_scope/tool/capture_plot.py b/cores/signal/signal_scope/tool/capture_plot.py index 2e6f835..e15abee 100755 --- a/cores/signal/signal_scope/tool/capture_plot.py +++ b/cores/signal/signal_scope/tool/capture_plot.py @@ -10,6 +10,7 @@ MEM_BASE = 0x00000000 REG_BASE = 0x80000000 REG_CONTROL = REG_BASE + 0x00 REG_STATUS = REG_BASE + 0x04 +REG_TRIG_VAL = REG_BASE + 0x08 def _add_bridge_module_path() -> None: @@ -46,6 +47,12 @@ def parse_args() -> argparse.Namespace: default=0.05, help="Seconds to wait after arm/dearm before reading", ) + parser.add_argument( + "--trigger-value", + type=lambda x: int(x, 0), + default=None, + help="Optional trigger threshold (16-bit, signal_a rising crossing). If omitted, triggering is disabled.", + ) parser.add_argument( "--unsigned", action="store_true", @@ -63,14 +70,38 @@ def parse_args() -> argparse.Namespace: action="store_true", help="Keep running: press Enter to recapture/replot in the same window", ) + parser.add_argument( + "--continuous", + action="store_true", + help="Keep running and recapture continuously without waiting for Enter", + ) return parser.parse_args() def capture_once(bridge, args: argparse.Namespace) -> list[tuple[int, int, int, int]]: samples = [] frame_count = args.depth - print("[signal_scope] Arming scope (write REG_CONTROL bit0=1)...") - bridge.write32(REG_CONTROL, 0x1) + if args.trigger_value is None: + print("[signal_scope] Arming scope with trigger disabled...") + bridge.write32(REG_CONTROL, 0x1) # bit0: arm pulse, bit1: trigger enable=0 + else: + trig_val = args.trigger_value & 0xFFFF + print(f"[signal_scope] Config trigger: trig_val=0x{trig_val:04x}, source=signal_a rising") + bridge.write32(REG_TRIG_VAL, trig_val) + print("[signal_scope] Arming scope with trigger enabled...") + bridge.write32(REG_CONTROL, 0x3) # bit0: arm pulse, bit1: trigger enable + + # Wait until the new arm command is active, then wait for its trigger event. + while (bridge.read32(REG_STATUS) & 0x4) == 0: + time.sleep(0.001) + + print("[signal_scope] Waiting for trigger...") + while True: + status = bridge.read32(REG_STATUS) + if status & 0x1: + break + time.sleep(0.001) + if args.wait_s > 0: print(f"[signal_scope] Waiting {args.wait_s:.3f}s for capture to complete...") time.sleep(args.wait_s) @@ -93,7 +124,6 @@ def capture_once(bridge, args: argparse.Namespace) -> list[tuple[int, int, int, samples.append((ch_a, ch_b, ch_c, ch_d)) if idx and (idx % max(1, frame_count // 10) == 0): pct = (100 * idx) // frame_count - print(f"[signal_scope] Read progress: {pct}% ({idx}/{frame_count})") print(f"[signal_scope] Read complete: {len(samples)} frames") return samples @@ -116,10 +146,10 @@ def plot_samples(ax, samples: list[tuple[int, int, int, int]], args: argparse.Na series[3].append(ch_d) ax.cla() - ax.plot(series[0], linewidth=1, label="ch_a") - ax.plot(series[1], linewidth=1, label="ch_b") - ax.plot(series[2], linewidth=1, label="ch_c") - ax.plot(series[3], linewidth=1, label="ch_d") + ax.plot(series[0], linewidth=1, label="ch_d") + ax.plot(series[1], linewidth=1, label="ch_c") + ax.plot(series[2], linewidth=1, label="ch_b") + ax.plot(series[3], linewidth=1, label="ch_a") ax.set_title(f"signal_scope_q15 capture #{capture_idx} (depth={args.depth}, chain={args.chain})") ax.set_xlabel("Sample") ax.set_ylabel("Value") @@ -178,10 +208,13 @@ def main() -> int: fig.savefig(out_path, dpi=150) print(f"Wrote plot: {out_path}") - if not args.interactive: + if not args.interactive and not args.continuous: break plt.show(block=False) + if args.continuous: + capture_idx += 1 + continue answer = input("[signal_scope] Press Enter to recapture, or 'q' + Enter to quit: ") if answer.strip().lower().startswith("q"): break