/****
 * audio_buffer.sv - holds a 2KiB audio buffer of 16-bit pcm audio
 *                   samples (with pcm being the audio format we are using)
 *
 * @author: Waylon Cude, Dilanthi
 * @date: 6/12/2025
 *
 * */
`ifdef VERILATOR
    `include "sdvd_defs.sv"
`endif

import sdvd_defs::SPEED;

//this interfaces with block ram
module audio_buffer(
    // The clock should be at 48khz
    input logic clk, reset,

    // Control signals
    input logic play, stop,
    input SPEED speed,


    // Whether the audio buffer is currently playing
    output logic playing,

    // A 16-bit audio sample to output
    output logic [15:0] sample,

    // Inputs for the memory buffer
    audio_buffer_interface.receiver driver
);
logic [9:0] address;

// State register
logic enb;
logic [15:0] doutb;

// A single bit counter, to avoid feeding samples given the 1 cycle read delay
logic delay;

// Whether the current address being read from is in the upper or lower
// half of the 2KiB buffer
//
// The MSB of the address == higher/lower half address
assign driver.address_half = address[9];

always_ff @(posedge clk) begin
    enb <= 0;
    if (reset) begin
        playing <= 0;
        address <= 0;
        sample <= 0;
        delay <= '1;
    end
    else if (!playing) begin
        if (play) begin
            playing <= 1;
            delay <= '1;
        end

    end
    else begin
        if (stop) begin
            playing <= 0;
            // If playback "Stops" then reset the address to a known value, 0
            address <= 0;
            delay <= '1;
        end
        else begin
            // This will overflow to the correct value always
            address <= address + speed;
            enb <= 1;
            // NOTE: I really don't know a good way to generate the load
            // signal. It maybe could be an inverted 48khz clock?
            if (delay == 0) begin
                sample <= doutb;
            end
            else begin
                sample <= '0;
                delay <= '0;
            end
            
        end
    end
end

//xpm_memory_sdpram #(
//    .WRITE_DATA_WIDTH_A(16)
//) buffer ();
xpm_memory_sdpram #(
  .ADDR_WIDTH_A(11),               // DECIMAL
  .ADDR_WIDTH_B(10),               // DECIMAL
  .AUTO_SLEEP_TIME(0),            // DECIMAL
  .BYTE_WRITE_WIDTH_A(8),        // DECIMAL
  .CASCADE_HEIGHT(0),             // DECIMAL
  .CLOCKING_MODE("independent_clock"), // String
  .ECC_BIT_RANGE("7:0"),          // String
  .ECC_MODE("no_ecc"),            // String
  .ECC_TYPE("none"),              // String
  .IGNORE_INIT_SYNTH(0),          // DECIMAL
  .MEMORY_INIT_FILE("none"),      // String
  .MEMORY_INIT_PARAM("0"),        // String
  .MEMORY_OPTIMIZATION("true"),   // String
  .MEMORY_PRIMITIVE("auto"),      // String
  .MEMORY_SIZE(16*1024),             // DECIMAL
  .MESSAGE_CONTROL(0),            // DECIMAL
  .RAM_DECOMP("auto"),            // String
  .READ_DATA_WIDTH_B(16),         // DECIMAL
  .READ_LATENCY_B(1),             // DECIMAL
  .READ_RESET_VALUE_B("0"),       // String
  .RST_MODE_A("SYNC"),            // String
  .RST_MODE_B("SYNC"),            // String
  .SIM_ASSERT_CHK(0),             // DECIMAL; 0=disable simulation messages, 1=enable simulation messages
  .USE_EMBEDDED_CONSTRAINT(0),    // DECIMAL
  .USE_MEM_INIT(1),               // DECIMAL
  .USE_MEM_INIT_MMI(0),           // DECIMAL
  .WAKEUP_TIME("disable_sleep"),  // String
  .WRITE_DATA_WIDTH_A(8),        // DECIMAL
  .WRITE_MODE_B("no_change"),     // String
  .WRITE_PROTECT(1)               // DECIMAL
)
buffer (
  .doutb(doutb),          // READ_DATA_WIDTH_B-bit output: Data output for port B read operations.
  .addra(driver.addra),          // ADDR_WIDTH_A-bit input: Address for port A write operations.
  .addrb(address),        // ADDR_WIDTH_B-bit input: Address for port B read operations.
  .clka(driver.clka),            // 1-bit input: Clock signal for port A. Also clocks port B when
                          // parameter CLOCKING_MODE is "common_clock".

  .clkb(clk),             // 1-bit input: Clock signal for port B when parameter CLOCKING_MODE is
                          // "independent_clock". Unused when parameter CLOCKING_MODE is
                          // "common_clock".
  .dina(driver.dina),            // WRITE_DATA_WIDTH_A-bit input: Data input for port A write operations.
  .ena(driver.ena),              // 1-bit input: Memory enable signal for port A. Must be high on clock
                          // cycles when write operations are initiated. Pipelined internally.

  .enb(enb),              // 1-bit input: Memory enable signal for port B. Must be high on clock
                          // cycles when read operations are initiated. Pipelined internally.
//active high reset
  .rstb(reset),            // 1-bit input: Reset signal for the final port B output register stage.
                          // Synchronously resets output port doutb to the value specified by
                          // parameter READ_RESET_VALUE_B.
  .wea(driver.ena),       // WRITE_DATA_WIDTH_A/BYTE_WRITE_WIDTH_A-bit input: Write enable vector
                          // for port A input data port dina. 1 bit wide when word-wide writes are
                          // used. In byte-wide write configurations, each bit controls the
                          // writing one byte of dina to address addra. For example, to
                          // synchronously write only bits [15-8] of dina when WRITE_DATA_WIDTH_A
                          // is 32, wea would be 4'b0010.
  // Extra inputs that I guess I need
  .sleep(0),
  .injectsbiterra(0),
  .injectdbiterra(0),
  // With a latency of 1 this surely does not matter
  .regceb(enb)
);

endmodule
