Others have recommended VHDL and Verilog as languages. I'd like to add some insight, being an embedded programmer that does C++, Verilog, and hardware design.
Describing Verilog and VHDL to a software programmer, especially one with a long track record, I generally tell them that Verilog's syntax reminds me of C and VHDL's reminds me of Pascal. Verilog, like C, is terse and to the point, but it also lets you shoot yourself in the foot incredibly well. VHDL makes you spell out everydangthing.
Regarding Verilog, there are three versions you'll encounter: Verilog 1995, Verilog 2001, and SystemVerilog (2005, IIRC). I recommend SystemVerilog because it adds a lot of stuff for logic synthesis folks like me.
I hear you now... "Logic synthesis? As opposed to what?" Welcome to the dark side of firmware, my friend: both VHDL and Verilog were originally designed to be simulation languages. In other words, they are intended to build models, so their focus is on describing the effects that inputs (and their associated events) have on outputs. Then one day, people had the bright idea that they should be able to "compile" their simulations into firmware. Hilarity ensued.
So, in your code, you don't say, "make me a D flip flop, use CLK as its clock, D as its input, and Q as its output." You say, "on the rising edge of CLK, D is copied into Q," and the synthesizer says, "oh, you want a flip flop!" There is no end of pain when, due to some incomplete case in your code, the synthesizer implies things (like latches) you don't want there.
For example, a D flip-flop in SystemVerilog looks like:
reg Q;
always_ff @(posedge RESET, posedge CLK) begin
if(RESET)
Q <= '0;
else if(CLK_ENA)
Q <= D;
end
(No, StackOverflow can't syntax-highlight Verilog. Oh well.)
always_ff
means that the code in this block should be implemented with flip flops, not asynchronous logic or latches; this helps moot the "inferred the wrong logic" bug I mentioned above, and was added by SystemVerilog. posedge
means rising edge (there's also negedge
), @(event1, event2, ...)
means that the block executes if any of the listed events occur.
'0
expands to enough 0 bits to fill whatever it is being put into (1 bit in this case), and is another SystemVerilog addition. (If you wanted to say "one bit only", then you'd write 1'b0
, where 1
is the number of bits, b
means binary (d
and h
work, too; forgot if o
is in there), and 0
is the value. Be careful with sign-extension and truncation if the bit widths don't match.)
<=
means an assignment that takes effect at the end of the block, and is the only kind of assignment you should use in an always_ff
block. (Conversely, =
means the assignment should take effect immediately, before evaluating the effect of the next statement, and works best in asynchronous, combinational logic, which uses always_comb
.) What this means in practice is that if you want to, say, shift a bit through a series of registers, on the same clock edge, you can actually assign them to each other:
reg [3:1] Q;
always_ff @(posedge CLK) begin
Q[1] <= D;
Q[2] <= Q[1];
Q[3] <= Q[2];
end
Expand this from one bit per step to several, and you've got a rudimentary pipeline.
Programming-by-inference and the fact that, unlike software, firmware code executes simultaneously and not sequentially, are the two big things that programmers have to get their head around when writing firmware.
I have yet to find a good book on synthesis for FPGAs with SystemVerilog. All the books I've found focus very heavily on the simulation side, since they're generally aimed at people who write Verilog for IC synthesis, i.e. microprocessor design. (Can't blame them, it's where the money's at.) There was one (Xilinx Design Series something) that was somewhat decent, but it's old and has gotten long in the tooth, and says nothing of SystemVerilog.
Welcome to the jungle. Watch your step! ^_^