B-Lang-org / bsc

Bluespec Compiler (BSC)
Other
925 stars 142 forks source link

Support for $value$plusargs #302

Open thoughtpolice opened 3 years ago

thoughtpolice commented 3 years ago

There is support for $test$plusargs : String -> ActionValue Bool in Bluesim, which just returns whether an argument was provided on the command line with +, i.e. sim.exe +foo means $test$plusargs("foo") returns true. This is useful for various test bench functionality.

Similarly, there is also a Verilog task $value$plusargs that allows you to actually pass a parameter to the simulator command line. Here's an example of using it to choose a firmware file to load (from picorv32):

    reg [1023:0] firmware_file;
    initial begin
        if (!$value$plusargs("firmware=%s", firmware_file))
            firmware_file = "firmware/firmware.hex";
        $readmemh(firmware_file, mem.memory);
    end

Which would be invoked with e.g. picorv32_sim.exe +firmware=custom.hex if you wanted to choose the firmware file at runtime.

It would be nice to support this kind of functionality, at least in some restricted form in Bluesim and the Verilog compiler. For example, maybe the API could look like this, where format specifiers like %d or %s are represented as typeclass instances:

function ActionValue#(t) $value$plusargs(String param, String default) provisos (Parseable#(t))

// parseable instances for String, Bit#(n), etc represent instances of '%d' or '%s' occurring

With a use like:

firmware_file <- $value$plusargs("firmware", "firmware/firmware.hex"); // uses the Parseable(String)/%s instance
stack_depth <- $value$plusargs("stackdepth", Bit#(XLEN)'(1024*XLEN)); // uses Parseable(Bit#(XLEN))/%b instance

So Parseable is a typeclass that basically describes how to decompose a type from a String into a value. In other words, it's basically Read from Haskell, where as FShow is really just Show.

I suspect doing something like this ranges from medium-to-nontrivial difficulty (library and compiler and doc additions), but it sure would be a nice quality of life improvement!

quark17 commented 3 years ago

Thanks for this suggestion! If we can decide how to do work out the types, supporting $value$plusargs in some way would be good. I will also look to see if there was past discussion at Bluespec and whether there were any other proposals. I don't have the time to think on this just now, but I wanted to mention a workaround: BSC can generate both Bluesim and Verilog for designs with imported C, so you can import a C function to get values from the environment (or from a file, or from a pipe etc). It's not the same as a command-line argument, but depending on your shell you could still do something like:

FIRMWARE=firmware.hex STACKDEPTH=12 ./sim.exe
john-terrell commented 2 years ago

Any progress on this?

quark17 commented 2 years ago

Functions in BSV/BH need to have a type. Generally, they have a fixed type. In BSV, we want to allow users to write the Verilog/SystemVerilog system tasks that they're familiar with, like $display and $finish, which don't have fixed types -- mostly because they can have variable numbers of arguments, and the types of those arguments can vary.

One approach would have been to not support Verilog's $display syntax in BSV, but to provide new functions (with well-behaved types) that users could use for displaying (and which would be implemented with $display in the generated Verilog, but would just look different in the source). For example, in the Prelude we could have:

displayReal :: Real -> Action

But since we want to support Verilog system tasks as closely as possible in BSV, we found a way to make it work by having special-case code in BSC's typecheck stage, that recognizes system tasks and typechecks them specially. That is, the type for them in the Prelude is entirely ignored, and special-case code is used. So, for example, you'll see this in the Prelude:

-- XXX: Well-intentioned lies
foreign $display :: PrimAction = "$display"

The type of $display is not PrimAction, and anything could be written there, because it is ignored. Instead, you'll see that the typechecker has this code:

-- lookup system-task handler in taskCheckMap and run
tiExpr as td exp@(CTaskApply f@(CVar task) es) =
  let undeftask as td f es = err (getPosition task, EUndefinedTask (pfpString task))
      checker = lookupWithDefaultBy qualEq taskCheckMap undeftask task
  in checker as td f es

This looks for the task in a map (taskCheckMap) and finds the function that should be used to typecheck the task. For example, it has this entry, which says to use the function taskCheckDisplay (defined in TCheck.hs) to typecheck the $display task:

(idDisplay,   taskCheckDisplay),

So, going back to $value$plusargs. What form should it have in BSV? The task has a return value and it also puts a result into a variable, that is passed as argument in Verilog. BSV/BH doesn't support passing destination variables, so that will need to be an additional return value. It probably makes sense to return a Maybe type, encapsulating both the success/fail value and the optional value on success:

$value$plusargs :: String -> Maybe t

Using a variable t here means that the user can get back any type they want (according to the type of the context where it's called). But what if the String has a format code that doesn't match that type? What if the user's string has %f (for a real number), but the type of t is Bit#(8) or even String? Should BSC try to do more checking?

Here's where we have a lot of options. Instead of supporting one $value$plusargs function, we could define several for the specific formats:

stringValuePlusargs :: String -> Maybe String
realValuePlusargs :: String -> Maybe Real
-- or possibly this:
realValuePlusargs :: (RealLiteral r) => String -> Maybe r
...

For this set of functions, we could even specify that the String argument is not to contain a format string (and BSC would append the appropriate format code in the generated Verilog).

If we want to stick with a single $value$plusargs function in BSV, we still have some options. If the argument is a string literal, then the typechecker can parse the string and see what the format is, and give it a type based on that. But this doesn't work if the string comes from a variable (or, worse, is computed through execution of the program, after typechecking). We could, instead, do a check of the string after elaboration (before generating Verilog): during typecheck, we'd allow a general type t (or possibly create a new typeclass to limit t to only the types that have formats) and after elaboration, once the string argument has been computed, BSC could do a check at that point to confirm that the format matches the type (and, if not, give a warning or error).

I'm not sure which of these options I'd prefer. I'd love to hear what others think.

If you're thinking of adding support, here are some more details:

In the Prelude, the declaration for $test$plusargs is a little more complicated than for $display. I don't offhand recall why, but $value$plusargs would probably need to be similar:

$test$plusargs :: String -> ActionValue Bool
$test$plusargs x = fromActionValue_ (__testplusargs__ x)
foreign __testplusargs__ :: String -> ActionValue_ 1 = "$test$plusargs"

You'll notice in PreIds.hs, there's a declaration for idTestPlusargs (based on the fsTestPlusargs in PreStrings.hs). And that ID is what is used in the task map in TCheck.hs.

And that may be all you need, for the Verilog backend? We'll want to support it in Bluesim, too. (If you're able to include that, that would be preferable, but if not, that's ok! I'd rather have someone's help to implement the Verilog part, than not have them contribute at all! We can add the Bluesim as a later step.) BSC converts the $ in the function name to dollar_ in the generated C++ for Bluesim, and these functions are implemented in bs_system_tasks.h (in src/bluesim/).

Test cases for $test$plusargs are in testsuite/bsc.verilog/tasks/plusargs/ and that's probably where new tests for new functions should go. We should test with and without a format code in the string argument, test the variety of format codes, and test when the return type doesn't match the code, and any other error situations.