-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Arc] Introduce a Python simulation script generator for arcilator #7942
Comments
One possible direction is to implement industry standard VPI. By doing so we should be able to execute arc model from frameworks like cocotb relatively easily. |
Hi,
To me, it might be what acrilator is expected to implement, i.e. emitting some IR that calls callback functions in certain conditions.
This seems too heavy and beyond what I want to do. If we want the acrilator to be one of the backends in cocotb, we might need to modify arcilator itself. However, my idea mainly focuses on forking the C++ header generator, providing a way for Python code that wants to call the arcilator's artifacts as well. The generated Python code will execute the produced IR with llvmlite in a single thread like the existing C++ arcilator simulation code. So cocotb's coroutine and simulation framework seems having limited usage in this situation. Thanks for your suggestions! |
Hi, My idea may not be clear enough. Let me present an example. A multiplexer: module mux_2to1(
input [2:0] a,
input [3:0] b,
input [1:0] sel,
output [2:0] out
);
assign out = sel[0] ? b[3:1] : (sel[1] ? a : 3'd2);
endmodule We can generate the python testbench like this: from enum import Enum, auto
import ctypes
import llvmlite
llvmlite.opaque_pointers_enabled = True
import llvmlite.binding as llvm
class SignalType(Enum):
INPUT = auto()
OUTPUT = auto()
REGISTER = auto()
MEMORY = auto()
WIRE = auto()
class Signal:
def __init__(self, name: str, offset: int, num_bits: int, signal_type: SignalType):
self.name = name
self.offset = offset
self.num_bits = num_bits
self.type = signal_type
class mux_2to1Layout:
name = "mux_2to1"
num_states = 4
num_state_bytes = 4
io = [Signal("a", 0, 3, SignalType.INPUT),
Signal("b", 1, 4, SignalType.INPUT),
Signal("sel", 2, 2, SignalType.INPUT),
Signal("out", 3, 3, SignalType.OUTPUT)]
class mux_2to1View:
class __io_in_op_uint8:
def __init__(self, ptr, offset):
self.__ptr = ptr
self.__offset = offset
def poke(self, data: int):
self.__ptr[self.__offset] = data
def peek(self):
return self.__ptr[self.__offset]
class __io_out_op_uint8:
def __init__(self, ptr, offset):
self.__ptr = ptr
self.__offset = offset
def poke(self, data: int):
raise TypeError("This port is read-only")
def peek(self):
return self.__ptr[self.__offset]
def __init__(self):
self.storage = ctypes.create_string_buffer(4)
self.a = self.__io_in_op_uint8(ctypes.cast(ctypes.pointer(self.storage),
ctypes.POINTER(ctypes.c_uint8)), 0)
self.b = self.__io_in_op_uint8(ctypes.cast(ctypes.pointer(self.storage),
ctypes.POINTER(ctypes.c_uint8)), 1)
self.sel = self.__io_in_op_uint8(ctypes.cast(ctypes.pointer(self.storage),
ctypes.POINTER(ctypes.c_uint8)), 2)
self.out = self.__io_out_op_uint8(ctypes.cast(ctypes.pointer(self.storage),
ctypes.POINTER(ctypes.c_uint8)), 3)
class mux_2to1:
llvm_ir = """
; ModuleID = 'LLVMDialectModule'
source_filename = "LLVMDialectModule"
define void @mux_2to1_eval(ptr %0) {
%2 = getelementptr i8, ptr %0, i32 2
%3 = load i2, ptr %2, align 1
%4 = getelementptr i8, ptr %0, i32 1
%5 = load i4, ptr %4, align 1
%6 = load i3, ptr %0, align 1
%7 = trunc i2 %3 to i1
%8 = lshr i4 %5, 1
%9 = trunc i4 %8 to i3
%10 = lshr i2 %3, 1
%11 = trunc i2 %10 to i1
%12 = select i1 %11, i3 %6, i3 2
%13 = select i1 %7, i3 %9, i3 %12
%14 = getelementptr i8, ptr %0, i32 3
store i3 %13, ptr %14, align 1
ret void
}
"""
def __init__(self):
if int(llvmlite.__version__.split('.')[1]) < 44:
raise ImportError("The version of llvmlite must greater than or equal to 0.44")
self.view = mux_2to1View()
llvm.initialize()
llvm.initialize_native_target()
llvm.initialize_native_asmprinter()
def create_execution_engine():
"""
Create an ExecutionEngine suitable for JIT code generation on
the host CPU. The engine is reusable for an arbitrary number of
modules.
"""
# Create a target machine representing the host
target = llvm.Target.from_default_triple()
target_machine = target.create_target_machine()
# And an execution engine with an empty backing module
backing_mod = llvm.parse_assembly("")
eng = llvm.create_mcjit_compiler(backing_mod, target_machine)
return eng
def compile_ir(engine_, llvm_ir_):
"""
Compile the LLVM IR string with the given engine.
The compiled module object is returned.
"""
# Create a LLVM module object from the IR
mod_ = llvm.parse_assembly(llvm_ir_)
mod_.verify()
# Now add the module and make sure it is ready for execution
engine_.add_module(mod_)
engine_.finalize_object()
engine_.run_static_constructors()
return mod_
self.engine = create_execution_engine()
self.mod = compile_ir(self.engine, self.llvm_ir)
# Look up the function pointer (a Python int)
self.func_ptr = self.engine.get_function_address("mux_2to1_eval")
# Run the function via ctypes
self.__eval_func = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_uint8))(self.func_ptr)
self.eval_param = ctypes.cast(ctypes.byref(self.view.storage, 0),
ctypes.POINTER(ctypes.c_uint8))
def eval(self):
self.__eval_func(self.eval_param)
if __name__ == "__main__":
dut = mux_2to1()
dut.view.a.poke(4)
dut.view.b.poke(5)
dut.view.sel.poke(2)
dut.eval()
print(dut.view.out.peek())
assert dut.view.out.peek() == 4
dut.view.a.poke(5)
dut.view.b.poke(7)
dut.view.sel.poke(1)
dut.eval()
print(dut.view.out.peek())
assert dut.view.out.peek() == 3
dut.view.a.poke(5)
dut.view.b.poke(7)
dut.view.sel.poke(0)
dut.eval()
print(dut.view.out.peek())
assert dut.view.out.peek() == 2 The output of arcilator is hardcoded in this python module, and can be used by other python code by |
Hi,
In #7929 , I refactored the C++ header generator Python script for the C++ code that wants to call the LLVM IR produced by
arcilator
. The PR mainly refactors its structure, paving the way for future modularization.The reason for making it modular is that the script contains some core functions that can serve as a reusable library for other scripts. My proposals are as follows:
Splitting the C++ header generator script
Introducing a Python simulation script generator
llvmlite
to execute the LLVM IR generated byarcilator
.poke
andpeek
methods. The generated scripts is like the generated C++ header and is used by other testbenches written in Python.Before proceeding, I would greatly appreciate your feedback on this proposal.
Thank you for your time and guidance!
Regards
The text was updated successfully, but these errors were encountered: