Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
252 lines (218 sloc) 8.21 KB
// Filename: lininterp.cpp
// Project: Example Interpolators
// Purpose: This file provides the Verilator test harness for lininterp.v.
// The input is set to a sine wave, and the output values are
// written to a debugging file for subsequent octave examination.
// Creator: Dan Gisselquist, Ph.D.
// Gisselquist Technology, LLC
// Copyright (C) 2017, Gisselquist Technology, LLC
// This program is free software (firmware): you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License, or (at
// your option) any later version.
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
// You should have received a copy of the GNU General Public License along
// with this program. (It's in the $(ROOT)/doc directory. Run make with no
// target there if the PDF file isn't present.) If not, see
// <> for a copy.
// License: GPL, v3, as defined and found on,
#include <stdio.h>
#include <math.h>
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "Vlininterp.h"
int main(int argc, char **argv) {
const char *DBGFNAME = "dbgfp.32t";
Verilated::commandArgs(argc, argv);
Vlininterp tb;
// Pretend (simulate) that we're running at 100MHz
const unsigned long CLOCKRATE_HZ = 100000000;
const unsigned CLOCKRATE_NS = 10;
// We'll simulate a signal that is sampled every fourty clocks, and then
// try to upample it via our linear upsampling routine
unsigned iclocks = 40, inow = 0;
printf("Testing: lininterp.v\n"
// We'll create a binary file of 32-bit integers, dbgfp.32t, that
// we'll later load into Octave to find any buts.
FILE *dbg_fp;
dbg_fp = fopen(DBGFNAME,"w");
if (NULL == dbg_fp) {
fprintf(stderr, "ERR: Could not open the debugging output file, \"dbgfp.32t\"\n");
perror("O/S Err:");
long input_rate = CLOCKRATE_HZ/iclocks;
// Pick a chosen output rate, less than our clock rate, but
// significantly greater than our input rate. Why significantly?
// Because it makes the test results more interesting to examine
long output_rate = 82000000;
assert(output_rate < CLOCKRATE_HZ);
assert(input_rate < output_rate);
// Calculate the i_step value to go into the core.
double dstep;
dstep = (double)input_rate / (double)output_rate;
printf("IRAT = %12lu\n", input_rate);
printf("ORAT = %12lu\n",output_rate);
printf("RAWD = %.4f\n",dstep);
// Convert this less than one value into an integer an FPGA can work
// with. Specifically, we're going to multiply by 2^32 here. The
// multiplication may be a touch harder to recognize, simply because
// (1<<32) would overflow. Hence, we do a touch more work here to
// get our multiply right.
dstep = dstep * 4.0 * (1ul<<30);
// Once within the range of 0 ... 2^N-1, we can set the step
// value.
tb.i_step = (unsigned int)dstep;
printf("STEP = %08x\n", tb.i_step);
// Set Verilator up for capturing a trace of this waveform
VerilatedVcdC* tfp = new VerilatedVcdC;
tb.trace(tfp, 99);
#define TRACE_POSEDGE tfp->dump(CLOCKRATE_NS*clocks)
#define TRACE_NOEDGE tfp->dump(CLOCKRATE_NS*clocks-1)
#define TRACE_CLOSE tfp->close()
// IBITS is the number of bits in the input. It *MUST* match the value
// within lininterp.v. Here, we calculate some other helper values
// as well.
const unsigned IBITS=28,
MAXIV = ((1<<(IBITS-1))-1),
ISGNBITS = (1u<<(IBITS-1)),
ISGNMSK = (IBITS>=32) ? 0 : (0xffffffff<<IBITS);
// OBITS is the number of bits in the linear interpolators output value.
// It *MUST* also match the value of the associated parameter within
// lininterp.v. As before, we'll calculate some other helper values
// as well.
const unsigned OBITS=28,
OSGNBITS = (1u<<(OBITS-1)),
OSGNMSK = (OBITS>=32) ? 0 : (0xffffffff<<OBITS);
// MPREC is the number of precision bits in the multiply
const unsigned MPREC= 28;
// Clocks keeps track of how many clock ticks have passed since
// we started
unsigned clocks = 1;
// dphase is the phase increment of our test sinewave. It's really
// represented by a phase step, rater than a frqeuency. The phase
// step is how many radians to advance on each SYSTEM clock pulse
// (not input sample pulse). This difference just makes things
// easier to track later.
// double dphase = 1 / (double)iclocks / 260.0, dtheta = 0.0;
double dphase = 1 / (double)iclocks / 24.0, dtheta = 0.0;
printf("DPHASE = %f\n", dphase);
// We're goingto run this simulation for a minimum number of clocks.
// Since iclocks is the number of clocks required to represent one
// input sample, 16*32 specifies that we'll want to wait out 16*32
// samples. If, as specified above, there are 24 input samples per
// wavelength, a value less than 32, then this will guarantee that we
// capture at least sixteen full wavelengths of the input signal
unsigned MAXTICKS = 16*32*iclocks;
double dv, rv;
while(clocks < MAXTICKS) {
// Advance our understanding of "now"
// Also count off the number of clocks between the input
// samples
// As well as the phase of the simulated input sinewave
dtheta = dtheta + dphase;
if (dtheta > 1.0)
dtheta -= 1.0;
// Do I need to produce a new input sample to be interpolated?
if (inow >= iclocks) {
// YES!
// Calculate a new test sample via a sine wave
inow = 0;
rv = cos(2.0 * M_PI * dtheta);
// Expand it to the maximum extent of our input bits
dv = rv * (double)MAXIV;
// Convert it to an input, and send it to the core.
tb.i_data = ((int)dv)&((1ul<<IBITS)-1);
// Tell the core there's a new value waiting for it
tb.i_ce = 1;
} else
// Otherwise, there's no "new data" for the core, let
// it keep working on the last data
tb.i_ce = 0;
// Toggle the clock
// First, toggle in our changes to i_ce and i_data without
// touching the clock
tb.i_clk = 0;
// Then toggle the clock high
tb.i_clk = 1;
// And low
tb.i_clk = 0;
// If the core is producing an output, then let's examine
// what went into it, and what it's calculations were.
if (tb.o_ce) {
// We'll record six values
int vals[6];
// Capture, from the core, the values to send to
// our binary debugging file
vals[0] = tb.i_data;
vals[1] = tb.o_data;
vals[2] = tb.o_last;
vals[3] = tb.o_next;
vals[4] = tb.o_slope;
vals[5] = tb.o_offset;
// Sign extend these values first, though, by shifting
// them so their sign bit is in the high bit position,
vals[0] = (int)(vals[0] <<(32- IBITS));
vals[1] = (int)(vals[1] <<(32- OBITS));
vals[2] = (int)(vals[2] <<(32- IBITS));
vals[3] = (int)(vals[3] <<(32- IBITS));
vals[4] = (int)(vals[4] <<(32- (IBITS+1)));
// The offset value, though, is unsigned
vals[5] = vals[5] & ((1<<MPREC)-1);
// and then dropping them back down to the range they
// were in initially.
vals[0] >>= (32-IBITS);
vals[1] >>= (32-IBITS);
vals[2] >>= (32-IBITS);
vals[3] >>= (32-IBITS);
vals[4] >>= (32-(IBITS+1));
// vals[5] is already good
// Write these to the debugging file
fwrite(vals, sizeof(int), 6, dbg_fp);
// Just to prove we are doing something useful, print
// results out. These tend to be incomprehensible to
// me in general, but I like seeing them because they
// convince me that something's going on.
printf("%8.2f: %08x, %08x, (%08x, %08x)\n",
rv, tb.i_data, tb.o_data, vals[0], vals[1]);
printf("Simulation complete. Output samples placed into %s\n",