[FPGA Development] About how to use FPGA to configure AD9528

In this work, I came into contact with the AD9528, a clock generation chip from Arnold & Son, which can generate multiple synchronous clocks. This clock uses the spi interface to configure accordingly. Later, I will record how I wrote the corresponding FPGA code for this process.

AD9528 basic introduction:

Specific configuration parameters and methods:

Specifically, we used a five-way clock in the project. Our configuration requirements are as follows:

There is corresponding host computer software that can be downloaded from the official website and can be configured accordingly. After configuration, you can know what the corresponding register parameters that need to be configured are.

This is the basic configuration method after consulting the manufacturer. For the specific configuration principle, you need to read the datasheet in detail. Specifically, the two clocks OUT1 and OUT2 need to use the clock generated from the PLL2 divider.

The specific registers that need to be configured are as follows:

Code writing and AD9528 spi timing diagram:

AD9528 schematic:

AD9528 timing diagram:

For this timing diagram, we started to write code:

Verilog code implements SPI configuration register:

//------------------------------------------------ --------------------------------
// Copyright (c) 2014-2023 All rights reserved
//------------------------------------------------ --------------------------
// Author: xibo wu (Gatsby)
// File: spi.v
// Create : 2023-10-25 16:16:08
// Revise : 2023-10-27 13:46:23
// Editor : sublime text3, tab size (4)
//------------------------------------------------ --------------------------



module spi_module
(
    input i_clk , // global clock 100MHz
    input i_rst_n, //reset signal, active low level

    //Four-wire standard SPI signal definition
    input i_spi_miso, // SPI serial input, used to receive data from the slave
    output o_spi_sck , // SPI clock
    output o_spi_cs , // SPI chip select signal
    output o_spi_mosi // SPI output, used to send data to the slave
);



//-------parameter--------//
parameter TX_IDLE = 4'b0001;
parameter TX_HEADER = 4'b0010;
parameter TX_PAYLOAD = 4'b0100;
parameter TX_END = 4'b1000;


//-----parameter_reg-------//
//---form "addr_data"------//
parameter REG_1 = 24'h0104_01;
parameter REG_2 = 24'h0100_01;
parameter REG_3 = 24'h0102_01;
parameter REG_4 = 24'h0204_01;
parameter REG_5 = 24'h0201_14;
parameter REG_6 = 24'h010A_02;
parameter REG_7 = 24'h0208_13;
parameter REG_8 = 24'h0501_E0;
parameter REG_9 = 24'h0502_3F;
parameter REG_10 = 24'h0302_11;
parameter REG_11 = 24'h030E_23;
parameter REG_12 = 24'h0303_00;
parameter REG_13 = 24'h0305_05;
parameter REG_14 = 24'h0308_05;

//---------regs-----------//
reg [4:0] tx_bit_sel;
reg wr_en;
reg [3:0] clk_cnt;
reg cs_n_t;
reg sck_t = 1'b0;
reg [4:0] reg_cnt = 5'd14; //It is not clear whether subsequent changes are needed here
reg mosi_t;
reg [23:0] reg_cfg; //Variable register writes configuration value

//--------debug-----------//
reg [23:0] reg_debug;





reg [3:0] cur_state;
reg [3:0] nxt_state;
reg skip_en;



//-------assign----------//
assign o_spi_sck = sck_t;
assign o_spi_cs = cs_n_t;
assign o_spi_mosi = mosi_t;



//---------debug-----------//


always @(posedge i_clk or negedge i_rst_n) begin
    if (i_rst_n == 1'b0) begin
        reg_debug <= 24'b0;
    end else if(cs_n_t == 1'b0 & amp; & amp; clk_cnt == 'd1) begin
        reg_debug <= {reg_debug[22:0], mosi_t};
    end else begin
        reg_debug <= reg_debug;
    end
end


//---------main------------//

always @(posedge i_clk or negedge i_rst_n) begin
    if (i_rst_n == 1'b0) begin
        clk_cnt <= 4'b0;
    end else if(clk_cnt == 'd3) begin
        clk_cnt <= 'd0;
    end else begin
        clk_cnt <= clk_cnt + 4'd1;
    end
end

//The data must be prepared before the rising edge of sck
always @(posedge i_clk or negedge i_rst_n) begin
    if (i_rst_n == 1'b0) begin
        wr_en <= 1'b0;
    end else if(clk_cnt == 'd2) begin
        wr_en <= 1'b1;
    end else begin
        wr_en <= 1'b0;
    end
end

always @(posedge i_clk or negedge i_rst_n) begin
    if (i_rst_n == 1'b0) begin
        sck_t <= 1'b0;
    end else if(clk_cnt == 'd1 & amp; & amp; cs_n_t == 1'b0) begin
        sck_t <= 1'b1;
    end else if(clk_cnt == 'd3 & amp; & amp; cs_n_t == 1'b0) begin
        sck_t <= 1'b0;
    end else begin
        sck_t <= sck_t;
    end
end


//Register data configuration unit j

always @(posedge i_clk or posedge i_rst_n) begin
    if (i_rst_n == 1'b0) begin
        reg_cfg <= REG_1;
    end else begin
        case(reg_cnt)
            5'd14: reg_cfg <= REG_1;
            //Fill the corresponding register value in the middle.
            5'd13: reg_cfg <= REG_2;
            5'd12: reg_cfg <= REG_3;
            5'd11: reg_cfg <= REG_4;
            5'd10: reg_cfg <= REG_5;
            5'd9 : reg_cfg <= REG_6;
            5'd8 : reg_cfg <= REG_7;
            5'd7 : reg_cfg <= REG_8;
            5'd6 : reg_cfg <= REG_9;
            5'd5 : reg_cfg <= REG_10;
            5'd4 : reg_cfg <= REG_11;
            5'd3 : reg_cfg <= REG_12;
            5'd2 : reg_cfg <= REG_13;
            5'd1 : reg_cfg <= REG_14;
            default:;
        endcase
    end
end


always @(posedge i_clk or negedge i_rst_n) begin
    if (i_rst_n == 1'b0) begin
        cur_state <= TX_IDLE;
    end else begin
        cur_state <= nxt_state;
    end
end

always @(*) begin
    nxt_state = TX_IDLE;
    case (cur_state)
        TX_IDLE: begin
            if (skip_en) begin
                nxt_state = TX_HEADER;
            end else begin
                nxt_state = TX_IDLE;
            end
        end
        TX_HEADER : begin
            if (skip_en) begin
                nxt_state = TX_PAYLOAD;
            end else begin
                nxt_state = TX_HEADER;
            end
        end
 
        TX_PAYLOAD : begin
            if (skip_en) begin
                nxt_state = TX_END;
            end else begin
                nxt_state = TX_PAYLOAD;
            end
        end
  
        TX_END : begin
            if (skip_en) begin
                nxt_state = TX_IDLE;
            end else begin
                nxt_state = TX_END;
            end
        end

        default: nxt_state = TX_IDLE;
    endcase
end

always @(posedge i_clk or negedge i_rst_n) begin
    if (i_rst_n == 1'b0) begin
        skip_en <= 1'b0;
        cs_n_t <= 1'b1;
        tx_bit_sel <= 5'd0;
    end else begin
        skip_en <= 1'b0;
        case(nxt_state)
            TX_IDLE: begin
                if(reg_cnt != 'd0) begin
                    skip_en <= 1'b1;
                end else begin
                cs_n_t <= 1'b1;
                end
            end
            TX_HEADER : begin
                cs_n_t <= 1'b0;
                if (wr_en == 1'b1) begin
                    tx_bit_sel <= tx_bit_sel + 'd1;
                    cs_n_t <= 1'b0;
                    case(tx_bit_sel)
                        5'd0: mosi_t <= reg_cfg[23]; //Read and write bits
                        5'd1: mosi_t <= reg_cfg[22]; //empty
                        5'd2: mosi_t <= reg_cfg[21]; //empty
                        5'd3 : mosi_t <= reg_cfg[20]; //addr[12]
                        5'd4 : mosi_t <= reg_cfg[19]; //addr[11]
                        5'd5 : mosi_t <= reg_cfg[18]; //addr[10]
                        5'd6 : mosi_t <= reg_cfg[17]; //addr[9]
                        5'd7 : mosi_t <= reg_cfg[16]; //addr[8]
                        5'd8 : mosi_t <= reg_cfg[15]; //addr[7]
                        5'd9 : mosi_t <= reg_cfg[14]; //addr[6]
                        5'd10: mosi_t <= reg_cfg[13]; //addr[5]
                        5'd11: mosi_t <= reg_cfg[12]; //addr[4]
                        5'd12: mosi_t <= reg_cfg[11]; //addr[3]
                        5'd13: mosi_t <= reg_cfg[10]; //addr[2]
                        5'd14: mosi_t <= reg_cfg[9]; //addr[1]
                        5'd15: begin
                                mosi_t <= reg_cfg[8]; //addr[0]
                                tx_bit_sel <= 'd0;
                                skip_en <= 1'b1;
                                end
                    endcase
                end
            end

            TX_PAYLOAD : begin
                cs_n_t <= 1'b0;
                if (wr_en == 1'b1) begin
                    tx_bit_sel <= tx_bit_sel + 'd1;
                    case(tx_bit_sel)
                        5'd0: mosi_t <= reg_cfg[7]; //data[7]
                        5'd1 : mosi_t <= reg_cfg[6]; //data[6]
                        5'd2 : mosi_t <= reg_cfg[5]; //data[5]
                        5'd3 : mosi_t <= reg_cfg[4]; //data[4]
                        5'd4 : mosi_t <= reg_cfg[3]; //data[3]
                        5'd5 : mosi_t <= reg_cfg[2]; //data[2]
                        5'd6: mosi_t <= reg_cfg[1]; //data[1]
                        5'd7 : begin
                               mosi_t <= reg_cfg[0]; //data[0]
                               tx_bit_sel <= 'd0;
                               skip_en <= 1'b1;
                               end
                    endcase
                end
            end

            TX_END : begin
                if(reg_cnt != 'd0) begin
                    reg_cnt <= reg_cnt - 1'd1;
                    skip_en <= 1'b1;
                end
            end
            default:;
        endcase
    end
end


endmodule

The simulation results are as follows:

This article is for communication only, please correct me if there are any errors.