OpenCores
URL https://opencores.org/ocsvn/cpu16/cpu16/trunk

Subversion Repositories cpu16

[/] [cpu16/] [trunk/] [cpu16.v] - Rev 3

Compare with Previous | Blame | View Log

/*
 * None pipelined, 3-state, 16-bit CPU
 * 
 * Copyright (C) 2019, Yvo Zoer
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
 
// comment this line out to use LE's (warning: size goes up dramatically to around 800LE's)
`define USE_RAM_FOR_REGFILE
 
 
module cpu16 (
	clk,
	reset_n,
	clk_en,
	address,
	din,
	dout,
	cs_n,
	oe_n,
	we_n,
	ub_n,
	lb_n,
	irq,		// combined interrupt request from intc
	busreq,
	busack
	);
 
	input clk;
	input reset_n;
	output [15:0] address;
	input clk_en;
	input [15:0] din;
	output [15:0] dout;
	output cs_n;
	output oe_n;
	output we_n;
	output ub_n;
	output lb_n;
	input irq;
	input busreq;
	output busack;
 
	// alu
	parameter OP_SUB		= 5'd0;		// ok
	parameter OP_SBC		= 5'd1;		// ok
	parameter OP_ADD		= 5'd2;
	parameter OP_ADC		= 5'd3;
	parameter OP_CMP		= 5'd4;
	parameter OP_AND		= 5'd5;		// ok
	parameter OP_OR			= 5'd6;		// ok
	parameter OP_XOR		= 5'd7;		// ok
 
	// alu immediate
	parameter OP_SUBI		= 5'd8;		// ok
	parameter OP_SBCI		= 5'd9;		// ok
	parameter OP_ADDI		= 5'd10;	// ok
	parameter OP_ADCI		= 5'd11;	// ok
	parameter OP_CMPI		= 5'd12;
	parameter OP_ANDI		= 5'd13;	// ok
	parameter OP_ORI		= 5'd14;	// ok
	parameter OP_XORI		= 5'd15;	// ok
 
	// shifter + load
	parameter OP_MOV		= 5'd16;	
	parameter OP_SRAV		= 5'd17;
	parameter OP_SLLV		= 5'd18;
	parameter OP_LD			= 5'd19;
 
	// shifter immediate + store
	parameter OP_LUI		= 5'd20;
	parameter OP_SRA		= 5'd21;
	parameter OP_SLL		= 5'd22;
	parameter OP_ST			= 5'd23;
 
	// jump
	parameter OP_J			= 5'd24;
	parameter OP_JAL		= 5'd25;
	parameter OP_JR			= 5'd26;
	parameter OP_JALR		= 5'd27;
 
	// conditional branch
	parameter OP_BXX		= 5'd28;
	parameter OP_MSR		= 5'd29;
	parameter OP_RFE		= 5'd30;
	parameter OP_SWI		= 5'd31;
 
	// states
	parameter STATE_FETCH	= 4'h0;
	parameter STATE_DECODE	= 4'h1;
	parameter STATE_ALU		= 4'h2;
	parameter STATE_SHIFT	= 4'h3;
	parameter STATE_LOAD	= 4'h4;
	parameter STATE_STORE	= 4'h5;
	parameter STATE_J		= 4'h6;
	parameter STATE_JAL		= 4'h7;
	parameter STATE_BRANCH	= 4'h8;
	parameter STATE_MOVSR	= 4'h9;
	parameter STATE_RFE		= 4'ha;
	parameter STATE_SWI		= 4'hb;
	parameter STATE_INTREQ	= 4'hc;
	parameter STATE_BUSREQ	= 4'hd;
 
	// instruction register wires
	wire [4:0] ir_opcode = ir[15:11];
	wire [2:0] ir_dst = ir[10:8];
	wire [2:0] ir_src = ir[7:5];
	wire ir_size = ir[4];	// byte/word flag
	wire [3:0] ir_imm4 = ir[3:0];	// load/store offset
	wire [7:0] ir_imm8 = ir[7:0];	// immediate value / relative conditional branch
	wire [10:0] ir_imm11 = ir[10:0];	// unconditional branch 
 
	// busack valid once we're in the busreq state
	reg busack;
	always @(*)
		if ( state == STATE_BUSREQ )
			busack = 1'b1;
		else
			busack = 1'b0;
 
// chip-select
	reg cs_n;
	always @(*)
		case (state)
			STATE_FETCH,
			STATE_LOAD,
			STATE_STORE : cs_n = 1'b0;
			default : cs_n = 1'b1;
		endcase
 
	// output enable
	reg oe_n;
	always @(*)
		case (state)
			STATE_FETCH,
			STATE_LOAD : oe_n = 1'b0;
			default : oe_n = 1'b1;
		endcase
	// memory write signal
	reg we_n;
	always @(posedge clk)
		if ( state == STATE_STORE )
			we_n <= clk_en;
		else
			we_n <= 1'b1;
 
	// upper / lower byte write/read enable
	// NOTE: could integrate dout here as well..
	reg ub_n;
	reg lb_n;
	always @(*)
		if ( state == STATE_STORE )
			begin
				if ( ir_size )
					begin	// word
						ub_n = 1'b0;
						lb_n = 1'b0;
					end
				else
					begin	// byte
						if ( address[0] )
							begin
								ub_n = 1'b1;
								lb_n = 1'b0;
							end
						else
							begin
								ub_n = 1'b0;
								lb_n = 1'b1;
							end
					end
			end
		else
			begin	// word access for everything else
				ub_n = 1'b0;
				lb_n = 1'b0;
			end
 
	// data output logic
	reg [15:0] dout;
	always @(*)
		if ( ir_size )
			dout = qa;	// word
		else
			begin
				if ( address[0] )
					dout = { 8'h00, qa[7:0] };
				else
					dout = { qa[7:0], 8'h00 };
			end
 
	// address mux 
	reg [15:0] address;
	always @(*)
		case (state)
			STATE_LOAD,
			STATE_STORE : address = qb + ir_imm4;
			default : address = pc;	// { pc[15:1], 1'b0 };
		endcase
 
	// main state machine
	reg [3:0] state, next_state;
	always @(*)
		case (state)
			STATE_FETCH : next_state = STATE_DECODE;
			STATE_DECODE : begin
				casex (ir_opcode)
					5'b0xxxx : next_state = STATE_ALU;		// sub/sbc/add/adc/and/or/xor/cmp
					5'b10x0x,
					5'b10x10 : next_state = STATE_SHIFT;
					5'b10011 : next_state = STATE_LOAD;
					5'b10111 : next_state = STATE_STORE;
					5'b110x0 : next_state = STATE_J;	// 24/26
					5'b110x1 : next_state = STATE_JAL;	// 25/27
					5'b11100 : next_state = STATE_BRANCH;
					5'b11101 : next_state = STATE_MOVSR;
					5'b11110 : next_state = STATE_RFE;
					5'b11111 : next_state = STATE_SWI;
					default : next_state = STATE_FETCH;	
				endcase
				end
			STATE_INTREQ : next_state = STATE_FETCH;
			STATE_BUSREQ : begin
				if ( busreq )
					next_state = STATE_BUSREQ;
				else
					next_state = STATE_FETCH;	// will miss interrupt when coming out of busreq (STATE_NOP?)
				end
			default : begin
				if ( busreq )
					next_state = STATE_BUSREQ;
				else if ( irq & i )	// only fires when interrupt enable is set (i = 1)
					next_state = STATE_INTREQ;
				else
					next_state = STATE_FETCH;
				end
		endcase
 
	// instruction register
	reg [15:0] ir, next_ir;
	always @(*)
		if ( state == STATE_FETCH )
			next_ir = din;
		else
			next_ir = ir;
 
	// exception pc- and status register
	reg [15:0] epc, next_epc;
	reg [4:0] esr, next_esr;
	always @(*)
		case (state)
			STATE_INTREQ : begin
				next_epc = pc;
				next_esr = { i,n,v,z,c };
				end
			default : begin
				next_epc = epc;
				next_esr = esr;
				end
		endcase
 
	// source data for program counter ( register / immediate )
	reg [15:0] pc_data;
	always @(*)
		if ( ir_opcode[1] )	// immediate for PC
			pc_data = qb;	//{ qb[15:1], 1'b0 };	// TEST: jr/jalr
		else
			pc_data = pc + { { 4 { ir_imm11[10] } }, ir_imm11[10:0], 1'b0 };	// TEST: j/jal doubled for jumps
 
	// condition check
	reg condition_true;
	always @(*)
		case (ir_dst)
			3'd0 : condition_true = z;
			3'd1 : condition_true = ~z;
			3'd2 : condition_true = ~n;
			3'd3 : condition_true = n;
			3'd4 : condition_true = ~c;
			3'd5 : condition_true = c;
			3'd6 : condition_true = ~v;
			3'd7 : condition_true = v;
		endcase
 
	// program counter
	reg [15:0] pc, next_pc;
	always @(*)
		case (state)
			STATE_FETCH : next_pc = pc + 16'd2;
			STATE_SWI : next_pc = { ir_imm8, 1'b0 };
			STATE_J,
			STATE_JAL : next_pc = pc_data;
			STATE_BRANCH : begin
				if ( condition_true )
					next_pc = pc + { {  7 { ir_imm8[7] } }, ir_imm8[7:0], 1'b0 };
				else
					next_pc = pc;
				end
			STATE_INTREQ : next_pc = 16'h0002;	// interrupt vector appears at 0x0002
			STATE_RFE : next_pc = epc;
			default : next_pc = pc;
		endcase
 
	// alu data mux
	reg [15:0] alu_data;
	always @(*)
		if (ir_opcode[3])	// immediate
			alu_data = { 8'h00, ir_imm8 };
		else
			alu_data = qb;	// register
 
	// alu
	wire [15:0] alu_result;
	wire alu_n;
	wire alu_v;
	wire alu_z;
	wire alu_c;
	alu i_alu (
		.a(qa),
		.b(alu_data),
		.c_in(c),
		.func(ir_opcode[2:0]),
		.result(alu_result),
		.n(alu_n),
		.v(alu_v),
		.z(alu_z),
		.c(alu_c)
		);
 
	// shifter data mux
	reg [15:0] shifter_data;
	always @(*)
		if ( ir_opcode[2] )	// immediate
			shifter_data = { ir_imm8, 8'h00 };	// lui from instruction word
		else
			shifter_data = qb;	// mov
 
	// shifter distance -- roll into b argument inside shifter?
	reg [3:0] shift_distance;
	always @(*)
		if ( ir_opcode[2] )
			shift_distance = ir_imm4;
		else
			shift_distance = qb[3:0];
 
	// shifter
	wire [15:0] shifter_result;
	wire shift_n;
	wire shift_z;
	wire shift_c;
	wire shift_v;
	shifter i_shifter (
		.a(qa),
		.b(shifter_data),
		.n_in(n),
		.v_in(v),
		.z_in(z),
		.c_in(c),
		.distance(shift_distance),
		.func(ir_opcode[1:0]),
		.result(shifter_result),
		.n(shift_n),
		.v(shift_v),
		.z(shift_z),
		.c(shift_c)
		);
 
	// memory data mux for lw/lb
	reg [15:0] mem_data;
	always @(*)
		if ( ir_size )
			mem_data = din;
		else
			begin
				if ( address[0] )
					mem_data = { 8'h00, din[7:0] };
				else
					mem_data = { 8'h00, din[15:8] };
			end
 
	// register data
	reg [15:0] reg_data;
	always @(*)
		case (state)
			STATE_ALU : reg_data = alu_result;
			STATE_SHIFT : reg_data = shifter_result;
			STATE_JAL,
			STATE_SWI : reg_data = pc;
			default : reg_data = mem_data;
		endcase
 
	// register writeback
	reg reg_write;
	always @(*)
		case (state)
			STATE_ALU,
			STATE_SHIFT,
			STATE_LOAD,
			STATE_JAL,
			STATE_SWI : reg_write = 1'b1;
			default : reg_write = 1'b0;
		endcase
 
	// override dst address for JAL to save 3 bits
	reg [2:0] ra_dst;
	always @(*)
		if ( state == STATE_JAL )
			ra_dst = 3'd6;	// hard linked register
		else
			ra_dst = ir_dst;
 
	// register file	
	`ifdef USE_RAM_FOR_REGFILE
		wire [15:0] qa, qb;
		regfile8x16 i_regfile (
			.clock(clk),
			.data(reg_data),
			.rdaddress_a(ir_dst),
			.rdaddress_b(ir_src),
			.wraddress(ra_dst),
			.wren(reg_write&clk_en),
			.qa(qa),
			.qb(qb)
			);
	`else
		reg [15:0] regs[7:0];
		always @(posedge clk)
			if (reg_write&clk_en)
				regs[ir_dst] <= reg_data;
		wire [15:0] qa = regs[ir_dst];
		wire [15:0] qb = regs[ir_src];
	`endif
 
	// flags
	reg i, next_i;
	reg n, next_n;
	reg v, next_v;
	reg z, next_z;
	reg c, next_c;
	always @(*)
		case (state)
			STATE_ALU : begin
				next_i = i;
				next_n = alu_n;
				next_v = alu_v;
				next_z = alu_z;
				next_c = alu_c;
				end
			STATE_SHIFT : begin
				next_i = i;
				next_n = shift_n;
				next_v = shift_v;
				next_z = shift_z;
				next_c = shift_c;
				end
			STATE_MOVSR : begin
				next_i = (i & ~ir_imm8[4]) | ( ir_imm8[7] & ir_imm8[4]);
				next_n = (n & ~ir_imm8[3]) | ( ir_imm8[7] & ir_imm8[3]);
				next_v = (v & ~ir_imm8[2]) | ( ir_imm8[7] & ir_imm8[2]);
				next_z = (z & ~ir_imm8[1]) | ( ir_imm8[7] & ir_imm8[1]);
				next_c = (c & ~ir_imm8[0]) | ( ir_imm8[7] & ir_imm8[0]);
				end
			STATE_INTREQ : begin
				next_i = 1'b0;	// disable interrupts
				next_n = n;
				next_v = v;
				next_z = z;
				next_c = c;
				end
			STATE_RFE : begin
				next_i = esr[4];
				next_n = esr[3];
				next_v = esr[2];
				next_z = esr[1];
				next_c = esr[0];
				end
			default : begin
				next_i = i;
				next_n = n;
				next_v = v;
				next_z = z;
				next_c = c;
				end
		endcase
 
	// synchronous writeback
	always @(posedge clk or negedge reset_n)
		if (!reset_n)
			begin
				state <= STATE_FETCH;
				ir <= 16'd0;
				pc <= 16'd0;
				i <= 1'b1;	// TEST: enable interrupts
				n <= 1'b0;
				v <= 1'b0;
				z <= 1'b0;
				c <= 1'b0;
				epc <= 16'd0;
				esr <= 5'd0;
			end
		else if ( clk_en )
			begin
				state <= next_state;
				ir <= next_ir;
				pc <= next_pc;
				i <= next_i;
				n <= next_n;
				v <= next_v;
				z <= next_z;
				c <= next_c;
				epc <= next_epc;
				esr <= next_esr;
			end
endmodule
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.