Skip to content

Commit

Permalink
Experimental support for Xyce co-simulation (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
sgherbst authored Jan 18, 2024
1 parent dd5a7b1 commit 9b06045
Show file tree
Hide file tree
Showing 14 changed files with 673 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ compile_flags.txt
results.xml
obj_dir
.pytest_cache
*.mt0

# queues
queue-*
Expand Down
17 changes: 17 additions & 0 deletions examples/xyce/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (c) 2024 Zero ASIC Corporation
# This code is licensed under Apache License 2.0 (see LICENSE for details)

.PHONY: verilator
verilator:
./test.py --tool verilator

.PHONY: icarus
icarus:
./test.py --tool icarus

.PHONY: clean
clean:
rm -f queue-* *.q
rm -f *.mt0
rm -f *.vcd *.fst *.fst.hier
rm -rf obj_dir build
18 changes: 18 additions & 0 deletions examples/xyce/rc.cir
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
RC circuit

* Copyright (c) 2024 Zero ASIC Corporation
* This code is licensed under Apache License 2.0 (see LICENSE for details)

* voltage input
YDAC DAC0 in 0 simpleDAC
.model simpleDAC DAC (tr=5e-9 tf=5e-9)

Rin in out 10k
Cout out 0 10p

* voltage output
.measure tran ADC0 EQN V(out)

.TRAN 0 1

.END
34 changes: 34 additions & 0 deletions examples/xyce/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env python3

# Example illustrating mixed-signal simulation

# Copyright (c) 2024 Zero ASIC Corporation
# This code is licensed under Apache License 2.0 (see LICENSE for details)

from argparse import ArgumentParser
from switchboard import SbDut


def main(fast=False, period=10e-9, tool='verilator'):
# build the simulator
dut = SbDut(tool=tool, default_main=True, xyce=True)
dut.input('testbench.sv')
dut.build(fast=fast)

# start chip simulation
chip = dut.simulate(period=period)

chip.wait()


if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument('--fast', action='store_true', help='Do not build'
' the simulator binary if it has already been built.')
parser.add_argument('--tool', default='verilator', choices=['icarus', 'verilator'],
help='Name of the simulator to use.')
parser.add_argument('--period', type=float, default=10e-9,
help='Period of the oversampling clock')
args = parser.parse_args()

main(fast=args.fast, period=args.period, tool=args.tool)
75 changes: 75 additions & 0 deletions examples/xyce/testbench.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2024 Zero ASIC Corporation
// This code is licensed under Apache License 2.0 (see LICENSE for details)

module testbench (
`ifdef VERILATOR
input clk
`endif
);
// clock

`ifndef VERILATOR
timeunit 1s;
timeprecision 1fs;

real period = 10e-9;
initial begin
$value$plusargs("period=%f", period);
end

reg clk;
always begin
clk = 1'b0;
#(0.5 * period);
clk = 1'b1;
#(0.5 * period);
end
`endif

xyce_intf xyce_intf_i ();

real in = 0.0;
real out = 0.0;

integer bits = 0;
integer count = 0;
always @(posedge clk) begin
if (count + 1 == 10) begin
count <= 0;
in = 1.0 - in;
if (bits + 1 == 10) begin
$finish;
end else begin
bits <= bits + 1;
end
end else begin
count <= count + 1;
end
end

always @(in) begin
xyce_intf_i.put("DAC0", in);
end

always @(clk) begin
xyce_intf_i.get("ADC0", out);
end

// Initialize

initial begin
/* verilator lint_off IGNOREDRETURN */
xyce_intf_i.init("rc.cir");
/* verilator lint_on IGNOREDRETURN */
end

// Waveforms

initial begin
if ($test$plusargs("trace")) begin
$dumpfile("testbench.vcd");
$dumpvars(0, testbench);
end
end

endmodule
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from setuptools import setup, find_packages
from pybind11.setup_helpers import Pybind11Extension, build_ext

__version__ = "0.0.32"
__version__ = "0.0.33"

#################################################################################
# parse_reqs, long_desc from https://github.com/siliconcompiler/siliconcompiler #
Expand Down
90 changes: 90 additions & 0 deletions switchboard/cpp/xyce.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) 2024 Zero ASIC Corporation
// This code is licensed under Apache License 2.0 (see LICENSE for details)

#include <map>
#include <string>
#include <vector>

#include <stdio.h>
#include <stdlib.h>

#include <N_CIR_XyceCInterface.h>

class XyceIntf {
public:
XyceIntf() {}

~XyceIntf() {
if (m_opened) {
xyce_close(m_xyceObj);
}

if (m_xyceObj) {
free(m_xyceObj);
}
}

void init(std::string file) {
// pointer to N_CIR_Xyce object
m_xyceObj = (void**)malloc(sizeof(void* [1]));

// xyce command
char* argList[] = {(char*)("Xyce"), (char*)("-quiet"), (char*)file.c_str()};
int argc = sizeof(argList) / sizeof(argList[0]);
char** argv = argList;

// Open N_CIR_Xyce object
xyce_open(m_xyceObj);
m_opened = true;

// Initialize N_CIR_Xyce object
xyce_initialize(m_xyceObj, argc, argv);
m_initialized = true;

// Simulate for a small amount of time
xyce_simulateUntil(m_xyceObj, 1e-10, &m_simTime);
}

void put(std::string name, double time, double value) {
if (!m_time.count(name)) {
m_time[name] = std::vector<double>();
}

m_time[name].push_back(time);

if (!m_value.count(name)) {
m_value[name] = std::vector<double>();
}

m_value[name].push_back(value);

// TODO: prune old values for higher performance?

if (m_initialized) {
std::string fullName = "YDAC!" + name;

xyce_updateTimeVoltagePairs(m_xyceObj, (char*)fullName.c_str(), m_time[name].size(),
m_time[name].data(), m_value[name].data());
}
}

void get(std::string name, double time, double* value) {
if (m_initialized) {
// advance simulation if necessary
if (time > m_simTime) {
int status = xyce_simulateUntil(m_xyceObj, time, &m_simTime);
}

// read out the value
xyce_obtainResponse(m_xyceObj, (char*)name.c_str(), value);
}
}

private:
void** m_xyceObj;
double m_simTime;
bool m_opened;
bool m_initialized;
std::map<std::string, std::vector<double>> m_time;
std::map<std::string, std::vector<double>> m_value;
};
36 changes: 36 additions & 0 deletions switchboard/dpi/xyce_dpi.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2024 Zero ASIC Corporation
// This code is licensed under Apache License 2.0 (see LICENSE for details)

#include "svdpi.h"

#include <string>

#include <stdio.h>
#include <stdlib.h>

#include "xyce.hpp"

// function definitions
#ifdef __cplusplus
extern "C" {
#endif
extern void pi_sb_xyce_init(char* file);
extern void pi_sb_xyce_put(char* name, double time, double value);
extern void pi_sb_xyce_get(char* name, double time, double* val);
#ifdef __cplusplus
}
#endif

XyceIntf x;

void pi_sb_xyce_init(char* file) {
x.init(std::string(file));
}

void pi_sb_xyce_put(char* name, double time, double value) {
x.put(std::string(name), time, value);
}

void pi_sb_xyce_get(char* name, double time, double* value) {
x.get(std::string(name), time, value);
}
40 changes: 31 additions & 9 deletions switchboard/icarus.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

# TODO: replace with SiliconCompiler functionality

from typing import Union
from typing import Union, List
from pathlib import Path

from .util import plusargs_to_args, binary_run
Expand All @@ -17,15 +17,32 @@ def run(command: list, cwd: str = None) -> str:
return check_output(command, cwd=cwd, stderr=STDOUT).decode()


def icarus_build_vpi(cwd: str = None) -> str:
def icarus_build_vpi(
cwd: str = None,
name: str = 'switchboard',
cincludes: List[str] = None,
ldflags: List[str] = None
) -> str:
if cincludes is None:
cincludes = []

if ldflags is None:
ldflags = []

sbdir = sb_path()
vpi_cc = sbdir / 'vpi/switchboard_vpi.cc'
vpi_flags = f'-I{sbdir}/cpp'
return run(['iverilog-vpi', vpi_flags, vpi_cc], cwd)
incdirs = cincludes + [f'{sbdir}/cpp']

cmd = []
cmd += ['iverilog-vpi']
cmd += [f'-I{incdir}' for incdir in incdirs]
cmd += ldflags
cmd += [str(sbdir / 'vpi' / f'{name}_vpi.cc')]

return run(cmd, cwd)

def icarus_find_vpi(cwd: Union[str, Path] = None) -> Path:
path = Path('switchboard_vpi.vpi')

def icarus_find_vpi(cwd: Union[str, Path] = None, name: str = 'switchboard') -> Path:
path = Path(f'{name}_vpi.vpi')

if cwd is not None:
path = Path(cwd) / path
Expand All @@ -41,13 +58,18 @@ def icarus_run(vvp, plusargs=None, modules=None, extra_args=None, **kwargs):

args += ['-n']

mdirs = set()

if modules is not None:
if not isinstance(modules, list):
raise TypeError('modules must be a list')
for module in modules:
args += [f'-M{Path(module.resolve().parent)}']
mdirs.add(str(Path(module.resolve().parent)))
args += ['-m', Path(module).stem]

for mdir in mdirs:
args += [f'-M{mdir}']

args += [vvp]
args += plusargs_to_args(plusargs)

Expand All @@ -56,4 +78,4 @@ def icarus_run(vvp, plusargs=None, modules=None, extra_args=None, **kwargs):
raise TypeError('extra_args must be a list')
args += extra_args

return binary_run(bin='vvp', args=args, **kwargs)
return binary_run(bin='vvp', args=args, **kwargs, print_command=True)
Loading

0 comments on commit 9b06045

Please sign in to comment.