-
Notifications
You must be signed in to change notification settings - Fork 49
Digital Logic Basics
This page describes basic examples common to most HDLs. Ultimately these should not be very interesting.
For real designs see something the Arty blinking example or all examples.
See more information on specifying top level modules+connections.
PipelineC stateful functions (functions that maintain state w/ static local variables) are just combinatorial logic and registers (can't be autopipelined), so they translate directly to HDL processes.
Consider the following generic VHDL:
-- Combinatorial logic with a storage register
signal the_reg : some_type_t;
signal the_wire : some_type_t;
process(input_wire, the_reg) is -- inputs sync to clk
variable input_variable: some_type_t;
variable the_reg_variable : some_type_t;
begin
input_variable := input_wire;
the_reg_variable := the_reg;
-- ... Do work with 'input_variable', 'the_reg_variable'
-- and other variables, functions, etc and it kinda looks like C ...
the_wire <= the_reg_variable;
end process;
the_reg <= the_wire when rising_edge(clk);
-- Connects output port
output <= the_wire;
The equivalent PipelineC is
some_type_t some_func_name(some_type_t input)
{
static some_type_t the_reg;
//... Do work with 'input', 'the_reg'
//... and other variables, functions, etc...
// Return connects output port
return the_reg;
}
PipelineC functions are a single clock domain, rising edge assumed. Function arguments are input ports, the return value is the output port. Function bodies are combinatorial logic dataflow graphs.
For more information on modules check out this page.
These wires are contained within a single function/module/process. As in C, data flows from inputs to return, thus these wires are all unidirectional (typical C 'execution order'). These wires differ from the 'clock crossing wires' and 'feedback wires' discussed below. To be clear: using and assigning to non-static local variables creates wires, as opposed to those variables being the single wire.
...
{
// Assigning to local variables creates wires; standard C assignment behavior
// Data flows from function inputs to return
float x = y; // Wire from whatever is driving y to x
}
//input_t input_reg;
//output_t output_reg;
output_t func(input_t in_wire)
{
static input_t input_reg; // Prefer static locals
static output_t output_reg;
// Reading directly from a register and assigning it to the return value is an output reg
// Good practice to do at the start of function to ensure any future logic driving to output_reg
// is not what is going out the output port
output_t out_wire = output_reg;
// ... function combinatorial logic using output_reg and and input_reg variables as desired ...
// Assigning to a register reading directly from an input is an input reg
// Good practice to do the end of function to ensure any prior reads from input_reg
// are from the static state any not just a pass through of the in_wire
input_reg = in_wire;
return out_wire;
}
uint1_t a_gate_example(uint1_t in0, uint1_t in1)
{
return in0 & in1; // Or, xor, etc
}
uint32_t counter(uint1_t increment)
{
static uint32_t counter_reg;
if(increment)
{
counter_reg += 1
}
return counter_reg;
}
See pages on how to use global variables and clock crossings to move data between functions.
In PipelineC signal flow is from inputs to return output. However, in digital logic it is often necessary to send signals in the opposite direction of data flow. FEEDBACK
wires can be used to construct backwards flowing wires as described below. (All assignments to a variable prior to it's FEEDBACK
pragma are removed/ignored, do reads to variable before writes).
Pseudo Code Example 1:
main(i)
{
bar_to_foo
#pragma FEEDBACK bar_to_foo
foo_to_bar = foo(i, bar_to_foo)
rv, bar_to_foo = bar(foo_to_bar)
return rv;
}
Example 2:
uint32_t main(uint32_t x, uint32_t y)
{
uint32_t x_feedback;
#pragma FEEDBACK x_feedback
// This doesnt make sense unless FEEDBACK pragma'd
// x_feedback has not been assigned a value
uint32_t x_looped_back = x_feedback;
// This last assignment to x_feedback is what
// shows up as the first x_feedback read value
x_feedback = x + 1;
return x_looped_back + y;
}
These backwards flowing signals can be thought of as leaving a function, outside of the function propagating backwards towards the inputs, and then reentering the function in a forward direction to be read like a normal wire (though carrying backwards propagated value).
Clock crossing/wires traveling out and then back into the same function are identical to these locally declared FEEDBACK
wires (a clock crossing between the same clock domain == a wire).
C syntax isn't ideal hardware description language. There is a fair amount of autogenerated code to help with that.
Write raw HDL if you must.
Continue onto more examples like LEDs and UART examples on a real board.