these patches add lots of unittests, documentation and exploration
tools for the cordic core.

-- 
Robert Jordens.
From f2cf74b8c1015f28225eb4100cf6fdaa2f92ab9e Mon Sep 17 00:00:00 2001
From: Robert Jordens <jord...@gmail.com>
Date: Sat, 30 Nov 2013 06:55:01 -0700
Subject: [PATCH 3/5] genlib/cordic: cleanup, documentation, unittests

---
 doc/api.rst                |   7 +
 examples/sim/cordic_err.py | 115 +++++++++++
 migen/genlib/cordic.py     | 493 +++++++++++++++++++++++++++------------------
 migen/test/test_cordic.py  | 163 +++++++++++++++
 4 files changed, 585 insertions(+), 193 deletions(-)
 create mode 100644 examples/sim/cordic_err.py
 create mode 100644 migen/test/test_cordic.py

diff --git a/doc/api.rst b/doc/api.rst
index 1362caa..958702d 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -21,3 +21,10 @@ migen API Documentation
 .. automodule:: migen.genlib.coding
         :members:
         :show-inheritance:
+
+:mod:`genlib.cordic` Module
+------------------------------
+
+.. automodule:: migen.genlib.cordic
+        :members:
+        :show-inheritance:
diff --git a/examples/sim/cordic_err.py b/examples/sim/cordic_err.py
new file mode 100644
index 0000000..4a7bc1b
--- /dev/null
+++ b/examples/sim/cordic_err.py
@@ -0,0 +1,115 @@
+import random
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+from migen.fhdl.std import *
+from migen.fhdl import verilog
+from migen.genlib.cordic import Cordic
+from migen.sim.generic import Simulator
+
+
+class TestBench(Module):
+	def __init__(self, n=None, xmax=.98, i=None, **kwargs):
+		self.submodules.cordic = Cordic(**kwargs)
+		if n is None:
+			n = 1<<flen(self.cordic.xi)
+		self.c = c = 2**(flen(self.cordic.xi) - 1)
+		self.cz = cz = 2**(flen(self.cordic.zi) - 1)
+		if i is None:
+			i = [(int(xmax*c/self.cordic.gain), 0, int(cz*(i/n - .5)))
+					for i in range(n)]
+		self.i = i
+		random.shuffle(self.i)
+		self.ii = iter(self.i)
+		self.o = []
+
+	def do_simulation(self, s):
+		if s.rd(self.cordic.new_in):
+			try:
+				xi, yi, zi = next(self.ii)
+			except StopIteration:
+				s.interrupt = True
+				return
+			s.wr(self.cordic.xi, xi)
+			s.wr(self.cordic.yi, yi)
+			s.wr(self.cordic.zi, zi)
+		if s.rd(self.cordic.new_out):
+			xo = s.rd(self.cordic.xo)
+			yo = s.rd(self.cordic.yo)
+			zo = s.rd(self.cordic.zo)
+			self.o.append((xo, yo, zo))
+
+	def run_io(self): 
+		with Simulator(self) as sim:
+			sim.run()
+		del self.i[-1], self.o[0]
+		if self.i[0] != (0, 0, 0):
+			assert self.o[0] != (0, 0, 0)
+		if self.i[-1] != self.i[-2]:
+			assert self.o[-1] != self.o[-2], self.o[-2:]
+
+def rms_err(width, guard=None, stages=None, n=None):
+	tb = TestBench(width=width, guard=guard, stages=stages,
+			n=n, eval_mode="combinatorial")
+	tb.run_io()
+	c = 2**(flen(tb.cordic.xi) - 1)
+	cz = 2**(flen(tb.cordic.zi) - 1)
+	g = tb.cordic.gain
+	xi, yi, zi = np.array(tb.i).T/c
+	zi *= c/cz*tb.cordic.zmax
+	xo1, yo1, zo1 = np.array(tb.o).T
+	xo = np.floor(c*g*(np.cos(zi)*xi - np.sin(zi)*yi))
+	yo = np.floor(c*g*(np.sin(zi)*xi + np.cos(zi)*yi))
+	dx = xo1 - xo
+	dy = yo1 - yo
+	mm = np.fabs([dx, dy]).max()
+	rms = np.sqrt(dx**2 + dy**2).sum()/len(xo)
+	return rms, mm
+
+def rms_err_map():
+	widths, stages = np.mgrid[8:33:1, 8:37:1]
+	errf = np.vectorize(lambda w, s: _rms_err(int(w), None, int(s), n=333))
+	err = errf(widths, stages)
+	print(err)
+	lev = np.arange(10)
+	fig, ax = plt.subplots()
+	c1 = ax.contour(widths, stages, err[0], lev/10, cmap=plt.cm.Greys_r)
+	c2 = ax.contour(widths, stages, err[1], lev, cmap=plt.cm.Reds_r)
+	ax.plot(widths[:, 0], stages[0, np.argmin(err[0], 1)], "ko")
+	ax.plot(widths[:, 0], stages[0, np.argmin(err[1], 1)], "ro")
+	print(widths[:, 0], stages[0, np.argmin(err[0], 1)],
+			stages[0, np.argmin(err[1], 1)])
+	ax.set_xlabel("width")
+	ax.set_ylabel("stages")
+	ax.grid("on")
+	fig.colorbar(c1)
+	fig.colorbar(c2)
+	fig.savefig("cordic_rms.pdf")
+
+def plot_function(**kwargs):
+	tb = TestBench(eval_mode="combinatorial", **kwargs)
+	tb.run_io()
+	c = 2**(flen(tb.cordic.xi) - 1)
+	cz = 2**(flen(tb.cordic.zi) - 1)
+	g = tb.cordic.gain
+	xi, yi, zi = np.array(tb.i).T
+	xo, yo, zo = np.array(tb.o).T
+	fig, ax = plt.subplots()
+	ax.plot(zi, xo, "r,")
+	ax.plot(zi, yo, "g,")
+	ax.plot(zi, zo, "g,")
+
+
+if __name__ == "__main__":
+	c = Cordic(width=16, guard=None, eval_mode="combinatorial")
+	print(verilog.convert(c, ios={c.xi, c.yi, c.zi, c.xo, c.yo, c.zo,
+		c.new_in, c.new_out}))
+	#print(rms_err(8))
+	#rms_err_map()
+	#plot_function(func_mode="hyperbolic", xmax=.3, width=16, n=333)
+	#plot_function(func_mode="circular", width=16, n=333)
+	#plot_function(func_mode="hyperbolic", cordic_mode="vector",
+    #        xmax=.3, width=16, n=333)
+	#plot_function(func_mode="circular", width=16, n=333)
+	plt.show()
diff --git a/migen/genlib/cordic.py b/migen/genlib/cordic.py
index b52ff78..d508257 100644
--- a/migen/genlib/cordic.py
+++ b/migen/genlib/cordic.py
@@ -1,71 +1,202 @@
-from math import atan, atanh, log, sqrt, pi
+from math import atan, atanh, log, sqrt, pi, ceil
 
 from migen.fhdl.std import *
 
 class TwoQuadrantCordic(Module):
-	"""
+	"""Coordinate rotation digital computer
+
+	Trigonometric, and arithmetic functions implemented using
+	additions/subtractions and shifts.
+
 	http://eprints.soton.ac.uk/267873/1/tcas1_cordic_review.pdf
+
+	http://www.andraka.com/files/crdcsrvy.pdf
+
+	http://zatto.free.fr/manual/Volder_CORDIC.pdf
+
+	The way the CORDIC is executed is controlled by `eval_mode`.
+	If `"iterative"` the stages are iteratively evaluated, one per clock
+	cycle. This mode uses the least amount of registers, but has the
+	lowest throughput and highest latency.  If `"pipelined"` all stages
+	are executed in every clock cycle but separated by registers.  This
+	mode has full throughput but uses many registers and has large
+	latency. If `"combinatorial"`, there are no registers, throughput is
+	maximal and latency is zero. `"pipelined"` and `"combinatorial"` use
+	the same number of sphifters and adders.
+
+	The type of trigonometric/arithmetic function is determined by
+	`cordic_mode` and `func_mode`. :math:`g` is the gain of the CORDIC.
+
+		* rotate-circular: rotate the vector `(xi, yi)` by an angle `zi`.
+		  Used to calculate trigonometric functions, `sin(), cos(),
+		  tan() = sin()/cos()`, or to perform polar-to-cartesian coordinate
+		  transformation:
+
+			.. math::
+				x_o = g \\cos(z_i) x_i - g \\sin(z_i) y_i
+
+				y_o = g \\sin(z_i) x_i + g \\cos(z_i) y_i
+
+		* vector-circular: determine length and angle of the vector
+		  `(xi, yi)`.  Used to calculate `arctan(), sqrt()` or
+		  to perform cartesian-to-polar transformation:
+
+			.. math::
+				x_o = g\\sqrt{x_i^2 + y_i^2}
+
+				z_o = z_i + \\tan^{-1}(y_i/x_i)
+
+		* rotate-hyperbolic: hyperbolic functions of `zi`. Used to
+		  calculate hyprbolic functions, `sinh, cosh, tanh = cosh/sinh,
+		  exp = cosh + sinh`:
+
+			.. math::
+				x_o = g \\cosh(z_i) x_i + g \\sinh(z_i) y_i
+
+				y_o = g \\sinh(z_i) x_i + g \\cosh(z_i) z_i
+
+		* vector-hyperbolic: natural logarithm `ln(), arctanh()`, and
+		  `sqrt()`. Use `x_i = a + b` and `y_i = a - b` to obtain `2*
+		  sqrt(a*b)` and `ln(a/b)/2`:
+
+			.. math::
+				x_o = g\\sqrt{x_i^2 - y_i^2}
+
+				z_o = z_i + \\tanh^{-1}(y_i/x_i)
+
+		* rotate-linear: multiply and accumulate (not a very good
+		  multiplier implementation):
+
+			.. math::
+				y_o = g(y_i + x_i z_i)
+
+		* vector-linear: divide and accumulate:
+
+			.. math::
+				z_o = g(z_i + y_i/x_i)
+
+	Parameters
+	----------
+	width : int
+		Bit width of the input and output signals. Defaults to 16. Input
+		and output signals are signed.
+	widthz : int
+		Bit with of `zi` and `zo`. Defaults to the `width`.
+	stages : int or None
+		Number of CORDIC incremental rotation stages. Defaults to
+		`width + min(1, guard)`.
+	guard : int or None
+		Add guard bits to the intermediate signals. If `None`,
+		defaults to `guard = log2(width)` which guarantees accuracy
+		to `width` bits.
+	eval_mode : str, {"iterative", "pipelined", "combinatorial"}
+	cordic_mode : str, {"rotate", "vector"}
+	func_mode : str, {"circular", "linear", "hyperbolic"}
+		Evaluation and arithmetic mode. See above.
+
+	Attributes
+	----------
+	xi, yi, zi : Signal(width), in
+		Input values, signed.
+	xo, yo, zo : Signal(width), out
+		Output values, signed.
+	new_out : Signal(1), out
+		Asserted if output values are freshly updated in the current
+		cycle.
+	new_in : Signal(1), out
+		Asserted if new input values are being read in the next cycle.
+	zmax : float
+		`zi` and `zo` normalization factor. Floating point `zmax`
+		corresponds to `1<<(widthz - 1)`. `x` and `y` are scaled such
+		that floating point `1` corresponds to `1<<(width - 1)`.
+	gain : float
+		Cumulative, intrinsic gain and scaling factor. In circular mode
+		`sqrt(xi**2 + yi**2)` should be no larger than `2**(width - 1)/gain`
+		to prevent overflow. Additionally, in hyperbolic and linear mode,
+		the operation itself can cause overflow.
+	interval : int
+		Output interval in clock cycles. Inverse throughput.
+	latency : int
+		Input-to-output latency. The result corresponding to the inputs
+		appears at the outputs `latency` cycles later.
+
+	Notes
+	-----
+
+	Each stage `i` in the CORDIC performs the following operation:
+
+	.. math::
+		x_{i+1} = x_i - m d_i y_i r^{-s_{m,i}},
+
+		y_{i+1} = y_i + d_i x_i r^{-s_{m,i}},
+
+		z_{i+1} = z_i - d_i a_{m,i},
+
+	where:
+
+		* :math:`d_i`: clockwise or counterclockwise, determined by
+		  `sign(z_i)` in rotate mode or `sign(-y_i)` in vector mode.
+
+		* :math:`r`: radix of the number system (2)
+
+		* :math:`m`: 1: circular, 0: linear, -1: hyperbolic
+
+		* :math:`s_{m,i}`: non decreasing integer shift sequence
+
+		* :math:`a_{m,i}`: elemetary rotation angle: :math:`a_{m,i} =
+		  \\tan^{-1}(\\sqrt{m} s_{m,i})/\\sqrt{m}`.
 	"""
-	def __init__(self, width=16, stages=None, guard=0,
+	def __init__(self, width=16, widthz=None, stages=None, guard=0,
 			eval_mode="iterative", cordic_mode="rotate",
 			func_mode="circular"):
 		# validate parameters
 		assert eval_mode in ("combinatorial", "pipelined", "iterative")
 		assert cordic_mode in ("rotate", "vector")
-		self.cordic_mode = cordic_mode
 		assert func_mode in ("circular", "linear", "hyperbolic")
+		self.cordic_mode = cordic_mode
 		self.func_mode = func_mode
 		if guard is None:
 			# guard bits to guarantee "width" accuracy
 			guard = int(log(width)/log(2))
+		if widthz is None:
+			widthz = width
 		if stages is None:
-			stages = width + guard
+			stages = width + min(1, guard) # cuts error below LSB
 
-		# calculate the constants
-		if func_mode == "circular":
-			s = range(stages)
-			a = [atan(2**-i) for i in s]
-			g = [sqrt(1 + 2**(-2*i)) for i in s]
-			zmax = pi/2
-		elif func_mode == "linear":
-			s = range(stages)
-			a = [2**-i for i in s]
-			g = [1 for i in s]
-			zmax = 2.
-		elif func_mode == "hyperbolic":
-			s = list(range(1, stages+1))
-			# need to repeat these stages:
-			j = 4
-			while j < stages+1:
-				s.append(j)
-				j = 3*j + 1
-			s.sort()
-			stages = len(s)
-			a = [atanh(2**-i) for i in s]
-			g = [sqrt(1 - 2**(-2*i)) for i in s]
-			zmax = 1.
+		# input output interface
+		self.xi = Signal((width, True))
+		self.yi = Signal((width, True))
+		self.zi = Signal((widthz, True))
+		self.xo = Signal((width, True))
+		self.yo = Signal((width, True))
+		self.zo = Signal((widthz, True))
+		self.new_in = Signal()
+		self.new_out = Signal()
 
-		a = [Signal((width+guard, True), "{}{}".format("a", i),
-			reset=int(round(ai*2**(width + guard - 1)/zmax)))
-			for i, ai in enumerate(a)]
-		self.zmax = zmax #/2**(width - 1)
-		self.gain = 1.
-		for gi in g:
-			self.gain *= gi
+		###
 
-		exec_target, num_reg, self.latency, self.interval = {
-			"combinatorial": (self.comb, stages + 1, 0,          1),
-			"pipelined":     (self.sync, stages + 1, stages,     1),
-			"iterative":     (self.sync, 3,          stages + 1, stages + 1),
-			}[eval_mode]
+		a, s, self.zmax, self.gain = self._constants(stages, widthz + guard)
+		stages = len(a) # may have increased due to repetitions
 
-		# i/o and inter-stage signals
-		self.fresh = Signal()
-		self.xi, self.yi, self.zi, self.xo, self.yo, self.zo = (
-				Signal((width, True), l + io) for io in "io" for l in "xyz")
-		x, y, z = ([Signal((width + guard, True), "{}{}".format(l, i))
-			for i in range(num_reg)] for l in "xyz")
+		if eval_mode == "iterative":
+			num_sig = 3
+			self.interval = stages + 1
+			self.latency = stages + 2
+		else:
+			num_sig = stages + 1
+			self.interval = 1
+			if eval_mode == "pipelined":
+				self.latency = stages
+			else: # combinatorial
+				self.latency = 0
 
+		# inter-stage signals
+		x = [Signal((width + guard, True)) for i in range(num_sig)]
+		y = [Signal((width + guard, True)) for i in range(num_sig)]
+		z = [Signal((widthz + guard, True)) for i in range(num_sig)]
+
+		# hook up inputs and outputs to the first and last inter-stage
+		# signals
 		self.comb += [
 			x[0].eq(self.xi<<guard),
 			y[0].eq(self.yi<<guard),
@@ -75,167 +206,143 @@ class TwoQuadrantCordic(Module):
 			self.zo.eq(z[-1]>>guard),
 			]
 
-		if eval_mode in ("combinatorial", "pipelined"):
-			self.comb += self.fresh.eq(1)
-			for i in range(stages):
-				exec_target += self.stage(x[i], y[i], z[i],
-						x[i + 1], y[i + 1], z[i + 1], i, a[i])
-		elif eval_mode == "iterative":
-			# we afford one additional iteration for register in/out
-			# shifting, trades muxes for registers
+		if eval_mode == "iterative":
+			# We afford one additional iteration for in/out.
 			i = Signal(max=stages + 1)
-			ai = Signal((width+guard, True))
-			self.comb += ai.eq(Array(a)[i])
-			exec_target += [
+			self.comb += [
+					self.new_in.eq(i == stages),
+					self.new_out.eq(i == 1),
+					]
+			ai = Signal((widthz + guard, True))
+			self.sync += ai.eq(Array(a)[i])
+			if range(stages) == s:
+				si = i - 1 # shortcut if no stage repetitions
+			else:
+				si = Signal(max=stages + 1)
+				self.sync += si.eq(Array(s)[i])
+			xi, yi, zi = x[1], y[1], z[1]
+			self.sync += [
+					self._stage(xi, yi, zi, xi, yi, zi, si, ai),
 					i.eq(i + 1),
 					If(i == stages,
 						i.eq(0),
-						self.fresh.eq(1),
-						Cat(x[1], y[1], z[1]).eq(Cat(x[0], y[0], z[0])),
-						Cat(x[2], y[2], z[2]).eq(Cat(x[1], y[1], z[1])),
-					).Else(
-						self.fresh.eq(0),
-						# in-place stages
-						self.stage(x[1], y[1], z[1], x[1], y[1], z[1], i, ai),
+					),
+					If(i == 0,
+						x[2].eq(xi),
+						y[2].eq(yi),
+						z[2].eq(zi),
+						xi.eq(x[0]),
+						yi.eq(y[0]),
+						zi.eq(z[0]),
 					)]
+		else:
+			self.comb += [
+					self.new_out.eq(1),
+					self.new_in.eq(1),
+					]
+			for i, si in enumerate(s):
+				stmt = self._stage(x[i], y[i], z[i],
+						x[i + 1], y[i + 1], z[i + 1], si, a[i])
+				if eval_mode == "pipelined":
+					self.sync += stmt
+				else: # combinatorial
+					self.comb += stmt
+
+	def _constants(self, stages, bits):
+		if self.func_mode == "circular":
+			s = range(stages)
+			a = [atan(2**-i) for i in s]
+			g = [sqrt(1 + 2**(-2*i)) for i in s]
+			#zmax = sum(a)
+			# use pi anyway as the input z can cause overflow
+			# and we need the range for quadrant mapping
+			zmax = pi
+		elif self.func_mode == "linear":
+			s = range(stages)
+			a = [2**-i for i in s]
+			g = [1 for i in s]
+			#zmax = sum(a)
+			# use 2 anyway as this simplifies a and scaling
+			zmax = 2.
+		else: # hyperbolic
+			s = []
+			# need to repeat some stages:
+			j = 4
+			for i in range(stages):
+				if i == j:
+					s.append(j)
+					j = 3*j + 1
+				s.append(i + 1)
+			a = [atanh(2**-i) for i in s]
+			g = [sqrt(1 - 2**(-2*i)) for i in s]
+			zmax = sum(a)*2
+		a = [int(ai*2**(bits - 1)/zmax) for ai in a]
+		# round here helps the width=2**i - 1 case but hurts the
+		# important width=2**i case
+		gain = 1.
+		for gi in g:
+			gain *= gi
+		return a, s, zmax, gain
 
-	def stage(self, xi, yi, zi, xo, yo, zo, i, a):
-		"""
-		x_{i+1} = x_{i} - m*d_i*y_i*r**(-s_{m,i})
-		y_{i+1} = d_i*x_i*r**(-s_{m,i}) + y_i
-		z_{i+1} = z_i - d_i*a_{m,i}
-
-		d_i: clockwise or counterclockwise
-		r: radix of the number system
-		m: 1: circular, 0: linear, -1: hyperbolic
-		s_{m,i}: non decreasing integer shift sequence
-		a_{m,i}: elemetary rotation angle
-		"""
-		dx, dy, dz = xi>>i, yi>>i, a
-		direction = {"rotate": zi < 0, "vector": yi >= 0}[self.cordic_mode]
-		dy = {"circular": dy, "linear": 0, "hyperbolic": -dy}[self.func_mode]
-		ret = If(direction,
-					xo.eq(xi + dy),
-					yo.eq(yi - dx),
+	def _stage(self, xi, yi, zi, xo, yo, zo, i, ai):
+		if self.cordic_mode == "rotate":
+			direction = zi < 0
+		else: # vector
+			direction = yi >= 0
+		dx = yi>>i
+		dy = xi>>i
+		dz = ai
+		if self.func_mode == "linear":
+			dx = 0
+		elif self.func_mode == "hyperbolic":
+			dx = -dx
+		stmt = If(direction,
+					xo.eq(xi + dx),
+					yo.eq(yi - dy),
 					zo.eq(zi + dz),
 				).Else(
-					xo.eq(xi - dy),
-					yo.eq(yi + dx),
+					xo.eq(xi - dx),
+					yo.eq(yi + dy),
 					zo.eq(zi - dz),
 				)
-		return ret
+		return stmt
 
 class Cordic(TwoQuadrantCordic):
+	"""Four-quadrant CORDIC
+
+	Same as :class:`TwoQuadrantCordic` but with support and convergence
+	for `abs(zi) > pi/2 in circular rotate mode or `xi < 0` in circular
+	vector mode.
+	"""
 	def __init__(self, **kwargs):
 		TwoQuadrantCordic.__init__(self, **kwargs)
-		if not (self.func_mode, self.cordic_mode) == ("circular", "rotate"):
+		if self.func_mode != "circular":
 			return # no need to remap quadrants
-		cxi, cyi, czi, cxo, cyo, czo = (self.xi, self.yi, self.zi,
-				self.xo, self.yo, self.zo)
+
 		width = flen(self.xi)
-		for l in "xyz":
-			for d in "io":
-				setattr(self, l+d, Signal((width, True), l+d))
-		qin = Signal()
-		qout = Signal()
-		if self.latency == 0:
-			self.comb += qout.eq(qin)
-		elif self.latency == 1:
-			self.sync += qout.eq(qin)
-		else:
-			sr = Signal(self.latency-1)
-			self.sync += Cat(sr, qout).eq(Cat(qin, sr))
-		pi2 = (1<<(width-2))-1
-		self.zmax *= 2
+		widthz = flen(self.zi)
+		cxi, cyi, czi = self.xi, self.yi, self.zi
+		self.xi = Signal((width, True))
+		self.yi = Signal((width, True))
+		self.zi = Signal((widthz, True))
+
+		###
+
+		pi2 = 1<<(widthz - 2)
+		if self.cordic_mode == "rotate":
+			#rot = self.zi + pi2 < 0
+			rot = self.zi[-1] ^ self.zi[-2]
+		else: # vector
+			rot = self.xi < 0
+			#rot = self.xi[-1]
 		self.comb += [
-				# zi, zo are scaled to cover the range, this also takes
-				# care of mapping the zi quadrants
-				Cat(cxi, cyi, czi).eq(Cat(self.xi, self.yi, self.zi<<1)),
-				Cat(self.xo, self.yo, self.zo).eq(Cat(cxo, cyo, czo>>1)),
-				# shift in the (2,3)-quadrant flag
-				qin.eq((-self.zi < -pi2) | (self.zi+1 < -pi2)),
-				# need to remap xo/yo quadrants (2,3) -> (4,1)
-				If(qout,
-					self.xo.eq(-cxo),
-					self.yo.eq(-cyo),
-				)]
-
-class TB(Module):
-	def __init__(self, n, **kwargs):
-		self.submodules.cordic = Cordic(**kwargs)
-		self.xi = [.9/self.cordic.gain] * n
-		self.yi = [0] * n
-		self.zi = [2*i/n-1 for i in range(n)]
-		self.xo = []
-		self.yo = []
-		self.zo = []
-
-	def do_simulation(self, s):
-		c = 2**(flen(self.cordic.xi)-1)
-		if s.rd(self.cordic.fresh):
-			self.xo.append(s.rd(self.cordic.xo))
-			self.yo.append(s.rd(self.cordic.yo))
-			self.zo.append(s.rd(self.cordic.zo))
-			if not self.xi:
-				s.interrupt = True
-				return
-			for r, v in zip((self.cordic.xi, self.cordic.yi, self.cordic.zi),
-					(self.xi, self.yi, self.zi)):
-				s.wr(r, int(v.pop(0)*c))
-
-def _main():
-	from migen.fhdl import verilog
-	from migen.sim.generic import Simulator, TopLevel
-	from matplotlib import pyplot as plt
-	import numpy as np
-
-	c = Cordic(width=16, eval_mode="iterative",
-		cordic_mode="rotate", func_mode="circular")
-	print(verilog.convert(c, ios={c.xi, c.yi, c.zi, c.xo,
-		c.yo, c.zo}))
-
-	n = 200
-	tb = TB(n, width=8, guard=3, eval_mode="pipelined",
-			cordic_mode="rotate", func_mode="circular")
-	sim = Simulator(tb, TopLevel("cordic.vcd"))
-	sim.run(n*16+20)
-	plt.plot(tb.xo)
-	plt.plot(tb.yo)
-	plt.plot(tb.zo)
-	plt.show()
-
-def _rms_err(width, stages, n):
-	from migen.sim.generic import Simulator
-	import numpy as np
-	import matplotlib.pyplot as plt
-
-	tb = TB(width=int(width), stages=int(stages), n=n,
-			eval_mode="combinatorial")
-	sim = Simulator(tb)
-	sim.run(n+100)
-	z = tb.cordic.zmax*(np.arange(n)/n*2-1)
-	x = np.cos(z)*.9
-	y = np.sin(z)*.9
-	dx = tb.xo[1:]-x*2**(width-1)
-	dy = tb.yo[1:]-y*2**(width-1)
-	return ((dx**2+dy**2)**.5).sum()/n
-
-def _test_err():
-	from matplotlib import pyplot as plt
-	import numpy as np
-
-	widths, stages = np.mgrid[4:33:1, 4:33:1]
-	err = np.vectorize(lambda w, s: rms_err(w, s, 173))(widths, stages)
-	err = -np.log2(err)/widths
-	print(err)
-	plt.contour(widths, stages, err, 50, cmap=plt.cm.Greys)
-	plt.plot(widths[:, 0], stages[0, np.argmax(err, 1)], "bx-")
-	print(widths[:, 0], stages[0, np.argmax(err, 1)])
-	plt.colorbar()
-	plt.grid("on")
-	plt.show()
-
-if __name__ == "__main__":
-	_main()
-	#_rms_err(16, 16, 345)
-	#_test_err()
+				cxi.eq(self.xi),
+				cyi.eq(self.yi),
+				czi.eq(self.zi),
+				If(rot,
+					cxi.eq(-self.xi),
+					cyi.eq(-self.yi),
+					czi.eq(self.zi + 2*pi2),
+					#czi.eq(self.zi ^ (2*pi2)),
+				),
+				]
diff --git a/migen/test/test_cordic.py b/migen/test/test_cordic.py
new file mode 100644
index 0000000..417bdb2
--- /dev/null
+++ b/migen/test/test_cordic.py
@@ -0,0 +1,163 @@
+import unittest
+from random import randrange, random
+from math import *
+
+from migen.fhdl.std import *
+from migen.genlib.cordic import *
+
+from migen.test.support import SimCase, SimBench
+
+
+class CordicCase(SimCase, unittest.TestCase):
+	class TestBench(SimBench):
+		def __init__(self, **kwargs):
+			k = dict(width=8, guard=None, stages=None,
+				eval_mode="combinatorial", cordic_mode="rotate",
+				func_mode="circular")
+			k.update(kwargs)
+			self.submodules.dut = Cordic(**k)
+
+	def _run_io(self, n, gen, proc, delta=1, deltaz=1):
+		c = 2**(flen(self.tb.dut.xi) - 1)
+		g = self.tb.dut.gain
+		zm = self.tb.dut.zmax
+		pipe = {}
+		genn = [gen() for i in range(n)]
+		def cb(tb, s):
+			if s.rd(tb.dut.new_in):
+				if genn:
+					xi, yi, zi = genn.pop(0)
+				else:
+					s.interrupt = True
+					return
+				xi = floor(xi*c/g)
+				yi = floor(yi*c/g)
+				zi = floor(zi*c/zm)
+				s.wr(tb.dut.xi, xi)
+				s.wr(tb.dut.yi, yi)
+				s.wr(tb.dut.zi, zi)
+				pipe[s.cycle_counter] = xi, yi, zi
+			if s.rd(tb.dut.new_out):
+				t = s.cycle_counter - tb.dut.latency - 1
+				if t < 1:
+					return
+				xi, yi, zi = pipe.pop(t)
+				xo, yo, zo = proc(xi/c, yi/c, zi/c*zm)
+				xo = floor(xo*c*g)
+				yo = floor(yo*c*g)
+				zo = floor(zo*c/zm)
+				xo1 = s.rd(tb.dut.xo)
+				yo1 = s.rd(tb.dut.yo)
+				zo1 = s.rd(tb.dut.zo)
+				print((xi, yi, zi), (xo, yo, zo), (xo1, yo1, zo1))
+				self.assertAlmostEqual(xo, xo1, delta=delta)
+				self.assertAlmostEqual(yo, yo1, delta=delta)
+				self.assertAlmostEqual(abs(zo - zo1) % (2*c), 0, delta=deltaz)
+		self.run_with(cb)
+
+	def test_rot_circ(self):
+		def gen():
+			ti = 2*pi*random()
+			r = random()*.98
+			return r*cos(ti), r*sin(ti), (2*random() - 1)*pi
+		def proc(xi, yi, zi):
+			xo = cos(zi)*xi - sin(zi)*yi
+			yo = sin(zi)*xi + cos(zi)*yi
+			return xo, yo, 0
+		self._run_io(50, gen, proc, delta=2)
+
+	def test_rot_circ_16(self):
+		self.setUp(width=16)
+		self.test_rot_circ()
+
+	def test_rot_circ_pipe(self):
+		self.setUp(eval_mode="pipelined")
+		self.test_rot_circ()
+
+	def test_rot_circ_iter(self):
+		self.setUp(eval_mode="iterative")
+		self.test_rot_circ()
+
+	def _test_vec_circ(self):
+		def gen():
+			ti = pi*(2*random() - 1)
+			r = .98 #*random()
+			return r*cos(ti), r*sin(ti), 0 #pi*(2*random() - 1)
+		def proc(xi, yi, zi):
+			return sqrt(xi**2 + yi**2), 0, zi + atan2(yi, xi)
+		self._run_io(50, gen, proc)
+
+	def test_vec_circ(self):
+		self.setUp(cordic_mode="vector")
+		self._test_vec_circ()
+
+	def test_vec_circ_16(self):
+		self.setUp(width=16, cordic_mode="vector")
+		self._test_vec_circ()
+
+	def _test_rot_hyp(self):
+		def gen():
+			return .6, 0, 2.1*(random() - .5)
+		def proc(xi, yi, zi):
+			xo = cosh(zi)*xi - sinh(zi)*yi
+			yo = sinh(zi)*xi + cosh(zi)*yi
+			return xo, yo, 0
+		self._run_io(50, gen, proc, delta=2)
+
+	def test_rot_hyp(self):
+		self.setUp(func_mode="hyperbolic")
+		self._test_rot_hyp()
+
+	def test_rot_hyp_16(self):
+		self.setUp(func_mode="hyperbolic", width=16)
+		self._test_rot_hyp()
+
+	def test_rot_hyp_iter(self):
+		self.setUp(cordic_mode="rotate", func_mode="hyperbolic",
+				eval_mode="iterative")
+		self._test_rot_hyp()
+
+	def _test_vec_hyp(self):
+		def gen():
+			xi = random()*.6 + .2
+			yi = random()*xi*.8
+			return xi, yi, 0
+		def proc(xi, yi, zi):
+			return sqrt(xi**2 - yi**2), 0, atanh(yi/xi)
+		self._run_io(50, gen, proc)
+
+	def test_vec_hyp(self):
+		self.setUp(cordic_mode="vector", func_mode="hyperbolic")
+		self._test_vec_hyp()
+
+	def _test_rot_lin(self):
+		def gen():
+			xi = 2*random() - 1
+			if abs(xi) < .01:
+				xi = .01
+			yi = (2*random() - 1)*.5
+			zi = (2*random() - 1)*.5
+			return xi, yi, zi
+		def proc(xi, yi, zi):
+			return xi, yi + xi*zi, 0
+		self._run_io(50, gen, proc)
+
+	def test_rot_lin(self):
+		self.setUp(func_mode="linear")
+		self._test_rot_lin()
+
+	def _test_vec_lin(self):
+		def gen():
+			yi = random()*.95 + .05
+			if random() > 0:
+				yi *= -1
+			xi = abs(yi) + random()*(1 - abs(yi))
+			zi = 2*random() - 1
+			return xi, yi, zi
+		def proc(xi, yi, zi):
+			return xi, 0, zi + yi/xi
+		self._run_io(50, gen, proc, deltaz=2, delta=2)
+
+	def test_vec_lin(self):
+		self.setUp(func_mode="linear", cordic_mode="vector", width=8)
+		self._test_vec_lin()
-- 
1.8.3.2

From 670809d3f0053310fb0d192e5f5ce57b1ecd22ab Mon Sep 17 00:00:00 2001
From: Robert Jordens <jord...@gmail.com>
Date: Sun, 1 Dec 2013 19:30:49 -0700
Subject: [PATCH 4/5] examples/cordic: scripted exploration of parameters space

---
 examples/cordic/cordic_impl.py      | 61 +++++++++++++++++++++++++++++++
 examples/cordic/cordic_impl_eval.py | 71 +++++++++++++++++++++++++++++++++++++
 2 files changed, 132 insertions(+)
 create mode 100644 examples/cordic/cordic_impl.py
 create mode 100644 examples/cordic/cordic_impl_eval.py

diff --git a/examples/cordic/cordic_impl.py b/examples/cordic/cordic_impl.py
new file mode 100644
index 0000000..d7ac084
--- /dev/null
+++ b/examples/cordic/cordic_impl.py
@@ -0,0 +1,61 @@
+import copy
+import json
+
+from migen.fhdl.std import *
+from migen.genlib.cordic import Cordic
+from mibuild.generic_platform import *
+from mibuild.xilinx_ise import XilinxISEPlatform, CRG_SE
+
+
+class CordicImpl(Module):
+	def __init__(self, name, **kwargs):
+		self.name = name
+		json.dump(kwargs, open("build/{}.json".format(name), "w"))
+		self.platform = platform = Platform()
+		self.submodules.cordic = Cordic(**kwargs)
+		width = flen(self.cordic.xi)
+		self.comb += self.cordic.xi.eq(
+				int((1<<width - 1)/self.cordic.gain*.98))
+		self.comb += self.cordic.yi.eq(0)
+		zi = self.cordic.zi
+		self.sync += zi.eq(zi + 1)
+		do = platform.request("do")
+		self.sync += do.eq(Cat(self.cordic.xo, self.cordic.yo))
+
+	def build(self):
+		self.platform.build(self, build_name=self.name)
+
+class Platform(XilinxISEPlatform):
+	_io = [
+		("clk", 0, Pins("AB13")),
+		("rst", 0, Pins("V5")),
+		("do", 0,
+			Pins("Y2 W3 W1 P8 P7 P6 P5 T4 T3",
+				"U4 V3 N6 N7 M7 M8 R4 P4 M6 L6 P3 N4",
+				"M5 V2 V1 U3 U1 T2 T1 R3 R1 P2 P1"),
+		),
+	]
+	def __init__(self):
+		XilinxISEPlatform.__init__(self, "xc6slx45-fgg484-2", self._io,
+			lambda p: CRG_SE(p, "clk", "rst", 10.))
+
+if __name__ == "__main__":
+	default = dict(width=16, guard=0, eval_mode="pipelined",
+			func_mode="circular", cordic_mode="rotate")
+	variations = dict(
+			eval_mode=["combinatorial", "pipelined", "iterative"],
+			width=[4, 8, 12, 14, 16, 20, 24, 32],
+			stages=[10, 12, 14, 16, 20, 24, 32],
+			guard=[0, 1, 2, 3, 4],
+			)
+	CordicImpl("cordic_test", eval_mode="combinatorial").build()
+
+	name = "cordic_baseline"
+	CordicImpl(name, **default).build()
+
+	for k, v in sorted(variations.items()):
+		for vi in v:
+			name = "cordic_{}_{}".format(k, vi)
+			kw = copy.copy(default)
+			kw[k] = vi
+			CordicImpl(name, **kw).build()
diff --git a/examples/cordic/cordic_impl_eval.py b/examples/cordic/cordic_impl_eval.py
new file mode 100644
index 0000000..eeefffb
--- /dev/null
+++ b/examples/cordic/cordic_impl_eval.py
@@ -0,0 +1,71 @@
+import glob, os, re, json
+
+import numpy as np
+import matplotlib.pyplot as plt
+import pandas
+
+
+def extract(b, n, r, c=int):
+	r = re.compile(r)
+	try:
+		f = open(b + n)
+	except:
+		return
+	for l in f:
+		m = r.search(l)
+		if m:
+			v = m.groups()[0]
+			v = v.replace(",", "")
+			return c(v)
+
+def load(prefix, base):
+	kw = json.load(open(base))
+	b = os.path.splitext(base)[0]
+	_, n = os.path.split(b)[1].split("_", 1)
+	try:
+		n, _ = n.rsplit("_", 1)
+		kw["vary"] = n
+	except:
+		pass
+	kw["slack"] = extract(b, ".par",
+			"GRPclk.*SETUP +\\| +([\d,]+\\.\d+)", float)
+	kw["freq"] = extract(b, ".srp",
+			"Maximum Frequency: +([\d,]+\\.\d+) *MHz", float)
+	kw["reg"] = extract(b, "_map.mrp",
+			"Number of Slice Registers: +([\d,]+) ")
+	kw["lut"] = extract(b, "_map.mrp",
+			"Number of Slice LUTs: +([\d,]+) ")
+	kw["slice"] = extract(b, "_map.mrp",
+			"Number of occupied Slices: +([\d,]+) ")
+	return kw
+
+def run(prefix):
+	dat = {}
+	for base in glob.glob("build/{}_*.json".format(prefix)):
+		kw = load(prefix, base)
+		if "vary" in kw:
+			dat[base] = kw
+	df = pandas.DataFrame.from_dict(dat, orient="index")
+	comp = "freq slice slack".split()
+	dfg = df.groupby("vary")
+	fig, ax = plt.subplots(len(dfg), len(comp))
+	for axj, (v, dfi) in zip(ax, dfg):
+		print(v, dfi)
+		if v not in dfi:
+			continue
+		dfi = dfi.sort(v)
+		for axi, n in zip(axj, comp):
+			x = dfi[v]
+			if type(x[0]) is type(""):
+				xi = range(len(x))
+				axi.set_xticks(xi)
+				axi.set_xticklabels(x)
+				x = xi
+			axi.plot(x, dfi[n])
+			axi.set_xlabel(v)
+			axi.set_ylabel(n)
+	fig.savefig("cordic_impl.pdf")
+	plt.show()
+
+if __name__ == "__main__":
+	run("cordic")
-- 
1.8.3.2

_______________________________________________
Devel mailing list
Devel@lists.milkymist.org
https://ssl.serverraum.org/lists/listinfo/devel

Reply via email to