Three PCBs arrived promptly from JLCPCB:
- The version 2 Test+SRAM board
- The version 2 Ethernet+Printer+Joystick board
- The version 1 Video+Sound board
The version 1 of the top two boards are described at the bottom of the post about the MIDI020 68020 computer, if you haven’t read it.
The first job was to build up the version 2 Test+SRAM board. Whereupon I immediately noticed that I’d forgotten to fix the problem with the AT24C64 (PDF) I2C EEPROM IC having swapped I2C leads. Very frustrating! It seems I’d updated the silk screen on the board to say “v2” but done very little else. Next time I find a problem I will change the CAD files immediately, at least the schematic, instead of hoping I remember to do it later.
Next, I decided to work on the Ethernet board, but for various reasons I will cover this in a separate post.
Onto the video and sound card then.
Here’s a picture of the bare board:
It’s strange being able to see the power traces on the internal layers. I’d assumed you wouldn’t be able to see, very clearly at least, the upper Vcc layer because of the ground plane immediately behind it.
Though I ordered a solder paste stencil for this board I ended up not using it. This board has a lot of 0.5mm pitch parts and the results I’ve achieved with stencils for boards with such fine pitch parts haven’t been too stellar, so I decided to use the iron instead.
Part of my reasoning for doing so is some fantastic results experimenting with a new flux:
I found out about this flux, SMFL, through a very educational YouTube channel called Mr Solderfix. This channel has heaps of advice on soldering. Using flux from an aerosol can is pretty weird. I generally squirt some into a bottle top and wait for it dry out a little before applying it with a brush. The result of this is I’ve not yet made a solder bridge I can’t clear, and as a consequence I’m very pleased that it seems I can finally attach 0.5mm pitch parts with relative confidence instead of trepidation.
The first job was to attach the SN74LVC8T245 (PDF). level shifters. I did these first just to gain some more confidence dealing with sub-50mil (SOIC) pitched parts. It took a while to notice, but eventually I realised that I used the wrong footprint on the board, or else I ordered the wrong part from mouser. It seems there are at least 3 standard footprints for 24 pin c. 0.6mm pitch parts which KiCAD knows about.
The parts I purchased from mouser.com have 0.65mm pitch leads, but the footprints have 0.625mm pitch leads. According to the datasheet TI make this level shifter in both package formats, so I could resolve this issue by ordering new parts in the package I designed the board for. But instead I discovered that the 0.65mm pitch part can be fitted, provided it is centered across the length, that is the pins in the center are perfectly aligned with the pads; the pins at the far ends ending up out of alignment but still close enough that they can be attached to their pads without bridging with the adjacent pad. The seven level shifters took a while. For the last few I experimented with using solder paste, and while I got a couple of bridges, they were cleared easily.
Attaching the 1.2V and 3.3V voltage regulators and related parts was next. After powering up the board I got 0V on the two outputs of these regulators. It was only then I realised that the two regulators outputting 3.3V and 1.2V from the MCP1826 (PDF) series I’d used had a different pin-out but the same package type – SOT-223-3 (PDF) – as the ones on the schematic, from the AZ1117 (PDF) series. I foolishly assumed the pin-out was standardised and that the parts I’d ordered were compatible. This problem was easily solved; as it happened I had some 3.3V AZ1117 parts available that I’d bought from eBay years ago in a drawer. I also had a 1.2V part in my parts drawer that I’d harvested from a broken FPGA development board. One unexpected problem with the 1.2V and 3.3V regulators: the LED attached to the 1.2V rail didn’t light, though the voltage was correct. The problem was obvious in retrospect: not enough voltage to light the LED, as I determined experimentally by powering one directly from my bench supply. In the end I removed both LEDs from the PCB. The proper arrangement would have been to drive these LEDs through a transistor driven from the rail instead of directly off the rail.
Attaching the Cyclone II (PDF) EP2C8 208 pin QFP FPGA was next. It was intimidating to start with. Interestingly this FPGA is not in a thin and flat package but rather a “chunky” one where the leads come out of the middle of the sides of the part, which is about 6mm thick. Alignment is by far the trickiest part. The general approach was to attach a corner by tacking a couple of pins in place using only the lead plating on the fresh pads. Some downward force is needed to force the pins to adhere to the pads when no “on iron” solder is used. The opposite corner was similarly tacked, though this required a small “nudge” to align these far pins correctly, the nudge being not more then about half a pin pitch (0.25mm) in order not to bend the pins too much.
Afterwards it was a pretty simple job to use barely a scrap of molten solder, and lots of flux applied to the board, to attach each pin, taking care to test the connection by gently poking each pin with the end of the tweezers to make sure it was secured to the board.
The final part of the core FPGA-related components was the Configuration Flash, related passives, the JTAG header, and its related passives.
Here I found another problem with my mouser order. I decided to use the EPQC4 Configuration Flash, which according to the EPCQ Configuration Flash manual (PDF) is compatible with the Cyclone II. Nonetheless, I can find no mention of the specific (4Mbit) part in any of Intel’s documentation, despite mouser selling them. And Quartus does not seem to support it either. In the end I used an EPCS4 (PDF) which was salvaged from the same dead FPGA development board as the voltage regulators.
It was a relief that the configuration flash could be programmed from Quartus.
Next up was the simple job of attaching a few remaining parts in order to be able to do a basic FPGA test: the power and FPGA output LEDs and resistors, and the oscillator can. I initially used a 50MHz one. While I was there I attached the ADV7123 (PDF) video DAC.
At this point the VHDL was very basic: just an LED flasher and some code to draw colour gradients on the screen, once the VGA connector was attached.
Amazingly the FPGA accepted the design first time, and dutifully flashed the LED. At this point the board was not plugged into MIDI020 (it lacked the connector to do so) and was instead jury rigged:
The video board was at this point balanced on a reel of solder.
Finishing the board was straight-forward enough, if tedious, and eventually I had a nice looking board:
Actually before the SRAM was attached I thought it would be fun to generate some pretty patterns and show off the ADV7123 24 bit RGB DAC:
The pretty colours were generated with this VHDL:
red <= STD_LOGIC_VECTOR (h_count (7 downto 0) + frame_count) when (visible = '1') else x"00"; green <= STD_LOGIC_VECTOR (v_count (7 downto 0) + frame_count) when (visible = '1') else x"00"; blue <= STD_LOGIC_VECTOR (h_count (7 downto 0) + v_count (7 downto 0) + frame_count) when (visible = '1') else x"00";
I was very pleased at this point: the colour gradients looked lovely, and further this design was only using about 2% of the logic resources of the EP2C8 FPGA.
As a quick test of the level shifters I first copied the processor clock signal onto the first pin on the FPGA user-header. It was a relief to see the expected waveform on the Saleae Logic16 logic analyser. This proved that the level shifters were forwarding the 5V signals through to the 3.3V FPGA IO pins.
The next step was to verify that the 68020 could read and write registers exposed by the FPGA.
In short, this required some fix-ups to the connections on one of the level shifters, but the result was good: I was first able to read from a test register, presented on every address on the expansion card, one that returned the address read from. I also added a register for controlling the connected LED and was able to control this LED through writes to a register on the EP2C8 FPGA. This all worked fine, after I fixed a problem with a connection to one of the level shifters.
The next thing to test was the SRAM. I did this by mapping the address pins at the FPGA through to the SRAM. The databus and other SRAM control pins were connected in a similar way:
va <= a (20 downto 1); n_vread <= n_read when (ucs = '1' or lcs = '1') else '1'; n_vwrite <= n_write when (ucs = '1' or lcs = '1') else '1'; vd <= d when (n_write = '0') else "ZZZZZZZZZZZZZZZZ"; n_vucs <= n_ucs; n_vlcs <= n_lcs; d <= vd when (n_read = '0') else "ZZZZZZZZZZZZZZZZ";
I used the monitor memory commands to verify that the SRAM was operating. After fixing an open pin on one of the SRAM databus pins I had some success. A memory copy from MIDI020’s Flash to the SRAM on the video card with a checksum done on both verified that the memory was operating properly.
The next step was to display an actual image from data stored in the card’s SRAM!
The colour format I decided upon was RGB565: 5 bits for red and blue and 6 bits for green. This would require a word read for every pixel, something I hadn’t yet attempted.
To make things easier, and borrowing some ideas from Steve Moody and his Y Ddraig (Welsh dragon) system, I opted to increase the clock speed on the card to a heady 100MHz. This would allow 4 ticks through a memory state machine, allowing reads and writes to the video memory on each pixel.
The result was the following:
I was pretty pleased to see this image. It took some effort to produce this display though.
The first job was to create the image data. In the end I used ffmpeg to convert a JPEG file to a raw RGB565 dump. This was transferred using the serial protocol I previously devised to send data from my Linux desktop to the MIDI020. In this case, the data was written directly to the video card, at 0x80000000.
There was one problem with displaying the image I hadn’t expected. While the outline of the shuttle was reconisable, the colours were jumbled up. Eventually I realised that the RGB565 file was byte-swapped because it was generated from my little-endian Linux box and ffmpeg had written it in its native byte ordering. Because it was the quickest way to remedy this problem, and would mean I wouldn’t have to transfer the image file a second time, I performed the byte swap in the VHDL code.
Having a working bitmap display showing static images was pretty cool, but the next step was to update the display using code running on the 68020. I settled on a classic:
This was drawn with C code, and to make things interesting, the 68882 FPU was used for the floating point maths. Rendering time for this image was about 12 minutes. Oddly, trying to use the GCC-provided floating point library instead of FPU instructions (I was curious to see how much slower the image would render without an FPU) resulted in linker errors. I did not spend a great deal of time tracking this down.
The final improvement made to the video controller was hardware-assisted drawing. This was something I was really looking forward to tackling, after being somewhat disappointed by MAXI000 having insufficient resources to tackle such a feature.
The general idea is to use multiple registers to configure the drawing operation. The following 16 bit registers are currently defined:
- 0x0: left most X coordinate
- 0x2: top most Y coordinate
- 0x4: right most X coordinate
- 0x6: bottom most Y coordinate
- 0x8: colour value
- 0xa: command
When written to, the command register will execute the command. The following commands are so far defined:
- 0: clear the screen to the colour value
- 1: draw a hollow box
- 2: draw a filled box
The draw operations use a state machine to write words into the video memory at the needed memory address. Converting an X and Y coordinate to a memory address is an interesting task. This would of course be trivial in a high level language. A function is used to multiply the Y coordinate by 640 and add on the X coordinate:
function coords_to_addr ( x : UNSIGNED (9 downto 0); y : UNSIGNED (9 downto 0) ) return STD_LOGIC_VECTOR is begin -- Multiply by 640 by shifting by 512 and 64 and adding them together. return STD_LOGIC_VECTOR ( ("000" & y & "0000000") + ("0" & y & "000000000") + x ); end function;
The VHDL shifting code is not especially readable, unfortunately.
Here is an example to show the general drawing mechanism; in this case the filled rectangle drawing code:
if (command_running = '1') then case command_code is ..... when x"0002" => engine_trigger <= '1'; engine_addr <= coords_to_addr(x_draw, y_draw); if (engine_ack = '1') then x_draw <= x_draw + 1; if (x_draw = reg_x1) then y_draw <= y_draw + 1; x_draw <= reg_x0; if (y_draw = reg_y1) then command_running <= '0'; end if; end if; end if; when others => end case; end if;
engine_trigger is used by the SRAM controller to know if it should write a word (at engine_addr) into memory. It sets engine_ack to 1 when it has done so, causing the drawing state machine to advance to the next pixel to draw, or to reach the end of the process when it has reached the bottom right corner. The drawing speed is the same as the pixel clock, namely 25 million pixels per second, which is pretty cool.
One further detail is a read-only status register is needed to return the command_running state. Currently the processor must poll on this register after issuing a draw instruction; in the future an interrupt will be used so the processor can get on with other tasks while it waits for the hardware to draw the shape it is has requested.
Initially I had problems with the display being “blocked out” while the writes were progressing. Ie. the writes succeeded but while the box was being drawn the display was not displaying a steady imagine. After playing about with various possible solutions, I sought the help of Steve Moody and borrowed some ideas from the SRAM read and write state machine used in his video card design. It was frustrating to not be able to figure this out from first principles, but on the other hand it was great to have someone’s help to draw upon. This part of the implementation will be covered in detail in a future post.
The final part of this puzzle is the code for the 68K which drives the drawing operations. As a demonstration, and stress test, filling the screen with random shapes sounded like a good idea. This was simple enough and just uses a crude pseudo random number generator and writes to the various hardware registers.
The current state of this demo is shown in this video:
Note that a delay is used in the 68K C code to pause the processor between the drawing operations. Without this delay the screen is just a blurry mess.
I’m very, very happy with the progress made on my video card. I’m especially pleased that I now have the resources in the card’s FPGA to play around with all kinds of cool video-related features:
- Drawing lines in hardware using the classic algorithm is an obvious next step
- Drawing circles in hardware is probably doable
- I could investigate how to implement sprites
- Porting my old console display, as a runtime selected video mode, would be useful. Then I can use the computer with an attached keyboard
Looking at the bigger picture, I really want to get on and start designing a 68030 based computer board. I should be able to make use of faster members of the Flex 10K (PDF) family of FPGAs to hopefully solve some of the issues I had (and still see) with the FPGA used on MIDI020.
I should probably have a play with the I2S DAC on the video and sound card as well. The only thing I’ve done with that section of the board is to solder on the parts.
On a practical level, I also need to rearrange my “work area” to accommodate a new microscope that I’ll soon be putting together.
So many fun and interesting things to be working on….