`timescale 1ns / 1ps

module tb_digital_top();

    // 参数定义
    parameter CLK_PERIOD = 20;      // 100MHz 系统时钟
    parameter SCLK_PERIOD = 200;    // 10MHz SPI时钟 (必须比系统时钟慢)

    // 信号声明
    logic clk;
    logic rst_n;
    logic [4:0] cfgid;
    
    logic sclk;
    logic csn;
    logic mosi;
    logic miso;
    logic oen;
    logic sig_in;

    // 实例化 DUT
    digital_top u_digital_top (
        .clk     (clk    ),
        .rst_n   (rst_n  ),
        .cfgid   (cfgid  ),
        .sclk    (sclk   ),
        .csn     (csn    ),
        .mosi    (mosi   ),
        .miso    (miso   ),
        .oen     (oen    ),
        .sig_in  (    sig_in   )
    );

    // 系统时钟产生
    initial begin
        clk = 0;
        forever #(CLK_PERIOD/2) clk = ~clk;
    end

    // 初始复位与赋值
    initial begin
        rst_n = 0;
        sclk  = 0;
        csn   = 1;
        mosi  = 0;
        cfgid = 5'h00; // 设定芯片ID为 10
        #(CLK_PERIOD * 10);
        rst_n = 1;
        #(CLK_PERIOD * 10);
        fork
            begin            // --- 开始测试 ---
            #2ms;
            spi_read(25'h08, 5'h00);
            #(CLK_PERIOD * 10);
            spi_read(25'h0c, 5'h00);
            #(CLK_PERIOD * 10);
            spi_write(25'h08, 5'h00, 32'h2);
            end

            begin
                gen_pulses(1000,5);
            end
        join


       //        spi_read(25'h04, 5'h00);
        //aa
       // spi_read(25'h00, 5'h0A);
        //  #(SCLK_PERIOD * 5);
        //  spi_read(25'h00, 5'h0A);
        // // 1. 测试写操作: 写入 0x12345678 到 地址 0x100
        // spi_write(25'h00, 5'h0A, 32'h12345678);
        
        // #(SCLK_PERIOD * 5);

        // // 2. 测试读操作: 从地址 0x100 读取 (验证读使能和地址)
        // spi_read(25'h00, 5'h0A);

        #(SCLK_PERIOD * 20);
        $display("Test Bench Finished.");
        $finish;
    end

    // ---------------------------------------------------------
    // Task: SPI 发送/接收 32位数据
    // ---------------------------------------------------------
    task spi_xfer_32(input [31:0] data_in, output [31:0] data_out);
        for (int i = 31; i >= 0; i--) begin
            mosi = data_in[i];      // MSB First  ,psclk前给数据  psclk让人采样. 自己什么时候采？要看别人什么时候给数据
            #(SCLK_PERIOD/2);
            sclk = 1;               // 上升沿，DUT采样 
            data_out[i] = miso; 
            // 采样从机发回的数据 
            #(SCLK_PERIOD/2);
            sclk = 0;
        end
    endtask

    // ---------------------------------------------------------
    // Task: 写操作 (wnr=0)
    // ---------------------------------------------------------
    task spi_write(input [24:0] target_addr, input [4:0] id, input [31:0] data);
        logic [31:0] header;
        logic [31:0] dummy;
        
        header = {1'b0, target_addr, id, 1'b0}; // 构造命令帧
        
        $display("[WRITE] Addr: %h, Data: %h", target_addr, data);
        
        csn = 0;
        spi_xfer_32(header, dummy); // 发送命令
        spi_xfer_32(data,   dummy); // 发送数据
        csn = 1;
        mosi = 0;
    endtask

    // ---------------------------------------------------------
    // Task: 读操作 (wnr=1)
    // ---------------------------------------------------------
    task spi_read(input [24:0] target_addr, input [4:0] id);
        logic [31:0] header;
        logic [31:0] read_val;
        
        header = {1'b1, target_addr, id, 1'b0}; // 构造命令帧 (wnr=1)
        
        $display("[READ]  Addr: %h", target_addr);
        
        csn = 0;
        spi_xfer_32(header, read_val); // 发送命令帧，此时读回的可能是无用数据
        spi_xfer_32(32'h0,  read_val); // 发送Dummy，接收真正的读数据
        csn = 1;
        mosi = 0;
        
        $display("[READ]  Result: %h", read_val);
    endtask


        task automatic gen_pulses(input int freq_khz, input int duration_ms);
        int half_period_ns;
        longint end_time_ns;
        begin
            if (freq_khz <= 0) begin
                sig_in = 0;
                #(duration_ms * 1000000);
            end else begin
                half_period_ns = 500000 / freq_khz;
                end_time_ns = $time + (longint'(duration_ms) * 1000000);
                
                $display("[%0t] Start generating signal: %0d kHz", $time, freq_khz);
                while ($time < end_time_ns) begin
                    sig_in = 1;
                    #(half_period_ns);
                    sig_in = 0;
                    #(half_period_ns);
                end
            end
        end
    endtask
endmodule