Jun 02 2012

Building a processor with Verilog, part 6

Category: Uncategorizedzvolkov @ 12:30 pm

This time, I implemented a new class of instructions: MOV, for setting a register to a value of another register.

I have also changed all instances of blocking assignment “=” to the non-blocking assignment, “<=” — to better simulate behavior of real registers. This way the display statements printing the values of registers A, B, C, etc. will more closely correspond to the actual value of the register during that tick, and the new value will not be printed until the next tick.

Last but not least, if you look at the code you will notice how messy and unreadable it became. So far, to keep the story simple, I kept the code structure as flat as possible. Next time I should split things up in more manageable logic blocks. Specifically, the case statements on register specifier fields in the instruction, should become a reusable function or perhaps even a separate module.

As always, I removed the old comments and commented the new and updated code lines:

module CPU(input iClk, input iRst, output [7:0] oAddressBus, input [7:0] i

    localparam MVI=2'b00, MOV=2'b01, ALU=2'b10, ALI=2'b11;

    localparam B = 3'b000, C = 3'b001, D = 3'b010, E = 3'b011,
               H = 3'b100, L = 3'b101, M = 3'b110, A = 3'b111;

    reg [7:0] _pc;

    reg _mode;
    localparam NORMAL=1'b0, READ_IMMED=1'b1;

    reg [7:0] _instruction;
    reg [7:0] _a, _b, _c, _d, _e, _h, _l;

    always @(posedge iClk, posedge iRst)
    begin
        if(iRst)
            begin
                $display("resetting...");
                //In real life registers don't take new value
                //until the raising edge of the next clock tick.
                //In Verilog, such behavior is simulated
                //with non-blocking assignment, "<="
                _pc <= 8'h0;
                _mode <= NORMAL;
                //setting all registers to zero on reset
                _a <= 8'h0;
                _b <= 8'h0;
                _c <= 8'h0;
                _d <= 8'h0;
                _e <= 8'h0;
                _h <= 8'h0;
                _l <= 8'h0;
            end

        else begin
            $display("PC:%d", _pc);
            $display("mem:%d", iMemoryValue);

            case(_mode)
                NORMAL:
                    if(iMemoryValue == 8'h0) $display("NOP");
                    else case(iMemoryValue[7:6])
                        MVI: if(iMemoryValue[2:0] == M) begin
                                $display("initiating MVI");
                                _mode <= READ_IMMED;
                                _instruction <= iMemoryValue;
                             end
                             else $display("unknown instruction");
                        //detecting a MOV-class instruction
                        MOV:
                            //register fields of the instruction
                            //specify source and destination
                            case(iMemoryValue[5:0])
                                //using {} syntaxis to concatenate
                                //the constants A and B
                                {A, B}: begin
                                    $display("MOV A, B");
                                    _a <= _b;
                                end
                                {B, A}: begin
                                    $display("MOV B, A");
                                    _b <= _a;
                                end
                                //TODO: add {A, C} etc.
                                //not supporting other combinations yet
                                default: $display("unknown instruction");
                            endcase
                        default:
                            $display("unknown instruction");
                    endcase

                READ_IMMED: begin
                    case(_instruction[5:3])
                        B: begin
                            $display("MVI B, %d", iMemoryValue);
                            _b <= iMemoryValue;
                        end
                        C: begin
                            $display("MVI C, %d", iMemoryValue);
                            _c <= iMemoryValue;
                        end
                        D: begin
                            $display("MVI D, %d", iMemoryValue);
                            _d <= iMemoryValue;
                        end
                        E: begin
                            $display("MVI E, %d", iMemoryValue);
                            _e <= iMemoryValue;
                        end
                        H: begin
                            $display("MVI H, %d", iMemoryValue);
                            _h <= iMemoryValue;
                        end
                        L: begin
                            $display("MVI L, %d", iMemoryValue);
                            _l <= iMemoryValue;
                        end
                        M: $display("unknown instruction");
                        A: begin
                            $display("MVI A, %d", iMemoryValue);
                            _a <= iMemoryValue;
                        end
                    endcase
                    _mode <= NORMAL;
                end
            endcase

            //more debug prints
            $display("A=%d", _a);
            $display("B=%d", _b);
            $display("C=%d", _c);
            $display("D=%d", _d);
            $display("E=%d", _e);
            $display("H=%d", _h);
            $display("L=%d", _l);

            _pc <= _pc + 1'b1;
        end
    end

    assign oAddressBus = _pc;

endmodule

module ROM(input [7:0] iAddress, output [7:0] oReadPort);

    reg [7:0] _rom [0:255];

    initial
    begin
        _rom[0] = 0;                //NOP
        //verilog allows underscores in numeric literals,
        //to improve readability
        _rom[1] = 8'b00_000_110;    //MVI B, 55
        _rom[2] = 55;
        _rom[3] = 8'b00_001_110;    //MVI C, 255
        _rom[4] = 255;
        _rom[5] = 8'b01_111_000;    //MOV A, B
        //etc.
    end

    assign oReadPort = _rom[iAddress];

endmodule

And here’s the printout (only showing clock ticks 3 to 10):

--------------Tick           3---------------
PC:  x
mem:  x
A=  x
B=  x
C=  x
D=  x
E=  x
H=  x
L=  x
resetting...
--------------Tick           4---------------
PC:  0
mem:  0
executing NOP!
A=  0
B=  0
C=  0
D=  0
E=  0
H=  0
L=  0
--------------Tick           5---------------
PC:  1
mem:  6
initiating MVI
A=  0
B=  0
C=  0
D=  0
E=  0
H=  0
L=  0
--------------Tick           6---------------
PC:  2
mem: 55
MVI B,  55
A=  0
B=  0
C=  0
D=  0
E=  0
H=  0
L=  0
--------------Tick           7---------------
PC:  3
mem: 14
initiating MVI
A=  0
B= 55
C=  0
D=  0
E=  0
H=  0
L=  0
--------------Tick           8---------------
PC:  4
mem:255
MVI C, 255
A=  0
B= 55
C=  0
D=  0
E=  0
H=  0
L=  0
--------------Tick           9---------------
PC:  5
mem:120
MOV A, B
A=  0
B= 55
C=255
D=  0
E=  0
H=  0
L=  0
--------------Tick          10---------------
PC:  6
mem:  x
unknown instruction
A= 55
B= 55
C=255
D=  0
E=  0
H=  0
L=  0


Jun 01 2012

Building a processor with Verilog, part 5

Category: Uncategorizedzvolkov @ 9:24 pm

Slowly and lazily, just like the snail climbing Fuji mountain, I added ability to execute MVI instructions. This was as easy as defining a handful of registers and setting them in corresponding places:

module CPU(input iClk, input iRst, output [7:0] oAddressBus, input [7:0] iMemoryValue);

	localparam MVI=2'b00, MOV=2'b01, ALU=2'b10, ALI=2'b11;

	localparam B = 3'b000, C = 3'b001, D = 3'b010, E = 3'b011,
			   H = 3'b100, L = 3'b101, M = 3'b110, A = 3'b111;

	reg [7:0] _pc;

	reg _mode;
	localparam
		NORMAL=1'b0,
		READ_IMMED=1'b1;

	reg [7:0] _instruction;
	//our new and shiny registers:
	reg [7:0] _a, _b, _c, _d, _e, _h, _l;

	always @(posedge iClk, posedge iRst)
	begin
		if(iRst)
			begin
				$display("resetting...");
				_pc = 8'h0;
				_mode = NORMAL;
			end
		else begin
			$display("PC:%d", _pc);
			$display("mem:%d", iMemoryValue);

			case(_mode)
				NORMAL:
					if(iMemoryValue == 8'h0) $display("executing NOP!");
					else case(iMemoryValue[7:6])
						MVI: if(iMemoryValue[2:0] == M) begin
								$display("initiating MVI");
								_mode = READ_IMMED;
								_instruction = iMemoryValue;
							 end
							 else $display("unknown instruction");
						default:
							$display("unknown instruction");
					endcase
				READ_IMMED: begin
					case(_instruction[5:3])
						B: begin
							$display("MVI B, %d", iMemoryValue);
							//now we can actually perform the operation requested:
							_b = iMemoryValue;
						end
						C: begin
							$display("MVI C, %d", iMemoryValue);
							_c = iMemoryValue;
						end
						D: begin
							$display("MVI D, %d", iMemoryValue);
							_d = iMemoryValue;
						end
						E: begin
							$display("MVI E, %d", iMemoryValue);
							_e = iMemoryValue;
						end
						H: begin
							$display("MVI H, %d", iMemoryValue);
							_h = iMemoryValue;
						end
						L: begin
							$display("MVI L, %d", iMemoryValue);
							_l = iMemoryValue;
						end
						//moving from memory to memory is not supported in this architecture
						M: $display("unknown instruction");
						A: begin
							$display("MVI A, %d", iMemoryValue);
							_a = iMemoryValue;
						end
					endcase
					_mode = NORMAL;
				end
			endcase

			_pc = _pc + 1'b1;
		end
	end

	assign oAddressBus = _pc;

endmodule

Since the debug messages were not changed, the printout looks exactly same as last time.

The next step should be support of MOV instructions.


May 24 2012

Building a processor with Verilog, part 4

Category: Uncategorizedzvolkov @ 10:41 pm

A funny thing about circuits: they work nothing like what we software developers expect them to. Specifically, it turns out that synchronous circuits, like memory, registers, counters etc. can only do one thing in one clock cycle. This means for example that you can’t read the instruction and the subsequent immediate value in the same clock cycle. You have to split your work across multiple clock cycles.

In our simplified example, ROM is an asynchronous circuit, so we could read multiple values  In real life RAM takes many cycles to get the value from, and the faster the processor, the more cycles it need to wait for RAM.

Let’s try to imagine how such logic could look like:

module CPU(input iClk, input iRst, output [7:0] oAddressBus, input [7:0] iMemoryValue);

	localparam MVI=2'b00, MOV=2'b01, ALU=2'b10, ALI=2'b11;

	localparam B = 3'b000, C = 3'b001, D = 3'b010, E = 3'b011,
			   H = 3'b100, L = 3'b101, M = 3'b110, A = 3'b111;

	reg [7:0] _pc;

	//Now that we realized we need to do things across multiple clock cycles,
	//we'll need a register to keep track of where we are.
	reg _mode;
	localparam
		NORMAL=1'b0, // reading the instruction
		READ_IMMED=1'b1; // reading the data value immediately following the instruction

	//also, need a register to remember the instruction once the memory has advanced to the next cell.
	reg [7:0] _instruction;

	always @(posedge iClk, posedge iRst)
	begin
		if(iRst)
			begin
				$display("resetting...");
				_pc = 8'h0;
				//always start in the Normal mode
				_mode = NORMAL;
			end
		else begin
			$display("PC:%d", _pc);
			$display("mem:%d", iMemoryValue);

			//do a different thing depending on the current mode
			case(_mode)
				NORMAL:
					if(iMemoryValue == 8'h0) $display("executing NOP!");
					else case(iMemoryValue[7:6])
						MVI: if(iMemoryValue[2:0] == M) begin
								$display("initiating MVI");
								//switch the mode so we can read the next memory value on the next clock cycle.
								_mode = READ_IMMED;
								//remember this is an MVI we're processing.
								_instruction = iMemoryValue;
							 end
							 else $display("unknown instruction");
						default:
							$display("unknown instruction");
					endcase
				READ_IMMED: begin
					//we know we're in the middle of an MVI instruction.
					//now we can read the second operand from memory and execute the instruction.
					case(_instruction[5:3])
						B: $display("MVI B, %d", iMemoryValue);
						C: $display("MVI C, %d", iMemoryValue);
						D: $display("MVI D, %d", iMemoryValue);
						E: $display("MVI E, %d", iMemoryValue);
						H: $display("MVI H, %d", iMemoryValue);
						L: $display("MVI L, %d", iMemoryValue);
						M: $display("MVI M, %d", iMemoryValue);
						A: $display("MVI A, %d", iMemoryValue);
					endcase
					//don't forget to switch back to the normal mode.
					_mode = NORMAL;
				end
			endcase

			_pc = _pc + 1'b1;
		end
	end

	assign oAddressBus = _pc;

endmodule

And the print out is:

--------------Tick           1---------------
PC:  x
mem:  x
--------------Tick           2---------------
PC:  x
mem:  x
--------------Tick           3---------------
PC:  x
mem:  x
resetting...
--------------Tick           4---------------
PC:  0
mem:  0
executing NOP!
--------------Tick           5---------------
PC:  1
mem:  6
initiating MVI
--------------Tick           6---------------
PC:  2
mem: 55
MVI B,  55
--------------Tick           7---------------
PC:  3
mem: 14
initiating MVI
--------------Tick           8---------------
PC:  4
mem:255
MVI C, 255
--------------Tick           9---------------
PC:  5
mem:  1
unknown instruction
--------------Tick          10---------------
PC:  6
mem:  x
unknown instruction
--------------Tick          11---------------
PC:  7
mem:  x
unknown instruction
--------------Tick          12---------------
PC:  8
mem:  x
unknown instruction
--------------Tick          13---------------
PC:  9
mem:  x
unknown instruction

See how on tick 5 we’re “initiating MVI” and on tick 6 we have a complete instruction, including the immediate value? I wonder what it would take to actually execute an instruction…


May 24 2012

Building a processor with Verilog, part 3

Category: Uncategorizedzvolkov @ 10:25 pm

Now that we got NOP working, let’s finally do something useful. Like recognizing an actual instruction.

For that, we’ll need to come up with some kind of system for giving our instructions numeric codes. For simplicity, I’ll just reuse roughly the same system Intel’s 8080 processor used.

We’ll have several major classes of instructions, coded by the first two bits of the command, plus two arguments, coded by the next three and three bits.

First instruction we’ll implement is MVI (MoVe Immediate) — it takes a byte of memory following the instruction itself and loads it in the specified register. Enough talking, let’s look at the code. Just like last time, I removed old comments, and commented newly added lines.

module CPU(input iClk, input iRst, output [7:0] oAddressBus, input [7:0] iMemoryValue);

	//Opcode constants, to improve code readability
	localparam 	//Like in 8080, assume there are 4 major types of instructions:
		MVI=2'b00, //Immediate memory load operations,
		MOV=2'b01, //Data transfer operations,
		ALU=2'b10, //Arithmetic/logic operations, and
		ALI=2'b11; //Immediate arithmetic/logic operations.

	//src/dest specifier constants
	localparam B = 3'b000, C = 3'b001, D = 3'b010, E = 3'b011, H = 3'b100, L = 3'b101, M = 3'b110, A = 3'b111;

	reg [7:0] _pc;

	always @(posedge iClk, posedge iRst)
	begin
		if(iRst)
			begin
				$display("resetting...");
				_pc = 8'h0;
			end
		else begin
			$display("PC:%d", _pc);
			$display("mem:%d", iMemoryValue);

			//see if we can recognize the instruction we know how to execute
			if(iMemoryValue == 8'h0)
				$display("executing NOP!");
			else case(iMemoryValue[7:6]) //first two bits of the instruction is the opcode
				MVI:
					//A true MVI instruction always has Memory as the source
					//(illegal "MVI" instructions are reused for other operations)
					if(iMemoryValue[2:0] == M) begin //last three bits is the source. must be M for MVI.
						case(iMemoryValue[5:3]) //the destination is specified in the middle three bits:
							//can't read the second operand, at the next memory address in the same clock cycle
							B: $display("MVI B, ?");
							C: $display("MVI C, ?");
							D: $display("MVI D, ?");
							E: $display("MVI E, ?");
							H: $display("MVI H, ?");
							L: $display("MVI L, ?");
							M: $display("MVI M, ?");
							A: $display("MVI A, ?");
						endcase
					end
			endcase
			_pc = _pc + 1'b1;
		end
	end

	assign oAddressBus = _pc;

endmodule

module ROM(input [7:0] iAddress, output [7:0] oReadPort);

	reg [7:0] _rom [0:255];

	initial
	begin
		//I went ahead and entered two "real" MVI instructions
		_rom[0] = 0; //NOP
		_rom[1] = 8'b00000110; //MVI B, 55
		_rom[2] = 55;
		_rom[3] = 8'b00001110; //MVI C, 255
		_rom[4] = 255;
		_rom[5] = 1; //unknown instr
		//etc.
	end

	assign oReadPort = _rom[iAddress];
endmodule

The rest of the code stayed the same as last time. This produces the following printout:

--------------Tick           1---------------
PC:  x
mem:  x
--------------Tick           2---------------
PC:  x
mem:  x
--------------Tick           3---------------
PC:  x
mem:  x
resetting...
--------------Tick           4---------------
PC:  0
mem:  0
executing NOP!
--------------Tick           5---------------
PC:  1
mem:  6
MVI B, ?
--------------Tick           6---------------
PC:  2
mem: 55
--------------Tick           7---------------
PC:  3
mem: 14
MVI C, ?
--------------Tick           8---------------
PC:  4
mem:255
--------------Tick           9---------------
PC:  5
mem:  1
--------------Tick          10---------------
PC:  6
mem:  x
--------------Tick          11---------------
PC:  7
mem:  x
--------------Tick          12---------------
PC:  8
mem:  x
--------------Tick          13---------------
PC:  9
mem:  x

This gets us as far as being able to recognize an MVI, but how should we go about reading the immediate value from memory? That’s a topic for the next post.


May 21 2012

Building a processor with Verilog, part 2

Category: Uncategorizedzvolkov @ 9:26 pm

Last time we constructed a walking skeleton of a CPU — it could reset itself, and incremented Program Counter on every clock tick. Today we’ll give it ability to read instructions and execute a very important instruction: NOP (“no operation”).

Again, let’s dive right into the code. I removed all the old comments, and added new comments before every line that was added or modified:

//We're adding two new signals our CPU will use to speak with the memory:
//one for the requested address, another one for the resulting value.
module CPU(input iClk, input iRst, output [7:0] oAddressBus, input [7:0] iMemoryValue);

	reg [7:0] _pc;

	always @(posedge iClk, posedge iRst)
	begin
		if(iRst)
			begin
                _pc = 0;
				$display("resetting...");
			end
		else begin
			$display("PC:%d", _pc);

			//memory value at the current PC
			$display("mem:%d", iMemoryValue);

			//The "if" statement works just like one would expect.
			//Note the notation of the numeric literal:
			//The "8" stands for number of bits and the "H" stands for hex.
			//Could have used 8'b00000000 for binary or 8'd0 for decimal.
			if(iMemoryValue == 8'h0) $display("executing NOP!");

			_pc = _pc + 1;
		end
	end

	assign oAddressBus = _pc;

endmodule

//a block of Read-Only-Memory
module ROM(input [7:0] iAddress, output [7:0] oReadPort);

	//this array of 256 x 8-bit registers is the actual memory
	reg [7:0] rom [0:255];

	//"initializing" ROM with some random values
	initial
	begin
		rom[0] = 0;
		rom[1] = 0;
		rom[2] = 55;
		rom[3] = 33;
		rom[4] = 255;
		rom[5] = 1;
		//etc.
	end

	//a ROM is an asyncronous device --
	//as soon as the read address changes, the output port reflects the new value
	assign oReadPort = rom[iAddress];

endmodule

module main;

	reg clk, reset;

	//Instantiating CPU and ROM and connecting them together.
	//As soon as CPU "latches" an address onto the Address Bus, the ROM will have the value available at its read port.
	wire [7:0] addressBus, memoryOutput;
	CPU processor(clk, reset, addressBus, memoryOutput);
	ROM memory(addressBus, memoryOutput);

	//no chages below this line

	integer tickN;
	localparam TICK=20;

	always
	begin
		clk = 1;
		#(TICK/2);
		clk = 0;
		#(TICK/2);

		tickN = tickN + 1;
		$display("--------------Tick %d---------------", tickN);
	end

	initial
	begin
		tickN = 1;
		$display("--------------Tick %d---------------", tickN);

		#(2.5 * TICK);

		reset = 1;
		#1;
		reset = 0;

		#(10 * TICK);

		$finish;
	end
endmodule

and the output is:

--------------Tick           1---------------
PC:  x
mem:  x
--------------Tick           2---------------
PC:  x
mem:  x
--------------Tick           3---------------
PC:  x
mem:  x
resetting...
--------------Tick           4---------------
PC:  0
mem:  0
--------------Tick           5---------------
PC:  1
mem:  0
--------------Tick           6---------------
PC:  2
mem: 55
--------------Tick           7---------------
PC:  3
mem: 33
--------------Tick           8---------------
PC:  4
mem:255
--------------Tick           9---------------
PC:  5
mem:  1
--------------Tick          10---------------
PC:  6
mem:  x
--------------Tick          11---------------
PC:  7
mem:  x
--------------Tick          12---------------
PC:  8
mem:  x
--------------Tick          13---------------
PC:  9
mem:  x


May 20 2012

Building a processor with Verilog, part 1

Category: Uncategorizedzvolkov @ 7:52 pm

Icarus is a GPL compiler/runtime for Verilog HDL. You can download a fresh Windows build here. Be sure to add C:\iverilog\bin to PATH.

Now I will try and build a partial prototype of a super simple processor very remotely resembling the venerable Intel 8080. Don’t know for how many posts will my steam last, this maybe the first and the last one, you’ve been warned.

Let’s dive right into code:

//"Module" is like a class. It may have inputs and outputs.
//The default data type in Verilog is "wire".
//Wire is like a pointer -- meaning it does not have its own value.
//Also, note the semicolon at the end of the module declaration. Funny, I agree.
module CPU(input iClk, input wire iRst);

	//"Reg" is another major type. It is like a variable that can remember its state.
	// [7:0] means this is actually an array of 8 bits.
	//This will be our Program Counter. Our programs will never get longer than 256 bytes.
	reg [7:0] _pc;

	//"Always" defines a block of code that will run every time either of its inputs turns ON ("at positive edge").
	always @(posedge iClk, posedge iRst)
	begin
		//if Reset and Clock happen to hit at the same time, we'll prefer Reset.
		if(iRst)
			begin
				//Reset signal is ON -- initialize CPU to initial state.
				_pc = 0;
				//"display" is used for debugging.
				$display("resetting...");
			end
		else begin
			//The clock has ticked -- incrementing the Program Counter.
			_pc = _pc + 1;
			$display("PC:%d", _pc);
		end
	end

endmodule

//This is the entry point for the simulation. It will serve as a test fixture for the CPU we're designing.
module main;

	//These variables will be used to send signals to the CPU.
	reg clk, reset;

	//Instantiating the CPU and connecting the inputs.
	CPU cpu(clk, reset);

	//Integer is a simulation-only datatype that cannot be synthesized.
	//This will be used to count clock ticks for display purposes.
	integer tickN;

	//"Localparam" is like a constant. This one defines duration of one clock tick (in nanoseconds).
	localparam TICK=20;

	//"Always" without arguments works like an endless loop.
	//This will be our system clock.
	always
	begin
		clk = 1; //Tick the clock.
		//"#" means sleep for the specified time.
		#(TICK/2); //Keep the clock ON for half a cycle.
		clk = 0; //Untick the clock.
		#(TICK/2); //Keep the clock OFF for half a cycle.

		//Print debug info.
		tickN = tickN + 1;
		$display("--------------Tick %d---------------", tickN);
	end

	//"Initial" defines a block of code that will run once, starting from time point 0.
	initial
	begin
		//Again, printing some debug info.
		tickN = 1;
		$display("--------------Tick %d---------------", tickN);

		#(2.5 * TICK); //Waiting 2.5 ticks -- want to reset right in the middle of the 3rd tick.

		//Turn ON the Reset signal for 1ns, then turn it OFF.
		reset = 1;
		#1;
		reset = 0;

		//Let the CPU run for 10 cycles
		#(10 * TICK);

		//Signal the simulator to finish.
		$finish;
	end
endmodule

This can be compiled and executed with the following commands:

>iverilog CPU.v
>vvp a.out

And it should produce a nice printout:

--------------Tick           1---------------
PC:  x
--------------Tick           2---------------
PC:  x
--------------Tick           3---------------
PC:  x
resetting...
--------------Tick           4---------------
PC:  1
--------------Tick           5---------------
PC:  2
--------------Tick           6---------------
PC:  3
--------------Tick           7---------------
PC:  4
--------------Tick           8---------------
PC:  5
--------------Tick           9---------------
PC:  6
--------------Tick          10---------------
PC:  7
--------------Tick          11---------------
PC:  8
--------------Tick          12---------------
PC:  9
--------------Tick          13---------------
PC: 10

First two ticks the PC value is “x” — meaning an undefined value. This is a simulator feature. In the real world the registers will usually start with a random value.

As planned, on the third tick the Reset signal is asserted, setting PC to 0.

During the subsequent ticks we’re letting the CPU do the only thing it can at the moment — increment its Program Counter register. In the next post I’ll try to add more meat to this walking skeleton.