Showing posts with label new-idea. Show all posts
Showing posts with label new-idea. Show all posts

Sunday, February 23, 2020

Framework for Designing Programmable Modules

First, I just want to say that I really like Chisel. As a software developer who has dabbled in creating hardware only occasionally so far, I've found it much more enjoyable to learn and use than Verilog. Though my knowledge of both languages is still limited, Chisel's focus on hardware generation rather than description satisfies my desire to parameterize and automate everything in sight. The learning process for Chisel was much improved with the extensive documentation, including the Bootcamp and API docs. The abundance of open-source Chisel projects also provide valuable examples of how to use certain features. The basic building blocks provided by the language and utilities make it easy to get started creating hardware from scratch. The testing facilities that come standard also enable verification at an early stage, which appeals to me as a Test-Driven Development fanatic. Finally, Scala is a powerful language with many conveniences for writing clear and concise code.
   All of that being said, there are difficulties with hardware design that even Chisel does not fully address. The simple circuits that are taught in tutorials and bootcamps are fine for getting off the ground, but there is a big gap between those and what's required to create a processor or any block that's part of a larger system. Interfaces between memories and caches and other blocks require synchronization or diplomacy, which involves keeping track of valid and/or ready signals in addition to internal state. Improving performance with techniques such as pipelining or parallelization increase the number of elements that have to work together at all times. Then the design becomes significantly more complex, especially for software developers, such as myself, who write mostly procedural programs. The concurrency of hardware is a hard thing to wrap your head around. This is a problem because creating a processor is a logical first goal of a new designer. The abundance of open specifications, toolchains, and compatible software make processors a rewarding project. We don't want to just design hardware but also want to use them to run programs written by ourselves and others. So it feels like there's an owl-drawing problem, where the circles are the existing languages and documentation, and the owl is the processor.



   So we have to add in more of these steps and take hardware generators a step further. Queues and shift registers are useful, but Chisel libraries should continue beyond those to offer frameworks that conform to the user's ultimate requirements more easily. dsptools is one step in the right direction, with its ready-made traits for adding interfaces for a variety of bus protocols. So just like for busses, the transition between processor specification and implementation must be made easier. Thankfully, instruction set specifications all have somewhat similar contents and format: the user visible state, and the instruction encodings and their effects on the state and IO. It should be possible to harness the power of Chisel to generate a processor given a specification pattern that resembles these documents.
   That's exactly what I'm attempting to do with the ProcessingModule framework. In it, a set of instructions and common, user-visible logic elements are defined. Instructions will declare their dependencies on state and/or external resources. An instruction also specifies what action will be done with those resources once they are available. Both sequential and combinational elements can be shared among instructions. Sequential elements can be general purpose register files or control/status registers. Large combinational elements like ALUs can also be shared to improve resource usage of the processor. The implementation will require parameters for data, instruction, and address widths, but there will eventually be more options to insert structural features like pipelining, speculation, and/or instruction re-ordering that improve processor performance but at the cost of additional hardware resources. The framework inherits from Module and features standard Decoupled and Valid interfaces, so it mixes in well with other Chisel code.

abstract class ProcessingModule(dWidth : Int, dAddrWidth : Int, iWidth : Int, queueDepth : Int) extends Module {

  val io = IO(new Bundle {
    val instr = new Bundle {
      val in = Flipped(util.Decoupled(UInt(iWidth.W)))
      val pc = util.Valid(UInt(64.W))
    }
    val data = new Bundle {
      val in = Flipped(util.Decoupled(UInt(dWidth.W)))
      val out = new Bundle {
        val addr = util.Valid(UInt(dAddrWidth.W))
        val value = util.Decoupled(UInt(dWidth.W))
      }
    }
  });

  def initInstrs : Instructions
  …
}

This is the beginning of the ProcessingModule class. First, there are constructor parameters for widths of data loaded and stored from memory, data memory addresses, and instructions. There is also a parameter to specify the depth of a queue that receives incoming instructions. Following that is a basic IO assembly that's divided into instruction and data bundles. The instruction part has a decoupled port for the incoming instructions and an output port for the program counter. The program counter is currently fixed to 64 bits wide, but that will be made parameterizeable in the future. The data bundle has a Decoupled input port and output address and value ports. Following the IO is the one abstract method called initInstrs, which will be called only once later in the constructor. Modules that inherit from ProcessingModule must implement this method to return the logic and instruction set they want to use. The return type is another abstract class called Instructions.

abstract class Instructions {

  def logic : Seq[InstructionLogic]
}

abstract class InstructionLogic (val name : String, val dataInDepend : Boolean, val dataOutDepend : Boolean) {

  def decode( instr : UInt) : Bool

  def load(instr : UInt) : UInt = 0.U

  def execute( instr : UInt) : Unit

  def store(instr : UInt) : UInt = 0.U
}

Subclasses of Instructions should declare logic shared between instructions in their constructor. The logic method also needs to be defined to return a sequence of InstructionLogic. Each instance of the InstructionLogic class represents an instruction. There are parameters for the instruction name and whether or not it depends on memory. The name field currently only exists to distinguish instructions in the Chisel code, but it will eventually prove useful for automatically generating debugging utilities for simulation. The dataInDepend and dataOutDepend parameters should be set to true if the instruction will read from or write to memory respectively. The virtual methods within the InstructionLogic class roughly correspond to the stages in a traditional pipelined processor architecture. decode and execute are required to be implemented by all instructions. decode takes a value given to the processor via the instruction bus and outputs a high value if its a match for this particular instruction type. Then, the rest of the stages will be run for that InstructionLogic instance. execute takes the same instruction value and performs some operation on the processor state. It does not return any value. If a memory dependency exists for the instruction, then the load and/or store methods will also be called, so they should also be implemented by the designer. Both of these methods should return the address in memory that should be accessed. For instructions that read from memory, the value in memory at that address will be retrieved and stored in the dataIn register. For instructions that store to memory, the value in the dataOut register will be stored at the given address.
   Following is a simple example of a processor that simply adds numbers to a pair of registers. Some common routines for extracting subfields from an instruction are defined at the top. In the initInstrs method, an Instructions instance is created with the 2-element register array that's accessible to all instructions. Then the logic method begins by defining a nop instruction, which contains only logic that indicates if the current instruction is a nop or not. There is no logic defined in the execute method, because the instruction does not do anything.

class AdderModule(dWidth : Int) extends ProcessingModule(dWidth, AdderInstruction.addrWidth, AdderInstruction.width, 3) {

  def getInstrCode(instr : UInt) : UInt = instr(2,0)
  def getInstrReg(instr : UInt) : UInt = instr(3)
  def getInstrAddr(instr : UInt) : UInt = instr(7,4)

  def initInstrs = new Instructions {
    val regs = RegInit(VecInit(Seq.fill(2){ 0.U(dWidth.W) }))
    def logic = {
      new InstructionLogic("nop", dataInDepend=false, dataOutDepend=false) {
        def decode ( instr : UInt ) : Bool = getInstrCode(instr) === AdderInstruction.codeNOP
        def execute ( instr : UInt ) : Unit = Unit
      } ::
      new InstructionLogic("incrData", dataInDepend=true, dataOutDepend=false) {
        …
      }
      …
    }
  }
}

The next instruction, incr1, increments the specified register by 1. The register to increment is determined from a subfield in the instruction, which is extracted in the execute stage with the getInstrReg method defined above.

new InstructionLogic("incr1", dataInDepend=false, dataOutDepend=false) {

  def decode ( instr : UInt ) : Bool = {
    getInstrCode(instr) === AdderInstruction.codeIncr1
  }

  def execute ( instr : UInt ) : Unit = {
    regs(getInstrReg(instr)) := regs(getInstrReg(instr)) + 1.U
  }
}

The incrData instruction increments a register by a number stored in memory. The dataInDepend parameter for this instruction is set to true since it needs to read from memory. The logic method is implemented here to provide the address to read from, which also comes from a subfield of the instruction. The value from memory is then automatically stored in the built-in dataIn register, which is used in the execute method.

new InstructionLogic("incrData", dataInDepend=true, dataOutDepend=false) {

  def decode ( instr : UInt ) : Bool = {
    getInstrCode(instr) === AdderInstruction.codeIncrData
  }

  override def load ( instr : UInt ) : UInt = getInstrAddr(instr)

  def execute ( instr : UInt ) : Unit = {
    regs(getInstrReg(instr)) := regs(getInstrReg(instr)) + dataIn
  }
}

The store instruction stores a register value to memory, and thus has its dataOutDepend parameter set to true. The dataOut register is written in the execute method. The value in the dataOut register will be stored at the address returned by the store method.

new InstructionLogic("store", dataInDepend=false, dataOutDepend=true) {

  def decode ( instr : UInt ) : Bool = {
    getInstrCode(instr) === AdderInstruction.codeStore
  }

  def execute ( instr : UInt ) : Unit = {
    dataOut := regs(getInstrReg(instr))
  }

  override def store ( instr : UInt ) : UInt = getInstrAddr(instr)
}

bgt (Branch if Greater Than) skips the next instruction if the specified register is greater than zero. This is implemented by adding 2 to the built-in pcReg register in the execute method.

new InstructionLogic("bgt", dataInDepend=false, dataOutDepend=false) {

  def decode ( instr : UInt ) : Bool = {
    getInstrCode(instr) === AdderInstruction.codeBGT
  }

  def execute ( instr : UInt ) : Unit = {
    when ( regs(getInstrReg(instr)) > 0.U ) { pcReg.bits := pcReg.bits + 2.U }
  }
}

Testing ProcessingModule began with the OrderedDecoupledHWIOTester from the iotesters package. The class makes it easy to define a sequence of input and output events without having to explicitly define the exact number of cycles to advance or which ports to peek and poke at. The logging abilities also enable some debugging without having to inspect waveforms. Even with these advantagges, I found it lacking in some aspects and even encountered a bug that hindered my progress for several days. Therefore, I created my own version of the class called DecoupledTester. This new class orders input and output events together instead of executing all input events immediately. By default, it fails the test when the maximum tick count is exceeded, which usually happens if the design under test incorrectly blocks on an input. DecouledTester also automatically initializes all design inputs, thus decreasing test sizes and elaborating errors. Finally, the log messages emitted by tests are slightly more verbose and clearly formatted. The following is an example of a test written for the AdderModule described above:

it should "increment by 1" in {
  assertTesterPasses {
    new DecoupledTester("incr1"){

      val dut = Module(new AdderModule(dWidth))

      val events = new OutputEvent((dut.io.instr.pc, 0)) ::
      new InputEvent((dut.io.instr.in, AdderInstruction.createInt(codeIncr1, regVal=0.U))) ::
      new OutputEvent((dut.io.instr.pc, 1)) ::
      new InputEvent((dut.io.instr.in, AdderInstruction.createInt(codeStore, regVal=0.U))) ::
      new OutputEvent((dut.io.data.out.value, 1)) ::
      Nil
    }
  }
}

This is an example of output from the test when the design is implemented correctly:

Waiting for event 0: instr.pc = 0
Waiting for event 1: instr.in = 1
Waiting for event 2: instr.pc = 1
Waiting for event 2: instr.pc = 1
Waiting for event 2: instr.pc = 1
Waiting for event 3: instr.in = 3
Waiting for event 4: data.out.value = 1
Waiting for event 4: data.out.value = 1
Waiting for event 4: data.out.value = 1
All events completed!

The framework does well enough for the simple examples explained here, but my next goal is to prove its utility with a "real" instruction set. The first target is RISC-V, followed by other open architectures like POWER and OpenRISC. In parallel to these projects, I'll work on improving the designer interface of the framework by reducing boilerplate code and enhancing debugging capabilities. Once some basic processor implementations have been written and tested, there will be enhancements to improve performance by pipelining, branch prediction, and instruction re-ordering. In the meantime, here are the slides for this presentation and a PDF containing the AdderModule example that fits on a 6x4 inch flash card.

Sunday, March 20, 2016

Unit Testing FRC C++ with CppUTest in Eclipse

Introduction

Participating as a programming mentor in this year's FIRST Robotics Competition has been a lot of fun, but I found myself wishing, once again, for better ways to test our robot's code. The existing methods are the traditional direct tests on the actual robot hardware, and simulation using Gazebo. Directly testing the robot is often difficult to even get started with, since the robot is not constructed for much of the 6-week build season. Once it is constructed, though, deploying builds and manually working the controls to verify functionality is slow and error-prone. Working with the simulator also has its own shortcomings. A realistic SolidWorks model is required to drive in a virtual arena, and the simulator itself consumes a lot of processor power and memory.
  Fortunately, there is another way, and that way is unit-testing. Unit tests are small, simple tests written in code (often in the same language as your production code) that set up inputs, exercise some part of the production code, and verify the outputs that are produced. These tests can run directly on the programming computer, no robot hardware required, and execute very quickly. For Java and C++, there are several great frameworks available for free to make the process of writing unit tests easier. In this post, I will describe how the CppUTest framework can be used to make unit tests for a C++ robot program based on WPILib. CppUMock, which is a part of CppUTest, is also used to create a mock version of WPILib that can be precisely controlled in the unit tests. Example code based on the 'Arcade Drive' sample project is available at GitHub.
  Please note that this strategy does not require any modifications to your existing robot code. The only change to your project will be an additional build configuration that allows it to be tested this way. Switching between the default configuration and unit-testing configuration (described below) is easy and fast.

Requirements

The following utilities are required to develop FRC programs with CppUTest.
  1. Eclipse with C/C++ development plugins (eclipse.org)
  2. CppUTest (github.com/cpputest/cpputest) (release 3.7.2 was used for this example)
  3. MockWPILib from the example code (unless you want to write your own mocks)
  4. Unix development tools (if developing on Windows, use Cygwin (cygwin.com)
    1. autoconf
    2. autogen
    3. automake
    4. make
    5. gcc-core
    6. gcc-g++
    7. libtool

Building CppUTest

The first step is to acquire the CppUTest sources either from a release package or the git repository. If you're using Cygwin, the sources should be placed somewhere in your Cygwin installation path (ex: C:\cygwin64\home\user). Then, using a bash shell (or Cygwin Terminal), navigate to the CppUTest root directory and run the following commands, in order:
  1. ./autogen.sh
  2. ./configure
  3. make
If you have all of the packages listed in the requirements section, this should generate the files libCppUTest.a and libCppUTestExt.a in the lib/ subdirectory. If the process is interrupted with errors, then one or more packages are probably missing and should be installed before continuing.

Building WPILib Mocks

The next step is to use CppUMock (included in CppUTest) to begin writing the mock classes you need for your tests. These will be located in a separate project in the same workspace as your main robot project. To create your own mocks project, select 'File > New > C++ Project' and set the project type to 'Static Library > EmptyProject'. For Windows users, set the toolchain to 'Cygwin GCC'.


Click 'Next' to get to the 'Select Configurations' window. Then, click on 'Advanced Settings' to go to the Tool Chain Editor (the Tool Chain Editor for an existing project can be accessed in it's Properties window).


Make sure you're in the 'C/C++ Build > Settings' section (in the menu on the left). Under 'Compiler > Includes', add an include path that points to the 'include' subdirectory of your CppUTest installation (ex: C:\cygwin64\home\user\cpputest\cpputest-3.7.2\include). Click 'Apply' at the bottom of the window.

For Windows users, go to the 'Environment' section (select 'C/C++ Build > Environment' in the menu on the left). There should be an environment variable with the name 'CYGWIN_HOME' with an empty value. Set the variable to the path of your Cygwin installation (ex: C:\cygwin64). Leave the other environment variables unchanged.


Click 'OK' at the bottom to leave the properties window and click 'Finish' to create your mock library project. You may now begin writing the code for your mocks. To learn how to create mock classes, see the CppUMock documentation at the CppUTest site (cpputest.github.io/mocking_manual.html). For examples, see the MockWPILib project. In addition to mock classes, the START_ROBOT_CLASS macro should be defined as empty (#define START_ROBOT_CLASS ()).
  If users want to use and extend the MockWPILib project instead of creating their own mocks project, it can be acquired from GitHub and imported into the Eclipse workspace. To import a project, select 'File > Import > General > Existing Projects into Workspace'.


The build properties explained above should be checked after importing the project to make sure that the correct include path for CppUTest is set. If Cygwin is being used, the Cygwin installation environment variable should be set appropriately as well.

Robot Project Configuration

The next step is to add a build configuration to the main robot project that uses the mock library instead of the real WPILib library. First, go to the project's properties (right-clock on the project and select 'Properties'). In the 'C/C++ Build' section, click on 'Manage Configurations' on the right. In the 'Manage Configurations' window, click 'New' and give a name and description for the new configuration. Copy the settings from the 'Debug' configuration.


Click 'OK' to create the configuration, and then select it and click 'Set Active' in the 'Manage Configurations' window. Click 'OK' to go back to the properties window.


In the properties window, go to 'C/C++ Build > Tool Chain Editor' and set 'Current toolchain' to 'Linux GCC' for Linux users and 'Cygwin GCC' for Windows users. For Windows users, the Cygwin installation directory needs to be set as explained above in 'Building WPILib Mocks'.


After setting the toolchain, go to the section 'C/C++ Build > Settings'. Under 'C++ Compiler > Includes' add an include path for the mock library.


In the 'Project References' section, check the mock library project to indicate that the main robot project now references it.


Click 'OK' to exit the properties window. The main robot project can now be built with the new testing configuration. Be sure that all necessary mock classes and functions are defined in the mock library project to avoid errors. To switch the build configuration to link in the actual WPILib and deploy to the roboRIO, right click on the projects and select 'Build Configurations > Set Active > Debug'.

Writing Tests

Now that the mock library is written and the robot project is configured for unit testing. the unit tests themselves can be written. These tests will reside in a separate project. Create a new project by clicking 'File > New > C++ Project'. Set the project type to 'Executable > Empty Project'. Windows users should set the toolchain to 'Cygwin GCC'.


Click 'Next' and select 'Advanced Settings' to go the properties window. For Windows users, the Cygwin installation path should be set as explained in the section 'Building WPILib Mocks'.
  In the section 'C/C++ Build > Settings', include paths for CppUTest, the mock library, and the main robot project should be added under 'C++ Compiler > Includes'.


In the same section under 'C++ Linker > Libraries', library names and search paths should be added for CppUTest, the mock library, and the main robot project. The library for the main robot project will be called 'FRCUserProgram' and is located in the project's subdirectory for the testing configuration. For CppUTest, both the CppUTest and CppUTestExt libraries should be linked.


After the compiler and linker are configured, go to the 'Project References' section and check both the main robot project and mock library project. Then, click 'OK' to exit the properties window, and click 'Finish' to create the project.


The unit tests can now be written in the new tester project. To learn how to write unit tests with CppUTest, see the documentation on the CppUTest site (cpputest.github.io/manual.html). For examples, see the 'Arcade Drive Test' project on GitHub. Remember to define the 'main' function with a call to 'CommandLineTestRunner::RunAllTests'.
  Once the unit tests are written and built, the tester project can be executed to run the tests. Right-click on the tester project and select 'Run As > Local C/C++ Application'. The test results should show up in the console in Eclipse. If a test fails, the exact location of the failing assertion will be given.


  If an error concerning a missing library appears when running the tests on Windows, then the Cygwin libraries may be missing from the dynamic linker search path. To fix this, go to the 'Run/Debug Settings' section in the project properties. Select the executable in the launch configurations list and click 'Edit' on the right. In the 'Edit Configuration' window, select the 'Environment' tab and click 'New' to add an environment variable. Set the variable name to 'PATH' and the value to the 'bin' subdirectory of the Cygwin installation (ex: C:\cygwin64\bin). Click 'OK' and make sure that 'Append environment to native environment' is selected at the bottom.


Click 'OK' to exit the 'Edit Configuration' window. Click 'OK' to exit the properties window. The tester project should now be able to run.

Sunday, April 13, 2014

Unit Testing Assembly Code

So I've been continuing to play around with AVR assembly and struggling to get things working. With my first full-time job underway, I've had limited time to devote to this hobby, and I don't have all of the tools that I should have to properly test my prototype systems. However, I still have had the nagging suspicion that I was approaching the development process the wrong way. I finally realized what was missing while listening to an episode of Elicia White's Making Embedded Systems podcast that discusses test-driven development (TDD) for embedded systems. At first, I simply refused to believe that TDD and bare-metal programs were compatible, but the more I thought about it, the more I understood that the process of modularizing your code and running each module one at a time in a specially-configured environment was almost exactly the same.
   That podcast, though, only discussed the semantics of testing C programs, which are easily compiled for almost any platform out there (as long as there aren't any platform-specific elements in your program, which should be discouraged). Assembly programs, though, are the least portable of all. A stable hardware environment with debugging support or an architectural simulator is required. Fortunately, Simulavr is available for AVR programs (though it only supports a limited subset of all devices). In addition, its developers have implemented a nice Tcl interface for it, which makes the process of writing unit tests much easier.
   I have started a simple project to implement and test the above concepts. The files can be found on GitHub. Downloading the files (as well as installing Tcl, Simulavr, and all of their prerequisites) and running 'make test' launches the testing process. As of this writing, it is still not completely functional due in part to the lack of documentation for the simulator interface, but some more experimentation should produce a system that is able to test individual assembly routines by providing arguments and testing outputs via core registers. If it becomes robust enough, I can hopefully merge it into my other projects to help with their development.

Sunday, April 14, 2013

New Idea: Smartphone as Robotics Controller

Okay, so it's not really a new idea. The most obvious antecedents of this are Parrot's apps for controlling their consumer AR drones. But I've just recently started to think about this due to the fact that I'll be able to upgrade my smartphone with Verizon next week. I'll probably use this opportunity to pick up a Samsung Galaxy S3, as it's one of the most powerful and popular Android smartphones currently in the market. Another interesting problem that I've been pondering for the past week is what I'm going to do with my current device, the Motorola Droid 3, pictured above. All in all, it's an excellent smartphone, solidly built and reasonably powerful (1GHz dual-core CPU, 512MB of RAM, and 16GB of on-board storage). The fact that it will never receive an OS upgrade beyond Gingerbread from Verizon, though, is the motivation for replacing it as my main mobile device. I'm aware that it is now supported by CyanogenMod (though apparently without camera support), but I'm unwilling to replace the OS on my only smartphone without support from the carrier. The battery life has also dropped to about 6 hours with moderate usage. So I'm planning on picking up a brand new phone and using the Droid 3 as a test bench as I learn Android development for the first time and work towards a robotics controller application. What the application is going to control, though, I have not yet decided. I've already read the introduction articles and gone through the first-app tutorials on the official Android development site, and I'm also currently working through CommonsWare's The Busy Coder's Guide to Android Development v1.0.

Sunday, March 17, 2013

RPRAC: RTL Power Reduction Approach Comparator

Normally, I try to not publish schoolwork on this site, mostly because the ideas for assigned projects come from the professor and not me. However, the final project for my Advanced VLSI Techniques class allows for students to present an original idea, so I feel that sharing this work is appropriate.
   My idea is not exactly groundbreaking. Rather, I aim to provide a convenient tool for students studying different methods for decreasing power consumption in integrated devices. I call it RPRAC, which stands for RTL (Register Transfer Level) Power Reduction Approach Comparator. It's a program that applies one or several power reduction techniques to a provided logic design separately and measures and compares the effects that each technique has on power consumption, chip area, and signal timing.
   Essentially, my plan for this program is to simply write a script that employs other VLSI design tools to do the actual heavy-lifting. As a student of a graduate VLSI course, I have access to machines with actual professional software suites, so I'm using ModelSim by Mentor Graphics for the Verilog testing and simulation, Synopsys Formality for functional verification, and Synopsys Design Compiler for netlist synthesis and measurement. A simple chart describing the general flow of the program is shown below:

   I plan on implementing this in Python, which I've only used for mall experiments so far. Check out this GitHub page to check on the source. I might eventually post some wiki pages on there as well.

Sunday, January 20, 2013

ATX Bench Supply Overview: Inaugural Post

Welcome to BotBakery, a blog that focuses on my personal work with software, electronics, robotics, and any other interests that I might pick up in the future. Posts will serve as overviews of completed projects, short updates on ongoing projects, or simply musings on ideas that might become projects. The occasional rant may also sneak in from time to time.
   To tell the truth, I had established this site and registered the domain named for quite a while before finally forcing myself to publish this inaugural post. The main reason for this was that I was reluctant to launch this blog without having a completed project to show off first. I had previously designated my ATX bench power supply conversion to be the showpiece, but as I began another phase of that project last night, I realized that I rarely ever consider any of my work to be "complete" and that I just might as well throw this post up with the photos that I've already acquired.


  I admit, an ATX bench power supply conversion is not a very original idea, but I figured that it would serve as a good starting point for me as I begin to take a deeper personal interest in electronics. I had also recently acquired a used 250 Watt ATX supply from Microcenter during the previous Black Friday for ten dollars and was also without any bench supply to speak of (except for a small collection of wall-warts). In addition, I gained inspiration from Ian Lee's post on Software & Sawdust. I had neither the skills nor materials to make a handsome case such as his, but my dad did, so I spent much of my winter vacation working with him to bring clean DC power to my workshop.


  As opposed to Lee's horizontal design, I opted for a vertical shape due to limited shelf space. I also added removable front panels for the binding posts and display and status LEDs for both ease of assembly and expansion of functionality later on. An intake fan at the top was also integrated to bring in cool air as well as to act as a dummy load for the supply, which is essential for it's operation. The only thing not included in the schematics is the handle at the top, as that particular (and very valuable) detail was suggested by my dad halfway through construction. The Sketchup files can be obtained here.
















  The building phase was much more time-consuming and intensive than I had imagined, and I became very grateful for my dad's experience in carpentry. It turns out wood is a messy material to work with. I would always emerge from each building session covered in a fine layer of sawdust. Numerous steps had to be taken for each cut of the wood, and I have tried to cover as many of these as I could in the attached photos. After sanding and staining each of the resulting pieces, the box was finally assembled with glue followed by screws. The removable panels were cut from a Lexan sheet and then spray-painted on the back side. Metallic grills were stapled to the insides of the input and output vents. The whole process took about a week of on-and-off work, which was much longer than I had expected. The quality of the product, though, is very high.






  The next step is to finish the wiring of each of the different supply rails to the binding posts. I have some 6 amp fuses and clips for additional short-circuit protection, and I still need to order some proper terminals and appropriately gauged wire to finish this phase. Even further down the road, I would like to learn to program an AVR to drive the LCD display at the top to show exact voltages and currents from each rail, and maybe a digital adjustable output system as well. Stay tuned.