There's an iCE40HX1K board in my drawer that was unused for about three years now. I decided to just dive in and "do something". First project: blinky of course. I'm using the icestorm toolchain.
After some discussions in the hack/fpga chats I was able to come up with a PWM module that does what I wanted to do - in a simulation.
pwm_dn.v (the sync reset is subject to change)
module pwm #(
parameter bits = 4
)(
input clkin,
input reset,
input[bits-1:0] max,
input[bits-1:0] compare,
output out
);
reg[bits-1:0] count = 0;
reg[bits-1:0] compareLatched = 0;
always @(posedge clkin)
begin
if(reset == 1)
begin
count <= 0;
compareLatched <= 0;
end
else
begin
if(count == 0)
begin
count <= max-1;
compareLatched <= compare;
end
else
count <= count-1;
end
end
assign out = count < compareLatched;
endmodule
Regarding reset: there seems to be a lot of discussion about sync vs. async reset, and I haven't made up my mind about what I'd actually like. One thing that is pointed out regularly is that some FPGA prefer one over the other, behavior-wise. I don't know what the case is for the iCE40 series.
@Frank Buss pointed out this topic on stack exchange, which includes further reading:
Then there's the assignment of out, which is not a register in the above code. A variant that was discussed is to assign the comparison result to a wire, and update that in the always block. This would make sure that it's always updated on a rising clock edge. However, it would also be delayed by one clock. All that probably doesn't matter for PWM which would usually be connected to some external device anyway.
I found a great makefile in the icestorm examples. It can do synthesis, place and route, packing, run a simulation (pre- and post-synthesis) and program your board. Since users and hardware are different, it might need some adjustments here and there. The original file is here: https://github.com/cliffordwolf/icestorm/blob/master/examples/icestick/Makefile
It's actually pretty simple and saves a ton of work on the command line - just like it's supposed to.
I decided to take my simple clock divider code to see what it actually does (because I really don't remember exactly): write a test bench, run a simulation and look at the results in gtkwave.
Again, the clockdivider.v code I used earlier (and we'll see why it might be bad code):
The first one will use the %.v and %_tb.v files (where % is "clockdivider") to build the testbench executable (clockdivider_tb), using iverilog. This executable is then run by vvp using the second rule. This second rule is the one we triggered on the command line. I'm not sure what vvp actually *does*, because simply running the testbench executable will also generate the desired vcd file (unless it causes subtle changes that I'm not able to spot).
The testbench dumps values in dump.vcd, which can be viewed using gtkwave. The result:
The signals can be dragged into the waves window for inspection. The first pulse on clkout was a bit unexpected, but that's probably because we don't have a proper reset for the device. Anyway, it seems to divide the clock by 4, which is what I wanted to achieve. There's probably a lot of room for improvement, but I'm a mechanical engineer - cut me some slack.
So with the first blinky test behind me, I wanted to get away from the fixed 12 MHz external oscillator. The HX1K has a PLL for that! For someone with a background in mechanical engineering, the act of creating a faster clock than we originally had is somewhat mind-blowing, so let's go for 24 MHz.
Instead of making the mistake of reading the datasheet and then failing to make sense of it, let's use icepll to create code for the PLL:
icepll -i 12 -o 24 -f pll.v -m
here's the output, which also kinda describes what the command line requests:
Double blinky indeed! Two LEDs are now blinking at two different frequencies, and they're so bright that I unplugged the board again. I'll need some sort of PWM to dim that. An obvious choice for the next experiment.
After installing the icestorm tools, arachne-pnr, nextpnr and yosys (which all worked out of the box, apart from some more packages to download), I modified some very old blinky verilog code that was originally written for #DIPSY to work with the board at hand. It was mainly just a bunch of pin assignment changes and using a different clock source.
This creates a down-counter with given bit count and reload value. When it reaches zero, it reloads the internal counter with the reload value and starts over. I have no idea if this is good code, or if it's even off by one or whatever. But it seems to do the job.
Next is the top-level module that takes an input clock, feeds it into a clockdivider (see above) and outputs the divided clock to an output:
top.v
module top (
output pin_led,
input clk_12m
);
reg led = 0;
SB_IO #( // IO IP instance
.PIN_TYPE(6'b011001) // configure as output
) pin_out_driver (
.PACKAGE_PIN(pin_led), // connect to this pin
.D_OUT_0(led) // output the state of "led"
);
wire clkout;
clockdivider #(.bits(28)) div(
.clkin(clk_12m),
.clkout(clkout),
.div(28'd12000000)
);
always @(posedge clkout)
led = ~led;
endmodule
Again, no quality claims here. But - again - it does the job!
My board has a 12 MHz oscillator and an LED connected like so:
top.pcf
set_io clk_12m 21
set_io pin_led 96
With icestorm, synthesis, placie&route and "packing" (I don't know what the correct technical term is) are three command lines:
Now there's a bin file to be downloaded to the FPGA's config memory. I couldn't figure out how to program my board's flash chip, so this is for SRAM config: