diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b183fce..cd10787 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,13 +8,23 @@ on: jobs: build: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v3 - - name: install dependencies - run: sudo apt install nasm + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + - name: Install python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Install iverilog + run: | + sudo apt-get update + sudo apt-get install iverilog + - name: make test + run: make test - name: make run: make all - name: Upload artifacts diff --git a/.gitignore b/.gitignore index d798700..d8b2e44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode/ +__pycache__/ build/ output/ \ No newline at end of file diff --git a/Makefile b/Makefile index fbeb89e..66e2400 100644 --- a/Makefile +++ b/Makefile @@ -1,58 +1,22 @@ -BUILD = build -OUTPUT = output -SIMULATOR_FLAG := -DOPC_SIM_ENABLED -OPC_CFLAGS = -nostdlib -nodefaultlibs +SRC_DIR=. +BUILD_DIR=build +OUTPUT_DIR=output -.PHONY: clean prep all artifacts - -all: prep $(BUILD)/greeting $(BUILD)/sim_greeting artifacts +.PHONY: clean test clean: - rm -f $(BUILD)/* - -prep: - mkdir -p $(BUILD)/ - mkdir -p $(OUTPUT)/ - -# simulator specific rules -$(BUILD)/sim_o: lib/sim.c - gcc -c -o $@ $< $(SIMULATOR_FLAG) -I include/ - -$(BUILD)/sim_asm_o: lib/sim.asm - nasm -f elf64 -o $@ $^ - -$(BUILD)/sim_logging_o: lib/logging.c - gcc -c -o $@ $< $(SIMULATOR_FLAG) -I include/ - -$(BUILD)/logging_o: lib/logging.c - gcc -c -o $@ $< $(OPC_CFLAGS) -I include/ - -$(BUILD)/sim_greeting: $(BUILD)/sim_o $(BUILD)/sim_asm_o $(BUILD)/greeting_o $(BUILD)/font_o $(BUILD)/sim_logging_o - gcc -o $@ $^ - -$(BUILD)/greeting: linker.ld $(BUILD)/ourpc_asm_o $(BUILD)/greeting_o $(BUILD)/font_o $(BUILD)/logging_o - ld -T linker.ld -o $@ $(BUILD)/ourpc_asm_o $(BUILD)/greeting_o $(BUILD)/font_o $(BUILD)/logging_o - -# simulator agnostic rules -$(BUILD)/font_o: lib/font.c - gcc -c -o $@ $(OPC_CFLAGS) $< -I include/ - -$(BUILD)/ourpc_asm_o: lib/ourpc.asm - nasm -f elf64 -o $@ $^ + rm -r $(BUILD_DIR) -$(BUILD)/greeting_o: greetings.c - gcc -c -o $@ $(OPC_CFLAGS) $^ -I include/ +include emulator/Makefile.mk -# independent helper tools -$(BUILD)/text_to_led: text_to_led.c $(BUILD)/font_o - gcc -o $@ $^ -I include/ +pytest: + pytest -s -# generate artifacts +test: pytest test_verilog_modules -artifacts: $(OUTPUT)/sample_rom_text.txt $(OUTPUT)/objdump_greetings.txt +$(OUTPUT_DIR)/programs/%.bin: programs/%.asm + mkdir -p $(dir $@) + python3 -m planner asm -b $^ > $@ -$(OUTPUT)/sample_rom_text.txt: $(BUILD)/text_to_led - $^ "Happy Diwali!" > $@ -$(OUTPUT)/objdump_greetings.txt: $(BUILD)/greeting - objdump -D $(BUILD)/greeting > $@ +all: $(patsubst programs/%.asm, $(OUTPUT_DIR)/programs/%.bin, $(shell find programs/ -name '*.asm')) diff --git a/README.md b/README.md index 0bf37f3..ca70da3 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,102 @@ The eventual goal(?) is to build a general-purpose processor integrated with simple input (e.g. buttons) and output devices (8x8 LED display). +## Verification + +### Emulation + +``` +# Build ROM[boot] +$ python3 -rb assembler/assembler.py programs/boot_sequence.asm | tee build/boot_rom.txt. + +# Write your program in custom assembly. +$ cat programs/ping_pong.asm # we can proceeding with this + +# Use assembler to translate the instructions into machine code. +$ mkdir -p build +$ python3 assembler/assembler.py -r programs/ping_pong.asm | tee build/ping_pong_resolved.txt # optional +$ python3 assembler/assembler.py -rb programs/ping_pong.asm | tee build/ping_pong_rom.txt + +# Use emulater the run the machine code. +$ python3 emulator/emulate.py build/ping_pong_rom.txt +``` + ## Design -TBU +### Specs + +* Address Line: 16-bits +* Max Memory: 64KB + +### Constants + +* INSZ = 0x20, independent input bytes +* OUTSZ = 0x20, independent output bytes +* IPC = 0x0100, intial value of `PC` (or Program Counter). + +### Memory Allocation + +* `RAM[0:INSZ]` is mapped to I/O module input +* `RAM[INSZ:OUTSZ]` is mapped to I/O module output +* `RAM[IPC:IPC+x]` is loaded from ROM. So it essentially contains `.text`, `.data`. + +### Sequencing + + +* At boot + * Load `ROM[0:x]` into `RAM[IPC:IPC+x]` + * TODO: How? + +### Assembly + +* `.bss` must be the last section. +* Registers don't really exists. `R[0-7]` are mapped to memory location in `.bss` for convenience and some instructions return response. + +### Architecture + +#### I/O + +Hardware interact asynchronously with IOM (I/O Module) which then interact with RAM at program's will. (WE ARE NOT DOING IT) + +* Input devices publish state change in IOM and Output devices read from IOM. +* Program use `IN ` 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. +* Program use `OUT ` instructions to read `RAM[INSZ+index]` and write to `IOM_out[index]`. + + + +# TODO + +## Processor + +* Address bits: 8 +* Register size: 8 +* Memory size: 2**8 = 256 bytes + +### Idea + +To keep number of component small, we would split a single instruction execution period into 4 cycles. + +* Reset + * Set `PC = 0` + * sub-cycle clock to cycle-0 +* Cycle 0 + * Fetch instruction from `ROM[$PC]` into `pin_INS` +* Cycle 1 + * Perform first read + +## Assembler + +### Details + +* Registers: R0, R1, R2, R3 or `R{NUM}` + +* Input/Output pin: IO0, IO1, ..., IO7 or `IO{NUM}` (8-bits) + +### Instructions + +* `IN R{NUM}`: short-blocking input with 8-bit response. +* `OUT R{NUM}`: short-blocking 8-bit output. + +## Syntax: High Level + +Not yet defined. diff --git a/emulator/Makefile.mk b/emulator/Makefile.mk new file mode 100644 index 0000000..1b65703 --- /dev/null +++ b/emulator/Makefile.mk @@ -0,0 +1,11 @@ +BUILD_EMULATOR = $(BUILD_DIR)/emulator +SRC_EMULATOR = $(SRC_DIR)/emulator + +.PHONY: test_verilog_modules + +$(BUILD_EMULATOR)/%_test: $(SRC_EMULATOR)/%_test.v + mkdir -p $(dir $@) + iverilog -o $@ $^ + +test_verilog_modules: $(patsubst $(SRC_EMULATOR)/%_test.v, $(BUILD_EMULATOR)/%_test, $(shell find $(SRC_EMULATOR) -name '*_test.v')) + $(foreach test_name, $^, echo "Executing $(test_name)" && ./$(test_name)) diff --git a/emulator/__init__.py b/emulator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/emulator/chipset.v b/emulator/chipset.v new file mode 100644 index 0000000..cb67033 --- /dev/null +++ b/emulator/chipset.v @@ -0,0 +1,128 @@ +`include "emulator/module/clock.v" + +module CHIPSET(); + // Global Registers + reg execute_from_brom; + + // Stages + wire[0:3] clk; + wire[0:3] is_stage; + CLOCK clock( + .clk(clk[0:3]), + .is_stage(is_stage[0:3])); + + // Boot Sequence + // + // When the device have power supply but `is_powered_on` is off. The pc_eval + // forces `program_counter_next` to be 0 and `execute_from_brom` to True. + // If we keep the `is_powered_on` button in off stage for at-least 4 cycles + // then at "stage3 posedge" program_counter should get updated to 0. + // After that for every "stage0 posedge" till is_powered_on is off, + // program_counter:0 along with execute_from_brom:True will be used to pull + // up and execute first instruction from BROM. + // + // Assumption: No stateful IO devices are connected. + // TODO: Implement `execute_from_brom` update implementation. + wire is_powered_on; + BOOT_CONTROL boot_control(.is_powered_on(is_powered_on)); + + + // MBLOCK is a continous circuit and doesn't depend on clock + // but the behaviour do depend on stage which is abstracted. + wire[15:0] program_counter; + wire[1:0] mblock_selector; + wire[15:0] mblock_address; + wire[31:0] mblock_input; + wire[31:0] mblock_output; + wire mblock_write; + + MBLOCK_MUX mblock_mux( + .mblock_address(mblock_address[15:0]), + .mblock_selector(mblock_selector[1:0]), + .execute_from_brom(execute_from_brom), + .is_stage(is_stage[0:3]), + .address0(program_counter), + .address1(v0_source), + .address2(v1_source), + .address3(v2_source), + .is_write(4'b0000)); + + MBLOCK mblock( + .out(mblock_output), + .selector(mblock_selector), + .in(mblock_input), + .address(mblock_address), + .is_write(mblock_write)); + + // STAGE0 + + // TODO: Ensure MBLOCK supplies expectations. + // MBLOCK_MUX is expected to fetch MBLOCK at `program_counter` from + // BROM / RAM based on `execute_from_brom` and redirect the value + // to full_ins via mblock_output. + + // @stage0 posedge following values should freeze. + wire[7:0] v0_source, v1_source, v2_source, instruction_op; + INS_RESOLVER stage0( + .v0(v0_source), .v1(v1_source), .v2(v2_source), .op(instruction_op), + .full_ins(.mblock_output), + clk[0]); + + // STAGE1 + + // TODO: Breakdown instruction_op into sub-operations + + // TODO: Ensure MBLOCK supplies expectations. + // MBLOCK_MUX is expected to fetch MBLOCK based on v0_source and + // instruction_op breakdowns and redirect the value into v0. + + // @stage1 posedge following should freeze. + wire[31:0] v0; + FETCH_AND_STORE stage1( + .value(v0), + .in(mblock_output), + .clk(clk[1])); + + // STAGE2 + + // TODO: Ensure MBLOCK supplies expectations. + // MBLOCK_MUX is expected to fetch MBLOCK based on v0_source and + // instruction_op breakdowns and redirect the value into v0. + + // @stage2 posedge following should freeze. + wire[31:0] v1; + FETCH_AND_STORE stage2( + .value(v1), + .in(mblock_output), + .clk(clk2)); + + // STAGE3 + // TODO: alu_op should be computed using instruction_op breakdowns. + wire[3:0] alu_op; + wire[31:0] v2; + + wire flag_alu_zero; + ALU alu( + .out(v2), + .is_zero(flag_alu_zero), + .op(alu_op), + .in0(v0), + .in1(v1)); + + // MBLOCK input only comes from ALU output. + assign mblock_input = v2; + + // TODO: jump instruction + PC_NEXT pc_next( + .program_counter_next(program_counter_next), + .program_counter(program_counter), + .is_powered_on(is_powered_on)); + + // @stage3 posedge following should freeze. + wire[15:0] program_counter_next; + flipflop16 pc( + .out(program_counter), + .in(program_counter_next), + .clk(clk3)); + +endmodule \ No newline at end of file diff --git a/emulator/lib/adder.v b/emulator/lib/adder.v new file mode 100644 index 0000000..f1502ba --- /dev/null +++ b/emulator/lib/adder.v @@ -0,0 +1,7 @@ +module ADDER( + output[15:0] out, + input[15:0] in0, + input[15:0] in1); + + assign out = in0+in1; +endmodule diff --git a/emulator/lib/adder_test.v b/emulator/lib/adder_test.v new file mode 100644 index 0000000..269d87c --- /dev/null +++ b/emulator/lib/adder_test.v @@ -0,0 +1,22 @@ +`include "emulator/lib/adder.v" + +module adder_test; + reg[15:0] in0, in1; + wire[15:0] out; + + ADDER dut( + .out(out), + .in0(in0), + .in1(in1)); + + initial begin + in0 = 2536; + in1 = 113; + # 10 + $display("ADDER_TEST: in0=%b in1=%b", in0, in1); + if (out !== 2649) begin + $error("latch failed"); + $fatal(1); + end + end +endmodule diff --git a/emulator/lib/alu.v b/emulator/lib/alu.v new file mode 100644 index 0000000..782ceb8 --- /dev/null +++ b/emulator/lib/alu.v @@ -0,0 +1,25 @@ +module ALU ( + output[31:0] out, + output is_zero, + input[2:0] op, + input[31:0] in0, + input[31:0] in1); + + reg[31:0] mem; + + always @(op, in0, in1) begin + case(op) + 3'b000: mem <= in0+in1; + 3'b001: mem <= in0-in1; + 3'b010: mem <= (in0<>in1); + 3'b100: mem <= (in0); + 3'b101: mem <= (in0&in1); + 3'b110: mem <= (in0|in1); + + endcase + end + assign out[31:0] = mem[31:0]; + assign is_zero = (mem == 0); + +endmodule diff --git a/emulator/lib/alu_test.v b/emulator/lib/alu_test.v new file mode 100644 index 0000000..7e34a65 --- /dev/null +++ b/emulator/lib/alu_test.v @@ -0,0 +1,106 @@ +`include "emulator/lib/alu.v" + +module alu_test; + reg[31:0] in0, in1; + reg[2:0] op; + wire[31:0] out; + wire is_zero; + + ALU dut( + .out(out), + .is_zero(is_zero), + .op(op), + .in0(in0), + .in1(in1)); + + initial begin + // ADD + op = 3'b000; + in0 = 2536; + in1 = 113; + # 10 + $display("ALU_TEST: op=%b in0=%b in1=%b", op, in0, in1); + if (out !== 2649 || is_zero !== 0) begin + $error("alu failed"); + $fatal(1); + end + + // SUB + op = 3'b001; + in0 = 2536; + in1 = 113; + # 10 + $display("ALU_TEST: op=%b in0=%b in1=%b", op, in0, in1); + if (out !== 2423 || is_zero !== 0) begin + $error("alu failed"); + $fatal(1); + end + + // LSR + op = 3'b010; + in0 = 2536; + in1 = 2; + # 10 + $display("ALU_TEST: op=%b in0=%b in1=%b", op, in0, in1); + if (out !== 10144 || is_zero !== 0) begin + $error("alu failed"); + $fatal(1); + end + + // RSR + op = 3'b011; + in0 = 2536; + in1 = 4; + # 10 + $display("ALU_TEST: op=%b in0=%b in1=%b", op, in0, in1); + if (out !== 158 || is_zero !== 0) begin + $error("alu failed"); + $fatal(1); + end + + // Fist Operand + op = 3'b100; + in0 = 2536; + in1 = 4; + # 10 + $display("ALU_TEST: op=%b in0=%b in1=%b", op, in0, in1); + if (out !== 2536 || is_zero !== 0) begin + $error("alu failed"); + $fatal(1); + end + + // CMP as alias of SUB + op = 3'b001; + in0 = 2536; + in1 = 2536; + # 10 + $display("ALU_TEST: op=%b in0=%b in1=%b", op, in0, in1); + if (out !== 0 || is_zero !== 1) begin + $error("alu failed"); + $fatal(1); + end + + // AND + op = 3'b101; + in0 = 2536; + in1 = 113; + # 10 + $display("ALU_TEST: op=%b in0=%b in1=%b", op, in0, in1); + if (out !== 96 || is_zero !== 0) begin + $error("alu failed"); + $fatal(1); + end + + // OR + op = 3'b110; + in0 = 2536; + in1 = 3113; + # 10 + $display("ALU_TEST: op=%b in0=%b in1=%b", op, in0, in1); + if (out !== 3561 || is_zero !== 0) begin + $error("alu failed"); + $fatal(1); + end + + end +endmodule diff --git a/emulator/lib/decoder.v b/emulator/lib/decoder.v new file mode 100644 index 0000000..b01db42 --- /dev/null +++ b/emulator/lib/decoder.v @@ -0,0 +1,22 @@ +module DECODER_4_2( + output[3:0] out, + input[1:0] in); + + assign out[0] = (~in[1] & ~in[0]); + assign out[1] = (~in[1] & in[0]); + assign out[2] = ( in[1] & ~in[0]); + assign out[3] = ( in[1] & in[0]); +endmodule + + +module DECODER_8_3( + output[7:0] out, + input[2:0] in); + + wire [3:0] _out; + DECODER_4_2 d(_out[3:0], in[1:0]); + + wire not_in2 = ~in[2]; + assign out[3:0] = _out[3:0] & {not_in2, not_in2, not_in2, not_in2}; + assign out[7:4] = _out[3:0] & {in[2], in[2], in[2], in[2]}; +endmodule diff --git a/emulator/lib/decoder_test.v b/emulator/lib/decoder_test.v new file mode 100644 index 0000000..eca58cb --- /dev/null +++ b/emulator/lib/decoder_test.v @@ -0,0 +1,61 @@ +`include "emulator/lib/decoder.v" + +module decoder_4_2_test; + reg [1:0] in; + wire [3:0] out; + + DECODER_4_2 dut(.out(out), .in(in)); + + initial begin + in = 2'b00; + # 10 + $display("DECODER_TEST: in=%b out=%b", in, out); + if (out != 4'b0001) begin + $error("decoder failed"); + end + in = 2'b01; + # 10 + $display("DECODER_TEST: in=%b out=%b", in, out); + if (out != 4'b0010) begin + $error("decoder failed"); + end + in = 2'b10; + # 10 + $display("DECODER_TEST: in=%b out=%b", in, out); + if (out != 4'b0100) begin + $error("decoder failed"); + end + in = 2'b11; + # 10 + $display("DECODER_TEST: in=%b out=%b", in, out); + if (out != 4'b1000) begin + $error("decoder failed"); + end + + end +endmodule + + +module decoder_8_3_test; + reg [2:0] in; + wire [7:0] out; + + DECODER_8_3 dut(.out(out), .in(in)); + + initial begin + in = 3'b010; + # 10 + $display("DECODER_TEST: in=%b out=%b", in, out); + if (out != 8'b00000100) begin + $error("decoder failed"); + $fatal(1); + end + in = 3'b101; + # 10 + $display("DECODER_TEST: in=%b out=%b", in, out); + if (out != 8'b00100000) begin + $error("decoder failed"); + $fatal(1); + end + end +endmodule diff --git a/emulator/lib/flipflop.v b/emulator/lib/flipflop.v new file mode 100644 index 0000000..e311b20 --- /dev/null +++ b/emulator/lib/flipflop.v @@ -0,0 +1,30 @@ +module __flipflop #(parameter BITS = 32)( + output[BITS-1:0] out, + input clk, + input[BITS-1:0] in); + + reg[BITS-1:0] mem; + always @(posedge clk) begin + mem[BITS-1:0] <= in[BITS-1:0]; + end + assign out[BITS-1:0] = mem[BITS-1:0]; +endmodule + +module flipflop32( + output[31:0] out, + input clk, + input[31:0] in); + + __flipflop #(.BITS(32)) ff(.out(out), .clk(clk), .in(in)); + +endmodule + +module flipflop16( + output[15:0] out, + input clk, + input[15:0] in); + + __flipflop #(.BITS(16)) ff(.out(out), .clk(clk), .in(in)); + +endmodule + diff --git a/emulator/lib/flipflop_test.v b/emulator/lib/flipflop_test.v new file mode 100644 index 0000000..9c93434 --- /dev/null +++ b/emulator/lib/flipflop_test.v @@ -0,0 +1,58 @@ +`include "emulator/lib/flipflop.v" + +module flipflop_test; + reg[31:0] in; + reg clk; + wire[31:0] out; + + flipflop32 dut(.out(out), .in(in), .clk(clk)); + + initial begin + clk = 0; + in = 32'b00110110110101010100101101101000; + # 10 + clk = 1; + # 10 + $display("FLIPFLOP: in=%b out=%b clk=%b", in, out, clk); + if (out !== 32'b00110110110101010100101101101000) begin + $error("flipflop failed"); + $fatal(1); + end + + clk = 0; + # 10 + in = 32'b01100011111101011011010100000100; + # 10 + $display("FLIPFLOP: in=%b out=%b clk=%b", in, out, clk); + if (out !== 32'b00110110110101010100101101101000) begin + $error("flipflop failed"); + $fatal(1); + end + + in = 32'b10101100011011010010001010011001; + # 10 + $display("FLIPFLOP: in=%b out=%b clk=%b", in, out, clk); + if (out !== 32'b00110110110101010100101101101000) begin + $error("flipflop failed"); + $fatal(1); + end + + clk = 1; + # 10 + $display("FLIPFLOP: in=%b out=%b clk=%b", in, out, clk); + if (out !== 32'b10101100011011010010001010011001) begin + $error("flipflop failed"); + $fatal(1); + end + + clk = 0; + # 10 + in = 32'b10101011001010001101001000011011; + # 10 + $display("FLIPFLOP: in=%b out=%b clk=%b", in, out, clk); + if (out !== 32'b10101100011011010010001010011001) begin + $error("flipflop failed"); + $fatal(1); + end + end +endmodule diff --git a/emulator/lib/latch.v b/emulator/lib/latch.v new file mode 100644 index 0000000..0479815 --- /dev/null +++ b/emulator/lib/latch.v @@ -0,0 +1,13 @@ +module latch_d(output out, input in, input enable); + // we want latch to always be on and returing + // while write when d is active. + + reg mem; + + always @(in, enable) begin + if (enable) begin + mem <= in; + end + end + assign out = mem; +endmodule diff --git a/emulator/lib/latch_test.v b/emulator/lib/latch_test.v new file mode 100644 index 0000000..c9fb2e1 --- /dev/null +++ b/emulator/lib/latch_test.v @@ -0,0 +1,73 @@ +`include "emulator/lib/latch.v" + +module latch_d_test; + reg I, ENABLE; + wire O; + + latch_d dut(.out(O), .in(I), .enable(ENABLE)); + + initial begin + I = 0; + ENABLE = 1; + # 10 + $display("LATCH_TEST: I=%b O=%b ENABLE=%b", I, O, ENABLE); + if (O !== 0) begin + $error("latch failed"); + $fatal(1); + end + + I = 1; + ENABLE = 1; + # 10 + $display("LATCH_TEST: I=%b O=%b ENABLE=%b", I, O, ENABLE); + if (O !== 1) begin + $error("latch failed"); + $fatal(1); + end + + I = 1; + ENABLE = 0; + # 10 + $display("LATCH_TEST: I=%b O=%b ENABLE=%b", I, O, ENABLE); + if (O !== 1) begin + $error("latch failed"); + $fatal(1); + end + + I = 0; + ENABLE = 0; + # 10 + $display("LATCH_TEST: I=%b O=%b ENABLE=%b", I, O, ENABLE); + if (O !== 1) begin + $error("latch failed"); + $fatal(1); + end + + I = 1; + ENABLE = 0; + # 10 + $display("LATCH_TEST: I=%b O=%b ENABLE=%b", I, O, ENABLE); + if (O !== 1) begin + $error("latch failed"); + $fatal(1); + end + + I = 0; + ENABLE = 0; + # 10 + $display("LATCH_TEST: I=%b O=%b ENABLE=%b", I, O, ENABLE); + if (O !== 1) begin + $error("latch failed"); + $fatal(1); + end + + I = 0; + ENABLE = 1; + # 10 + $display("LATCH_TEST: I=%b O=%b ENABLE=%b", I, O, ENABLE); + if (O !== 0) begin + $error("latch failed"); + $fatal(1); + end + end +endmodule diff --git a/emulator/lib/mux.v b/emulator/lib/mux.v new file mode 100644 index 0000000..acf17b4 --- /dev/null +++ b/emulator/lib/mux.v @@ -0,0 +1,55 @@ +// MUX 8-bit IO with 1-bit selection +// B is returned when S is active. +module MUX_8_1( + output [7:0] value, + input[7:0] A,B, + input S); + + wire Snot; + not (Snot, S); + + wire [7:0] x; + wire [7:0] y; + and (x[0], A[0], Snot); + and (x[1], A[1], Snot); + and (x[2], A[2], Snot); + and (x[3], A[3], Snot); + and (x[4], A[4], Snot); + and (x[5], A[5], Snot); + and (x[6], A[6], Snot); + and (x[7], A[7], Snot); + + + and (y[0], B[0], S); + and (y[1], B[1], S); + and (y[2], B[2], S); + and (y[3], B[3], S); + and (y[4], B[4], S); + and (y[5], B[5], S); + and (y[6], B[6], S); + and (y[7], B[7], S); + + or (value[0], x[0], y[0]); + or (value[1], x[1], y[1]); + or (value[2], x[2], y[2]); + or (value[3], x[3], y[3]); + or (value[4], x[4], y[4]); + or (value[5], x[5], y[5]); + or (value[6], x[6], y[6]); + or (value[7], x[7], y[7]); + +endmodule + +module MUX_8_2( + output[7:0] value, + input[7:0] A,B,C,D, + input[1:0] S); + + wire[7:0] val_a, val_b; + + MUX_8_1 m1(.value(val_a), .A(A), .B(B), .S(S[0])); + MUX_8_1 m2(.value(val_b), .A(C), .B(D), .S(S[0])); + MUX_8_1 m3(.value(value), .A(val_a), .B(val_b), .S(S[1])); + +endmodule + diff --git a/emulator/lib/mux_test.v b/emulator/lib/mux_test.v new file mode 100644 index 0000000..afa5c09 --- /dev/null +++ b/emulator/lib/mux_test.v @@ -0,0 +1,70 @@ +`include "emulator/lib/mux.v" + +module mux_8_1_test; + reg[7:0] A = 8'b01100101; + reg[7:0] B = 8'b10101100; + reg S; + wire[7:0] out; + + MUX_8_1 dut(.value(out), .A(A), .B(B), .S(S)); + + initial begin + S = 0; + # 10 + $display("MUX_TEST: A=%b B=%b S=%b OUT=%b", A, B, S, out); + if (out !== A) begin + $error("mux failed"); + $fatal(1); + end + S = 1; + # 10 + $display("MUX_TEST: A=%b B=%b S=%b OUT=%b", A, B, S, out); + if (out !== B) begin + $error("mux failed"); + $fatal(1); + end + end +endmodule + + +module mux_8_2_test; + reg[7:0] A = 8'b11101110; + reg[7:0] B = 8'b11100110; + reg[7:0] C = 8'b11100100; + reg[7:0] D = 8'b00110111; + reg[1:0] S; + wire[7:0] out; + + MUX_8_2 dut(.value(out), .A(A), .B(B), .C(C), .D(D), .S(S)); + + initial begin + S = 0; + # 10 + $display("MUX_TEST: A=%b B=%b S=%b OUT=%b", A, B, S, out); + if (out !== A) begin + $error("mux failed"); + $fatal(1); + end + S = 1; + # 10 + $display("MUX_TEST: A=%b B=%b S=%b OUT=%b", A, B, S, out); + if (out !== B) begin + $error("mux failed"); + $fatal(1); + end + S = 2; + # 10 + $display("MUX_TEST: A=%b B=%b S=%b OUT=%b", A, B, S, out); + if (out !== C) begin + $error("mux failed"); + $fatal(1); + end + S = 3; + # 10 + $display("MUX_TEST: A=%b B=%b S=%b OUT=%b", A, B, S, out); + if (out !== D) begin + $error("mux failed"); + $fatal(1); + end + end +endmodule diff --git a/emulator/module/boot_control.v b/emulator/module/boot_control.v new file mode 100644 index 0000000..7b376ec --- /dev/null +++ b/emulator/module/boot_control.v @@ -0,0 +1,12 @@ +module BOOT_CONTROL(output is_powered_on); + // As we don't have a button to control boot. + // We say boot will automatically get pressed after + // 100 time units. + reg _is_powered_on; + initial begin + assign _is_powered_on = 0; + # 100 + assign _is_powered_on = 1; + end + assign is_powered_on = _is_powered_on; +endmodule \ No newline at end of file diff --git a/emulator/module/boot_control_test.v b/emulator/module/boot_control_test.v new file mode 100644 index 0000000..a9872b9 --- /dev/null +++ b/emulator/module/boot_control_test.v @@ -0,0 +1,28 @@ +`include "emulator/module/boot_control.v" + +module boot_control_test; + wire is_powered_on; + BOOT_CONTROL dut(.is_powered_on(is_powered_on)); + + initial begin + # 5 + $display("BOOT_CONTROL: is_powered_on=%b", is_powered_on); + if (is_powered_on !== 0) begin + $error("BOOT_CONTROL failed"); + $fatal(1); + end + # 80 + $display("BOOT_CONTROL: is_powered_on=%b", is_powered_on); + if (is_powered_on !== 0) begin + $error("BOOT_CONTROL failed"); + $fatal(1); + end + # 20 + $display("BOOT_CONTROL: is_powered_on=%b", is_powered_on); + if (is_powered_on !== 1) begin + $error("BOOT_CONTROL failed"); + $fatal(1); + end + end +endmodule + diff --git a/emulator/module/clock.v b/emulator/module/clock.v new file mode 100644 index 0000000..26bb7a5 --- /dev/null +++ b/emulator/module/clock.v @@ -0,0 +1,54 @@ +module CLOCK( + output[0:3] clk, + output[0:3] is_stage); + + // stages: + // The architecture splits instructions execution in 4 stages. + // stage0: fetch and expand instruction at program_counter. + // stage1: fetch value0 (usually operand0) from desidered source. + // stage2: fetch value1 (usually operand1) from desidered source. + // stage3: write value2 (result) to desidered source and update + // program_counter if and when desired. + // hw_clk (internal): + // clock signal + // clk[0],clk[1],clk[2],clk[3]: + // clk splitted into 4. Each clk{i} posedge represents flipflop write + // of stage{i}. + // e.g. stage1 is active b/w clk0 and clk1 posedge. + + + // TODO: make clock more realistic. + reg[0:3] hw_clk; + reg[0:3] _is_stage; + initial begin + forever begin + #10 + _is_stage[0]=0; + hw_clk[0]=1; + _is_stage[1]=1; + #10 + hw_clk[0]=0; + #10 + _is_stage[1]=0; + hw_clk[1]=1; + _is_stage[2]=1; + #10 + hw_clk[1]=0; + #10 + _is_stage[2]=0; + hw_clk[2]=1; + _is_stage[3]=1; + #10 + hw_clk[2]=0; + #10 + _is_stage[3]=0; + hw_clk[3]=1; + _is_stage[0]=1; + #10 + hw_clk[3]=0; + end + end + + assign clk[0:3] = hw_clk[0:3]; + assign is_stage[0:3] = _is_stage[0:3]; +endmodule diff --git a/emulator/module/clock_test.v b/emulator/module/clock_test.v new file mode 100644 index 0000000..6b54f31 --- /dev/null +++ b/emulator/module/clock_test.v @@ -0,0 +1,44 @@ +`include "emulator/module/clock.v" + +module clock_test; + wire[0:3] clk; + wire[0:3] is_stage; + CLOCK dut( + .clk(clk[0:3]), + .is_stage(is_stage[0:3])); + + initial begin + # 75 + $display("CLK: clk=%b is_stage=%b", clk, is_stage); + if (clk !== 4'b0001 || is_stage !== 4'b1000) begin + $error("clock failed"); + $fatal(1); + end + # 20 + $display("CLK: clk=%b is_stage=%b", clk, is_stage); + if (clk !== 4'b1000 || is_stage !== 4'b0100) begin + $error("clock failed"); + $fatal(1); + end + # 20 + $display("CLK: clk=%b is_stage=%b", clk, is_stage); + if (clk !== 4'b0100 || is_stage !== 4'b0010) begin + $error("clock failed"); + $fatal(1); + end + # 20 + $display("CLK: clk=%b is_stage=%b", clk, is_stage); + if (clk !== 4'b0010 || is_stage !== 4'b0001) begin + $error("clock failed"); + $fatal(1); + end + # 20 + $display("CLK: clk=%b is_stage=%b", clk, is_stage); + if (clk !== 4'b0001 || is_stage !== 4'b1000) begin + $error("clock failed"); + $fatal(1); + end + $finish(); + end +endmodule + diff --git a/emulator/module/mblock/mblock.v b/emulator/module/mblock/mblock.v new file mode 100644 index 0000000..d94ec13 --- /dev/null +++ b/emulator/module/mblock/mblock.v @@ -0,0 +1,35 @@ +`include "emulator/module/mblock/mconst.v" +`include "emulator/module/mblock/ram.v" +`include "emulator/module/mblock/rom.v" +`include "emulator/lib//mux.v" + +module MBLOCK(output[31:0] out, + input[1:0] selector, + input[31:0] in, + input[15:0] address, + input is_write); + + // TODO: How can be careful about accidental write occurance + // due to race between how the values are propogated? + + wire[31:0] out0, out1, out2, out3; + + ROM_BOOT romb(.out(out0), + .address(address)); + + RAM_32bit_16aline ram(.out(out1), + .in(in), + .address(address), + .is_write(is_write & ~selector[1] & selector[0])); + + // selector 10 is reserved for IO + + MCONST mconst(.out(out3), + .in(address)); + + MUX_8_2 m0(.value(out[ 7: 0]), .A(out0[ 7: 0]), .B(out1[ 7: 0]), .D(out3[ 7: 0]), .S(selector[1:0])); + MUX_8_2 m1(.value(out[15: 8]), .A(out0[15: 8]), .B(out1[15: 8]), .D(out3[15: 8]), .S(selector[1:0])); + MUX_8_2 m2(.value(out[23:16]), .A(out0[23:16]), .B(out1[23:16]), .D(out3[23:16]), .S(selector[1:0])); + MUX_8_2 m3(.value(out[31:24]), .A(out0[31:24]), .B(out1[31:24]), .D(out3[31:24]), .S(selector[1:0])); + +endmodule diff --git a/emulator/module/mblock/mblock_test.v b/emulator/module/mblock/mblock_test.v new file mode 100644 index 0000000..0b81c30 --- /dev/null +++ b/emulator/module/mblock/mblock_test.v @@ -0,0 +1,68 @@ +`include "emulator/module/mblock/mblock.v" + +module BLOCK_32bit_16aline_test; + wire[31:0] out; + reg[15:0] address; + reg[1:0] selector; + reg[31:0] in; + reg is_write; + + localparam [31:0] INPUT16z = 16'bzzzzzzzzzzzzzzzz; + localparam [31:0] INPUT32z = 32'bzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz; + localparam [31:0] INPUT1 = 32'b11100101111110000100101010110001; + localparam [31:0] INPUT2 = 32'b01011100100011000110101000000001; + + MBLOCK dut(.out(out), + .selector(selector), + .in(in), + .address(address), + .is_write(is_write)); + + initial begin + selector = 2'b01; // RAM write + address = 16'b1011100000111010; + in = INPUT1; + # 10 + is_write = 1; + # 10 + $display("MBLOCK: RAM_TEST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== INPUT1) begin + $error("mblock failed"); + $fatal(1); + end + + selector = 2'b00; // ROM_BOOT + address = 16'b0000000000000001; + is_write = 1'bz; + in = INPUT32z; + # 10 + $display("MBLOCK: ROM_TEST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== 32'b11111100101011001101000010101001) begin + $error("mblock failed"); + $fatal(1); + end + + selector = 2'b11; // MCONST + address = 16'b0001100100111101; + is_write = 1'bz; + in = INPUT32z; + # 10 + $display("MBLOCK: MCONST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== 32'b00000000000000000001100100111101) begin + $error("mblock failed"); + $fatal(1); + end + + selector = 2'b01; // RAM read + address = 16'b1011100000111010; + is_write = 0; + # 10 + in = INPUT32z; + # 10 + $display("MBLOCK: RAM_TEST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== INPUT1) begin + $error("mblock failed"); + $fatal(1); + end + end +endmodule diff --git a/emulator/module/mblock/mconst.v b/emulator/module/mblock/mconst.v new file mode 100644 index 0000000..f6e8242 --- /dev/null +++ b/emulator/module/mblock/mconst.v @@ -0,0 +1,9 @@ +module MCONST( + output[31:0] out, + input[15:0] in); + // returns constant value + + assign out[31:16] = 16'b0000000000000000; + assign out[15:0] = in[15:0]; + +endmodule diff --git a/emulator/module/mblock/mconst_test.v b/emulator/module/mblock/mconst_test.v new file mode 100644 index 0000000..b3d6570 --- /dev/null +++ b/emulator/module/mblock/mconst_test.v @@ -0,0 +1,26 @@ +`include "emulator/module/mblock/mconst.v" + +module MCONST_test; + reg[31:0] in; + wire[31:0] out; + + MCONST dut(.out(out), + .in(in)); + + initial begin + in = 16'b0010111100010010; + # 10 + $display("MCONST_TEST: in=%b out=%b", in, out); + if (out !== 32'b00000000000000000010111100010010) begin + $error("mconst failed"); + $fatal(1); + end + in = 16'b1001011000011000; + # 10 + $display("MCONST_TEST: in=%b out=%b", in, out); + if (out !== 32'b00000000000000001001011000011000) begin + $error("mconst failed"); + $fatal(1); + end + end +endmodule diff --git a/emulator/module/mblock/ram.v b/emulator/module/mblock/ram.v new file mode 100644 index 0000000..31be343 --- /dev/null +++ b/emulator/module/mblock/ram.v @@ -0,0 +1,15 @@ +module RAM_32bit_16aline(output[31:0] out, + input[31:0] in, + input[15:0] address, + input is_write); + // 4 * 64KB RAM + + reg[31:0] mem [65535:0]; + always @(address, in, is_write) begin + if (is_write) begin + mem[address] <= in; + end + end + assign out = mem[address]; + +endmodule diff --git a/emulator/module/mblock/ram_test.v b/emulator/module/mblock/ram_test.v new file mode 100644 index 0000000..d4cfb5b --- /dev/null +++ b/emulator/module/mblock/ram_test.v @@ -0,0 +1,69 @@ +`include "emulator/module/mblock/ram.v" + +module RAM_32bit_16aline_test; + reg[15:0] address; + reg is_write; + reg[31:0] in; + wire[31:0] out; + + localparam [31:0] INPUT1 = 32'b11100101111110000100101010110001; + localparam [31:0] INPUT2 = 32'b01011100100011000110101000000001; + + RAM_32bit_16aline dut(.out(out), + .in(in), + .address(address), + .is_write(is_write)); + + initial begin + address = 16'b1100001110111100; + is_write = 1; + in = INPUT1; + # 10 + $display("RAM_TEST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== INPUT1) begin + $error("ram failed"); + $fatal(1); + end + + address = 16'b1100001110111100; + is_write = 0; + # 10 + in = INPUT2; + # 10 + $display("RAM_TEST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== INPUT1) begin + $error("ram failed"); + $fatal(1); + end + + address = 16'b1011100000111010; + in = INPUT2; + # 10 + is_write = 1; + # 10 + $display("RAM_TEST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== INPUT2) begin + $error("ram failed"); + $fatal(1); + end + + is_write = 0; + # 10 + address = 16'b1100001110111100; + # 10 + $display("RAM_TEST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== INPUT1) begin + $error("ram failed"); + $fatal(1); + end + + address = 16'b1011100000111010; + # 10 + $display("RAM_TEST: address=%b is_write=%b in=%b out=%b", address, is_write, in, out); + if (out !== INPUT2) begin + $error("ram failed"); + $fatal(1); + end + + end +endmodule diff --git a/emulator/module/mblock/rom.v b/emulator/module/mblock/rom.v new file mode 100644 index 0000000..5fe74c4 --- /dev/null +++ b/emulator/module/mblock/rom.v @@ -0,0 +1,26 @@ +module _ROM_32bit_16aline( + output[31:0] out, + input[15:0] address); + // 4 * 64KB ROM + + parameter filename = "/dev/null"; + + reg[31:0] buffer[0:65535]; + initial begin + $readmemb(filename, buffer); + $display("ROM: %h", buffer[0]); + end + + assign out = buffer[address]; + +endmodule + +module ROM_BOOT( + output[31:0] out, + input[15:0] address); + // 4 * 64KB ROM + + _ROM_32bit_16aline #(.filename("emulator/module/mblock/rom_boot.bin")) + dut(.out(out), + .address(address)); +endmodule diff --git a/emulator/module/mblock/rom_boot.bin b/emulator/module/mblock/rom_boot.bin new file mode 100644 index 0000000..cfa321f --- /dev/null +++ b/emulator/module/mblock/rom_boot.bin @@ -0,0 +1,10 @@ +10000010101111110111100001010111 +11111100101011001101000010101001 +00000001001101101111110110011101 +11111010111111110100000111111110 +00010110010001100011011100001001 +00111110100111011000101110111011 +10000011101110100011100000011000 +10011111011110101000001000101001 +00110101001001011000000100011101 +01110000101110000000100100011101 diff --git a/emulator/module/mblock/rom_test.bin b/emulator/module/mblock/rom_test.bin new file mode 100644 index 0000000..cfa321f --- /dev/null +++ b/emulator/module/mblock/rom_test.bin @@ -0,0 +1,10 @@ +10000010101111110111100001010111 +11111100101011001101000010101001 +00000001001101101111110110011101 +11111010111111110100000111111110 +00010110010001100011011100001001 +00111110100111011000101110111011 +10000011101110100011100000011000 +10011111011110101000001000101001 +00110101001001011000000100011101 +01110000101110000000100100011101 diff --git a/emulator/module/mblock/rom_test.v b/emulator/module/mblock/rom_test.v new file mode 100644 index 0000000..3629dab --- /dev/null +++ b/emulator/module/mblock/rom_test.v @@ -0,0 +1,59 @@ +`include "emulator/module/mblock/rom.v" + +module rom_test; + reg[15:0] address; + wire[31:0] out; + + _ROM_32bit_16aline #(.filename("emulator/module/mblock/rom_test.bin")) + dut(.out(out), + .address(address)); + + initial begin + address = 0; + # 10 + $display("ROM_TEST: address=%b out=%b", address, out); + if (out !== 32'b10000010101111110111100001010111) begin + $error("rom failed"); + $fatal(1); + end + address = 1; + # 10 + $display("ROM_TEST: address=%b out=%b", address, out); + if (out !== 32'b11111100101011001101000010101001) begin + $error("rom failed"); + $fatal(1); + end + address = 3; + # 10 + $display("ROM_TEST: address=%b out=%b", address, out); + if (out !== 32'b11111010111111110100000111111110) begin + $error("rom failed"); + $fatal(1); + end + address = 7; + # 10 + $display("ROM_TEST: address=%b out=%b", address, out); + if (out !== 32'b10011111011110101000001000101001) begin + $error("rom failed"); + $fatal(1); + end + end +endmodule + +module rom_boot_test; + reg[15:0] address; + wire[31:0] out; + + ROM_BOOT dut(.out(out), + .address(address)); + + initial begin + address = 0; + # 10 + $display("ROM_TEST: address=%b out=%b", address, out); + if (out !== 32'b10000010101111110111100001010111) begin + $error("rom failed"); + $fatal(1); + end + end +endmodule diff --git a/emulator/module/mblock_mux.v b/emulator/module/mblock_mux.v new file mode 100644 index 0000000..a0dec38 --- /dev/null +++ b/emulator/module/mblock_mux.v @@ -0,0 +1,56 @@ +module MBLOCK_MUX(output[31:0] mblock_address, + output[1:0] mblock_selector, + + input execute_from_brom, + input[0:3] is_stage, + input[15:0] address0, // program_counter + input[15:0] address1, // v0_source + input[15:0] address2, // v1_source + input[15:0] address3, // v2_source + input[3:0] is_write, + ); + + // TODO Starts + // TODO: We should support I/O and const in mblock_selector + // during stage0, before stage0 posedge + // mblock_selector[0] = execute_from_brom; + // mblock_selector[1] = 0; + // after stage0 posedge + // mblock_selector = will come from instruction_op + // after stage1 posedge + // mblock_selector = will come from instruction_op + // after stage2 posedge + // mblock_selector = will come from instruction_op + // Challenge: How will we will fit, 3 mux_selector, ALU_OP and + // instructions classes all within 1 byte... + // TODO Ends + + always @(is_stage, is_write, address0, address1, address2, address3) + begin + if (is_stage == 2'b00) + begin + // Active RAM or BROM based upon $execute_from_brom. + assign mblock_selector[1:0] = {0, execute_from_brom}; + end + else if (is_stage == 2'b01) + begin + // TODO: mblock_selector compute from instruction_op + end + else if (is_stage == 2'b10) + begin + // TODO: mblock_selector compute from instruction_op + end + else + begin + // TODO: mblock_selector compute from instruction_op + end + else + + end + + MUX_8_2 m0(.value(mblock_address[ 7: 0]), .A(address0[ 7: 0]), .B(address1[ 7: 0]), ,C(address2[ 7: 0]), .D(address3[ 7: 0]), .S(mblock_selector[1:0])); + MUX_8_2 m1(.value(mblock_address[15: 8]), .A(address0[15: 8]), .B(address1[15: 8]), ,C(address2[15: 8]), .D(address3[15: 8]), .S(mblock_selector[1:0])); + MUX_8_2 m2(.value(mblock_address[23:16]), .A(address0[23:16]), .B(address1[23:16]), ,C(address2[23:16]), .D(address3[23:16]), .S(mblock_selector[1:0])); + MUX_8_2 m3(.value(mblock_address[31:24]), .A(address0[31:24]), .B(address1[31:24]), ,C(address2[31:24]), .D(address3[31:24]), .S(mblock_selector[1:0])); + +endmodule diff --git a/emulator/module/pc_next.v b/emulator/module/pc_next.v new file mode 100644 index 0000000..97f60fc --- /dev/null +++ b/emulator/module/pc_next.v @@ -0,0 +1,37 @@ +module PC_NEXT( + inout [15:0] program_counter, + input is_powered_on, + input clk); + + // TODO: we should support jmp like instructions. + + wire[15:0] program_counter_plus_one; + wire[15:0] program_counter_next; + + ADDER pc_adder( + .out(program_counter_plus_one), + .in0(program_counter), + .in1(1)); + + AND(program_counter_next[00], program_counter_plus_one[00], is_powered_on); + AND(program_counter_next[01], program_counter_plus_one[01], is_powered_on); + AND(program_counter_next[02], program_counter_plus_one[02], is_powered_on); + AND(program_counter_next[03], program_counter_plus_one[03], is_powered_on); + AND(program_counter_next[04], program_counter_plus_one[04], is_powered_on); + AND(program_counter_next[05], program_counter_plus_one[05], is_powered_on); + AND(program_counter_next[06], program_counter_plus_one[06], is_powered_on); + AND(program_counter_next[07], program_counter_plus_one[07], is_powered_on); + AND(program_counter_next[08], program_counter_plus_one[08], is_powered_on); + AND(program_counter_next[09], program_counter_plus_one[09], is_powered_on); + AND(program_counter_next[10], program_counter_plus_one[10], is_powered_on); + AND(program_counter_next[11], program_counter_plus_one[11], is_powered_on); + AND(program_counter_next[12], program_counter_plus_one[12], is_powered_on); + AND(program_counter_next[13], program_counter_plus_one[13], is_powered_on); + AND(program_counter_next[14], program_counter_plus_one[14], is_powered_on); + AND(program_counter_next[15], program_counter_plus_one[15], is_powered_on); + + flipflop16 pc( + .out(program_counter), + .in(program_counter_next), + .clk(clk)); +endmodule diff --git a/emulator/module/stage0_ins_resolver.v b/emulator/module/stage0_ins_resolver.v new file mode 100644 index 0000000..083c61b --- /dev/null +++ b/emulator/module/stage0_ins_resolver.v @@ -0,0 +1,14 @@ +module INS_RESOLVER(output[7:0] v0, v1, v2, op, + input[32:0] full_ins, + input clk); + + wire[31:0] mem_ins; + flipflop32 f(.out(mem_ins[31:0]), .in(full_ins[31:0]), .clk(clk)); + + assign v0[7:0] = full_ins[ 7: 0]; + assign v1[7:0] = full_ins[15: 8]; + // TODO: v2_source can be drived from v1 + assign v2[7:0] = full_ins[23:16]; + assign op[7:0] = full_ins[31:24]; + +endmodule diff --git a/emulator/module/stage1or2_store.v b/emulator/module/stage1or2_store.v new file mode 100644 index 0000000..f36ea0e --- /dev/null +++ b/emulator/module/stage1or2_store.v @@ -0,0 +1,7 @@ +module FETCH_AND_STORE(output[31:0] value, + input[31:0] in, + input clk); + + flipflop32 f(.out(value[31:0]), .in(in[31:0]), .clk(clk)); + +endmodule diff --git a/emulator/specs.py b/emulator/specs.py new file mode 100644 index 0000000..04c48ea --- /dev/null +++ b/emulator/specs.py @@ -0,0 +1,8 @@ +RAM_ADDRESS_LINES = 16 +ROM_ADDRESS_LINES = 8 + +WORD_SIZE = 8 +ROM_MAXSIZE = 2**(ROM_ADDRESS_LINES) * 8 +RAM_SIZE = 2**(RAM_ADDRESS_LINES) * 8 + +PROGRAM_START = 0x20 diff --git a/greetings.c b/greetings.c deleted file mode 100644 index 8fbd6ae..0000000 --- a/greetings.c +++ /dev/null @@ -1,47 +0,0 @@ -#include -#include -#include -#include -#include - -int is_clicked() { - // check bit-0 - uint8_t *key = &MEM_IO[2]; - if((*key)&1) { - // can lead to race conditions, but it's ok - (*key) &= ~1; // reset bit - // TODO: need some sort of flush - } -} - -void print_character(char c) { - if(c>=128) return; - // display char for 1s - for(int dur=0;dur<200;dur++) { - for(int i=0;i<8;i++) { - MEM_IO[0]=1< - -extern uint8_t font[128][8]; diff --git a/include/opc/io.h b/include/opc/io.h deleted file mode 100644 index a64852d..0000000 --- a/include/opc/io.h +++ /dev/null @@ -1,5 +0,0 @@ -#include - -// MEM_IO[0..2] connects to 8x8 display -// MEM_IO[2] connects to keys, only MEM_IO[2][0] is used -extern uint8_t MEM_IO[3]; diff --git a/include/opc/logging.h b/include/opc/logging.h deleted file mode 100644 index 4650ab5..0000000 --- a/include/opc/logging.h +++ /dev/null @@ -1,2 +0,0 @@ -int loggingf(char *fmt, ...); -int logging_screenf(char *fmt, ...); diff --git a/include/opc/time.h b/include/opc/time.h deleted file mode 100644 index dde0670..0000000 --- a/include/opc/time.h +++ /dev/null @@ -1,8 +0,0 @@ -// TODO: The value is rough estimation for a regular laptop. -static int TICKS_PER_MS = 450000; - -void sleep(int ms) { - int _x=1; - int steps = TICKS_PER_MS * ms; - for(int i=0;i - -// Font source: https://github.com/dhepper/font8x8/blob/master/font8x8_basic.h -// - International Business Machines (public domain VGA fonts) - -uint8_t font[128][8] = { - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (null) - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) - {0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) - {0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") - {0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) - {0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) - {0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) - {0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) - {0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') - {0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() - {0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) - {0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) - {0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) - {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) - {0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) - {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) - {0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) - {0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) - {0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) - {0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) - {0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) - {0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) - {0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) - {0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) - {0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) - {0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) - {0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) - {0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) - {0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;) - {0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) - {0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) - {0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) - {0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) - {0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) - {0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) - {0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) - {0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) - {0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) - {0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) - {0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) - {0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) - {0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) - {0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) - {0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) - {0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) - {0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) - {0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) - {0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) - {0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) - {0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) - {0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) - {0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) - {0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) - {0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) - {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) - {0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) - {0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) - {0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) - {0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) - {0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) - {0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) - {0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) - {0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) - {0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) - {0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) - {0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) - {0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) - {0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) - {0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) - {0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) - {0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) - {0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) - {0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) - {0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) - {0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) - {0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) - {0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) - {0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) - {0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) - {0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) - {0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) - {0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) - {0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) - {0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) - {0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) - {0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) - {0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) - {0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) - {0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) - {0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) - {0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) - {0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) - {0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) - {0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) - {0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F -}; diff --git a/lib/logging.c b/lib/logging.c deleted file mode 100644 index ac93654..0000000 --- a/lib/logging.c +++ /dev/null @@ -1,27 +0,0 @@ -#ifdef OPC_SIM_ENABLED -#include -#include - -int loggingf(char *fmt, ...) { - va_list args; - va_start (args, fmt); - int ret = vfprintf(stderr, fmt, args); - fflush(stdout); - va_end (args); - return ret; -} -int logging_screenf(char *fmt, ...) { - va_list args; - va_start (args, fmt); - int ret = vfprintf(stdout, fmt, args); - fflush(stdout); - va_end (args); - return ret; -} - -#else - -int loggingf(char *fmt, ...) {} -int logging_screenf(char *fmt, ...){} - -#endif \ No newline at end of file diff --git a/lib/ourpc.asm b/lib/ourpc.asm deleted file mode 100644 index cd058ec..0000000 --- a/lib/ourpc.asm +++ /dev/null @@ -1,11 +0,0 @@ -global _start -global MEM_IO -extern main - -[SECTION .text] -_start: - jmp main - hlt - -[SECTION .bss] -MEM_IO resb 3 diff --git a/lib/sim.asm b/lib/sim.asm deleted file mode 100644 index 3a7abbb..0000000 --- a/lib/sim.asm +++ /dev/null @@ -1,6 +0,0 @@ -global MEM_IO - -[SECTION .text] - -[SECTION .bss] -MEM_IO resb 3 diff --git a/lib/sim.c b/lib/sim.c deleted file mode 100644 index 7af7650..0000000 --- a/lib/sim.c +++ /dev/null @@ -1,72 +0,0 @@ -#ifdef OPC_SIM_ENABLED - -#include -#include -#include -#include -#include - -static int LED8x8[8][8]={0}; -static const int LED_MAX_CHARGE = 10000; -static const int LED_STEP_CHARGE = 20; -static const int LED_MIN_CHARGE = LED_MAX_CHARGE/LED_STEP_CHARGE; - - -void sim_display_charge() { - while(1) { - // break; - int row_active = MEM_IO[0]; - int col_active = MEM_IO[1]; - // loggingf("> %d, %d\n", row_active, col_active); - for(int i=0;i<8;i++) if(row_active&(1<LED_MIN_CHARGE) LED8x8[i][j]-=LED_MIN_CHARGE; - } - } - sleep(1); // we want 20ms to should fully discharge - - sched_yield(); - } -} - -void sim_display() { - while(1) { - logging_screenf("\033[2J\033[H"); - logging_screenf("Screen 8x8\n"); - for(int i=0;i<8;i++) { - for(int j=0;j<8;j++) { - logging_screenf("%c", LED8x8[i][j]>LED_MIN_CHARGE?('#'):' '); - // loggingf("%03d ", LED8x8[i][j]); - } - logging_screenf("\n"); - } - sleep(200); - sched_yield(); - - } -} - -__attribute__ ((__constructor__)) -void init_simulator(void) { - pthread_t thread_id; - pthread_t thread_id2; - pthread_t thread_id3; - pthread_create(&thread_id, NULL, sim_display, NULL); - pthread_create(&thread_id2, NULL, sim_display_charge, NULL); - pthread_create(&thread_id3, NULL, sim_display_discharge, NULL); -} - -#endif \ No newline at end of file diff --git a/linker.ld b/linker.ld deleted file mode 100644 index 2dc7d5f..0000000 --- a/linker.ld +++ /dev/null @@ -1,8 +0,0 @@ -SECTIONS -{ -. = 0x10000; -.text : { *(.text) } -. = 0x8000000; -.data : { *(.data) } -.bss : { *(.bss) } -} \ No newline at end of file diff --git a/output/programs/3_led_switch.bin b/output/programs/3_led_switch.bin new file mode 100644 index 0000000..cce40d0 --- /dev/null +++ b/output/programs/3_led_switch.bin @@ -0,0 +1,4 @@ +00001100 01000010 00000001 00000001 +00000101 01001000 00000001 00000110 +00000001 11001111 00000000 00000000 +01000000 diff --git a/planner/__init__.py b/planner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/planner/__main__.py b/planner/__main__.py new file mode 100644 index 0000000..83d7b51 --- /dev/null +++ b/planner/__main__.py @@ -0,0 +1,48 @@ +import argparse +import logging + + +from planner.asm import program_parser +from planner.sim import bin_parser +from planner.sim import devices + + +def args_parser(): + parser = argparse.ArgumentParser() + parser.add_argument("-v", "--verbose", action="store_true") + subparsers = parser.add_subparsers(dest="source") + + asm_parser = subparsers.add_parser("asm", help="Parse Assembly") + asm_parser.add_argument("asm_file") + asm_parser.add_argument("-r", "--resolved", action="store_true") + asm_parser.add_argument("-b", "--rom-binary", + action="store_true", + help="prints instruction as binary in ROM (metadata+program) format." + ) + + bin_parser = subparsers.add_parser("bin", help="Parse Binary") + bin_parser.add_argument("bin_file") + return parser + +def main(): + args = args_parser().parse_args() + if args.verbose: + logging.basicConfig(level=logging.INFO) + if args.source == "asm": + asm = program_parser.AsmParser() + with open(args.asm_file, "r") as f: + for line in f.readlines(): + asm.parse_line(line) + output = asm.get_str(resolved=args.resolved, rom_binary=args.rom_binary) + print(output) + if args.source == "bin": + with open(args.bin_file, "r") as f: + _bin = bin_parser.BinRunner(f.read()) + _bin.set_input_device(5, devices.Numpad("id(5), range(0-9)")) + _bin.set_output_device(6, devices.IntegerOutput("Screen6")) + while True: + _bin.step() + + +if __name__ == '__main__': + main() diff --git a/planner/asm/__init__.py b/planner/asm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/planner/asm/line_parser.py b/planner/asm/line_parser.py new file mode 100644 index 0000000..99b7d5a --- /dev/null +++ b/planner/asm/line_parser.py @@ -0,0 +1,49 @@ +from enum import Enum +import re +from typing import List, Tuple, Optional +from planner import unit, util + + + +def parse_line(line: str) -> Tuple[Optional[str], Optional[List[Tuple[unit.Operand, unit.LazyLabel]]]]: + try: + comment_index = line.index("#") + line = line[:comment_index] + except ValueError: + # no comments + pass + line = line.strip() + if len(line) == 0: + return (None, None) + line = line + " " + first_space = line.index(" ") + instruction = line[:first_space].upper() + + tokens = [] + + # Parsing operands + line = line[first_space:].strip() + operands = line.split(",") + for op in operands: + op = op.strip() + if len(op) == 0: + raise ValueError("no-length operand found in %s" % line) + if op.startswith("R"): + if len(op) != 2 or op[1] not in "0123456789": + raise ValueError("Invalid mem-register {op} provided, only R0..R9 supported.") + optype = unit.Operand.ADDRESS + op = op[1:] + elif op.startswith("[") and op.endswith("]"): + optype = unit.Operand.ADDRESS + op = op[1:-1].strip() + else: + optype = unit.Operand.CONSTANT + try: + value = unit.LazyLabel(util.LABEL_CONSTANT, int(op, 0)) # automatically understand base-10 and base-16 + except ValueError as e: + if util.is_valid_label(op): + value = unit.LazyLabel(op) + else: + raise ValueError(f"Invalid operand value '{str(e)}' in '{str(line)}'") + tokens.append((optype, value)) + return (instruction, tokens) diff --git a/planner/asm/line_parser_test.py b/planner/asm/line_parser_test.py new file mode 100644 index 0000000..a658542 --- /dev/null +++ b/planner/asm/line_parser_test.py @@ -0,0 +1,39 @@ +from unittest import TestCase + +from planner.asm import line_parser +from planner import unit + + +class ParserTest(TestCase): + + def test_parse_line_success(self): + name, tokens = line_parser.parse_line("mov [10], [20]") + self.assertEqual(name, "MOV") + self.assertEqual(tokens, [ + (unit.Operand.ADDRESS, 10), + (unit.Operand.ADDRESS, 20)]) + + name, tokens = line_parser.parse_line("movc [30], 0x15") + self.assertEqual(name, "MOVC") + self.assertEqual(tokens, [ + (unit.Operand.ADDRESS, 30), + (unit.Operand.CONSTANT, 21)]) + + name, tokens = line_parser.parse_line("jmp 10") + self.assertEqual(name, "JMP") + self.assertEqual(tokens, [ + (unit.Operand.CONSTANT, 10)]) + + name, tokens = line_parser.parse_line("add [ 0x16 ] , [15 ]") + self.assertEqual(name, "ADD") + self.assertEqual(tokens, [ + (unit.Operand.ADDRESS, 22), + (unit.Operand.ADDRESS, 15)]) + + def test_parse_line_failures(self): + with self.assertRaises(ValueError): + line_parser.parse_line("mov [10],, [20]") + with self.assertRaises(ValueError): + line_parser.parse_line("mov [10], [[20]]") + with self.assertRaises(ValueError): + line_parser.parse_line("mov 10 10") diff --git a/planner/asm/program_parser.py b/planner/asm/program_parser.py new file mode 100644 index 0000000..e5887b0 --- /dev/null +++ b/planner/asm/program_parser.py @@ -0,0 +1,212 @@ +import logging +from typing import List, Union, Optional + +from planner import instruction, unit, util + +from planner.asm import line_parser + +class LabelsManager: + def __init__(self): + self.labels = {} + self.passive_labels = [] + + def add_lazy(self, lazy: unit.LazyLabel): + if lazy.name == util.LABEL_CONSTANT: + # do nothing + return + self.passive_labels.append(lazy) + + def propogate(self): + for label in self.passive_labels: + if label.name not in self.labels: + raise ValueError(f"'{label.name}' couldn't be resolved") + label.assign(self.labels[label.name]) + + def new_label(self, name: str, value: int): + label = unit.LazyLabel(name, value) + assert name not in self.labels, f"repeated label found: {name}" + # this is already resolved label + assert isinstance(value, int) + self.labels[name] = label + return label + + +class AsmParser: + def __init__(self) -> None: + self.reset() + + def reset(self): + self.address = 0 + self.lm = LabelsManager() + self.section = "text" + # self.ins = [] # type: List[instruction.ParsedInstruction] + self.final_bytes = [] # type: Union[instruction.ParsedInstruction, unit.Data] + + def get_str(self, resolved=False, rom_binary=False): + '''Get printable instructions. + + Also removes don't care byte(s) at the end coming + from .bss section (if rom_binary is true). + ''' + if rom_binary: + resolved = True # implict + if resolved: + self.lm.propogate() + assert util.PROGRAM_ORG in self.lm.labels, f"{util.PROGRAM_ORG} label must be defined" + + track_binary_address = None + _content = [] + if not rom_binary: + _content.append(f"{util.PROGRAM_ORG} equ {self.lm.labels[util.PROGRAM_ORG].get()}") + for add, x in self.final_bytes: + if not rom_binary: + _content.append(f"{add:03x}: {x.get_str(resolved=resolved, binary=False)}") + else: + if track_binary_address is None: + track_binary_address = add # first address can be util.PROGRAM_ORG + assert track_binary_address == add, "gaps found in binary representation" + out = f"{x.get_str(resolved=resolved, binary=True)}" + _content.append(out) + assert len(out) % 8 == 0 + track_binary_address += len(out)//8 + + content = '\n'.join(_content) + if rom_binary: + _program_content = (content + .replace("\n", "") + .replace(" ", "") + .replace("-", " ") + .rstrip() + ) + assert len(_program_content) % 8 == 0 + _program_size = len(_program_content) // 8 # bytes + assert _program_size < 2**8 # as we are using 1 byte for metadata + _binary_content = f"{_program_size:08b}" + _program_content + assert len(_binary_content) % 8 == 0 + assert set(_binary_content) <= set(['0', '1']), "only binary context is expected" + + content = '\n'.join([ + "%s %s %s %s" % ( + _binary_content[32*i:32*i+8], + _binary_content[32*i+8:32*i+16], + _binary_content[32*i+16:32*i+24], + _binary_content[32*i+24:32*i+32]) + for i in range((len(_binary_content)+24)//32) + ]) + return content + + def get_section(self): + return self.section + + def section_update(self, name): + assert name in ["text", "data", "bss"] + if self.get_section() == "bss" and name != "bss": + raise ValueError(".bss must be the last section") + self.section = name + + def get_address(self): + return self.address + + def add_address(self, add): + self.address += add + + def add_ins(self, ins: instruction.ParsedInstruction): + # self.ins.append(ins) + self.final_bytes.append((self.get_address(), ins)) + self.add_address(ins.size()) + + def add_data(self, data: unit.Data): + self.final_bytes.append((self.get_address(), data)) + self.add_address(data.size()) + + def parse_text(self, tokens: List[str], line: str): + if tokens[0].endswith(":"): + # label + assert len(tokens) == 1, "label: should exists in isolation in line" + assert len(tokens[0]) >= 2, "label should be atleast 1 char long" + label = tokens[0][:-1] + self.lm.new_label(label, self.get_address()) + return + ins_name, tokens = line_parser.parse_line(line) + if ins_name is None: + # no instruction + return + for _, token in tokens: + self.lm.add_lazy(token) + self.add_ins(instruction.get_parser(ins_name).parse(tokens)) + + def parse_data(self, tokens: List[str]): + label = tokens[0] + self.lm.new_label(label, self.get_address()) + + times = 1 + if tokens[1] == "times": + times = int(tokens[2]) + tokens = [tokens[0]] + tokens[3:] + + assert len(tokens) == 3, "Three tokens expected in .data other than times" + if tokens[1] == "db": + sz = 1 + elif tokens[1] == "dw": + sz = 2 + else: + raise ValueError("unsupported data size provided") + + # unsigned integer only for now + val = int(tokens[2]) + assert val >= 0 and val < (2**(8*sz)) + + for _ in range(times): + for byte in val.to_bytes(sz, 'big'): + self.add_data(unit.Data(byte)) + + def parse_bss(self, tokens: List[str]): + if len(tokens) == 3: + assert tokens[0].endswith(":"), f"bss tokens: {tokens}" + label = tokens[0][:-1] + self.lm.new_label(label, self.get_address()) + tokens = tokens[1:] + + assert tokens[0] == "resb" + sz = int(tokens[1]) + for _ in range(sz): + self.add_data(unit.Data(None)) + + def parse_constant(self, tokens: List[str]): + assert tokens[1].lower() == "equ" + self.lm.new_label(tokens[0], int(tokens[2], 0)) + if tokens[0] == util.PROGRAM_ORG: + assert self.get_address() == 0, f"{util.PROGRAM_ORG} must be the first label defined" + self.add_address(self.lm.labels[util.PROGRAM_ORG].get()) + return + + def parse_line(self, line: str): + line = line.strip() + if len(line) == 0: + # empty line + return + if line.startswith("#"): + # comment + return + # case insensitive + tokens = [x for x in line.split()] + + if tokens[0].lower() == "section": + assert len(tokens) == 2,f"expects section , line: {line}" + assert tokens[1].lower() in [".text", ".data", ".bss"] + self.section_update(tokens[1][1:].lower()) + return + + if len(tokens) == 3 and tokens[1].lower() == "equ": + # constants + self.parse_constant(tokens) + return + + if self.get_section() == "text": + self.parse_text(tokens, line) + elif self.get_section() == "data": + self.parse_data(tokens) + elif self.get_section() == "bss": + self.parse_bss(tokens) + else: + raise Exception(f"unknown section: {self.get_section()}") diff --git a/planner/asm/program_parser_test.py b/planner/asm/program_parser_test.py new file mode 100644 index 0000000..50a8a35 --- /dev/null +++ b/planner/asm/program_parser_test.py @@ -0,0 +1,50 @@ +from unittest import TestCase + +from planner.asm import program_parser + +PROGRAM = """ +# Sample Program + +PROGRAM_ORG equ 0x40 +TOTAL equ 0x10 + +section .text +main: + movc R1, 0 + movc R2, TOTAL + movc R3, 1 +loop: + cmp R1, R2 + jeq loop_exit + add R1, R3 + mov [last_value], R1 + out 0x00, R1 + jmp loop +loop_exit: + jmp loop_exit + +section .data +last_value db 0 + +""".splitlines() + +class AsmParserTest(TestCase): + + def test_asm_parser_binary(self): + asm = program_parser.AsmParser() + for line in PROGRAM: + asm.parse_line(line) + output = asm.get_str(resolved=True, rom_binary=True) + + def test_asm_parser_unresolved(self): + asm = program_parser.AsmParser() + for line in PROGRAM: + asm.parse_line(line) + output = asm.get_str(resolved=False, rom_binary=False) + + def test_asm_parser_resolved(self): + asm = program_parser.AsmParser() + for line in PROGRAM: + asm.parse_line(line) + output = asm.get_str(resolved=True, rom_binary=False) + diff --git a/planner/asm_parser.py b/planner/asm_parser.py new file mode 100644 index 0000000..9f82f73 --- /dev/null +++ b/planner/asm_parser.py @@ -0,0 +1,205 @@ +import logging +from typing import List, Union, Optional + +from planner import instruction, unit, util + +from planner.asm import line_parser + +class LabelsManager: + def __init__(self): + self.labels = {} + self.passive_labels = [] + + def add_lazy(self, lazy: unit.LazyLabel): + if lazy.name == util.LABEL_CONSTANT: + # do nothing + return + self.passive_labels.append(lazy) + + def propogate(self): + for label in self.passive_labels: + if label.name not in self.labels: + raise ValueError(f"'{label.name}' couldn't be resolved") + label.assign(self.labels[label.name]) + + def new_label(self, name: str, value: int): + label = unit.LazyLabel(name, value) + assert name not in self.labels, f"repeated label found: {name}" + # this is already resolved label + assert isinstance(value, int) + self.labels[name] = label + return label + + +class AsmParser: + def __init__(self) -> None: + self.reset() + + def reset(self): + self.address = 0 + self.lm = LabelsManager() + self.section = "text" + # self.ins = [] # type: List[instruction.ParsedInstruction] + self.final_bytes = [] # type: Union[instruction.ParsedInstruction, unit.Data] + + def get_str(self, resolved=False, rom_binary=False): + '''Get printable instructions. + + Also removes don't care byte(s) at the end coming + from .bss section (if rom_binary is true). + ''' + if rom_binary: + resolved = True # implict + if resolved: + self.lm.propogate() + track_binary_address = None + _content = [] + for add, x in self.final_bytes: + if not rom_binary: + _content.append(f"{add:03x}: {x.get_str(resolved=resolved, binary=False)}") + else: + if track_binary_address is None: + track_binary_address = add + assert track_binary_address == add, "gaps found in binary representation" + out = f"{x.get_str(resolved=resolved, binary=True)}" + _content.append(out) + assert len(out) % 8 == 0 + track_binary_address += len(out)//8 + + content = '\n'.join(_content) + if rom_binary: + _program_content = (content + .replace("\n", "") + .replace(" ", "") + .replace("-", " ") + .rstrip() + ) + assert len(_program_content) % 8 == 0 + _program_size = len(_program_content) // 8 # bytes + assert _program_size < 2**8 # as we are using 1 byte for metadata + _binary_content = f"{_program_size:08b}" + _program_content + assert len(_binary_content) % 8 == 0 + assert set(_binary_content) <= set(['0', '1']), "only binary context is expected" + + content = '\n'.join([ + "%s %s %s %s" % ( + _binary_content[32*i:32*i+8], + _binary_content[32*i+8:32*i+16], + _binary_content[32*i+16:32*i+24], + _binary_content[32*i+24:32*i+32]) + for i in range((len(_binary_content)+24)//32) + ]) + return content + + def get_section(self): + return self.section + + def section_update(self, name): + assert name in ["text", "data", "bss"] + if self.get_section() == "bss" and name != "bss": + raise ValueError(".bss must be the last section") + self.section = name + + def get_address(self): + return self.address + + def add_address(self, add): + self.address += add + + def add_ins(self, ins: instruction.ParsedInstruction): + # self.ins.append(ins) + self.final_bytes.append((self.get_address(), ins)) + self.add_address(ins.size()) + + def add_data(self, data: unit.Data): + self.final_bytes.append((self.get_address(), data)) + self.add_address(data.size()) + + def parse_text(self, tokens: List[str], line: str): + if tokens[0].endswith(":"): + # label + assert len(tokens) == 1, "label: should exists in isolation in line" + assert len(tokens[0]) >= 2, "label should be atleast 1 char long" + label = tokens[0][:-1] + self.lm.new_label(label, self.get_address()) + return + ins_name, tokens = line_parser.parse_line(line) + if ins_name is None: + # no instruction + return + for _, token in tokens: + self.lm.add_lazy(token) + self.add_ins(instruction.get_parser(ins_name).parse(tokens)) + + def parse_data(self, tokens: List[str]): + label = tokens[0] + self.lm.new_label(label, self.get_address()) + + times = 1 + if tokens[1] == "times": + times = int(tokens[2]) + tokens = [tokens[0]] + tokens[3:] + + assert len(tokens) == 3, "Three tokens expected in .data other than times" + if tokens[1] == "db": + sz = 1 + elif tokens[1] == "dw": + sz = 2 + else: + raise ValueError("unsupported data size provided") + + # unsigned integer only for now + val = int(tokens[2]) + assert val >= 0 and val < (2**(8*sz)) + + for _ in range(times): + for byte in val.to_bytes(sz, 'big'): + self.add_data(unit.Data(byte)) + + def parse_bss(self, tokens: List[str]): + if len(tokens) == 3: + assert tokens[0].endswith(":"), f"bss tokens: {tokens}" + label = tokens[0][:-1] + self.lm.new_label(label, self.get_address()) + tokens = tokens[1:] + + assert tokens[0] == "resb" + sz = int(tokens[1]) + for _ in range(sz): + self.add_data(unit.Data(None)) + + def parse_constant(self, tokens: List[str]): + assert tokens[1].lower() == "equ" + self.lm.new_label(tokens[0], int(tokens[2], 0)) + return + + def parse_line(self, line: str): + line = line.strip() + if len(line) == 0: + # empty line + return + if line.startswith("#"): + # comment + return + # case insensitive + tokens = [x for x in line.split()] + + if tokens[0].lower() == "section": + assert len(tokens) == 2,f"expects section , line: {line}" + assert tokens[1].lower() in [".text", ".data", ".bss"] + self.section_update(tokens[1][1:].lower()) + return + + if len(tokens) == 3 and tokens[1].lower() == "equ": + # constants + self.parse_constant(tokens) + return + + if self.get_section() == "text": + self.parse_text(tokens, line) + elif self.get_section() == "data": + self.parse_data(tokens) + elif self.get_section() == "bss": + self.parse_bss(tokens) + else: + raise Exception(f"unknown section: {self.get_section()}") diff --git a/planner/instruction.py b/planner/instruction.py new file mode 100644 index 0000000..7b2c57e --- /dev/null +++ b/planner/instruction.py @@ -0,0 +1,243 @@ +from enum import Enum +from typing import List, Union, Tuple + +from planner import unit, util + +# # Global parser map to instruction encoder +# _PARSER_MAPPING = {} + +class MBlockSelector(Enum): + '''MBlockSelector defines the purpose of value_s[i].''' + DONT_CARE = -1 + RAM = 0 # value[i] => RAM[value_s[i]] + # TODO: Claim, we don't need AUTO_BRAM + # We should never be reading any data from boot_ram. Any tiny miny data should be + # part of instruction set as CONST. + # AUTO_BRAM = 1 # value[i] => BROM[value_s[i]] is execute_from_brom else RAM + IO = 2 + CONST = 3 + + # TODO: What happens if we try to write on a CONST. + + @staticmethod + def wire(sel, do_not_care = None) -> List: + if sel == MBlockSelector.DONT_CARE: + assert do_not_care is not None + sel = do_not_care + + assert sel in [MBlockSelector.RAM, MBlockSelector.IO, MBlockSelector.CONST], "found: %s" % (sel) + return [sel.value%2, sel.value>>1] + + @classmethod + def from_binary(cls, bin: List[int]): + assert len(bin) == 2 + val = bin[0]+bin[1]*2 + assert val >= 0 and val <= 3 + return MBlockSelector(val) + +class ALU(Enum): + ADD = 0 + SUB = 1 + SHL = 2 + SHR = 3 + PASS_R = 4 + AND = 5 + OR = 6 + + @staticmethod + def wire(sel) -> List: + assert sel.value >= 0 and sel.value < 8 + return [sel.value%2, (sel.value>>1)%2, sel.value>>2] + + @classmethod + def from_binary(cls, bits: List[int]): + value = bits[0]+bits[1]*2+bits[2]*4 + assert value >= 0 and value <= 6 + return ALU(value) + +MAPPING = { + "mblock_selector_r" : [0, 1], + "mblock_selector_rw": [2, 3], + "alu_op" : [4, 5, 6], + "update_pc" : [7], + "mblock_is_write" : [8], +} + +class EncodedInstruction: + def __init__(self, + mblock_selector_r: MBlockSelector, + mblock_selector_rw: MBlockSelector, + alu_op: ALU, + mblock_is_write: Union[bool,int], + update_program_counter: Union[bool,int]) -> None: + self.mblock_selector_r = mblock_selector_r + self.mblock_selector_rw = mblock_selector_rw + self.alu_op = alu_op + self.mblock_is_write = 1 if mblock_is_write else 0 + self.update_program_counter = 1 if update_program_counter else 0 + + + @staticmethod + def assign_bits(bits: List[int], name: str, values: List[int]): + indexs = MAPPING[name] + assert len(indexs) == len(values) + for i, j in enumerate(indexs): + assert values[i] in [0,1] + bits[j] = values[i] + + @staticmethod + def get_bits(bits: List[int], name: str): + return [bits[i] for i in MAPPING[name]] + + def encode(self): + # value_r = &mblock_resolve(address_r) + # value_rw = &mblock_resolve(address_rw) + # value_rw = op(value_r, [value_rw]) + + bits = [0]*16 + + # Implemented in encode_full(...) + # bits[16:24] is address_r (address to read only from) + # bits[24:32] is address_rw (address to read from or write to) + + self.assign_bits(bits, "mblock_selector_r", MBlockSelector.wire(self.mblock_selector_r)) + self.assign_bits(bits, "mblock_selector_rw", MBlockSelector.wire(self.mblock_selector_rw, do_not_care=MBlockSelector.CONST)) + self.assign_bits(bits, "alu_op", ALU.wire(self.alu_op)[0:3]) + self.assign_bits(bits, "update_pc",[self.update_program_counter]) + self.assign_bits(bits, "mblock_is_write",[self.mblock_is_write]) + + return sum([bits[i]<0 else 0 for i in range(16)] + mblock_selector_r = MBlockSelector.from_binary(cls.get_bits(encoded_bits, 'mblock_selector_r')) + mblock_selector_rw = MBlockSelector.from_binary(cls.get_bits(encoded_bits, 'mblock_selector_rw')) + alu_op = ALU.from_binary(cls.get_bits(encoded_bits, 'alu_op')) + update_program_counter = cls.get_bits(encoded_bits, 'update_pc')[0] + mblock_is_write = cls.get_bits(encoded_bits, 'mblock_is_write')[0] + return EncodedInstruction(mblock_selector_r, mblock_selector_rw, alu_op, mblock_is_write, update_program_counter) + + def plug(self, address_rw: unit.LazyLabel, address_r: unit.LazyLabel): + return FullyEncodedInstruction(self, address_rw, address_r) + + +class FullyEncodedInstruction: + def __init__(self, encoded_instruction: EncodedInstruction, address_rw: unit.LazyLabel, address_r: unit.LazyLabel) -> None: + self.address_rw = address_rw + self.address_r = address_r + self.encoded_instruction = encoded_instruction + + @classmethod + def from_binary(cls, ins: List[int]): + return cls( + EncodedInstruction.from_binary(ins[0]+(ins[1]<<8)), + unit.LazyLabel(util.LABEL_CONSTANT, ins[2]), + unit.LazyLabel(util.LABEL_CONSTANT, ins[3]), + ) + + def get_binary(self, ensure_resolved=False) -> List[unit.Data]: + encoded = self.encoded_instruction.encode() + return [ + unit.Data(encoded%256), + unit.Data(encoded//256), + unit.Data(self.address_rw.get(ensure_resolved=ensure_resolved)), + unit.Data(self.address_r.get(ensure_resolved=ensure_resolved)) + ] + + def size(self): + return 4 # bytes + + +class ParserInstruction: + def __init__(self, name: str, type_rw: unit.Operand, type_r: unit.Operand, encoded_instruction: EncodedInstruction): + self.name = name + self.type_rw = type_rw + self.type_r = type_r + self.encoded_instruction = encoded_instruction + + def expects_operands(self): + expects = [] + if self.type_rw != unit.Operand.IGNORE: + expects.append(self.type_rw) + if self.type_r != unit.Operand.IGNORE: + expects.append(self.type_r) + return expects + + def parse(self, values: List[Tuple[unit.Operand, int]]): + return ParsedInstruction(self, values) + + def __str__(self) -> str: + expects = ', '.join([str(x) for x in self.expects_operands()]) + return "%s %s" % (self.name, expects) + + +class ParsedInstruction: + def __init__(self, _parser: ParserInstruction, values: List[Tuple[unit.Operand, unit.LazyLabel]]): + self.parser = _parser + self.values = values + self.fully_encoded_instruction = self.get_fully_encoded_instruction(values) + + def get_fully_encoded_instruction(self, values: List[Tuple[unit.Operand, unit.LazyLabel]]): + if self.parser.expects_operands() != [t[0] for t in values]: + raise ValueError(f"{self.parser.name} want operand type {self.parser.expects_operands()}, given operand values {values}") + values_index = 0 + if self.parser.type_rw != unit.Operand.IGNORE: + address_rw = values[values_index][1] + values_index+=1 + else: + address_rw = unit.LazyLabel(util.LABEL_CONSTANT, 0) + if self.parser.type_r != unit.Operand.IGNORE: + address_r = values[values_index][1] + values_index+=1 + else: + address_r = unit.LazyLabel(util.LABEL_CONSTANT, 0) + assert len(values) == values_index + return self.parser.encoded_instruction.plug(address_rw, address_r) + + def get_str(self, resolved=False, binary=False): + if binary: + assert resolved + return ''.join([x.get_str(binary=True) for x in self.fully_encoded_instruction.get_binary(ensure_resolved=True)]) + + printable_values = [] + for val_type, val in self.values: + assert val_type in [unit.Operand.ADDRESS, unit.Operand.CONSTANT] + if val_type == unit.Operand.ADDRESS: + printable_values.append("[%s]" % (val.get_str(resolved=resolved))) + else: + printable_values.append("%s" % (val.get_str(resolved=resolved))) + return "%s %s" % (self.parser.name, ', '.join(printable_values)) + + def __str__(self): + return self.get_str(resolved=False, binary=False) + + def size(self): + return self.fully_encoded_instruction.size() + +INSTRUCTIONS = [ + ParserInstruction("IN", unit.Operand.ADDRESS, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector.IO, MBlockSelector.RAM, ALU.PASS_R, True, False)), + ParserInstruction("OUT", unit.Operand.CONSTANT, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.IO, ALU.PASS_R, True, False)), + # CALL? + # HLT? + ParserInstruction("MOV", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.RAM, ALU.PASS_R, True, False)), + ParserInstruction("MOVC", unit.Operand.ADDRESS, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector.CONST, MBlockSelector.RAM, ALU.PASS_R, True, False)), + ParserInstruction("ADD", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.RAM, ALU.ADD, True, False)), + ParserInstruction("SUB", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.RAM, ALU.SUB, True, False)), + ParserInstruction("SHL", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.RAM, ALU.SHL, True, False)), + ParserInstruction("SHR", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.RAM, ALU.SHR, True, False)), + ParserInstruction("AND", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.RAM, ALU.AND, True, False)), + ParserInstruction("OR", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.RAM, ALU.OR, True, False)), + + ParserInstruction("CMP", unit.Operand.ADDRESS, unit.Operand.ADDRESS, EncodedInstruction(MBlockSelector.RAM, MBlockSelector.RAM, ALU.SUB, False, False)), + ParserInstruction("JMP", unit.Operand.IGNORE, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector.CONST, MBlockSelector.DONT_CARE, ALU.PASS_R, False, True)), + # TODO: Fix JEQ will always jump + ParserInstruction("JEQ", unit.Operand.IGNORE, unit.Operand.CONSTANT, EncodedInstruction(MBlockSelector.CONST, MBlockSelector.DONT_CARE, ALU.PASS_R, False, True)) +] + +def get_parser(name: str) -> ParserInstruction: + name = name.upper() + for ins in INSTRUCTIONS: + if name == ins.name: + return ins + raise ValueError(f"Instruction parser for '{name}' not found") diff --git a/planner/instruction_test.py b/planner/instruction_test.py new file mode 100644 index 0000000..6d91349 --- /dev/null +++ b/planner/instruction_test.py @@ -0,0 +1,39 @@ +from unittest import TestCase + +from planner import instruction +from planner.asm import line_parser + +class InstructionTest(TestCase): + + def test_all_instructions_validation(self): + all_instructions_sample_list = [ + ("in R1, 20", "IN [1], 20"), + ("out 10, R2", "OUT 10, [2]"), + ("mov R3, R1", "MOV [3], [1]"), + ("movc R4, 0x2", "MOVC [4], 2"), + ("add R5, [35]", "ADD [5], [35]"), + ("sub R6, [0x45]", "SUB [6], [69]"), + ("shl R7, R1", "SHL [7], [1]"), + ("shr R8, R1", "SHR [8], [1]"), + ("and [0x32], R9", "AND [50], [9]"), + ("or [0x12], [0x33]", "OR [18], [51]"), + ("cmp R2, [33]", "CMP [2], [33]"), + ("jmp 0x55", "JMP 85"), + ("jeq 0x22", "JEQ 34"), + ] + + instructions = set() + for input_line, want in all_instructions_sample_list: + name, tokens = line_parser.parse_line(input_line) + self.assertIsNotNone(name) + ins = instruction.get_parser(name).parse(tokens) + self.assertEqual(want, str(ins)) + instructions.add(name) + + self.assertEqual( + len(instructions), + len(instruction.INSTRUCTIONS), + msg="Sample instructions not provided for all instructions") + + + diff --git a/planner/sim/bin_parser.py b/planner/sim/bin_parser.py new file mode 100644 index 0000000..74cc857 --- /dev/null +++ b/planner/sim/bin_parser.py @@ -0,0 +1,180 @@ +import random +import logging + +from typing import List, Optional +from planner import instruction +from planner.sim import devices + +PROGRAM_ORG = 0x40 +IO_DEVICES = 16 + +class BinRunner: + def __init__(self, content): + self.ram = [] + self.input_devices = [None]*IO_DEVICES + self.output_devices = [None]*IO_DEVICES + for _ in range(PROGRAM_ORG): + self.ram.append(random.randint(0, 256)) + + self.parse(content) + self.pc = PROGRAM_ORG + # self.is_powered_on = False + # self.step() + self.is_powered_on = True + # self.step() + + def set_input_device(self, index: int, d: devices.InputDevice): + self.input_devices[index] = d + + def set_output_device(self, index: int, d: devices.Device): + self.output_devices[index] = d + + def parse(self, content: str): + content = content.replace(" ", "").replace("\n", "") + assert len(content)%8 == 0 + assert set(content) <= set(['0', '1']) + program_size = int(content[:8], 2) + assert program_size*8+8 == len(content) + for i in range(1, len(content)//8): + self.ram.append(int(content[i*8:(i+1)*8], 2)) + + def read_ram(self, addr: int, count: int) -> List[int]: + ans = [] + assert addr >= 0 + for i in range(count): + if addr+i >= len(self.ram): + ans.append(random.randint(0, 256)) + else: + ans.append(self.ram[addr+i]) + + logging.info("RAM[%04x]: %s", addr, ans) + return ans + + @staticmethod + def m_fetch_and_store( + ram: List[int], + input_devices: List[int], + output_devices: List[int], + source: int, + sel: instruction.MBlockSelector, + mblock_is_write: bool, + value: Optional[int] = None, + to_be_ignored_sim_hack: Optional[bool] = False): + if not mblock_is_write: + if sel == instruction.MBlockSelector.RAM: + assert source >= 0 and source < len(ram) + return ram[source] + if sel == instruction.MBlockSelector.CONST: + assert source >= 0 and source < 256 + return source + if sel == instruction.MBlockSelector.IO: + # assert source >= 0 and source < 16 + if to_be_ignored_sim_hack: + value = 0 + else: + # print(f"Input for device[{source}]: ", end="") + value = input_devices[source].take_input() + # value = int(input(), 0) + assert value >= 0 and value < (1<<32) + return value + # input_devices[source] = value + # return input_devices[source] + else: + if sel == instruction.MBlockSelector.RAM: + assert source >= 0 and source < len(ram) + assert value >= 0 and value < 256 + ram[source] = value + return None + if sel == instruction.MBlockSelector.CONST: + # no-op + return + if sel == instruction.MBlockSelector.IO: + # assert source >= 0 and source < 16 + assert value >= 0 and value < (1<<32) + # input_devices[source] = value + output_devices[source].update(value) + return + raise Exception(f"unsupported selector: {sel}") + + @staticmethod + def m_alu(rw: int, r: int, op: instruction.ALU): + assert rw>=0 and rw<256 + assert r>=0 and r<256 + MASK = 255 + if op == instruction.ALU.ADD: + return MASK&(rw+r) + if op == instruction.ALU.SUB: + # TODO: deal with negative number + return MASK&(rw-r) + if op == instruction.ALU.SHL: + return MASK&(rw<>r) + if op == instruction.ALU.PASS_R: + return MASK&(r) + if op == instruction.ALU.AND: + return MASK&(rw&r) + if op == instruction.ALU.OR: + return MASK&(rw|r) + raise Exception(f"unsupported ALU op: {op}") + + @staticmethod + def m_pc_next(pc: int, value: int, flag_alu_zero: bool, update_program_counter: bool, is_powered_on: bool): + if not is_powered_on: + return PROGRAM_ORG + if update_program_counter: + return value + # TODO: handle JEQ + return pc+4 + + def step(self): + ins_binary = self.read_ram(self.pc, 4) + ins = instruction.FullyEncodedInstruction.from_binary(ins_binary) + mblock_selector_r = ins.encoded_instruction.mblock_selector_r + mblock_selector_rw = ins.encoded_instruction.mblock_selector_rw + alu_op = ins.encoded_instruction.alu_op + mblock_is_write = ins.encoded_instruction.mblock_is_write + update_program_counter = ins.encoded_instruction.update_program_counter + + vr_source = ins.address_r.get() + vrw_source = ins.address_rw.get() + + value_r = self.m_fetch_and_store( + self.ram, + self.input_devices, + self.output_devices, + vr_source, + mblock_selector_r, + False, + value=None + ) + value_rw = self.m_fetch_and_store( + self.ram, + self.input_devices, + self.output_devices, + vrw_source, + mblock_selector_rw, + False, + value=None, + to_be_ignored_sim_hack = (alu_op == instruction.ALU.PASS_R) + ) + + value = self.m_alu(value_rw, value_r, alu_op) + flag_alu_zero = (value == 0) + + self.m_fetch_and_store( + self.ram, + self.input_devices, + self.output_devices, + vrw_source, + mblock_selector_rw, + mblock_is_write and self.is_powered_on, + value=value + ) + + self.pc = self.m_pc_next( + self.pc, + value, + flag_alu_zero, + update_program_counter, + self.is_powered_on) diff --git a/planner/sim/bin_parser_test.py b/planner/sim/bin_parser_test.py new file mode 100644 index 0000000..30145f4 --- /dev/null +++ b/planner/sim/bin_parser_test.py @@ -0,0 +1,41 @@ +from planner.asm import program_parser +from planner.sim import bin_parser +from planner.sim import devices +from unittest import TestCase + + +PROGRAM = """ +# Sample Program + +PROGRAM_ORG equ 0x40 +ADD_WITH equ 15 + +section .text +main: + movc R2, ADD_WITH + in R4, 0x05 + add R4, R2 + out 0x06, R4 +loop_exit: + jmp loop_exit +""".splitlines() + +class BinParserTest(TestCase): + + def test_overall(self): + asm = program_parser.AsmParser() + for line in PROGRAM: + asm.parse_line(line) + binary_program = asm.get_str(resolved=True, rom_binary=True) + + _bin = bin_parser.BinRunner(binary_program) + fake_input = devices.LatchInput("fake") + fake_ouput = devices.Device() + + _bin.set_input_device(5, fake_input) + _bin.set_output_device(6, fake_ouput) + + fake_input.set_input(56) + for _ in range(100): + _bin.step() + self.assertEqual(fake_ouput.get(), 56+15) diff --git a/planner/sim/devices.py b/planner/sim/devices.py new file mode 100644 index 0000000..9d8130a --- /dev/null +++ b/planner/sim/devices.py @@ -0,0 +1,95 @@ +from typing import List +import random + + +class Device: + def __init__(self, bits = 8): + assert 1 <= bits and bits <= 32 + self.value = [0 for _ in range(bits)] + self.change_handlers = [] + + def add_change_handler(self, f): + self.change_handlers.append(f) + + def _new_value(self, val): + if val != self.value: + for handle in self.change_handlers: + handle(val, self.value) + self.value = val + + def get(self): + return sum([self.value[i]<0 else 0 for i in range(len(self.value))] + self._new_value(value) + + def update_bit(self, index: int, value: int): + assert index >= 0 and index < len(self.value) + assert value in [0, 1] + new_value = self.value.copy() + new_value[index] = value + self._new_value(new_value) + + + def flip_bit(self, index: int): + assert index >= 0 and index < len(self.value) + new_value = self.value.copy() + new_value[index] ^= 1 + self._new_value(new_value) + + +class InputDevice(Device): + def __init__(self, *args, **kwargs): + super(InputDevice, self).__init__(*args, **kwargs) + + def take_input(self): + # empty device + pass + + +class LatchInput(InputDevice): + def __init__(self, name: str): + super(LatchInput, self).__init__() + self.name = name + self.update(0) + + def set_input(self, value: int): + self.update(value) + + def take_input(self): + print(f"Input for Latch[{self.name}]: ", end="") + return self.get() + + +class Numpad(InputDevice): + def __init__(self, name: str): + bits = 10 + super(Numpad, self).__init__(bits=bits) + self.name = name + self.update(0) + + def take_input(self): + print(f"Input for Numpad[{self.name}]: ", end="") + val = int(input()) + assert val >= 0 and val <= 9 + self.flip_bit(val) + return self.get() + + +class IntegerOutput(Device): + def __init__(self, name: str): + self.name = name + super(IntegerOutput, self).__init__(bits=16) + + def _on_change(new_val, old_val): + self.on_change(new_val, old_val) + self.add_change_handler(_on_change) + + def on_change(self, new_val, old_val): + print(f"Output [{self.name}]: {new_val}") + diff --git a/planner/unit.py b/planner/unit.py new file mode 100644 index 0000000..e9cb154 --- /dev/null +++ b/planner/unit.py @@ -0,0 +1,81 @@ +from typing import List, Optional +from planner import util +from enum import Enum +import logging + + +class LazyLabel: + def __init__(self, name: str, value: Optional[int] = None): + assert util.is_valid_label(name), ValueError(f"{name} is not a valid label") + self.name = name + if name == util.LABEL_CONSTANT: + assert value is not None + self.value = value + + def __repr__(self): + if self.name == util.LABEL_CONSTANT: + return str(self.value) + return self.name + + def __eq__(self, o): + if isinstance(o, int): + return self.value == o + if isinstance(o, LazyLabel): + if self.name == util.LABEL_CONSTANT: + return self.value == o.value + return self.name == o.name + return False + + def get(self, ensure_resolved=False): + if self.value is None: + assert not ensure_resolved + logging.info("LazyLabel[%s] is empty", self.name) + return 0 + return self.value + + def assign(self, o): + if not isinstance(o.value, int): + raise ValueError(f"{name} resolution failed during assign as {o.value}") + + if self.value is None: + self.value = o.value + else: + assert self.value == o.value + + def get_str(self, resolved=False): + if self.name == util.LABEL_CONSTANT: + return str(self.value) + if not resolved: + return self.name + assert isinstance(self.value, int), f"{self.name} is still not resolved" + return str(self.value) + + +class Operand(Enum): + ADDRESS = 1 + CONSTANT = 2 + IGNORE = 3 + + def __repr__(self): + return self.name + + +class Data: + def __init__(self, byte: Optional[int]): + self.byte=byte + + def size(self): + return 1 + + def get_str(self, resolved=False, binary=False): + if binary: + if self.byte is None: + return "--------" + return f"{self.byte:08b}" + else: + if self.byte is None: + return "--" + return f"{self.byte:02}" + + def __repr__(self) -> str: + return self.get_str(binary=True) diff --git a/planner/util.py b/planner/util.py new file mode 100644 index 0000000..9ba8b64 --- /dev/null +++ b/planner/util.py @@ -0,0 +1,15 @@ +import string + +LABEL_CONSTANT = "constant" +PROGRAM_ORG = "PROGRAM_ORG" + +def is_valid_label(msg): + if len(msg) == 0: + return False + if msg[0] in string.digits: + return False + if msg in [f"R{i}" for i in range(10)]: + return False + if msg in ["section", "data", "bss"]: + return False + return set(msg) <= set(string.ascii_uppercase + string.ascii_lowercase + string.digits + "_") diff --git a/programs/3_led_switch.asm b/programs/3_led_switch.asm new file mode 100644 index 0000000..8a1e904 --- /dev/null +++ b/programs/3_led_switch.asm @@ -0,0 +1,18 @@ +# Program +# Turn on/off the led corresponding to the button. +# +# Chip +# Button(s) with one end at input_device[5][pin:0..2] with another end with +# # pull down resister +# [in0-button] [in1-button] [in2-button] +# # Led(s) anode at out[0-2] and cathode at ground with some resister +# [out0-led] [out1-button1] [out2-button2] +# at output_device[6][pin:0..2] + +PROGRAM_ORG equ 0x40 + +section .text + main: + IN R1, 5 + OUT 6, R1 + jmp main diff --git a/programs/boot_sequence.not_ready_asm b/programs/boot_sequence.not_ready_asm new file mode 100644 index 0000000..f1e74fc --- /dev/null +++ b/programs/boot_sequence.not_ready_asm @@ -0,0 +1,34 @@ +# Program +# ROM[BootSequence] +# +# Input devices required +# * ROM[Program] output at 0 +# Output devices +# * ROM[Program] address-line at 0 +# Really small program to copy ROM[Program] to RAM[Program] + +PROGRAM_START equ 0x20 + +section .text + main: + # read metadata: program size + mov [R1], 0 # const -> ram + OUT 0, [R1] # ram -> io + IN [R0], 0 # io -> ram + + mov [R2], PROGRAM_START # const -> ram + mov [R1], 1 # const -> ram + _copy_more: + OUT 0, [R1] # ram -> io + IN [R2], 0 # io -> ram + add [R2], 1 + add [R1], 1 + # bytes left to copy + sub [R0], 1 + cmp [R0], 0 + jneq _copy_more + + _copy_completed: + jmp _copy_completed + + diff --git a/programs/ping_pong.not_ready_asm b/programs/ping_pong.not_ready_asm new file mode 100644 index 0000000..6855492 --- /dev/null +++ b/programs/ping_pong.not_ready_asm @@ -0,0 +1,94 @@ +# Program +# Two Player Ping Pong Game +# +# Controls +# Up/Down button for each player +# Game Reset button +# Display (16*8) +# +# X +# X +# X O X +# X +# X +# +# +# Chip +# H/W: 4+1 buttons, 2 8x8 LED matrix +# OUT[0..7] = LED Matrix Row +# OUT[8..15] = LED Matrix 1 Col +# OUT[16..23] = LED Matrix 2 Col + + +BAT_H equ 3 +BAT_MAXY equ 5 + +section .text + main: + + game: + call print + call step + jmp game + hlt + + wait_led_lit: + # wait for 100 ins to let LED powered up + mov [R0], 100 + _wait_led_lit_internal: + sub [R0], 1 + cmp [R0], 0 + jneq _wait_led_lit_internal + ret + + print: + # bat1 = 7< -#include - -void print_char(char c) { - for(int i=0;i<8;i++) { - int col=font[c][i]; - printf("%02x\n", col); - } -} - -int main(int argc, char *argv[]) { - if(argc!=2) { - printf("Please provide exactly one argument\n"); - return 1; - } - for(int i=0;argv[1][i]!='\0';i++) { - print_char(argv[1][i]); - } -} \ No newline at end of file