Question

I am designing a microcontroller in VHDL. I am at the point where I understand the role of each component (ALU/Memory...), and some ideas on how to realise them. I basically want to implement a Von Neumann architecture.

But here is what I don't get : how do the components communicate ? I don't know how to design my bus (buses?). I am therefore looking for a simple bus implementation and protocol.

My unresolved questions :

  • Is it simpler to have one bus for everything or to separate the different kind of data ?
  • How does each component knows when to "listen" and when to "write" ?

The emphasis is on the simplicity of the design (and thus of the implementation). I do not care about speed. I want to do everything from scratch (ie. no pre-made softcore).

I don't know if this is of importance at this stage, but it will not need to run "real" compiled code, is have any kind of compatibility with anything existing. Also, at which point do I begin to think about my 'assembly' instructions ? I thinks that I will load them directly in the memory.

Thank you for your help.

EDIT : I ended up drawing (a lot of) inspiration from the Picoblaze, because it is :

  • simple to understand
  • under a BSD Licence

Specifically, I started by adding a few instructions to it.

Was it helpful?

Solution

Since your main concern seems to be learning about microcontroller design, a good approach could be taking a look into some of the earlier microprocessor models. Take for instance the Z80:

Z80 Memory and I/O

To answer your first question (single vs. multiple buses), this chip uses a single bus for everything, and it has a very simple design. You could probably use something similar. To make the terminology clear, a single system bus may be composed of sub-buses (and they are also called buses). The figure shows a system bus composed of a bidirection data bus (8-bit wide) and an address bus (16-bit wide).

To answer your second question (how do components know when they are active), in the image above you see two distinct signals, memory request and I/O request. Only one will be active at a time, and when I/O request is active, that's when a peripheral could potentially be accessed.

If you don't have many peripherals, you don't need to use all 16 address lines (some Z80's have an 8-bit I/O space). Each peripheral would be accessed through some addresses in this space. For instance, in a very simple system:

  • a timer peripheral could use addresses from 00h to 03h
  • a uart could addresses from 08h to 0Fh

In this simple example, you need to provide two circuits: one would detect when the address is within the range 00-03h, and another would do the same for 08-0Fh. If you do a logic "and" between the output of each detector and the I/O request signal, then you would have two signals indicating when each of the peripherals is being accessed. Your peripheral hardware should primarily listen to this signal.

Finally, regarding your question about instructions, the dataflow inside your microprocessor would have several stages. This is usually called a processor's datapath. It is common to divide the stages into:

  1. FETCH: read an instruction from program memory
  2. DECODE: check specific bits within the instructions, and decide what type of instruction it is
  3. EXECUTE: take the actions required by the instruction (e.g., ALU operations)
  4. MEMORY: for some instructions, you need to do a data read or write
  5. WRITE BACK: update your CPU registers with new values affected by the instruction

A Typical Microprocessor Datapath

Source: https://www.cs.umd.edu/class/fall2001/cmsc411/projects/DLX/proj.html

Most of your job of dealing with individual instructions would be done in the DECODE and EXECUTE stages. As for the datapath control, you will need a state machine that controls the sequence of operations through the 5 stages. This functional block is usually called a Control Unit. Here you have a few choices:

  1. Your state machine could go throgh all stages sequentially, one at a time. An instruction would take several clock cycles to execute.
  2. Similar as the choice above, but combining two or more stages in a single cycle if you want to make things simpler and faster.
  3. Pipeline the execution of instructions. This can give a great speed boost, but maybe it's better left for later because things can get quite complex.

As for the implementation, I recommend keeping the functional blocks as separate entities, and make sure you write a testbench for each block. Your job will go faster if you write those testbenches.

As for the blocks, the Register File is pretty easy to code. The Instruction Decoder is also easy if you have a clear idea of your instruction layout and opcodes. And the ALU is also easy if you know the operations it needs to perform.

I would start by writing testbenches for the Instruction Decoder and the Register File. Then I would write a script that runs all the testbenches and checks their results automatically. Only then I would focus on the implementation of the functional blocks themselves.

OTHER TIPS

Basically on-chip busses will use parallel busses for address and data input and output. Usually there will be some kind of arbiter which decides which component is allowed to write to the bus. So a common approach is:

  • The component that wants to write will set a data line connected to the arbiter to high or low to signal that it wants to access the bus.
  • The arbiter decides who gets access to the bus
  • The arbiter sets the chip select of the component that should be allowed next to access the bus.

Usually your on chip bus will use a master/slave concept, so only masters have acting access to the bus. The slaves only wait for requests from the master.

I for one like the AMBA AHB/APB design but this might be a little over the top for your application. You can have a look at this book looking for ideas on how to implement your bus

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top