A mandelbrot set is a set of complex numbers that satisfy a certain mathematical property. The set is defined by iterating a function on a complex number, and checking if the result of the iteration is bounded. If the result is bounded, the complex number is part of the mandelbrot set. If the result is unbounded, the complex number is not part of the mandelbrot set.
This project calculates the function z = z^2 + c
iteratively, where z
and c
are complex numbers. The function is iterated on
every clock cycle, and the result is checked to see if it is bounded (|z| <= 2
). When the result is unbounded, the unbounded
signal is set high, and the iter
signal is set to the number of iterations it took for the result to become unbounded.
All the calculations are done in 32-bit IEEE 754 floating point format. The floating point addition code is taken from Caravel FPU, and the floating point multiplication code was generated by GPT-4o.
Load the value of the complex number c
that you want to test into the Cr
and Ci
registers. Each register holds a 32-bit IEEE 754 floating point number. The value of c
is Cr + Ci * i
, where i
is the imaginary unit.
The registers are shifted in LSB first, 8 bits at a time. When shifting the last byte, the corresponding load
signal should be set high to load the value into the register.
For example, to load the real part of c
into Cr
, you would need four clock cycles:
data_in
to the least significant byte (Cr[7:0]
)data_in
to the second byte (Cr[15:8]
)data_in
to the third byte (Cr[23:16]
)data_in
to the most significant byte (Cr[31:24]
), and set load_Cr
high to load the value into the register.Do the same for the imaginary part of c
and Ci
. In case you want to load the same value into both Cr
and Ci
, you can set load_Cr
and load_Ci
high at the same time.
Strobe the start
signal to begin the calculation. The design will iterate the function z = z^2 + c
, one iteration per clock cycle. When the result is unbounded, the unbounded
signal will be set high. For numbers that are part of the mandelbrot set, the unbounded
signal will remain low as the design iterates the function.
The values of Cr
and Ci
are buffered, so you can load new values into the registers while the design is calculating the mandelbrot set for the previous values. When you strobe the start
signal, the design will begin calculating the mandelbrot set for the new values of c
.
The following example illustrates how to load the value c = 1.2 + 1.4i
into the registers and start the calculation:
Clock | data_in |
load_Cr |
load_Ci |
start |
unbounded |
---|---|---|---|---|---|
1 | 0x9A | 0 | 0 | 0 | 0 |
2 | 0x99 | 0 | 0 | 0 | 0 |
3 | 0x99 | 0 | 0 | 0 | 0 |
4 | 0x3F | 1 | 0 | 0 | 0 |
5 | 0x33 | 0 | 0 | 0 | 0 |
6 | 0x33 | 0 | 0 | 0 | 0 |
7 | 0xB3 | 0 | 0 | 0 | 0 |
8 | 0x3F | 0 | 1 | 1 | 0 |
9 | 0x00 | 0 | 0 | 0 | 0 |
10 | 0x00 | 0 | 0 | 0 | 1 |
Where:
c
(1.2)c
(1.4)When the calculation concludes, the iter
signal will hold the number of iterations it took for the result to become unbounded. The iter
signal is valid when the unbounded
signal is set high.
None
# | Input | Output | Bidirectional |
---|---|---|---|
0 | start | unbounded | data_in[0] |
1 | load_Cr | iter[0] | data_in[1] |
2 | load_Ci | iter[1] | data_in[2] |
3 | iter[2] | data_in[3] | |
4 | iter[3] | data_in[4] | |
5 | iter[4] | data_in[5] | |
6 | iter[5] | data_in[6] | |
7 | iter[6] | data_in[7] |