diff --git a/.gitignore b/.gitignore index 9545ed7..a3e8109 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ .DS_Store msdsl.egg-info __pycache__ -build \ No newline at end of file +tests/*/build \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d304a5c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,68 @@ +matrix: + include: + - os: linux + dist: bionic + addons: + apt: + packages: + - g++-7 + - libgmp-dev + - libmpfr-dev + - libmpc-dev + - iverilog + env: + - CC=gcc-7 + - CXX=g++-7 + - os: osx + osx_image: xcode10.2 + addons: + homebrew: + packages: + - icarus-verilog + +install: +# Get conda installer +- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh; + else + wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; + fi + +# Install conda (https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/use-conda-with-travis-ci.html) +- bash miniconda.sh -b -p $HOME/miniconda +- source "$HOME/miniconda/etc/profile.d/conda.sh" +- hash -r +- conda config --set always_yes yes --set changeps1 no +- conda update -q conda +- conda info -a + +# create and activate conda environment +- conda create -q -n test-env python=3.7.3 +- conda activate test-env + +# install pip so that we can install other packages +- conda install pip + +# speed up the build process until it's fixed at z3 +# see https://github.com/Z3Prover/z3/issues/2800 +- | + if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + pip install https://github.com/Z3Prover/z3/releases/download/Nightly/z3_solver-4.8.8.0-py2.py3-none-macosx_10_14_x86_64.whl + fi + +# install various python dependencies +- pip install pytest mantle fault pytest-cov + +# install svreal +- git clone https://github.com/sgherbst/svreal.git +- cd svreal +- git checkout v1 +- pip install -e . +- cd .. + +# install msdsl +- pip install -e . + +script: +- pytest --cov-report=xml --cov=msdsl tests/ -v -r s +- bash <(curl -s https://codecov.io/bash) diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1a0ed77 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include msdsl/msdsl.sv diff --git a/README.md b/README.md index 213f83a..7a8ecf5 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,14 @@ -**NOTE**: Please check out the **v1** branch for the latest work. These updates will be merged into the master branch soon, coinciding with a release on PyPI. +# msdsl +[![Travis Status](https://travis-ci.com/sgherbst/msdsl.svg?branch=master)](https://travis-ci.com/sgherbst/msdsl) +[![Code Coverage](https://codecov.io/gh/sgherbst/msdsl/branch/master/graph/badge.svg)](https://codecov.io/gh/sgherbst/msdsl) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ---- - -# Introduction - -**msdsl** is a Python3 package for generating synthesizable real number models (RNMs) from analog circuits. +**msdsl** is a tool for generating synthesizable real number models (RNMs) from analog circuits for use in FPGA emulation. # Installation -1. Open a terminal, and note the current directory, since the **pip** command below will clone some code from GitHub and place it in a subdirectory called **src**. If you prefer to place the cloned code in a different directory, you can specify that by providing the **--src** flag to **pip**. -2. Install **msdsl** with **pip**: ```shell -> pip install -e git+https://github.com/sgherbst/msdsl.git#egg=msdsl +> pip install msdsl ``` If you get a permissions error when running the **pip** command, you can try adding the **--user** flag. This will cause **pip** to install packages in your user directory rather than to a system-wide location. diff --git a/include/msdsl.sv b/include/msdsl.sv deleted file mode 100644 index 81c5881..0000000 --- a/include/msdsl.sv +++ /dev/null @@ -1,224 +0,0 @@ -// Steven Herbst -// sherbst@stanford.edu - -// Analog modeling library - -`ifndef __MSDSL_SV__ -`define __MSDSL_SV__ - - `include "real.sv" - - // Convenience functions - - `define DATA_TYPE_DIGITAL(width_expr) \ - logic [((width_expr)-1):0] - - // Add quotes to a DEFINE parameter - `define ADD_QUOTES_TO_MACRO(macro) `"macro`" - - // Memory - - `define MEM_INTO_ANALOG(in_name, out_name, cke_name, clk_name, rst_name, init_expr) \ - mem_analog #( \ - .init(init_expr), \ - `PASS_REAL(in, in_name), \ - `PASS_REAL(out, out_name) \ - ) mem_analog_``out_name``_i ( \ - .in(in_name), \ - .out(out_name), \ - .clk(clk_name), \ - .rst(rst_name), \ - .cke(cke_name) \ - ) - - `define MEM_ANALOG(in_name, out_name, cke_name, clk_name, rst_name, init_expr) \ - `COPY_FORMAT_REAL(in_name, out_name); \ - `MEM_INTO_ANALOG(in_name, out_name, cke_name, clk_name, rst_name, init_expr) - - `define MEM_INTO_DIGITAL(in_name, out_name, cke_name, clk_name, rst_name, init_expr, width_expr) \ - mem_digital #( \ - .init(init_expr), \ - .width(width_expr) \ - ) mem_digital_``out_name``_i ( \ - .in(in_name), \ - .out(out_name), \ - .clk(clk_name), \ - .rst(rst_name), \ - .cke(cke_name) \ - ) - - `define MEM_DIGITAL(in_name, out_name, cke_name, clk_name, rst_name, init_expr, width_expr) \ - `DATA_TYPE_DIGITAL(width_expr) out_name; \ - `MEM_INTO_DIGITAL(in_name, out_name, cke_name, clk_name, rst_name, init_expr, width_expr) - - // Probing waveforms - - `define DUMP_VAR(in_name) \ - initial begin \ - #0; \ - $dumpvars(0, in_name); \ - end - - `define PROBE_NAME(in_name) \ - ``in_name``_probe - - `define MARK_DEBUG \ - mark_debug = `"true`" - - `define MARK_TIME \ - time_signal = `"true`" - - `define MARK_RESET \ - reset_signal = `"true`" - - `define MARK_ANALOG \ - analog_signal = `"true`" - - `define MARK_DIGITAL \ - digital_signal = `"true`" - - `define MARK_EXPONENT_REAL(in_name) \ - fixed_point_exponent = `EXPONENT_PARAM_REAL(in_name) - - `define PROBE_ANALOG_CTRL(in_name, in_name_abspath) \ - `ifdef SIMULATION_MODE_MSDSL \ - real `PROBE_NAME(in_name); \ - `DUMP_VAR(`PROBE_NAME(in_name)) \ - assign `PROBE_NAME(in_name) = `TO_REAL_CTRL(in_name, in_name_abspath) \ - `else \ - (* `MARK_DEBUG, `MARK_ANALOG, `MARK_EXPONENT_REAL(in_name) *) `GET_FORMAT_REAL(in_name) `PROBE_NAME(in_name); \ - assign `PROBE_NAME(in_name) = in_name \ - `endif - - `define TO_REAL_CTRL(name, abs_name) \ - `ifdef FLOAT_REAL \ - name \ - `else \ - (1.0*name) * `POW2_MATH(`EXPONENT_PARAM_REAL(abs_name)) \ - `endif - - `define PROBE_ANALOG (in_name) \ - `ifdef SIMULATION_MODE_MSDSL \ - real `PROBE_NAME(in_name); \ - `DUMP_VAR(`PROBE_NAME(in_name)) \ - assign `PROBE_NAME(in_name) = `TO_REAL(in_name) \ - `else \ - (* `MARK_DEBUG, `MARK_ANALOG, `MARK_EXPONENT_REAL(in_name) *) `GET_FORMAT_REAL(in_name) `PROBE_NAME(in_name); \ - assign `PROBE_NAME(in_name) = in_name \ - `endif - - `define PROBE_TIME(in_name) \ - `ifdef SIMULATION_MODE_MSDSL \ - real `PROBE_NAME(in_name); \ - `DUMP_VAR(`PROBE_NAME(in_name)) \ - assign `PROBE_NAME(in_name) = `TO_REAL(in_name) \ - `else \ - (* `MARK_DEBUG, `MARK_TIME, `MARK_EXPONENT_REAL(in_name) *) `GET_FORMAT_REAL(in_name) `PROBE_NAME(in_name); \ - assign `PROBE_NAME(in_name) = in_name \ - `endif - - `define PROBE_DIGITAL(in_name, width_expr) \ - `ifdef SIMULATION_MODE_MSDSL \ - `DATA_TYPE_DIGITAL(width_expr) `PROBE_NAME(in_name); \ - `DUMP_VAR(`PROBE_NAME(in_name)) \ - assign `PROBE_NAME(in_name) = in_name \ - `else \ - (* `MARK_DEBUG, `MARK_DIGITAL *) `DATA_TYPE_DIGITAL(width_expr) `PROBE_NAME(in_name); \ - assign `PROBE_NAME(in_name) = in_name \ - `endif - - `define MAKE_RESET_PROBE \ - `ifdef SIMULATION_MODE_MSDSL \ - logic reset_probe; \ - `DUMP_VAR(reset_probe) \ - assign reset_probe = `RST_MSDSL \ - `else \ - (* `MARK_DEBUG, `MARK_RESET *) logic reset_probe; \ - assign reset_probe = `RST_MSDSL \ - `endif - - // Time management - // Note that a emu_time is wider than the default for fixed-point numbers - // The reason is that very high dynamic range is required. - // TODO: avoid using a hard-coded value for the emu_time width - - `define MAKE_TIME_PROBE \ - `MAKE_GENERIC_REAL(emu_time, 1.1*`TSTOP_MSDSL, 39); \ - `COPY_FORMAT_REAL(emu_time, emu_time_next); \ - `COPY_FORMAT_REAL(emu_time, emu_time_dt); \ - `ASSIGN_CONST_REAL(`DT_MSDSL, emu_time_dt); \ - `ADD_INTO_REAL(emu_time, emu_time_dt, emu_time_next); \ - `MEM_INTO_ANALOG(emu_time_next, emu_time, 1'b1, `CLK_MSDSL, `RST_MSDSL, 0); \ - `PROBE_TIME(emu_time) - - // Decimation counter - - `define MAKE_DEC_PROBE \ - logic [(`DEC_BITS_MSDSL-1):0] emu_dec_cnt; \ - logic [(`DEC_BITS_MSDSL-1):0] emu_dec_nxt; \ - logic emu_dec_cmp; \ - assign emu_dec_cmp = (emu_dec_cnt == `DEC_THR_MSDSL) ? 1'b1 : 0; \ - assign emu_dec_nxt = (emu_dec_cmp == 1'b1) ? 'd0 : (emu_dec_cnt + 'd1); \ - `MEM_INTO_DIGITAL(emu_dec_nxt, emu_dec_cnt, 1'b1, `CLK_MSDSL, `RST_MSDSL, 'd0, `DEC_BITS_MSDSL); \ - `ifdef SIMULATION_MODE_MSDSL \ - logic emu_dec_cmp_probe; \ - `DUMP_VAR(emu_dec_cmp_probe) \ - assign emu_dec_cmp_probe = emu_dec_cmp \ - `else \ - (* `MARK_DEBUG, `MARK_DIGITAL *) logic emu_dec_cmp_probe; \ - assign emu_dec_cmp_probe = emu_dec_cmp \ - `endif - - // - - `define MAKE_EMU_CTRL_PROBES \ - `MAKE_RESET_PROBE; \ - `MAKE_TIME_PROBE; \ - `MAKE_DEC_PROBE - - // Other macros - - `define PWM_INTO(duty_expr, freq_expr, out_name) \ - `MAKE_CONST_REAL(`DT_MSDSL, dt_``out_name``); \ - pwm #( \ - .duty(duty_expr), \ - .freq(freq_expr), \ - `PASS_REAL(dt, dt_``out_name``) \ - ) pwm_``out_name``_i ( \ - .dt(dt_``out_name``), \ - .out(out_name), \ - .clk(`CLK_MSDSL), \ - .rst(`RST_MSDSL) \ - ) - - `define PWM(duty_expr, freq_expr, out_name) \ - logic out_name; \ - `PWM_INTO(duty_expr, freq_expr, out_name) - - `define EDGE_DET_INTO(in_name, out_name, active_expr, init_expr) \ - edge_det_msdsl #( \ - .init(init_expr), \ - .active(active_expr) \ - ) edge_det_msdsl_``out_name``_i ( \ - .in(in_name), \ - .out(out_name), \ - .clk(`CLK_MSDSL), \ - .rst(`RST_MSDSL) \ - ) - - `define EDGE_DET(in_name, out_name, active_expr, init_expr) \ - logic out_name; \ - `EDGE_DET_INTO(in_name, out_name, active_expr, init_expr) - - `define POSEDGE_INTO(in_name, out_name) \ - `EDGE_DET_INTO(in_name, out_name, 1, 0) - - `define POSEDGE(in_name, out_name) \ - `EDGE_DET(in_name, out_name, 1, 0) - - `define NEGEDGE_INTO(in_name, out_name) \ - `EDGE_DET_INTO(in_name, out_name, 0, 1) - - `define NEGEDGE(in_name, out_name) \ - `EDGE_DET(in_name, out_name, 0, 1) -`endif \ No newline at end of file diff --git a/msdsl/__init__.py b/msdsl/__init__.py index bfe9165..c403437 100644 --- a/msdsl/__init__.py +++ b/msdsl/__init__.py @@ -1,3 +1,4 @@ +from .files import get_msdsl_header from .expr.svreal import RangeOf from .expr.signals import AnalogSignal, DigitalOutput, DigitalInput, AnalogInput, AnalogOutput, DigitalSignal from .expr.simplify import distribute_mult @@ -5,4 +6,4 @@ from .generator.verilog import VerilogGenerator from .eqn.deriv import Deriv from .eqn.cases import eqn_case -from .expr.expr import to_real, to_sint, to_uint, min_op, max_op, sum_op \ No newline at end of file +from .expr.expr import to_real, to_sint, to_uint, min_op, max_op, sum_op diff --git a/msdsl/files.py b/msdsl/files.py new file mode 100644 index 0000000..baf8cd3 --- /dev/null +++ b/msdsl/files.py @@ -0,0 +1,6 @@ +from pathlib import Path + +PACK_DIR = Path(__file__).resolve().parent + +def get_msdsl_header(): + return PACK_DIR / 'msdsl.sv' \ No newline at end of file diff --git a/msdsl/generator/verilog.py b/msdsl/generator/verilog.py index d494d8c..7b48172 100644 --- a/msdsl/generator/verilog.py +++ b/msdsl/generator/verilog.py @@ -148,7 +148,7 @@ def make_mem(self, next_: Signal, curr: Signal, init: Number=0, clk: Signal=None # create memory for real number if isinstance(next_.format_, RealFormat) and isinstance(curr.format_, RealFormat): - self.macro_call('MEM_INTO_ANALOG', next_.name, curr.name, ce_name, clk_name, rst_name, str(init)) + self.macro_call('DFF_INTO_REAL', next_.name, curr.name, rst_name, clk_name, ce_name, str(init)) # create memory for integer elif (isinstance(next_.format_, SIntFormat) and isinstance(curr.format_, SIntFormat)) or \ (isinstance(next_.format_, UIntFormat) and isinstance(curr.format_, UIntFormat)): @@ -481,9 +481,7 @@ def init_file(self): self.writeln() # include required libraries - self.include('real.sv') - self.include('math.sv') - self.include('msdsl.sv') + self.include('svreal.sv') self.writeln() def include(self, file): diff --git a/msdsl/msdsl.sv b/msdsl/msdsl.sv new file mode 100644 index 0000000..a8d99b8 --- /dev/null +++ b/msdsl/msdsl.sv @@ -0,0 +1,349 @@ +// Steven Herbst +// sherbst@stanford.edu + +// Analog modeling library + +`ifndef __MSDSL_SV__ +`define __MSDSL_SV__ + +`include "svreal.sv" + +// Convenience functions + +`define DATA_TYPE_DIGITAL(width_expr) \ + logic [((width_expr)-1):0] + +// Add quotes to a DEFINE parameter +`define ADD_QUOTES_TO_MACRO(macro) `"macro`" + +// Memory + +`define MEM_INTO_DIGITAL(in_name, out_name, cke_name, clk_name, rst_name, init_expr, width_expr) \ + mem_digital #( \ + .init(init_expr), \ + .width(width_expr) \ + ) mem_digital_``out_name``_i ( \ + .in(in_name), \ + .out(out_name), \ + .clk(clk_name), \ + .rst(rst_name), \ + .cke(cke_name) \ + ) + +`define MEM_DIGITAL(in_name, out_name, cke_name, clk_name, rst_name, init_expr, width_expr) \ + `DATA_TYPE_DIGITAL(width_expr) out_name; \ + `MEM_INTO_DIGITAL(in_name, out_name, cke_name, clk_name, rst_name, init_expr, width_expr) + +// Probing waveforms + +`define DUMP_VAR(in_name) \ + initial begin \ + #0; \ + $dumpvars(0, in_name); \ + end + +`define PROBE_NAME(in_name) \ + ``in_name``_probe + +`define MARK_DEBUG \ + mark_debug = `"true`" + +`define MARK_TIME \ + time_signal = `"true`" + +`define MARK_RESET \ + reset_signal = `"true`" + +`define MARK_ANALOG \ + analog_signal = `"true`" + +`define MARK_DIGITAL \ + digital_signal = `"true`" + +`define MARK_EXPONENT_REAL(in_name) \ + fixed_point_exponent = `EXPONENT_PARAM_REAL(in_name) + +`define PROBE_ANALOG_CTRL(in_name, in_name_abspath) \ + `ifdef SIMULATION_MODE_MSDSL \ + real `PROBE_NAME(in_name); \ + `DUMP_VAR(`PROBE_NAME(in_name)) \ + assign `PROBE_NAME(in_name) = `TO_REAL_CTRL(in_name, in_name_abspath) \ + `else \ + (* `MARK_DEBUG, `MARK_ANALOG, `MARK_EXPONENT_REAL(in_name) *) `GET_FORMAT_REAL(in_name) `PROBE_NAME(in_name); \ + assign `PROBE_NAME(in_name) = in_name \ + `endif + + `define TO_REAL_CTRL(name, abs_name) \ + `ifdef FLOAT_REAL \ + name \ + `else \ + (1.0*name) * `POW2_MATH(`EXPONENT_PARAM_REAL(abs_name)) \ + `endif + +`define PROBE_ANALOG (in_name) \ + `ifdef SIMULATION_MODE_MSDSL \ + real `PROBE_NAME(in_name); \ + `DUMP_VAR(`PROBE_NAME(in_name)) \ + assign `PROBE_NAME(in_name) = `TO_REAL(in_name) \ + `else \ + (* `MARK_DEBUG, `MARK_ANALOG, `MARK_EXPONENT_REAL(in_name) *) `GET_FORMAT_REAL(in_name) `PROBE_NAME(in_name); \ + assign `PROBE_NAME(in_name) = in_name \ + `endif + +`define PROBE_TIME(in_name) \ + `ifdef SIMULATION_MODE_MSDSL \ + real `PROBE_NAME(in_name); \ + `DUMP_VAR(`PROBE_NAME(in_name)) \ + assign `PROBE_NAME(in_name) = `TO_REAL(in_name) \ + `else \ + (* `MARK_DEBUG, `MARK_TIME, `MARK_EXPONENT_REAL(in_name) *) `GET_FORMAT_REAL(in_name) `PROBE_NAME(in_name); \ + assign `PROBE_NAME(in_name) = in_name \ + `endif + +`define PROBE_DIGITAL(in_name, width_expr) \ + `ifdef SIMULATION_MODE_MSDSL \ + `DATA_TYPE_DIGITAL(width_expr) `PROBE_NAME(in_name); \ + `DUMP_VAR(`PROBE_NAME(in_name)) \ + assign `PROBE_NAME(in_name) = in_name \ + `else \ + (* `MARK_DEBUG, `MARK_DIGITAL *) `DATA_TYPE_DIGITAL(width_expr) `PROBE_NAME(in_name); \ + assign `PROBE_NAME(in_name) = in_name \ + `endif + +`define MAKE_RESET_PROBE \ + `ifdef SIMULATION_MODE_MSDSL \ + logic reset_probe; \ + `DUMP_VAR(reset_probe) \ + assign reset_probe = `RST_MSDSL \ + `else \ + (* `MARK_DEBUG, `MARK_RESET *) logic reset_probe; \ + assign reset_probe = `RST_MSDSL \ + `endif + +// Time management +// Note that a emu_time is wider than the default for fixed-point numbers +// The reason is that very high dynamic range is required. +// TODO: avoid using a hard-coded value for the emu_time width + +`define MAKE_TIME_PROBE \ + `MAKE_GENERIC_REAL(emu_time, 1.1*`TSTOP_MSDSL, 39); \ + `COPY_FORMAT_REAL(emu_time, emu_time_next); \ + `COPY_FORMAT_REAL(emu_time, emu_time_dt); \ + `ASSIGN_CONST_REAL(`DT_MSDSL, emu_time_dt); \ + `ADD_INTO_REAL(emu_time, emu_time_dt, emu_time_next); \ + `DFF_INTO_REAL(emu_time_next, emu_time, `RST_MSDSL, `CLK_MSDSL, 1'b1, 0.0); \ + `PROBE_TIME(emu_time) + +// Decimation counter + +`define MAKE_DEC_PROBE \ + logic [(`DEC_BITS_MSDSL-1):0] emu_dec_cnt; \ + logic [(`DEC_BITS_MSDSL-1):0] emu_dec_nxt; \ + logic emu_dec_cmp; \ + assign emu_dec_cmp = (emu_dec_cnt == `DEC_THR_MSDSL) ? 1'b1 : 0; \ + assign emu_dec_nxt = (emu_dec_cmp == 1'b1) ? 'd0 : (emu_dec_cnt + 'd1); \ + `MEM_INTO_DIGITAL(emu_dec_nxt, emu_dec_cnt, 1'b1, `CLK_MSDSL, `RST_MSDSL, 'd0, `DEC_BITS_MSDSL); \ + `ifdef SIMULATION_MODE_MSDSL \ + logic emu_dec_cmp_probe; \ + `DUMP_VAR(emu_dec_cmp_probe) \ + assign emu_dec_cmp_probe = emu_dec_cmp \ + `else \ + (* `MARK_DEBUG, `MARK_DIGITAL *) logic emu_dec_cmp_probe; \ + assign emu_dec_cmp_probe = emu_dec_cmp \ + `endif + +// + +`define MAKE_EMU_CTRL_PROBES \ + `MAKE_RESET_PROBE; \ + `MAKE_TIME_PROBE; \ + `MAKE_DEC_PROBE + +// Other macros + +`define PWM_INTO(duty_expr, freq_expr, out_name) \ + `MAKE_CONST_REAL(`DT_MSDSL, dt_``out_name``); \ + pwm #( \ + .duty(duty_expr), \ + .freq(freq_expr), \ + `PASS_REAL(dt, dt_``out_name``) \ + ) pwm_``out_name``_i ( \ + .dt(dt_``out_name``), \ + .out(out_name), \ + .clk(`CLK_MSDSL), \ + .rst(`RST_MSDSL) \ + ) + +`define PWM(duty_expr, freq_expr, out_name) \ + logic out_name; \ + `PWM_INTO(duty_expr, freq_expr, out_name) + +`define EDGE_DET_INTO(in_name, out_name, active_expr, init_expr) \ + edge_det_msdsl #( \ + .init(init_expr), \ + .active(active_expr) \ + ) edge_det_msdsl_``out_name``_i ( \ + .in(in_name), \ + .out(out_name), \ + .clk(`CLK_MSDSL), \ + .rst(`RST_MSDSL) \ + ) + +`define EDGE_DET(in_name, out_name, active_expr, init_expr) \ + logic out_name; \ + `EDGE_DET_INTO(in_name, out_name, active_expr, init_expr) + +`define POSEDGE_INTO(in_name, out_name) \ + `EDGE_DET_INTO(in_name, out_name, 1, 0) + +`define POSEDGE(in_name, out_name) \ + `EDGE_DET(in_name, out_name, 1, 0) + +`define NEGEDGE_INTO(in_name, out_name) \ + `EDGE_DET_INTO(in_name, out_name, 0, 1) + +`define NEGEDGE(in_name, out_name) \ + `EDGE_DET(in_name, out_name, 0, 1) + +///////////////////////////////////////////////// +// Module implementations are defined below... +///////////////////////////////////////////////// + +// Edge detector + +module edge_det_msdsl #( + parameter init = 0, + parameter active = 1 +) ( + input wire logic in, + output wire logic out, + input wire logic clk, + input wire logic rst +); + + // internal state + logic last; + always @(posedge clk) begin + if (rst == 1'b1) begin + last <= init; + end else begin + last <= in; + end + end + + // output assignment + assign out = ((last == (1-active)) && (in == active)) ? 1'b1 : 1'b0; + +endmodule + +// Generic DFF + +module mem_digital #( + parameter init = 0, + parameter width = 1 +) ( + input wire logic [(width-1):0] in, + output wire logic [(width-1):0] out, + input wire logic clk, + input wire logic rst, + input wire logic cke +); + + // internal state + + logic [(width-1):0] state; + + // create the memory unit + + always @(posedge clk) begin + if (rst == 1'b1) begin + state <= init; + end else if (cke == 1'b1) begin + state <= in; + end else begin + state <= state; + end + end + + // assign output + + assign out = state; + +endmodule + +// PWM model + +module pwm #( + parameter real duty = 0.5, + parameter real freq = 1e6, + `DECL_REAL(dt) +) ( + `INPUT_REAL(dt), + output wire logic out, + input wire logic clk, + input wire logic rst +); + + // store the time on and the time off + localparam real period = 1.0/freq; + localparam real time_on = (1.0*duty)*period; + localparam real time_off = (1.0-duty)*period; + + // constants + `MAKE_CONST_REAL(time_on, time_on_const); + `MAKE_CONST_REAL(time_off, time_off_const); + + // make a signal to contain acucmulated time + `MAKE_REAL(time_accum, period); + + // case 1: increment time + `ADD_REAL(time_accum, dt, incr_by_dt); + `COPY_FORMAT_REAL(time_accum, incr_by_dt_aligned); + `ASSIGN_REAL(incr_by_dt, incr_by_dt_aligned); + + // case 2: rewind by on time + `SUB_REAL(incr_by_dt, time_on_const, rewind_by_on); + `COPY_FORMAT_REAL(time_accum, rewind_by_on_aligned); + `ASSIGN_REAL(rewind_by_on, rewind_by_on_aligned); + + // case 3: rewind by off time + `SUB_REAL(incr_by_dt, time_off_const, rewind_by_off); + `COPY_FORMAT_REAL(time_accum, rewind_by_off_aligned); + `ASSIGN_REAL(rewind_by_off, rewind_by_off_aligned); + + // comparisons + `GT_REAL(time_accum, time_on_const, goto_off); + `GT_REAL(time_accum, time_off_const, goto_on); + + // create the memory unit + logic state; + + always @(posedge clk) begin + if (rst == 1'b1) begin + state <= 1'b0; + time_accum <= 0; + end else if (state == 1'b1) begin + if (goto_off == 1'b1) begin + state <= 1'b0; + time_accum <= rewind_by_on_aligned; + end else begin + state <= 1'b1; + time_accum <= incr_by_dt_aligned; + end + end else begin + if (goto_on == 1'b1) begin + state <= 1'b1; + time_accum <= rewind_by_off_aligned; + end else begin + state <= 1'b0; + time_accum <= incr_by_dt_aligned; + end + end + end + + // assign output + assign out = state; + +endmodule + +`endif // `ifndef __MSDSL_SV__ \ No newline at end of file diff --git a/setup.py b/setup.py index b6f324a..c164baf 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,49 @@ -from setuptools import setup, find_packages +from setuptools import setup + +name = 'msdsl' +version = '0.1.0' + +DESCRIPTION = '''\ +Library for generating synthesizable mixed-signal models for FPGA emulation\ +''' + +with open('README.md', 'r') as fh: + LONG_DESCRIPTION = fh.read() setup( - name='msdsl', - version='0.0.1', - description='Library for describing mixed-signal circuits for emulation', - url='https://github.com/sgherbst/msdsl', - author='Steven Herbst', - author_email='sherbst@stanford.edu', - packages=['msdsl'], + name=name, + version=version, + description=DESCRIPTION, + long_description=LONG_DESCRIPTION, + long_description_content_type='text/markdown', + keywords = ['analog', 'mixed-signal', 'mixed signal', 'behavioral', + 'real number model', 'real number models', 'rnm', 'rnms', + 'model', 'models', 'generator', 'verilog', 'system-verilog', + 'system verilog', 'synthesizable', 'emulation', 'fpga'], + packages=[ + f'{name}' + ], + scripts=[ + ], install_requires=[ + 'svreal', 'scipy', 'numpy', 'matplotlib' ], + license='MIT', + url=f'https://github.com/sgherbst/{name}', + author='Steven Herbst', + author_email='sgherbst@gmail.com', + python_requires='>=3.7', + download_url = f'https://github.com/sgherbst/{name}/archive/v{version}.tar.gz', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)', + 'License :: OSI Approved :: MIT License', + f'Programming Language :: Python :: 3.7' + ], include_package_data=True, - zip_safe=False, + zip_safe=False ) diff --git a/src/edge_det_msdsl.sv b/src/edge_det_msdsl.sv deleted file mode 100644 index b47cd46..0000000 --- a/src/edge_det_msdsl.sv +++ /dev/null @@ -1,30 +0,0 @@ -`timescale 1ns / 1ps - -`default_nettype none - -module edge_det_msdsl #( - parameter init = 0, - parameter active = 1 -) ( - input wire logic in, - output wire logic out, - input wire logic clk, - input wire logic rst -); - - // internal state - logic last; - always @(posedge clk) begin - if (rst == 1'b1) begin - last <= init; - end else begin - last <= in; - end - end - - // output assignment - assign out = ((last == (1-active)) && (in == active)) ? 1'b1 : 1'b0; - -endmodule - -`default_nettype wire diff --git a/src/mem_analog.sv b/src/mem_analog.sv deleted file mode 100644 index 7686f0c..0000000 --- a/src/mem_analog.sv +++ /dev/null @@ -1,49 +0,0 @@ -`timescale 1ns / 1ps - -`include "real.sv" - -`default_nettype none - -module mem_analog #( - parameter real init = 0, - `DECL_REAL(in), - `DECL_REAL(out) -) ( - `INPUT_REAL(in), - `OUTPUT_REAL(out), - input wire logic clk, - input wire logic rst, - input wire logic cke -); - - // create wires to hold reset and input value, - // using the output format - - `COPY_FORMAT_REAL(out, in_aligned); - `COPY_FORMAT_REAL(out, init_aligned); - `COPY_FORMAT_REAL(out, out_aligned); - - // assign reset and input values - - `ASSIGN_REAL(in, in_aligned); - `ASSIGN_CONST_REAL(init, init_aligned); - - // create the memory unit - - always @(posedge clk) begin - if (rst == 1'b1) begin - out_aligned <= init_aligned; - end else if (cke == 1'b1) begin - out_aligned <= in_aligned; - end else begin - out_aligned <= out_aligned; - end - end - - // assign output - - `ASSIGN_REAL(out_aligned, out); - -endmodule - -`default_nettype wire diff --git a/src/mem_digital.sv b/src/mem_digital.sv deleted file mode 100644 index 3320a25..0000000 --- a/src/mem_digital.sv +++ /dev/null @@ -1,38 +0,0 @@ -`timescale 1ns / 1ps - -`default_nettype none - -module mem_digital #( - parameter init = 0, - parameter width = 1 -) ( - input wire logic [(width-1):0] in, - output wire logic [(width-1):0] out, - input wire logic clk, - input wire logic rst, - input wire logic cke -); - - // internal state - - logic [(width-1):0] state; - - // create the memory unit - - always @(posedge clk) begin - if (rst == 1'b1) begin - state <= init; - end else if (cke == 1'b1) begin - state <= in; - end else begin - state <= state; - end - end - - // assign output - - assign out = state; - -endmodule - -`default_nettype wire diff --git a/src/pwm.sv b/src/pwm.sv deleted file mode 100644 index 0527aaf..0000000 --- a/src/pwm.sv +++ /dev/null @@ -1,80 +0,0 @@ -`timescale 1ns / 1ps - -`include "real.sv" - -`default_nettype none - -module pwm #( - parameter real duty = 0.5, - parameter real freq = 1e6, - `DECL_REAL(dt) -) ( - `INPUT_REAL(dt), - output wire logic out, - input wire logic clk, - input wire logic rst -); - - // store the time on and the time off - localparam real period = 1.0/freq; - localparam real time_on = (1.0*duty)*period; - localparam real time_off = (1.0-duty)*period; - - // constants - `MAKE_CONST_REAL(time_on, time_on_const); - `MAKE_CONST_REAL(time_off, time_off_const); - - // make a signal to contain acucmulated time - `MAKE_REAL(time_accum, period); - - // case 1: increment time - `ADD_REAL(time_accum, dt, incr_by_dt); - `COPY_FORMAT_REAL(time_accum, incr_by_dt_aligned); - `ASSIGN_REAL(incr_by_dt, incr_by_dt_aligned); - - // case 2: rewind by on time - `SUB_REAL(incr_by_dt, time_on_const, rewind_by_on); - `COPY_FORMAT_REAL(time_accum, rewind_by_on_aligned); - `ASSIGN_REAL(rewind_by_on, rewind_by_on_aligned); - - // case 3: rewind by off time - `SUB_REAL(incr_by_dt, time_off_const, rewind_by_off); - `COPY_FORMAT_REAL(time_accum, rewind_by_off_aligned); - `ASSIGN_REAL(rewind_by_off, rewind_by_off_aligned); - - // comparisons - `GT_REAL(time_accum, time_on_const, goto_off); - `GT_REAL(time_accum, time_off_const, goto_on); - - // create the memory unit - logic state; - - always @(posedge clk) begin - if (rst == 1'b1) begin - state <= 1'b0; - time_accum <= 0; - end else if (state == 1'b1) begin - if (goto_off == 1'b1) begin - state <= 1'b0; - time_accum <= rewind_by_on_aligned; - end else begin - state <= 1'b1; - time_accum <= incr_by_dt_aligned; - end - end else begin - if (goto_on == 1'b1) begin - state <= 1'b1; - time_accum <= rewind_by_off_aligned; - end else begin - state <= 1'b0; - time_accum <= incr_by_dt_aligned; - end - end - end - - // assign output - assign out = state; - -endmodule - -`default_nettype wire diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/adc/__init__.py b/tests/adc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/adc/test_adc.py b/tests/adc/test_adc.py new file mode 100644 index 0000000..d15e989 --- /dev/null +++ b/tests/adc/test_adc.py @@ -0,0 +1,89 @@ +# general imports +import numpy as np +from math import floor +from pathlib import Path + +# AHA imports +import magma as m +import fault + +# svreal import +from svreal import get_svreal_header + +# msdsl imports +from ..common import pytest_sim_params, get_file +from msdsl import MixedSignalModel, VerilogGenerator, to_sint, min_op, max_op + +BUILD_DIR = Path(__file__).resolve().parent / 'build' + +def pytest_generate_tests(metafunc): + pytest_sim_params(metafunc) + +def clamp(a, min_val, max_val): + return min_op([max_op([a, min_val]), max_val]) + +def gen_model(n, vn, vp, dt): + # declare model I/O + m = MixedSignalModel('model', dt=dt) + m.add_analog_input('a_in') + m.add_digital_output('d_out', width=n, signed=True) + + # compute expression for ADC output as an unclamped, real number + expr = ((m.a_in-vn)/(vp-vn) * ((2**n)-1)) - (2**(n-1)) + + # clamp to ADC range + clamped = clamp(expr, -(2**(n-1)), (2**(n-1))-1) + + # assign expression to output + m.set_this_cycle(m.d_out, to_sint(clamped, width=n)) + + # compile to a file + BUILD_DIR.mkdir(parents=True, exist_ok=True) + model_file = BUILD_DIR / 'model.sv' + m.compile_to_file(VerilogGenerator(), filename=model_file) + + # return file location + return model_file + +def test_adc(simulator, n_adc=8, v_ref_n=-1.0, v_ref_p=+1.0, dt=0.1e-6): + model_file = gen_model(n=n_adc, vn=v_ref_n, vp=v_ref_p, dt=dt) + + # declare circuit + dut = m.DeclareCircuit( + 'test_adc', + 'a_in', fault.RealIn, + 'd_out', m.Out(m.SInt[n_adc]) + ) + + def model(a_in): + code = ((a_in - v_ref_n) / (v_ref_p - v_ref_n)) * ((2**n_adc) - 1) + code -= 2**(n_adc - 1) + code = min(max(code, -(2**(n_adc-1))), (2**(n_adc-1))-1) + code = floor(code) + return code + + # create mechanism to run trials + t = fault.Tester(dut, expect_strict_default=True) + def run_trial(a_in, should_print=False): + t.poke(dut.a_in, a_in) + t.eval() + if should_print: + t.print('a_in: %0f, d_out: %0d\n', dut.a_in, dut.d_out) + t.expect(dut.d_out, model(a_in)) + + # specify trials to be run + delta = 0.1*(v_ref_p - v_ref_n) + for x in np.linspace(v_ref_n - delta, v_ref_p + delta, 1000): + run_trial(x) + + # run the simulation + t.compile_and_run( + target='system-verilog', + directory=BUILD_DIR, + simulator=simulator, + ext_srcs=[model_file, get_file('adc/test_adc.sv')], + inc_dirs=[get_svreal_header().parent], + parameters={'n_adc': n_adc}, + ext_model_file=True, + disp_type='realtime' + ) diff --git a/tests/adc/test_adc.sv b/tests/adc/test_adc.sv new file mode 100644 index 0000000..b279a7f --- /dev/null +++ b/tests/adc/test_adc.sv @@ -0,0 +1,18 @@ +`include "svreal.sv" + +module test_adc #( + parameter integer n_adc=8 +) ( + input real a_in, + output signed [(n_adc-1):0] d_out +); + `MAKE_REAL(a_in_int, 10); + assign `FORCE_REAL(a_in, a_in_int); + + model #( + `PASS_REAL(a_in, a_in_int) + ) model_i ( + .a_in(a_in_int), + .d_out(d_out) + ); +endmodule \ No newline at end of file diff --git a/tests/adc_and_dac.py b/tests/adc_and_dac.py deleted file mode 100644 index 32f6673..0000000 --- a/tests/adc_and_dac.py +++ /dev/null @@ -1,26 +0,0 @@ -from msdsl import MixedSignalModel, VerilogGenerator, to_sint, to_uint, min_op, max_op - -def clamp(a, min_val, max_val): - return min_op([max_op([a, min_val]), max_val]) - -def main(): - model = MixedSignalModel('model') - - model.add_analog_input('a_in') - model.add_digital_output('d_out', width=8) - - model.add_analog_output('a_out') - model.add_digital_input('d_in', width=8) - - # DAC from 0 to 1V as the input code varies from 0-255 - - clamped = clamp(to_sint(model.a_in*255, width=model.d_out.width+1), 0, 255) - model.set_this_cycle(model.d_out, to_uint(clamped, width=model.d_out.width)) - - # ADC code goes from 0-255 as input voltage goes from 0 to 1V - model.set_this_cycle(model.a_out, model.d_in/255) - - model.compile_and_print(VerilogGenerator()) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 0000000..9d99e6b --- /dev/null +++ b/tests/common.py @@ -0,0 +1,31 @@ +from pathlib import Path +from shutil import which + +TEST_DIR = Path(__file__).resolve().parent + +def get_file(path): + return Path(TEST_DIR, path) + +def get_dir(path): + # alias for get_file + return get_file(path) + +def get_files(*args): + return [get_file(path) for path in args] + +def get_dirs(*args): + # alias for get_files + return get_files(*args) + +def pytest_sim_params(metafunc, simulators=None): + if simulators is None: + simulators = ['vcs', 'vivado', 'ncsim', 'iverilog'] + + # parameterize with the simulators available + if 'simulator' in metafunc.fixturenames: + targets = [] + for simulator in simulators: + if which(simulator): + targets.append(simulator) + + metafunc.parametrize('simulator', targets) diff --git a/tests/dac/__init__.py b/tests/dac/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/dac/test_dac.py b/tests/dac/test_dac.py new file mode 100644 index 0000000..b6e3f38 --- /dev/null +++ b/tests/dac/test_dac.py @@ -0,0 +1,87 @@ +# general imports +from pathlib import Path + +# AHA imports +import magma as m +import fault + +# svreal import +from svreal import get_svreal_header + +# msdsl imports +from ..common import pytest_sim_params, get_file +from msdsl import MixedSignalModel, VerilogGenerator, min_op, max_op + +BUILD_DIR = Path(__file__).resolve().parent / 'build' + +def pytest_generate_tests(metafunc): + pytest_sim_params(metafunc) + +def clamp(a, min_val, max_val): + return min_op([max_op([a, min_val]), max_val]) + +def gen_model(n, vn, vp, dt): + # declare model I/O + m = MixedSignalModel('model', dt=dt) + m.add_digital_input('d_in', width=n, signed=True) + m.add_analog_output('a_out') + + # compute expression for DAC output + expr = ((m.d_in + (2**(n-1)))/((2**n)-1))*(vp-vn) + vn + + # assign expression to output + m.set_this_cycle(m.a_out, expr) + + # compile to a file + BUILD_DIR.mkdir(parents=True, exist_ok=True) + model_file = BUILD_DIR / 'model.sv' + m.compile_to_file(VerilogGenerator(), filename=model_file) + + # return file location + return model_file + +def test_adc(simulator, n_dac=8, v_ref_n=-1.0, v_ref_p=+1.0, dt=0.1e-6): + model_file = gen_model(n=n_dac, vn=v_ref_n, vp=v_ref_p, dt=dt) + + # declare circuit + dut = m.DeclareCircuit( + 'test_dac', + 'd_in', m.In(m.SInt[n_dac]), + 'a_out', fault.RealOut + ) + + def model(d_in): + # scale code to real number from 0 to 1 + out = (d_in + (2**(n_dac-1))) / ((2**n_dac)-1) + + # apply scaling and offset + out *= (v_ref_p - v_ref_n) + out += v_ref_n + + # return output + return out + + # create mechanism to run trials + t = fault.Tester(dut, expect_strict_default=True) + def run_trial(d_in, should_print=False): + t.poke(dut.d_in, d_in) + t.eval() + if should_print: + t.print('d_in: %0d, a_out: %0f\n', dut.d_in, dut.a_out) + t.expect(dut.a_out, model(d_in), abs_tol=1e-3) + + # determine tolerance + for k in range(-(2**(n_dac-1)), (2**(n_dac-1)) - 1): + run_trial(k) + + # run the simulation + t.compile_and_run( + target='system-verilog', + directory=BUILD_DIR, + simulator=simulator, + ext_srcs=[model_file, get_file('dac/test_dac.sv')], + inc_dirs=[get_svreal_header().parent], + parameters={'n_dac': n_dac}, + ext_model_file=True, + disp_type='realtime' + ) diff --git a/tests/dac/test_dac.sv b/tests/dac/test_dac.sv new file mode 100644 index 0000000..0c87ca1 --- /dev/null +++ b/tests/dac/test_dac.sv @@ -0,0 +1,18 @@ +`include "svreal.sv" + +module test_dac #( + parameter integer n_dac=8 +) ( + input signed [(n_dac-1):0] d_in, + output real a_out +); + `MAKE_REAL(a_out_int, 10); + assign a_out = `TO_REAL(a_out_int); + + model #( + `PASS_REAL(a_out, a_out_int) + ) model_i ( + .d_in(d_in), + .a_out(a_out_int) + ); +endmodule \ No newline at end of file diff --git a/tests/rc.py b/tests/rc.py deleted file mode 100644 index 07418d3..0000000 --- a/tests/rc.py +++ /dev/null @@ -1,16 +0,0 @@ -from msdsl import MixedSignalModel, VerilogGenerator, Deriv - -def main(): - tau = 1e-6 - dt = 0.1e-6 - - model = MixedSignalModel('model', dt=dt) - model.add_analog_input('v_in') - model.add_analog_output('v_out', init=1.23) - - model.add_eqn_sys([Deriv(model.v_out) == (model.v_in - model.v_out)/tau]) - - model.compile_and_print(VerilogGenerator()) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/tests/rc/__init__.py b/tests/rc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/rc/test_rc.py b/tests/rc/test_rc.py new file mode 100644 index 0000000..13142c1 --- /dev/null +++ b/tests/rc/test_rc.py @@ -0,0 +1,82 @@ +# general imports +from math import exp +from pathlib import Path + +# AHA imports +import magma as m +import fault + +# svreal import +from svreal import get_svreal_header + +# msdsl imports +from ..common import pytest_sim_params, get_file +from msdsl import MixedSignalModel, VerilogGenerator, Deriv + +BUILD_DIR = Path(__file__).resolve().parent / 'build' + +def pytest_generate_tests(metafunc): + pytest_sim_params(metafunc) + +def gen_model(tau, dt): + model = MixedSignalModel('model', dt=dt) + model.add_analog_input('v_in') + model.add_analog_output('v_out') + + model.add_eqn_sys([Deriv(model.v_out) == (model.v_in - model.v_out)/tau]) + + BUILD_DIR.mkdir(parents=True, exist_ok=True) + model_file = BUILD_DIR / 'model.sv' + model.compile_to_file(VerilogGenerator(), filename=model_file) + + return model_file + +def test_rc(simulator, tau=1e-6, dt=0.1e-6): + model_file = gen_model(tau=tau, dt=dt) + + # declare circuit + dut = m.DeclareCircuit( + 'test_rc', + 'v_in', fault.RealIn, + 'v_out', fault.RealOut, + 'clk', m.BitIn, + 'rst', m.BitIn + ) + + # create the tester + tester = fault.Tester(dut, expect_strict_default=True) + + def cycle(): + tester.poke(dut.clk, 1) + tester.eval() + tester.poke(dut.clk, 0) + tester.eval() + + # initialize + v_in = 1.0 + tester.poke(dut.clk, 0) + tester.poke(dut.rst, 1) + tester.poke(dut.v_in, v_in) + tester.eval() + + # reset + cycle() + + # print the first few outputs + tester.poke(dut.rst, 0) + for k in range(20): + tester.expect(dut.v_out, v_in*(1-exp(-k*dt/tau)), abs_tol=0.025) + tester.print("v_out: %0f\n", dut.v_out) + cycle() + + # run the simulation + tester.compile_and_run( + target='system-verilog', + directory=BUILD_DIR, + simulator=simulator, + ext_srcs=[model_file, get_file('rc/test_rc.sv')], + inc_dirs=[get_svreal_header().parent], + defines={'CLK_MSDSL': 'dut.clk', 'RST_MSDSL': 'dut.rst'}, + ext_model_file=True, + disp_type='realtime' + ) diff --git a/tests/rc/test_rc.sv b/tests/rc/test_rc.sv new file mode 100644 index 0000000..63dae03 --- /dev/null +++ b/tests/rc/test_rc.sv @@ -0,0 +1,22 @@ +`include "svreal.sv" + +module test_rc( + input real v_in, + output real v_out, + input clk, + input rst +); + `MAKE_REAL(v_in_int, 10); + assign `FORCE_REAL(v_in, v_in_int); + + `MAKE_REAL(v_out_int, 10); + assign v_out = `TO_REAL(v_out_int); + + model #( + `PASS_REAL(v_in, v_in_int), + `PASS_REAL(v_out, v_out_int) + ) model_i ( + .v_in(v_in_int), + .v_out(v_out_int) + ); +endmodule \ No newline at end of file