/*
Copyright (c) 2004 Pablo Bleyer Kocik.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/** @file
Behavioral KCPSMX implementation.
*/
`include "kcpsmx_inc.v"
/** Top kcpsmx module. */
module kcpsmx
(
address, instruction,
port_id,
write_strobe, out_port,
read_strobe, in_port,
interrupt,
`ifdef HAS_INTERRUPT_ACK
interrupt_ack,
`endif
reset, clk
);
output reg [`code_depth-1:0] address; ///< Address output.
input [`code_width-1:0] instruction; ///< Instruction input.
output reg [`port_depth-1:0] port_id; ///< Port address.
output reg write_strobe; ///< Port output strobe.
output reg [`port_width-1:0] out_port; ///< Port output.
output reg read_strobe; ///< Port input strobe.
input [`port_width-1:0] in_port; ///< Port input.
input interrupt; ///< Interrupt request.
`ifdef HAS_INTERRUPT_ACK
output interrupt_ack; ///< Interrupt acknowledge (output).
`endif
input reset; ///< Reset input.
input clk; ///< Clock input.
/* Processor registers and signals. */
reg [`code_depth-1:0] program_counter; ///< Program counter.
reg timing_control; ///< Timing control register.
reg zero; ///< Zero flag
reg carry; ///< Carry flag
reg interrupt_enable; ///< Interrupt enable.
reg interrupt_latch; ///< Interrupt latch hold.
reg interrupt_ack; ///< Interrupt acknowledge.
reg zero_saved; ///< Interrupt-saved zero flag.
reg carry_saved; ///< Interrupt-saved carry flag.
`ifdef HAS_RESET_LATCH
reg [1:0] reset_latch; ///< Reset latch.
`endif
reg zero_carry_write_enable; ///< Zero/Carry update.
wire internal_reset; ///< Internal reset signal.
wire [`code_depth-1:0] program_counter_1; ///< Program_counter plus 1.
wire [`stack_depth-1:0] stack_pointer_1; ///< Stack_pointer plus/minus 1.
reg [`stack_depth-1:0] stack_pointer_1p; ///< Registered (previous) stack_pointer_1
wire conditional_match; ///< True when unconditional or flags match.
/* IDU - Instruction decode unit. */
wire [`operation_width-1:0] idu_operation;
`ifdef KCPSM1
wire [`suboperation_width-1:0] idu_suboperation;
`endif
`ifdef KCPSM2
wire idu_suboperation;
`endif
wire [2:0] idu_shift_operation;
wire idu_shift_direction, idu_shift_constant;
wire idu_operand_selection;
wire [`register_depth-1:0] idu_x_address, idu_y_address;
wire [`operand_width-1:0] idu_implied_value;
wire [`port_depth-1:0] idu_port_address;
`ifdef HAS_SCRATCH_MEMORY
wire [`scratch_depth-1:0] idu_scratch_address;
`endif
wire [`code_depth-1:0] idu_code_address;
wire idu_conditional;
wire [1:0] idu_condition_flags;
wire idu_interrupt_enable;
kcpsmx_idu idu(
instruction,
idu_operation,
`ifdef KCPSM1
idu_suboperation,
`endif
`ifdef KCPSM2
idu_suboperation,
`endif
idu_shift_operation, idu_shift_direction, idu_shift_constant,
idu_operand_selection,
idu_x_address, idu_y_address,
idu_implied_value, idu_port_address,
`ifdef HAS_SCRATCH_MEMORY
idu_scratch_address,
`endif
idu_code_address,
idu_conditional, idu_condition_flags,
idu_interrupt_enable
);
/* ALU - Arithmetic/logic unit. */
wire [`operand_width-1:0] alu_result, alu_operand_a, alu_operand_b;
wire alu_zero_out, alu_carry_out;
kcpsmx_alu alu(
idu_operation,
idu_shift_operation, idu_shift_direction, idu_shift_constant,
alu_result, alu_operand_a, alu_operand_b, carry,
alu_zero_out, alu_carry_out
);
/* Register file. */
reg register_x_write_enable;
wire [`register_width-1:0] register_x_data_in, register_x_data_out, register_y_data_out;
kcpsmx_register register(
idu_x_address, register_x_write_enable, register_x_data_in, register_x_data_out,
idu_y_address, register_y_data_out,
reset, clk
);
/* Call/return stack. */
reg [`stack_depth-1:0] stack_pointer; ///< Call stack pointer.
wire [`stack_width-1:0] stack_data_out;
wire [`stack_width-1:0] stack_data_in =
(interrupt_latch == 0) ? program_counter_1 : program_counter;
reg stack_write_enable;
wire [`stack_depth-1:0] stack_address =
(stack_write_enable) ? stack_pointer : stack_pointer_1;
kcpsmx_stack stack(
stack_address, stack_write_enable, stack_data_in, stack_data_out,
reset, clk
);
/* Scratchpad RAM. */
`ifdef HAS_SCRATCH_MEMORY
reg scratch_write_enable;
wire [`scratch_depth-1:0] scratch_address =
(idu_operand_selection == 0) ? idu_scratch_address :
register_y_data_out[`scratch_depth-1:0];
wire [`scratch_width-1:0] scratch_data_out;
kcpsmx_scratch scratch(
scratch_address, scratch_write_enable, register_x_data_out, scratch_data_out,
reset, clk
);
`endif
/* Miscellaneous. */
`ifdef HAS_RESET_LATCH
assign internal_reset = reset_latch[1];
`else
assign internal_reset = reset;
`endif
assign program_counter_1 = program_counter + 1;
assign stack_pointer_1 =
(stack_write_enable) ? stack_pointer + 1 : stack_pointer - 1;
assign conditional_match =
(!idu_conditional
|| idu_condition_flags == `flag_c && carry
|| idu_condition_flags == `flag_nc && ~carry
|| idu_condition_flags == `flag_z && zero
|| idu_condition_flags == `flag_nz && ~zero
) ? 1 : 0;
assign alu_operand_a = register_x_data_out;
assign alu_operand_b =
(idu_operand_selection == 0) ? idu_implied_value : register_y_data_out;
// synthesis parallel_case full_case
assign register_x_data_in =
`ifdef HAS_SCRATCH_MEMORY
(idu_operation == `opcode_fetch) ? scratch_data_out :
`endif
`ifdef KCPSM1
(idu_operation[3:1] == `opcode_input) ? in_port :
`else
(idu_operation == `opcode_input) ? in_port :
`endif
alu_result;
task jump;
input [`code_depth-1:0] next_address;
begin
program_counter <= next_address;
end
endtask
task push;
input [`code_depth-1:0] next_address;
begin
stack_pointer <= stack_pointer_1p; // == stack_pointer+1
program_counter <= next_address;
end
endtask
task pop;
begin
stack_pointer <= stack_pointer_1; // == stack_pointer-1
program_counter <= stack_data_out;
end
endtask
task execute;
input [`operation_width-1:0] operation;
`ifdef KCPSM1
input [`suboperation_width-1:0] suboperation;
`endif
`ifdef KCPSM2
input suboperation;
`endif
begin
// synthesis parallel_case full_case
`ifdef KCPSM1
casex (operation)
`else
case (operation)
`endif
`opcode_load: register_x_write_enable <= 1;
`opcode_and,
`opcode_or,
`opcode_xor,
`opcode_add,
`opcode_addcy,
`opcode_sub,
`opcode_subcy,
`opcode_rs: begin
register_x_write_enable <= 1; // writeback sX
zero_carry_write_enable <= 1; // writeback zero, carry
end
`ifdef KCPSM1
{`opcode_ctl, 1'b?}:
casex (suboperation)
{`opcode_jump, 2'b??}:
if (conditional_match) jump(idu_code_address);
{`opcode_call, 2'b??}:
if (conditional_match) push(idu_code_address);
`opcode_return:
if (conditional_match) pop;
`opcode_returni: begin
pop;
zero <= zero_saved;
carry <= carry_saved;
interrupt_enable <= idu_interrupt_enable;
end
`opcode_interrupt: interrupt_enable <= idu_interrupt_enable;
endcase
{`opcode_input, 1'b?}: begin
read_strobe <= 1;
register_x_write_enable <= 1;
end
{`opcode_output, 1'b?}:
write_strobe <= 1;
`endif // KCPSM1
`ifdef KCPSM2
`opcode_jump: // == `opcode_return
if (suboperation) // jump
if (conditional_match) jump(idu_code_address);
else // return
if (conditional_match) pop;
`opcode_call:
if (suboperation && conditional_match) push(idu_code_address);
`opcode_returni: // == `opcode_interrupt
if (!suboperation) begin // returni
pop;
zero <= zero_saved;
carry <= carry_saved;
interrupt_enable <= idu_interrupt_enable;
end
else // interrupt
interrupt_enable <= idu_interrupt_enable;
`opcode_input: begin
read_strobe <= 1;
register_x_write_enable <= 1;
end
`opcode_output:
write_strobe <= 1;
`endif // KCPSM2
`ifdef KCPSM3
`opcode_jump:
if (conditional_match) jump(idu_code_address);
`opcode_call:
if (conditional_match) push(idu_code_address);
`opcode_return:
if (conditional_match) pop;
`opcode_returni: begin
pop;
zero <= zero_saved;
carry <= carry_saved;
interrupt_enable <= idu_interrupt_enable;
end
`opcode_interrupt: interrupt_enable <= idu_interrupt_enable;
`opcode_input: begin
read_strobe <= 1;
register_x_write_enable <= 1;
end
`opcode_output:
write_strobe <= 1;
`endif // KCPSM3
`ifdef HAS_COMPARE_OPERATION
`opcode_compare:
zero_carry_write_enable <= 1;
`endif
`ifdef HAS_TEST_OPERATION
`opcode_test:
zero_carry_write_enable <= 1;
`endif
`ifdef HAS_SCRATCH_MEMORY
`opcode_fetch:
register_x_write_enable <= 1; // transfer scratch to sX
`opcode_store:
scratch_write_enable <= 1; // transfer sX to scratch
`endif
default: ;
endcase
end
endtask
// combinatorial block
always @(program_counter, idu_port_address, register_x_data_out) begin
// default actions
address = program_counter;
port_id = idu_port_address;
out_port = register_x_data_out;
end
`ifdef HAS_RESET_LATCH
always @(posedge clk) begin: on_reset
if (reset) reset_latch <= 'b11;
else begin
reset_latch[1] <= reset_latch[0];
reset_latch[0] <= 0;
end
end
`endif
// sequential block
always @(posedge clk) begin
/* Idle values and default actions. */
read_strobe <= 0; write_strobe <= 0;
register_x_write_enable <= 0;
`ifdef HAS_SCRATCH_MEMORY
scratch_write_enable <= 0;
`endif
interrupt_ack <= 0;
zero_carry_write_enable <= 0;
if (internal_reset) begin: on_internal_reset
/* Reset values. */
timing_control <= 0;
program_counter <= `reset_vector;
stack_write_enable <= 1; stack_pointer <= 0; stack_pointer_1p <= 0;
zero <= 0; carry <= 0;
interrupt_enable <= 0; interrupt_latch <= 0;
end
else begin: on_run
timing_control <= ~timing_control; // step timing control
stack_write_enable <= ~timing_control;
stack_pointer_1p <= stack_pointer_1;
if (interrupt && interrupt_enable) interrupt_latch <= 1;
if (timing_control == 0) begin
program_counter <= program_counter_1; // default next program counter
if (interrupt_latch) begin
interrupt_enable <= 0; interrupt_latch <= 0; interrupt_ack <= 1;
zero_saved <= zero; carry_saved <= carry;
push(`interrupt_vector);
end
else
`ifdef KCPSM1
execute(idu_operation, idu_suboperation);
`endif
`ifdef KCPSM2
execute(idu_operation, idu_suboperation);
`endif
`ifdef KCPSM3
execute(idu_operation);
`endif
end
else begin // timing_control == 1
if (zero_carry_write_enable) begin
zero <= alu_zero_out; carry <= alu_carry_out;
end
end
end // on_run
end
endmodule
/** Register file; dual-port RAM. */
module kcpsmx_register
(
x_address, x_write_enable, x_data_in, x_data_out,
y_address, y_data_out,
reset, clk
);
input clk, reset, x_write_enable;
input [`register_depth-1:0] x_address, y_address;
input [`register_width-1:0] x_data_in;
output [`register_width-1:0] x_data_out, y_data_out;
reg [`register_width-1:0] dpr[0:`register_size-1];
assign x_data_out = dpr[x_address];
assign y_data_out = dpr[y_address];
always @(posedge clk)
if (x_write_enable) dpr[x_address] <= x_data_in;
endmodule
/** Scratchpad RAM; single-port RAM. */
`ifdef HAS_SCRATCH_MEMORY
module kcpsmx_scratch
(
address, write_enable, data_in, data_out,
reset, clk
);
input clk, reset, write_enable;
input [`scratch_depth-1:0] address;
input [`scratch_width-1:0] data_in;
output [`scratch_width-1:0] data_out;
reg [`scratch_width-1:0] spr[0:`scratch_size-1];
assign data_out = spr[address];
always @(posedge clk) if (write_enable) spr[address] <= data_in;
endmodule
`endif
/** Call/return stack; single-port RAM. */
module kcpsmx_stack
(
address, write_enable, data_in, data_out,
reset, clk
);
input clk, reset, write_enable;
input [`stack_depth-1:0] address;
input [`stack_width-1:0] data_in;
output [`stack_width-1:0] data_out;
reg [`stack_width-1:0] spr[0:`stack_size-1];
assign data_out = spr[address];
always @(posedge clk) if (write_enable) spr[address] <= data_in;
endmodule
/** Instruction decode unit. */
module kcpsmx_idu
(
instruction,
operation,
`ifdef KCPSM1
suboperation,
`endif
`ifdef KCPSM2
suboperation,
`endif
shift_operation, shift_direction, shift_constant,
operand_selection,
x_address, y_address,
implied_value, port_address,
`ifdef HAS_SCRATCH_MEMORY
scratch_address,
`endif
code_address,
conditional, condition_flags,
interrupt_enable
);
input [`code_width-1:0] instruction; ///< Instruction.
output [`operation_width-1:0] operation; ///< Main operation.
`ifdef KCPSM1
output [`suboperation_width-1:0] suboperation; ///< Suboperation operation.
`endif
`ifdef KCPSM2
output suboperation; ///< Suboperation operation.
`endif
output [2:0] shift_operation; ///< Rotate/shift operation.
output shift_direction; ///< Rotate/shift left(0)/right(1).
output shift_constant; ///< Shift constant value.
output operand_selection; ///< Operand selection (kps(0)/y(1)).
output [`register_depth-1:0] x_address, y_address; ///< Operation x source/target, y source.
output [`operand_width-1:0] implied_value; ///< Operand constant source.
output [`port_depth-1:0] port_address; ///< Port address.
`ifdef HAS_SCRATCH_MEMORY
output [`scratch_depth-1:0] scratch_address; ///< Scratchpad address.
`endif
output [`code_depth-1:0] code_address; ///< Program address.
output conditional; ///< Conditional operation (unconditional(0)/conditional(1)).
output [1:0] condition_flags; ///< Condition flags on zero and carry.
output interrupt_enable; ///< Interrupt disable(0)/enable(1).
`ifdef KCPSM1
assign operation =
(instruction[15:12] == `opcode_reg) ? instruction[3:0] :
instruction[15:12];
assign suboperation = instruction[9:6];
assign x_address = instruction[11:8];
assign y_address = instruction[7:4];
assign operand_selection =
(instruction[15] && instruction[13]) ? instruction[12] :
instruction[15];
assign code_address = instruction[7:0];
assign interrupt_enable = instruction[5];
`endif
`ifdef KCPSM2
assign operation = {instruction[17], instruction[15:13]};
assign suboperation = instruction[16];
assign x_address = instruction[12:8];
assign y_address = instruction[7:3];
assign operand_selection = instruction[16];
assign code_address = instruction[9:0];
assign interrupt_enable = instruction[0];
`endif
`ifdef KCPSM3
assign operation = instruction[17:13];
assign x_address = instruction[11:8];
assign y_address = instruction[7:4];
assign operand_selection = instruction[12];
assign code_address = instruction[9:0];
assign interrupt_enable = instruction[0];
assign scratch_address = instruction[5:0];
`endif
assign shift_direction = instruction[3];
assign shift_operation = instruction[2:1];
assign shift_constant = instruction[0];
assign conditional = instruction[12];
assign condition_flags = instruction[11:10];
assign implied_value = instruction[7:0];
assign port_address = instruction[7:0];
endmodule
/** Arithmetic-logic unit. */
module kcpsmx_alu
(
operation,
shift_operation, shift_direction, shift_constant,
result, operand_a, operand_b, carry_in,
zero_out, carry_out
);
input [`operation_width-1:0] operation; ///< Main operation.
input [2:0] shift_operation; ///< Rotate/shift operation.
input shift_direction; ///< Rotate/shift left(0)/right(1).
input shift_constant; ///< Shift constant (0 or 1).
output reg [`operand_width-1:0] result; ///< ALU result.
input [`operand_width-1:0] operand_a, operand_b; ///< ALU operands.
input carry_in; ///< Carry in.
output reg zero_out; ///< Zero out.
output reg carry_out; ///< Carry out.
/** Adder/substracter second operand. */
wire [`operand_width-1:0] addsub_b =
(operation == `opcode_sub
|| operation == `opcode_subcy
`ifdef HAS_COMPARE_OPERATION
|| operation == `opcode_compare
`endif
) ? ~operand_b :
operand_b;
/** Adder/substracter carry. */
wire addsub_carry =
(operation == `opcode_addcy) ? carry_in :
(operation == `opcode_sub
`ifdef HAS_COMPARE_OPERATION
|| operation == `opcode_compare
`endif
) ? 1 : // ~b => b'
(operation == `opcode_subcy) ? ~carry_in : // ~b - c => b' - c
0;
/** Adder/substracter with carry. */
wire [1+`operand_width-1:0] addsub_result = operand_a + addsub_b + addsub_carry;
/** Shift bit value. */
// synthesis parallel_case full_case
wire shift_bit =
(shift_operation == `opcode_rr) ? result[0] : // == `opcode_slx
(shift_operation == `opcode_rl) ? result[7] : // == `opcode_srx
(shift_operation == `opcode_rsa) ? carry_in :
shift_constant; // == `opcode_rsc
// always @*
always @(operation,
shift_operation, shift_direction, shift_constant, shift_bit,
result, operand_a, operand_b, carry_in,
zero_out, carry_out,
addsub_result, addsub_carry
) begin: on_alu
/* Defaults */
carry_out = 0;
// synthesis parallel_case full_case
case (operation)
`opcode_add,
`opcode_addcy:
{carry_out, result} = addsub_result;
`ifdef HAS_COMPARE_OPERATION
`opcode_compare,
`endif
`opcode_sub,
`opcode_subcy:
{carry_out, result} = {~addsub_result[8], addsub_result[7:0]};
`opcode_and:
result = operand_a & operand_b;
`opcode_or:
result = operand_a | operand_b;
`ifdef HAS_TEST_OPERATION
`opcode_test:
begin result = operand_a & operand_b; carry_out = ^result; end
`endif
`opcode_xor:
result = operand_a ^ operand_b;
`opcode_rs:
if (shift_direction) // shift right
{result, carry_out} = {shift_bit, operand_a};
else // shift left
{carry_out, result} = {operand_a, shift_bit};
default:
result = operand_b;
endcase
zero_out = ~|result;
end
endmodule