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 @@ + + + + + + +