Verilog was designed from the getgo to produce digital logic.
VHDL is a generic hardware description language. You could even describe a nut and bolt or screwdriver with it. For logic you use the logic libraries
systemverilog solves a number of 'perceived issues' ( like latch inferrence ) by providing constructs like always_ff and always_comb , but overall makes coding simpler.
The big thing is : learn how a logic synthesizer works and use it ! There is a very good book by Bhasker :
https://www.amazon.com/Verilog-HDL-Synthesis-Practical-Primer/dp/0965039153There is a difference between writing verilog ( or vhdl) and verilog (or vhdl) for SYNTHESIS ( fpga/ asic). Sure you can model things very high level , but the generated output will be horrible in terms of performance and logic size.
One of the least known, or least understood, (verilog/systemverilog) synthesis rules is this one : logic shall be executed IN THE ORDER IT IS WRITTEN. And most people ( non-asic designers, or software-approach people ) never use this.
It solves many problems , and it tells you EXACTLY what the synthesizer is going to build. I've written multimillion gate logic designs that were first-time right. No need to simulate it even. Synthesize and go ( this involved bus arbiters between two cpu's , memory and a whole bunch of accelerator logic. Some of that system sits in every harddisk produced.
I see so much code with if-then else constructions that are unneeded, unterminated (missing cases) , do not cover all possible pathways and create 'unwanted states'. That all could be solved if you just learn how a synthesizer works.
Here goes a short description:
To convert your list of instructions (which are sequential in nature ) to logic the synthesizer needs to build a 'cloud' of , let's call it 'command logic'. You tell it what you want a block to do and the synthesizer converts that in a bunch of combinatorial logic that will control a register. Doesn't matter what you are building. A counter, a memory , a shifter , a lookup table, a comparator, or even if this block is registered (flipflops) or combinatorial (and/or/not)
The synthesizer builds logic on-the-fly. It reads a line of code and drops down a bunch of gates/flipflops , then it evaluates the next line and does this again. At the end of the command list it runs a logic minimization algorithm that compresses the combinatorial stuff to the least amount of gates. This is NOT necessarily the final output. Depending on your target architecture (fpga or asic) the gates may or may not exist and logic may need to be 'expanded' again to fit what is available. ( your selected device may not have a 8 input AND gate and one will have to be built using two 4 input and gates and one 2 input and gate, the device compiler takes care of that, none of your worries, except timing wise )
let's look at an example:
build me a 4 bit counter.
- it needs a system_reset, reset , a preset , a parallel load , a count up/down control, an enable 4 inputs and 4 outputs. And a clk of course.
- the system_reset should work always, irrespective of any other control line.
- the reset,preset,load,up/down are tied to the 'enable' signal. if the counter is not enabled nothing is to happen.
- reset takes priority ,followed by preset , and load (meaning if more than one signal is active , reset has the highest 'weight'. if both reset and load commands arrive at the same time : reset wins.)
- updown zero means count down, if one : count up
always_ff(@ posedge clk) begin
if (enable) begin
count <= count-1;
if (updown) count <= count+1;
if (load) count <=data_in;
if (preset) count <=4'd9;
if (reset) count <=0;
end;
if(system_reset) count <=0;
end;
Let's analyse this a bit:
always_ff : i need flipflops here. Don't muck around with other constructs. You tell the compiler explicitly that it must create register logic, so there is no wiggle room for interpretation.
@ posedge clk : and we will perform operations based on the rising edge of a clock signal.
if(enable) : look at the enable signal : if it is high, process the attached list of commands
count <=count-1 : wait, what ? why do you do this. well here is the thing, i am using the synthesizer to my advantage here by writing so called 'scheduled' logic ( this comes down to the statement that 'statements will be executed in the order they are given' )
I Tell the synthesizer : schedule (prepare) to count down.
The next statement says "if updown count <= count +1". so now i tell the synthesizer : if updown is active : count up .
What is going on here ? ( and this is the KEY difference between software languages and hardware languages. once you grasp this , you can do anything you want. )
You will no doubt have had people tell you 'everything happens in parallel in an fpga . Well, it does , but that does not mean there is no 'order' ! there is : the order in which the statements are written !
in software this would happen :
count <=count -1 // we subtract one
if(enable) count <= count+1 ' if enabled we add one
so, if count was 3, we end up with either 2 (enable=0) or 3 (enable=1, we subtracted 1 (3-1=2), then added one 2+1 = 3
In logic that does NOT happen. You end up with 2 or 4 why ? Because of how a synthesizer works. And now i need to switch to a graphic representation of how a synthesizer 'builds' logic. Take a look at the attached picture.
The synthesizer starts reading your instruction list. The first thing it encounters is always_ff (posedge clk)
So, it drops down a register and attaches the clock to CLK. Since the synthesizer does not know what comes next , it attaches the output (count) to the input. We don't want any dangling lines ...
So whenever a CLK happens the register simply stores its own output. No data is corrupted.
Now it sees 'if(enable) {list of instructions} (second image down)
So the synthesizer injects a multiplexer ( the trapezoid thingie) under control of ENABLE. If ENABLE =0 then COUNT is connected to the data input of the flipflops, if ENABLE =1 then the list {} is connected to the data input of the flipflops.
A multiplexer is nothing but a switch under control of a signal. Such a switch in Boolean logic is a very simple equation : out = (in0 and not(enable)) or (in1 and enable) : two and gates, an inverter and an or gate.
Note : the list is represented by the yellow box.
Now the synthesizer sees : count <= count -1, so it drops a logic subtractor circuit in place.
The next statement says : if (updown) count up. So it injects another multiplexer , under control of UPDOWN , that switches between what it already had (count down) , and the new command (count up).
The next statement says if LOAD then we need to take in DATA_IN , so we drop another multiplexer that switches between everything that came before , and the now operation.
This is the key thing to remember: you switch between WHAT CAME BEFORE and the NEW statement. The ORDER IN WHICH INSTRUCTIONS ARE GIVEN.
Yes, all that stuff happens in parallel, the adding, subtracting , loading , but it happens IN THE ORDER IT IS GIVEN. So the LOAD has a higher priority than anything that came before. It doesn't matter what state up-down is in, or what the count value is incremented or decremented , if LOAD is active then we will take in DATA_IN.
The synthesizer keeps on reading statements and dropping multiplexers : one for preset , one for reset. Every new statement has a HIGHER priority than what came before. So you will never get a conflict of interest. We have two signals active.. how do we decide ? We need if then else ... no you don't it follows the order given. If you write if-then-else statements there is a risk you forget to specify certain conditions. As we cannot have 'floating' lines ,the synthesizer will tie those 'forgotten' pathways' to something ( and that is not always what you might think it is ... ) and then you end up with logic that does weird things if a state is encountered that was not covered.
By using scheduling as i describe it here there are no undefined states in the logic. You know exactly what is described and in what order of priority.
now, we have come to the end of the begin..end block so the synthesizer considers this block as 'closed and it goes back to the if(enable) multiplexer if build the very first.
The next statement says if system_reset ... so we get another multiplexer under control of that signal. If this signal is active, it doesn't matter what came before as instructions: that entire block is cut off by the multiplexer.
So now the synthesizer has a chunk of logic built form multiplexers, input signals and operators such as + and - . This entire 'cloud' of logic is nothing but and,or and not gates. So one huge boolean expression that can be minimized very quickly and efficiently.
The truth table is complete, there are no undefined states in the table because if the scheduling principle.
If you tried to build the table using if-then elseif or switch case statements , there is a risk you have undefined states. in a switch case you can specify a DEFAULT state that enures the undefined states fall back to this state. With if-then,elseif,else statements you have to be careful to cover all possible combinations. Also ,when someone comes back and say : we want preset to take priority over reset you have carefully alter a complex if-then else cluster. with scheduled logic : move that statement one line below so it gets higher priority.
Now, there are going to be people that will tell you ' this is nonsense' , this is not 'how it is done', or 'how can you be sure the compiler works that way' and many other things. Don't listen to them.
The compiler ? there is such a thing as a STANDARD (vhdl, verilog,systemverilog). And this scheduling is PART of all these standards. All the synthesizers work this way ( there really is only one way to translate a list of statements into a cloud of logic and that is the way i described it : inject multiplexers under control of signals, that switches the datapath around. Then minimize that logic into a truth table. This gives the fastest and smallest implementation. All the synthesizers i know (alteray, xilinx, lattice, cypress, Synopsys ) all respect the standard.
As for this 'is nonsense' or this is 'not how it is done': i've worked with silicon for 23 years and did many complex designs, some of which sit in ADSL/VDSL modems and central office equipment , some of which sit in almost any harddisk that was produced in the last 15 years and controls the motors and positions the heads. And that logic was built exactly the way i described it above. Using scheduling.
Learn this principle, and use it to your advantage. You will write more concise code, without undefined states, that is easier to maintain/modify when priorities change.
Block level simulation ? Virtually not needed.
High level (system level), yes.
Timing , yes.
Functionally (does it do what i intend it to do) , no. There is no 'wiggle room' or undefined path.