Skip to content

Commit

Permalink
add a bunch of comments and clean up source
Browse files Browse the repository at this point in the history
  • Loading branch information
zhemao committed Mar 3, 2014
1 parent cbaa3f8 commit a14e8eb
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 26 deletions.
31 changes: 23 additions & 8 deletions processor/rtl/alu.v
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ parameter GET_PUT_OPCODE = 4'b0000;
parameter SET_OPCODE = 4'b0100;
parameter RET_OPCODE = 4'b1101;
parameter INDIR_OPCODE = 4'b1110;
parameter SKBC_OPCODE = 4'b1100;

wire [7:0] a = accum;
// immediate instructions and ret instructions use the operand
// instead of the value read from memory
wire [7:0] b = (opcode[3:2] == 2'b01) ? operand : regvalue;
wire [7:0] as_res;

Expand All @@ -39,15 +41,23 @@ addsub as (
.cout (cout)
);

// The bitwise unit's A is normally the regular A,
// but in a CLR or COM instruction on a register,
// a GET, IGET, or SET instruction, the bitwise units
// A should actually be B
wire bw_a_sel = (opcode == CLR_COM_OPCODE && direction) ||
(opcode == GET_PUT_OPCODE && !direction) ||
(opcode == INDIR_OPCODE && !direction) ||
opcode == SET_OPCODE || opcode == RET_OPCODE;
opcode == SET_OPCODE;
wire [1:0] bw_b_sel;
// If this is a GET, PUT, SET, IGET, IPUT, or COM instruction,
// the bitwise unit's B should be all ones
// Recall that X and 1 = X, X xor 1 = ~X
assign bw_b_sel[0] =
(opcode == GET_PUT_OPCODE || opcode == SET_OPCODE ||
opcode == INDIR_OPCODE ||
opcode == RET_OPCODE || opcode == CLR_COM_OPCODE);
opcode == INDIR_OPCODE || opcode == CLR_COM_OPCODE);
// If this is a CLR instruction, the bitwise unit's B should be 0
// so that X and 0 = 0
assign bw_b_sel[1] = (opcode == CLR_COM_OPCODE && !selector[2]);
wire [7:0] bw_res;

Expand Down Expand Up @@ -75,24 +85,29 @@ always @(*) begin
if (opcode == INDIR_OPCODE)
result = bw_res;
else case (opcode[1:0])
// otherwise, we can determine which unit to select the output
// from based on last two bits of the opcode
2'b01: result = shift_res;
2'b10: result = as_res;
default: result = bw_res;
endcase
end

parameter SKBC_OPCODE = 4'b1100;

wire write_out = !(opcode[3:2] == 2'b10 ||
opcode == SKBC_OPCODE ||
opcode == RET_OPCODE);
// GOTO, CALL, RETURN, RETINT, and all the skip instructions
// Do not write anything.
wire write_out = !(opcode[3:2] == 2'b10 || opcode == SKBC_OPCODE);
// Otherwise, write to register or accumulator based on direction bit
assign reg_write = write_out && direction;
assign accum_write = write_out && !direction;
// Z is modified by all instructions in the first half except
// GET, PUT, and SET
assign z_write = (opcode[3] == 1'b0 && opcode[1:0] != 2'b00);
// C is modified by addition or subtraction instructions
assign c_write = (opcode[3] == 1'b0 && opcode[1:0] == 2'b10);
assign zout = (result == 8'd0);
assign retint = (opcode == RET_OPCODE && selector[2] == 1'b1);

// calculate skips here
skip_calc sc (
.opcode (opcode),
.reg_value (regvalue),
Expand Down
3 changes: 3 additions & 0 deletions processor/rtl/ez8_cpu.v
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ reg direction;

wire indir_read_en = (instr[15:12] == 4'b1110);

// Synchronize parts of the instruction for the alu.
// operand is synchronized inside the memory controller,
// so we don't need to handle it here
always @(posedge clk) begin
opcode <= instr[15:12];
selector <= instr[3:1];
Expand Down
3 changes: 3 additions & 0 deletions processor/rtl/io_ctrl.v
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// This module handles access to IO memory.
// Make changes here if you want to add new peripherals

module io_ctrl (
input clk,
input reset,
Expand Down
46 changes: 38 additions & 8 deletions processor/rtl/mem_ctrl.v
Original file line number Diff line number Diff line change
Expand Up @@ -56,24 +56,29 @@ wire [1:0] bank = (writeaddr == 8'h01 && write_en) ?
writedata[6:5] : status[6:5];
reg [1:0] bank_sync;

reg [7:0] indir_addr;
reg [7:0] indir_value;
wire [7:0] indir_addr = indir_value + readaddr;

// if the indirect register is being written to, we have to bypass
always @(*) begin
if (writeaddr == {6'd1, indir_addr_in} && write_en)
indir_addr = writedata + readaddr;
indir_value = writedata;
else
indir_addr = indirects[indir_addr_in] + readaddr;
indir_value = indirects[indir_addr_in];
end

reg [7:0] real_readaddr;

// mux the actual read address
always @(*) begin
if (indir_read_en)
real_readaddr = indir_addr;
else
real_readaddr = readaddr;
end

// create registered versions of all the inputs to match with the
// gpmem registers
always @(posedge clk) begin
if (!pause) begin
writeaddr_sync <= writeaddr;
Expand All @@ -86,21 +91,29 @@ end

assign readaddr_out = readaddr_sync;

// generate the io_* signals
// address is 2 bits of bank + lower 3 bits of address
assign io_readaddr = {bank, real_readaddr[2:0]};
assign io_writeaddr = {bank, writeaddr[2:0]};
assign io_writedata = writedata;
assign io_write_en = (writeaddr[7:3] == 5'd1) && write_en;

wire [7:0] gp_outputs [0:NUM_BANKS-1];

// generate statement for the general purpose memories
// create 1 per bank
genvar i;
generate
for (i = 0; i < NUM_BANKS; i = i + 1) begin : MEM
wire gp_wren = write_en && bank == i && writeaddr[7:4] != 4'd0;
wire [7:0] gp_rdaddr =
(real_readaddr[7:4] == 4'd0) ? 8'd0 : real_readaddr - 8'h10;
wire [7:0] gp_wraddr =
(writeaddr[7:4] == 4'd0) ? 8'd0 : writeaddr - 8'h10;
// are we in the general purpose memory section?
wire write_gp_section = writeaddr[7:4] != 4'd0;
wire read_gp_section = real_readaddr[7:4] != 4'd0;

// don't enable write if we are in the wrong section or bank
wire gp_wren = write_en && bank == i && write_gp_section;
wire [7:0] gp_rdaddr = (!read_gp_section) ? 8'd0 : real_readaddr - 8'h10;
wire [7:0] gp_wraddr = (!write_gp_section) ? 8'd0 : writeaddr - 8'h10;

gpmem mem (
.clock (clk),
.data (writedata),
Expand All @@ -113,9 +126,12 @@ for (i = 0; i < NUM_BANKS; i = i + 1) begin : MEM
end
endgenerate

// mux readdata from the correct memory section
always @(*) begin
// bypass if writing to the same address as read
if (readaddr_sync == writeaddr_sync && write_en_sync)
readdata = writedata_sync;
// null register always returns 0
else if (readaddr_sync == 0)
readdata = 8'd0;
else if (readaddr_sync == 8'd1)
Expand All @@ -132,6 +148,8 @@ always @(*) begin
readdata = gp_outputs[bank_sync];
end

// interrupt if GIE is set, one of the interrupts is triggered,
// and the triggered interrupt is enabled in intcon
wire internal_interrupt = status[7] && ((intcon & io_interrupts) != 0);
assign interrupt = internal_interrupt;

Expand All @@ -146,6 +164,8 @@ always @(posedge clk) begin
intcon <= 8'h00;
intstatus <= 8'h00;
end else begin
// save_accum gets asserted by the PC controller
// before switching into the interrupt context
if (save_accum)
accum_backup <= accum;
if (accum_write)
Expand All @@ -160,21 +180,31 @@ always @(posedge clk) begin
intstatus <= writedata;
else if (writeaddr[7:2] == 6'd1)
indirects[writeaddr[1:0]] <= writedata;
// don't need gpmem here, since that was taken care of
// inside the for-generate statement
end

// only modify individual bits in the status register
// if we are not already writing to the status register
if (!(write_en && writeaddr == 8'd1)) begin
if (z_write)
status[0] <= zin;
if (c_write)
status[1] <= cin;
if (retint) begin
// on retint, set GIE again, restore the accumulator,
// and clear intstatus so that it doesn't get confused
// on the next interrupt
status[7] <= 1'b1;
accum <= accum_backup;
intstatus <= 8'd0;
end
end

if (internal_interrupt) begin
// if an interrupt occurs, we need to clear GIE to disable
// further interrupts and save which interrupts are set
// into instatus
status[7] <= 1'b0;
intstatus <= io_interrupts;
end
Expand Down
42 changes: 40 additions & 2 deletions processor/rtl/pc_ctrl.v
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ module pc_ctrl (
output kill
);

// this is the address the pc jumps to after an interrupt
parameter INTERRUPT_VECTOR = 12'd4;

reg [11:0] pc = 12'd0;
assign pc_out = pc;

// The kill signal is used to cancel an instruction that was mistakenly
// issued (i.e. a skipped instruction or instruction issued after a goto)
// it does this by disabling the write enables going to the memory controller.
// Since there are two pipeline stages between the pc stage and the write
// stage, we need a 2-deep shift register.
reg [1:0] kill_shift;
assign kill = kill_shift[1];

Expand All @@ -42,11 +49,15 @@ stack s (
.full (full)
);

// After an interrupt, we need to wait at most two cycles for the
// instructions already in the pipeline (in the instruction decode and
// read/exec stage) to be committed
reg interrupt_wait = 1'b0;
reg interrupt_save = 1'b0;
reg stop_wait = 1'b0;

initial begin
// make sure the reg outputs are set correctly in the beginning
stopped = 1'b0;
error = 1'b0;
end
Expand All @@ -65,36 +76,53 @@ always @(posedge clk) begin
pop <= 1'b0;
save_accum <= 1'b0;

// we enter this state when we are ready to switch into the
// interrupt context
if (interrupt_save) begin
// we can't safely enter the interrupt context if we can't
// save the pc on the stack
if (full) begin
error <= 1'b1;
stopped <= 1'b1;
end else begin
// program counter should have correct return address by now
// see below for how this is handled
stack_input <= pc;
push <= 1'b1;
end
pc <= INTERRUPT_VECTOR;
interrupt_save <= 1'b0;
// we still need to kill the last instruction,
// which will be the one at the return address
kill_shift <= {kill_shift[0], 1'b1};
save_accum <= 1'b1;
end else if (interrupt_wait) begin
// if the current pc was actually skipped
// the return address should be the next one
if (skip && !kill_shift[1])
pc <= pc + 1'b1;
// transition into interrupt_save
interrupt_wait <= 1'b0;
interrupt_save <= 1'b1;
kill_shift <= {kill_shift[0], 1'b1};
end else if (skip && !kill_shift[1]) begin
// pc is at the correct instruction,
// but the last instruction was issued improperly
// but the last instruction was issued improperly,
// so send a kill signal to cancel
if (interrupt) begin
// If the instruction in read/exec during an interrupt
// is a taken skip, we can go directly to interrupt_save,
// since the instruction currently in decode will be skipped
kill_shift <= 2'b11;
interrupt_save <= 1'b1;
end else begin
kill_shift <= 2'b10;
pc <= pc + 1'b1;
end
end else if (goto && !kill_shift[0]) begin
// If this is a call, then we need to save the
// address after that of the call instruction
// on the stack.
if (call) begin
if (full) begin
// stack overflow
Expand All @@ -103,18 +131,25 @@ always @(posedge clk) begin
end else begin
// pc should currently be at
// call instruction's address + 1
// since we are one cycle after issue
stack_input <= pc;
push <= 1'b1;
end
end
// kill the last instruction since it was issued
// by mistake
kill_shift <= {kill_shift[0], 1'b1};
pc <= goto_addr;
// instruction currently in instruction decode is a goto or call
// which doesn't write anything, so we can skip to interrupt_save
if (interrupt)
interrupt_save <= 1'b1;
end else if (ret && !kill_shift[0]) begin
// this is mostly the same as call,
// except we pop the stack instead of pushing
if (empty)
// processor exited
// wait a cycle for the instruction to go through
// wait a cycle for the last instruction to go through
stop_wait <= 1'b1;
else begin
pc <= stack_output;
Expand All @@ -127,6 +162,9 @@ always @(posedge clk) begin
stopped <= 1'b1;
stop_wait <= 1'b0;
end else begin
// if the instructions in the pipeline are not skips or gotos
// we need to wait the full two cycles before switching
// to the interrupt context
if (interrupt) begin
kill_shift <= {kill_shift[0], 1'b1};
interrupt_wait <= 1'b1;
Expand Down
22 changes: 14 additions & 8 deletions processor/rtl/skip_calc.v
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ module skip_calc (
output reg skip
);

// the value we actually compare to depends on the direction
wire [7:0] used_value = (direction) ? reg_value : accum_value;

wire eqz = (used_value == 8'd0);
wire nez = !eqz;
// negative numbers have the MSB set
wire ltz = used_value[7];
// greater than is the same as not less than and not zero
wire gtz = !ltz && !eqz;
// less than or equal to: pretty self-explanatory
wire lez = ltz || eqz;
// greater than or equal to is the opposite of less than
wire gez = !ltz;

wire bs = used_value[selector];
Expand All @@ -22,15 +27,16 @@ wire bc = !bs;
always @(*) begin
case (opcode)
4'b1010: case (selector)
3'b000: skip = eqz;
3'b001: skip = nez;
3'b010: skip = ltz;
3'b011: skip = gez;
3'b100: skip = gtz;
default: skip = lez;
3'b000: skip = eqz; // skeqz
3'b001: skip = nez; // sknez
3'b010: skip = ltz; // skltz
3'b011: skip = gez; // skgez
3'b100: skip = gtz; // skgtz
default: skip = lez; // sklez
endcase
4'b1011: skip = bs;
4'b1100: skip = bc;
// skbc
4'b1011: skip = bs; // skbs
4'b1100: skip = bc; // skbc
default: skip = 1'b0;
endcase
end
Expand Down

0 comments on commit a14e8eb

Please sign in to comment.