tdraudio: add irq_enable flag, add pcmaudio library

runtime: disable interrupts on PTERM
stdlib: check for error state in FileSize
This commit is contained in:
slederer 2025-10-07 00:37:53 +02:00
parent 7cc9ee807d
commit 5c00dfcec9
8 changed files with 390 additions and 21 deletions

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.

60
examples/pcmtest2.pas Normal file
View file

@ -0,0 +1,60 @@
{$H1536}
program pcmtest2;
uses pcmaudio;
var filename:string;
buf:SndBufPtr;
f:file;
size:integer;
i:integer;
c:char;
sampleRate:integer;
err:integer;
done:boolean;
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);
SampleQStart(buf, sampleRate);
write('Press Q to stop> ');
done := false;
while not done do
begin
read(c);
if upcase(c) = 'Q' then
done := true
else
writeln('Queue size: ', SampleQSize);
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;

241
lib/pcmaudio.s Normal file
View file

@ -0,0 +1,241 @@
.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
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 ; Terminate program: clear estack and
; jump to coreloader ; jump to coreloader
PTERM: PTERM:
; just to be safe, disable interrupts
LOADC $980
LOADC 0
STOREI
DROP
LOADCP _CLEARESTACK LOADCP _CLEARESTACK
CALL CALL
LOADCP LOADER_START LOADCP LOADER_START

View file

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

View file

@ -42,12 +42,12 @@ module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22,
assign fifo_rd_en = (div_count == 0) && channel_enable && ~fifo_empty; assign fifo_rd_en = (div_count == 0) && channel_enable && ~fifo_empty;
assign fifo_wr_en = wr_en && (reg_sel == TDRAU_REG_AMP); assign fifo_wr_en = wr_en && (reg_sel == TDRAU_REG_AMP);
reg irq_buf, irq_done; reg irq_buf, irq_enable;
assign irq = irq_buf; assign irq = channel_enable && irq_buf;
reg [DATA_WIDTH-1:0] rd_data_buf; reg [DATA_WIDTH-1:0] rd_data_buf;
assign rd_data = rd_data_buf; assign rd_data = rd_data_buf;
// assign rd_data = {{DATA_WIDTH-8{1'b0}}, {4{1'b0}}, fifo_full, fifo_empty, amp_phase, channel_enable};
assign amp_val = amp_out; assign amp_val = amp_out;
assign running = channel_enable; assign running = channel_enable;
@ -56,7 +56,8 @@ module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22,
/* update read data buffer */ /* update read data buffer */
always @(posedge clk) always @(posedge clk)
begin begin
rd_data_buf <= {{DATA_WIDTH-8{1'b0}}, {4{1'b0}}, fifo_full, fifo_empty, amp_phase, channel_enable}; rd_data_buf <= {{DATA_WIDTH-8{1'b0}},
{3{1'b0}}, irq_enable, fifo_full, fifo_empty, amp_phase, channel_enable};
end end
/* irq signal to interrupt controller */ /* irq signal to interrupt controller */
@ -65,23 +66,23 @@ module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22,
if(reset) if(reset)
irq_buf <= 0; irq_buf <= 0;
else else
if(fifo_empty && ~irq_done) if(fifo_empty && irq_enable)
irq_buf <= 1; irq_buf <= 1;
else else
irq_buf <= 0; irq_buf <= 0;
end end
/* interrupt done flag, used to ensure the irq signal is set for just one clock tick */ /* interrupt enable flag */
always @(posedge clk) always @(posedge clk)
begin begin
if(reset) if(reset)
irq_done <= 0; irq_enable <= 0;
else else
if(rd_en) // reset irq done flag on any register read if(ctl_reg_write)
irq_done <= 0; irq_enable <= wr_data[4];
else else
if(irq_buf) if(irq_buf)
irq_done <= 1; irq_enable <= 0; // disable interrupts after an interrupt
end end
/* channel enable flag */ /* channel enable flag */
@ -139,8 +140,8 @@ module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22,
amp_out <= AMP_BIAS; amp_out <= AMP_BIAS;
// reset phase bit when enabling the channel // reset phase bit when enabling the channel
if (ctl_reg_write && wr_data[0]) if (ctl_reg_write && wr_data[0] && ~channel_enable)
// when channel is enabled, phase will be flipped on next tick // when channel is being enabled, phase will be flipped on next tick
// because div_count will become zero // because div_count will become zero
amp_phase <= 1; amp_phase <= 1;
end end
@ -232,9 +233,6 @@ module tdraudio #(DATA_WIDTH=32) (
chan3_amp, chan3_amp,
chan3_running, chan3_irq); chan3_running, chan3_irq);
reg irq_out_buf;
assign irq_out = irq_out_buf;
reg [DAC_WIDTH:0] deltasigma_acc; // one extra bit 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 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 gain_sel = 1; // gain select: 0 -> 12dB, 1 -> 6dB
@ -242,6 +240,9 @@ module tdraudio #(DATA_WIDTH=32) (
// assign shutdown_n = running; // assign shutdown_n = running;
assign shutdown_n = 1; /* don't enable shutdown mode, it creates a mains hum */ 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; always @(posedge clk) irq_out_buf <= chan0_irq || chan1_irq || chan2_irq || chan3_irq;
/* delta-sigma DAC */ /* delta-sigma DAC */

View file

@ -378,16 +378,18 @@
<Report Name="ROUTE_DESIGN.REPORT_METHODOLOGY" Enabled="1"/> <Report Name="ROUTE_DESIGN.REPORT_METHODOLOGY" Enabled="1"/>
<RQSFiles/> <RQSFiles/>
</Run> </Run>
<Run Id="impl_1" Type="Ft2:EntireDesign" Part="xc7a35ticsg324-1L" ConstrsSet="constrs_1" Description="Includes alternate algorithms for timing-driven optimization" 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"> <Strategy Version="1" Minor="2">
<StratHandle Name="Performance_ExtraTimingOpt" Flow="Vivado Implementation 2024"> <StratHandle Name="Performance_ExploreWithRemap" Flow="Vivado Implementation 2024">
<Desc>Includes alternate algorithms for timing-driven optimization</Desc> <Desc>Similar to Performance_ExplorePostRoutePhysOpt, but enables logic optimization step (opt_design) with the ExploreWithRemap directive.</Desc>
</StratHandle> </StratHandle>
<Step Id="init_design"/> <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="power_opt_design"/>
<Step Id="place_design"> <Step Id="place_design">
<Option Id="Directive">8</Option> <Option Id="Directive">0</Option>
</Step> </Step>
<Step Id="post_place_power_opt_design"/> <Step Id="post_place_power_opt_design"/>
<Step Id="phys_opt_design"> <Step Id="phys_opt_design">
@ -395,8 +397,11 @@
</Step> </Step>
<Step Id="route_design"> <Step Id="route_design">
<Option Id="Directive">1</Option> <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>
<Step Id="post_route_phys_opt_design"/>
<Step Id="write_bitstream"> <Step Id="write_bitstream">
<Option Id="BinFile">1</Option> <Option Id="BinFile">1</Option>
</Step> </Step>