All checks were successful
ci/woodpecker/push/test-workflow Pipeline was successful
I think I did some funny math errors so new goal is 8-bit pcm
159 lines
5.6 KiB
Systemverilog
159 lines
5.6 KiB
Systemverilog
/****
|
|
* 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
|