本文基于之前的Quartus工程创建教程:8-LTC2308控制器设计——Quartus工程创建

1.提前在你的工程目录下新建一个ip文件夹,然后在ip文件夹下新建一个ADC_LTC2308_FIFO文件夹:

 

2. 点击View——Utility Windows——IP Catalog打开IP库:

 3. 在IP Catalog中键入fifo进行搜索找到FIFO IP,双击它:

4. 在Save IP Variation界面点击箭头处:

 

5. 将文件路径设置到你工作目录下~\DE10_Standard_ADC\ip\ADC_LTC2308_FIFO, 给这个FIFO模块命名为adc_data_fifo,然后点击保存

6. 然后点击OK

 7. 随即跳出如下对话框,FIFO位宽设置为12bit,深度设置为2048个word。设置异步读写时钟,然后点击Next:

 

8. 点选同步复位复选框如下:

 

9. 接下来一直点击Next,直到如下界面出现,然后点击Finish结束FIFO 模块的设置:

10. 系统会弹出对话框问是否将刚才配置好的FIFO IP 添加到工程,点击Yes

 

11. 这时候可以看到~\DE10_Standard_ADC\ip\ADC_LTC2308_FIFO路径下产生了这些文件:

 

12. 按照8-LTC2308控制器设计——Quartus工程创建里面创建top-level文件的方法继续创建adc_ltc2308.v和adc_ltc2308_fifo.v两个.v文件(文件代码见本文附录)并放在如下路径:

 

13. 点击Platform Designer按钮:

 

14.在弹出来的窗口上选择File——New Component...

 

15.在子页面Component Type处填写如下信息:

 

16.然后点击子页面Files,点击Add File..., 选择adc_data_fifo.v、adc_ltc2308.v和adc_ltc2308_fifo.v三个文件,点击打开

 

17.系统自动将adc_data_fifo.v文件默认为自定义IP的顶层文件了,而真正的顶层文件是adc_ltc2308_fifo.v,此时需要点击箭头处,然后将Top-level File复选框选上,然后点击OK

 

18. 打开Signals&Interfaces可以看到这个窗口还是空白,没有任何端口:

 19. 返回到Files窗口,点击Analyze Synthesis Files 对文件进行分析和综合,Analyze Synthesis Files Completed窗口点击Close

 

 注意,这里要保证Analyze Synthesis Files Completed窗口没有error出现,至于下面Messages窗口出现error,可以暂时不管,后面的操作会解决这些问题的。

 20. 此时再返回到Signals&Interfaces可以看到这个窗口里有很多端口且被分类了,但是这些端口是系统自动识别的,还需要我们手动去调整:

 

21.点击add interface,选择Clock Input

 22. 鼠标选中avalon_slave_0下面的slave_clk信号,将其拖拽到clcok_sink下面:

 

23. slave_clk 信号的Signal Type 处选择clk

 

24. 再次点击add interface,选择Clock Input,然后将clock_sink_1改名字为clock_sink_adc, 并把adc_clk信号拖拽到clock_sink_adc下面,同样,adc_clk 信号的Signal Type 处选择clk

 25. 点击add interface,选择Reset Input:

   26. 将slave_reset_n信号拖拽到reset_sink接口下面, 并在Signal Type处选择reset_n:

 

27.将slave_addr信号拖拽到slave接口处,Signal Type处选择address :

28. 点击avalon_slave接口,在Type处下拉菜单选择Conduit接口:

  29. 将Conduit接口下面的ADC_CONVST、ADC_SCK、ADC_SDI、ADC_SDO接口的Signal Type改成跟接口一样的名称:

30. 点击slave接口,将Associated Clock设置为clock_sink, 将Associated Reset设置为reset_sink:

 

31. 这时候返回子页面Block Symbol 查看到如下接口框图,最后点击Finish完成自定义IP的封装:

 

31.点击Yes,Save来保存自定义IP的所有设置到adc_ltc2308_hw.tcl文件当中:

 32. 这时候再返回到Platform Designer 界面可以看到自定义IP adc_ltc2308已经在IP Catalog里面显示出来了, 接下来就可以在Platform Designer 里面调用这个IP了。

 

如今自定义IP 都是默认使用动态地址对齐,本案例的Nios 程序使用静态地址对齐(每个寄存器在Avalon总线上占4个字节的地址),此时需要在hw.tcl脚本里加上一句话:set_interface_property slave addressAlignment NATIVE

 

 

附录

adc_ltc2308.v文件代码

如下:

 

module adc_ltc2308(
    clk, // max 40mhz
    
    // start measure
    measure_start, // posedge triggle
    measure_ch,
    measure_done,
    measure_dataread,
    
    // adc interface
    ADC_CONVST,
    ADC_SCK,
    ADC_SDI,
    ADC_SDO 
);

input                                clk;

// start measure
input                                measure_start;
input        [2:0]                    measure_ch;
output    reg                    measure_done;
output    [11:0]                measure_dataread;



output                          ADC_CONVST;
output                          ADC_SCK;
output    reg                  ADC_SDI;
input                           ADC_SDO;


/////////////////////////////////
// Timing definition 

// using 40MHz clock
// to acheive fsample = 500KHz
// ntcyc = 2us / 25ns  = 80



`define DATA_BITS_NUM        12
`define CMD_BITS_NUM            6
`define CH_NUM                    8

`define tWHCONV            3   // CONVST High Time, min 20 ns
`define tCONV                 64 //52  // tCONV: type 1.3 us, MAX 1.6 us, 1600/25(assumed clk is 40mhz)=64  -> 1.3us/25ns = 52
                              // set 64 for suite for 1.6 us max
//                         +12 //data

`define tHCONVST           320 // 12  // here set 320( fsample = 100KHz) for if ADC input impedance is high, see below  
                                // If the source impedance of the driving circuit is low, the ADC inputs can be driven directly.
                                //Otherwise, more acquisition time should be allowed for a source with higher impedance.

                                          // for acheiving 500KHz  fmax. set n cyc = 80.
`define tCONVST_HIGH_START    0     
`define tCONVST_HIGH_END      (`tCONVST_HIGH_START+`tWHCONV) 

`define tCONFIG_START        (`tCONVST_HIGH_END)     
`define tCONFIG_END          (`tCLK_START+`CMD_BITS_NUM - 1)     

`define tCLK_START             (`tCONVST_HIGH_START+`tCONV)
`define tCLK_END                (`tCLK_START+`DATA_BITS_NUM)

`define tDONE                    (`tCLK_END+`tHCONVST)

// create triggle message: reset_n
reg pre_measure_start;
always @ (posedge clk)    
begin
    pre_measure_start <= measure_start;
end

wire reset_n;
assign reset_n = (~pre_measure_start & measure_start)?1'b0:1'b1;

// tick
reg [15:0] tick;    
always @ (posedge clk or negedge reset_n)    
begin
    if (~reset_n)
        tick <= 0;
    else if (tick < `tDONE)
        tick <= tick + 1;
end


/////////////////////////////////
// ADC_CONVST 
assign ADC_CONVST = (tick >= `tCONVST_HIGH_START && tick < `tCONVST_HIGH_END)?1'b1:1'b0;

/////////////////////////////////
// ADC_SCK 

reg clk_enable; // must sync to clk in clk low
always @ (negedge clk or negedge reset_n)     
begin
    if (~reset_n)
        clk_enable <= 1'b0;
    else if ((tick >= `tCLK_START && tick < `tCLK_END))
        clk_enable <= 1'b1;
    else
        clk_enable <= 1'b0;
end

assign ADC_SCK = clk_enable?clk:1'b0;


///////////////////////////////
// read data
reg [(`DATA_BITS_NUM-1):0] read_data;
reg [3:0] write_pos;



assign measure_dataread = read_data;

always @ (negedge clk or negedge reset_n)    
begin
    if (~reset_n)
    begin
        read_data <= 0;
        write_pos <= `DATA_BITS_NUM-1;
    end
    else if (clk_enable)
    begin
        read_data[write_pos] <= ADC_SDO;
        write_pos <= write_pos - 1;
    end
end

///////////////////////////////
// measure done
wire read_ch_done;

assign read_ch_done = (tick == `tDONE)?1'b1:1'b0;

always @ (posedge clk or negedge reset_n)    
begin
    if (~reset_n)
        measure_done <= 1'b0;
    else if (read_ch_done)
        measure_done <= 1'b1;
end

///////////////////////////////
// adc channel config

// pre-build config command
reg [(`CMD_BITS_NUM-1):0] config_cmd;


`define UNI_MODE        1'b1   //1: Unipolar, 0:Bipolar
`define SLP_MODE        1'b0   //1: enable sleep

always @(negedge reset_n)
begin
    if (~reset_n)
    begin
        case (measure_ch)
            0 : config_cmd <= {4'h8, `UNI_MODE, `SLP_MODE}; 
            1 : config_cmd <= {4'hC, `UNI_MODE, `SLP_MODE}; 
            2 : config_cmd <= {4'h9, `UNI_MODE, `SLP_MODE}; 
            3 : config_cmd <= {4'hD, `UNI_MODE, `SLP_MODE}; 
            4 : config_cmd <= {4'hA, `UNI_MODE, `SLP_MODE}; 
            5 : config_cmd <= {4'hE, `UNI_MODE, `SLP_MODE}; 
            6 : config_cmd <= {4'hB, `UNI_MODE, `SLP_MODE}; 
            7 : config_cmd <= {4'hF, `UNI_MODE, `SLP_MODE}; 
            default : config_cmd <= {4'hF, 2'b00}; 
        endcase
    end
end

// serial config command to adc chip
wire config_init;
wire config_enable;
wire config_done;
reg [2:0] sdi_index;

assign config_init = (tick == `tCONFIG_START)?1'b1:1'b0;    
assign config_enable = (tick > `tCLK_START && tick <= `tCONFIG_END)?1'b1:1'b0;    // > because this is negative edge triggle
assign config_done = (tick > `tCONFIG_END)?1'b1:1'b0;    
always @(negedge clk)    
begin
    if (config_init)
    begin
        ADC_SDI <= config_cmd[`CMD_BITS_NUM-1];
        sdi_index <= `CMD_BITS_NUM-2;
    end
    else if (config_enable)
    begin
        ADC_SDI <= config_cmd[sdi_index];
        sdi_index <= sdi_index - 1;
    end
    else if (config_done)
        ADC_SDI <= 1'b0; //
end




endmodule

 

adc_ltc2308_fifo.v文件代码

如下:

 

/* note for avalon interface
    bus type: nagtive
    read legacy = 0 (to consistent to FIFO)

*/

module adc_ltc2308_fifo(
    // avalon slave port
    slave_clk,//总线时钟,100M,同步所有总线操作
    slave_reset_n,//复位信号,低有效
    slave_chipselect_n,//片选信号,低有效
    slave_addr,//地址总线,单bit地址空间
    slave_read_n,//读使能,低有效
    slave_write_n,//写使能,低有效
    slave_readdata,//输出数据总线,16bit
    slave_writedata,//输入数据总线,16bit
    
    adc_clk, // max 40mhz
    // adc interface
    ADC_CONVST,
    ADC_SCK,
    ADC_SDI,
    ADC_SDO
);

    // avalon slave port
input                                    slave_clk;
input                                    slave_reset_n;
input                                    slave_chipselect_n;
input                                      slave_addr;
input                                    slave_read_n;
input                                    slave_write_n;
output    reg    [15:0]            slave_readdata;
input                [15:0]            slave_writedata;



input                                adc_clk;

output                          ADC_CONVST;
output                          ADC_SCK;
output                        ADC_SDI;
input                           ADC_SDO;


////////////////////////////////////
// avalon slave port
`define WRITE_REG_START_CH                0//常量定义,用于标识通道选择寄存器和启动转换标志寄存器的地址
`define WRITE_REG_MEASURE_NUM            1//常量定义,用于标识采样次数寄存器地址

// write for control
reg                 measure_fifo_start;//存储启动转换标志的寄存器
reg  [11:0]     measure_fifo_num;//存储采样次数的寄存器
reg    [2:0]        measure_fifo_ch;//存储通道选择的寄存器

//根据地址和控制信号,将写入数据存储到相应的寄存器中
always @ (posedge slave_clk or negedge slave_reset_n)    
begin
    if (~slave_reset_n)
        measure_fifo_start <= 1'b0;
    else if (~slave_chipselect_n && ~slave_write_n && slave_addr == `WRITE_REG_START_CH)   
        {measure_fifo_ch, measure_fifo_start} <= slave_writedata[3:0]; 
    else if (~slave_chipselect_n && ~slave_write_n && slave_addr == `WRITE_REG_MEASURE_NUM)   
        measure_fifo_num <= slave_writedata;
end

///////////////////////
// read 
`define READ_REG_MEASURE_DONE    0//常量定义,用于标识寄存器地址
`define READ_REG_ADC_VALUE        1
wire slave_read_status;
wire slave_read_data;

//根据地址和控制信号,将相应数据输出到读数据端口
assign slave_read_status = (~slave_chipselect_n && ~slave_read_n && slave_addr == `READ_REG_MEASURE_DONE) ?1'b1:1'b0;
assign slave_read_data = (~slave_chipselect_n && ~slave_read_n && slave_addr == `READ_REG_ADC_VALUE) ?1'b1:1'b0;

reg measure_fifo_done;
always @ (posedge slave_clk)    
begin
    if (slave_read_status)   
        slave_readdata <= {11'b0, measure_fifo_done};//转换完成状态指示
    else if (slave_read_data) //读取FIFO中的转换结果  
        slave_readdata <= fifo_q;
end

reg pre_slave_read_data;
always @ (posedge slave_clk or negedge slave_reset_n)    
begin
    if (~slave_reset_n)
        pre_slave_read_data <= 1'b0;
    else
        pre_slave_read_data <= slave_read_data;
end

// read ack for adc data. (note. Slave_read_data is read lency=2, so slave_read_data is assert two clock)
assign fifo_rdreq = (pre_slave_read_data & slave_read_data)?1'b1:1'b0;

////////////////////////////////////
// create triggle message: adc_reset_n

//生成ADC复位信号,当测量开始时复位ADC
reg pre_measure_fifo_start;
always @ (posedge adc_clk)    
begin
    pre_measure_fifo_start <= measure_fifo_start;
end

wire adc_reset_n;
assign adc_reset_n = (~pre_measure_fifo_start & measure_fifo_start)?1'b0:1'b1;

////////////////////////////////////
// control measure_start 
reg [11:0] measure_count;

reg config_first;
reg wait_measure_done;
reg measure_start;
wire measure_done;
wire [11:0] measure_dataread;

//控制测量过程的启动、进行和结束
always @ (posedge adc_clk or negedge adc_reset_n)    
begin
    if (~adc_reset_n)
    begin
        measure_start <= 1'b0;
        config_first <= 1'b1;//复位时: Reset config_first 被置为 1,表示系统处于首次配置状态。
        measure_count <= 0;
        measure_fifo_done <= 1'b0;
        wait_measure_done <= 1'b0;
    end 
    else if (~measure_fifo_done & ~measure_start & ~wait_measure_done)
    begin
        measure_start <= 1'b1;
        wait_measure_done <= 1'b1;
    end
    else if (wait_measure_done) // && measure_start)
    begin
        measure_start <= 1'b0;
        if (measure_done)
        begin
            if (config_first)// 当config_first = 1 时,系统会 跳过第一次转换的数据存储
                config_first <= 1'b0;
            else//当 config_first = 0 时,系统进入正常数据采集模式,每次转换完成后将数据写入 FIFO
            begin    // read data and save into fifo
                
                if (measure_count < measure_fifo_num) // && ~fifo_wrfull)
                begin
                    measure_count <= measure_count + 1;
                    wait_measure_done <= 1'b0;
                end
                else
                    measure_fifo_done <= 1'b1;
            end
        end
    end
end


// write data into fifo

reg pre_measure_done;

always @ (posedge adc_clk or negedge adc_reset_n)    
begin
    if (~adc_reset_n)
        pre_measure_done <= 1'b0;
    else
        pre_measure_done <= measure_done;
end

assign fifo_wrreq = (~pre_measure_done & measure_done & ~config_first)?1'b1:1'b0;


///////////////////////////////////////
// SPI
//实例化LTC2308 ADC模块
adc_ltc2308 adc_ltc2308_inst(
    .clk(adc_clk), // max 40mhz

    // start measure
    .measure_start(measure_start), // posedge triggle
    .measure_done(measure_done),//一次采样转换完毕指示信号
    .measure_ch(measure_fifo_ch),
    .measure_dataread(measure_dataread),    //ADC输出的数据
    

    // adc interface
    .ADC_CONVST(ADC_CONVST),
    .ADC_SCK(ADC_SCK),
    .ADC_SDI(ADC_SDI),
    .ADC_SDO(ADC_SDO) 
);


///////////////////////////////////////
// FIFO
wire fifo_wrfull;
wire fifo_rdempty;
wire  fifo_wrreq;
wire [11:0]     fifo_q;
wire fifo_rdreq;

//实例化FIFO模块,用于存储ADC读取的数据    
adc_data_fifo adc_data_fifo_inst(
    .aclr(~adc_reset_n),
    .data(measure_dataread),
    .rdclk(slave_clk),
    .rdreq(fifo_rdreq),
    .wrclk(adc_clk),
    .wrreq(fifo_wrreq),
    .q(fifo_q),
    .rdempty(fifo_rdempty),
    .wrfull(fifo_wrfull) 
);    

    
endmodule
    

 

 

往期推荐阅读: