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