Having already designed the microcontroller in a simulation environment, I wanted to take it to the implementation phase. I’d previously shown an implementation of a timer in simulation so keeping the same test program, I wanted to make sure it worked in implementation!
Firstly, I decided to replace the simulation ROM and RAM for IP generated ones with some minor interfacing logic to work with my simple bus. This wasn’t a problem and I was able to write the VHDL for this in a couple of minutes. The ROM was initialized using a .mif file – a format used by Altera standing for Memory Initialization File. I made a minor modification to my assembler to directly output the mif file into the compilation folder, allowing me to run the assembler then go straight to compilation in Quartus, streamlining the process of testing.
GPIO
I also decided to code a simple GPIO unit. As with timers, the GPIO units are another really important section of a microcontroller. The GPIO is used to interface the outer world with the microcontroller providing both inputs and outputs.
Features:
- Bidirectional, both inputs and outputs
- Atomic set, reset and toggle registers (single write to perform port actions)
- 16bits wide
- 6 registers – Direction, Input, Output, Atomic set, Atomic reset and Atomic toggle
In my implementation (after asking on Stack Overflow), I decided to move away from an address mux/peripheral and change to a master address multiplexer which controls which ack signals are connected to the master, dependent on the address from the master. The mux also ensures the address outputted is zero referenced. All addresses with the topmost bit set (32768->65535) are reserved for peripherals with all addresses with the topmost bit cleared (0->32767) are reserved for RAM. The mux takes two generics, the number of slaves and 2^(the amount of bits/slave). Currently, 4 bits are reserved per slave meaning up to 16 registers.
To make my test program a bit more interesting, I added a GPIO toggle in the PWM increment loop. In the image below, the fourth channel represents this GPIO output.
Image may be NSFW.
Clik here to view.3x seperate PWM channels with different prescalers and a GPIO toggled output
Instruction additions
To add to functionality of the microcontroller, I’ve decided to add a few instruction changes. Along side all the previously mentioned instructions, I’ve added:
- MRR – Memory to register (memory address located in a register)
- RMR – Register to memory (memory address located in a register). Both of the above two instructions allow me to mass clear sections of data (set RA to 0, set RB to the start address, set RC to the end address, copy RA to RB, increment RA, subtract RA from RC and store in RD, jump out of clear loop if RD is equal to 0 (RA == RC if RA-RC = 0)
- I’ve also added SHL and SHR (Shift logical left and Shift logical right) to the ALG instruction, allowing for easier logic manipulation.
- I’ve renamed the instruction JNZ to JRZ as JNZ didn’t actually make sense!
To test my conditional programming, I decided to control the toggling of a GPIO pin, dependent on a push button input. Connecting the output of GPIOA0 to my logic analyzer and a tactile push button switch to GPIOA1, I was able to control when the output was toggling and when it wasn’t.
Image may be NSFW.
Clik here to view.When the button is high (not being pressed as the button was connected to a pullup resistor, pulling the input voltage low when the button was pressed). The blocks of white that can be seen above are the GPIO output toggling.
Program listing:
str ra 0
rtm ra GPIOA_DIR
str ra 1
rtm ra GPIOA_DIR
jmp ‘ReadLoop
‘ReadLoop
mtr ra GPIOA_INP
str rb 2
alg and ra ra rb
jrz ra ‘OpReset
jmp ‘ReadLoop
‘OpReset
str ra 1
rtm ra GPIOA_TOG
jmp ‘ReadLoop
Further assembler improvements
Having got to the point of writing more complex programs, I’ve added the ability to load constants from a special def.txt file. This allows me to define all the register address locations in a single file and load them in upon compilation of my program file.
Image may be NSFW.
Clik here to view.Having a dedicated def and program file
My latest test program was reading a button and jumping to a subroutine if the button is pressed. Inside the subroutine, the current PWM value is incrementor is paused. Outside of the subroutine in the main loop, the PWM value is continuously incremented.
Program listing:
str ra 0
rtm ra GPIOA_DIR
str ra 1
rtm ra GPIOA_DIR
str ra 2 #Reset timer1
rtm ra TIM1_CTRL
str ra 0 #Set prescaler
rtm ra TIM1_PSC
str ra 4095 #Set Overflow
rtm ra TIM1_OVF
str ra 1 #Set PWM
rtm ra TIM1_PWM
str ra 13 #Start timer1 and PWM polarity 0
rtm ra TIM1_CTRL
str rd 0
jmp ‘ReadLoop
‘ReadLoop
mtr ra GPIOA_INP #Check to see if push button is pressed
str rb 2
alg and ra ra rb
alg xor ra ra rb #As button is connected as pullup, invert
jrz ra ‘PWMINC #If button pressed, increment PWM
jmp ‘ReadLoop
‘PWMINC
str rb 4095 #Wrap RD
alg inc rd
alg and rd rd rb
rtm rd TIM1_PWM
jmp ‘ReadLoop
Image may be NSFW.
Clik here to view.Pressing he button (channel 4 being low) stops the PWM incrementation, releasing the button allows the PWM incrementation to resume.
Synthesis and implementation
My FPGA is an Altera Cyclone IV EP4CE6E22C8. With one GPIO port, three hardware timers, 512bytes of SRAM and 256 program words of ROM, compiling under balanced conditions consumes:
- 1340/6272 Logic elements (22% of the total chip), 933 of those being registers
- 13824/276480 (5% usage) bits used
- 1/2 (50% usage) PLLs
This actually leaves room for quite a fair bit more improvement! I was getting worst case clock times under area optimizations of ~110Mhz and was able to run my program at 120MHz which seems pretty fast, even though my IPS is FOsc/5. This translates to ~24MIPS.
I’ve got more improvements to be made, I’m hoping to make a DMA controller soon though my next module will likely be a bus connected 4x 7 segment display module.
Image may be NSFW.
Clik here to view.
Clik here to view.
