Skip to content

esdmr/mano-machine

Repository files navigation

Mano Machine implementation in SystemVerilog

For the Computer Architecture class, I created a project that simulates the Mano machine. It consists of synthesizable modules for the components that do not involve input/output, a macro-based assembler, and a module that can run the simulation.

Requirements

To run the simulator along with the preloaded assembly program, you will need the following tools:

To run everything else, you will need the following tools too:

Usage

  • Prepare the VPI modules: ./sv.py make.
  • Simulate preloaded with the assembly program in src/program.asm.sv: ./sv.py run src/VirtualComputer.sv.
  • Synthesize to logic gates: (A) ./sv.py synthesize --online http://localhost:15555 src/SOC.sv or (B) ./sv.py synthesize --vscode src/SOC.sv or (C) ./sv.py synthesize src/SOC.sv.
  • Translate to VHDL (not tested): ./sv.py compile src/SOC.sv --type vhdl --out SOC.vhdl.
  • Run linter: ./sv.py lint.
  • Run tests: ./sv.py test.
  • Run the formatter: ./sv.py format.

Project structure

  • src
    • Virtual*.sv: non-synthesizable modules
    • /[a-z].*\.sv/: non-module source files
    • Anything else: synthesizable modules
  • test
    • /[a-z].*\.sv/: utility files
    • Anything else: testbench modules
  • vpi
    • *.c: VPI source files for iverilog
    • Makefile: configuration for Make
  • ckl: C-like programming language which compiles to the Mano machine
  • sv.py: Script which wraps almost any command that you will run
  • extended_instructions.py: Script which generates new (and dare I say mostly useless) instructions for the current CPU architecture.

The preamble

The files src/preamble.sv and test/preamble.sv contain macros which will be loaded at the top of every file.

An important macro from the preamble is IMPORT. Instead of wrapping each module in ifndef-define, we will wrap the include which will import that module. As long as no module is circularly importing itself, it is a more elegant solution to importing a file only once and in order.

In test/preamble.sv, the IMPORT macro will be redirected to the src directory, so IMPORT(RAM) imports src/RAM.sv and not test/RAM.sv. Additionally, test/preamble.sv includes some macros to assist in running testbenches.

The assembler

We use SystemVerilog macros to implement the assembler. In a module which will accept the assembler, you should implement the _ASM_SET_MEMORY_ macro which will be called with the bytes from the assembled program.

Inside the module body, you will use the ASM_DEFINE_PROGRAM macro and pass the program as its argument. For multiline programs, I recommend moving the content of the argument into a macro. For very long programs, I recommend using include and the .asm.sv suffix. (The formatter correctly indents the instructions with this suffix.)

Non-memory-reference instructions are available as ASM_<name>. Memory-reference instructions are available as ASM_<name>_<mode><operand_type>. See assembler.sv for more information.

Labels can be created via ASM_LABEL. Some labels can be marked private, e.g., subroutine branches and variables. These can be defined via ASM_SUBLABEL and are only accessible between two ASM_LABELs (or the end of the program).

You can set the assembler address via ASM_ADDR and ASM_ADDR_REL.

You can set raw data via ASM_DATA. If the data is the address of a label, you can use ASM_DATA_LABEL and ASM_DATA_SUBLABEL. If the data is a string, you can use ASM_DATA_STR. (It will not append a null character). Finally, you can use ASM_DATA_FILL to initialize a span in the memory.

There are some shortcuts available, such as ASM_SUBROUTINE, ASM_CALL, ASM_RETURN, ASM_ARG_SKIP, ASM_ARG_NEXT, ASM_SHR, ASM_SHL, and ASM_ASR. See assembler.sv for more information.

Finally, there is the disassemble task which will decode an instruction.

The test library

There is an implementation of a Test Anything Protocol (TAP) producer inside test/preamble.sv. For every test case, it will output a ok or not ok line to the stdout which will be picked up by a tap consumer.

There is a test runner implemented in Python in sv.py. You can use the test sub-command to run some or all testbenches. If you are running this command via a TTY and have tap-mocha-reporter installed, the output will be passed into it.

For asynchronous modules, you can use TAP_TEST and TAP_CASE. For synchronous modules, you can also use TAP_CASE_AT and TAP_CASE_AT_NEGEDGE.

See test/preamble.sv for more information.

The VPI module for IO

While SystemVerilog has $fgetc, the standard input is line-buffered, so it cannot be used for the keyboard module. Additionally, $fgetc waits for user input, blocking the simulation.

To replace $fgetc, we will implement the necessary logic in C and load it into the iverilog simulation. This is implemented inside vpi/io.c. It supports both Windows (untested) and Linux. If you would like to avoid using this module for some reason, you can pass the --no-vpi option to the ./sv.py run command.

The CKL programming language

Pronounced like “sickle”, it is a C-like programming language specifically for the Mano Machine. It is currently a work in progress and also lacks many optimizations.

// Top-level variable declaration
a;
// Top-level variable declaration with initializer
b = 123;

// Forward declaration
add(a, b);

// Function declaration
add(a, b) {
  // Last expression is returned automatically.
  a + b;
}

// Special function declarations
$start() { /* … */ }
$isr()   { /* … */ }

io() {
  // Asynchronous input
  a = $input;
  // Synchronous input
  a = $input();

  // Asynchronous input
  $output = a;
  // Synchronous input
  $output(a);

  // I/O flags
  do; while (!$fgo);
  do; while (!$fgi);

  // IEN flag
  $ien = 1;
  $ien = 0;

  // Address of the word immediately after the assembly program.
  string = $post;

  // Inline assembly
  asm("`ASM_ISZ_DL(%s)", a);
}

// Also inline assembly
asm("`ASM_ADDR_REL(2)");