Skip to content

Commit

Permalink
Try to count overflows with amaranth simulation
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Münker <mail@chipmuenk.de>
  • Loading branch information
chipmuenk committed Apr 24, 2024
1 parent 2d76306 commit ae4edbf
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 9 deletions.
5 changes: 4 additions & 1 deletion pyfda/fixpoint_widgets/fir_df/fir_df_amaranth.py
Expand Up @@ -125,10 +125,12 @@ def process(self):
and collect filter outputs from `mod.o` in list `self.output`
"""
self.output = []
self.ovfl_acc = []
for i in self.input:
yield self.mod.i.eq(int(i))
yield Tick()
self.output.append((yield self.mod.o))
self.ovfl_acc.append((yield self.mod.ovfl_acc_o))

# ---------------------------------------------------------
def reset(self):
Expand Down Expand Up @@ -201,7 +203,8 @@ def fxfilter(self, x: iterable = None, zi: iterable = None) -> np.ndarray:

# TODO: Pass Quantizer instead of quantizer dict to requant etc. to update overflow counter
# logger.warning(f"y = {self.Q_O.fixp(self.output, in_frmt='qint', out_frmt=fb.fil[0]['qfrmt'])}")

N_ovfl_acc = sum(self.ovfl_acc)
logger.error(f"N_ovfl_acc = {self.ovfl_acc}")
return self.Q_O.fixp(self.output, in_frmt='qint', out_frmt=fb.fil[0]['qfrmt']), self.zi


Expand Down
14 changes: 9 additions & 5 deletions pyfda/fixpoint_widgets/fir_df/fir_df_amaranth_mod.py
Expand Up @@ -120,6 +120,8 @@ def init(self, p, zi: iterable = None) -> None:
self.WO = p['QO']['WI'] + p['QO']['WF'] + 1 # total output word length
self.i = Signal(signed(self.WI)) # input signal
self.o = Signal(signed(self.WO)) # output signal
self.ovfl_acc_o = Signal() # requantization overflow bit accumulator
self.ovfl_out_o = Signal() # requantization overflow bit output

# ---------------------------------------------------------
def reset(self):
Expand Down Expand Up @@ -150,18 +152,20 @@ def elaborate(self, platform) -> Module:
m.d.sync += sreg.eq(src) # with input word length
src = sreg
# TODO: keep old data sreg to allow frame based processing (requiring reset)?
muls[i] = int(b_q)*sreg
muls[i] = int(b_q) * sreg
i += 1

sum_full = Signal(signed(self.W_mul)) # sum of all multiplication products with
m.d.sync += sum_full.eq(reduce(add, muls)) # full product wordlength

# requantize from full partial product wordlength to accumulator format
sum_accu = Signal(signed(self.W_acc))
m.d.comb += sum_accu.eq(requant(m, sum_full, self.Q_mul.q_dict, self.p['QACC']))
m.d.comb += sum_accu.eq(requant(m, sum_full, self.Q_mul.q_dict, self.p['QACC'])[0])
m.d.comb += self.ovfl_acc_o.eq(requant(m, sum_full, self.Q_mul.q_dict, self.p['QACC'])[1])

# requantize from accumulator format to output width
m.d.comb += self.o.eq(requant(m, sum_accu, self.p['QACC'], self.p['QO']))
m.d.comb += self.o.eq(requant(m, sum_accu, self.p['QACC'], self.p['QO'])[0])
m.d.comb += self.ovfl_out_o.eq(requant(m, sum_accu, self.p['QACC'], self.p['QO'])[1])

return m # return result as list of integers

Expand All @@ -181,15 +185,15 @@ def elaborate(self, platform) -> Module:
'QI': {'WI': 2, 'WF': 3, 'ovfl': 'sat', 'quant': 'round'},
'QO': {'WI': 6, 'WF': 3, 'ovfl': 'wrap', 'quant': 'round'}
}

Q_b = fx.Fixed(p['QCB']) # quantizer for transversal coeffs
b_q = quant_coeffs([1, 2, 3, 2, 1], Q_b, out_frmt="qint")
p.update({'ba': b_q})
Q_I = fx.Fixed(p['QI'])
Q_O = fx.Fixed(p['QO'])

dut = FIR_DF_amaranth_mod(p)

def process():
# input = stimulus
output = []
Expand Down
14 changes: 11 additions & 3 deletions pyfda/libs/pyfda_fix_lib_amaranth.py
Expand Up @@ -101,7 +101,6 @@ def requant(mod: Module, sig_i: Signal, QI: dict, QO: dict) -> Signal:
WO_I = QO['WI'] # number of integer bits (output signal)
WO_F = QO['WF'] # number of fractional bits (output signal)
WO = WO_I + WO_F + 1 # total word length (output signal)
N_over = QO['N_over'] # number of overflows

dWF = WI_F - WO_F # difference of fractional lengths
dWI = WI_I - WO_I # difference of integer lengths
Expand All @@ -113,6 +112,8 @@ def requant(mod: Module, sig_i: Signal, QI: dict, QO: dict) -> Signal:
# intermediate signal with requantized fractional part
sig_i_q = Signal(signed(max(WI, WO)))
sig_o = Signal(signed(WO))
# overflow bit, indicating that the requantization caused an overflow
ovfl_o = Signal() # single bit

# logger.debug(f"rescale: dWI={dWI}, dWF={dWF}, Qu:{QO['quant']}, Ov:{QO['ovfl']}")

Expand Down Expand Up @@ -142,27 +143,34 @@ def requant(mod: Module, sig_i: Signal, QI: dict, QO: dict) -> Signal:
# -----------------------------------------------------------------------
if dWI < 0: # WI_I < WO_I, sign extend integer part (prepend copies of sign bit)
mod.d.comb += sig_o.eq(Cat(sig_i_q, Repl(sig_i_q[-1], -dWI)))
mod.d.comb += ovfl_o.eq(0)
elif dWI == 0: # WI = WO, don't change integer part
mod.d.comb += sig_o.eq(sig_i_q)
mod.d.comb += ovfl_o.eq(0)
elif QO['ovfl'] == 'sat':
with mod.If(sig_i_q[-1] == 1):
with mod.If(sig_i_q < MIN_o):
mod.d.comb += sig_o.eq(MIN_o)
mod.d.comb += ovfl_o.eq(1)
with mod.Else():
mod.d.comb += sig_o.eq(sig_i_q)
mod.d.comb += ovfl_o.eq(0)
with mod.Elif(sig_i_q > MAX_o): # sig_i_q[-1] == 0
mod.d.comb += sig_o.eq(MAX_o)
mod.d.comb += ovfl_o.eq(1)
with mod.Else():
mod.d.comb += sig_o.eq(sig_i_q)
mod.d.comb += ovfl_o.eq(0)

else: # wrap around (shift left)
mod.d.comb += sig_o.eq(sig_i_q)
mod.d.comb += ovfl_o.eq(0) # TODO: detect overflow from discarded bits

if QO['ovfl'] != 'wrap':
logger.error(f"Unknown output overflow method <{QO['ovfl']}>,\n"
"\tusing <wrap> instead.")
QO['N_over'] = 15 # TODO: dummy
return sig_o
# QO['N_over'] = 15 # TODO: this passes the value to the quantizer, but it's a dummy
return sig_o, ovfl_o
else:
logger.error('Module "amaranth" not found!')

Expand Down

0 comments on commit ae4edbf

Please sign in to comment.