From 003750d972e90b30b60c141cc23a0f893ad41ce0 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 15 Apr 2026 12:47:10 +0300 Subject: [PATCH] rtl: add controller first version --- rtl/controller/src/controller.sv | 472 +++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 rtl/controller/src/controller.sv diff --git a/rtl/controller/src/controller.sv b/rtl/controller/src/controller.sv new file mode 100644 index 0000000..d60c265 --- /dev/null +++ b/rtl/controller/src/controller.sv @@ -0,0 +1,472 @@ +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; + + 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] + // ------------------------------------------------------------------------- + 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; + + eth_state_t eth_state; + + logic [3:0] cfg_byte_cnt; + + // Busy flag: set by START command, cleared by finish event from ADC domain + 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 + // command packet must be exactly 1 byte + if (s_axis_tlast) begin + rst_toggle_eth <= ~rst_toggle_eth; + end else begin + eth_state <= ST_DISCARD; + end + end + + CMD_START: begin + // command packet must be exactly 1 byte + if (s_axis_tlast) begin + start_toggle_eth <= ~start_toggle_eth; + busy_flag_eth <= 1'b1; + end else begin + eth_state <= ST_DISCARD; + end + 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 \ No newline at end of file