Last active
November 23, 2019 11:55
-
-
Save davidthings/32049f13ee8aa346d6724e0b0cbd9d53 to your computer and use it in GitHub Desktop.
Statemachine-based Domain Specific Language in Verilog Speculation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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