`timescale 1ns/1ps // ----------------------------------------------------------------------------- // pwm_trace_measure // // Passive digital-signal measurement core. // // Measures: // 1) edge_count : total rising-edge count since reset // 2) period_ticks : clocks between two consecutive rising edges // 3) high_ticks : clocks that the input stayed high in that same period // // With a 25 MHz FPGA clock: // frequency_hz = 25_000_000 / period_ticks // duty_percent = high_ticks * 100 / period_ticks // // The observed signal is treated as asynchronous to clk, therefore a // two-flop synchronizer is used before edge detection. // ----------------------------------------------------------------------------- module pwm_trace_measure #( parameter integer CLK_HZ = 25_000_000, parameter integer NO_SIGNAL_MS = 100 )( input wire clk, input wire reset_n, input wire signal_in, output reg [31:0] edge_count, output reg [31:0] period_ticks, output reg [31:0] high_ticks, output reg measurement_valid, output reg signal_present, output wire signal_level ); localparam integer NO_SIGNAL_TICKS = (CLK_HZ / 1000) * NO_SIGNAL_MS; // Synchronizer + delayed sample for edge detection. reg signal_meta; reg signal_sync; reg signal_sync_d; wire rising_edge = signal_sync & ~signal_sync_d; wire falling_edge = ~signal_sync & signal_sync_d; // Raw synchronized level is reported separately from period validity so // static LOW and static HIGH remain distinguishable after edge timeout. assign signal_level = signal_sync; reg [31:0] period_counter; reg [31:0] high_counter; reg [31:0] no_signal_counter; reg have_rise; reg have_high; always @(posedge clk or negedge reset_n) begin if (!reset_n) begin signal_meta <= 1'b0; signal_sync <= 1'b0; signal_sync_d <= 1'b0; end else begin signal_meta <= signal_in; signal_sync <= signal_meta; signal_sync_d <= signal_sync; end end always @(posedge clk or negedge reset_n) begin if (!reset_n) begin edge_count <= 32'd0; period_ticks <= 32'd0; high_ticks <= 32'd0; measurement_valid <= 1'b0; signal_present <= 1'b0; period_counter <= 32'd0; high_counter <= 32'd0; no_signal_counter <= 32'd0; have_rise <= 1'b0; have_high <= 1'b0; end else begin // --------------------------------------------------------- // Rising edge: // - counts one pulse // - closes the previous complete period // - starts a new period/high counter // --------------------------------------------------------- if (rising_edge) begin edge_count <= edge_count + 1'b1; signal_present <= 1'b1; no_signal_counter <= 32'd0; if (have_rise) begin period_ticks <= period_counter; // high_ticks was latched by the previous falling edge, // therefore period_ticks + high_ticks belong to the // same just-finished PWM period. if (have_high) measurement_valid <= 1'b1; end period_counter <= 32'd1; high_counter <= 32'd1; have_rise <= 1'b1; have_high <= 1'b0; end else begin if (have_rise && period_counter != 32'hFFFF_FFFF) period_counter <= period_counter + 1'b1; if (have_rise && signal_sync && high_counter != 32'hFFFF_FFFF) high_counter <= high_counter + 1'b1; // ----------------------------------------------------- // No-signal timeout. // Keep edge_count, but clear frequency/duty information. // ----------------------------------------------------- if (signal_present) begin if (no_signal_counter >= NO_SIGNAL_TICKS - 1) begin no_signal_counter <= 32'd0; signal_present <= 1'b0; measurement_valid <= 1'b0; period_ticks <= 32'd0; high_ticks <= 32'd0; period_counter <= 32'd0; high_counter <= 32'd0; have_rise <= 1'b0; have_high <= 1'b0; end else begin no_signal_counter <= no_signal_counter + 1'b1; end end end // --------------------------------------------------------- // Falling edge closes the HIGH portion of the current PWM // period. The value is paired with period_ticks on the next // rising edge. // --------------------------------------------------------- if (falling_edge && have_rise) begin high_ticks <= high_counter; have_high <= 1'b1; end end end endmodule