diff --git a/doc/irqctrl.md b/doc/irqctrl.md
index 580c123..bf76999 100644
--- a/doc/irqctrl.md
+++ b/doc/irqctrl.md
@@ -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
diff --git a/doc/tdraudio.md b/doc/tdraudio.md
new file mode 100644
index 0000000..999ebfc
--- /dev/null
+++ b/doc/tdraudio.md
@@ -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.
diff --git a/examples/pcmtest.pas b/examples/pcmtest.pas
new file mode 100644
index 0000000..423faaf
--- /dev/null
+++ b/examples/pcmtest.pas
@@ -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.
diff --git a/examples/pcmtest2.pas b/examples/pcmtest2.pas
new file mode 100644
index 0000000..56ecb95
--- /dev/null
+++ b/examples/pcmtest2.pas
@@ -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.
diff --git a/lib/pcmaudio.inc b/lib/pcmaudio.inc
new file mode 100644
index 0000000..4c3cdb3
--- /dev/null
+++ b/lib/pcmaudio.inc
@@ -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;
diff --git a/lib/pcmaudio.s b/lib/pcmaudio.s
new file mode 100644
index 0000000..a030985
--- /dev/null
+++ b/lib/pcmaudio.s
@@ -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
diff --git a/lib/runtime.s b/lib/runtime.s
index ce60b8d..9eb35d7 100644
--- a/lib/runtime.s
+++ b/lib/runtime.s
@@ -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
diff --git a/lib/stdlib.pas b/lib/stdlib.pas
index dd6294c..84025b3 100644
--- a/lib/stdlib.pas
+++ b/lib/stdlib.pas
@@ -1844,6 +1844,8 @@ end;
function filesize(var fil:file):integer;
begin
+ checkerror(fil);
+
if fil.typ = IOChannel then
filesize := -1
else
diff --git a/tridoracpu/tridoracpu.srcs/tdraudio.v b/tridoracpu/tridoracpu.srcs/tdraudio.v
index b9ecc94..0cc055e 100644
--- a/tridoracpu/tridoracpu.srcs/tdraudio.v
+++ b/tridoracpu/tridoracpu.srcs/tdraudio.v
@@ -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_wr_en = wr_en && (reg_sel == TDRAU_REG_AMP);
- reg irq_buf, irq_done;
- assign irq = irq_buf;
+ 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 rd_data = {{DATA_WIDTH-8{1'b0}}, {4{1'b0}}, fifo_full, fifo_empty, amp_phase, channel_enable};
+
assign amp_val = amp_out;
assign running = channel_enable;
@@ -56,7 +56,8 @@ module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22,
/* update read data buffer */
always @(posedge clk)
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
/* irq signal to interrupt controller */
@@ -65,23 +66,23 @@ module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22,
if(reset)
irq_buf <= 0;
else
- if(fifo_empty && ~irq_done)
+ if(fifo_empty && irq_enable)
irq_buf <= 1;
else
irq_buf <= 0;
end
- /* interrupt done flag, used to ensure the irq signal is set for just one clock tick */
+ /* interrupt enable flag */
always @(posedge clk)
begin
if(reset)
- irq_done <= 0;
+ irq_enable <= 0;
else
- if(rd_en) // reset irq done flag on any register read
- irq_done <= 0;
+ if(ctl_reg_write)
+ irq_enable <= wr_data[4];
else
if(irq_buf)
- irq_done <= 1;
+ irq_enable <= 0; // disable interrupts after an interrupt
end
/* channel enable flag */
@@ -139,8 +140,8 @@ module wavegen #(DATA_WIDTH=32, CLOCK_DIV_WIDTH=22,
amp_out <= AMP_BIAS;
// reset phase bit when enabling the channel
- if (ctl_reg_write && wr_data[0])
- // when channel is enabled, phase will be flipped on next tick
+ 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
@@ -232,9 +233,6 @@ module tdraudio #(DATA_WIDTH=32) (
chan3_amp,
chan3_running, chan3_irq);
- reg irq_out_buf;
- assign irq_out = irq_out_buf;
-
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
@@ -242,6 +240,9 @@ module tdraudio #(DATA_WIDTH=32) (
// 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 */
diff --git a/tridoracpu/tridoracpu.xpr b/tridoracpu/tridoracpu.xpr
index 3b60ca9..3767063 100644
--- a/tridoracpu/tridoracpu.xpr
+++ b/tridoracpu/tridoracpu.xpr
@@ -378,16 +378,18 @@
-
+
-
- Includes alternate algorithms for timing-driven optimization
+
+ Similar to Performance_ExplorePostRoutePhysOpt, but enables logic optimization step (opt_design) with the ExploreWithRemap directive.
-
+
+
+
-
+
@@ -395,8 +397,11 @@
+
+
+
+
-