In Verilog, so many things that should be obviously supported are not. For instance, one cannot pass a multidimensional array of wires into a module, which is basically the "class" of Verilog. This is a problem if you want to pass an array of n-bit values into the module. If you do, then you have to transform it into one single array and then back inside the module—what a pain! Many of these issues are fixed in SystemVerilog, which introduces so many useful features, but is still not universally supported. Since Verilog is ubiquitous, I have to use it for my processor.
Verilog does have one feature that I think would make it completely useless without: parameters. It IS possible to make generic modules in Verilog that have variable inputs, outputs, or constant values. I used this in my VGA controller to allow for different VGA timings and modes. The timing is set through parameters. In core0, I must be able to have a variable amount of targets to communicate with connected through the inputs. This works fine for most purposes, but one particular thing annoyed me to no end today. I have to be able to handle interrupts from multiple targets inside the core itself, but I need to be able to get a number so I can look up the PC to set for a particular interrupt. In actual hardware this is not necessary because tristate can be used, so long as you can create a priority circuit that only allows one of the tristate buffers to be on for a given PC in the table, but this is also part of the process of getting the number as well using a priority encoder. In a normal encoder, you assume that only one input is chosen and output the number for the bit that was selected. However, a processor could be interrupted from multiple sources at once, so it is necessary to prioritize certain interrupts over others, since they cannot be handled simultaneously. The priority encoder has to do as said above and ensure only one input bit is visible to the actual encoder portion, depending on the priority scheme. For now, core0 uses a simple priority scheme: the highest bus ID always comes first. Eventually the priority might become programmable in a later version, but for now this is a sufficient scheme. I thought making a parameterized priority encoder would be trivial, but I was sure wrong! This is the diagram for a priority encoder. I ended up writing my Verilog a bit differently, using a string of OR and AND gates to compute the outputs and the output bit that specifies if any inputs are used at all. I hope that any smart synthesis software will be able to optimize this, because this was difficult enough as it is and it could be simplified. On top of all of this, I had to create an array of wires for every output OR gate so I could OR the variable amount of parameters together. I also had to compute which input bits corresponded to which output bit (check the code). You have to create enough wires to hold all of the intermediary AND and OR operations and use a generate loop. Yes, this is frustrating, but it works. I created a unit test for the encoder as well to make sure everything works properly. Who would have known it would take several hours to write 50 lines of Verilog?
| Priority encoder unit test |
Once the core is complete, I can go ahead with writing the terminal_test, which is a module that will simulate an environment with a terminal UARC core that communicates keystrokes across the bus and receives text to display on the terminal. It will also load a FORTH OS that I plan to write in assembly and add to the repository as well. Of course it will use my assembler to build the FORTH kernel. The reason I am using FORTH is to fully test the core in a programmable environment that I can write easily in assembly and possibly to play around with its capabilities before I write an LLVM backend for the core so I can compile programming languages for it.
No comments:
Post a Comment