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.
To run the simulator along with the preloaded assembly program, you will need the following tools:
- Python v3.12 or later
- Icarus Verilog v12.0 or later
To run everything else, you will need the following tools too:
- Node.JS v18 or later
tap-mocha-reporter
- C compiler such as GCC to be used via
iverilog-vpi
- Make, preferably GNU Make
- Verible
v0.0-3428-gcfcbb82b
or later - Verilator
v5.019
or later - At least one of:
- Option A (recommended):
- My fork of DigitalJS online
- Yosys
v0.35
or later - cURL
xdg-open
- Option B:
- Option C:
- Option A (recommended):
- 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
.
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 foriverilog
Makefile
: configuration for Make
ckl
: C-like programming language which compiles to the Mano machinesv.py
: Script which wraps almost any command that you will runextended_instructions.py
: Script which generates new (and dare I say mostly useless) instructions for the current CPU architecture.
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.
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_LABEL
s (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.
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.
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.
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)");