Skip to content

Instantly share code, notes, and snippets.

@davidthings
Last active November 23, 2019 11:55
Show Gist options
  • Save davidthings/32049f13ee8aa346d6724e0b0cbd9d53 to your computer and use it in GitHub Desktop.
Save davidthings/32049f13ee8aa346d6724e0b0cbd9d53 to your computer and use it in GitHub Desktop.
Statemachine-based Domain Specific Language in Verilog Speculation
/*
DSL Experiment
What if some tiny piece of programability is needed in a design?
Using even the smallest SoftCPU takes a large amount of resources.
Is there something in between a SoftCPU and doing everything in Verilog?
Can some kind of sequential programming DSL be implemented inside a module?
Modules are a good unit
- inputs
- outputs
- clock & reset
Internally, Tasks can instanciate all the functionality
A combination of macros and tasks can provide a clean base for implementing
this kind of code
The DSL part comes with the ability to write functions that call other modules.
and handle special data that is specific to the task at hand.
Advantages
Tiny
One instruction / Cycle
Zero instruction fetch cycle
Uses Verilog infrastructure (variables / expressions / modules)
Infinitely expandable instruction set (including advanced or specialized code)
Huge Bummers
Macros - a necessary evil? At least the main work is done by tasks
Line numbers - line numbers are awful, and their use in loops is worse.
Skip a line and the whole thing stalls
Questions
Is this actually idiotic for some reason?
Can macro redef help with line numbering?
Next Steps
Fixed Point types
Arrays
Vector instructions
Structures (using sub modules, maybe?)
Timers / Timeouts
Exceptions
CRC function (takes a buffer returns CRC, and another bytewise)
Text IO (parse, format)
Pipe interface (read, process, output)
Applications
character generator for LCD
various image processing algorithms
REPL (USB CDC - Verilog interface)
Debugging (capture / count values)
Compilation
To try this out:
[install icarus]
iverilog dsl_tb.v -o dsl_tb
*/
`timescale 1ns / 100ps
//
// Testbench
//
// Set up the experiment. Run the dsl_test
module dsl_tb();
reg reset;
initial begin
$dumpfile("dsl_tb.vcd");
$dumpvars( 0, dsl_tb );
end
reg clock;
initial begin
forever begin
clock = 1;
#10
clock = 0;
#10
;
end
end
task dsl_reset;
begin
reset = 1;
dsl_clock;
reset = 0;
dsl_clock;
end
endtask
task dsl_clock;
begin
#2
@( posedge clock );
// `Info( " Clock");
#2
;
end
endtask
integer cmc;
task dsl_clock_multiple( input integer count );
begin
cmc = 0;
while ( cmc < count ) begin
dsl_clock;
cmc <= cmc + 1;
end
end
endtask
//
// DSL Test
//
// Runs autonomously, interacting via port values only.
// Flags for start and finish depend on the test internals
// In this example, when start is raised, x is added to itself
// 10 times and returned in y. At the end, finished is raised.
localparam DSLn = 8;
reg [DSLn-1:0] x;
wire [DSLn-1:0] y;
reg start;
wire finished;
dsl_test #( .DSLn( DSLn ) ) test_A( clock, reset, start, finished, x, y );
//
// Testbench
//
initial begin
$display( "DSL Tests %s", `__FILE__ );
dsl_reset;
start = 0;
$display( " Start" );
dsl_clock;
dsl_clock;
// setup for the start
x = 3;
start = 1;
dsl_clock;
start = 0;
// chill until the function is done
while ( !finished )
dsl_clock;
$display( " Output %-d", y );
$display( " End" );
$finish;
end
endmodule
//
// DSL Parts
//
// These declarations will be stuffed away in a separate file and included where needed.
// There are mechanics helpers:
// dsl_start - sets up the pc, and the always @(), reset, and case statements
// dsl_stop - closes up the case statement and always
// There are variable declarations (more to come)
// dsl_int( x ) - defines the variable as an int (sized appropriately)
// Then there are various function declarations. Every line of code in the
// dsl has to be a task because the PC has to be managed too. Other things too?
// dsl_flag
// dsl_while_loop
// dsl_arithmetic
// ...
// Module declaration helpers (do these help at all?)
`define dsl_int_input( i ) input [DSLn-1:0] i
`define dsl_int_output( i ) output reg [DSLn-1:0] i
`define dsl_int_inout( i ) inout [DSLn-1:0] i
`define dsl_flag_input( i ) input i
`define dsl_flag_output( i ) output reg [0:0] i
// Variable declarations
`define dsl_int( i ) reg [DSLn-1:0] i = 0;
// Program enclosure declarations
`define dsl_start \
reg [DSLn-1:0] pc = 0; \
always @( posedge clock ) begin \
if ( reset ) begin \
pc <= 0; \
end else begin \
case ( pc )
`define dsl_end \
default: pc <= 0; \
endcase \
end \
end
// function declarations
// ... implemented as tasks
// ... do the work they're invoked for and when ready increment (or change) the PC
// ... each function macro provides one or more tasks, and any other required declarations
`define dsl_arithmetic \
task inc( inout [DSLn-1:0] v ); \
begin \
v = v + 1; /* note assign inouts? */ \
pc <= pc + 1; \
end \
endtask \
task dec( inout [DSLn-1:0] v ); \
begin \
v = v - 1; /* note assign inouts? */ \
pc <= pc + 1; \
end \
endtask
`define dsl_while_loop \
task while_loop( input reg c, input [DSLn-1:0] end_while_location ); \
begin \
if ( !c ) \
pc <= end_while_location + 1; \
else \
pc <= pc + 1; \
end \
endtask \
task end_while_loop( input [DSLn-1:0] while_loop_location ); \
begin \
pc <= while_loop_location; \
end \
endtask
`define dsl_flag \
task flag_set( output reg s ); \
begin \
s = 1; \
pc <= pc + 1; \
end \
endtask \
task flag_clear( output reg s ); \
begin \
s = 0; \
pc <= pc + 1; \
end \
endtask \
task flag_wait( input reg c ); \
begin \
if ( c ) pc <= pc + 1; \
end \
endtask
`define dsl_print \
task print( input reg [32*8:0] s, input [DSLn-1:0] v ); \
begin \
$write( "%0s%-d", s, v ); \
pc <= pc + 1; \
end \
endtask \
task println; \
begin \
$write( "\n" ); \
pc <= pc + 1; \
end \
endtask
`define dsl_load \
task load_s( inout signed [DSLn-1:0] variable, input signed [DSLn-1:0] value ); \
begin \
variable = value; /* note assign inouts? */ \
pc <= pc + 1; \
end \
endtask \
task load( inout [DSLn-1:0] variable, input [DSLn-1:0] value ); \
begin \
variable = value; /* note assign inouts? */ \
pc <= pc + 1; \
end \
endtask
`define dsl_goto \
task goto( input [DSLn-1:0] l ); \
begin \
pc <= l; \
end \
endtask
// Here's add_long, a sample of a long running function (1 cycle!)
// There's a task called by the program which manages the module (which does the work)
// The trick is not letting the PC advance until everything is done.
module dsl_add #( parameter DSLn = 8 ) ( input clock, input reset, input start, output reg [0:0] finished,
input [DSLn-1:0] x, input [DSLn-1:0] y, output [DSLn-1:0] sum );
reg [7:0] add_sum;
always @( posedge clock ) begin
if ( reset ) begin
finished <= 1;
add_sum <= 0;
end else begin
if ( start ) begin
add_sum <= x + y;
finished <= 1;
end else begin
finished <= 0;
end
end
end
assign sum = add_sum;
endmodule
`define dsl_add_long \
reg add_start = 0; \
wire add_finished; \
reg [7:0] add_x = 0; \
reg [7:0] add_y = 0; \
wire [7:0] add_sum; \
dsl_add add_function( clock, reset, add_start, add_finished, add_x, add_y, add_sum ); \
task add_long( input [7:0] ax, input [7:0] ay, output [7:0] asum ); \
begin \
if ( !add_start ) begin\
add_x <= ax; \
add_y <= ay; \
add_start <= 1; \
end else begin \
if ( add_finished ) begin \
asum = add_sum; \
add_start <= 0; \
pc <= pc + 1; \
end \
end \
end \
endtask
//
// DSL Test
//
// Declare the DSL code here. Note the (defaulted) word size.
// Note also the dsl in / out / inout helpers (which just make the
// variables the right size)
module dsl_test #( parameter DSLn = 8 )
( input clock, input reset,
`dsl_flag_input( start ), `dsl_flag_output( finished ),
`dsl_int_input( x ), `dsl_int_output( y ) );
// Build up the DSL functionality
`dsl_flag
`dsl_arithmetic
`dsl_while_loop
`dsl_load
`dsl_print
`dsl_goto
`dsl_add_long
// Declare local variables
`dsl_int( i )
// Present the actual code
`dsl_start
0: flag_clear( finished );
1: flag_wait( start );
2: load( i, 10 );
3: load( y, 0 );
4: while_loop( i > 0, 11 ); // need the line number of the end while... )-:,
5: add_long( x, y, y ); // could also have done load( y, x + y ), but need to use add_long
6: print( " i:", i ); // varargs for tasks would help
7: print( " x:", x );
8: print( " y:", y );
9: println();
10: dec( i );
11: end_while_loop( 4 ); // need the line number of the while... )-:,
12: flag_set( finished );
13: goto( 0 );
`dsl_end
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment