CORDIC in Verilog: Rotation-Mode Sine/Cosine with Code
CORDIC (COordinate Rotation DIgital Computer) computes trigonometric functions such as sine and cosine using nothing but shifts, adds, and a small precomputed arctangent table — no multipliers and no divider. That makes it a favourite for FPGAs and ASICs that need trig but cannot spare a hardware multiplier. This page gives you clean, synthesizable CORDIC Verilog code for the rotation mode (sine/cosine) and a vectoring-mode variant (magnitude/phase), both self-checking against known-answer vectors.
Every example here is written in strict Verilog-2005 and has been compiled and simulated with Icarus Verilog. The simulation log under each listing is the real captured output, so you can see the circuit hitting the expected answers within a stated tolerance.
Rotation mode: seed the vector with (1/K, 0) and rotate it by the target angle. Because the datapath is seeded with the reciprocal gain, the outputs come out directly as cos and sin in Q2.14 fixed point. This 16-bit, 14-iteration design drives 0°, 45° and 90° and checks both the x/y outputs and the magnitude invariant.
// Rotation-mode CORDIC: sine/cosine generator, Q2.14 fixed point, 14 iterations
module cordic_rot (
input wire signed [15:0] angle, // target angle, radians in Q2.14
output reg signed [15:0] cos_out, // cos(angle) in Q2.14
output reg signed [15:0] sin_out // sin(angle) in Q2.14
);
integer i;
reg signed [15:0] x, y, z;
reg signed [15:0] xn, yn;
// atan(2^-i) in radians, Q2.14
reg signed [15:0] atanlut [0:13];
initial begin
atanlut[0] = 16'sd12868; atanlut[1] = 16'sd7596;
atanlut[2] = 16'sd4014; atanlut[3] = 16'sd2038;
atanlut[4] = 16'sd1023; atanlut[5] = 16'sd512;
atanlut[6] = 16'sd256; atanlut[7] = 16'sd128;
atanlut[8] = 16'sd64; atanlut[9] = 16'sd32;
atanlut[10] = 16'sd16; atanlut[11] = 16'sd8;
atanlut[12] = 16'sd4; atanlut[13] = 16'sd2;
end
always @(*) begin
// seed with 1/K so the gain cancels: 0.607253 * 16384 = 9950
x = 16'sd9950;
y = 16'sd0;
z = angle;
for (i = 0; i < 14; i = i + 1) begin
if (z >= 0) begin
xn = x - (y >>> i);
yn = y + (x >>> i);
z = z - atanlut[i];
end else begin
xn = x + (y >>> i);
yn = y - (x >>> i);
z = z + atanlut[i];
end
x = xn;
y = yn;
end
cos_out = x;
sin_out = y;
end
endmodule
module tb;
reg signed [15:0] angle;
wire signed [15:0] cos_out, sin_out;
integer magsq;
integer errc, errs;
cordic_rot dut (.angle(angle), .cos_out(cos_out), .sin_out(sin_out));
// known-answer references in Q2.14 (computed offline from exact trig):
// 0 deg -> cos=16384, sin=0
// 45 deg (angle 12868) -> cos=sin=11585
// 90 deg (angle 25735) -> cos=0, sin=16384
// tolerance covers angle quantization + finite iterations
task check;
input signed [15:0] ang;
input signed [15:0] rc, rs;
input integer tol;
begin
angle = ang; #1;
errc = cos_out - rc; if (errc < 0) errc = -errc;
errs = sin_out - rs; if (errs < 0) errs = -errs;
if (errc <= tol && errs <= tol)
$display("PASS angle=%0d cos=%0d(ref %0d) sin=%0d(ref %0d)",
ang, cos_out, rc, sin_out, rs);
else
$display("FAIL angle=%0d cos=%0d(ref %0d err %0d) sin=%0d(ref %0d err %0d)",
ang, cos_out, rc, errc, sin_out, rs, errs);
end
endtask
initial begin
check(16'sd0, 16'sd16384, 16'sd0, 40);
check(16'sd12868, 16'sd11585, 16'sd11585, 40);
check(16'sd25735, 16'sd0, 16'sd16384, 40);
// magnitude invariant: |out| ~= |seed| * K = 16384 regardless of angle.
// check cos^2 + sin^2 ~= 16384^2 = 268435456 (avoids $sqrt).
angle = 16'sd12868; #1;
magsq = cos_out*cos_out + sin_out*sin_out;
if (magsq > 266000000 && magsq < 270900000)
$display("PASS magnitude^2=%0d (ref ~268435456)", magsq);
else
$display("FAIL magnitude^2=%0d out of band", magsq);
$display("ALL TESTS PASSED");
$finish;
end
endmodule
PASS angle=0 cos=16387(ref 16384) sin=1(ref 0) PASS angle=12868 cos=11587(ref 11585) sin=11586(ref 11585) PASS angle=25735 cos=1(ref 0) sin=16387(ref 16384) PASS magnitude^2=268493965 (ref ~268435456) ALL TESTS PASSEDThe log shows cos/sin at 0°, 45° and 90° landing within a few LSBs of the offline-computed Q2.14 references (16384 = 1.0), and the magnitude check confirms cos²+sin² stays near 16384² — proof that the shift-add rotation preserves vector length after the 1/K pre-scale, so a real bug in the LUT or the shift direction would print FAIL.
Vectoring mode runs the same hardware with the opposite decision rule: instead of driving the angle accumulator to zero, it drives
yto zero and accumulates the rotation. The result is the vector's phase (atan2) and its magnitude (scaled by the CORDIC gain K). The iteration count is a parameter so you can trade accuracy for area.// Vectoring-mode CORDIC: magnitude + phase of a vector, parameterized iteration count. module cordic_vec #(parameter ITER = 14) ( input wire signed [17:0] x0, // Q3.14 input wire signed [17:0] y0, // Q3.14 output reg signed [17:0] mag, // K * sqrt(x0^2 + y0^2), Q3.14 output reg signed [17:0] phase // atan2(y0, x0), radians Q3.14 ); integer i; reg signed [17:0] x, y, z, xn, yn; reg signed [17:0] atanlut [0:15]; initial begin atanlut[0] = 18'sd12868; atanlut[1] = 18'sd7596; atanlut[2] = 18'sd4014; atanlut[3] = 18'sd2038; atanlut[4] = 18'sd1023; atanlut[5] = 18'sd512; atanlut[6] = 18'sd256; atanlut[7] = 18'sd128; atanlut[8] = 18'sd64; atanlut[9] = 18'sd32; atanlut[10] = 18'sd16; atanlut[11] = 18'sd8; atanlut[12] = 18'sd4; atanlut[13] = 18'sd2; atanlut[14] = 18'sd1; atanlut[15] = 18'sd1; end always @(*) begin x = x0; y = y0; z = 18'sd0; for (i = 0; i < ITER; i = i + 1) begin if (y >= 0) begin // drive y toward 0 xn = x + (y >>> i); yn = y - (x >>> i); z = z + atanlut[i]; end else begin xn = x - (y >>> i); yn = y + (x >>> i); z = z - atanlut[i]; end x = xn; y = yn; end mag = x; // still carries the K gain (~1.6468) phase = z; end endmodule module tb; reg signed [17:0] x0, y0; wire signed [17:0] mag, phase; integer em, ep; cordic_vec #(.ITER(14)) dut (.x0(x0), .y0(y0), .mag(mag), .phase(phase)); // Known-answer vectors (inputs in Q3.14, references computed offline): // (0.5, 0.5) -> phase=45deg=12868, mag=sqrt(0.5)*K=19077 // (0.5, 0.0) -> phase=0, mag=0.5*K=13490 // (0.25,0.4330) -> phase=60deg=17160, mag=0.5*K=13490 task check; input signed [17:0] ix, iy; input signed [17:0] rmag, rph; input integer tol; begin x0 = ix; y0 = iy; #1; em = mag - rmag; if (em < 0) em = -em; ep = phase - rph; if (ep < 0) ep = -ep; if (em <= tol && ep <= tol) $display("PASS x0=%0d y0=%0d mag=%0d(ref %0d) phase=%0d(ref %0d)", ix, iy, mag, rmag, phase, rph); else $display("FAIL x0=%0d y0=%0d mag=%0d(ref %0d err %0d) phase=%0d(ref %0d err %0d)", ix, iy, mag, rmag, em, phase, rph, ep); end endtask initial begin check(18'sd8192, 18'sd8192, 18'sd19077, 18'sd12868, 40); check(18'sd8192, 18'sd0, 18'sd13490, 18'sd0, 40); check(18'sd4096, 18'sd7094, 18'sd13490, 18'sd17160, 40); $display("ALL TESTS PASSED"); $finish; end endmodulePASS x0=8192 y0=8192 mag=19079(ref 19077) phase=12867(ref 12868) PASS x0=8192 y0=0 mag=13494(ref 13490) phase=1(ref 0) PASS x0=4096 y0=7094 mag=13492(ref 13490) phase=17159(ref 17160) ALL TESTS PASSEDThree known-answer vectors — (0.5, 0.5)→45°, (0.5, 0)→0°, and (0.25, 0.433)→60° — return the expected phase in Q3.14 radians and a magnitude equal to the true length times K, all within tolerance. The checks are against values computed by hand offline (no $atan/$sqrt), so the PASS lines really exercise the atan2 and magnitude paths.
How the shift-add rotation works
CORDIC is built on a sequence of micro-rotations. A true rotation of a vector (x, y) by angle φ is
x' = x·cosφ − y·sinφ,y' = y·cosφ + x·sinφ. If we only ever rotate by the special angles φi = arctan(2−i), then tanφi = 2−i and each rotation becomes:x' = x - d·(y >> i) y' = y + d·(x >> i) z' = z - d·atan(2^-i)The multiply by cosφi is dropped on purpose — it is a constant we deal with later as the gain. What remains is a right shift (the
>> i) and an add/subtract. The directiondis +1 or −1, chosen each step by the sign of the residual anglez(rotation mode) or the sign ofy(vectoring mode). After enough steps the chosen quantity has been driven to (almost) zero and the other outputs hold the answer.The arctan table and the gain 1/K
The constants
atan(2-i)are precomputed and stored in a small ROM — in the code above,atanlut. There are only as many entries as iterations, and they are hard constants, so the synthesizer maps them to wires or a tiny LUT, not real memory.Dropping the per-step cosine factor means the vector grows by
1/cosφi = sqrt(1 + 2-2i)each iteration. The product over all iterations converges to the CORDIC gainK ≈ 1.64676. There are two standard ways to handle it. In rotation mode we seed the vector withx = 1/K ≈ 0.60725(value 9950 in Q2.14) so the final gain cancels and cos/sin come out correctly scaled. In vectoring mode the magnitude output simply carries the factor K, which you divide out downstream if you need the true length.Fixed-point angle and coordinate format
The examples use Q2.14 (example 1) and Q3.14 (example 2): the low 14 bits are the fraction, so a value of 16384 represents 1.0. Angles are stored the same way, as radians in Q*.14, which is why 45° = π/4 appears as 12868 and 90° = π/2 as 25735. Keeping angles in radians lets the arctan LUT entries be plain radian constants.
Pick the integer part to cover your worst-case value. In vectoring mode the x register grows toward K× the input magnitude during the iterations, so example 2 uses an 18-bit datapath (Q3.14) with headroom above 1.0 to avoid overflow. If you widen the inputs, widen the datapath and the LUT to match.
Convergence range and corner cases
The sum of all the micro-angles is about ±99.7° (≈ 1.743 rad). Rotation-mode CORDIC therefore converges only for target angles inside roughly ±π/2. For a full 0–2π sine/cosine you first fold the angle into the first quadrant (or the ±π/2 range) with simple sign swaps and a π/2 offset, then run CORDIC on the reduced angle. The 90° case in example 1 sits right at the edge of the native range and still resolves because 25735 is just under the angle sum, but anything beyond that needs pre-reduction.
Vectoring mode converges for any vector with
x0 > 0; for x0 ≤ 0 you rotate the input by ±π first and correct the phase, because the native range is again the right half-plane.Why tolerance-based checking is used
Two independent error sources make an exact bit-match the wrong test. First, the target angle is quantized into Q*.14 before CORDIC ever runs — 45° is stored as 12868, which is not exactly π/4. Second, a finite iteration count leaves a residual angle below the smallest LUT step, and every shift truncates. The result is correct to within a few LSBs, not to the bit. The testbenches therefore compare against offline-computed known-answer constants within a small tolerance (here ±40 LSBs, well under 0.3%), which is still tight enough that a genuine bug — a wrong LUT value, the wrong shift amount, or a flipped add/subtract — blows past it and prints FAIL. Because iverilog's
-g2005mode has no$sin/$cos/$sqrt, the references are computed by hand from exact trig rather than inside the simulator, and the magnitude invariant is checked on the squared length to avoid$sqrtentirely.Synthesis notes
This combinational, unrolled form elaborates into a chain of adders and hardwired shifts — shifts by a constant are free in hardware, just rewiring. It is fine for a quick block but the critical path is long (14 adder stages). For real silicon you normally pipeline it: register x, y, z between iterations so each stage is one add, giving one result per clock after the pipeline fills. Alternatively, fold it into a single iterative stage with a counter and a small state machine to save area at the cost of one result every N cycles. Either way the LUT and the shift structure are unchanged; only the time/area trade-off differs.
FAQ
Does CORDIC need a multiplier? No — that is the whole point. Only adders, a few registers, and hardwired shifts. The arctan table is constants.
How many iterations do I need? Roughly one bit of accuracy per iteration, so 14 iterations give about 14 fractional bits — a good match for a Q*.14 datapath.
Can the same block do both sine/cosine and atan2? Yes. Rotation mode and vectoring mode share the datapath; only the sign-decision input (residual angle vs. y) and the outputs you read differ.
Have questions about this topic?
Share your question in comments or talk to our mentor team for batch guidance.▶ Try it in the ChipXpert LabRun it in the LabOpen this design in the free in-browser simulator — edit, compile and see the waveform/log instantly.
Learn Verilog with mentors, labs and placement support
This free series is maintained by the ChipXpert training team in Hyderabad & Bengaluru. Go deeper with instructor-led courses on real EDA tools.
Explore VLSI Courses Hi! Ask me about courses, fees, batches or discounts. ×ChipXpert AssistantAsk about courses, fees, batches and centres