diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 826d87d..eb2c7fa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -145,4 +145,19 @@ mapper_code sim: - cd hw/efinix_fpga/simulation - make clean - TEST_PROGRAM_NAME=mapper_test make mapper_code_tb - - ./mapper_code_tb \ No newline at end of file + - ./mapper_code_tb + +interrupt_controller sim: + tags: + - linux + - iverilog + stage: simulate + artifacts: + paths: + - hw/efinix_fpga/simulation/interrupt_controller.vcd + script: + - source init_env.sh + - cd hw/efinix_fpga/simulation + - make clean + - TEST_PROGRAM_NAME=mapper_test make interrupt_controller_tb + - ./interrupt_controller_tb \ No newline at end of file diff --git a/doc/pic.drawio b/doc/pic.drawio new file mode 100644 index 0000000..d96fc65 --- /dev/null +++ b/doc/pic.drawio @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hw/efinix_fpga/simulation/Makefile b/hw/efinix_fpga/simulation/Makefile index 612d07f..2d6328d 100644 --- a/hw/efinix_fpga/simulation/Makefile +++ b/hw/efinix_fpga/simulation/Makefile @@ -9,6 +9,8 @@ TEST_PROGRAM_NAME?=loop_test TEST_FOLDER?=$(REPO_TOP)/sw/test_code/$(TEST_PROGRAM_NAME) TEST_PROGRAM?=$(REPO_TOP)/sw/test_code/$(TEST_PROGRAM_NAME)/$(TEST_PROGRAM_NAME).hex +STANDALONE_TB= interrupt_controller_tb mapper_code_tb mapper_tb + #TODO implement something like sources.list TOP_MODULE=sim_top @@ -29,11 +31,11 @@ sim: $(TARGET) full_sim: $(TARGET) $(SD_IMAGE) vvp -i $(TARGET) -fst -mapper_tb: $(SRCS) $(TBS) +$(STANDALONE_TB): $(SRCS) $(TBS) iverilog -g2005-sv $(FLAGS) -s $@ -o $@ $(INC) $(SRCS) $(TBS) -mapper_code_tb: $(SRCS) $(TBS) $(INIT_MEM) - iverilog -g2005-sv $(FLAGS) -s $@ -o $@ $(INC) $(SRCS) $(TBS) +# mapper_code_tb: $(SRCS) $(TBS) $(INIT_MEM) +# iverilog -g2005-sv $(FLAGS) -s $@ -o $@ $(INC) $(SRCS) $(TBS) $(TARGET): $(INIT_MEM) $(SRCS) @@ -55,5 +57,5 @@ clean: rm -rf $(TARGET) rm -rf $(INIT_MEM) rm -rf $(SD_IMAGE) - rm -rf mapper_tb - rm -rf mapper_tb.vcd + rm -rf $(STANDALONE_TB) + rm -rf *.vcd diff --git a/hw/efinix_fpga/simulation/tbs/interrupt_controller_tb.sv b/hw/efinix_fpga/simulation/tbs/interrupt_controller_tb.sv new file mode 100644 index 0000000..a234f5f --- /dev/null +++ b/hw/efinix_fpga/simulation/tbs/interrupt_controller_tb.sv @@ -0,0 +1,208 @@ +`timescale 1ns/1ps + +module interrupt_controller_tb(); + +logic r_clk_cpu; + +localparam BITS_256 = 256'hffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + +// clk_cpu +initial begin + r_clk_cpu <= '1; + forever begin + #125 r_clk_cpu <= ~r_clk_cpu; + end +end + +logic reset; +logic addr; +logic [7:0] i_data; +logic [7:0] o_data; +logic cs; +logic rwb; + +logic [255:0] int_in; +logic int_out; + +interrupt_controller u_interrupt_controller( + .clk(r_clk_cpu), + .reset(reset), + .i_data(i_data), + .o_data(o_data), + .addr(addr), + .cs(cs), + .rwb(rwb), + .int_in(int_in), + .int_out(int_out) +); + +/* Test Level triggered IRQ by triggering IRQ0 + * and then clearing it, + */ +task test_edge_irq(); + $display("Testing Edge IRQ"); + do_reset(); + set_enable(255'hff); + set_edge_type(255'h0); + set_interrupts(1); + assert (int_out == 1) else begin + errors = errors + 1; + $error("Interrupt should be high!"); + end + send_eoi(); + assert (int_out == 0) else begin + errors = errors + 1; + $error("Interrupt should be low!"); + end + set_interrupts(0); + assert (int_out == 0) else begin + errors = errors + 1; + $error("Interrupt should be low!"); + end +endtask + +task test_level_irq(); + $display("Testing level IRQ"); + do_reset(); + set_enable(255'hff); + set_edge_type(255'hff); + set_interrupts(1); + assert (int_out == 1) else begin + errors = errors + 1; + $error("Interrupt should be high!"); + end + send_eoi(); + assert (int_out == 1) else begin + errors = errors + 1; + $error("Interrupt should be high!"); + end + set_interrupts(0); + send_eoi(); + assert (int_out == 0) else begin + errors = errors + 1; + $error("Interrupt should be low!"); + end +endtask + + +task test_irq_val(); + int irq_val = -1; + $display("Testing IRQ val output"); + do_reset(); + set_enable('1); + set_edge_type('1); + for (int i = 255; i >= 0; i--) begin + set_interrupts(BITS_256 << i); + read_irqval(irq_val); + assert(i == irq_val) else begin + errors = errors + 1; + $display("Expected %d got %d", i, irq_val); + end + end + + for (int i = 0; i < 256; i++) begin + set_interrupts(BITS_256 >> i); + read_irqval(irq_val); + assert(int_out == 1) else begin + errors = errors + 1; + $display("int_out should be asserted!"); + end + assert(0 == irq_val) else begin + errors = errors + 1; + $display("Expected %d got %d", i, irq_val); + end + end + +endtask + +int errors; +initial begin + errors = 0; + test_edge_irq(); + test_level_irq(); + test_irq_val(); + if (errors > 0) + $finish_and_return(-1); + else + $finish(); +end + +initial +begin + $dumpfile("interrupt_controller_tb.vcd"); + $dumpvars(0,interrupt_controller_tb); +end + +/* These should be shared */ +task write_reg(input logic [4:0] _addr, input logic [7:0] _data); + @(negedge r_clk_cpu); + cs <= '1; + addr <= _addr; + rwb <= '0; + i_data <= '1; + @(posedge r_clk_cpu); + i_data <= _data; + @(negedge r_clk_cpu); + cs <= '0; + rwb <= '1; + @(posedge r_clk_cpu); +endtask + +task read_reg(input logic [2:0] _addr, output logic [7:0] _data); + @(negedge r_clk_cpu); + cs <= '1; + addr <= _addr; + rwb <= '1; + i_data <= '1; + @(posedge r_clk_cpu); + _data <= o_data; + @(negedge r_clk_cpu); + cs <= '0; + rwb <= '1; + @(posedge r_clk_cpu); +endtask + +task do_reset(); + repeat (5) @(posedge r_clk_cpu); + reset = 1; + cs = 0; + rwb = 1; + addr = '0; + i_data = '0; + int_in = '0; + repeat (5) @(posedge r_clk_cpu); + reset = 0; + repeat (5) @(posedge r_clk_cpu); +endtask + +task set_enable(input logic [255:0] en); + for (int i = 0; i < 32; i++) begin + write_reg(0, 8'h20 | i); + write_reg(1, en[8*i +: 8]); + end +endtask + +task set_edge_type(input logic [255:0] edge_type); + for (int i = 0; i < 32; i++) begin + write_reg(0, 8'h40 | i); + write_reg(1, edge_type[8*i +: 8]); + end +endtask + +task set_interrupts(logic [255:0] ints); + int_in = ints; + @(posedge r_clk_cpu); +endtask + +task send_eoi(); + write_reg(0, 8'hff); + write_reg(1, 8'h01); +endtask + +task read_irqval(output logic [7:0] _irq_val); + write_reg(0, 8'h00); + read_reg(1, _irq_val); +endtask + + +endmodule diff --git a/hw/efinix_fpga/src/byte_sel_register.sv b/hw/efinix_fpga/src/byte_sel_register.sv new file mode 100644 index 0000000..c36c5c0 --- /dev/null +++ b/hw/efinix_fpga/src/byte_sel_register.sv @@ -0,0 +1,31 @@ +module byte_sel_register +#( + parameter DATA_WIDTH = 8, + parameter ADDR_WIDTH = 32 +)( + input i_clk, + input i_reset, + input i_write, + input [$clog2(ADDR_WIDTH)-1:0] i_byte_sel, + input [DATA_WIDTH-1:0] i_data, + output [DATA_WIDTH-1:0] o_data, + output [DATA_WIDTH*ADDR_WIDTH-1:0] o_full_data +); + +logic [DATA_WIDTH*ADDR_WIDTH-1:0] r_data; + +assign o_data = r_data[DATA_WIDTH*i_byte_sel +: DATA_WIDTH]; +assign o_full_data = r_data; + +always_ff @(posedge i_clk) begin + if (i_reset) begin + r_data <= '0; + end else begin + r_data <= r_data; + if (i_write) begin + r_data[DATA_WIDTH*i_byte_sel +: DATA_WIDTH] <= i_data; + end + end +end + +endmodule \ No newline at end of file diff --git a/hw/efinix_fpga/src/interrupt_controller.sv b/hw/efinix_fpga/src/interrupt_controller.sv index 4e00957..7f54f31 100644 --- a/hw/efinix_fpga/src/interrupt_controller.sv +++ b/hw/efinix_fpga/src/interrupt_controller.sv @@ -4,26 +4,148 @@ module interrupt_controller input reset, input [7:0] i_data, output logic [7:0] o_data, + input addr, input cs, input rwb, - output logic irqb_master, - - input irqb0, irqb1, irqb2, irqb3, - input irqb4, irqb5, irqb6, irqb7 + input [255:0] int_in, + output logic int_out ); +logic w_enable_write; +logic [7:0] w_enable_data; +logic [255:0] w_enable_full_data; -//All of the inputs are low level triggered. -logic [7:0] irqbv; -assign irqbv = {irqb0, irqb1, irqb2, irqb3, irqb4, irqb5, irqb6, irqb7}; +logic [255:0] int_in_d1; -always @(posedge clk) begin - o_data <= irqbv; - irqb_master = &irqbv; +logic [4:0] w_byte_sel; - if (cs & ~rwb) begin - o_data <= o_data | i_data; +logic [7:0] irq_val; + +byte_sel_register #( + .DATA_WIDTH(8), + .ADDR_WIDTH(32) +) reg_enable ( + .i_clk(~clk), + .i_reset(reset), + .i_write(w_enable_write), + .i_byte_sel(w_byte_sel), + .i_data(i_data), + .o_data(w_enable_data), + .o_full_data(w_enable_full_data) +); + +logic we, re; + +assign we = cs & ~rwb; +assign re = cs & rwb; + +logic [255:0] int_masked; +assign int_masked = int_in & w_enable_full_data; + + +logic w_type_write; +logic [7:0] w_type_data; +logic [255:0] w_type_full_data; + +byte_sel_register #( + .DATA_WIDTH(8), + .ADDR_WIDTH(32) +) reg_type ( + .i_clk(~clk), + .i_reset(reset), + .i_write(w_type_write), + .i_byte_sel(w_byte_sel), + .i_data(i_data), + .o_data(w_type_data), + .o_full_data(w_type_full_data) +); + +logic [7:0] cmd, cmd_next; + +logic w_eoi; + +logic [255:0] r_int, r_int_next; + +always_comb begin + w_eoi = 0; + + if (addr == '0 && we) begin + cmd_next = i_data; + end else begin + cmd_next = cmd; + end + + + w_type_write = '0; + w_enable_write = '0; + + if (addr == '1) begin + unique casez (cmd) + 8'b000?????: begin + o_data = irq_val; + end + + 8'b001?????: begin + w_enable_write = we; + w_byte_sel = cmd[4:0]; + o_data = w_enable_data; + end + + 8'b010?????: begin + w_type_write = we; + w_byte_sel = cmd[4:0]; + o_data = w_type_data; + end + + 8'hff: begin + // Kind of dumb, still requires a data write + w_eoi = i_data[0] & we; + end + endcase + end + + int_out = |r_int; + + irq_val = 8'hff; + for (int i = 255; i >= 0; i--) begin + if (r_int[i] == 1) begin + irq_val = i; + end + end + + for (int i = 0; i < 256; i++) begin + case (w_type_full_data[i]) + 0: begin // Edge triggered + if (w_eoi && i == irq_val) begin + r_int_next[i] = 0; + end else begin + r_int_next[i] = (~int_in_d1[i] & int_masked[i]) | r_int[i]; + end + end + + 1: begin // Level Triggered + // If we are trying to clear this interrupt but it is still active, + // then we don't actually want to clear it. + if (w_eoi && i == irq_val) begin + r_int_next[i] = int_masked[i]; + end else begin + r_int_next[i] = r_int[i] | int_masked[i]; + end + end + endcase + end +end + +always_ff @(negedge clk) begin + if (reset) begin + r_int <= '0; + cmd <= '0; + int_in_d1 <= '0; + end else begin + r_int <= r_int_next; + cmd <= cmd_next; + int_in_d1 <= int_in; end end diff --git a/hw/efinix_fpga/src/super6502.sv b/hw/efinix_fpga/src/super6502.sv index 66fd0fd..f296019 100644 --- a/hw/efinix_fpga/src/super6502.sv +++ b/hw/efinix_fpga/src/super6502.sv @@ -267,16 +267,7 @@ interrupt_controller u_interrupt_controller( .i_data(cpu_data_in), .o_data(w_irq_data_out), .cs(w_irq_cs), - .rwb(cpu_rwb), - .irqb_master(cpu_irqb), - .irqb0(w_timer_irqb), - .irqb1('1), - .irqb2('1), - .irqb3('1), - .irqb4('1), - .irqb5('1), - .irqb6('1), - .irqb7('1) + .rwb(cpu_rwb) ); diff --git a/hw/efinix_fpga/super6502.xml b/hw/efinix_fpga/super6502.xml index 013bb59..709d4f1 100644 --- a/hw/efinix_fpga/super6502.xml +++ b/hw/efinix_fpga/super6502.xml @@ -1,5 +1,5 @@ - + @@ -20,6 +20,7 @@ +