// SPDX-License-Identifier: CERN-OHL-S-2.0 /* Copyright (c) 2015-2025 FPGA Ninja, LLC Authors: - Alex Forencich */ `resetall `timescale 1ns / 1ps `default_nettype none /* * I2C init */ module si5324_i2c_init # ( parameter logic SIM_SPEEDUP = 1'b0 ) ( input wire logic clk, input wire logic rst, /* * I2C master interface */ taxi_axis_if.src m_axis_cmd, taxi_axis_if.src m_axis_tx, /* * Status */ output wire logic busy, /* * Configuration */ input wire logic start ); /* Generic module for I2C bus initialization. Good for use when multiple devices on an I2C bus must be initialized on system start without intervention of a general-purpose processor. Copy this file and change init_data and INIT_DATA_LEN as needed. This module can be used in two modes: simple device initialization, or multiple device initialization. In multiple device mode, the same initialization sequence can be performed on multiple different device addresses. To use single device mode, only use the start write to address and write data commands. The module will generate the I2C commands in sequential order. Terminate the list with a 0 entry. To use the multiple device mode, use the start data and start address block commands to set up lists of initialization data and device addresses. The module enters multiple device mode upon seeing a start data block command. The module stores the offset of the start of the data block and then skips ahead until it reaches a start address block command. The module will store the offset to the address block and read the first address in the block. Then it will jump back to the data block and execute it, substituting the stored address for each current address write command. Upon reaching the start address block command, the module will read out the next address and start again at the top of the data block. If the module encounters a start data block command while looking for an address, then it will store a new data offset and then look for a start address block command. Terminate the list with a 0 entry. Normal address commands will operate normally inside a data block. Commands: 00 0000000 : halt 00 0000001 : exit multiple device mode 00 0000011 : start write to current address 00 0001000 : start address block 00 0001001 : start data block 00 001dddd : delay 2**(16+d) cycles 00 1000001 : send I2C stop 01 aaaaaaa : start write to address 1 dddddddd : write 8-bit data Examples write 0x11223344 to register 0x0004 on device at 0x50 01 1010000 start write to 0x50 1 00000000 write address 0x0004 1 00000100 1 00010001 write data 0x11223344 1 00100010 1 00110011 1 01000100 0 00000000 halt write 0x11223344 to register 0x0004 on devices at 0x50, 0x51, 0x52, and 0x53 00 0001001 start data block 00 0000011 start write to current address 1 00000000 write address 0x0004 1 00000100 1 00010001 write data 0x11223344 1 00100010 1 00110011 1 01000100 00 0001000 start address block 01 1010000 address 0x50 01 1010001 address 0x51 01 1010010 address 0x52 01 1010011 address 0x53 00 0000001 exit multi-dev mode 00 0000000 halt */ // check configuration if (m_axis_cmd.DATA_W < 12) $fatal(0, "Command interface width must be at least 12 bits (instance %m)"); if (m_axis_tx.DATA_W != 8) $fatal(0, "Data interface width must be 8 bits (instance %m)"); function [8:0] cmd_start(input [6:0] addr); cmd_start = {2'b01, addr}; endfunction function [8:0] cmd_wr(input [7:0] data); cmd_wr = {1'b1, data}; endfunction function [8:0] cmd_stop(); cmd_stop = {2'b00, 7'b1000001}; endfunction function [8:0] cmd_delay(input [3:0] d); cmd_delay = {2'b00, 3'b001, d}; endfunction function [8:0] cmd_halt(); cmd_halt = 9'd0; endfunction function [8:0] blk_start_data(); blk_start_data = {2'b00, 7'b0001001}; endfunction function [8:0] blk_start_addr(); blk_start_addr = {2'b00, 7'b0001000}; endfunction function [8:0] cmd_start_cur(); cmd_start_cur = {2'b00, 7'b0000011}; endfunction function [8:0] cmd_exit(); cmd_exit = {2'b00, 7'b0000001}; endfunction // init_data ROM localparam INIT_DATA_LEN = 41; logic [8:0] init_data [INIT_DATA_LEN-1:0]; initial begin // Initial delay init_data[0] = cmd_delay(6); // delay 30 ms // Select Si5324 init_data[1] = cmd_start(7'h74); init_data[2] = cmd_wr(8'h80); init_data[3] = cmd_stop(); init_data[4] = cmd_start(7'h75); init_data[5] = cmd_wr(8'h00); init_data[6] = cmd_stop(); // init Si5324 registers init_data[7] = cmd_start(7'h68); // start write to 0x68 (Si5324) init_data[8] = cmd_wr(8'd0); // register 0 init_data[9] = cmd_wr(8'h54); // Reg 0: Free run, Clock off before ICAL, Bypass off (normal operation) init_data[10] = cmd_wr(8'hE4); // Reg 1: CKIN2 second priority, CKIN1 first priority init_data[11] = cmd_wr(8'h12); // Reg 2: BWSEL = 1 init_data[12] = cmd_wr(8'h15); // Reg 3: CKIN1 selected, Digital Hold off, Output clocks disabled during ICAL init_data[13] = cmd_wr(8'h92); // Reg 4: Automatic Revertive, HIST_DEL = 0x12 init_data[14] = cmd_start(7'h68); // start write to 0x68 (Si5324) init_data[15] = cmd_wr(8'd10); // register 10 init_data[16] = cmd_wr(8'h08); // Reg 10: CKOUT2 disabled, CKOUT1 enabled init_data[17] = cmd_wr(8'h40); // Reg 11: CKIN2 enabled, CKIN1 enabled init_data[18] = cmd_start(7'h68); // start write to 0x68 (Si5324) init_data[19] = cmd_wr(8'd25); // register 25 init_data[20] = cmd_wr(8'hA0); // Reg 25: N1_HS = 9 init_data[21] = cmd_start(7'h68); // start write to 0x68 (Si5324) init_data[22] = cmd_wr(8'd31); // register 31 init_data[23] = cmd_wr(8'h00); // Regs 31,32,33: NC1_LS = 4 init_data[24] = cmd_wr(8'h00); init_data[25] = cmd_wr(8'h03); init_data[26] = cmd_start(7'h68); // start write to 0x68 (Si5324) init_data[27] = cmd_wr(8'd40); // register 40 init_data[28] = cmd_wr(8'hC2); // Regs 40,41,42: N2_HS = 10, N2_LS = 150000 init_data[29] = cmd_wr(8'h49); init_data[30] = cmd_wr(8'hEF); init_data[31] = cmd_wr(8'h00); // Regs 43,44,45: N31 = 30475 init_data[32] = cmd_wr(8'h77); init_data[33] = cmd_wr(8'h0B); init_data[34] = cmd_wr(8'h00); // Regs 46,47,48: N32 = 30475 init_data[35] = cmd_wr(8'h77); init_data[36] = cmd_wr(8'h0B); init_data[37] = cmd_start(7'h68); // start write to 0x68 (Si5324) init_data[38] = cmd_wr(8'd136); // register 136 init_data[39] = cmd_wr(8'h40); // Reg 136: ICAL = 1 init_data[40] = cmd_halt(); // stop end localparam [2:0] STATE_IDLE = 3'd0, STATE_RUN = 3'd1, STATE_TABLE_1 = 3'd2, STATE_TABLE_2 = 3'd3, STATE_TABLE_3 = 3'd4; logic [2:0] state_reg = STATE_IDLE, state_next; localparam AW = $clog2(INIT_DATA_LEN); logic [8:0] init_data_reg = '0; logic [AW-1:0] address_reg = '0, address_next; logic [AW-1:0] address_ptr_reg = '0, address_ptr_next; logic [AW-1:0] data_ptr_reg = '0, data_ptr_next; logic [6:0] cur_address_reg = '0, cur_address_next; logic [31:0] delay_counter_reg = '0, delay_counter_next; logic [6:0] m_axis_cmd_address_reg = '0, m_axis_cmd_address_next; logic m_axis_cmd_start_reg = 1'b0, m_axis_cmd_start_next; logic m_axis_cmd_write_reg = 1'b0, m_axis_cmd_write_next; logic m_axis_cmd_stop_reg = 1'b0, m_axis_cmd_stop_next; logic m_axis_cmd_valid_reg = 1'b0, m_axis_cmd_valid_next; logic [7:0] m_axis_tx_tdata_reg = '0, m_axis_tx_tdata_next; logic m_axis_tx_tvalid_reg = 1'b0, m_axis_tx_tvalid_next; logic start_flag_reg = 1'b0, start_flag_next; logic busy_reg = 1'b0; assign m_axis_cmd.tdata[6:0] = m_axis_cmd_address_reg; assign m_axis_cmd.tdata[7] = m_axis_cmd_start_reg; assign m_axis_cmd.tdata[8] = 1'b0; // read assign m_axis_cmd.tdata[9] = m_axis_cmd_write_reg; assign m_axis_cmd.tdata[10] = 1'b0; // write multi assign m_axis_cmd.tdata[11] = m_axis_cmd_stop_reg; assign m_axis_cmd.tvalid = m_axis_cmd_valid_reg; assign m_axis_cmd.tlast = 1'b1; assign m_axis_cmd.tid = '0; assign m_axis_cmd.tdest = '0; assign m_axis_cmd.tuser = '0; assign m_axis_tx.tdata = m_axis_tx_tdata_reg; assign m_axis_tx.tvalid = m_axis_tx_tvalid_reg; assign m_axis_tx.tlast = 1'b1; assign m_axis_tx.tid = '0; assign m_axis_tx.tdest = '0; assign m_axis_tx.tuser = '0; assign busy = busy_reg; always_comb begin state_next = STATE_IDLE; address_next = address_reg; address_ptr_next = address_ptr_reg; data_ptr_next = data_ptr_reg; cur_address_next = cur_address_reg; delay_counter_next = delay_counter_reg; m_axis_cmd_address_next = m_axis_cmd_address_reg; m_axis_cmd_start_next = m_axis_cmd_start_reg && !(m_axis_cmd.tvalid && m_axis_cmd.tready); m_axis_cmd_write_next = m_axis_cmd_write_reg && !(m_axis_cmd.tvalid && m_axis_cmd.tready); m_axis_cmd_stop_next = m_axis_cmd_stop_reg && !(m_axis_cmd.tvalid && m_axis_cmd.tready); m_axis_cmd_valid_next = m_axis_cmd_valid_reg && !m_axis_cmd.tready; m_axis_tx_tdata_next = m_axis_tx_tdata_reg; m_axis_tx_tvalid_next = m_axis_tx_tvalid_reg && !m_axis_tx.tready; start_flag_next = start_flag_reg; if (m_axis_cmd.tvalid || m_axis_tx.tvalid) begin // wait for output registers to clear state_next = state_reg; end else if (delay_counter_reg != 0) begin // delay delay_counter_next = delay_counter_reg - 1; state_next = state_reg; end else begin case (state_reg) STATE_IDLE: begin // wait for start signal if (!start_flag_reg && start) begin address_next = '0; start_flag_next = 1'b1; state_next = STATE_RUN; end else begin state_next = STATE_IDLE; end end STATE_RUN: begin // process commands if (init_data_reg[8] == 1'b1) begin // write data m_axis_cmd_write_next = 1'b1; m_axis_cmd_stop_next = 1'b0; m_axis_cmd_valid_next = 1'b1; m_axis_tx_tdata_next = init_data_reg[7:0]; m_axis_tx_tvalid_next = 1'b1; address_next = address_reg + 1; state_next = STATE_RUN; end else if (init_data_reg[8:7] == 2'b01) begin // write address m_axis_cmd_address_next = init_data_reg[6:0]; m_axis_cmd_start_next = 1'b1; address_next = address_reg + 1; state_next = STATE_RUN; end else if (init_data_reg[8:4] == 5'b00001) begin // delay if (SIM_SPEEDUP) begin delay_counter_next = 32'd1 << (init_data_reg[3:0]); end else begin delay_counter_next = 32'd1 << (init_data_reg[3:0]+16); end address_next = address_reg + 1; state_next = STATE_RUN; end else if (init_data_reg == 9'b001000001) begin // send stop m_axis_cmd_write_next = 1'b0; m_axis_cmd_start_next = 1'b0; m_axis_cmd_stop_next = 1'b1; m_axis_cmd_valid_next = 1'b1; address_next = address_reg + 1; state_next = STATE_RUN; end else if (init_data_reg == 9'b000001001) begin // data table start data_ptr_next = address_reg + 1; address_next = address_reg + 1; state_next = STATE_TABLE_1; end else if (init_data_reg == 9'd0) begin // stop m_axis_cmd_start_next = 1'b0; m_axis_cmd_write_next = 1'b0; m_axis_cmd_stop_next = 1'b1; m_axis_cmd_valid_next = 1'b1; state_next = STATE_IDLE; end else begin // invalid command, skip address_next = address_reg + 1; state_next = STATE_RUN; end end STATE_TABLE_1: begin // find address table start if (init_data_reg == 9'b000001000) begin // address table start address_ptr_next = address_reg + 1; address_next = address_reg + 1; state_next = STATE_TABLE_2; end else if (init_data_reg == 9'b000001001) begin // data table start data_ptr_next = address_reg + 1; address_next = address_reg + 1; state_next = STATE_TABLE_1; end else if (init_data_reg == 1) begin // exit mode address_next = address_reg + 1; state_next = STATE_RUN; end else if (init_data_reg == 9'd0) begin // stop m_axis_cmd_start_next = 1'b0; m_axis_cmd_write_next = 1'b0; m_axis_cmd_stop_next = 1'b1; m_axis_cmd_valid_next = 1'b1; state_next = STATE_IDLE; end else begin // invalid command, skip address_next = address_reg + 1; state_next = STATE_TABLE_1; end end STATE_TABLE_2: begin // find next address if (init_data_reg[8:7] == 2'b01) begin // write address command // store address and move to data table cur_address_next = init_data_reg[6:0]; address_ptr_next = address_reg + 1; address_next = data_ptr_reg; state_next = STATE_TABLE_3; end else if (init_data_reg == 9'b000001001) begin // data table start data_ptr_next = address_reg + 1; address_next = address_reg + 1; state_next = STATE_TABLE_1; end else if (init_data_reg == 9'd1) begin // exit mode address_next = address_reg + 1; state_next = STATE_RUN; end else if (init_data_reg == 9'd0) begin // stop m_axis_cmd_start_next = 1'b0; m_axis_cmd_write_next = 1'b0; m_axis_cmd_stop_next = 1'b1; m_axis_cmd_valid_next = 1'b1; state_next = STATE_IDLE; end else begin // invalid command, skip address_next = address_reg + 1; state_next = STATE_TABLE_2; end end STATE_TABLE_3: begin // process data table with selected address if (init_data_reg[8] == 1'b1) begin // write data m_axis_cmd_write_next = 1'b1; m_axis_cmd_stop_next = 1'b0; m_axis_cmd_valid_next = 1'b1; m_axis_tx_tdata_next = init_data_reg[7:0]; m_axis_tx_tvalid_next = 1'b1; address_next = address_reg + 1; state_next = STATE_TABLE_3; end else if (init_data_reg[8:7] == 2'b01) begin // write address m_axis_cmd_address_next = init_data_reg[6:0]; m_axis_cmd_start_next = 1'b1; address_next = address_reg + 1; state_next = STATE_TABLE_3; end else if (init_data_reg == 9'b000000011) begin // write current address m_axis_cmd_address_next = cur_address_reg; m_axis_cmd_start_next = 1'b1; address_next = address_reg + 1; state_next = STATE_TABLE_3; end else if (init_data_reg[8:4] == 5'b00001) begin // delay if (SIM_SPEEDUP) begin delay_counter_next = 32'd1 << (init_data_reg[3:0]); end else begin delay_counter_next = 32'd1 << (init_data_reg[3:0]+16); end address_next = address_reg + 1; state_next = STATE_TABLE_3; end else if (init_data_reg == 9'b001000001) begin // send stop m_axis_cmd_write_next = 1'b0; m_axis_cmd_start_next = 1'b0; m_axis_cmd_stop_next = 1'b1; m_axis_cmd_valid_next = 1'b1; address_next = address_reg + 1; state_next = STATE_TABLE_3; end else if (init_data_reg == 9'b000001001) begin // data table start data_ptr_next = address_reg + 1; address_next = address_reg + 1; state_next = STATE_TABLE_1; end else if (init_data_reg == 9'b000001000) begin // address table start address_next = address_ptr_reg; state_next = STATE_TABLE_2; end else if (init_data_reg == 9'd1) begin // exit mode address_next = address_reg + 1; state_next = STATE_RUN; end else if (init_data_reg == 9'd0) begin // stop m_axis_cmd_start_next = 1'b0; m_axis_cmd_write_next = 1'b0; m_axis_cmd_stop_next = 1'b1; m_axis_cmd_valid_next = 1'b1; state_next = STATE_IDLE; end else begin // invalid command, skip address_next = address_reg + 1; state_next = STATE_TABLE_3; end end default: begin // invalid state state_next = STATE_IDLE; end endcase end end always_ff @(posedge clk) begin state_reg <= state_next; // read init_data ROM init_data_reg <= init_data[address_next]; address_reg <= address_next; address_ptr_reg <= address_ptr_next; data_ptr_reg <= data_ptr_next; cur_address_reg <= cur_address_next; delay_counter_reg <= delay_counter_next; m_axis_cmd_address_reg <= m_axis_cmd_address_next; m_axis_cmd_start_reg <= m_axis_cmd_start_next; m_axis_cmd_write_reg <= m_axis_cmd_write_next; m_axis_cmd_stop_reg <= m_axis_cmd_stop_next; m_axis_cmd_valid_reg <= m_axis_cmd_valid_next; m_axis_tx_tdata_reg <= m_axis_tx_tdata_next; m_axis_tx_tvalid_reg <= m_axis_tx_tvalid_next; start_flag_reg <= start && start_flag_next; busy_reg <= (state_reg != STATE_IDLE); if (rst) begin state_reg <= STATE_IDLE; init_data_reg <= '0; address_reg <= '0; address_ptr_reg <= '0; data_ptr_reg <= '0; cur_address_reg <= '0; delay_counter_reg <= '0; m_axis_cmd_valid_reg <= 1'b0; m_axis_tx_tvalid_reg <= 1'b0; start_flag_reg <= 1'b0; busy_reg <= 1'b0; end end endmodule `resetall