The first step in my Road Test of the Digilent BASYS3 was to download the Vivado software from Xilinx. My first post ended with me waiting for it to download. I somehow think that web sites and me just do not go together as it turned out that I downloaded the update rather than the package itself so I had to go through the whole process again. Xilinx do make their software downloads a little bit confusing as there are only a few references to the free web pack version of Vivado that can be used with the Digilent BASYS 3 and even then the download link states you need to have a FLEX licence, which of course I do not have. Anyway, I persisted and eventually found and downloaded the correct software. It didn't go that smoothly as it took 4 hours and 38 minutes just to download (where did my internet speed disappear to?). It then took 63 hours and 23 minutes to install, but this was mainly my fault as my laptop goes to sleep if you stop pressing keys, so every now and then I would wander in, wake it up, let install a bit more, get bored and walk away again, when it then went back to sleep again. I'm sure I could have sorted this out if I had tried harder, but the sun was shining and the gardening needed doing. Still the software is downloaded now and seems to work. It does take a long time to get going but perhaps my laptop is a bit on the slow side, taking 1 minute and 46 seconds before the splash screen is shown. It is probably talking to some cloud somewhere to see if I am a legitimate user. This is a bit disconcerting as nothing seems to be happening during this period so the temptation is to think it isn't started and start clicking icons and pressing buttons - which doesn't actually help. Once the splash screen is present it was only another 5 seconds before the software seems to be fully up and running.
The activity that I am attempting to use with Vivado and the BASYS3 PCB is to design a simplified microprocessor. This is not to try and create a useful new microprocessor but rather to better understand some of the design decisions involved in designing a microprocessor. My approach at all times will be to go for simplicity above everything rather than efficiency or speed, so that everyone should be able to understand what is happening.
I think that there are three stands to my activity:
1) Getting to grips with Vivado (which has not been that successful so far)
2) Designing the structure of the microprocessor,
3) Implementing the microprocessor design in VHDL (and Vivado).
This Blog is going to be about some of the design decisions that have to be made and maybe a little bit about the VHDL.
Designing a Simplified Instruction Set Microprocessor (or Computer)
My first consideration is to think about the instruction set, at a binary or machine code level. Microprocessor instructions at this level are comprised of two elements
Opcode and Operands
The approach will be simplicity at all times so each instruction will be atomic and by this I mean it will not be divisible or sub-divided into different categories. For example, a memory move instruction can be direct access to memory, or indirect access to memory where a register is used to hold the memory address. In SISC this will be considered to be two different instructions. This simplifies the decoding of the opcode allowing a simple binary encoding to be used. This means that the number of instructions is a binary multiple. So for a 1 bit opcode that will be only two instructions, for example NOOP and JUMP (no-operation and absolute jump to the specified programme memory location). If two bits are used for the opcode then there are four instructions and so on. You might be interested to know that the theoretical minimum number of instructions a microprocessor needs is only one. It is some complicated conditional jump instruction which I have currently forgotten. We only need to have more than one instruction in order to make it simpler to write the programmes. Typically the useful minimum number of instructions is between 32 and 64 so the opcode can be 5 bits or 6 bits. As a FPGA is being used to implement the design we can actually make the number of bits for the opcode anything we want. For realistic microprocessor design, instructions usually tend to be some multiple of 8 bits as this was the traditional memory chip bus width, but this can limit the number of bits available for the opcode and the corresponding operands. As I am planning to implement the memory directly with the FPGA as well, then we can have memory locations of any bit width we want.
Instructions then divide into four categories:
1) No operands. These are typically hardware based such as NOOP. There are not many of this type.
2) One operand. An example of this would be INCR to increment a register. There are several useful one operand instructions.
3) Two operand instructions. This tends to be the majority of instructions and the most common would be a register to register move such as MOVR R1, R2.
4) Three operand instructions. These are the operator type of instructions such as add. An example might be ADDR R1, R2, R3. With this instruction the contents of register R1 is added to the contents of register R2 and the results placed into register R3, leaving the contents of R1 and R2 unchanged.
There are not many microprocessors that use three operand instructions as it makes the instructions much longer. For example, a microprocessor with 256 instructions and 256 registers needs an 8 bit opcode and 3x8 = 24 bits of operand, making a total of 32 bits.
You may have noticed that I have tended to use registers for the operands and not memory locations. This is a part of the SISC philosophy as memory moves slow down execution speeds so most instructions use registers for operands and there are only two instructions that access memory, SAVE and LOAD. The syntax of these instructions would be
SAVE Rx, memlocation ; which saves the contents of register Rx into the specified memory location directly.
LOAD memlocation, Rx ; which moves the contents of the specified memory location into register Rx.
x has the value 0 to the maximum number of registers, which again, would be a binary multiple.
Well that seems enough about the design of the instructions for the moment, what about some VHDL. Let us start with the simplest microprocessor possible which just has two instructions NOOP and JUMP. I have decided to use 2 bits for the opcode (even though I will only use 1 bit) and the rest of the bits are for the programme memory address. NOOP does not have any operands so the rest of the bits in those instructions are unused. For an 8 bit instruction, this would allow 6 bits for the memory address and creates a programme memory space of 64 locations. This simple design does not have any data memory and cannot really do anything much except waste time and jump about in the programme memory. A simple programme could be something like that below.
START: NOOP
NOOP
NOOP
JUMP START
This could be executed with some VHDL implementing a microprocessor as listed below:
-- RISC Design
-- Dubbie 24th May'18
--
library ieee;
use ieee.std_logic_1164.all;
use work.std_arith.all;
entity risc_detector is
port (clk : in std_logic;
data : in std_logic_vector(7 downto 0);
pc : buffer std_logic_vector(5 downto 0);
end risc_detector;
architecture struct of risc_detector is
begin
process
constant noop : std_logic_vector(1 downto 0) := "00";
constant jump : std_logic_vector(1 downto 0) := "01";
constant reset: std_logic_vector(5 downto 0) := "000000";
begin
wait until (clk = '1' and clk'event);
case data(7 downto 6) is
when noop =>
pc <= pc + 1;
when jump =>
pc <= data(5 downto 0);
when others =>
pc <= reset;
end case;
end process;
end struct;
I haven't compiled this code yet so if you're trying this at home do not be surprised if it doesn't work. Please note that pc is the Programme Counter which is used to point to the next memory location (so the programme memory address) and data represents the data value obtained from the programme memory. This VHDL programme does not yet implement the programme memory. Also, with 2 bits of opcode and only two instructions this leaves two possible opcodes unused and this is taken care of within the VHDL by the when others statement which will implement a reset. At present there is no external reset input.
I'll discuss the entity part at the beginning starting with library in my next Blog (if I remember). But for now it is getting dark and a glass of wine is calling.
Top Comments