Skip to content

Commit 7982860

Browse files
Merge pull request #3 from scopeInfinity/emulator
POC Python Assembler and Emulator
2 parents 6e2c8ec + 64fd3b1 commit 7982860

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2841
-398
lines changed

.github/workflows/ci.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,23 @@ on:
88

99
jobs:
1010
build:
11-
1211
runs-on: ubuntu-latest
13-
1412
steps:
1513
- uses: actions/checkout@v3
16-
- name: install dependencies
17-
run: sudo apt install nasm
14+
- name: Set up Python
15+
uses: actions/setup-python@v5
16+
with:
17+
python-version: '3.x'
18+
- name: Install python dependencies
19+
run: |
20+
python -m pip install --upgrade pip
21+
pip install -r requirements.txt
22+
- name: Install iverilog
23+
run: |
24+
sudo apt-get update
25+
sudo apt-get install iverilog
26+
- name: make test
27+
run: make test
1828
- name: make
1929
run: make all
2030
- name: Upload artifacts

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.vscode/
2+
__pycache__/
23
build/
34
output/

Makefile

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,22 @@
1-
BUILD = build
2-
OUTPUT = output
3-
SIMULATOR_FLAG := -DOPC_SIM_ENABLED
4-
OPC_CFLAGS = -nostdlib -nodefaultlibs
1+
SRC_DIR=.
2+
BUILD_DIR=build
3+
OUTPUT_DIR=output
54

6-
.PHONY: clean prep all artifacts
7-
8-
all: prep $(BUILD)/greeting $(BUILD)/sim_greeting artifacts
5+
.PHONY: clean test
96

107
clean:
11-
rm -f $(BUILD)/*
12-
13-
prep:
14-
mkdir -p $(BUILD)/
15-
mkdir -p $(OUTPUT)/
16-
17-
# simulator specific rules
18-
$(BUILD)/sim_o: lib/sim.c
19-
gcc -c -o $@ $< $(SIMULATOR_FLAG) -I include/
20-
21-
$(BUILD)/sim_asm_o: lib/sim.asm
22-
nasm -f elf64 -o $@ $^
23-
24-
$(BUILD)/sim_logging_o: lib/logging.c
25-
gcc -c -o $@ $< $(SIMULATOR_FLAG) -I include/
26-
27-
$(BUILD)/logging_o: lib/logging.c
28-
gcc -c -o $@ $< $(OPC_CFLAGS) -I include/
29-
30-
$(BUILD)/sim_greeting: $(BUILD)/sim_o $(BUILD)/sim_asm_o $(BUILD)/greeting_o $(BUILD)/font_o $(BUILD)/sim_logging_o
31-
gcc -o $@ $^
32-
33-
$(BUILD)/greeting: linker.ld $(BUILD)/ourpc_asm_o $(BUILD)/greeting_o $(BUILD)/font_o $(BUILD)/logging_o
34-
ld -T linker.ld -o $@ $(BUILD)/ourpc_asm_o $(BUILD)/greeting_o $(BUILD)/font_o $(BUILD)/logging_o
35-
36-
# simulator agnostic rules
37-
$(BUILD)/font_o: lib/font.c
38-
gcc -c -o $@ $(OPC_CFLAGS) $< -I include/
39-
40-
$(BUILD)/ourpc_asm_o: lib/ourpc.asm
41-
nasm -f elf64 -o $@ $^
8+
rm -r $(BUILD_DIR)
429

43-
$(BUILD)/greeting_o: greetings.c
44-
gcc -c -o $@ $(OPC_CFLAGS) $^ -I include/
10+
include emulator/Makefile.mk
4511

46-
# independent helper tools
47-
$(BUILD)/text_to_led: text_to_led.c $(BUILD)/font_o
48-
gcc -o $@ $^ -I include/
12+
pytest:
13+
pytest -s
4914

50-
# generate artifacts
15+
test: pytest test_verilog_modules
5116

52-
artifacts: $(OUTPUT)/sample_rom_text.txt $(OUTPUT)/objdump_greetings.txt
17+
$(OUTPUT_DIR)/programs/%.bin: programs/%.asm
18+
mkdir -p $(dir $@)
19+
python3 -m planner asm -b $^ > $@
5320

54-
$(OUTPUT)/sample_rom_text.txt: $(BUILD)/text_to_led
55-
$^ "Happy Diwali!" > $@
5621

57-
$(OUTPUT)/objdump_greetings.txt: $(BUILD)/greeting
58-
objdump -D $(BUILD)/greeting > $@
22+
all: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%.bin, $(shell find programs/ -name '*.asm'))

README.md

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,102 @@
55

66
The eventual goal(?) is to build a general-purpose processor integrated with simple input (e.g. buttons) and output devices (8x8 LED display).
77

8+
## Verification
9+
10+
### Emulation
11+
12+
```
13+
# Build ROM[boot]
14+
$ python3 -rb assembler/assembler.py programs/boot_sequence.asm | tee build/boot_rom.txt.
15+
16+
# Write your program in custom assembly.
17+
$ cat programs/ping_pong.asm # we can proceeding with this
18+
19+
# Use assembler to translate the instructions into machine code.
20+
$ mkdir -p build
21+
$ python3 assembler/assembler.py -r programs/ping_pong.asm | tee build/ping_pong_resolved.txt # optional
22+
$ python3 assembler/assembler.py -rb programs/ping_pong.asm | tee build/ping_pong_rom.txt
23+
24+
# Use emulater the run the machine code.
25+
$ python3 emulator/emulate.py build/ping_pong_rom.txt
26+
```
27+
828
## Design
929

10-
TBU
30+
### Specs
31+
32+
* Address Line: 16-bits
33+
* Max Memory: 64KB
34+
35+
### Constants
36+
37+
* INSZ = 0x20, independent input bytes
38+
* OUTSZ = 0x20, independent output bytes
39+
* IPC = 0x0100, intial value of `PC` (or Program Counter).
40+
41+
### Memory Allocation
42+
43+
* `RAM[0:INSZ]` is mapped to I/O module input
44+
* `RAM[INSZ:OUTSZ]` is mapped to I/O module output
45+
* `RAM[IPC:IPC+x]` is loaded from ROM. So it essentially contains `.text`, `.data`.
46+
47+
### Sequencing
48+
49+
50+
* At boot
51+
* Load `ROM[0:x]` into `RAM[IPC:IPC+x]`
52+
* TODO: How?
53+
54+
### Assembly
55+
56+
* `.bss` must be the last section.
57+
* Registers don't really exists. `R[0-7]` are mapped to memory location in `.bss` for convenience and some instructions return response.
58+
59+
### Architecture
60+
61+
#### I/O
62+
63+
Hardware interact asynchronously with IOM (I/O Module) which then interact with RAM at program's will. (WE ARE NOT DOING IT)
64+
65+
* Input devices publish state change in IOM and Output devices read from IOM.
66+
* Program use `IN <index>` instructions to read from `IOM_in[index]` and write to `RAM[index]`. `IOM_in` won't cache input and it will be read as real-time value. If a input state needs to be cached, it's the input device responsibility.
67+
* Program use `OUT <index>` instructions to read `RAM[INSZ+index]` and write to `IOM_out[index]`.
68+
69+
70+
71+
# TODO
72+
73+
## Processor
74+
75+
* Address bits: 8
76+
* Register size: 8
77+
* Memory size: 2**8 = 256 bytes
78+
79+
### Idea
80+
81+
To keep number of component small, we would split a single instruction execution period into 4 cycles.
82+
83+
* Reset
84+
* Set `PC = 0`
85+
* sub-cycle clock to cycle-0
86+
* Cycle 0
87+
* Fetch instruction from `ROM[$PC]` into `pin_INS`
88+
* Cycle 1
89+
* Perform first read
90+
91+
## Assembler
92+
93+
### Details
94+
95+
* Registers: R0, R1, R2, R3 or `R{NUM}`
96+
97+
* Input/Output pin: IO0, IO1, ..., IO7 or `IO{NUM}` (8-bits)
98+
99+
### Instructions
100+
101+
* `IN R{NUM}`: short-blocking input with 8-bit response.
102+
* `OUT R{NUM}`: short-blocking 8-bit output.
103+
104+
## Syntax: High Level
105+
106+
Not yet defined.

emulator/Makefile.mk

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
BUILD_EMULATOR = $(BUILD_DIR)/emulator
2+
SRC_EMULATOR = $(SRC_DIR)/emulator
3+
4+
.PHONY: test_verilog_modules
5+
6+
$(BUILD_EMULATOR)/%_test: $(SRC_EMULATOR)/%_test.v
7+
mkdir -p $(dir $@)
8+
iverilog -o $@ $^
9+
10+
test_verilog_modules: $(patsubst $(SRC_EMULATOR)/%_test.v, $(BUILD_EMULATOR)/%_test, $(shell find $(SRC_EMULATOR) -name '*_test.v'))
11+
$(foreach test_name, $^, echo "Executing $(test_name)" && ./$(test_name))

emulator/__init__.py

Whitespace-only changes.

emulator/chipset.v

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
`include "emulator/module/clock.v"
2+
3+
module CHIPSET();
4+
// Global Registers
5+
reg execute_from_brom;
6+
7+
// Stages
8+
wire[0:3] clk;
9+
wire[0:3] is_stage;
10+
CLOCK clock(
11+
.clk(clk[0:3]),
12+
.is_stage(is_stage[0:3]));
13+
14+
// Boot Sequence
15+
//
16+
// When the device have power supply but `is_powered_on` is off. The pc_eval
17+
// forces `program_counter_next` to be 0 and `execute_from_brom` to True.
18+
// If we keep the `is_powered_on` button in off stage for at-least 4 cycles
19+
// then at "stage3 posedge" program_counter should get updated to 0.
20+
// After that for every "stage0 posedge" till is_powered_on is off,
21+
// program_counter:0 along with execute_from_brom:True will be used to pull
22+
// up and execute first instruction from BROM.
23+
//
24+
// Assumption: No stateful IO devices are connected.
25+
// TODO: Implement `execute_from_brom` update implementation.
26+
wire is_powered_on;
27+
BOOT_CONTROL boot_control(.is_powered_on(is_powered_on));
28+
29+
30+
// MBLOCK is a continous circuit and doesn't depend on clock
31+
// but the behaviour do depend on stage which is abstracted.
32+
wire[15:0] program_counter;
33+
wire[1:0] mblock_selector;
34+
wire[15:0] mblock_address;
35+
wire[31:0] mblock_input;
36+
wire[31:0] mblock_output;
37+
wire mblock_write;
38+
39+
MBLOCK_MUX mblock_mux(
40+
.mblock_address(mblock_address[15:0]),
41+
.mblock_selector(mblock_selector[1:0]),
42+
.execute_from_brom(execute_from_brom),
43+
.is_stage(is_stage[0:3]),
44+
.address0(program_counter),
45+
.address1(v0_source),
46+
.address2(v1_source),
47+
.address3(v2_source),
48+
.is_write(4'b0000));
49+
50+
MBLOCK mblock(
51+
.out(mblock_output),
52+
.selector(mblock_selector),
53+
.in(mblock_input),
54+
.address(mblock_address),
55+
.is_write(mblock_write));
56+
57+
// STAGE0
58+
59+
// TODO: Ensure MBLOCK supplies expectations.
60+
// MBLOCK_MUX is expected to fetch MBLOCK at `program_counter` from
61+
// BROM / RAM based on `execute_from_brom` and redirect the value
62+
// to full_ins via mblock_output.
63+
64+
// @stage0 posedge following values should freeze.
65+
wire[7:0] v0_source, v1_source, v2_source, instruction_op;
66+
INS_RESOLVER stage0(
67+
.v0(v0_source), .v1(v1_source), .v2(v2_source), .op(instruction_op),
68+
.full_ins(.mblock_output),
69+
clk[0]);
70+
71+
// STAGE1
72+
73+
// TODO: Breakdown instruction_op into sub-operations
74+
75+
// TODO: Ensure MBLOCK supplies expectations.
76+
// MBLOCK_MUX is expected to fetch MBLOCK based on v0_source and
77+
// instruction_op breakdowns and redirect the value into v0.
78+
79+
// @stage1 posedge following should freeze.
80+
wire[31:0] v0;
81+
FETCH_AND_STORE stage1(
82+
.value(v0),
83+
.in(mblock_output),
84+
.clk(clk[1]));
85+
86+
// STAGE2
87+
88+
// TODO: Ensure MBLOCK supplies expectations.
89+
// MBLOCK_MUX is expected to fetch MBLOCK based on v0_source and
90+
// instruction_op breakdowns and redirect the value into v0.
91+
92+
// @stage2 posedge following should freeze.
93+
wire[31:0] v1;
94+
FETCH_AND_STORE stage2(
95+
.value(v1),
96+
.in(mblock_output),
97+
.clk(clk2));
98+
99+
// STAGE3
100+
// TODO: alu_op should be computed using instruction_op breakdowns.
101+
wire[3:0] alu_op;
102+
wire[31:0] v2;
103+
104+
wire flag_alu_zero;
105+
ALU alu(
106+
.out(v2),
107+
.is_zero(flag_alu_zero),
108+
.op(alu_op),
109+
.in0(v0),
110+
.in1(v1));
111+
112+
// MBLOCK input only comes from ALU output.
113+
assign mblock_input = v2;
114+
115+
// TODO: jump instruction
116+
PC_NEXT pc_next(
117+
.program_counter_next(program_counter_next),
118+
.program_counter(program_counter),
119+
.is_powered_on(is_powered_on));
120+
121+
// @stage3 posedge following should freeze.
122+
wire[15:0] program_counter_next;
123+
flipflop16 pc(
124+
.out(program_counter),
125+
.in(program_counter_next),
126+
.clk(clk3));
127+
128+
endmodule

emulator/lib/adder.v

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module ADDER(
2+
output[15:0] out,
3+
input[15:0] in0,
4+
input[15:0] in1);
5+
6+
assign out = in0+in1;
7+
endmodule

emulator/lib/adder_test.v

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
`include "emulator/lib/adder.v"
2+
3+
module adder_test;
4+
reg[15:0] in0, in1;
5+
wire[15:0] out;
6+
7+
ADDER dut(
8+
.out(out),
9+
.in0(in0),
10+
.in1(in1));
11+
12+
initial begin
13+
in0 = 2536;
14+
in1 = 113;
15+
# 10
16+
$display("ADDER_TEST: in0=%b in1=%b", in0, in1);
17+
if (out !== 2649) begin
18+
$error("latch failed");
19+
$fatal(1);
20+
end
21+
end
22+
endmodule

0 commit comments

Comments
 (0)