Skip to content

Modules, Hierarchy, Composability

Julian Kemmerer edited this page Sep 17, 2021 · 24 revisions

Top Level IO

Any function marked marked as a pragma 'MAIN', ex. #pragma MAIN my_main_inst, will have its inputs and output arguments/value exposed as a port on the top level generated VHDL module. ex.

#pragma MAIN_MHZ my_main 100.0
uint8_t my_main(uint8_t input_port_name)
{
  return ...
}
entity top is
port(
  clk_100p0 : in std_logic;
  -- IO for each main func
  my_main_input_port_name : in unsigned(7 downto 0);
  my_main_return_output : out unsigned(7 downto 0)
);
end top;

It is also typical to use clock crossing mechanisms to create globally visible versions of ports (so top level ports are not confined to my_main instance). For example:

#include "wire.h"
#pragma MAIN_MHZ my_main 100.0
uint8_t my_global_input;
#include "clock_crossing/my_global_input.h"
uint8_t my_global_output;
#include "clock_crossing/my_global_output.h"
uint8_t my_main(uint8_t input_port_name)
{
  // Connect top level port to global port
  // my_global_input = input_port_name
  WIRE_WRITE(uint8_t, my_global_input, input_port_name)
  // Connect top level port to global port
  // output_wire = my_global_output
  uint8_t output_wire;
  WIRE_READ(uint8_t, output_wire, my_global_output)
  return output_wire;
}
// Later, anywhere in code can connect to the other side of those wires
WIRE_READ(uint8_t, some_input_val, my_global_input)
WIRE_WRITE(uint8_t, my_global_output, some_output_val)

Feed forward nested functions

stylediagram

// sub.c
output_t sub(input_t input)
{
  // Do work on input to get output 
  return work(input);
}
// top.c
#include "sub.c"
output_t top(input_t input)
{
  return sub(input);
}

Point to point wires between modules

// top.c
// Globally visible ports/wires with built in READ and WRITE
input_t top_to_sub; // Output from top, input to sub
output_t sub_to_top; // Input into top, output from sub
output_t top(input_t input)
{
  WRITE(top_to_sub, input);
  return READ(sub_to_top);
}
// sub.c
#include "top.c"
void sub()
{
  // Do work on input to get output
  input_t input = READ(top_to_sub);
  output_t output = work(input);
  WRITE(sub_to_top, output);
}

Using both - debug/status LEDs example

bothstyles

As opposed to rewiring the LEDs manually through the ports of top,sub, and work using a globally visible point to point wire makes reading and writing arbitrary signals in the design possible.

// leds.c
uint4_t leds; // Globally visible port/wire name
uint4_t leds_module()
{
  // Drive the output port with the global wire
  return READ(leds);
}
#include "leds.c"
output_t work(input_t input)
{
  // Doing lots of complicated stuff
  // ...
  // Lets drive status leds
  WRITE(leds, 0xF);
  // ...
} 
output_t sub(input_t input)
{
  return work(input);
} 
output_t top(input_t input)
{
  return sub(input);
}

C Syntax

The above code snippets are pseudo code as the real C syntax is a bit uglier for global wires (a type of non volatile clock crossing wires). However some helpful macros have been defined to WIRE_READ and WIRE_WRITE with similar syntax to what is shown above.

Clone this wiki locally