module control #( parameter int unsigned DAC_DATA_WIDTH = 12 ) ( input logic eth_clk_in, input logic dac_clk_in, input logic adc_clk_in, input logic rst_n, // AXI stream slave, eth_clk_in domain input logic [7:0] s_axis_tdata, input logic s_axis_tvalid, output logic s_axis_tready, input logic s_axis_tlast, // adc_clk_in domain input logic finish, // dac_clk_in domain outputs output logic [31:0] dac_pulse_width, output logic [31:0] dac_pulse_period, output logic [DAC_DATA_WIDTH-1:0] dac_pulse_height, output logic [15:0] dac_pulse_num, // adc_clk_in domain outputs output logic [31:0] adc_pulse_period, output logic [15:0] adc_pulse_num, // pulse outputs output logic dac_start, output logic adc_start, output logic dac_rst, output logic adc_rst ); // static checks initial begin if (DAC_DATA_WIDTH > 16) begin $error("DAC_DATA_WIDTH must be <= 16"); end if (DAC_DATA_WIDTH == 0) begin $error("DAC_DATA_WIDTH must be > 0"); end end // command constants localparam logic [7:0] CMD_SOFT_RESET = 8'h0F; localparam logic [7:0] CMD_START = 8'hF0; localparam logic [7:0] CMD_SET_DATA = 8'h88; // reset synchronizers: async assert, sync deassert in each domain logic eth_rst_ff1, eth_rst_ff2; logic dac_rst_ff1, dac_rst_ff2; logic adc_rst_ff1, adc_rst_ff2; logic eth_rst; logic dac_rst_int; logic adc_rst_int; always_ff @(posedge eth_clk_in or negedge rst_n) begin if (!rst_n) begin eth_rst_ff1 <= 1'b1; eth_rst_ff2 <= 1'b1; end else begin eth_rst_ff1 <= 1'b0; eth_rst_ff2 <= eth_rst_ff1; end end always_ff @(posedge dac_clk_in or negedge rst_n) begin if (!rst_n) begin dac_rst_ff1 <= 1'b1; dac_rst_ff2 <= 1'b1; end else begin dac_rst_ff1 <= 1'b0; dac_rst_ff2 <= dac_rst_ff1; end end always_ff @(posedge adc_clk_in or negedge rst_n) begin if (!rst_n) begin adc_rst_ff1 <= 1'b1; adc_rst_ff2 <= 1'b1; end else begin adc_rst_ff1 <= 1'b0; adc_rst_ff2 <= adc_rst_ff1; end end assign eth_rst = eth_rst_ff2; assign dac_rst_int = dac_rst_ff2; assign adc_rst_int = adc_rst_ff2; // axi stream is always accepted. If packet is not needed, it is discarded. assign s_axis_tready = 1'b1; (* MARK_DEBUG="true" *) wire axis_hs = s_axis_tvalid & s_axis_tready; // ------------------------------------------------------------------------- // Shared 96-bit config bus in ETH domain // // Byte order for SET_DATA payload, little-endian: // payload byte 0 -> cfg_bus_eth[7:0] // payload byte 1 -> cfg_bus_eth[15:8] // ...etc... // payload byte 11 -> cfg_bus_eth[95:88] // // Field layout inside cfg_bus_eth: // [31:0] pulse_width // [63:32] pulse_period // [79:64] pulse_num // [95:80] pulse_height_raw[15:0] // ------------------------------------------------------------------------- (* MARK_DEBUG="true" *) logic [95:0] cfg_bus_eth; logic [95:0] cfg_shift_eth; // ETH-domain parser and control typedef enum logic [2:0] { ST_IDLE = 3'd0, ST_RECV_CFG = 3'd1, ST_WAIT_CFG_ACK = 3'd2, ST_DISCARD = 3'd3 } eth_state_t; (* MARK_DEBUG="true" *) eth_state_t eth_state; logic [3:0] cfg_byte_cnt; // Busy flag: set by START command, cleared by finish event from ADC domain (* MARK_DEBUG="true" *) logic busy_flag_eth; // Pending ACKs for config delivery logic cfg_wait_dac_ack; logic cfg_wait_adc_ack; // Event toggles ETH -> DAC/ADC logic start_toggle_eth; logic rst_toggle_eth; // Config request toggles ETH -> DAC/ADC logic cfg_req_toggle_dac_eth; logic cfg_req_toggle_adc_eth; // ACK toggles DAC/ADC -> ETH logic cfg_ack_toggle_dac; logic cfg_ack_toggle_adc; (* ASYNC_REG = "TRUE" *) logic cfg_ack_toggle_dac_meta, cfg_ack_toggle_dac_sync, cfg_ack_toggle_dac_sync_d; (* ASYNC_REG = "TRUE" *) logic cfg_ack_toggle_adc_meta, cfg_ack_toggle_adc_sync, cfg_ack_toggle_adc_sync_d; wire cfg_ack_pulse_dac_eth = cfg_ack_toggle_dac_sync ^ cfg_ack_toggle_dac_sync_d; wire cfg_ack_pulse_adc_eth = cfg_ack_toggle_adc_sync ^ cfg_ack_toggle_adc_sync_d; always_ff @(posedge eth_clk_in or posedge eth_rst) begin if (eth_rst) begin cfg_ack_toggle_dac_meta <= 1'b0; cfg_ack_toggle_dac_sync <= 1'b0; cfg_ack_toggle_dac_sync_d <= 1'b0; cfg_ack_toggle_adc_meta <= 1'b0; cfg_ack_toggle_adc_sync <= 1'b0; cfg_ack_toggle_adc_sync_d <= 1'b0; end else begin cfg_ack_toggle_dac_meta <= cfg_ack_toggle_dac; cfg_ack_toggle_dac_sync <= cfg_ack_toggle_dac_meta; cfg_ack_toggle_dac_sync_d <= cfg_ack_toggle_dac_sync; cfg_ack_toggle_adc_meta <= cfg_ack_toggle_adc; cfg_ack_toggle_adc_sync <= cfg_ack_toggle_adc_meta; cfg_ack_toggle_adc_sync_d <= cfg_ack_toggle_adc_sync; end end // finish event: ADC -> ETH via toggle CDC logic finish_toggle_adc; logic finish_meta_eth, finish_sync_eth, finish_sync_eth_d; wire finish_pulse_eth = finish_sync_eth ^ finish_sync_eth_d; always_ff @(posedge adc_clk_in or posedge adc_rst_int) begin if (adc_rst_int) begin finish_toggle_adc <= 1'b0; end else if (finish) begin finish_toggle_adc <= ~finish_toggle_adc; end end always_ff @(posedge eth_clk_in or posedge eth_rst) begin if (eth_rst) begin finish_meta_eth <= 1'b0; finish_sync_eth <= 1'b0; finish_sync_eth_d <= 1'b0; end else begin finish_meta_eth <= finish_toggle_adc; finish_sync_eth <= finish_meta_eth; finish_sync_eth_d <= finish_sync_eth; end end // ETH FSM always_ff @(posedge eth_clk_in or posedge eth_rst) begin if (eth_rst) begin eth_state <= ST_IDLE; cfg_byte_cnt <= '0; cfg_shift_eth <= '0; cfg_bus_eth <= '0; busy_flag_eth <= 1'b0; start_toggle_eth <= 1'b0; rst_toggle_eth <= 1'b0; cfg_req_toggle_dac_eth <= 1'b0; cfg_req_toggle_adc_eth <= 1'b0; cfg_wait_dac_ack <= 1'b0; cfg_wait_adc_ack <= 1'b0; end else begin // finish always clears busy if (finish_pulse_eth) begin busy_flag_eth <= 1'b0; end // config acks if (cfg_ack_pulse_dac_eth) begin cfg_wait_dac_ack <= 1'b0; end if (cfg_ack_pulse_adc_eth) begin cfg_wait_adc_ack <= 1'b0; end case (eth_state) ST_IDLE: begin cfg_byte_cnt <= '0; cfg_shift_eth <= cfg_shift_eth; if (axis_hs) begin // if busy, drop the whole packet if (busy_flag_eth) begin if (!s_axis_tlast) begin eth_state <= ST_DISCARD; end end else begin unique case (s_axis_tdata) CMD_SOFT_RESET: begin rst_toggle_eth <= ~rst_toggle_eth; end CMD_START: begin start_toggle_eth <= ~start_toggle_eth; busy_flag_eth <= 1'b1; end CMD_SET_DATA: begin // expect exactly 12 bytes after command if (s_axis_tlast) begin // no payload, invalid packet eth_state <= ST_IDLE; end else begin cfg_byte_cnt <= 4'd0; cfg_shift_eth <= '0; eth_state <= ST_RECV_CFG; end end default: begin // unknown command: discard packet remainder if any if (!s_axis_tlast) begin eth_state <= ST_DISCARD; end end endcase end end end ST_RECV_CFG: begin if (axis_hs) begin // little endian packing cfg_shift_eth[cfg_byte_cnt*8 +: 8] <= s_axis_tdata; if (cfg_byte_cnt == 4'd11) begin // this must be the final payload byte if (s_axis_tlast) begin cfg_bus_eth <= {s_axis_tdata, cfg_shift_eth[87:0]}; cfg_req_toggle_dac_eth <= ~cfg_req_toggle_dac_eth; cfg_req_toggle_adc_eth <= ~cfg_req_toggle_adc_eth; cfg_wait_dac_ack <= 1'b1; cfg_wait_adc_ack <= 1'b1; eth_state <= ST_WAIT_CFG_ACK; end else begin // too many bytes in packet eth_state <= ST_DISCARD; end end else begin // early tlast means packet too short!! if (s_axis_tlast) begin eth_state <= ST_IDLE; end else begin cfg_byte_cnt <= cfg_byte_cnt + 4'd1; end end end end ST_WAIT_CFG_ACK: begin // any incoming packet while waiting ack is discarded if (cfg_ack_pulse_dac_eth || cfg_ack_pulse_adc_eth) begin if ((~cfg_wait_dac_ack || cfg_ack_pulse_dac_eth) && (~cfg_wait_adc_ack || cfg_ack_pulse_adc_eth)) begin eth_state <= ST_IDLE; end end if (axis_hs && !s_axis_tlast) begin eth_state <= ST_DISCARD; end end ST_DISCARD: begin if (axis_hs && s_axis_tlast) begin eth_state <= ST_IDLE; end end default: begin eth_state <= ST_IDLE; end endcase end end // ETH -> DAC: start/reset event sync (* ASYNC_REG = "TRUE" *) logic start_meta_dac, start_sync_dac; logic start_sync_dac_d; (* ASYNC_REG = "TRUE" *) logic rst_meta_dac, rst_sync_dac; logic rst_sync_dac_d; wire dac_start_pulse = start_sync_dac ^ start_sync_dac_d; wire dac_rst_pulse = rst_sync_dac ^ rst_sync_dac_d; always_ff @(posedge dac_clk_in or posedge dac_rst_int) begin if (dac_rst_int) begin start_meta_dac <= 1'b0; start_sync_dac <= 1'b0; start_sync_dac_d <= 1'b0; rst_meta_dac <= 1'b0; rst_sync_dac <= 1'b0; rst_sync_dac_d <= 1'b0; dac_start <= 1'b0; dac_rst <= 1'b0; end else begin start_meta_dac <= start_toggle_eth; start_sync_dac <= start_meta_dac; start_sync_dac_d <= start_sync_dac; rst_meta_dac <= rst_toggle_eth; rst_sync_dac <= rst_meta_dac; rst_sync_dac_d <= rst_sync_dac; dac_start <= dac_start_pulse; dac_rst <= dac_rst_pulse; end end // ETH -> ADC: start/reset event sync (* ASYNC_REG = "TRUE" *) logic start_meta_adc, start_sync_adc; logic start_sync_adc_d; (* ASYNC_REG = "TRUE" *) logic rst_meta_adc, rst_sync_adc; logic rst_sync_adc_d; wire adc_start_pulse = start_sync_adc ^ start_sync_adc_d; wire adc_rst_pulse = rst_sync_adc ^ rst_sync_adc_d; always_ff @(posedge adc_clk_in or posedge adc_rst_int) begin if (adc_rst_int) begin start_meta_adc <= 1'b0; start_sync_adc <= 1'b0; start_sync_adc_d <= 1'b0; rst_meta_adc <= 1'b0; rst_sync_adc <= 1'b0; rst_sync_adc_d <= 1'b0; adc_start <= 1'b0; adc_rst <= 1'b0; end else begin start_meta_adc <= start_toggle_eth; start_sync_adc <= start_meta_adc; start_sync_adc_d <= start_sync_adc; rst_meta_adc <= rst_toggle_eth; rst_sync_adc <= rst_meta_adc; rst_sync_adc_d <= rst_sync_adc; adc_start <= adc_start_pulse; adc_rst <= adc_rst_pulse; end end // ETH -> DAC config CDC // cfg_bus_eth is kept stable in ETH domain until DAC and ADC both ACK. (* ASYNC_REG = "TRUE" *) logic cfg_req_meta_dac, cfg_req_sync_dac; logic cfg_req_sync_dac_d; wire cfg_req_pulse_dac = cfg_req_sync_dac ^ cfg_req_sync_dac_d; always_ff @(posedge dac_clk_in or posedge dac_rst_int) begin if (dac_rst_int) begin cfg_req_meta_dac <= 1'b0; cfg_req_sync_dac <= 1'b0; cfg_req_sync_dac_d<= 1'b0; cfg_ack_toggle_dac<= 1'b0; dac_pulse_width <= '0; dac_pulse_period <= '0; dac_pulse_num <= '0; dac_pulse_height <= '0; end else begin cfg_req_meta_dac <= cfg_req_toggle_dac_eth; cfg_req_sync_dac <= cfg_req_meta_dac; cfg_req_sync_dac_d <= cfg_req_sync_dac; if (cfg_req_pulse_dac) begin dac_pulse_width <= cfg_bus_eth[31:0]; dac_pulse_period <= cfg_bus_eth[63:32]; dac_pulse_num <= cfg_bus_eth[79:64]; dac_pulse_height <= cfg_bus_eth[80 +: DAC_DATA_WIDTH]; cfg_ack_toggle_dac <= ~cfg_ack_toggle_dac; end end end // ETH -> ADC config CDC logic cfg_req_meta_adc, cfg_req_sync_adc, cfg_req_sync_adc_d; wire cfg_req_pulse_adc = cfg_req_sync_adc ^ cfg_req_sync_adc_d; always_ff @(posedge adc_clk_in or posedge adc_rst_int) begin if (adc_rst_int) begin cfg_req_meta_adc <= 1'b0; cfg_req_sync_adc <= 1'b0; cfg_req_sync_adc_d <= 1'b0; cfg_ack_toggle_adc <= 1'b0; adc_pulse_period <= '0; adc_pulse_num <= '0; end else begin cfg_req_meta_adc <= cfg_req_toggle_adc_eth; cfg_req_sync_adc <= cfg_req_meta_adc; cfg_req_sync_adc_d <= cfg_req_sync_adc; if (cfg_req_pulse_adc) begin adc_pulse_period <= cfg_bus_eth[63:32]; adc_pulse_num <= cfg_bus_eth[79:64]; cfg_ack_toggle_adc <= ~cfg_ack_toggle_adc; end end end endmodule