
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] |