You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

517 lines
16 KiB

/*.......1.........2.........3.........4.........5.........6.........7.........8
================================================================================
FILE d_process/cfunc.mod
Copyright 2017-2018 Isotel d.o.o. http://www.isotel.eu
PROJECT http://isotel.eu/mixedsim
License: 3-clause BSD
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
AUTHORS
2017-2018 Uros Platse <uros@isotel.eu>
SUMMARY
This module provides an interface to an external process via
standard unix stdin/stdout pipe to extend ngspice functionality
to the level of embedded systems.
If a process ends with | character then rather than invoking
a process it opens named pipe, process_in which is input to the
process and pipe process_out for reading back from process.
Communication between this code model and a process is in 8-bit
binary format. On start-up
0x01: version
0x00-0xFF: number of inputs, max 255, 0 means none
0x00-0xFF: number of outputs, max 255, 0 means none
On start:
outputs are set to uknown state and high impedance
On each rising edge of a clock and reset being low
double (8-byte): positive value of TIME if reset is low otherwise -TIME
[inputs array ]: input bytes, each byte packs up to 8 inputs
outputs are defined by returning process
and process must return:
[output array]: output bytes, each byte packs up to 8 outputs
For example project please see: http://isotel.eu/mixedsim
MODIFICATIONS
9 November 2017 Uros Platse <uros@isotel.eu>
- Initial design, ready for use with projects
4 April 2018 Uros Platse <uros@isotel.eu>
- Tested and polished ready to be published
7 April 2018 Uros Platse <uros@isotel.eu>
Removed async reset and converted it to synchronous reset only.
Code cleanup.
30 September 2023 Brian Taylor
Modify the code for Windows VisualC and Mingw.
In VisualC, #pragma pack(...) compiler directives are needed for
the struct declarations using __attribute__(packed).
Use Microsoft CRT library functions _pipe, _read, _write, _spawn.
For Windows VisualC and Mingw the pipes use binary mode, and
named pipes or fifos are not supported.
14 October 2023 Brian Taylor
Use cm_message_send() to report errors, avoid exit(1) calls.
18 October 2023 Brian Taylor
Use cm_cexit() to halt simulation after fatal errors.
Cleanup (terminate) Windows child processes.
REFERENCED FILES
Inputs from and outputs to ARGS structure.
==============================================================================*/
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#if !defined(_MSC_VER)
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include <fcntl.h>
#define D_PROCESS_FORMAT_VERSION 0x01
#define DLEN(x) (uint8_t)( ((x)==0) ? 0 : (((x)-1)/8 + 1) )
#define DIN_SIZE_MAX 256 // also represents a theoretical maximum for 8-bit input length specifier
typedef unsigned char uint8_t;
typedef struct {
int pipe_to_child;
int pipe_from_child;
unsigned int error_count;
uint8_t N_din, N_dout; // number of inputs/outputs bytes
Digital_State_t dout_old[256]; // max possible storage to track output changes
} Process_t;
#if defined(_MSC_VER) || defined(__MINGW64__)
#include <io.h>
static int w_start(char *system_command, const char *const *argv, Process_t * process);
#endif
static int sendheader(Process_t * process, int N_din, int N_dout)
{
#if defined(_MSC_VER)
#pragma pack(push, 1)
struct header_s {
uint8_t version, N_din, N_dout;
} header = {D_PROCESS_FORMAT_VERSION, (uint8_t)N_din, (uint8_t)N_dout};
#pragma pack(pop)
#else
struct header_s {
uint8_t version, N_din, N_dout;
} __attribute__((packed)) header = {D_PROCESS_FORMAT_VERSION, (uint8_t)N_din, (uint8_t)N_dout};
#endif
if (N_din > 255 || N_dout > 255) {
cm_message_send("ERROR: d_process supports max 255 input and output and 255 output signals");
return 1;
}
#if defined(_MSC_VER) || defined(__MINGW64__)
if (_write(process->pipe_to_child, &header, sizeof(header)) == -1) {
#else
if (write(process->pipe_to_child, &header, sizeof(header)) == -1) {
#endif
cm_message_send("ERROR: d_process when sending header");
return 1;
}
// Wait for echo which must return the same header to ack transfer
#if defined(_MSC_VER) || defined(__MINGW64__)
if (_read(process->pipe_from_child, &header, sizeof(header)) != sizeof(header)) {
#else
if (read(process->pipe_from_child, &header, sizeof(header)) != sizeof(header)) {
#endif
cm_message_send("ERROR: d_process didn't respond to the header");
return 1;
}
if (header.version != D_PROCESS_FORMAT_VERSION) {
cm_message_printf("ERROR: d_process returned invalid version: %d", header.version);
return 1;
}
if (header.N_din != N_din || header.N_dout != N_dout) {
cm_message_printf("ERROR: d_process I/O mismatch: in %d vs. returned %d, out %d vs. returned %d",
N_din, header.N_din, N_dout, header.N_dout);
return 1;
}
process->N_din = (uint8_t)DLEN(N_din);
process->N_dout = (uint8_t)DLEN(N_dout);
return 0;
}
static int dprocess_exchangedata(Process_t * process, double time, uint8_t din[], uint8_t dout[])
{
#if defined(_MSC_VER)
#pragma pack(push, 1)
typedef struct {
double time;
uint8_t din[DIN_SIZE_MAX];
} packet_t;
#pragma pack(pop)
#else
typedef struct {
double time;
uint8_t din[DIN_SIZE_MAX];
} __attribute__((packed)) packet_t;
#endif
int wlen = 0;
packet_t packet;
packet.time = time;
if (process->N_din > 0) {
memcpy(packet.din, din, process->N_din);
} else {
packet.din[0] = 0;
}
#if defined(_MSC_VER) || defined(__MINGW64__)
wlen = _write(process->pipe_to_child, &packet, sizeof(double) + process->N_din);
#else
wlen = write(process->pipe_to_child, &packet, sizeof(double) + process->N_din);
#endif
if (wlen == -1) {
cm_message_send("ERROR: d_process when writing exchange data");
return 1;
}
#if defined(_MSC_VER) || defined(__MINGW64__)
if (_read(process->pipe_from_child, dout, process->N_dout) != process->N_dout) {
#else
if (read(process->pipe_from_child, dout, process->N_dout) != process->N_dout) {
#endif
cm_message_printf(
"ERROR: d_process received invalid dout count, expected %d",
process->N_dout);
return 1;
}
return 0;
}
#if !defined(_MSC_VER) && !defined(__MINGW64__)
static int start(char *system_command, char * c_argv[], Process_t * process)
{
int pipe_to_child[2];
int pipe_from_child[2];
int pid;
size_t syscmd_len = 0;
if (system_command) {
syscmd_len = strlen(system_command);
}
if (!system_command || syscmd_len == 0) {
cm_message_send("ERROR: d_process process_file argument is not given");
return 1;
}
if (system_command[syscmd_len-1] == '|') {
char *filename_in = NULL, *filename_out = NULL;
filename_in = (char *) malloc(syscmd_len + 5);
filename_out = (char *) malloc(syscmd_len + 5);
filename_in[0] = '\0';
filename_out[0] = '\0';
strncpy(filename_in, system_command, syscmd_len-1);
strcpy(&filename_in[syscmd_len-1], "_in");
strncpy(filename_out, system_command, syscmd_len-1);
strcpy(&filename_out[syscmd_len-1], "_out");
if ((process->pipe_to_child = open(filename_in, O_WRONLY)) == -1) {
cm_message_send("ERROR: d_process failed to open in fifo");
return 1;
}
if ((process->pipe_from_child = open(filename_out, O_RDONLY)) == -1) {
cm_message_send("ERROR: d_process failed to open out fifo");
return 1;
}
if (filename_in) free(filename_in);
if (filename_out) free(filename_out);
}
else {
if (pipe(pipe_to_child) || pipe(pipe_from_child) || (pid=fork()) ==-1) {
cm_message_send("ERROR: d_process cannot create pipes and fork");
return 1;
}
if (pid == 0) {
dup2(pipe_to_child[0],0);
dup2(pipe_from_child[1],1);
close(pipe_to_child[1]);
close(pipe_from_child[0]);
if (execv(system_command, c_argv) == -1) {
fprintf(stderr,
"\nERROR: d_process failed to fork or start process %s\n",
system_command);
exit(1);
}
}
else {
process->pipe_to_child = pipe_to_child[1];
process->pipe_from_child = pipe_from_child[0];
close(pipe_to_child[0]);
close(pipe_from_child[1]);
}
}
return 0;
}
#endif
static void cm_d_process_callback(ARGS, Mif_Callback_Reason_t reason)
{
switch (reason) {
case MIF_CB_DESTROY: {
Process_t *proc = STATIC_VAR(process);
if (proc) {
free(proc);
STATIC_VAR(process) = NULL;
}
break;
}
}
}
void cm_d_process(ARGS)
{
int i; /* generic loop counter index */
int err;
Digital_State_t *reset, /* storage for reset value */
*reset_old; /* previous reset value */
Digital_State_t *clk, /* storage for clock value */
*clk_old; /* previous clock value */
Process_t *local_process; /* Structure containing process vars */
if (INIT) {
#define C_ARGV_SIZE 1024
char * c_argv[C_ARGV_SIZE];
int c_argc = 1;
cm_event_alloc(0,sizeof(Digital_State_t));
cm_event_alloc(1,sizeof(Digital_State_t));
clk = clk_old = (Digital_State_t *) cm_event_get_ptr(0,0);
reset = reset_old = (Digital_State_t *) cm_event_get_ptr(1,0);
STATIC_VAR(process) = calloc(1, sizeof(Process_t));
local_process = STATIC_VAR(process);
CALLBACK = cm_d_process_callback;
if (!PARAM_NULL(process_params)) {
int upper_limit;
if (PARAM_SIZE(process_params) > (C_ARGV_SIZE - 2)) {
upper_limit = C_ARGV_SIZE - 2;
} else {
upper_limit = PARAM_SIZE(process_params);
}
for (i=0; i<upper_limit; i++) {
c_argv[c_argc++] = PARAM(process_params[i]);
}
}
c_argv[0] = PARAM(process_file);
c_argv[c_argc] = NULL;
#undef C_ARGV_SIZE
#if defined(_MSC_VER) || defined(__MINGW64__)
err = w_start(c_argv[0], (const char *const *)c_argv, local_process);
#else
err = start(c_argv[0], c_argv, local_process);
#endif
if (err) {
local_process->error_count++;
return;
}
err = sendheader(local_process, PORT_SIZE(in), PORT_SIZE(out));
if (err) {
local_process->error_count++;
return;
}
for (i=0; i<PORT_SIZE(in); i++) {
LOAD(in[i]) = PARAM(input_load);
}
LOAD(clk) = PARAM(clk_load);
if ( !PORT_NULL(reset) ) {
LOAD(reset) = PARAM(reset_load);
}
}
else {
local_process = STATIC_VAR(process);
if (local_process->error_count > 0) {
cm_cexit(1);
return;
}
clk = (Digital_State_t *) cm_event_get_ptr(0,0);
clk_old = (Digital_State_t *) cm_event_get_ptr(0,1);
reset = (Digital_State_t *) cm_event_get_ptr(1,0);
reset_old = (Digital_State_t *) cm_event_get_ptr(1,1);
}
if ( 0.0 == TIME ) { /****** DC analysis...output w/o delays ******/
for (i=0; i<PORT_SIZE(out); i++) {
local_process->dout_old[i] = UNKNOWN;
OUTPUT_STATE(out[i]) = UNKNOWN;
OUTPUT_STRENGTH(out[i]) = HI_IMPEDANCE;
}
}
else { /****** Transient Analysis ******/
*clk = INPUT_STATE(clk);
if ( PORT_NULL(reset) ) {
*reset = *reset_old = ZERO;
}
else {
*reset = INPUT_STATE(reset);
}
if (*clk != *clk_old && ONE == *clk) {
uint8_t *dout = NULL, *din = NULL;
uint8_t b;
dout = (uint8_t *)malloc(local_process->N_dout * sizeof(uint8_t));
if (local_process->N_din > 0) {
din = (uint8_t *)malloc(local_process->N_din * sizeof(uint8_t));
memset(din, 0, local_process->N_din);
}
for (i=0; i<PORT_SIZE(in); i++) {
switch(INPUT_STATE(in[i])) {
case ZERO: b = 0; break;
case ONE: b = 1; break;
#if defined(_MSC_VER) || defined(__MINGW64__)
default: b = rand() & 1; break;
#else
default: b = random() & 1; break;
#endif
}
din[i >> 3] |= (uint8_t)(b << (i & 7));
}
(void) dprocess_exchangedata(local_process, (ONE != *reset) ? TIME : -TIME, din, dout);
for (i=0; i<PORT_SIZE(out); i++) {
Digital_State_t new_state = ((dout[i >> 3] >> (i & 7)) & 0x01) ? ONE : ZERO;
if (new_state != local_process->dout_old[i]) {
OUTPUT_STATE(out[i]) = new_state;
OUTPUT_STRENGTH(out[i]) = STRONG;
OUTPUT_DELAY(out[i]) = PARAM(clk_delay);
local_process->dout_old[i] = new_state;
}
else {
OUTPUT_CHANGED(out[i]) = FALSE;
}
}
if (din) free(din);
if (dout) free(dout);
}
else {
for (i=0; i<PORT_SIZE(out); i++) {
OUTPUT_CHANGED(out[i]) = FALSE;
}
}
}
}
#if defined(_MSC_VER) || defined(__MINGW64__)
#include <process.h>
#include <io.h>
static int w_start(char *system_command, const char *const *argv, Process_t * process)
{
int pipe_to_child[2];
int pipe_from_child[2];
intptr_t pid = 0;
int mode = _O_BINARY;
size_t syscmd_len = 0;
if (system_command) {
syscmd_len = strlen(system_command);
}
if (!system_command || syscmd_len == 0) {
cm_message_send("ERROR: d_process process_file argument is not given");
return 1;
}
if (system_command[syscmd_len-1] == '|') {
cm_message_send("ERROR: d_process named pipe/fifo not supported");
return 1;
}
if (_pipe(pipe_to_child, 1024, mode) == -1) {
cm_message_send("ERROR: d_process failed to open pipe_to_child");
return 1;
}
if (_pipe(pipe_from_child, 1024, mode) == -1) {
cm_message_send("ERROR: d_process failed to open pipe_from_child");
return 1;
}
_dup2(pipe_to_child[0],0);
_dup2(pipe_from_child[1],1);
_close(pipe_to_child[0]);
_close(pipe_from_child[1]);
_flushall();
pid = _spawnvp(_P_NOWAIT, system_command, argv);
if (pid == -1) {
cm_message_printf("ERROR: d_process failed to spawn %s", system_command);
return 1;
}
process->pipe_to_child = pipe_to_child[1];
process->pipe_from_child = pipe_from_child[0];
return 0;
}
#endif