/**** * 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