Tridora-CPU/tridoracpu/tridoracpu.srcs/tdraudio.v
2025-09-26 01:36:26 +02:00

169 lines
4.8 KiB
Verilog

`timescale 1ns / 1ps
// waveform generator module (only pulse wave for now)
module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22, AMP_WIDTH=16) (
input wire clk,
input wire reset,
input wire [1:0] reg_sel,
output wire [DATA_WIDTH-1:0] rd_data,
input wire [DATA_WIDTH-1:0] wr_data,
input wire rd_en,
input wire wr_en,
output wire [AMP_WIDTH-1:0] amp_val,
output wire running
);
localparam TDRAU_REG_CTL = 0; /* control register */
localparam TDRAU_REG_CLK = 1; /* clock divider register */
localparam TDRAU_REG_AMP = 2; /* amplitude (volume) register */
reg channel_enable;
reg [CLOCK_DIV_WIDTH-1:0] clock_div;
reg [CLOCK_DIV_WIDTH-1:0] div_count;
reg amp_phase;
reg [AMP_WIDTH-1:0] amp_start;
reg [AMP_WIDTH-1:0] amp_out;
//assign rd_data = {{DATA_WIDTH-8-CLOCK_DIV_WIDTH{1'b0}}, div_count, {7{1'b0}}, channel_enable};
assign rd_data = {8'b0, amp_start, {7{1'b0}}, channel_enable};
assign amp_val = amp_out;
assign running = channel_enable;
/* channel enable flag */
always @(posedge clk)
begin
if(reset)
channel_enable <= 0;
else if (wr_en && (reg_sel == TDRAU_REG_CTL))
channel_enable <= wr_data[0];
end
/* clock divider register */
always @(posedge clk)
begin
if(reset)
clock_div <= 0;
else
if (wr_en && (reg_sel == TDRAU_REG_CLK))
clock_div <= wr_data;
end
/* amplitude register */
always @(posedge clk)
begin
if(reset)
amp_start <= 0;
else
if (wr_en && (reg_sel == TDRAU_REG_AMP))
amp_start <= wr_data;
end
/* divider counter */
always @(posedge clk)
begin
if(channel_enable)
begin
if(div_count == 0) // reset counter if it reaches zero
div_count <= clock_div;
else
div_count <= div_count - 1; // else just decrement it
end
else
if (wr_en && (reg_sel == TDRAU_REG_CLK))
div_count <= 1; // start cycle in next clock tick
end
/* amplitude out */
always @(posedge clk)
begin
if (reset)
begin
amp_out <= 0;
amp_phase <= 1;
end
else
if (channel_enable && (div_count == 0)) // invert amplitude on clock tick
begin
amp_out <= amp_phase ? amp_start : ~amp_start;
amp_phase <= ~amp_phase;
end
// reset phase bit when enabling the channel
if (wr_en && (reg_sel == TDRAU_REG_CTL) && wr_data[0])
// when channel is enabled, phase will be flipped on next tick
// because div_count will become zero
amp_phase <= 1;
end
endmodule
module tdraudio #(DATA_WIDTH=32) (
input wire clk,
input wire reset,
input wire [6:0] io_addr,
output wire [DATA_WIDTH-1:0] rd_data,
input wire [DATA_WIDTH-1:0] wr_data,
input wire rd_en,
input wire wr_en,
output wire pdm_out,
output wire gain_sel,
output wire shutdown_n
);
localparam CLOCK_DIV_WIDTH = 22;
localparam AMP_WIDTH = 16;
wire chan_sel = io_addr[6:2];
wire [1:0] reg_sel = io_addr[1:0];
wire [AMP_WIDTH-1:0] chan0_amp;
wire [DATA_WIDTH-1:0] chan0_rd_data;
wire chan0_running;
wire chan0_sel = chan_sel == 0;
wire chan0_rd_en = chan0_sel && rd_en;
wire chan0_wr_en = chan0_sel && wr_en;
wire [AMP_WIDTH-1:0] chan1_amp;
wire [DATA_WIDTH-1:0] chan1_rd_data;
wire chan1_running;
wire chan1_sel = chan_sel == 1;
wire chan1_rd_en = chan1_sel && rd_en;
wire chan1_wr_en = chan1_sel && wr_en;
wire running = chan0_running || chan1_running;
assign rd_data = chan0_sel ? chan0_rd_data :
chan1_sel ? chan1_rd_data :
{DATA_WIDTH{1'b1}};
wavegen chan0(clk, reset, reg_sel,
chan0_rd_data, wr_data,
chan0_rd_en, chan0_wr_en,
chan0_amp, chan0_running);
wavegen chan1(clk, reset, reg_sel,
chan1_rd_data, wr_data,
chan1_rd_en, chan1_wr_en,
chan1_amp, chan1_running);
reg [AMP_WIDTH:0] deltasigma_acc; // one extra bit
wire [AMP_WIDTH:0] amp_sum = chan0_amp + chan1_amp; // also one overflow bit here
wire [AMP_WIDTH-1:0] amp_sum_scaled = amp_sum[AMP_WIDTH:1]; // shifted right to scale down
assign gain_sel = 1; // gain select: 0 -> 12dB, 1 -> 6dB
assign shutdown_n = running;
/* delta-sigma DAC */
always @(posedge clk)
begin
if(reset)
deltasigma_acc <= 0;
else
if (running)
deltasigma_acc <= deltasigma_acc[AMP_WIDTH-1:0] + amp_sum_scaled;
end
/* 1-bit audio output */
assign pdm_out = deltasigma_acc[AMP_WIDTH];
endmodule