Compare commits

..

15 commits

Author SHA1 Message Date
e295a774d7 Merge pull request 'tdraudio-pcm' (#3) from tdraudio-pcm into main
Reviewed-on: #3
2025-10-13 00:42:14 +02:00
slederer
536c0adde7 pcmaudio: set amplitude to biased zero at end
pcmtest2: small updates to the demo program
2025-10-12 22:52:17 +02:00
slederer
598ee8921f tdraudio: add documentation 2025-10-07 01:16:25 +02:00
slederer
5c00dfcec9 tdraudio: add irq_enable flag, add pcmaudio library
runtime: disable interrupts on PTERM
stdlib: check for error state in FileSize
2025-10-07 00:37:53 +02:00
slederer
7cc9ee807d tdraudio: remove pulse/noise waves, add sample buffer and irq 2025-10-04 00:09:10 +02:00
slederer
5db9631592 correct last benchmark results 2025-10-03 21:56:20 +02:00
slederer
e690d3eb2b tdraudio: correctly generate silence, clear DAC accumulator 2025-09-30 00:50:33 +02:00
slederer
4d4cc0c535 dram_bridge: cleanup
- mem_wait must be enabled on each write
- dcache_hit is never true on a write, so the
  ~dcache_hit clause was always true
2025-09-30 00:49:17 +02:00
slederer
2735b80fec tdraudio: remove unneeded status flags, tweak project settings 2025-09-29 20:40:07 +02:00
slederer
12033bb6d2 tdraudio: add direct amplitude control 2025-09-29 19:10:48 +02:00
slederer
57430a4df6 tdraudio: add noise generator 2025-09-28 02:21:58 +02:00
slederer
2342683836 tdraudio: implement four channels 2025-09-27 01:34:17 +02:00
slederer
c354bb8cb8 tdraudio: implement multiple channels 2025-09-26 01:36:26 +02:00
slederer
a73fad5786 tdraudio: implement ΔΣ-DAC and volume control 2025-09-25 00:14:00 +02:00
slederer
d5888861d3 tdraudio: first step of implementing a sound generator 2025-09-23 23:39:04 +02:00
15 changed files with 858 additions and 34 deletions

View file

@ -9,12 +9,13 @@ The interrupt controller uses a single register at address: $980
|_bit_ |15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|t |t |t |t |t |t |t |t |- |- |- |- |- |- |p1 |p0 |
|_Value_|t |t |t |t |t |t |t |t |- |- |- |- |- |p2 |p1 |p0 |
|Bitfields|Description|
|---------|-----------|
| _t_ | unsigned 24 bit counter of timer ticks since reset
| _p2_ | IRQ 2 (audio) interrupt pending if 1
| _p1_ | IRQ 1 (timer tick) interrupt pending if 1
| _p0_ | IRQ 0 (UART) interrupt pending if 1

104
doc/tdraudio.md Normal file
View file

@ -0,0 +1,104 @@
# Audio Controller
The audio controller provides four channels of 16-bit PCM audio playback.
It uses multiple registers starting at address $A00.
Each of the four channels has three registers.
For the first channel the register addresses are:
|Address|Description|
|-------|-----------|
| $A00 | Control Register |
| $A01 | Clock Divider Register |
| $A02 | Amplitude Register |
The register addresses for the second channel start at $A04,
the third channel at $A08
and the fourth channel at $A0C.
## Reading the control register
|_bit_ |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|- |- |- |- |- |- |- |- |- |-|- |- |- |- |- |- |
|_bit_ |15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|- |- |- |- |- |- |- |- |- |- |- |i |f | e | p | c |
|Bitfields|Description|
|---------|-----------|
| _i_ | interrupt is enabled for this channel when 1 |
| _f_ | sample buffer is full when 1 |
| _e_ | sample buffer is empty when 1 |
| _p_ | changes from 0 to 1 and vice versa on each sample clock |
| _c_ | channel is enabled if 1 |
## Writing the control register
|_bit_ |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|- |- |- |- |- |- |- |- |- |-|- |- |- |- |- |- |
|_bit_ |15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|- |- |- |- |- |- |- |- |- |- |- |i |- | - | - | c |
|Bitfields|Description|
|---------|-----------|
| _c_ | enable channel if 1, disable if 0 |
| _i_ | enable channel interrupt if 1, disable if 0 |
## Writing the clock divider register
|_bit_ |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|d |d |d |d |d |d |d |d |d |d|d |d |d |d |d |d |
|_bit_ |15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|d |d |d |d |d |d |d |d |d |d|d |d |d |d |d |d |
|Bitfields|Description|
|---------|-----------|
| _d_ | an unsigned 32-bit value for the clock divider |
## Writing the amplitude register
|_bit_ |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|- |- |- |- |- |- |- |- |- |-|- |- |- |- |- |- |
|_bit_ |15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|
|- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |- |
|_Value_|a |a |a |a |a |a |a |a |a |a |a |a |a | a | a | a |
|Bitfields|Description|
|---------|-----------|
| _a_ | an unsigned 16-bit value for the amplitude (sample) value with a bias of 32768 |
## Notes
The clock divider specifies the number of CPU clock ticks between two samples.
Writing to the amplitude register adds the sample value to the sample buffer. The sample buffer is organized as a FIFO with 16 elements.
Amplitude (sample) values are represented as unsigned, biased 16-bit numbers. The bias is 32768, so given an amplitude range of 1.0 to -1.0, a 1.0 is represented by 65535, 0.0 by 32768 and -1.0 by 0.
Interrupt processing needs to be enabled for each channel if required.
An interrupt on any channel will be signalled to the interrupt controller
as IRQ 2. The interrupt service routine should check all running channels
for an emtpy buffer.
If an audio interrupt has occured on a channel, the interrupt enable flag
is cleared for that channel. It needs to be re-enabled in the interrupt service routine.
Interrupts also need to be enabled on the interrupt controller,
and re-enabled there after each interrupt.

View file

@ -97,7 +97,7 @@ Running benchmarks.prog
Arty-A7-35T
76.92MHz, 64KB SRAM, 256MB DRAM,
16B instruction cache, 16B wt data cache
running in DRAM (except corelib, stdlib, runtime)
running in SRAM
Running benchmarks.prog
empty loop 10M 00:00:07
@ -124,7 +124,7 @@ Running benchmarks.prog
Arty-A7-35T
76.92MHz, 64KB SRAM, 256MB DRAM,
16B instruction cache, 16B wb data cache
running in DRAM (except corelib, stdlib, runtime)
running in SRAM
Running benchmarks.prog
empty loop 10M 00:00:04
@ -149,7 +149,7 @@ Running benchmarks.prog
Arty-A7-35T
76.92MHz, 32KB SRAM, 256MB DRAM,
16B instruction cache, 16B wb data cache
running in DRAM (except corelib, stdlib, runtime)
running in SRAM
Running benchmarks.prog
empty loop 10M 00:00:04
@ -169,3 +169,28 @@ Running benchmarks.prog
array copy 128k 1K 00:00:39
exp() 10K 00:00:25
cos() 10K 00:00:05
--------------------------------------------
Arty-A7-35T
76.92MHz, 64KB SRAM, 256MB DRAM,
16B instruction cache, 16B wb data cache
running in DRAM (except corelib, stdlib, runtime)
Running benchmarks.prog
empty loop 10M 00:00:10
write variable 10M 00:00:11
read variable 10M 00:00:11
integer addition 10M 00:00:13
real addition 1M 00:00:27
integer multiplication 1M 00:00:35
real multiplication 1M 00:00:43
integer division 1M 00:01:05
real division 1M 00:00:51
string indexing 1M 00:00:36
string iteration 1M 00:00:20
new/dispose 1k 1M 00:00:23
new/dispose 128k 1M 00:00:23
array copy 1k 10K 00:00:03
array copy 128k 1K 00:00:48
exp() 10K 00:00:28
cos() 10K 00:00:04

47
examples/pcmtest.pas Normal file
View file

@ -0,0 +1,47 @@
{$H1536}
program pcmtest;
uses pcmaudio;
var filename:string;
buf:SndBufPtr;
f:file;
size:integer;
i:integer;
c:char;
sampleRate:integer;
err:integer;
begin
if ParamCount > 0 then
filename := ParamStr(1)
else
begin
write('Filename> ');
readln(filename);
end;
err := 1;
if ParamCount > 1 then
val(ParamStr(2),sampleRate, err);
if err <> 0 then
sampleRate := 16000;
open(f, filename, ModeReadOnly);
size := FileSize(f);
new(buf, size);
buf^ := '';
write('Reading ', size, ' bytes...');
for i := 1 to size do
begin
read(f,c);
AppendChar(buf^,c);
end;
writeln;
close(f);
PlaySample(buf, sampleRate);
dispose(buf);
end.

74
examples/pcmtest2.pas Normal file
View file

@ -0,0 +1,74 @@
{$H2560}
program pcmtest2;
uses pcmaudio;
var filename:string;
buf:SndBufPtr;
sampleRate:integer;
err:integer;
done:boolean;
c:char;
function readAudioFile(fname:string):SndBufPtr;
var i,size:integer;
c:char;
buf:SndBufPtr;
f:file;
begin
open(f, fname, ModeReadOnly);
size := FileSize(f);
new(buf, size);
buf^ := '';
write('Reading ', size, ' bytes...');
for i := 1 to size do
begin
read(f,c);
AppendChar(buf^,c);
end;
writeln;
close(f);
readAudioFile := buf;
end;
begin
if ParamCount > 0 then
filename := ParamStr(1)
else
begin
write('Filename> ');
readln(filename);
end;
err := 1;
if ParamCount > 1 then
val(ParamStr(2), sampleRate, err);
if err > 0 then
sampleRate := 32000;
buf := readAudioFile(filename);
SampleQStart(buf, sampleRate);
write('Press ESC to stop> ');
done := false;
while not done do
begin
read(c);
if c = #27 then
begin
done := true; writeln(';');
end
else
if c = '?' then
begin
writeln; writeln('Queue: ', SampleQSize);
end;
end;
SampleQStop;
dispose(buf);
end.

7
lib/pcmaudio.inc Normal file
View file

@ -0,0 +1,7 @@
type SndBuf = string[32768];
type SndBufPtr = ^SndBuf;
procedure PlaySample(buf:SndBufPtr;sampleRate:integer); external;
procedure SampleQStart(buf:SndBufPtr;sampleRate:integer); external;
procedure SampleQStop; external;
function SampleQSize:integer; external;

247
lib/pcmaudio.s Normal file
View file

@ -0,0 +1,247 @@
.EQU AUDIO_BASE $A00
.EQU IRQC_REG $980
.EQU IRQC_EN $80
; args: sample rate
START_PCMAUDIO:
; calculate clock divider
LOADCP 77000000
SWAP
LOADCP _DIV
CALL
LOADC AUDIO_BASE + 1
SWAP ; put clock divider on ToS
; LOADCP 4812 ; clock divider for 16KHz sample rate
; LOADCP 2406 ; clock divider for 32KHz sample rate
STOREI 1
LOADCP 32768 ; set amplitude to biased 0
STOREI
DROP
LOADC AUDIO_BASE
LOADC 17 ; enable channel, enable interrupt
STOREI
DROP
RET
STOP_AUDIO:
LOADC AUDIO_BASE
LOADC 0
STOREI
DROP
RET
; args: pointer to pascal string, sample rate
.EQU PS_PTR 0
.EQU PS_COUNT 4
.EQU PS_FS 12
PLAYSAMPLE:
FPADJ -PS_FS
LOADCP START_PCMAUDIO
CALL
DUP
LOADI ; get string size from header
SHR ; divide by 4 to get word count
SHR
STORE PS_COUNT
INC 8 ; skip rest of header
STORE PS_PTR ; store sample data pointer
PS_L0:
LOAD PS_PTR ; load pointer
INC.S1.X2Y 4 ; increment and keep old value
STORE PS_PTR ; store incremented value
LOADI ; load 32 bit word
DUP
BROT ; get upper 16 bit word
BROT
LOADCP $FFFF
AND
LOADCP PLAY_1SAMPLE
CALL
LOADCP $FFFF ; get lower 16 bit word
AND
LOADCP PLAY_1SAMPLE
CALL
LOAD PS_COUNT ; load word count
DEC 1 ; decrement
DUP
STORE PS_COUNT
CBRANCH.NZ PS_L0 ; loop if not zero
LOADCP STOP_AUDIO
CALL
FPADJ PS_FS
RET
; play one sample, waiting
; for the clock divider, which
; is visible via the phase flag
; args: 16-bit unsigned sample
PLAY_1SAMPLE:
PLAY1_L0:
LOADC AUDIO_BASE
LOADI
LOADC 8 ; get fifo_full flag
AND
CBRANCH.NZ PLAY1_L0 ; loop if fifo is full
LOADC AUDIO_BASE+2 ; store amplitude value
SWAP
STOREI
DROP
RET
; start interrupt-driven sample playback
; args: pointer to pascal string, sample rate
SAMPLEQSTART:
LOADCP START_PCMAUDIO
CALL
LOADCP SMPLQ_COUNT
OVER
LOADI ; get string size from header
SHR ; divide by 4 to get word count
SHR
STOREI
DROP
LOADCP SMPLQ_PTR
SWAP
INC 8 ; skip rest of header
STOREI ; store sample data pointer
DROP
LOADCP SMPLQ_ISR ; set interrupt handler
STOREREG IV
LOADC IRQC_REG ; enable irq
LOADC IRQC_EN
STOREI
DROP
RET
SAMPLEQSTOP:
LOADCP SMPLQ_PTR
LOADC 0
STOREI
DROP
LOADCP STOP_AUDIO
CALL
LOADC IRQC_REG ; disable irq
LOADC 0
STOREI
DROP
RET
SAMPLEQSIZE:
LOADCP SMPLQ_COUNT
LOADI
RET
SMPLQ_PTR: .WORD 0
SMPLQ_COUNT: .WORD 0
SMPLQ_ISR:
LOADC IRQC_REG
LOADI
LOADC 4 ; check for audio interrupt
AND
CBRANCH.Z SMPLQ_I_XT ; if flag not set, exit
SMPLQ_I_L:
LOADCP SMPLQ_PTR
LOADI ; load word pointer
DUP
CBRANCH.NZ SMPLQ_I_B ; check for null pointer
DROP
BRANCH SMPLQ_I_XT ; if null, end interrupt routine
SMPLQ_I_B:
LOADI ; load next word
DUP
BROT ; get high half-word
BROT
LOADCP $FFFF
AND
LOADC AUDIO_BASE+2
SWAP
STOREI ; write sample, keep addr
SWAP ; addr to NoS, lower halfword on ToS
LOADCP $FFFF
AND
STOREI ; write sample
DROP
; decrement word count
LOADCP SMPLQ_COUNT
LOADI.S1.X2Y ; load counter, keep addr
DEC 1
DUP
CBRANCH.Z SMPLQ_I_END ; end if zero
STOREI ; store new counter value
DROP
; increment pointer
LOADCP SMPLQ_PTR
LOADI.S1.X2Y
INC 4
STOREI
DROP
; check if fifo is full
LOADC AUDIO_BASE
LOADI
LOADC 8 ; fifo_full
AND
CBRANCH.Z SMPLQ_I_L ; next sample if not full
LOADC AUDIO_BASE
LOADC 17 ; re-enable channel interrupt
STOREI
DROP
BRANCH SMPLQ_I_XT
; end playback, set ptr and counter to zero
SMPLQ_I_END:
DROP
DROP
LOADCP SMPLQ_PTR
LOADC 0
STOREI
DROP
LOADCP SMPLQ_COUNT
LOADC 0
STOREI
DROP
; set amplitude out to zero (biased)
LOADC AUDIO_BASE+2
LOADCP 32768
STOREI
DROP
SMPLQ_I_XT:
LOADC IRQC_REG ; re-enable interrupts
LOADC IRQC_EN
STOREI
DROP
LOADREG IR ; jump via interrupt return register
JUMP

View file

@ -1931,6 +1931,12 @@ _CLEARESTACK_XT:
; Terminate program: clear estack and
; jump to coreloader
PTERM:
; just to be safe, disable interrupts
LOADC $980
LOADC 0
STOREI
DROP
LOADCP _CLEARESTACK
CALL
LOADCP LOADER_START

View file

@ -1844,6 +1844,8 @@ end;
function filesize(var fil:file):integer;
begin
checkerror(fil);
if fil.typ = IOChannel then
filesize := -1
else

View file

@ -70,10 +70,10 @@ set_property -dict {PACKAGE_PIN V14 IOSTANDARD LVCMOS33} [get_ports VGA_VS_O]
#set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { jc[7] }]; #IO_L23N_T3_A02_D18_14 Sch=jc_n[4]
## Pmod Header JD
#set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { jd[0] }]; #IO_L11N_T1_SRCC_35 Sch=jd[1]
#set_property -dict { PACKAGE_PIN D3 IOSTANDARD LVCMOS33 } [get_ports { jd[1] }]; #IO_L12N_T1_MRCC_35 Sch=jd[2]
set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { amp2_ain }]; #IO_L11N_T1_SRCC_35 Sch=jd[1]
set_property -dict { PACKAGE_PIN D3 IOSTANDARD LVCMOS33 } [get_ports { amp2_gain }]; #IO_L12N_T1_MRCC_35 Sch=jd[2]
#set_property -dict { PACKAGE_PIN F4 IOSTANDARD LVCMOS33 } [get_ports { jd[2] }]; #IO_L13P_T2_MRCC_35 Sch=jd[3]
#set_property -dict { PACKAGE_PIN F3 IOSTANDARD LVCMOS33 } [get_ports { jd[3] }]; #IO_L13N_T2_MRCC_35 Sch=jd[4]
set_property -dict { PACKAGE_PIN F3 IOSTANDARD LVCMOS33 } [get_ports { amp2_shutdown_n }]; #IO_L13N_T2_MRCC_35 Sch=jd[4]
#set_property -dict { PACKAGE_PIN E2 IOSTANDARD LVCMOS33 } [get_ports { jd[4] }]; #IO_L14P_T2_SRCC_35 Sch=jd[7]
#set_property -dict { PACKAGE_PIN D2 IOSTANDARD LVCMOS33 } [get_ports { jd[5] }]; #IO_L14N_T2_SRCC_35 Sch=jd[8]
#set_property -dict { PACKAGE_PIN H2 IOSTANDARD LVCMOS33 } [get_ports { jd[6] }]; #IO_L15P_T2_DQS_35 Sch=jd[9]

View file

@ -165,7 +165,7 @@ module dram_bridge #(ADDR_WIDTH = 32, WIDTH = 32)
assign app_wdf_data = { {4{mem_write_data}} };
assign mem_wait = (dram_read_enable & ~read_inprogress) |
(mem_write_enable & ~dcache_hit & (~app_wdf_rdy | ~app_rdy)) |
(mem_write_enable & (~app_wdf_rdy | ~app_rdy)) |
(read_inprogress & ~app_rd_data_valid);
assign app_en = (dram_read_enable & ~read_inprogress) |

View file

@ -1,6 +1,6 @@
`timescale 1ns / 1ps
module irqctrl #(IRQ_LINES = 2, IRQ_DELAY_WIDTH = 4) (
module irqctrl #(IRQ_LINES = 3, IRQ_DELAY_WIDTH = 4) (
input wire clk,
input wire [IRQ_LINES-1:0] irq_in,
input wire cs,

View file

@ -0,0 +1,262 @@
`timescale 1ns / 1ps
// waveform generator module (PCM)
module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22,
AMP_WIDTH=16, AMP_BIAS=32768) (
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,
output wire irq
);
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_out;
wire fifo_wr_en;
wire fifo_rd_en, fifo_full, fifo_empty;
wire [DATA_WIDTH-1:0] fifo_rd_data;
fifo #(.ADDR_WIDTH(4), .DATA_WIDTH(16)) sample_buf(
clk, reset,
fifo_wr_en, fifo_rd_en,
wr_data, fifo_rd_data,
fifo_full,
fifo_empty
);
assign fifo_rd_en = (div_count == 0) && channel_enable && ~fifo_empty;
assign fifo_wr_en = wr_en && (reg_sel == TDRAU_REG_AMP);
reg irq_buf, irq_enable;
assign irq = channel_enable && irq_buf;
reg [DATA_WIDTH-1:0] rd_data_buf;
assign rd_data = rd_data_buf;
assign amp_val = amp_out;
assign running = channel_enable;
wire ctl_reg_write = wr_en && (reg_sel == TDRAU_REG_CTL);
/* update read data buffer */
always @(posedge clk)
begin
rd_data_buf <= {{DATA_WIDTH-8{1'b0}},
{3{1'b0}}, irq_enable, fifo_full, fifo_empty, amp_phase, channel_enable};
end
/* irq signal to interrupt controller */
always @(posedge clk)
begin
if(reset)
irq_buf <= 0;
else
if(fifo_empty && irq_enable)
irq_buf <= 1;
else
irq_buf <= 0;
end
/* interrupt enable flag */
always @(posedge clk)
begin
if(reset)
irq_enable <= 0;
else
if(ctl_reg_write)
irq_enable <= wr_data[4];
else
if(irq_buf)
irq_enable <= 0; // disable interrupts after an interrupt
end
/* channel enable flag */
always @(posedge clk)
begin
if(reset)
channel_enable <= 0;
else if (ctl_reg_write)
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
/* 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)) // when setting divider,
div_count <= 1; // start cycle on next clock tick
end
/* amplitude out */
always @(posedge clk)
begin
if (reset)
begin
amp_out <= AMP_BIAS;
amp_phase <= 1;
end
else
if (channel_enable)
begin
if (div_count == 0) // invert amplitude on clock tick
begin
amp_out <= fifo_rd_data;
amp_phase <= ~amp_phase;
end
end
else
amp_out <= AMP_BIAS;
// reset phase bit when enabling the channel
if (ctl_reg_write && wr_data[0] && ~channel_enable)
// when channel is being 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 irq_out,
output wire pdm_out,
output wire gain_sel,
output wire shutdown_n
);
localparam CLOCK_DIV_WIDTH = 22;
localparam AMP_WIDTH = 16;
localparam AMP_BIAS = 32768;
localparam DAC_WIDTH = 18;
wire [4:0] 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_irq;
wire chan0_sel = chan_sel == 5'd0;
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_irq;
wire chan1_sel = chan_sel == 5'd1;
wire chan1_rd_en = chan1_sel && rd_en;
wire chan1_wr_en = chan1_sel && wr_en;
wire [AMP_WIDTH-1:0] chan2_amp;
wire [DATA_WIDTH-1:0] chan2_rd_data;
wire chan2_running;
wire chan2_irq;
wire chan2_sel = chan_sel == 5'd2;
wire chan2_rd_en = chan2_sel && rd_en;
wire chan2_wr_en = chan2_sel && wr_en;
wire [AMP_WIDTH-1:0] chan3_amp;
wire [DATA_WIDTH-1:0] chan3_rd_data;
wire chan3_running;
wire chan3_irq;
wire chan3_sel = chan_sel == 5'd3;
wire chan3_rd_en = chan3_sel && rd_en;
wire chan3_wr_en = chan3_sel && wr_en;
wire running = chan0_running || chan1_running || chan2_running || chan3_running;
assign rd_data = chan0_sel ? chan0_rd_data :
chan1_sel ? chan1_rd_data :
chan2_sel ? chan2_rd_data :
chan3_sel ? chan3_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, chan0_irq);
wavegen chan1(clk, reset, reg_sel,
chan1_rd_data, wr_data,
chan1_rd_en, chan1_wr_en,
chan1_amp,
chan1_running, chan1_irq);
wavegen chan2(clk, reset, reg_sel,
chan2_rd_data, wr_data,
chan2_rd_en, chan2_wr_en,
chan2_amp,
chan2_irq, chan2_running);
wavegen chan3(clk, reset, reg_sel,
chan3_rd_data, wr_data,
chan3_rd_en, chan3_wr_en,
chan3_amp,
chan3_running, chan3_irq);
reg [DAC_WIDTH:0] deltasigma_acc; // one extra bit
wire [DAC_WIDTH:0] amp_sum = chan0_amp + chan1_amp + chan2_amp + chan3_amp; // also one overflow bit here
assign gain_sel = 1; // gain select: 0 -> 12dB, 1 -> 6dB
// assign shutdown_n = running;
assign shutdown_n = 1; /* don't enable shutdown mode, it creates a mains hum */
reg irq_out_buf;
assign irq_out = irq_out_buf;
always @(posedge clk) irq_out_buf <= chan0_irq || chan1_irq || chan2_irq || chan3_irq;
/* delta-sigma DAC */
always @(posedge clk)
begin
if(reset)
deltasigma_acc <= 0;
else
// if (running)
deltasigma_acc <= deltasigma_acc[DAC_WIDTH-1:0] + amp_sum;
// else
// deltasigma_acc <= deltasigma_acc[DAC_WIDTH-1:0] + (4*AMP_BIAS);
end
/* 1-bit audio output */
assign pdm_out = deltasigma_acc[DAC_WIDTH];
endmodule

View file

@ -10,6 +10,7 @@
//`define clock clk_1hz
`define ENABLE_VGAFB
`define ENABLE_MICROSD
`define ENABLE_TDRAUDIO
module top(
input wire clk,
@ -60,6 +61,13 @@ module top(
output wire sd_sck,
input wire sd_cd
`endif
`ifdef ENABLE_TDRAUDIO
,
output wire amp2_ain,
output wire amp2_gain,
output wire amp2_shutdown_n
`endif
);
reg clk_1hz;
@ -220,11 +228,35 @@ module top(
assign uart_tx_data = mem_write_data[7:0];
assign uart_rd_data = { {WIDTH-10{1'b1}}, uart_rx_avail, uart_tx_busy, uart_rx_data };
wire audio_irq;
`ifdef ENABLE_TDRAUDIO
wire [WIDTH-1:0] tdraudio_wr_data;
wire [WIDTH-1:0] tdraudio_rd_data;
wire tdraudio_rd_en, tdraudio_wr_en;
wire tdraudio_irq;
wire tdraudio_cs_en = io_enable && (io_slot == 4);
assign tdraudio_rd_en = tdraudio_cs_en && mem_read_enable;
assign tdraudio_wr_en = tdraudio_cs_en && mem_write_enable;
assign tdraudio_wr_data = mem_write_data;
tdraudio tdraudio0(`clock, ~rst,
mem_addr[6:0],
tdraudio_rd_data,
tdraudio_wr_data,
tdraudio_rd_en,
tdraudio_wr_en,
tdraudio_irq,
amp2_ain, amp2_gain, amp2_shutdown_n);
assign audio_irq = tdraudio_irq;
`endif
// interrupt controller
reg timer_tick;
reg[23:0] tick_count;
wire [1:0] irq_in = { timer_tick, uart_rx_avail };
wire [1:0] irqc_rd_data0;
wire [WIDTH-1:0] irqc_rd_data = { tick_count, 6'b0, irqc_rd_data0 };
wire [2:0] irq_in = { audio_irq, timer_tick, uart_rx_avail };
wire [2:0] irqc_rd_data0;
wire [WIDTH-1:0] irqc_rd_data = { tick_count, 5'b0, irqc_rd_data0 };
wire irqc_seten = mem_write_data[7];
wire irqc_cs = io_enable && (io_slot == 3);
@ -236,7 +268,9 @@ module top(
(io_slot == 2) ? fb_rd_data :
`endif
(io_slot == 3) ? irqc_rd_data:
`ifdef ENABLE_TDRAUDIO
(io_slot == 4) ? tdraudio_rd_data:
`endif
-1;
buart #(.CLKFREQ(`clkfreq)) uart0(`clock, rst,

View file

@ -111,7 +111,7 @@
<Attr Name="UsedIn" Val="implementation"/>
</FileInfo>
</File>
<File Path="$PSRCDIR/stack.v">
<File Path="$PSRCDIR/stack.v" Mode="RelativeOnly">
<FileInfo>
<Attr Name="UsedIn" Val="synthesis"/>
<Attr Name="UsedIn" Val="implementation"/>
@ -142,7 +142,7 @@
</FileInfo>
</File>
<File Path="$PSRCDIR/testbench.v"/>
<File Path="$PPRDIR/rom.mem">
<File Path="$PPRDIR/rom.mem" Mode="RelativeOnly">
<FileInfo>
<Attr Name="UsedIn" Val="synthesis"/>
<Attr Name="UsedIn" Val="simulation"/>
@ -173,14 +173,14 @@
<Attr Name="UsedIn" Val="simulation"/>
</FileInfo>
</File>
<File Path="$PSRCDIR/bram_tdp.v">
<File Path="$PSRCDIR/bram_tdp.v" Mode="RelativeOnly">
<FileInfo>
<Attr Name="UsedIn" Val="synthesis"/>
<Attr Name="UsedIn" Val="implementation"/>
<Attr Name="UsedIn" Val="simulation"/>
</FileInfo>
</File>
<File Path="$PSRCDIR/palette.v">
<File Path="$PSRCDIR/palette.v" Mode="RelativeOnly">
<FileInfo>
<Attr Name="UsedIn" Val="synthesis"/>
<Attr Name="UsedIn" Val="implementation"/>
@ -205,6 +205,13 @@
<Attr Name="UsedIn" Val="simulation"/>
</FileInfo>
</File>
<File Path="$PSRCDIR/tdraudio.v">
<FileInfo>
<Attr Name="UsedIn" Val="synthesis"/>
<Attr Name="UsedIn" Val="implementation"/>
<Attr Name="UsedIn" Val="simulation"/>
</FileInfo>
</File>
<Config>
<Option Name="DesignMode" Val="RTL"/>
<Option Name="TopModule" Val="top"/>
@ -349,17 +356,12 @@
</Simulator>
</Simulators>
<Runs Version="1" Minor="22">
<Run Id="synth_1" Type="Ft3:Synth" SrcSet="sources_1" Part="xc7a35ticsg324-1L" ConstrsSet="constrs_1" Description="Higher performance designs, resource sharing is turned off, the global fanout guide is set to a lower number, FSM extraction forced to one-hot, LUT combining is disabled, equivalent registers are preserved, SRL are inferred with a larger threshold" AutoIncrementalCheckpoint="false" WriteIncrSynthDcp="false" State="current" Dir="$PRUNDIR/synth_1" IncludeInArchive="true" IsChild="false" AutoIncrementalDir="$PSRCDIR/utils_1/imports/synth_1" AutoRQSDir="$PSRCDIR/utils_1/imports/synth_1" ParallelReportGen="true">
<Run Id="synth_1" Type="Ft3:Synth" SrcSet="sources_1" Part="xc7a35ticsg324-1L" ConstrsSet="constrs_1" Description="Vivado Synthesis Defaults" AutoIncrementalCheckpoint="false" WriteIncrSynthDcp="false" State="current" Dir="$PRUNDIR/synth_1" IncludeInArchive="true" IsChild="false" AutoIncrementalDir="$PSRCDIR/utils_1/imports/synth_1" AutoRQSDir="$PSRCDIR/utils_1/imports/synth_1" ParallelReportGen="true">
<Strategy Version="1" Minor="2">
<StratHandle Name="Flow_PerfOptimized_high" Flow="Vivado Synthesis 2024"/>
<Step Id="synth_design">
<Option Id="Directive">7</Option>
<Option Id="FsmExtraction">1</Option>
<Option Id="KeepEquivalentRegisters">1</Option>
<Option Id="NoCombineLuts">1</Option>
<Option Id="ResourceSharing">2</Option>
<Option Id="ShregMinSize">5</Option>
</Step>
<StratHandle Name="Vivado Synthesis Defaults" Flow="Vivado Synthesis 2024">
<Desc>Vivado Synthesis Defaults</Desc>
</StratHandle>
<Step Id="synth_design"/>
</Strategy>
<GeneratedRun Dir="$PRUNDIR" File="gen_run.xml"/>
<ReportStrategy Name="Vivado Synthesis Default Reports" Flow="Vivado Synthesis 2024"/>
@ -376,17 +378,30 @@
<Report Name="ROUTE_DESIGN.REPORT_METHODOLOGY" Enabled="1"/>
<RQSFiles/>
</Run>
<Run Id="impl_1" Type="Ft2:EntireDesign" Part="xc7a35ticsg324-1L" ConstrsSet="constrs_1" Description="Default settings for Implementation." AutoIncrementalCheckpoint="false" WriteIncrSynthDcp="false" State="current" Dir="$PRUNDIR/impl_1" SynthRun="synth_1" IncludeInArchive="true" IsChild="false" GenFullBitstream="true" AutoIncrementalDir="$PSRCDIR/utils_1/imports/impl_1" LaunchOptions="-jobs 6 " AutoRQSDir="$PSRCDIR/utils_1/imports/impl_1" ParallelReportGen="true">
<Run Id="impl_1" Type="Ft2:EntireDesign" Part="xc7a35ticsg324-1L" ConstrsSet="constrs_1" Description="Similar to Performance_ExplorePostRoutePhysOpt, but enables logic optimization step (opt_design) with the ExploreWithRemap directive." AutoIncrementalCheckpoint="false" WriteIncrSynthDcp="false" State="current" Dir="$PRUNDIR/impl_1" SynthRun="synth_1" IncludeInArchive="true" IsChild="false" GenFullBitstream="true" AutoIncrementalDir="$PSRCDIR/utils_1/imports/impl_1" LaunchOptions="-jobs 6 " AutoRQSDir="$PSRCDIR/utils_1/imports/impl_1" ParallelReportGen="true">
<Strategy Version="1" Minor="2">
<StratHandle Name="Vivado Implementation Defaults" Flow="Vivado Implementation 2024"/>
<StratHandle Name="Performance_ExploreWithRemap" Flow="Vivado Implementation 2024">
<Desc>Similar to Performance_ExplorePostRoutePhysOpt, but enables logic optimization step (opt_design) with the ExploreWithRemap directive.</Desc>
</StratHandle>
<Step Id="init_design"/>
<Step Id="opt_design"/>
<Step Id="opt_design">
<Option Id="Directive">6</Option>
</Step>
<Step Id="power_opt_design"/>
<Step Id="place_design"/>
<Step Id="place_design">
<Option Id="Directive">0</Option>
</Step>
<Step Id="post_place_power_opt_design"/>
<Step Id="phys_opt_design"/>
<Step Id="route_design"/>
<Step Id="post_route_phys_opt_design"/>
<Step Id="phys_opt_design">
<Option Id="Directive">0</Option>
</Step>
<Step Id="route_design">
<Option Id="Directive">1</Option>
<Option Id="MoreOptsStr"><![CDATA[-tns_cleanup]]></Option>
</Step>
<Step Id="post_route_phys_opt_design" EnableStepBool="1">
<Option Id="Directive">0</Option>
</Step>
<Step Id="write_bitstream">
<Option Id="BinFile">1</Option>
</Step>