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/benchmarks.results.text b/examples/benchmarks.results.text
index 19824ee..c05b034 100644
--- a/examples/benchmarks.results.text
+++ b/examples/benchmarks.results.text
@@ -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
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..f72e5e6
--- /dev/null
+++ b/examples/pcmtest2.pas
@@ -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.
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..d1add4f
--- /dev/null
+++ b/lib/pcmaudio.s
@@ -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
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/Arty-A7-35-Master.xdc b/tridoracpu/tridoracpu.srcs/Arty-A7-35-Master.xdc
index 7c62767..2a33ae0 100644
--- a/tridoracpu/tridoracpu.srcs/Arty-A7-35-Master.xdc
+++ b/tridoracpu/tridoracpu.srcs/Arty-A7-35-Master.xdc
@@ -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]
diff --git a/tridoracpu/tridoracpu.srcs/dram_bridge.v b/tridoracpu/tridoracpu.srcs/dram_bridge.v
index d4f798b..9bd2a92 100644
--- a/tridoracpu/tridoracpu.srcs/dram_bridge.v
+++ b/tridoracpu/tridoracpu.srcs/dram_bridge.v
@@ -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) |
diff --git a/tridoracpu/tridoracpu.srcs/irqctrl.v b/tridoracpu/tridoracpu.srcs/irqctrl.v
index 1a079bf..b71df60 100644
--- a/tridoracpu/tridoracpu.srcs/irqctrl.v
+++ b/tridoracpu/tridoracpu.srcs/irqctrl.v
@@ -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,
diff --git a/tridoracpu/tridoracpu.srcs/tdraudio.v b/tridoracpu/tridoracpu.srcs/tdraudio.v
new file mode 100644
index 0000000..0cc055e
--- /dev/null
+++ b/tridoracpu/tridoracpu.srcs/tdraudio.v
@@ -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
diff --git a/tridoracpu/tridoracpu.srcs/top.v b/tridoracpu/tridoracpu.srcs/top.v
index 00066fe..6a70ef0 100644
--- a/tridoracpu/tridoracpu.srcs/top.v
+++ b/tridoracpu/tridoracpu.srcs/top.v
@@ -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,
diff --git a/tridoracpu/tridoracpu.xpr b/tridoracpu/tridoracpu.xpr
index 36b5512..3767063 100644
--- a/tridoracpu/tridoracpu.xpr
+++ b/tridoracpu/tridoracpu.xpr
@@ -111,7 +111,7 @@
-
+
@@ -142,7 +142,7 @@
-
+
@@ -173,14 +173,14 @@
-
+
-
+
@@ -205,6 +205,13 @@
+
+
+
+
+
+
+
@@ -349,17 +356,12 @@
-
+
-
-
-
-
-
-
-
-
-
+
+ Vivado Synthesis Defaults
+
+
@@ -376,17 +378,30 @@
-
+
-
+
+ Similar to Performance_ExplorePostRoutePhysOpt, but enables logic optimization step (opt_design) with the ExploreWithRemap directive.
+
-
+
+
+
-
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+