Jump to content

How to use and manage memory with my FPGA?


GalD101

Recommended Posts

12 hours ago, reddish said:

(2) Run this design (make sure that the names of the top-level ports correspond to your XDC file) and observe on the scope the marvels of a 10 MHz, 30% duty-cycle signal on a scope.

Unfortunately, as of now I don't really have access to the oscilloscopeūüôĀ

Link to comment
Share on other sites

14 hours ago, reddish said:

(1) Compare what I did to what you're doing. Find the differences and commonalities.

TBH, I don't really understand the clocksynth.v that you wrote or the one I generated (module clk_wiz_0_clk_wiz). I get some of it by reading and it makes sense but there seem to be a lot of instances of modules I'm unfamiliar with and parameters that I don't understand what is their purpose. I just see it as a black box that generates a 100MHz clock and I use it as a layer of abstraction.
As for the toplevel.v and the my_first_fsm.v, well they seem a lot more user friendly.
The toplevel design is, as the name suggest a lot more top level (higher level of abstraction).
you declared a module that receives the original 12 MHz clock the FPGA provides, and an output that will be one of the pins on the FPGA.
you created a "variable" wire called clock that will be our desired 100 MHz clock. you made it a wire(which is a net) and not a reg since this is not memory (reg is used to store things and wire/nets, like a real wire is in charge of making sure that whatever is connected to it (assigned to it) has the same value that we assign it (so you can think about it like you physically connected a wire to the clk.
Then you wanted your clk variable to act as a 100MHz clock, so you used your prebuilt module clocksynth to create an instance with the output of this module(CLK_OUT_100MHz) receiving the clk variable you declared (in practice I will also have to set the clk variable to match with a physical pin on the XDC file) (question: why do these instances need names? maybe to use them as input parameters to another module DELETE THIS AND GOOGLE INSTEAD - answer according to google: we can have multiple instance of same module in the same program).
Then you instantiated your finite state machine that receives the 100MHz clock and an output pin. This is all, as the name suggests toplevel and there isn't really a lot of logic displayed here (but that's the beauty of it, isn't it?) also it's very reminiscent of OOP in a way.
As for the my_first_fsm module, well here the logic hides.
you created a module that receives a clk (should be a 100MHz clock in our case) as input and an output which is the finite state machine output.
you now created 2 registers "variables" and you made them a register since you want them to act like memory, you want to be able to store information on these variables.
you created a "vector" of size 4 bits that uses address 0 through 3 inclusive and assigned it to 0 (I think you should write it like this if you want to be more explicit:

reg [3:0] r_counter = 4'b0000;
reg [3:0] r_counter = 4'b0;

I think the latter is a bit cleaner. And I think the leftmost number (before the ' symbol) represents the number of bits (we need 4 in our case since were using 0 through 3).
Then, after the ' symbol, you specify which base you want to use (nothing or d for decimal, b for binary and so on)
in short: <size>'<radix><value>
). You gave it a size of 4 bits since you want to be able to store integers from 0 to 9 and while 3 bits won't suffice (2^3 -1 = 7<=9). 4 on the other hand will (2^4 -1 = 15>=9).
Then you created another register r_fsm_out that will later be used as the output and its value will depend on r_counter. you gave it a value of 1 (since in the sequence you want to generate 1 1 1 0 0 0 0 0 0 0 there is indeed a 1 on the first bit) also I think that also here you can use a similar syntax as I explained above (1'b1).
Now you created 2 wires that will depend on the value of the aforementioned registers. They are wires since you don't want them to be used as memory. You already have the registers for that, now what you did with the logic in the assignment is, sort of like connecting a wire to the registers that does logic to what it receives from the  register and implements it, so the values of next_counter and next_fsm_out will always be in accordance to the values stored in the registers. It's like physically connecting a wire. This is how this HDL thing differs from regular programming. It's more of a design of things that happens in parallel more than sequential, row by row as you can see in high level languages such as C or python.
In the next part, you created an always block (and this part does resemble programming languages a bit more).
You simply made sure that on every positive edge of your 100MHz clock (which will be 10ns?) you will update the value of the registers. You used the non blocking assignment (<=) since you want those two assignments to happen simultaneously because if you were to use blocking assignments (=) so much like in a normal programming language, you will make the assignments sequentially, meaning that r_counter will be updated first and only when that operation is finished then you will alter the value of r_fsm_out. This would create some pretty unexpected output, since we can easily see that r_fsm_out is dependent on next_fsm_out, which depends on next_counter which depends on r_counter so that means that, by transitivity, r_fsm_out depends on r_counter so if we will execute these commands in sequence rather than parallel, we will encounter some unexpected results and out program won't work as we expect it to. That is why we have to use non blocking assignments. We want the assignments to be parallel and not sequential.
 

Then in the end you assign your output value (FSM_OUT) to be whatever is stored in r_fsm_out.
a small question, can't you just make your output (FSM_OUT) a reg and then you wont have to use another auxiliary variable(r_fsm_out)? Also, I am pretty sure that Verilog is weakly typed so you don't have to specify types, but shouldn't you specify your input CLK as a wire and your output (FSM_OUT) as a reg? As I said above, I think you can also use less variables if you will do that but perhaps there is a reason for this, also maybe it's cleaner to use another variable, especially in more complex designs.

Edited by GalD101
Oops forgot to delete that, I'll strikethrough instead
Link to comment
Share on other sites

14 hours ago, reddish said:

Given that you are using a 100 MHz clock, alter the 'my_first_fsm' code so that it produces a signal that toggles at a rate of approximately 115200 times per second, and show the resulting image on a scope.

Does that involves only changing the sequence of bits I generate? How did you arrive at this number? I think I get how the program works I just don't get what the sequence of ones and zeros essentially means. Does it mean that my output will generate a current for 30 ns and then no current for 70ns and repeating like so? 

Link to comment
Share on other sites

I wanted to avoid a few alternative approaches, steering towards where we are now.

The joint 10 MHz, 30% requirement forces the generation of a different clock (so an MMCM is required), and prevents the use of the "duty cycle" feature of the MMCM.

The design we arrived at accomplishes a few important milestone learning goals: (1) introduction to clock synthesis; (2) introduction to finite state machines, which are the bread and butter of HDL design; and (3) introducing the mindset of thinking in terms of combinatorial logic the sole purpose of which is to prepare the values to be loaded in the registers at the next rising edge of the clock.

Number (3) was a watershed moment in my own development as a VHDL programmer. Before that, it was chaos -- too many language constructs, and too many ways to approach a given design challenge. After that: relative tranquility and a straightforward way to think about design. Best of all, it is mostly what is called a zero-cost abstraction; the designs following from this mindset are usually at least as efficient as what comes out of more ad-hoc approaches.


What will come next is that we will develop a bunch of state machines that will help to transfer data from the FPGA to the PC via the serial-over-USB link.

Substeps:

(i) Continuously transfer an ASCII 'U' character from the FPGA to the PC, which is achieved by making a block wave that feeds into the CMOD's FTDI chip.
(ii) Alter this to send an 'A' character instead.
(iii) Alter this to send any character offered by a 'parent' module.
(iv) Write a state machine that writes "hello world!" from the FPGA to the PC.

Steps (i) and (ii) will teach how (basic) serial communication works, which is super useful background knowledge.
Steps (iii) and (iv) will force us to think about communication between modules (each of which is a finite state machine). I'll propose a generic approach there that is efficient and has served me well in the past.

After that, the big goals:

* Write a hexadecimal counter via serial communications to the PC.
* Introduce a FIFO memory as a buffer between the "data generator" and the serial communication output. This, I think was your original question.
* Implement an event time tagger. (I secretly hope that this will suffice for your lab application).
* Implement an FPGA-side coincidence detector.
* (If needed, very much optional and quite advanced): improve the time resolution of the design from ~ 10 ns down to 1 or 1.25 ns.

And with all that, we're essentially looking at a first HDL-design course curriculum :-)
 

Link to comment
Share on other sites

32 minutes ago, GalD101 said:

Does that involves only changing the sequence of bits I generate? How did you arrive at this number? I think I get how the program works I just don't get what the sequence of ones and zeros essentially means. Does it mean that my output will generate a current for 30 ns and then no current for 70ns and repeating like so? 

It only involves the sequence of bits generated on FSM_OUT.

I want to see a train of 1's the duration of which is approximately (1/115200.0) seconds, followed by a train of 0's the duration of which is approximately (1/115200.0) seconds.
Hint: 1/115200.0 seconds is approximately equal to 868 times 10 ns (verify this, then think about the implications given that we have a 100 MHz clock available to us).

> How did you arrive at this number?

We're working towards serial communication now. 115200 baud is a standard baudrate (speed) used for serial communication.

> Does it mean that my output will generate a current for 30 ns and then no current for 70ns and repeating like so? 

Please don't confuse voltage and current - more so because you're a physics student :) FPGA signals should be thought of in terms of voltage, not current.

I want to see a signal on the PIO pin (and on your scope) that shows a signal that regularly alternates between 0 and 1 at a rate of 115200 times per second, give or take 1%. Given the code we had working before, this really isn't that difficult. Don't overthink it.


 

Link to comment
Share on other sites

37 minutes ago, GalD101 said:

I have no clue how to. Do I need to change the series of bits so that will "blink" (switch from 0 to 1 or vice versa) at that frequency? (115200Hz)

Close. But a 115200 Hz signal would actually change 230400 times per second (counting both 0->1 and 1->0 transitions).

This is the difference between the unit Hz (periods-per-second) compared to baud (signal-changes-per-second). there's a factor 2 between them.

Link to comment
Share on other sites

5 minutes ago, reddish said:

Hint: 1/115200.0 seconds is approximately equal to 868 times 10 ns (verify this, then think about the implications given that we have a 100 MHz clock available to us)

so I need to change the code to look like this?:
 

  wire [3:0] next_counter = (r_counter + 1) % (868*2 + 1);
  wire       next_fsm_out = (next_counter < 868) ? 1 : 0;

 

Link to comment
Share on other sites

Just now, GalD101 said:

so I need to change the code to look like this?:
 

  wire [3:0] next_counter = (r_counter + 1) % (868*2 + 1);
  wire       next_fsm_out = (next_counter < 868) ? 1 : 0;

 

You should try and look on the scope what you see.

if you get stuck, ask for help.

Link to comment
Share on other sites

1 minute ago, GalD101 said:

I hope I can get scope now. I'll try to ask for one. Thanks

Doing FPGA programming without a proper scope is really challenging, especially if you're new to all this. No need to play on hard mode, it's hard enough already.

 

Link to comment
Share on other sites

@reddish what i wrote above (the long post) is it correct? Please correct me if i said something that isn't true
can you please comment on this as well?:

1 hour ago, GalD101 said:

a small question, can't you just make your output (FSM_OUT) a reg and then you wont have to use another auxiliary variable(r_fsm_out)?

EDIT: I think it's not possible to directly change the output but it is possible to use another variable that will be assigned to it like you did

Edited by GalD101
Link to comment
Share on other sites

1 hour ago, GalD101 said:

@reddish what i wrote above (the long post) is it correct? Please correct me if i said something that isn't true

Good that you mention it, I missed it, due to the many messages :)

I appreciate the writeup, shows you're doing your side to really study this stuff and think about it.

Some responses to what you write:

> TBH, I don't really understand the clocksynth.v

Yep, no problem. Precisely understanding what all the values do there requires quite a bit of low-level understanding about the MMCMs that you don't have. You may have noticed I used an MMCME2_BASE instead of the MMCME2_ADV that the Clock Wizard made for you; it's a bit more concise and leaves out a few rarely-used features.

One nice feature in Vivado is that you can add language constructs and predefined component instantiation (like the MMCM) via the Tools / Language Templates menu; what I did was select the Verilog / Device primitive Instantiation / Artix-7 / Clock Components / MMCM/PLL / Base Mixed Mode Clock Manager (MMCE2_BASE), and I started editing from there. Of course I can do that because I have a bit more knowledge about what's available and what the numbers mean.

Perhaps this "Language Templates" function can be useful for you as well at some point.

> I just see it as a black box that generates a 100MHz clock and I use it as a layer of abstraction.

At this time, that's perfectly fine. I hope the little explanation I gave about how MMCMs work internally will serve you at some time in the future, but for now just seeing it as a magical block that takes a 12 MHz clock and generates a 100 MHz clock from that is sufficient.

> The toplevel design is, as the name suggest a lot more top level (higher level of abstraction).

Indeed, and that's precisely the intention. As soon as projects grow beyond trivial, it is good to separate modules (at 1 module per file), and have a "toplevel" module that just instantiates stuff and glues it together. It's a bit comparable to "main" in C, Java, or Python.

> I think the latter is a bit cleaner.

That's a matter of taste (or perhaps lack thereof, in my case). I agree that usually, more explicit is better.

I am still new to Verilog, and integer literal values have an associated bit width, and it is indeed possible (and perhaps advisable) to be explicit about this.

However, to what extent one wants/needs to be explicit is not beyond debate. As an analogy, consider the following C fragment:
 

unsigned p = 0;   // Everybody writes this ...
unsigned q = 0u;  // Even though this is more explicit.

if you know the rules of the language, there is little harm in assigning 0 (a signed integer) to an unsigned variable. The more explicit form (second line) is rarely if ever seen.

Anyway, point taken.

> Now you created 2 wires

The truth of the matter is that the precise difference between wire and reg is quite subtle. The way I read it (on the many Verilog tutorials I find online, of various quality) is that a wire is always a purely combinatorial value, while a reg may (but doesn't have to) give rise to a register (i.e., is stored in one or more flipflops). However, there are circumstances where a reg is purely combinatorial, so it will not correspond to flip-flops once the design is in the FPGA. It's subtle.

What you must know is that the two best-known HDLs (VHDL and Verilog) were originally defined as programming languages that can run on normal PCs; if used in that way, the runtime model is that of a "discrete event simulator". It's a long story, harkening back to the time when these languages were made primarily to unambiguously specify the behavior of electronic components between, for example, the US DoD and chip suppliers. This "discrete event simulator" model has all kinds of implications for the semantics of both languages, not in the least that you can do "deferred signal assignment", i.e., specify that a signal should get some value somewhere in the future. (That is what a non-blocking assignment actually is: it says: this effect will only be visible in the future).

It was a bit later people figured out that a subset of both languages can actually be used to program devices like CPLDs and FPGAs.

In VHDL (which I am much more familiar with) you have a similar but subtly different distinction between "signals" and "variables", and it is a source of much confusion especially for beginners there as well. They are not quite comparable to the "reg" / "wire" distinction in Verilog, unfortunately, so my understanding of them doesn't carry over well.

Added to this is the difference between "blocking" and "non-blocking" assignments. (Likewise, in VHDL, you have the 'variable assignment operator' := and the 'signal assignment operator' <=). Verilog tutorials are vague on these. What they appear to agree on is that in a clock-sensitive always block (i.e., an always block that is sensitive to posedge CLK) you should use '<='. But I experimented a bit and you can actually use '='. The semantical distinction and the implications for coding style are not fully clear to me at this time; I'm afraid the only way to go is to read an authoritative source on Verilog (like the standard, or an authoritative book), and figure out what tools like Vivado support.

I did that for VHDL, and there I understand what I'm doing 99.9% of the time, but in Verilog, I'm not there yet; and I guess this is even more difficult for you.

> a small question, can't you just make your output (FSM_OUT) a reg

Perhaps. But I will only want to do that once I really understand what that means and what implications it has.

> Also, I am pretty sure that Verilog is weakly typed so you don't have to specify types, but shouldn't you specify your input CLK as a wire and your output (FSM_OUT) as a reg?

As I understand from tutorials, if not specified, module-level symbols will be 'wires' if not specified. As I said, I'm still vague on what the implications of the wire/reg annotation are, so I cannot yet decide on whether this is a good idea or not.

So far, coming from VHDL, Verilog appears to be less well-designed as a language. Especially its type system is very limited compared to what VHDL offers, although I understand that some of that is rectified in SystemVerilog, which can be seen as the modern incarnation of Verilog.

It's also clear that Verilog gets most of its syntax from the C family of languages, whereas VHDL is a language in the Algol/Pascal/Modula/Ada line of succession. I prefer the latter. It is true that VHDL is very verbose compared to Verilog, but I don't mind. As I like to say, if your work speed is bottlenecked by the number of keystrokes per minute, your job is not challenging enough... :)


 

Edited by reddish
Link to comment
Share on other sites

No, you're supposed to see a square wave at 57600 Hz with 0.0 and 3.3V levels, with each constant-signal stretch taking 8.68 us. The signals are now slow enough that, when properly zoomed out, everything should look pretty nice and crispy:
 

image.png.807d96b3419be9446f4a39d6e6430a29.png


As always, try to find the bug yourself. It's part of the learning; finding the bug by yourself will prevent similar mistakes in the future much better than me telling you what's wrong.

But if you really get stuck, let me see the code.
 

Link to comment
Share on other sites

yeah, I had a bug where I wrote a variable in CAPS instead of lowercase lol. this is what I got with this bit of code:
wire [3:0] next_counter = (r_counter + 1) % (10);
wire       next_fsm_out = (next_counter < 3) ? 1 : 0;

20230905_143440.jpg

16939141064683185037157732476154.jpg

Edited by GalD101
Link to comment
Share on other sites

> yeah, I had a bug where I wrote a variable in CAPS instead of lowercase lol

See, you won't make that mistake again anytime soon. It's working :)

From the pic it seems youre doing the at the 10 MHz / 30% duty cycle thing.

My picture above is for the thing that comes after: the 115200 baud signal.

Should be easy from where you are now.


 

Link to comment
Share on other sites

this is what I got with this bit of code:
wire [3:0] next_counter = (r_counter + 1) % (868*2 + 1);
  wire       next_fsm_out = (next_counter < 868) ? 1 : 0;
I don't think that's right
 

16939145495949062186546273985501.jpg

Edited by GalD101
Link to comment
Share on other sites

Just now, GalD101 said:

this is what I got with this bit of code:
wire [3:0] next_counter = (r_counter + 1) % (868*2 + 1);
  wire       next_fsm_out = (next_counter < 868) ? 1 : 0;
I don't think that's right
 


I see two distinct bugs in those two lines.

Happy hunting :-)

Link to comment
Share on other sites

I also see that if I want the register to go up to 868*2 + 1 = 1737 I need to use 11 bits (11011001001)
so that means I need to change the size of the register but you said I have a bug in those lines so I think that's at least another bug

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...