File interaction

This library exports functions for basic file interaction. The exposed functions enable you to read integer data from a basic input file, and to dump integer values into an output file. While this is rather basic, the file API can be extended using a shared C++ library and an actsim configuration file.

Files in actsim can be accessed using the common simulator prefix and file IDs. The common prefix is set using an actsim configuration file. The following is a snippet from the default actsim configuration.

#
# parameters for external file names for sim::file* functions
#
begin file
    # by default, file names will be prefix.<number>
    string prefix "_infile_"

    # alternatively, you can specify file names here
    # where 0 = first file name, 1 = second file name, etc.
    # string_table name_table "file1.in" "file2.in"

    # by default, output file names will be prefix.<number>
    string outprefix "_outfile_"
    # alternatively, you can specify output file names here
    # where 0 = first file name, 1 = second file name, etc.
    # string_table outname_table "file1.in" "file2.in"
end

This configuration can be found on your local machine by going to $ACT_HOME/conf/generic/actsim.conf. Please see the actsim documentation page for more details.

By default, the prefix for input files is infile. and outfile. for output files, where the period is followed by the file ID. This simulation library allows you simultaneous read access to a given file by multiple processes. Simultaneous read and write access, or multi-write access is not allowed. The following functions are provided inside the sim::file namespace:

// read from files
export function openr (int<32> file_id) : int<32>;
export function read (int<32> reader_id) : int<64>;
export function eof (int<32> reader_id) : bool;
export function closer (int<32> reader_id) : bool;
 
// write to files
export function openw (int<32> file_id) : int<32>;
export function write (int<32> writer_id; int<64> val) : bool;
export function closew (int<32> writer_id) : bool;

File reader example

We will go over a simple example for reading an input file. Input files can provide integers of up to 32 bid width. Numbers are interpreted as base 10 by default, base 16, 8, and 2 are also allowed by the use of prefixes. Input files also support comments using # as a prefix. Files can contain a trailing newline character after the last value, multiple newlines or comments after the last input value are currently not supported (to be changed in the future).

In this example, we will construct a simple file source, which reads from infile.0 using the simulation library (however, this library also already provides a fully functional one, see the page on sources, which we recommend using instead).

defproc source_file (chan!(int<32>) O)
{
    int<32> buf;
    int reader_id;
    bool success;
 
    chp {
        // we will put all further code here
    }
}

File interactions are not synthesizable. All of our code is CHP based. You might have spotted the variables at the beginning of the block, we will explain them shortly.

First, we need to open the file for reading. This will return a reader ID, which is quite similar to a file descriptor on POSIX systems. As indicated by the comment, this will happen in the CHP block.

// open the file and get a reader ID
reader_id := sim::file::openr (0);
 
// this is not necessary but good practice error checking
assert (reader_id != 0, "Oh no! We failed to open input file with ID 0!");

openr will return a number larger than 0, if the file was successfully opened; a reader ID of 0 will indicate that something went wrong. It is good practice to make sure that everything worked; a call to assert is an easy solution for this.

We have now opened file 0 for reading and saved the reader ID in reader_id. The next step is to read a value:

// read from the file
buf := sim::file::read(reader_id);

Since we are writing a source, we then go on to send this value to our output channel:

// send the value off
O!buf;

We want to repeat this process until we reach the end of the input file. For this, we simply put these two steps into a do-while loop:

// read until the file reports EOF and send it to the output channel
*[
    // read, then send the value
    buf := sim::file::read(reader_id);
    O!buf;
<- ~sim::file::eof (reader_id) ];

In the end, we want to close the file again. Similarly to openr, closer's return value will allow us to make sure everything worked fine.

// close the file
success := sim::file::closer (reader_id);
 
// making sure everything worked
assert (success, "Oh no! We failed to close input file with ID 0!")

Putting things together, the full example looks like this:

defproc source_file (chan!(int<32>) O)
{
    int<32> buf;
    int reader_id;
    bool success;
 
    chp {
        // open the file and get a reader ID
        reader_id := sim::file::openr (0);
 
        // this is not necessary but good practice error checking
        assert (reader_id != 0, "Oh no! We failed to open input file with ID 0!");
 
        // read until the file reports EOF and send it to the output channel
        *[
            // read, then send the value
            buf := sim::file::read(reader_id);
            O!buf;
        <- ~sim::file::eof (reader_id) ];
 
        // close the file
        success := sim::file::closer (reader_id);
 
        // making sure everything worked
        assert (success, "Oh no! We failed to close input file with ID 0!")
    }
}

File writer example

We will go over a simple example for writing an output file. Without writing C code, which hooks into the already provided API, the file writer can only push simple integer values to an output file.

In this example, we will construct a simple file sink, which writes to outfile.0 using the simulation library (however, this library also already provides a fully functional one, see the page on sinks, which we recommend using instead).

defproc sink_file (chan!(int<32>) I)
{
    int<32> buf;
    int writer_id;
    bool success;
 
    chp {
        // we will put all further code here
    }
}

File interactions are not synthesizable. All of our code is CHP based. You might have spotted the variables at the beginning of the block, we will explain them shortly.

First, we need to open the file for writing. This will return a writer ID, which is quite similar to a file descriptor on POSIX systems. As indicated by the comment, this will happen in the CHP block.

// open the file and get a writer ID
writer_id := sim::file::openw (0);
 
// this is not necessary but good practice error checking
assert (writer_id != 0, "Oh no! We failed to open output file with ID 0!");

openw will return a number larger than 0, if the file was successfully opened; a writer ID of 0 will indicate that something went wrong. It is good practice to make sure that everything worked; a call to assert is an easy solution for this.

We have now opened file 0 for writing and saved the writer ID in writer_id. The next step is to receive a value from the input channel and write it to the file:

// receive the value and write to file
I?buf;
success := sim::file::write (write_id, buf)

We want to repeat this process indefinitely (or until the simulation is ended). For this, we simply put these two steps into an infinite loop:

*[
    // receive the value and write to file
    I?buf;
    success := sim::file::write (write_id, buf)
];

Since we don't know how much data will be there, we'll assume the file will only be closed when the simulation ends. In this case, we don't need to explicitly close the file.

Putting things together, the full example looks like this:

defproc sink_file (chan!(int<32>) I)
{
    int<32> buf;
    int writer_id;
    bool success;
 
    chp {
        // open the file and get a writer ID
        writer_id := sim::file::openw (0);
 
        // this is not necessary but good practice error checking
        assert (writer_id != 0, "Oh no! We failed to open output file with ID 0!");
 
        *[
            // receive the value and write to file
            I?buf;
            success := sim::file::write (write_id, buf)
        ];
    }
}

Extending the File API

File interactions can be extended using a shared C++ library, however the functions exposed to actsim must be made external C functions due to C++ name mangling. Include the simlib_file.h header to access the core file functions. With this, cou can use actsim_file_write_core to write your own custom output generator. Here is a brief example taken from the logger output generation. For more information about the structure of an external C function callable from CHP, as well as how to make it accessible to actsim, see the actsim page or read the default actsim.conf in the actsim source code in the simlib/ directory.

extern "C" expr_res actsim_file_write_log(int argc, struct expr_res* args) {
    expr_res ret;
    ret.width = 1;
    ret.v = 1;  // on error we return 1
 
    // make sure we have the appropriate amount of arguments
    if (argc != 5) {
        std::cerr
            << "actim_file_write_log: Must be invoked with 5 arguments only"
            << std::endl;
        return ret;
    }
 
    uint32_t writer_id = args[0].v;
    uint8_t verbosity = args[1].v;
    uint32_t logger_id = args[2].v;
    uint32_t channel = args[3].v;
    uint64_t value = args[4].v;
 
    // build the log line
    std::ostringstream builder;
 
    switch (verbosity) {
        case 0:
            builder << channel << ":" << std::hex << value << std::endl;
            break;
 
        case 1:
            builder << logger_id << " (" << channel << "): " << std::hex
                    << value << std::endl;
            break;
 
        case 2:
            builder << "Logger " << logger_id << " (Channel " << channel
                    << "): Received value " << value << "%x (0x" << std::hex
                    << value << ")" << std::endl;
 
        default:
            break;
    }
 
    ret.v = actsim_file_write_core(writer_id, builder.str());
    return ret;
}

When you have compiled your .so, you need to make the library available to actsim via a configuration file:

begin extern

    string_tablex libs "mylib"

    begin mylib
        string path "/some/path/to/my_external_lib.so"

        # logger file output
        string my_namespace::write_log      "actsim_file_write_log"
    end

end

And finally, make the function available to CHP:

namespace my_namespace {
export function write_log (int<32> writer_id; int<8> verbosity; int<32> log_id, channel; int<64> val) : bool;
}

Mind the name change set in the actsim configuration! Functions in CHP must always return a value. If you do not need a return value, either use a boolean in combination with an assert function for error checking, or just read into a dummy variable. Not reading into anything will currently cause your function to be removed by the optimization pass.