Is it possible to tighten this loop?
Andrey Demenev
Posts: 386
I need to make an arbitrary waveform generator using an external DAC. Frequency value should be read from HUB RAM. Once frequency is set to zero, the generation should be stopped - not immediately, but as close as possible to the moment when waveform crosses the zero. The following code works just fine:
The problem is that sampling frequency is not big enough. I would like to use another cog to double the sampling freq. Obviously, they should be interleaved, and at the moment one cog outputs a sample, another one should output zero, so they do not step each others toes. It may seem that there is some space to do this (see the NOP'ed instructions) - but zeroing OUTA occurs at wrong time. One sampling period takes 32 cycles, and each cog should zero OUTA exactly 16 cycles after it outputs its sample. The problem here is that I need to access HUB RAM twice per sampling period - first to read sample value, second - to read frequency value. Because I read sample directly to OUTA, frequency reading occurs at same moment when zeroing should happen. Reading sample to a register could help - but I am out of cycles.
I could save 4 cycles per sample if the waveform table was located starting from zero address - then I would not need add addr, base, but unfortunately this does not seem possible (well, it IS possible, but in that case I would have to code everything in assembler, and use very non-trivial programming techniques)
Any advise?
gen
:loop rdword OUTA, addr
mov addr, PHSA
nop 'mov OUTA, #0
rdlong freq, PAR wz
shr addr, #19
add addr, base
rdword OUTA, addr
mov addr, PHSA
nop 'mov OUTA, #0
shr addr, #19
add addr, base
if_z jmp #fadeout
nop
rdword OUTA, addr
mov addr, PHSA
nop 'mov OUTA, #0
shr addr, #19
add addr, base
mov FRQA, freq
jmp #:loop
fadeout test middle, OUTA wc
:loop rdword OUTA, addr
test middle, OUTA wz
nop 'mov OUTA, #0
mov addr, PHSA
shr addr, #19
add addr, base
if_c_ne_z jmp #:loop
mov OUTA, middle
:wait rdlong freq, PAR wz
if_z jmp #:wait
jmp #gen
middle long $200
base long 16384
pinmask long $3FF
addr res 1
sample res 1
freq res 1
The problem is that sampling frequency is not big enough. I would like to use another cog to double the sampling freq. Obviously, they should be interleaved, and at the moment one cog outputs a sample, another one should output zero, so they do not step each others toes. It may seem that there is some space to do this (see the NOP'ed instructions) - but zeroing OUTA occurs at wrong time. One sampling period takes 32 cycles, and each cog should zero OUTA exactly 16 cycles after it outputs its sample. The problem here is that I need to access HUB RAM twice per sampling period - first to read sample value, second - to read frequency value. Because I read sample directly to OUTA, frequency reading occurs at same moment when zeroing should happen. Reading sample to a register could help - but I am out of cycles.
I could save 4 cycles per sample if the waveform table was located starting from zero address - then I would not need add addr, base, but unfortunately this does not seem possible (well, it IS possible, but in that case I would have to code everything in assembler, and use very non-trivial programming techniques)
Any advise?

Comments
Your waveform is in a DAT section, the first one in your main source file (so it will be placed at the lowest address). When initializing you just move that block a few longs down making it start at zero
CON _clkmode = XTAL1 + PLL16 _xinfreq = 80_000_000 OBJ { objects here } DAT waveform long 0, 0, 0, 0 DAT ' this DAT identifier can be skipped org ' assembly here PUB startall movelong(@waveform, 0, length_waveform)or something like that
DAT ' setup eins as initial sample address ' ... gen :loop ' eins: active ' zwei: usable (base) rdword temp, eins ' +0 = mov outa, temp ' +8 set add zwei, phsa ' -4 base + offset (rotated) rdlong freq, par wz ' +0 = mov outa, #0 ' +8 clr ror zwei, #19 ' sample address ' eins: dirty ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) add zwei, base ' +0 = offset + base (rotated) ror zwei, #19 ' sample address mov outa, #0 ' +8 clr if_z jmp #fadeout ' exit ' eins: dirty ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) add zwei, base ' +0 = offset + base (rotated) ror zwei, #19 ' sample address mov outa, #0 ' +8 clr mov frqa, freq ' update ' eins: dirty ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) add zwei, base ' +0 = offset + base (rotated) ror zwei, #19 ' sample address mov outa, #0 ' +8 clr mov eins, base ' reset (base) ' eins: usable ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set add eins, phsa ' -4 offset + base (rotated) ror eins, #19 ' +0 = sample address mov zwei, base ' reset (base) mov outa, #0 ' +8 clr ' eins: active ' zwei: usable jmp #:loop fadeout ' ... base long 16384 <- 19 eins long 16384 <- 19 zwei long 16384 <- 19 freq res 1 temp res 1 DATScratch that, I need more coffee. 10 days off the grid is doing that to you. 4/12 sounds fine.
- at higher resolution, DACs are too expensive
- it makes no sense to use higher resolution with 16KiB of samples memory
Also, I want 2 channels - so I think I will go 10 bits, with 12-bit DACs (lower bits always zero, to minimize linearity errors). Otherwise I do not have enough pins - I need rotary encoder, some keys, input for frequency counter, 2 outputs for DC offsets, LCD etc.
If I wanted only sine, triangle and square - it would make more sense to use an integrated DDS circuit. But I want an AWG
DAT ' eins: current phsx value (slot -4) ' zwei: initial sample address ' ... gen :loop ' eins: primed[1] ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set [COLOR="red"]shr eins, #19 ' -4 offset (based on phsx[0])[/COLOR] rdlong freq, par wz ' +0 = mov outa, #0 ' +8 clr [COLOR="red"]add eins, base ' sample address[/COLOR] ' eins: active ' zwei: unused [COLOR="red"]rdword temp, eins ' +0 =[/COLOR] mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) shr zwei, #19 ' +0 = offset add zwei, base ' sample address mov outa, #0 ' +8 clr if_z jmp #fadeout ' exit ' eins: unused ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) shr zwei, #19 ' +0 = offset add zwei, base ' sample address mov outa, #0 ' +8 clr mov frqa, freq ' update ' eins: unused ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) [COLOR="red"]mov eins, zwei ' +0 = phsx[-128][/COLOR] shr zwei, #19 ' offset mov outa, #0 ' +8 clr add zwei, base ' sample address ' eins: primed[0] ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) shr zwei, #19 ' +0 = offset add zwei, base ' sample address mov outa, #0 ' +8 clr [COLOR="red"]shl freq, #7 ' frqx*128[/COLOR] ' eins: primed[0] ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) shr zwei, #19 ' +0 = offset add zwei, base ' sample address mov outa, #0 ' +8 clr [COLOR="red"]add eins, freq ' phsx[-128] + frqx*128 = phsx[0][/COLOR] ' eins: primed[1] ' zwei: active rdword temp, zwei ' +0 = mov outa, temp ' +8 set mov zwei, phsa ' -4 offset (rotated) shr zwei, #19 ' +0 = offset add zwei, base ' sample address mov outa, #0 ' +8 clr ' eins: primed[1] ' zwei: active jmp #:loop fadeout ' ... base long 16384 eins res 1 zwei res 1 freq res 1 temp res 1 DAT