/****
 * rom_sd.sv - reads audio data off a rom and feeds it to the audio buffer
 *
 * @author: Dilanthi Prentice, Waylon Cude
 * @date: 6/12/25
 *
 * **/
module rom_sd(
    input logic clk,
    input logic reset,
    output logic ready,

    audio_buffer_interface.driver buffer
);
parameter MEM_FILE = "roundabout.mem";

typedef enum logic [3:0]{
    RESET, DELAY1, DELAY2, DELAY3, WRITEBUF, ENDWRITE1, ENDWRITE2, ENDWRITE3, WAIT
} state_t;

state_t current, next;
// First we write 2048B into the memory buffer, then signal to play it and
// wait for half signal to avoid overwriting memory
logic [18:0] rom_addr;
logic [7:0] rom_data;
logic rom_enable;
logic buffer_half;


// xpm_memory_sprom: Single Port ROM
// Xilinx Parameterized Macro, version 2024.2

// The ROM has 17 address bits and 8 data bits to store 128KiB, more than
// enough for one second of 48khz audio
xpm_memory_sprom #(
  .ADDR_WIDTH_A(19),               // DECIMAL
  .AUTO_SLEEP_TIME(0),             // DECIMAL
  .CASCADE_HEIGHT(0),              // DECIMAL
  .ECC_BIT_RANGE("7:0"),           // String
  .ECC_MODE("no_ecc"),             // String
  .ECC_TYPE("none"),               // String
  .IGNORE_INIT_SYNTH(0),           // DECIMAL
  .MEMORY_INIT_FILE(MEM_FILE),     // String
  .MEMORY_INIT_PARAM(""),         // String
  .MEMORY_OPTIMIZATION("true"),    // String
  .MEMORY_PRIMITIVE("auto"),       // String
  .MEMORY_SIZE((1<<19)*8),          // DECIMAL
  .MESSAGE_CONTROL(0),             // DECIMAL
  .RAM_DECOMP("auto"),             // String
  .READ_DATA_WIDTH_A(8),           // DECIMAL
  .READ_LATENCY_A(2),              // DECIMAL
  .READ_RESET_VALUE_A("0"),        // String
  .RST_MODE_A("SYNC"),             // String
  .SIM_ASSERT_CHK(0),              // DECIMAL; 0=disable simulation messages, 1=enable simulation messages
  .USE_MEM_INIT(1),                // DECIMAL
  .USE_MEM_INIT_MMI(0),            // DECIMAL
  .WAKEUP_TIME("disable_sleep")    // String
)
xpm_memory_sprom_inst (
  .douta(rom_data),                // READ_DATA_WIDTH_A-bit output: Data output for port A read operations.
  .addra(rom_addr),                // ADDR_WIDTH_A-bit input: Address for port A read operations.
  .clka(clk),                      // 1-bit input: Clock signal for port A.
  .ena(rom_enable),                // 1-bit input: Memory enable signal for port A. Must be high on clock
                                   // cycles when read operations are initiated. Pipelined internally.

  .rsta(reset),                     // 1-bit input: Reset signal for the final port A output register stage.
                                   // Synchronously resets output port douta to the value specified by
                                   // parameter READ_RESET_VALUE_A.
                                   
  // These are required I think? The ROM gets optimized out without them
  .sleep(0),
  // Should this have a separate control signal? What happens if it gets
  // turned on early like I'm doing now?
  .regcea(rom_enable),
  .injectsbiterra(0),
  .injectdbiterra(0)
);
// End of xpm_memory_sprom_inst instantiation

assign buffer_half = buffer.addra[10];
// The audio buffer memory is clocked at the same speed as this module
assign buffer.clka = clk;

//next state logic
always_comb
begin
    case (current)
        RESET:      if (reset) next = RESET;
                    else next = DELAY1;
        DELAY1:     next = DELAY2;
        DELAY2:     next = DELAY3;
        DELAY3:     next = WRITEBUF;
        
        WRITEBUF:   if (buffer.addra[9:0] < 1020) next = WRITEBUF;
                    else next = ENDWRITE1;
        
        ENDWRITE1:   next = ENDWRITE2;
        ENDWRITE2:   next = ENDWRITE3;
        ENDWRITE3:   next = WAIT;
        
        WAIT:       if (buffer_half == buffer.address_half) next = WAIT;
                    else next = DELAY1;
        
        default: next = RESET;
    endcase
end

//sequential output logic
always_ff @(posedge clk)
begin
    case (current)
        RESET:      begin
                    rom_addr <= 0;
                    rom_enable <= 1;
                    buffer.addra <= 0;
                    buffer.ena <= 0;
                    ready <= 0;
                    end
        
        DELAY1:     rom_addr <= rom_addr + 1;
        DELAY2:     rom_addr <= rom_addr + 1;
        DELAY3:     begin
                    rom_addr <= rom_addr + 1;
                    buffer.dina <= rom_data;
                    buffer.ena <= 1;
                    end
        
        WRITEBUF:   begin
                    buffer.ena <= 1;
                    buffer.dina <= rom_data;
                    buffer.addra <= buffer.addra + 1;
                    rom_addr <= rom_addr + 1;
                    end

        ENDWRITE1, ENDWRITE2:   
                    begin
                    buffer.ena <= 1;
                    buffer.dina <= rom_data;
                    buffer.addra <= buffer.addra + 1;
                    end

        
        ENDWRITE3:  begin
                    buffer.ena <= 0;
                    buffer.dina <= rom_data;
                    buffer.addra <= buffer.addra + 1;
                    ready <= 1;
                    end
        
        WAIT:       ;
        
        default:    begin
                    rom_addr <= 0;
                    rom_enable <= 0; 
                    buffer.addra <= 0;
                    buffer.dina <= 0;
                    buffer.ena <= 0;
                    ready <= 0;
                    end
    endcase
end

//sequential clocking block
always_ff @(posedge clk)
begin
    if (reset)
        current <= RESET;
    else
        current <= next;
end

endmodule
