Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Allow global variables to be directly used (not as wires) to communicate between functions #73

Closed
JulianKemmerer opened this issue Mar 26, 2022 · 12 comments
Assignees
Labels
enhancement New feature or request

Comments

@JulianKemmerer
Copy link
Owner

As @suarezvictor has pointed out - wouldnt it be nice to not have to specify WIRE for things - and have the tool do it all? 😏

Currently global variables are identical in functionality to static local variables. They are associated with a single function only. Even though global variables are in the global scope, they cannot be directly used by multiple functions. Global variables are locked to a single function right now.

Currently WIRE's are the work around for this. A global variable is declared but special WIRE/clock crossing functions are used to make clear where writes to the global vs reads to global are occurring.

Why is it needed to know when writes and reads occur? Well lets get into why this issue has few layers to it...

@JulianKemmerer JulianKemmerer added the enhancement New feature or request label Mar 26, 2022
@JulianKemmerer JulianKemmerer self-assigned this Mar 26, 2022
@JulianKemmerer
Copy link
Owner Author

First off:
When moving data between main functions you might be crossing clock domains. Since these variables wont have the WIRE macro to include clock crossing logic pipelinec builds in (the stuff about multiple data path width ratio'd clock crossing stuff as needed etc), these global variables need to be either not clock crossings at all - literally just wires, or marked as ASYNC_WIRE

@JulianKemmerer
Copy link
Owner Author

JulianKemmerer commented Mar 26, 2022

Immediately - like WIREs these globals are going to be limited to have a single function writing the variable and 0 or more functions reading the variable.

Otherwise very hard to make sense of what something like this means - would implied shared/arbitrated access to the global variable register for writing. Ex. bad example below

// Ex. What occurs in this code? Not specified/apparent

uint32_t the_global = 0;
// Must be same clock domain or mark global as ASYNC_WIRE
#pragma MAIN_MHZ write_main 100.0
#pragma MAIN_MHZ read_main 100.0

uint32_t write_main()
{
  the_global += 1;
  return the_global;
}

uint32_t read_main()
{
  the_global += 1;
  return the_global;
}

@JulianKemmerer
Copy link
Owner Author

Ok so Why is it needed to know when writes and reads occur? ... consider the following:

@suarezvictor what do you make of this

int32_t the_global = 0;
// Must be same clock domain or mark global as ASYNC_WIRE
#pragma MAIN_MHZ write_main 100.0
#pragma MAIN_MHZ read_main 100.0

void write_main()
{
  // Save global
  int32_t temp = the_global;
  // 'Erase it'
  the_global = -1;
  // Do some stuff
  do_some_stuff();
  // Reset global
  the_global = temp;
  // Count as demo
  the_global += 1;
}

uint32_t read_main()
{
  static uint32_t local_counter;
  if(the_global==-1)
  {
    // Never reaches here
    // the_global = -1; from write_main not seen
  }
  // Count as demo
  local_counter += the_global;
  return local_counter;
}

Does the read_main ever see the temp -1 value?

I propose no.

Currently writes to globals dont 'go through'/'commit'/'write to registers' until the 'end of the function' / clock edge. So in that same sense that each 'iteration' of the function updates the registers: writes to globals are not updated/'seen' in other functions until the next clock cycle.

(Otherwise if yes read_main should see -1 then oh gosh... somehow need to figure out 'ordering'/mixing of wiring of the combinatorial logic of write_main and read_main as their writes and reads occur relative to each other ...needs to happen so the data can propagate to the other function in the same clock cycle (and potentially back). Sounds terribly messy/dont even know what im saying describing the circuit really yet.)

@JulianKemmerer
Copy link
Owner Author

This should almost certainly hijack the existing VHDL generation clock cross module hierarchy signal routing stuff - maybe good time to rename signals 'global_bus' or something

@JulianKemmerer
Copy link
Owner Author

Its good to realize that the above is almost a 'trick' question in that do_some_stuff(); doesnt appear to be ~dataflow style - it doesn't have inputs flowing into it, and outputs coming out. But it gets to the point of "what is the concept of ~execution ~ time passing ~ side effects" here. Its all one clock cycle so you either have weird-to-software 'wire' behavior (comb. loops possible, etc) during one clock cycle - or the Does the read_main ever see the temp -1 value? point of 'register only updated globally at end of function' behavior demoed above.

Again I am planning to implement this as one clock delayed/registered 'Does the read_main ever see the temp -1 value? No.' style...

@JulianKemmerer
Copy link
Owner Author

JulianKemmerer commented Jun 21, 2022

One the above is done. It should be possible to add a pragma to global var def making it a wire - and same checks of one driver process and many readers is fine - but driver of wire is comb logic D into reg, instead of from output Q

@JulianKemmerer
Copy link
Owner Author

Seems possible to make the default the opposite - global readers get the comb. logic D input but for something meant to be storage that seems to itself to making comb. loops out of your intended storage too easily... hmmm

@JulianKemmerer
Copy link
Owner Author

Ive decided to actually make the default behavior to have writes to global wires - and the comb logic attached - to be directly wired into reading locations. Not from the register itself. (wire doing into D, not wire from Q)

This makes the default behavior of shared globals like WIREs - which also completes some of this issue #72

@JulianKemmerer
Copy link
Owner Author

JulianKemmerer commented Jun 25, 2022

@suarezvictor and others would be happy to know that this issue has implemented and tested. 👍
Support starts around this commit 4e0cf07

Now

// Dont need clock cross marker/include
#include "clock_crossing/the_global.h" // NOT NEEDED

WIRE_WRITE(type, the_global, rhs)
// Can be replaced with
the_global = rhs;

WIRE_READ(type, lhs, the_global)
// Can be replaced with
lhs = the_global;

Recent uart demos are a working demo of the syntax

// Globally visible ports / wires

Documentation will be updated shortly to point users to use of this new syntax...

@suarezvictor
Copy link

it looks so much better!

@suarezvictor
Copy link

suarezvictor commented Oct 11, 2022 via email

@JulianKemmerer
Copy link
Owner Author

The bare minimum example is like above

int32_t the_global = 0;
// Must be same clock domain or mark global as ASYNC_WIRE
#pragma MAIN_MHZ write_main 100.0
#pragma MAIN_MHZ read_main 100.0

void write_main()
{
  // Count as demo
  the_global += 1;
}

uint32_t read_main()
{
  static uint32_t local_counter;
  // Count as demo
  local_counter += the_global;
  return local_counter;
}

Where the function reading the global variable read_main sees the value of the_global after the write_main completes (i.e. after the the_global += 1; happens)

So during the first clock the_global += 1; increments to ==1 and in the same clock local_counter += the_global; increments the local counter by the_global==1, local_counter==1 as well.

Happy to talk more about it on Discord 🤓

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants